/** * @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 "llgltfmaterialpreviewmgr.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 == 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 = 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(); // *NOTE: Vertex buffer for sphere preview is still cached mGLTFPreview = nullptr; } // 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; LLPointer old_material = mGLTFMaterial; mGLTFMaterial = NULL; if (mImageAssetID.notNull()) { if (mInventoryPickType == PICK_MATERIAL) { mGLTFMaterial = (LLFetchedGLTFMaterial*) gGLTFMaterialList.getMaterial(mImageAssetID); llassert(mGLTFMaterial == nullptr || dynamic_cast(gGLTFMaterialList.getMaterial(mImageAssetID)) != nullptr); if (mGLTFPreview.isNull() || mGLTFMaterial.isNull() || (old_material.notNull() && (old_material.get() != mGLTFMaterial.get()))) { // Only update the preview if needed, since gGLTFMaterialPreviewMgr does not cache the preview. if (mGLTFMaterial.isNull()) { mGLTFPreview = nullptr; } else { mGLTFPreview = gGLTFMaterialPreviewMgr.getPreview(mGLTFMaterial); } } if (mGLTFPreview) { mGLTFPreview->setBoostLevel(LLGLTexture::BOOST_PREVIEW); } } else { LLPointer texture = NULL; mGLTFPreview = nullptr; 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* preview; if (mGLTFMaterial) { preview = mGLTFPreview.get(); } else { preview = mTexturep.get(); } if( preview ) { preview->addTextureStats( (F32)(interior.getWidth() * interior.getHeight()) ); if( preview->getComponents() == 4 ) { gl_rect_2d_checkerboard( interior, alpha ); } gl_draw_scaled_image( interior.mLeft, interior.mBottom, interior.getWidth(), interior.getHeight(), preview, UI_VERTEX_COLOR % alpha ); } 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 == 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 == 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(p.pick_type), 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::onVisibilityChange(bool new_visibility) { if (!new_visibility) { // *NOTE: Vertex buffer for sphere preview is still cached mGLTFPreview = nullptr; } else { llassert(!mGLTFPreview); } } 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()); LLPointer preview = NULL; if (!mValid) { mTexturep = NULL; mGLTFMaterial = NULL; mGLTFPreview = NULL; } else if (!mImageAssetID.isNull()) { if (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(mImageAssetID)) { LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); if (obj) { LLViewerTexture* viewerTexture = obj->getBakedTextureForMagicId(mImageAssetID); mTexturep = viewerTexture ? dynamic_cast(viewerTexture) : NULL; mGLTFMaterial = NULL; mGLTFPreview = NULL; preview = mTexturep; } } if (preview.isNull()) { LLPointer old_material = mGLTFMaterial; mGLTFMaterial = NULL; mTexturep = NULL; if (mInventoryPickType == PICK_MATERIAL) { mGLTFMaterial = gGLTFMaterialList.getMaterial(mImageAssetID); if (mGLTFPreview.isNull() || mGLTFMaterial.isNull() || (old_material.notNull() && (old_material.get() != mGLTFMaterial.get()))) { // Only update the preview if needed, since gGLTFMaterialPreviewMgr does not cache the preview. if (mGLTFMaterial.isNull()) { mGLTFPreview = nullptr; } else { mGLTFPreview = gGLTFMaterialPreviewMgr.getPreview(mGLTFMaterial); } } if (mGLTFPreview) { mGLTFPreview->setBoostLevel(LLGLTexture::BOOST_PREVIEW); } preview = mGLTFPreview; } else { preview = LLViewerTextureManager::getFetchedTexture(mImageAssetID, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); preview->setBoostLevel(LLGLTexture::BOOST_PREVIEW); } } } else//mImageAssetID == LLUUID::null { mTexturep = NULL; mGLTFMaterial = NULL; mGLTFPreview = 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( preview ) { if( preview->getComponents() == 4 ) { gl_rect_2d_checkerboard( interior, alpha ); } gl_draw_scaled_image( interior.mLeft, interior.mBottom, interior.getWidth(), interior.getHeight(), preview, UI_VERTEX_COLOR % alpha); preview->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 = 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()); } namespace LLInitParam { void TypeValues::declareValues() { declare("texture_material", PICK_TEXTURE_MATERIAL); declare("texture", PICK_TEXTURE); declare("material", PICK_MATERIAL); } }