hbfloaterthumbnail.cpp 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280
  1. /**
  2. * @file hbfloaterthumbnail.cpp
  3. * @author Henri Beauchamp
  4. * @brief HBFloaterThumbnail class implementation
  5. *
  6. * $LicenseInfo:firstyear=2023&license=viewergpl$
  7. *
  8. * Copyright (c) 2023-2024, Henri Beauchamp.
  9. *
  10. * Second Life Viewer Source Code
  11. * The source code in this file ("Source Code") is provided by Linden Lab
  12. * to you under the terms of the GNU General Public License, version 2.0
  13. * ("GPL"), unless you have obtained a separate licensing agreement
  14. * ("Other License"), formally executed by you and Linden Lab. Terms of
  15. * the GPL can be found in doc/GPL-license.txt in this distribution, or
  16. * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  17. *
  18. * There are special exceptions to the terms and conditions of the GPL as
  19. * it is applied to this Source Code. View the full text of the exception
  20. * in the file doc/FLOSS-exception.txt in this software distribution, or
  21. * online at
  22. * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  23. *
  24. * By copying, modifying or distributing this software, you acknowledge
  25. * that you have read and understood your obligations described above,
  26. * and agree to abide by those obligations.
  27. *
  28. * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  29. * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  30. * COMPLETENESS OR PERFORMANCE.
  31. * $/LicenseInfo$
  32. */
  33. #include "llviewerprecompiledheaders.h"
  34. #include "hbfloaterthumbnail.h"
  35. #include "llbutton.h"
  36. #include "llcombobox.h"
  37. #include "llcorehttputil.h"
  38. #include "lldir.h"
  39. #include "hbfileselector.h"
  40. #include "llfontgl.h"
  41. #include "lliconctrl.h"
  42. #include "llimagej2c.h"
  43. #include "llnotifications.h"
  44. #include "llrenderutils.h"
  45. #include "llscrolllistctrl.h"
  46. #include "lltextbox.h"
  47. #include "lltrans.h"
  48. #include "lluictrlfactory.h"
  49. #include "llagent.h"
  50. #include "llappviewer.h" // For gDisconnected & LLApp::isExiting()
  51. #include "llfloaterimagepreview.h"
  52. #include "hbfloaterinvitemspicker.h"
  53. #include "llfloatersnapshot.h"
  54. #include "llgltfmateriallist.h"
  55. #include "llgltfmaterialpreview.h"
  56. #include "hbinventoryclipboard.h"
  57. #include "llinventoryicon.h"
  58. #include "llinventorymodel.h"
  59. #include "llpipeline.h" // For LLPipeline::sReflectionProbesEnabled
  60. #include "llviewercontrol.h"
  61. #include "llviewerinventory.h"
  62. #include "llviewerobjectlist.h"
  63. #include "llviewertexturelist.h"
  64. #include "llvoinventorylistener.h"
  65. constexpr LLInventoryType::EType TEXTYPE = LLInventoryType::IT_TEXTURE;
  66. static LLTimer sAutoCloseTimer;
  67. // Helper functions
  68. static bool validate_item_permissions(const LLViewerInventoryItem* itemp)
  69. {
  70. const LLPermissions& perms = itemp->getPermissions();
  71. return perms.allowCopyBy(gAgentID) &&
  72. perms.allowTransferBy(gAgentID);
  73. }
  74. static bool validate_asset_perms(const LLUUID& asset_id)
  75. {
  76. LLViewerInventoryCategory::cat_array_t cats;
  77. LLViewerInventoryItem::item_array_t items;
  78. LLAssetIDMatches asset_id_matches(asset_id);
  79. gInventory.collectDescendentsIf(LLUUID::null, cats, items,
  80. LLInventoryModel::INCLUDE_TRASH,
  81. asset_id_matches);
  82. if (items.empty())
  83. {
  84. // No inventory item bears any such asset, so it is most likely another
  85. // thumbnail Id, and thus allowed to copy/transfer already.
  86. return true;
  87. }
  88. for (U32 i = 0, count = items.size(); i < count; ++i)
  89. {
  90. if (items[i] && validate_item_permissions(items[i]))
  91. {
  92. return true;
  93. }
  94. }
  95. return false;
  96. }
  97. //-----------------------------------------------------------------------------
  98. // HBThumbnailDropTarget class - UI element for texture drop targets. It also
  99. // handles automatically click-selection via the inventory items picker.
  100. // Finally, it acts as an agent's inventory or object's inventory observer for
  101. // its owner (this avoids having to #include the corresponding headers from the
  102. // hbfloaterthumbnail.h header).
  103. //-----------------------------------------------------------------------------
  104. class HBThumbnailDropTarget final : public LLView, public LLInventoryObserver,
  105. public LLVOInventoryListener
  106. {
  107. protected:
  108. LOG_CLASS(HBThumbnailDropTarget);
  109. public:
  110. HBThumbnailDropTarget(HBFloaterThumbnail* ownerp, LLView* parentp)
  111. : LLView(parentp->getName() + "_drop", false), // Not mouse-opaque
  112. mParentFloater(ownerp),
  113. mObserveAgentInventory(false),
  114. mObserveObjectInventory(false)
  115. {
  116. setFollows(FOLLOWS_LEFT | FOLLOWS_TOP);
  117. // Set our rect to the parent view (usually a view border) rect.
  118. LLRect rect = parentp->getRect();
  119. setRect(rect);
  120. // Create a text box associated with our drop target view (we will not
  121. // use this as an actual text box, but this allows to set a clicked
  122. // callback for it, that a simple LLView would not have).
  123. LLTextBox* textp = new LLTextBox(parentp->getName() + "_click", rect,
  124. "", LLFontGL::getFontSansSerif(),
  125. true); // Opaque text box
  126. // Add as a child of our owner floater
  127. ownerp->addChild(textp);
  128. // Add ourselves as a child of the floater: this must be done *after*
  129. // the text box was added, so that the drop target view is on top (note
  130. // that it is however not opaque to mouse: tool tip hovers and clicks
  131. // do get to the underlying text box).
  132. ownerp->addChild(this);
  133. // Add an adequate tool tip
  134. textp->setToolTip(ownerp->getString("thumbnail_tool_tip"));
  135. // Setup click-action on the text of the drop target (inventory picker
  136. // or edit thumbmail floater opening)
  137. textp->setClickedCallback(onTextClicked, this);
  138. // Observe inventory changes on behalf of our owner.
  139. updateObservers();
  140. }
  141. void updateObservers(bool remove_only = false)
  142. {
  143. // Remove any old observer
  144. if (mObserveAgentInventory)
  145. {
  146. gInventory.removeObserver(this);
  147. mObserveAgentInventory = false;
  148. }
  149. if (mObserveObjectInventory)
  150. {
  151. removeVOInventoryListeners();
  152. mObserveObjectInventory = false;
  153. }
  154. if (!remove_only)
  155. {
  156. // Add an appropriate observer for the new item
  157. if (mParentFloater->mTaskId.isNull())
  158. {
  159. mObserveAgentInventory = true;
  160. gInventory.addObserver(this);
  161. }
  162. else
  163. {
  164. LLViewerObject* objectp =
  165. gObjectList.findObject(mParentFloater->mTaskId);
  166. if (objectp)
  167. {
  168. mObserveObjectInventory = true;
  169. registerVOInventoryListener(objectp, NULL);
  170. }
  171. }
  172. }
  173. }
  174. ~HBThumbnailDropTarget() override
  175. {
  176. updateObservers(true); // Remove observers
  177. }
  178. bool handleDragAndDrop(S32 x, S32 y, MASK, bool drop,
  179. EDragAndDropType cargo_type,
  180. void* cargo_data, EAcceptance* accept,
  181. std::string&) override
  182. {
  183. // Careful: pointInView() gets f*cked up whenever the panel is embedded
  184. // inside a layout stack.
  185. if (mParentFloater->mOwner || !getEnabled() || !pointInView(x, y))
  186. {
  187. return false;
  188. }
  189. *accept = ACCEPT_NO;
  190. if (cargo_type == DAD_TEXTURE)
  191. {
  192. LLViewerInventoryItem* itemp = (LLViewerInventoryItem*)cargo_data;
  193. if (itemp && gInventory.getItem(itemp->getUUID()) &&
  194. validate_item_permissions(itemp))
  195. {
  196. *accept = ACCEPT_YES_COPY_SINGLE;
  197. if (drop)
  198. {
  199. // Inform our owner about the user choice
  200. mParentFloater->onChoosenTexture(itemp, true);
  201. }
  202. }
  203. }
  204. return true;
  205. }
  206. // LLInventoryObserver override
  207. void changed(U32 mask) override
  208. {
  209. constexpr U32 WATCHED_CHANGES = LABEL | INTERNAL | REMOVE;
  210. if (mask & WATCHED_CHANGES)
  211. {
  212. // Passing a null UUID causes a simple refresh.
  213. mParentFloater->setInventoryObjectId(LLUUID::null);
  214. }
  215. }
  216. // LLVOInventoryListener override
  217. void inventoryChanged(LLViewerObject*, LLInventoryObject::object_list_t*,
  218. S32, void*) override
  219. {
  220. // Passing a null UUID causes a simple refresh.
  221. mParentFloater->setInventoryObjectId(LLUUID::null);
  222. }
  223. private:
  224. static void onTextClicked(void* userdata)
  225. {
  226. HBThumbnailDropTarget* self = (HBThumbnailDropTarget*)userdata;
  227. if (!self || !self->getEnabled())
  228. {
  229. return;
  230. }
  231. HBFloaterThumbnail* floaterp = self->mParentFloater;
  232. if (floaterp->mOwner)
  233. {
  234. const LLUUID& item_id = floaterp->mInventoryObjectId;
  235. if (item_id.notNull())
  236. {
  237. // Show a thumbnail edit floater for our viewed item.
  238. HBFloaterThumbnail::showInstance(item_id, floaterp->mTaskId);
  239. }
  240. // Flag our parent floater for closing (do not close it ourselves,
  241. // since this could cause the clicked callback or focus underlying
  242. // code to possibly use destroyed UI elements pointers).
  243. floaterp->mMustClose = true;
  244. return;
  245. }
  246. HBFloaterInvItemsPicker* pickerp =
  247. new HBFloaterInvItemsPicker(self, invItemsPickerCallback, self);
  248. // We want an empty selection callback on picker closing by any other
  249. // mean than the "Select" button.
  250. pickerp->callBackOnClose();
  251. if (pickerp)
  252. {
  253. pickerp->setAssetType(LLAssetType::AT_TEXTURE);
  254. }
  255. pickerp->setApplyImmediatelyControl("ApplyThumbnailImmediately");
  256. // Thumbnails must be at least copy OK and transfer OK.
  257. pickerp->setFilterPermMask(PERM_COPY | PERM_TRANSFER);
  258. static LLCachedControl<bool> auto_pick(gSavedSettings,
  259. "ThumbnailAutoPickTexture");
  260. if (!auto_pick || floaterp->mTaskId.notNull())
  261. {
  262. return;
  263. }
  264. // Search for a texture with the right permissions in the folder (or
  265. // parent folder for an item) we want to set the thumbnail for, and
  266. // select it by default. The rationale is that if a texture exists at
  267. // this level it is likely representative of the thumbnail we want for
  268. // this folder or item...
  269. LLInventoryObject* invobjp = floaterp->getInventoryObject();
  270. if (!invobjp) // Paranoia
  271. {
  272. return;
  273. }
  274. const LLUUID& cat_id =
  275. floaterp->mIsCategory ? invobjp->getUUID()
  276. : invobjp->getParentUUID();
  277. // First, search among direct descendents...
  278. LLInventoryModel::cat_array_t* cats;
  279. LLInventoryModel::item_array_t* items;
  280. gInventory.getDirectDescendentsOf(cat_id, cats, items);
  281. if (!items) // Failed to collect descendents !
  282. {
  283. return;
  284. }
  285. for (LLInventoryModel::item_array_t::iterator it = items->begin(),
  286. end = items->end();
  287. it != end; ++it)
  288. {
  289. LLViewerInventoryItem* itemp = *it;
  290. if (itemp && itemp->getType() == LLAssetType::AT_TEXTURE &&
  291. validate_item_permissions(itemp))
  292. {
  293. // Select this texture by default.
  294. pickerp->setSelection(itemp->getUUID());
  295. return;
  296. }
  297. }
  298. // Finally, search deeper down in the whole folder tree (there is no
  299. // set order on the returned items, thus why we searched for direct
  300. // descendents first)...
  301. LLInventoryModel::cat_array_t all_cats;
  302. LLInventoryModel::item_array_t all_items;
  303. gInventory.collectDescendents(cat_id, all_cats, all_items, false);
  304. for (LLInventoryModel::item_array_t::iterator it = all_items.begin(),
  305. end = all_items.end();
  306. it != end; ++it)
  307. {
  308. LLViewerInventoryItem* itemp = *it;
  309. if (itemp && itemp->getType() == LLAssetType::AT_TEXTURE &&
  310. validate_item_permissions(itemp))
  311. {
  312. // Select this texture by default.
  313. pickerp->setSelection(itemp->getUUID());
  314. return;
  315. }
  316. }
  317. }
  318. static void invItemsPickerCallback(const std::vector<std::string>&,
  319. const uuid_vec_t& ids, void* userdata,
  320. bool on_close)
  321. {
  322. HBThumbnailDropTarget* self = (HBThumbnailDropTarget*)userdata;
  323. if (!self) // Paranoia
  324. {
  325. return;
  326. }
  327. // Empty ids happen on close by any other mean than "Select".
  328. if (ids.empty())
  329. {
  330. if (on_close)
  331. {
  332. self->mParentFloater->onChoosenTexture(NULL, true);
  333. }
  334. return;
  335. }
  336. LLUUID inv_id = ids[0];
  337. // Make sure we are not trying to use a link and get the linked item
  338. // Id in that case.
  339. if (inv_id.notNull())
  340. {
  341. inv_id = gInventory.getLinkedItemID(inv_id);
  342. }
  343. LLViewerInventoryItem* itemp = gInventory.getItem(inv_id);
  344. if (itemp)
  345. {
  346. // Inform our owner about the user choice
  347. self->mParentFloater->onChoosenTexture(itemp, on_close);
  348. }
  349. }
  350. private:
  351. HBFloaterThumbnail* mParentFloater;
  352. bool mObserveAgentInventory;
  353. bool mObserveObjectInventory;
  354. };
  355. ///////////////////////////////////////////////////////////////////////////////
  356. // HBFloaterThumbnail class proper
  357. ///////////////////////////////////////////////////////////////////////////////
  358. //static
  359. HBFloaterThumbnail::instances_map_t HBFloaterThumbnail::sInstances;
  360. //static
  361. HBFloaterThumbnail* HBFloaterThumbnail::findInstance(const LLUUID& id)
  362. {
  363. instances_map_t::iterator it = sInstances.find(id);
  364. return it != sInstances.end() ? it->second : NULL;
  365. }
  366. //static
  367. void HBFloaterThumbnail::showInstance(const LLUUID& inv_obj_id,
  368. const LLUUID& task_id, LLView* ownerp)
  369. {
  370. // Search for our owner's parent floater.
  371. LLFloater* parentp = ownerp ? ownerp->getParentFloater() : NULL;
  372. if (parentp)
  373. {
  374. sAutoCloseTimer.reset();
  375. }
  376. HBFloaterThumbnail* floaterp = findInstance(inv_obj_id ^ task_id);
  377. if (floaterp) // A floater for this inventory object exists already.
  378. {
  379. if (!floaterp->getVisible())
  380. {
  381. floaterp->open();
  382. }
  383. // If it is an edit floater with matching item, we can close the
  384. // temporary view floater when it exists.
  385. if (parentp && !floaterp->mOwner)
  386. {
  387. // The null UUID is used for the unique and shared, temporary view
  388. // floater.
  389. floaterp = findInstance(LLUUID::null);
  390. if (floaterp)
  391. {
  392. floaterp->close();
  393. }
  394. }
  395. return;
  396. }
  397. if (parentp) // Look for an existing thumbnail shared view floater
  398. {
  399. // The null UUID is used for the unique and shared, temporary view
  400. // floater.
  401. floaterp = findInstance(LLUUID::null);
  402. // If this floater exists and does not belong to our new owner's parent
  403. // floater, then close it.
  404. if (floaterp && floaterp->mOwner != parentp)
  405. {
  406. floaterp->close();
  407. floaterp = NULL;
  408. }
  409. }
  410. if (floaterp)
  411. {
  412. // Set the new inventory item for this shared, unique floater. Note
  413. // that this call may actually close the said floater, when there is
  414. // no thumbnail associated with this item.
  415. if (parentp)
  416. {
  417. floaterp->setInventoryObjectId(inv_obj_id);
  418. floaterp->mTaskId = task_id;
  419. // We need this in case the view floater switched from an agent's
  420. // inventory item to the item of an object's inventory, or vice
  421. // versa (observers need updating).
  422. floaterp->updateDropTarget();
  423. }
  424. }
  425. else
  426. {
  427. new HBFloaterThumbnail(inv_obj_id, task_id, parentp);
  428. if (!parentp)
  429. {
  430. // Check that the temporary floater is not open for this same item
  431. // and when it is, close it.
  432. floaterp = findInstance(LLUUID::null);
  433. if (floaterp && floaterp->mInventoryObjectId == inv_obj_id &&
  434. floaterp->mTaskId == task_id)
  435. {
  436. floaterp->close();
  437. }
  438. }
  439. }
  440. }
  441. //static
  442. void HBFloaterThumbnail::hideInstance(const LLUUID& id)
  443. {
  444. HBFloaterThumbnail* self = findInstance(id);
  445. if (self &&
  446. (self->mOwner ||
  447. // Do not close an edit floater with unsaved changes.
  448. self->mThumbnailId == self->mInitialThumbnailId))
  449. {
  450. self->close();
  451. }
  452. }
  453. HBFloaterThumbnail::HBFloaterThumbnail(const LLUUID& inv_obj_id,
  454. const LLUUID& task_id,
  455. LLFloater* ownerp)
  456. : mOwner(ownerp),
  457. mPasteThumbnail(NULL),
  458. mCopyThumbnail(NULL),
  459. mClearThumbnail(NULL),
  460. mUndoThumbnail(NULL),
  461. mCancelButton(NULL),
  462. mTaskId(task_id),
  463. mMustClose(false),
  464. mIsCategory(false),
  465. mIsMaterialPreview(false)
  466. {
  467. // Note: the only floater with an owner is the preview one.
  468. std::string xml_file = ownerp ? "floater_thumbnail_view.xml"
  469. : "floater_thumbnail.xml";
  470. LLUICtrlFactory::getInstance()->buildFloater(this, xml_file, NULL,
  471. !mOwner);
  472. if (ownerp)
  473. {
  474. setIsChrome(true);
  475. setSoundFlags(SILENT);
  476. ownerp->addDependentFloater(this);
  477. }
  478. setInventoryObjectId(inv_obj_id);
  479. }
  480. //virtual
  481. HBFloaterThumbnail::~HBFloaterThumbnail()
  482. {
  483. unregister();
  484. }
  485. void HBFloaterThumbnail::unregister()
  486. {
  487. // There shall be exactly one entry for each floater registered in the map.
  488. // Always use setInventoryObjectId() when changing the associated inventory
  489. // object !
  490. for (instances_map_t::iterator it = sInstances.begin(),
  491. end = sInstances.end();
  492. it != end; ++it)
  493. {
  494. if (it->second == this)
  495. {
  496. sInstances.erase(it);
  497. break;
  498. }
  499. }
  500. }
  501. //virtual
  502. bool HBFloaterThumbnail::postBuild()
  503. {
  504. mIcon = getChild<LLIconCtrl>("icon");
  505. mInventoryObjectName = getChild<LLTextBox>("item_name");
  506. mDropTarget = new HBThumbnailDropTarget(this,
  507. getChild<LLView>("thumbnail"));
  508. mThumbnailRect = mDropTarget->getRect();
  509. // Adjust to keep the view border showing while we will draw the thumbnail
  510. // inside it.
  511. ++mThumbnailRect.mBottom;
  512. --mThumbnailRect.mTop;
  513. ++mThumbnailRect.mLeft;
  514. --mThumbnailRect.mRight;
  515. if (mOwner)
  516. {
  517. return true;
  518. }
  519. LLFlyoutButton* change_buttonp = getChild<LLFlyoutButton>("change");
  520. change_buttonp->setCommitCallback(onBtnChange);
  521. change_buttonp->setCallbackUserData(this);
  522. std::string operation;
  523. for (U32 i = 0, count = change_buttonp->getItemCount(); i < count; ++i)
  524. {
  525. LLScrollListItem* itemp = change_buttonp->getItemByIndex(i);
  526. operation = itemp->getValue().asString();
  527. if (operation == "copy")
  528. {
  529. mCopyThumbnail = itemp;
  530. }
  531. else if (operation == "paste")
  532. {
  533. mPasteThumbnail = itemp;
  534. }
  535. else if (operation == "clear")
  536. {
  537. mClearThumbnail = itemp;
  538. }
  539. else if (operation == "undo")
  540. {
  541. mUndoThumbnail = itemp;
  542. }
  543. }
  544. mCancelButton = getChild<LLButton>("cancel_btn");
  545. mCancelButton->setClickedCallback(onBtnCancel, this);
  546. childSetAction("ok_btn", onBtnClose, this);
  547. return true;
  548. }
  549. //virtual
  550. void HBFloaterThumbnail::draw()
  551. {
  552. static LLCachedControl<U32> timeout(gSavedSettings,
  553. "ThumbnailViewTimeout");
  554. if (mMustClose ||
  555. (mOwner && timeout &&
  556. sAutoCloseTimer.getElapsedTimeF32() > F32(timeout)))
  557. {
  558. close();
  559. return;
  560. }
  561. if (mCancelButton)
  562. {
  563. mCancelButton->setEnabled(mThumbnailId != mInitialThumbnailId);
  564. }
  565. if (mPasteThumbnail)
  566. {
  567. mPasteThumbnail->setEnabled(HBInventoryClipboard::hasAssets(TEXTYPE));
  568. }
  569. // Draw all UI elements before we would draw the texture.
  570. LLFloater::draw();
  571. if (isMinimized())
  572. {
  573. return; // No need to draw the texture.
  574. }
  575. bool has_texture = mTexturep.notNull();
  576. bool has_preview = !has_texture && mMatTexturep.notNull();
  577. if (!has_texture && !has_preview && mMaterialp.isNull())
  578. {
  579. // No texture, draw a grey square...
  580. gl_rect_2d(mThumbnailRect, LLColor4::grey);
  581. // ... with a black X.
  582. gl_draw_x(mThumbnailRect, LLColor4::black);
  583. return;
  584. }
  585. F32 width = mThumbnailRect.getWidth();
  586. F32 height = mThumbnailRect.getHeight();
  587. if (has_texture)
  588. {
  589. // Update the texture the priority
  590. mTexturep->addTextureStats(width * height);
  591. }
  592. F32 left = mThumbnailRect.mLeft;
  593. F32 bottom = mThumbnailRect.mBottom;
  594. F32 tex_width = 0.f;
  595. F32 tex_height = 0.f;
  596. if (has_texture)
  597. {
  598. tex_width = mTexturep->getFullWidth();
  599. tex_height = mTexturep->getFullHeight();
  600. }
  601. else if (has_preview)
  602. {
  603. tex_width = mMatTexturep->getFullWidth();
  604. tex_height = mMatTexturep->getFullHeight();
  605. }
  606. if (tex_width && tex_height && tex_width != tex_height)
  607. {
  608. // If necessary, compute the offset in the display, to draw the texture
  609. // with its native aspect ratio.
  610. F32 proportion = tex_height / tex_width;
  611. if (proportion > 1.f)
  612. {
  613. left += (width - width / proportion) * 0.5f;
  614. width /= proportion;
  615. }
  616. else
  617. {
  618. bottom += (height - height * proportion) * 0.5f;
  619. height *= proportion;
  620. }
  621. }
  622. // If one of the dimensions of the image is smaller than the display,
  623. // center it.
  624. if (tex_width && tex_height && (tex_width < width || tex_height < height))
  625. {
  626. if (tex_width < width)
  627. {
  628. left += (width - tex_width) * .5f;
  629. width = tex_width;
  630. }
  631. if (tex_height < height)
  632. {
  633. bottom += (height - tex_height) * .5f;
  634. height = tex_height;
  635. }
  636. }
  637. if (has_texture)
  638. {
  639. gl_draw_scaled_image(left, bottom, width, height, mTexturep);
  640. }
  641. else if (has_preview)
  642. {
  643. gl_draw_scaled_image(left, bottom, width, height, mMatTexturep);
  644. }
  645. else
  646. {
  647. // Preview not ready, draw a grey square...
  648. gl_rect_2d(mThumbnailRect, LLColor4::grey);
  649. }
  650. if ((has_texture && !mTexturep->isFullyLoaded()) ||
  651. (!has_texture && !has_preview))
  652. {
  653. if (mOwner && timeout)
  654. {
  655. sAutoCloseTimer.reset();
  656. }
  657. // Show "Loading..." string on the bottom left corner while the texture
  658. // is loading.
  659. static LLFontGL* fontp = LLFontGL::getFontSansSerif();
  660. static LLWString loading = LLTrans::getWString("texture_loading");
  661. fontp->render(loading, 0, mThumbnailRect.mLeft + 8,
  662. mThumbnailRect.mBottom + 6, LLColor4::white,
  663. LLFontGL::LEFT, LLFontGL::BASELINE,
  664. LLFontGL::DROP_SHADOW);
  665. }
  666. // If we are waiting for the preview texture, retry to get it now.
  667. if (!has_texture && !has_preview && mMaterialp.notNull())
  668. {
  669. mMatTexturep = LLGLTFPreviewTexture::getPreview(mMaterialp);
  670. }
  671. }
  672. void HBFloaterThumbnail::updateDropTarget()
  673. {
  674. if (mDropTarget)
  675. {
  676. mDropTarget->updateObservers();
  677. }
  678. }
  679. LLInventoryObject* HBFloaterThumbnail::getInventoryObject()
  680. {
  681. mIsCategory = false;
  682. LLInventoryObject* invobjp = NULL;
  683. if (mInventoryObjectId.notNull())
  684. {
  685. if (mTaskId.notNull())
  686. {
  687. LLViewerObject* objectp = gObjectList.findObject(mTaskId);
  688. if (objectp)
  689. {
  690. invobjp = objectp->getInventoryObject(mInventoryObjectId);
  691. }
  692. }
  693. else
  694. {
  695. invobjp = gInventory.getCategory(mInventoryObjectId);
  696. if (invobjp)
  697. {
  698. mIsCategory = true;
  699. }
  700. else
  701. {
  702. invobjp = gInventory.getItem(mInventoryObjectId);
  703. }
  704. }
  705. }
  706. return invobjp;
  707. }
  708. void HBFloaterThumbnail::setThumbTexture()
  709. {
  710. mTexturep = NULL;
  711. mMatTexturep = NULL;
  712. const LLUUID& id = mTempThumbId.notNull() ? mTempThumbId : mThumbnailId;
  713. if (id.notNull())
  714. {
  715. if (mIsMaterialPreview && id == mInitialThumbnailId)
  716. {
  717. mMaterialp = gGLTFMaterialList.getMaterial(id);
  718. mMatTexturep = LLGLTFPreviewTexture::getPreview(mMaterialp);
  719. }
  720. else
  721. {
  722. mMaterialp = NULL;
  723. mTexturep =
  724. LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT,
  725. true,
  726. LLGLTexture::BOOST_PREVIEW);
  727. }
  728. }
  729. bool has_texture = mTexturep.notNull();
  730. if (mCopyThumbnail)
  731. {
  732. mCopyThumbnail->setEnabled(has_texture && validate_asset_perms(id));
  733. }
  734. if (mClearThumbnail)
  735. {
  736. mClearThumbnail->setEnabled(has_texture);
  737. }
  738. if (mUndoThumbnail)
  739. {
  740. mUndoThumbnail->setEnabled(mThumbnailId != mInitialThumbnailId);
  741. }
  742. }
  743. void HBFloaterThumbnail::setInventoryObjectId(const LLUUID& inv_obj_id)
  744. {
  745. // A null UUID is passed by the inventory observer when we only need a
  746. // refresh for the currently associated inventory object.
  747. if (inv_obj_id.notNull())
  748. {
  749. mInventoryObjectId = inv_obj_id;
  750. unregister();
  751. sInstances.emplace(mOwner ? LLUUID::null : inv_obj_id ^ mTaskId, this);
  752. }
  753. LLInventoryObject* invobjp = getInventoryObject();
  754. if (!invobjp)
  755. {
  756. close(); // No associated inventory object, so just commit suicide.
  757. return;
  758. }
  759. mIsMaterialPreview = false;
  760. LLUUID thumb_id = invobjp->getThumbnailUUID();
  761. if (thumb_id.isNull() && gUsePBRShaders &&
  762. // Note: for now, material preview does not render at all when
  763. // reflection probes are disabled. *TODO: find out why and fix it.
  764. LLPipeline::sReflectionProbesEnabled)
  765. {
  766. bool is_mat = invobjp->getType() == LLAssetType::AT_MATERIAL;
  767. if (is_mat)
  768. {
  769. thumb_id = ((LLInventoryItem*)invobjp)->getAssetUUID();
  770. mIsMaterialPreview = true;
  771. }
  772. }
  773. if (mOwner)
  774. {
  775. if (thumb_id.isNull())
  776. {
  777. // Nothing to display, close the temporary floater.
  778. close();
  779. return;
  780. }
  781. // If there is indeed something to display, we can open the temporary
  782. // floater.
  783. if (!getVisible())
  784. {
  785. open();
  786. }
  787. }
  788. if (thumb_id != mInitialThumbnailId) // May not have changed on refresh.
  789. {
  790. mThumbnailId = mInitialThumbnailId = thumb_id;
  791. setThumbTexture();
  792. }
  793. else if (mCopyThumbnail)
  794. {
  795. mCopyThumbnail->setEnabled(mThumbnailId.notNull() &&
  796. validate_asset_perms(mThumbnailId));
  797. }
  798. mInventoryObjectName->setText(invobjp->getName());
  799. // Set the corresponding inventory icon.
  800. LLInventoryItem* itemp = invobjp->asInventoryItem();
  801. if (itemp)
  802. {
  803. mIcon->setValue(LLInventoryIcon::getIconName(itemp->getType(),
  804. itemp->getInventoryType(),
  805. itemp->getFlags()));
  806. }
  807. else
  808. {
  809. static const std::string folder_icon = "inv_folder_plain_closed.tga";
  810. mIcon->setValue(folder_icon);
  811. }
  812. }
  813. void HBFloaterThumbnail::onChoosenTexture(LLViewerInventoryItem* itemp,
  814. bool final_choice)
  815. {
  816. if (!itemp) // Happens on picker closing with "Close" instead of "Select".
  817. {
  818. if (final_choice)
  819. {
  820. mTempThumbId.setNull();
  821. setThumbTexture();
  822. }
  823. return;
  824. }
  825. if (!validate_item_permissions(itemp))
  826. {
  827. gNotifications.add("ThumbnailInsufficientPermissions");
  828. return;
  829. }
  830. if (final_choice)
  831. {
  832. mThumbnailId = itemp->getAssetUUID();
  833. mTempThumbId.setNull();
  834. }
  835. else
  836. {
  837. mTempThumbId = itemp->getAssetUUID();
  838. }
  839. setThumbTexture();
  840. }
  841. void HBFloaterThumbnail::setThumbnail()
  842. {
  843. LLInventoryObject* invobjp = getInventoryObject();
  844. if (!invobjp || invobjp->getThumbnailUUID() == mThumbnailId ||
  845. (mIsMaterialPreview && mThumbnailId == mInitialThumbnailId))
  846. {
  847. return; // Nothing to do.
  848. }
  849. #if 1
  850. // Set the thumbnail locally
  851. invobjp->setThumbnailUUID(mThumbnailId);
  852. #endif
  853. if (mTaskId.notNull())
  854. {
  855. return;
  856. }
  857. gInventory.addChangedMask(LLInventoryObserver::INTERNAL,
  858. mInventoryObjectId);
  859. // Update the thumbnail on the server.
  860. LLSD updates;
  861. if (mThumbnailId.notNull())
  862. {
  863. updates["thumbnail"] = LLSD().with("asset_id",
  864. mThumbnailId.asString());
  865. }
  866. else
  867. {
  868. updates["thumbnail"] = LLSD();
  869. }
  870. if (mIsCategory)
  871. {
  872. update_inventory_category(mInventoryObjectId, updates, NULL);
  873. }
  874. else
  875. {
  876. update_inventory_item(mInventoryObjectId, updates, NULL);
  877. }
  878. }
  879. //static
  880. void HBFloaterThumbnail::onBtnCancel(void* userdata)
  881. {
  882. HBFloaterThumbnail* self = (HBFloaterThumbnail*)userdata;
  883. if (self)
  884. {
  885. self->close();
  886. }
  887. }
  888. //static
  889. void HBFloaterThumbnail::onBtnClose(void* userdata)
  890. {
  891. HBFloaterThumbnail* self = (HBFloaterThumbnail*)userdata;
  892. if (self)
  893. {
  894. self->setThumbnail();
  895. self->close();
  896. }
  897. }
  898. static void file_selector_callback(HBFileSelector::ELoadFilter,
  899. std::string& filename, void* datap)
  900. {
  901. LLUUID id;
  902. if (datap)
  903. {
  904. LLUUID* idp = (LLUUID*)datap;
  905. id = *idp;
  906. delete idp;
  907. }
  908. if (filename.empty()) // Selection cancelled.
  909. {
  910. return;
  911. }
  912. if (!HBFloaterThumbnail::findInstance(id))
  913. {
  914. // Thumbnail floater already gone... Give up !
  915. return;
  916. }
  917. // Open the texture preview.
  918. new LLFloaterImagePreview(filename, id);
  919. }
  920. //static
  921. void HBFloaterThumbnail::onBtnChange(LLUICtrl* ctrlp, void* userdata)
  922. {
  923. HBFloaterThumbnail* self = (HBFloaterThumbnail*)userdata;
  924. if (!self || !ctrlp) return;
  925. std::string operation = ctrlp->getValue().asString();
  926. if (operation == "clear")
  927. {
  928. self->mThumbnailId.setNull();
  929. self->setThumbTexture();
  930. return;
  931. }
  932. if (operation == "undo")
  933. {
  934. self->mThumbnailId = self->mInitialThumbnailId;
  935. self->setThumbTexture();
  936. return;
  937. }
  938. if (operation == "upload")
  939. {
  940. LLUUID* idp = new LLUUID(self->mInventoryObjectId ^ self->mTaskId);
  941. HBFileSelector::loadFile(HBFileSelector::FFLOAD_IMAGE,
  942. file_selector_callback, (void*)idp);
  943. return;
  944. }
  945. if (operation == "copy")
  946. {
  947. const LLUUID& asset_id = self->mThumbnailId;
  948. if (asset_id.notNull())
  949. {
  950. if (validate_asset_perms(asset_id))
  951. {
  952. HBInventoryClipboard::storeAsset(asset_id, TEXTYPE);
  953. }
  954. else
  955. {
  956. gNotifications.add("ThumbnailInsufficientPermissions");
  957. }
  958. }
  959. return;
  960. }
  961. if (operation == "paste")
  962. {
  963. uuid_vec_t asset_ids;
  964. HBInventoryClipboard::retrieveAssets(asset_ids, TEXTYPE);
  965. size_t count = asset_ids.size();
  966. if (count)
  967. {
  968. for (size_t i = 0; i < count; ++i)
  969. {
  970. // Use the first valid asset Id...
  971. if (validate_asset_perms(asset_ids[i]))
  972. {
  973. self->mThumbnailId = asset_ids[i];
  974. self->setThumbTexture();
  975. return;
  976. }
  977. }
  978. gNotifications.add("ThumbnailInsufficientPermissions");
  979. }
  980. return;
  981. }
  982. if (operation == "upload")
  983. {
  984. LLUUID* idp = new LLUUID(self->mInventoryObjectId ^ self->mTaskId);
  985. HBFileSelector::loadFile(HBFileSelector::FFLOAD_IMAGE,
  986. file_selector_callback, (void*)idp);
  987. return;
  988. }
  989. // "snapshot" in pull-down list or direct click on the button
  990. LLFloaterSnapshot::show(NULL);
  991. LLFloaterSnapshot* snapshotp = LLFloaterSnapshot::getInstance();
  992. snapshotp->setupForInventoryThumbnail(self->mInventoryObjectId);
  993. }
  994. void HBFloaterThumbnail::uploadFailure(const std::string& reason)
  995. {
  996. LLSD args;
  997. args["MESSAGE"] = reason;
  998. gNotifications.add("ThumbnailFailedUpload", args);
  999. mThumbnailId = mInitialThumbnailId;
  1000. setThumbTexture();
  1001. }
  1002. //static
  1003. void HBFloaterThumbnail::uploadThumbnailCoro(std::string url, LLSD data,
  1004. LLUUID id)
  1005. {
  1006. HBFloaterThumbnail* self = findInstance(id);
  1007. if (!self)
  1008. {
  1009. return; // Floater already gone...
  1010. }
  1011. // Copy this on stack, in case the floater gets closed before we get a
  1012. // server reply, which would not prevent us to continue the upload...
  1013. std::string filename = self->mTempFilename;
  1014. LLCore::HttpOptions::ptr_t options(new LLCore::HttpOptions);
  1015. options->setFollowRedirects(true);
  1016. LLCoreHttpUtil::HttpCoroutineAdapter adapter("uploadThumbnail");
  1017. LLSD result = adapter.postAndSuspend(url, data, options);
  1018. if (gDisconnected || LLApp::isExiting())
  1019. {
  1020. return; // Too late, abort.
  1021. }
  1022. bool failed = false;
  1023. LLCore::HttpStatus status =
  1024. LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
  1025. if (!status)
  1026. {
  1027. llwarns << "Failed to get the uploader capability. Status: "
  1028. << status.toString() << llendl;
  1029. failed = true;
  1030. }
  1031. else if (!result.has("uploader"))
  1032. {
  1033. llwarns << "Failed to get uploader cap, response contains no data."
  1034. << llendl;
  1035. failed = true;
  1036. }
  1037. else
  1038. {
  1039. url = result["uploader"].asString();
  1040. failed = url.empty();
  1041. }
  1042. if (failed)
  1043. {
  1044. self = findInstance(id); // This could be NULL now...
  1045. if (self)
  1046. {
  1047. self->uploadFailure(self->getString("upload_failure"));
  1048. }
  1049. LLFile::remove(filename);
  1050. return;
  1051. }
  1052. S32 length = LLFile::getFileSize(filename);
  1053. LLCore::HttpHeaders::ptr_t headers(new LLCore::HttpHeaders);
  1054. headers->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2");
  1055. headers->append(HTTP_OUT_HEADER_CONTENT_LENGTH, llformat("%d", length));
  1056. result = adapter.postFileAndSuspend(url, filename, options, headers);
  1057. LLFile::remove(filename); // We are done with it, now.
  1058. if (gDisconnected || LLApp::isExiting())
  1059. {
  1060. return; // Too late, abort.
  1061. }
  1062. status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
  1063. if (!status)
  1064. {
  1065. llwarns << "Failed to upload image data. Status: " << status.toString()
  1066. << llendl;
  1067. failed = true;
  1068. }
  1069. else if (!result.has("state") || !result.has("new_asset") ||
  1070. result["state"].asString() != "complete")
  1071. {
  1072. llwarns << "Failed to upload image data.";
  1073. if (result.has("state"))
  1074. {
  1075. llcont << ". State: " << result["state"].asString();
  1076. }
  1077. if (result.has("message"))
  1078. {
  1079. llcont << ". Message: " << result["message"].asString();
  1080. }
  1081. if (!result.has("new_asset"))
  1082. {
  1083. llcont << ". No thumbnail UUID transmitted.";
  1084. }
  1085. llcont << llendl;
  1086. failed = true;
  1087. }
  1088. if (failed)
  1089. {
  1090. self = findInstance(id); // This could be NULL now...
  1091. if (self)
  1092. {
  1093. self->uploadFailure(self->getString("upload_failure"));
  1094. }
  1095. return;
  1096. }
  1097. // Update inventory accordingly. This will also cause a refresh of the
  1098. // corresponding thumbnail floater, if still open.
  1099. LLInventoryObject* invobjp = NULL;
  1100. if (data.has("task_id"))
  1101. {
  1102. LLUUID task_id = data["task_id"].asUUID();
  1103. LLViewerObject* objectp = gObjectList.findObject(task_id);
  1104. if (objectp)
  1105. {
  1106. LLUUID item_id = data["item_id"].asUUID();
  1107. invobjp = objectp->getInventoryObject(item_id);
  1108. }
  1109. }
  1110. else if (data.has("category_id"))
  1111. {
  1112. LLUUID cat_id = data["category_id"].asUUID();
  1113. invobjp = gInventory.getCategory(cat_id);
  1114. }
  1115. else if (data.has("item_id"))
  1116. {
  1117. LLUUID item_id = data["item_id"].asUUID();
  1118. invobjp = gInventory.getItem(item_id);
  1119. }
  1120. if (invobjp)
  1121. {
  1122. invobjp->setThumbnailUUID(result["new_asset"].asUUID());
  1123. if (!data.has("task_id"))
  1124. {
  1125. gInventory.addChangedMask(LLInventoryObserver::INTERNAL,
  1126. invobjp->getUUID());
  1127. }
  1128. }
  1129. }
  1130. //static
  1131. void HBFloaterThumbnail::uploadThumbnail(const LLUUID& id,
  1132. LLPointer<LLImageRaw> rawp)
  1133. {
  1134. HBFloaterThumbnail* self = findInstance(id);
  1135. if (!self || rawp.isNull())
  1136. {
  1137. return;
  1138. }
  1139. constexpr S32 MAX_THUMBNAIL_SIZE = 256;
  1140. rawp->biasedScaleToPowerOfTwo(MAX_THUMBNAIL_SIZE);
  1141. LLPointer<LLImageJ2C> imagep =
  1142. LLViewerTextureList::convertToUploadFile(rawp);
  1143. if (imagep.isNull())
  1144. {
  1145. self->uploadFailure(self->getString("error_conversion"));
  1146. return;
  1147. }
  1148. self->mTempFilename = gDirUtil.getTempFilename();
  1149. if (!imagep->save(self->mTempFilename))
  1150. {
  1151. std::string error_msg = self->getString("error_file_write") + ":\n";
  1152. self->uploadFailure(error_msg + self->mTempFilename);
  1153. return;
  1154. }
  1155. const std::string& url =
  1156. gAgent.getRegionCapability("InventoryThumbnailUpload");
  1157. if (url.empty())
  1158. {
  1159. LLFile::remove(self->mTempFilename);
  1160. self->uploadFailure(self->getString("missing_capability"));
  1161. return;
  1162. }
  1163. LLSD data;
  1164. bool is_cat = self->mIsCategory;
  1165. if (self->mTaskId.notNull())
  1166. {
  1167. data["task_id"] = self->mTaskId;
  1168. is_cat = false;
  1169. }
  1170. const char* type_id = is_cat ? "category_id" : "item_id";
  1171. data[type_id] = self->mInventoryObjectId;
  1172. gCoros.launch("HBFloaterThumbnail::uploadThumbnailCoro",
  1173. boost::bind(&HBFloaterThumbnail::uploadThumbnailCoro, url,
  1174. data, id));
  1175. }