12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280 |
- /**
- * @file hbfloaterthumbnail.cpp
- * @author Henri Beauchamp
- * @brief HBFloaterThumbnail class implementation
- *
- * $LicenseInfo:firstyear=2023&license=viewergpl$
- *
- * Copyright (c) 2023-2024, Henri Beauchamp.
- *
- * Second Life Viewer Source Code
- * The source code in this file ("Source Code") is provided by Linden Lab
- * to you under the terms of the GNU General Public License, version 2.0
- * ("GPL"), unless you have obtained a separate licensing agreement
- * ("Other License"), formally executed by you and Linden Lab. Terms of
- * the GPL can be found in doc/GPL-license.txt in this distribution, or
- * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
- *
- * There are special exceptions to the terms and conditions of the GPL as
- * it is applied to this Source Code. View the full text of the exception
- * in the file doc/FLOSS-exception.txt in this software distribution, or
- * online at
- * http://secondlifegrid.net/programs/open_source/licensing/flossexception
- *
- * By copying, modifying or distributing this software, you acknowledge
- * that you have read and understood your obligations described above,
- * and agree to abide by those obligations.
- *
- * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
- * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
- * COMPLETENESS OR PERFORMANCE.
- * $/LicenseInfo$
- */
- #include "llviewerprecompiledheaders.h"
- #include "hbfloaterthumbnail.h"
- #include "llbutton.h"
- #include "llcombobox.h"
- #include "llcorehttputil.h"
- #include "lldir.h"
- #include "hbfileselector.h"
- #include "llfontgl.h"
- #include "lliconctrl.h"
- #include "llimagej2c.h"
- #include "llnotifications.h"
- #include "llrenderutils.h"
- #include "llscrolllistctrl.h"
- #include "lltextbox.h"
- #include "lltrans.h"
- #include "lluictrlfactory.h"
- #include "llagent.h"
- #include "llappviewer.h" // For gDisconnected & LLApp::isExiting()
- #include "llfloaterimagepreview.h"
- #include "hbfloaterinvitemspicker.h"
- #include "llfloatersnapshot.h"
- #include "llgltfmateriallist.h"
- #include "llgltfmaterialpreview.h"
- #include "hbinventoryclipboard.h"
- #include "llinventoryicon.h"
- #include "llinventorymodel.h"
- #include "llpipeline.h" // For LLPipeline::sReflectionProbesEnabled
- #include "llviewercontrol.h"
- #include "llviewerinventory.h"
- #include "llviewerobjectlist.h"
- #include "llviewertexturelist.h"
- #include "llvoinventorylistener.h"
- constexpr LLInventoryType::EType TEXTYPE = LLInventoryType::IT_TEXTURE;
- static LLTimer sAutoCloseTimer;
- // Helper functions
- static bool validate_item_permissions(const LLViewerInventoryItem* itemp)
- {
- const LLPermissions& perms = itemp->getPermissions();
- return perms.allowCopyBy(gAgentID) &&
- perms.allowTransferBy(gAgentID);
- }
- static bool validate_asset_perms(const LLUUID& asset_id)
- {
- 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);
- if (items.empty())
- {
- // No inventory item bears any such asset, so it is most likely another
- // thumbnail Id, and thus allowed to copy/transfer already.
- return true;
- }
- for (U32 i = 0, count = items.size(); i < count; ++i)
- {
- if (items[i] && validate_item_permissions(items[i]))
- {
- return true;
- }
- }
- return false;
- }
- //-----------------------------------------------------------------------------
- // HBThumbnailDropTarget class - UI element for texture drop targets. It also
- // handles automatically click-selection via the inventory items picker.
- // Finally, it acts as an agent's inventory or object's inventory observer for
- // its owner (this avoids having to #include the corresponding headers from the
- // hbfloaterthumbnail.h header).
- //-----------------------------------------------------------------------------
- class HBThumbnailDropTarget final : public LLView, public LLInventoryObserver,
- public LLVOInventoryListener
- {
- protected:
- LOG_CLASS(HBThumbnailDropTarget);
- public:
- HBThumbnailDropTarget(HBFloaterThumbnail* ownerp, LLView* parentp)
- : LLView(parentp->getName() + "_drop", false), // Not mouse-opaque
- mParentFloater(ownerp),
- mObserveAgentInventory(false),
- mObserveObjectInventory(false)
- {
- setFollows(FOLLOWS_LEFT | FOLLOWS_TOP);
- // Set our rect to the parent view (usually a view border) rect.
- LLRect rect = parentp->getRect();
- setRect(rect);
- // Create a text box associated with our drop target view (we will not
- // use this as an actual text box, but this allows to set a clicked
- // callback for it, that a simple LLView would not have).
- LLTextBox* textp = new LLTextBox(parentp->getName() + "_click", rect,
- "", LLFontGL::getFontSansSerif(),
- true); // Opaque text box
- // Add as a child of our owner floater
- ownerp->addChild(textp);
- // Add ourselves as a child of the floater: this must be done *after*
- // the text box was added, so that the drop target view is on top (note
- // that it is however not opaque to mouse: tool tip hovers and clicks
- // do get to the underlying text box).
- ownerp->addChild(this);
- // Add an adequate tool tip
- textp->setToolTip(ownerp->getString("thumbnail_tool_tip"));
- // Setup click-action on the text of the drop target (inventory picker
- // or edit thumbmail floater opening)
- textp->setClickedCallback(onTextClicked, this);
- // Observe inventory changes on behalf of our owner.
- updateObservers();
- }
- void updateObservers(bool remove_only = false)
- {
- // Remove any old observer
- if (mObserveAgentInventory)
- {
- gInventory.removeObserver(this);
- mObserveAgentInventory = false;
- }
- if (mObserveObjectInventory)
- {
- removeVOInventoryListeners();
- mObserveObjectInventory = false;
- }
- if (!remove_only)
- {
- // Add an appropriate observer for the new item
- if (mParentFloater->mTaskId.isNull())
- {
- mObserveAgentInventory = true;
- gInventory.addObserver(this);
- }
- else
- {
- LLViewerObject* objectp =
- gObjectList.findObject(mParentFloater->mTaskId);
- if (objectp)
- {
- mObserveObjectInventory = true;
- registerVOInventoryListener(objectp, NULL);
- }
- }
- }
- }
- ~HBThumbnailDropTarget() override
- {
- updateObservers(true); // Remove observers
- }
- bool handleDragAndDrop(S32 x, S32 y, MASK, bool drop,
- EDragAndDropType cargo_type,
- void* cargo_data, EAcceptance* accept,
- std::string&) override
- {
- // Careful: pointInView() gets f*cked up whenever the panel is embedded
- // inside a layout stack.
- if (mParentFloater->mOwner || !getEnabled() || !pointInView(x, y))
- {
- return false;
- }
- *accept = ACCEPT_NO;
- if (cargo_type == DAD_TEXTURE)
- {
- LLViewerInventoryItem* itemp = (LLViewerInventoryItem*)cargo_data;
- if (itemp && gInventory.getItem(itemp->getUUID()) &&
- validate_item_permissions(itemp))
- {
- *accept = ACCEPT_YES_COPY_SINGLE;
- if (drop)
- {
- // Inform our owner about the user choice
- mParentFloater->onChoosenTexture(itemp, true);
- }
- }
- }
- return true;
- }
- // LLInventoryObserver override
- void changed(U32 mask) override
- {
- constexpr U32 WATCHED_CHANGES = LABEL | INTERNAL | REMOVE;
- if (mask & WATCHED_CHANGES)
- {
- // Passing a null UUID causes a simple refresh.
- mParentFloater->setInventoryObjectId(LLUUID::null);
- }
- }
- // LLVOInventoryListener override
- void inventoryChanged(LLViewerObject*, LLInventoryObject::object_list_t*,
- S32, void*) override
- {
- // Passing a null UUID causes a simple refresh.
- mParentFloater->setInventoryObjectId(LLUUID::null);
- }
- private:
- static void onTextClicked(void* userdata)
- {
- HBThumbnailDropTarget* self = (HBThumbnailDropTarget*)userdata;
- if (!self || !self->getEnabled())
- {
- return;
- }
- HBFloaterThumbnail* floaterp = self->mParentFloater;
- if (floaterp->mOwner)
- {
- const LLUUID& item_id = floaterp->mInventoryObjectId;
- if (item_id.notNull())
- {
- // Show a thumbnail edit floater for our viewed item.
- HBFloaterThumbnail::showInstance(item_id, floaterp->mTaskId);
- }
- // Flag our parent floater for closing (do not close it ourselves,
- // since this could cause the clicked callback or focus underlying
- // code to possibly use destroyed UI elements pointers).
- floaterp->mMustClose = true;
- return;
- }
- HBFloaterInvItemsPicker* pickerp =
- new HBFloaterInvItemsPicker(self, invItemsPickerCallback, self);
- // We want an empty selection callback on picker closing by any other
- // mean than the "Select" button.
- pickerp->callBackOnClose();
- if (pickerp)
- {
- pickerp->setAssetType(LLAssetType::AT_TEXTURE);
- }
- pickerp->setApplyImmediatelyControl("ApplyThumbnailImmediately");
- // Thumbnails must be at least copy OK and transfer OK.
- pickerp->setFilterPermMask(PERM_COPY | PERM_TRANSFER);
- static LLCachedControl<bool> auto_pick(gSavedSettings,
- "ThumbnailAutoPickTexture");
- if (!auto_pick || floaterp->mTaskId.notNull())
- {
- return;
- }
- // Search for a texture with the right permissions in the folder (or
- // parent folder for an item) we want to set the thumbnail for, and
- // select it by default. The rationale is that if a texture exists at
- // this level it is likely representative of the thumbnail we want for
- // this folder or item...
- LLInventoryObject* invobjp = floaterp->getInventoryObject();
- if (!invobjp) // Paranoia
- {
- return;
- }
- const LLUUID& cat_id =
- floaterp->mIsCategory ? invobjp->getUUID()
- : invobjp->getParentUUID();
- // First, search among direct descendents...
- LLInventoryModel::cat_array_t* cats;
- LLInventoryModel::item_array_t* items;
- gInventory.getDirectDescendentsOf(cat_id, cats, items);
- if (!items) // Failed to collect descendents !
- {
- return;
- }
- for (LLInventoryModel::item_array_t::iterator it = items->begin(),
- end = items->end();
- it != end; ++it)
- {
- LLViewerInventoryItem* itemp = *it;
- if (itemp && itemp->getType() == LLAssetType::AT_TEXTURE &&
- validate_item_permissions(itemp))
- {
- // Select this texture by default.
- pickerp->setSelection(itemp->getUUID());
- return;
- }
- }
- // Finally, search deeper down in the whole folder tree (there is no
- // set order on the returned items, thus why we searched for direct
- // descendents first)...
- LLInventoryModel::cat_array_t all_cats;
- LLInventoryModel::item_array_t all_items;
- gInventory.collectDescendents(cat_id, all_cats, all_items, false);
- for (LLInventoryModel::item_array_t::iterator it = all_items.begin(),
- end = all_items.end();
- it != end; ++it)
- {
- LLViewerInventoryItem* itemp = *it;
- if (itemp && itemp->getType() == LLAssetType::AT_TEXTURE &&
- validate_item_permissions(itemp))
- {
- // Select this texture by default.
- pickerp->setSelection(itemp->getUUID());
- return;
- }
- }
- }
- static void invItemsPickerCallback(const std::vector<std::string>&,
- const uuid_vec_t& ids, void* userdata,
- bool on_close)
- {
- HBThumbnailDropTarget* self = (HBThumbnailDropTarget*)userdata;
- if (!self) // Paranoia
- {
- return;
- }
- // Empty ids happen on close by any other mean than "Select".
- if (ids.empty())
- {
- if (on_close)
- {
- self->mParentFloater->onChoosenTexture(NULL, true);
- }
- return;
- }
- LLUUID inv_id = ids[0];
- // Make sure we are not trying to use a link and get the linked item
- // Id in that case.
- if (inv_id.notNull())
- {
- inv_id = gInventory.getLinkedItemID(inv_id);
- }
- LLViewerInventoryItem* itemp = gInventory.getItem(inv_id);
- if (itemp)
- {
- // Inform our owner about the user choice
- self->mParentFloater->onChoosenTexture(itemp, on_close);
- }
- }
- private:
- HBFloaterThumbnail* mParentFloater;
- bool mObserveAgentInventory;
- bool mObserveObjectInventory;
- };
- ///////////////////////////////////////////////////////////////////////////////
- // HBFloaterThumbnail class proper
- ///////////////////////////////////////////////////////////////////////////////
- //static
- HBFloaterThumbnail::instances_map_t HBFloaterThumbnail::sInstances;
- //static
- HBFloaterThumbnail* HBFloaterThumbnail::findInstance(const LLUUID& id)
- {
- instances_map_t::iterator it = sInstances.find(id);
- return it != sInstances.end() ? it->second : NULL;
- }
- //static
- void HBFloaterThumbnail::showInstance(const LLUUID& inv_obj_id,
- const LLUUID& task_id, LLView* ownerp)
- {
- // Search for our owner's parent floater.
- LLFloater* parentp = ownerp ? ownerp->getParentFloater() : NULL;
- if (parentp)
- {
- sAutoCloseTimer.reset();
- }
- HBFloaterThumbnail* floaterp = findInstance(inv_obj_id ^ task_id);
- if (floaterp) // A floater for this inventory object exists already.
- {
- if (!floaterp->getVisible())
- {
- floaterp->open();
- }
- // If it is an edit floater with matching item, we can close the
- // temporary view floater when it exists.
- if (parentp && !floaterp->mOwner)
- {
- // The null UUID is used for the unique and shared, temporary view
- // floater.
- floaterp = findInstance(LLUUID::null);
- if (floaterp)
- {
- floaterp->close();
- }
- }
- return;
- }
- if (parentp) // Look for an existing thumbnail shared view floater
- {
- // The null UUID is used for the unique and shared, temporary view
- // floater.
- floaterp = findInstance(LLUUID::null);
- // If this floater exists and does not belong to our new owner's parent
- // floater, then close it.
- if (floaterp && floaterp->mOwner != parentp)
- {
- floaterp->close();
- floaterp = NULL;
- }
- }
- if (floaterp)
- {
- // Set the new inventory item for this shared, unique floater. Note
- // that this call may actually close the said floater, when there is
- // no thumbnail associated with this item.
- if (parentp)
- {
- floaterp->setInventoryObjectId(inv_obj_id);
- floaterp->mTaskId = task_id;
- // We need this in case the view floater switched from an agent's
- // inventory item to the item of an object's inventory, or vice
- // versa (observers need updating).
- floaterp->updateDropTarget();
- }
- }
- else
- {
- new HBFloaterThumbnail(inv_obj_id, task_id, parentp);
- if (!parentp)
- {
- // Check that the temporary floater is not open for this same item
- // and when it is, close it.
- floaterp = findInstance(LLUUID::null);
- if (floaterp && floaterp->mInventoryObjectId == inv_obj_id &&
- floaterp->mTaskId == task_id)
- {
- floaterp->close();
- }
- }
- }
- }
- //static
- void HBFloaterThumbnail::hideInstance(const LLUUID& id)
- {
- HBFloaterThumbnail* self = findInstance(id);
- if (self &&
- (self->mOwner ||
- // Do not close an edit floater with unsaved changes.
- self->mThumbnailId == self->mInitialThumbnailId))
- {
- self->close();
- }
- }
- HBFloaterThumbnail::HBFloaterThumbnail(const LLUUID& inv_obj_id,
- const LLUUID& task_id,
- LLFloater* ownerp)
- : mOwner(ownerp),
- mPasteThumbnail(NULL),
- mCopyThumbnail(NULL),
- mClearThumbnail(NULL),
- mUndoThumbnail(NULL),
- mCancelButton(NULL),
- mTaskId(task_id),
- mMustClose(false),
- mIsCategory(false),
- mIsMaterialPreview(false)
- {
- // Note: the only floater with an owner is the preview one.
- std::string xml_file = ownerp ? "floater_thumbnail_view.xml"
- : "floater_thumbnail.xml";
- LLUICtrlFactory::getInstance()->buildFloater(this, xml_file, NULL,
- !mOwner);
- if (ownerp)
- {
- setIsChrome(true);
- setSoundFlags(SILENT);
- ownerp->addDependentFloater(this);
- }
- setInventoryObjectId(inv_obj_id);
- }
- //virtual
- HBFloaterThumbnail::~HBFloaterThumbnail()
- {
- unregister();
- }
- void HBFloaterThumbnail::unregister()
- {
- // There shall be exactly one entry for each floater registered in the map.
- // Always use setInventoryObjectId() when changing the associated inventory
- // object !
- for (instances_map_t::iterator it = sInstances.begin(),
- end = sInstances.end();
- it != end; ++it)
- {
- if (it->second == this)
- {
- sInstances.erase(it);
- break;
- }
- }
- }
- //virtual
- bool HBFloaterThumbnail::postBuild()
- {
- mIcon = getChild<LLIconCtrl>("icon");
- mInventoryObjectName = getChild<LLTextBox>("item_name");
- mDropTarget = new HBThumbnailDropTarget(this,
- getChild<LLView>("thumbnail"));
- mThumbnailRect = mDropTarget->getRect();
- // Adjust to keep the view border showing while we will draw the thumbnail
- // inside it.
- ++mThumbnailRect.mBottom;
- --mThumbnailRect.mTop;
- ++mThumbnailRect.mLeft;
- --mThumbnailRect.mRight;
- if (mOwner)
- {
- return true;
- }
- LLFlyoutButton* change_buttonp = getChild<LLFlyoutButton>("change");
- change_buttonp->setCommitCallback(onBtnChange);
- change_buttonp->setCallbackUserData(this);
- std::string operation;
- for (U32 i = 0, count = change_buttonp->getItemCount(); i < count; ++i)
- {
- LLScrollListItem* itemp = change_buttonp->getItemByIndex(i);
- operation = itemp->getValue().asString();
- if (operation == "copy")
- {
- mCopyThumbnail = itemp;
- }
- else if (operation == "paste")
- {
- mPasteThumbnail = itemp;
- }
- else if (operation == "clear")
- {
- mClearThumbnail = itemp;
- }
- else if (operation == "undo")
- {
- mUndoThumbnail = itemp;
- }
- }
- mCancelButton = getChild<LLButton>("cancel_btn");
- mCancelButton->setClickedCallback(onBtnCancel, this);
- childSetAction("ok_btn", onBtnClose, this);
- return true;
- }
- //virtual
- void HBFloaterThumbnail::draw()
- {
- static LLCachedControl<U32> timeout(gSavedSettings,
- "ThumbnailViewTimeout");
- if (mMustClose ||
- (mOwner && timeout &&
- sAutoCloseTimer.getElapsedTimeF32() > F32(timeout)))
- {
- close();
- return;
- }
- if (mCancelButton)
- {
- mCancelButton->setEnabled(mThumbnailId != mInitialThumbnailId);
- }
- if (mPasteThumbnail)
- {
- mPasteThumbnail->setEnabled(HBInventoryClipboard::hasAssets(TEXTYPE));
- }
-
- // Draw all UI elements before we would draw the texture.
- LLFloater::draw();
- if (isMinimized())
- {
- return; // No need to draw the texture.
- }
- bool has_texture = mTexturep.notNull();
- bool has_preview = !has_texture && mMatTexturep.notNull();
- if (!has_texture && !has_preview && mMaterialp.isNull())
- {
- // No texture, draw a grey square...
- gl_rect_2d(mThumbnailRect, LLColor4::grey);
- // ... with a black X.
- gl_draw_x(mThumbnailRect, LLColor4::black);
- return;
- }
- F32 width = mThumbnailRect.getWidth();
- F32 height = mThumbnailRect.getHeight();
- if (has_texture)
- {
- // Update the texture the priority
- mTexturep->addTextureStats(width * height);
- }
- F32 left = mThumbnailRect.mLeft;
- F32 bottom = mThumbnailRect.mBottom;
- F32 tex_width = 0.f;
- F32 tex_height = 0.f;
- if (has_texture)
- {
- tex_width = mTexturep->getFullWidth();
- tex_height = mTexturep->getFullHeight();
- }
- else if (has_preview)
- {
- tex_width = mMatTexturep->getFullWidth();
- tex_height = mMatTexturep->getFullHeight();
- }
- if (tex_width && tex_height && tex_width != tex_height)
- {
- // If necessary, compute the offset in the display, to draw the texture
- // with its native aspect ratio.
- F32 proportion = tex_height / tex_width;
- if (proportion > 1.f)
- {
- left += (width - width / proportion) * 0.5f;
- width /= proportion;
- }
- else
- {
- bottom += (height - height * proportion) * 0.5f;
- height *= proportion;
- }
- }
- // If one of the dimensions of the image is smaller than the display,
- // center it.
- if (tex_width && tex_height && (tex_width < width || tex_height < height))
- {
- if (tex_width < width)
- {
- left += (width - tex_width) * .5f;
- width = tex_width;
- }
- if (tex_height < height)
- {
- bottom += (height - tex_height) * .5f;
- height = tex_height;
- }
- }
- if (has_texture)
- {
- gl_draw_scaled_image(left, bottom, width, height, mTexturep);
- }
- else if (has_preview)
- {
- gl_draw_scaled_image(left, bottom, width, height, mMatTexturep);
- }
- else
- {
- // Preview not ready, draw a grey square...
- gl_rect_2d(mThumbnailRect, LLColor4::grey);
- }
- if ((has_texture && !mTexturep->isFullyLoaded()) ||
- (!has_texture && !has_preview))
- {
- if (mOwner && timeout)
- {
- sAutoCloseTimer.reset();
- }
- // Show "Loading..." string on the bottom left corner while the texture
- // is loading.
- static LLFontGL* fontp = LLFontGL::getFontSansSerif();
- static LLWString loading = LLTrans::getWString("texture_loading");
- fontp->render(loading, 0, mThumbnailRect.mLeft + 8,
- mThumbnailRect.mBottom + 6, LLColor4::white,
- LLFontGL::LEFT, LLFontGL::BASELINE,
- LLFontGL::DROP_SHADOW);
- }
- // If we are waiting for the preview texture, retry to get it now.
- if (!has_texture && !has_preview && mMaterialp.notNull())
- {
- mMatTexturep = LLGLTFPreviewTexture::getPreview(mMaterialp);
- }
- }
- void HBFloaterThumbnail::updateDropTarget()
- {
- if (mDropTarget)
- {
- mDropTarget->updateObservers();
- }
- }
- LLInventoryObject* HBFloaterThumbnail::getInventoryObject()
- {
- mIsCategory = false;
- LLInventoryObject* invobjp = NULL;
- if (mInventoryObjectId.notNull())
- {
- if (mTaskId.notNull())
- {
- LLViewerObject* objectp = gObjectList.findObject(mTaskId);
- if (objectp)
- {
- invobjp = objectp->getInventoryObject(mInventoryObjectId);
- }
- }
- else
- {
- invobjp = gInventory.getCategory(mInventoryObjectId);
- if (invobjp)
- {
- mIsCategory = true;
- }
- else
- {
- invobjp = gInventory.getItem(mInventoryObjectId);
- }
- }
- }
- return invobjp;
- }
- void HBFloaterThumbnail::setThumbTexture()
- {
- mTexturep = NULL;
- mMatTexturep = NULL;
- const LLUUID& id = mTempThumbId.notNull() ? mTempThumbId : mThumbnailId;
- if (id.notNull())
- {
- if (mIsMaterialPreview && id == mInitialThumbnailId)
- {
- mMaterialp = gGLTFMaterialList.getMaterial(id);
- mMatTexturep = LLGLTFPreviewTexture::getPreview(mMaterialp);
- }
- else
- {
- mMaterialp = NULL;
- mTexturep =
- LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT,
- true,
- LLGLTexture::BOOST_PREVIEW);
- }
- }
- bool has_texture = mTexturep.notNull();
- if (mCopyThumbnail)
- {
- mCopyThumbnail->setEnabled(has_texture && validate_asset_perms(id));
- }
- if (mClearThumbnail)
- {
- mClearThumbnail->setEnabled(has_texture);
- }
- if (mUndoThumbnail)
- {
- mUndoThumbnail->setEnabled(mThumbnailId != mInitialThumbnailId);
- }
- }
- void HBFloaterThumbnail::setInventoryObjectId(const LLUUID& inv_obj_id)
- {
- // A null UUID is passed by the inventory observer when we only need a
- // refresh for the currently associated inventory object.
- if (inv_obj_id.notNull())
- {
- mInventoryObjectId = inv_obj_id;
- unregister();
- sInstances.emplace(mOwner ? LLUUID::null : inv_obj_id ^ mTaskId, this);
- }
- LLInventoryObject* invobjp = getInventoryObject();
- if (!invobjp)
- {
- close(); // No associated inventory object, so just commit suicide.
- return;
- }
- mIsMaterialPreview = false;
- LLUUID thumb_id = invobjp->getThumbnailUUID();
- if (thumb_id.isNull() && gUsePBRShaders &&
- // Note: for now, material preview does not render at all when
- // reflection probes are disabled. *TODO: find out why and fix it.
- LLPipeline::sReflectionProbesEnabled)
- {
- bool is_mat = invobjp->getType() == LLAssetType::AT_MATERIAL;
- if (is_mat)
- {
- thumb_id = ((LLInventoryItem*)invobjp)->getAssetUUID();
- mIsMaterialPreview = true;
- }
- }
- if (mOwner)
- {
- if (thumb_id.isNull())
- {
- // Nothing to display, close the temporary floater.
- close();
- return;
- }
- // If there is indeed something to display, we can open the temporary
- // floater.
- if (!getVisible())
- {
- open();
- }
- }
- if (thumb_id != mInitialThumbnailId) // May not have changed on refresh.
- {
- mThumbnailId = mInitialThumbnailId = thumb_id;
- setThumbTexture();
- }
- else if (mCopyThumbnail)
- {
- mCopyThumbnail->setEnabled(mThumbnailId.notNull() &&
- validate_asset_perms(mThumbnailId));
- }
- mInventoryObjectName->setText(invobjp->getName());
- // Set the corresponding inventory icon.
- LLInventoryItem* itemp = invobjp->asInventoryItem();
- if (itemp)
- {
- mIcon->setValue(LLInventoryIcon::getIconName(itemp->getType(),
- itemp->getInventoryType(),
- itemp->getFlags()));
- }
- else
- {
- static const std::string folder_icon = "inv_folder_plain_closed.tga";
- mIcon->setValue(folder_icon);
- }
- }
- void HBFloaterThumbnail::onChoosenTexture(LLViewerInventoryItem* itemp,
- bool final_choice)
- {
- if (!itemp) // Happens on picker closing with "Close" instead of "Select".
- {
- if (final_choice)
- {
- mTempThumbId.setNull();
- setThumbTexture();
- }
- return;
- }
- if (!validate_item_permissions(itemp))
- {
- gNotifications.add("ThumbnailInsufficientPermissions");
- return;
- }
- if (final_choice)
- {
- mThumbnailId = itemp->getAssetUUID();
- mTempThumbId.setNull();
- }
- else
- {
- mTempThumbId = itemp->getAssetUUID();
- }
- setThumbTexture();
- }
- void HBFloaterThumbnail::setThumbnail()
- {
- LLInventoryObject* invobjp = getInventoryObject();
- if (!invobjp || invobjp->getThumbnailUUID() == mThumbnailId ||
- (mIsMaterialPreview && mThumbnailId == mInitialThumbnailId))
- {
- return; // Nothing to do.
- }
- #if 1
- // Set the thumbnail locally
- invobjp->setThumbnailUUID(mThumbnailId);
- #endif
- if (mTaskId.notNull())
- {
- return;
- }
- gInventory.addChangedMask(LLInventoryObserver::INTERNAL,
- mInventoryObjectId);
- // Update the thumbnail on the server.
- LLSD updates;
- if (mThumbnailId.notNull())
- {
- updates["thumbnail"] = LLSD().with("asset_id",
- mThumbnailId.asString());
- }
- else
- {
- updates["thumbnail"] = LLSD();
- }
- if (mIsCategory)
- {
- update_inventory_category(mInventoryObjectId, updates, NULL);
- }
- else
- {
- update_inventory_item(mInventoryObjectId, updates, NULL);
- }
- }
- //static
- void HBFloaterThumbnail::onBtnCancel(void* userdata)
- {
- HBFloaterThumbnail* self = (HBFloaterThumbnail*)userdata;
- if (self)
- {
- self->close();
- }
- }
- //static
- void HBFloaterThumbnail::onBtnClose(void* userdata)
- {
- HBFloaterThumbnail* self = (HBFloaterThumbnail*)userdata;
- if (self)
- {
- self->setThumbnail();
- self->close();
- }
- }
- static void file_selector_callback(HBFileSelector::ELoadFilter,
- std::string& filename, void* datap)
- {
- LLUUID id;
- if (datap)
- {
- LLUUID* idp = (LLUUID*)datap;
- id = *idp;
- delete idp;
- }
- if (filename.empty()) // Selection cancelled.
- {
- return;
- }
- if (!HBFloaterThumbnail::findInstance(id))
- {
- // Thumbnail floater already gone... Give up !
- return;
- }
- // Open the texture preview.
- new LLFloaterImagePreview(filename, id);
- }
- //static
- void HBFloaterThumbnail::onBtnChange(LLUICtrl* ctrlp, void* userdata)
- {
- HBFloaterThumbnail* self = (HBFloaterThumbnail*)userdata;
- if (!self || !ctrlp) return;
- std::string operation = ctrlp->getValue().asString();
- if (operation == "clear")
- {
- self->mThumbnailId.setNull();
- self->setThumbTexture();
- return;
- }
- if (operation == "undo")
- {
- self->mThumbnailId = self->mInitialThumbnailId;
- self->setThumbTexture();
- return;
- }
- if (operation == "upload")
- {
- LLUUID* idp = new LLUUID(self->mInventoryObjectId ^ self->mTaskId);
- HBFileSelector::loadFile(HBFileSelector::FFLOAD_IMAGE,
- file_selector_callback, (void*)idp);
- return;
- }
- if (operation == "copy")
- {
- const LLUUID& asset_id = self->mThumbnailId;
- if (asset_id.notNull())
- {
- if (validate_asset_perms(asset_id))
- {
- HBInventoryClipboard::storeAsset(asset_id, TEXTYPE);
- }
- else
- {
- gNotifications.add("ThumbnailInsufficientPermissions");
- }
- }
- return;
- }
- if (operation == "paste")
- {
- uuid_vec_t asset_ids;
- HBInventoryClipboard::retrieveAssets(asset_ids, TEXTYPE);
- size_t count = asset_ids.size();
- if (count)
- {
- for (size_t i = 0; i < count; ++i)
- {
- // Use the first valid asset Id...
- if (validate_asset_perms(asset_ids[i]))
- {
- self->mThumbnailId = asset_ids[i];
- self->setThumbTexture();
- return;
- }
- }
- gNotifications.add("ThumbnailInsufficientPermissions");
- }
- return;
- }
- if (operation == "upload")
- {
- LLUUID* idp = new LLUUID(self->mInventoryObjectId ^ self->mTaskId);
- HBFileSelector::loadFile(HBFileSelector::FFLOAD_IMAGE,
- file_selector_callback, (void*)idp);
- return;
- }
- // "snapshot" in pull-down list or direct click on the button
- LLFloaterSnapshot::show(NULL);
- LLFloaterSnapshot* snapshotp = LLFloaterSnapshot::getInstance();
- snapshotp->setupForInventoryThumbnail(self->mInventoryObjectId);
- }
- void HBFloaterThumbnail::uploadFailure(const std::string& reason)
- {
- LLSD args;
- args["MESSAGE"] = reason;
- gNotifications.add("ThumbnailFailedUpload", args);
- mThumbnailId = mInitialThumbnailId;
- setThumbTexture();
- }
- //static
- void HBFloaterThumbnail::uploadThumbnailCoro(std::string url, LLSD data,
- LLUUID id)
- {
- HBFloaterThumbnail* self = findInstance(id);
- if (!self)
- {
- return; // Floater already gone...
- }
- // Copy this on stack, in case the floater gets closed before we get a
- // server reply, which would not prevent us to continue the upload...
- std::string filename = self->mTempFilename;
- LLCore::HttpOptions::ptr_t options(new LLCore::HttpOptions);
- options->setFollowRedirects(true);
- LLCoreHttpUtil::HttpCoroutineAdapter adapter("uploadThumbnail");
- LLSD result = adapter.postAndSuspend(url, data, options);
- if (gDisconnected || LLApp::isExiting())
- {
- return; // Too late, abort.
- }
- bool failed = false;
- LLCore::HttpStatus status =
- LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
- if (!status)
- {
- llwarns << "Failed to get the uploader capability. Status: "
- << status.toString() << llendl;
- failed = true;
- }
- else if (!result.has("uploader"))
- {
- llwarns << "Failed to get uploader cap, response contains no data."
- << llendl;
- failed = true;
- }
- else
- {
- url = result["uploader"].asString();
- failed = url.empty();
- }
- if (failed)
- {
- self = findInstance(id); // This could be NULL now...
- if (self)
- {
- self->uploadFailure(self->getString("upload_failure"));
- }
- LLFile::remove(filename);
- return;
- }
- S32 length = LLFile::getFileSize(filename);
- LLCore::HttpHeaders::ptr_t headers(new LLCore::HttpHeaders);
- headers->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2");
- headers->append(HTTP_OUT_HEADER_CONTENT_LENGTH, llformat("%d", length));
- result = adapter.postFileAndSuspend(url, filename, options, headers);
- LLFile::remove(filename); // We are done with it, now.
- if (gDisconnected || LLApp::isExiting())
- {
- return; // Too late, abort.
- }
- status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
- if (!status)
- {
- llwarns << "Failed to upload image data. Status: " << status.toString()
- << llendl;
- failed = true;
- }
- else if (!result.has("state") || !result.has("new_asset") ||
- result["state"].asString() != "complete")
- {
- llwarns << "Failed to upload image data.";
- if (result.has("state"))
- {
- llcont << ". State: " << result["state"].asString();
- }
- if (result.has("message"))
- {
- llcont << ". Message: " << result["message"].asString();
- }
- if (!result.has("new_asset"))
- {
- llcont << ". No thumbnail UUID transmitted.";
- }
- llcont << llendl;
- failed = true;
- }
- if (failed)
- {
- self = findInstance(id); // This could be NULL now...
- if (self)
- {
- self->uploadFailure(self->getString("upload_failure"));
- }
- return;
- }
- // Update inventory accordingly. This will also cause a refresh of the
- // corresponding thumbnail floater, if still open.
- LLInventoryObject* invobjp = NULL;
- if (data.has("task_id"))
- {
- LLUUID task_id = data["task_id"].asUUID();
- LLViewerObject* objectp = gObjectList.findObject(task_id);
- if (objectp)
- {
- LLUUID item_id = data["item_id"].asUUID();
- invobjp = objectp->getInventoryObject(item_id);
- }
- }
- else if (data.has("category_id"))
- {
- LLUUID cat_id = data["category_id"].asUUID();
- invobjp = gInventory.getCategory(cat_id);
- }
- else if (data.has("item_id"))
- {
- LLUUID item_id = data["item_id"].asUUID();
- invobjp = gInventory.getItem(item_id);
- }
- if (invobjp)
- {
- invobjp->setThumbnailUUID(result["new_asset"].asUUID());
- if (!data.has("task_id"))
- {
- gInventory.addChangedMask(LLInventoryObserver::INTERNAL,
- invobjp->getUUID());
- }
- }
- }
- //static
- void HBFloaterThumbnail::uploadThumbnail(const LLUUID& id,
- LLPointer<LLImageRaw> rawp)
- {
- HBFloaterThumbnail* self = findInstance(id);
- if (!self || rawp.isNull())
- {
- return;
- }
- constexpr S32 MAX_THUMBNAIL_SIZE = 256;
- rawp->biasedScaleToPowerOfTwo(MAX_THUMBNAIL_SIZE);
- LLPointer<LLImageJ2C> imagep =
- LLViewerTextureList::convertToUploadFile(rawp);
- if (imagep.isNull())
- {
- self->uploadFailure(self->getString("error_conversion"));
- return;
- }
- self->mTempFilename = gDirUtil.getTempFilename();
- if (!imagep->save(self->mTempFilename))
- {
- std::string error_msg = self->getString("error_file_write") + ":\n";
- self->uploadFailure(error_msg + self->mTempFilename);
- return;
- }
- const std::string& url =
- gAgent.getRegionCapability("InventoryThumbnailUpload");
- if (url.empty())
- {
- LLFile::remove(self->mTempFilename);
- self->uploadFailure(self->getString("missing_capability"));
- return;
- }
- LLSD data;
- bool is_cat = self->mIsCategory;
- if (self->mTaskId.notNull())
- {
- data["task_id"] = self->mTaskId;
- is_cat = false;
- }
- const char* type_id = is_cat ? "category_id" : "item_id";
- data[type_id] = self->mInventoryObjectId;
- gCoros.launch("HBFloaterThumbnail::uploadThumbnailCoro",
- boost::bind(&HBFloaterThumbnail::uploadThumbnailCoro, url,
- data, id));
- }
|