diff --git a/.github/workflows/build_viewer.yml b/.github/workflows/build_viewer.yml index fec716a74d..4f76b9a085 100644 --- a/.github/workflows/build_viewer.yml +++ b/.github/workflows/build_viewer.yml @@ -308,7 +308,17 @@ jobs: if: ${{ matrix.variant == 'avx' }} shell: bash run: echo "EXTRA_ARGS=${{ env.EXTRA_ARGS }} --avx2" >> $GITHUB_ENV - + - name: Add custom UA string if provided + env: + FS_PF_UA: ${{ secrets.FS_PF_UA }} + run: | + if [ -n "${FS_PF_UA}" ]; then + echo "EXTRA_ARGS=${{ env.EXTRA_ARGS }} -DFS_PF_USER_AGENT=\"${FS_PF_UA}\"" >> $GITHUB_ENV + echo "Building with custom user-agent string." + else + echo "No custom user-agent string provided." + fi + shell: bash - name: Clean up packages to give more space run: rm *${{ env.fallback_platform }}*bz2 shell: bash diff --git a/.github/workflows/deploy_only.yml b/.github/workflows/deploy_only.yml index 1fc415bac4..906ed7e0da 100644 --- a/.github/workflows/deploy_only.yml +++ b/.github/workflows/deploy_only.yml @@ -45,7 +45,7 @@ jobs: run: pip install discord-webhook - name: Download Build Artifacts - uses: dawidd6/action-download-artifact@v9 + uses: dawidd6/action-download-artifact@v10 id: download with: workflow: build_viewer.yml diff --git a/.github/workflows/sign.yml b/.github/workflows/sign.yml index 56adbcb0db..d5dc39a70b 100644 --- a/.github/workflows/sign.yml +++ b/.github/workflows/sign.yml @@ -41,7 +41,7 @@ jobs: setup_files: ${{ steps.get-files.outputs.setup_files }} steps: - name: Download Build Artifacts - uses: dawidd6/action-download-artifact@v9 + uses: dawidd6/action-download-artifact@v10 id: download with: workflow: build_viewer.yml diff --git a/.github/workflows/tag-fs-build.yml b/.github/workflows/tag-fs-build.yml index f98376d067..9f4f2f470f 100644 --- a/.github/workflows/tag-fs-build.yml +++ b/.github/workflows/tag-fs-build.yml @@ -71,7 +71,7 @@ jobs: echo "build_run_number=${{ github.event.inputs.build_run_number }}" >> "$GITHUB_OUTPUT" fi - name: Download Build Artifacts - uses: dawidd6/action-download-artifact@v9 + uses: dawidd6/action-download-artifact@v10 id: download_build_info with: workflow: build_viewer.yml diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index c2dd47f0fb..abc0df8095 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -73,6 +73,15 @@ if(TESTBUILD AND TESTBUILDPERIOD) endif(TESTBUILD AND TESTBUILDPERIOD) # +# Support for custom Primfeed UA +option(FS_PF_USER_AGENT "Optional compile‐time Primfeed user‐agent string" "") +if (FS_PF_USER_AGENT) + add_compile_definitions(FS_PF_USER_AGENT="${FS_PF_USER_AGENT}") + message(STATUS "Compiling with custom Primfeed user-agent: ${FS_PF_USER_AGENT}") +else (FS_PF_USER_AGENT) + message(STATUS "Compiling with standard Primfeed user-agent") +endif (FS_PF_USER_AGENT) +# # [AVX Optimization] option(USE_AVX_OPTIMIZATION "AVX optimization support" OFF) option(USE_AVX2_OPTIMIZATION "AVX2 optimization support" OFF) diff --git a/indra/llcorehttp/_httplibcurl.cpp b/indra/llcorehttp/_httplibcurl.cpp index 4988e1e4c0..b42fe865e7 100644 --- a/indra/llcorehttp/_httplibcurl.cpp +++ b/indra/llcorehttp/_httplibcurl.cpp @@ -432,7 +432,8 @@ bool HttpLibcurl::completeRequest(CURLM * multi_handle, CURL * handle, CURLcode } if (!bFailed && (op->mReqOffset || op->mReqLength)) { - if (op->mReqOffset != op->mReplyOffset || (op->mReqLength && op->mReqLength < op->mReplyLength)) + // We should only check the offset and length if we are handling a partial content request. + if (op->mStatus == HttpStatus(HTTP_PARTIAL_CONTENT) && (op->mReqOffset != op->mReplyOffset || (op->mReqLength && op->mReqLength < op->mReplyLength))) { std::stringstream strm; strm << "HTTP pipelining possibly out of sync, request wanted: " << op->mReqOffset << "-"; diff --git a/indra/llinventory/llsettingssky.cpp b/indra/llinventory/llsettingssky.cpp index 95bdcd960c..97015dc68b 100644 --- a/indra/llinventory/llsettingssky.cpp +++ b/indra/llinventory/llsettingssky.cpp @@ -661,6 +661,7 @@ void LLSettingsSky::blend(LLSettingsBase::ptr_t &end, F64 blendf) mHasLegacyHaze |= lerp_legacy_float(mHazeDensity, mLegacyHazeDensity, other->mHazeDensity, other->mLegacyHazeDensity, 0.7f, (F32)blendf); mHasLegacyHaze |= lerp_legacy_float(mDistanceMultiplier, mLegacyDistanceMultiplier, other->mDistanceMultiplier, other->mLegacyDistanceMultiplier, 0.8f, (F32)blendf); mHasLegacyHaze |= lerp_legacy_float(mDensityMultiplier, mLegacyDensityMultiplier, other->mDensityMultiplier, other->mLegacyDensityMultiplier, 0.0001f, (F32)blendf); + mHasLegacyHaze |= lerp_legacy_color(mAmbientColor, mLegacyAmbientColor, other->mAmbientColor, other->mLegacyAmbientColor, LLColor3(0.25f, 0.25f, 0.25f), (F32)blendf); mHasLegacyHaze |= lerp_legacy_color(mBlueHorizon, mLegacyBlueHorizon, other->mBlueHorizon, other->mLegacyBlueHorizon, LLColor3(0.4954f, 0.4954f, 0.6399f), (F32)blendf); mHasLegacyHaze |= lerp_legacy_color(mBlueDensity, mLegacyBlueDensity, other->mBlueDensity, other->mLegacyBlueDensity, LLColor3(0.2447f, 0.4487f, 0.7599f), (F32)blendf); diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp index f3f4cf68ba..16016d7aaa 100644 --- a/indra/llui/llscrolllistctrl.cpp +++ b/indra/llui/llscrolllistctrl.cpp @@ -430,6 +430,13 @@ bool LLScrollListCtrl::setMaxItemCount(S32 max_count) return (max_count == mMaxItemCount); } +// +S32 LLScrollListCtrl::getMaxItemCount() +{ + return mMaxItemCount; +} +// + S32 LLScrollListCtrl::isEmpty() const { return mItemList.empty(); diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h index f67563fcf4..8c220808f8 100644 --- a/indra/llui/llscrolllistctrl.h +++ b/indra/llui/llscrolllistctrl.h @@ -213,6 +213,7 @@ public: // returns false if unable to set the max count so low bool setMaxItemCount(S32 max_count); + S32 getMaxItemCount(); // bool selectByID( const LLUUID& id ); // false if item not found diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index aef53389de..34863f03a3 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -54,7 +54,7 @@ include(jemalloc) include(Discord) # if using ndPhysicsstub this variable will be unset, we don't need to build any stub code viewer side in that case -if( LLPHYSICSEXTENSIONS_SRC_DIR ) +if (LLPHYSICSEXTENSIONS_SRC_DIR) # if (NOT HAVOK_TPV) @@ -81,7 +81,7 @@ if (NOT HAVOK_TPV) endif (NOT HAVOK_TPV) # -endif( LLPHYSICSEXTENSIONS_SRC_DIR ) +endif (LLPHYSICSEXTENSIONS_SRC_DIR) # set(viewer_SOURCE_FILES @@ -128,7 +128,7 @@ set(viewer_SOURCE_FILES fsfloaternearbychat.cpp fsfloaterpartialinventory.cpp fsfloaterplacedetails.cpp - fsfloaterposer.cpp + fsfloaterposer.cpp fsfloaterposestand.cpp fsfloaterprotectedfolders.cpp fsfloaterradar.cpp @@ -141,7 +141,7 @@ set(viewer_SOURCE_FILES fsfloatervramusage.cpp fsfloaterwearablefavorites.cpp fsfloaterwhitelisthelper.cpp - fsjointpose.cpp + fsjointpose.cpp fskeywords.cpp fslslbridge.cpp fslslbridgerequest.cpp @@ -163,8 +163,8 @@ set(viewer_SOURCE_FILES fspanelradar.cpp fsparticipantlist.cpp fspose.cpp - fsposeranimator.cpp - fsposingmotion.cpp + fsposeranimator.cpp + fsposingmotion.cpp fsprimfeedauth.cpp fsradar.cpp fsradarentry.cpp @@ -174,7 +174,7 @@ set(viewer_SOURCE_FILES fsscriptlibrary.cpp fsscrolllistctrl.cpp fsslurlcommand.cpp - fsvirtualtrackpad.cpp + fsvirtualtrackpad.cpp fsworldmapmessage.cpp lggbeamcolormapfloater.cpp lggbeammapfloater.cpp @@ -194,10 +194,12 @@ set(viewer_SOURCE_FILES vjlocalmeshimportdae.cpp gltfscenemanager.cpp - gltf/asset.cpp - gltf/accessor.cpp - gltf/primitive.cpp - gltf/animation.cpp + # Group GLTF files into their subfolders + #gltf/asset.cpp + #gltf/accessor.cpp + #gltf/primitive.cpp + #gltf/animation.cpp + # Group GLTF files into their subfolders groupchatlistener.cpp llaccountingcostmanager.cpp llaisapi.cpp @@ -885,6 +887,18 @@ set(viewer_SOURCE_FILES NACLfloaterexploresounds.cpp ) +# Group GLTF files into their subfolders +set(viewer_GLTF_SOURCE_FILES + gltf/asset.cpp + gltf/accessor.cpp + gltf/primitive.cpp + gltf/animation.cpp + ) + +source_group("Source Files\\GLTF" FILES ${viewer_GLTF_SOURCE_FILES}) +list(APPEND viewer_SOURCE_FILES ${viewer_GLTF_SOURCE_FILES}) +# Group GLTF files into their subfolders + if (OPENSIM) list(APPEND viewer_SOURCE_FILES fsgridhandler.cpp @@ -949,7 +963,7 @@ set(viewer_HEADER_FILES fsfloaternearbychat.h fsfloaterpartialinventory.h fsfloaterplacedetails.h - fsfloaterposer.h + fsfloaterposer.h fsfloaterposestand.h fsfloaterprotectedfolders.h fsfloaterradar.h @@ -985,8 +999,8 @@ set(viewer_HEADER_FILES fspanelradar.h fsparticipantlist.h fspose.h - fsposeranimator.h - fsposingmotion.h + fsposeranimator.h + fsposingmotion.h fsprimfeedauth.h fsradar.h fsradarentry.h @@ -997,7 +1011,7 @@ set(viewer_HEADER_FILES fsscrolllistctrl.h fsslurl.h fsslurlcommand.h - fsvirtualtrackpad.h + fsvirtualtrackpad.h fsworldmapmessage.h lggbeamcolormapfloater.h lggbeammapfloater.h @@ -1017,11 +1031,13 @@ set(viewer_HEADER_FILES gltfscenemanager.h groupchatlistener.h - gltf/asset.h - gltf/accessor.h - gltf/buffer_util.h - gltf/primitive.h - gltf/animation.h + # Group GLTF files into their subfolders + #gltf/asset.h + #gltf/accessor.h + #gltf/buffer_util.h + #gltf/primitive.h + #gltf/animation.h + # Group GLTF files into their subfolders llaccountingcost.h llaccountingcostmanager.h llaisapi.h @@ -1710,6 +1726,19 @@ set(viewer_HEADER_FILES list(APPEND viewer_SOURCE_FILES llperfstats.cpp) list(APPEND viewer_HEADER_FILES llperfstats.h) +# Group GLTF files into their subfolders +set(viewer_GLTF_HEADER_FILES + gltf/asset.h + gltf/accessor.h + gltf/buffer_util.h + gltf/primitive.h + gltf/animation.h + ) + +source_group("Header Files\\GLTF" FILES ${viewer_GLTF_HEADER_FILES}) +list(APPEND viewer_HEADER_FILES ${viewer_GLTF_HEADER_FILES}) +# Group GLTF files into their subfolders + if (USE_BUGSPLAT) list(APPEND viewer_SOURCE_FILES bugsplatattributes.cpp diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 7788d78c2a..901db4ef92 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -26366,6 +26366,17 @@ Change of this parameter will affect the layout of buttons in notification toast Value 1 + FSManipShowJointMarkers + + Comment + Show small markers where the selectable joints are. + Persist + 1 + Type + Boolean + Value + 1 + FSLocalMeshApplyJointOffsets Comment diff --git a/indra/newview/fsfloaterprimfeed.cpp b/indra/newview/fsfloaterprimfeed.cpp index c665d09198..b740cecc06 100644 --- a/indra/newview/fsfloaterprimfeed.cpp +++ b/indra/newview/fsfloaterprimfeed.cpp @@ -74,9 +74,14 @@ FSPrimfeedPhotoPanel::FSPrimfeedPhotoPanel() : mDescriptionTextBox(nullptr), mLocationCheckbox(nullptr), mRatingComboBox(nullptr), + mStoresComboBox(nullptr), mPostButton(nullptr), mBtnPreview(nullptr), - mBigPreviewFloater(nullptr) + mBigPreviewFloater(nullptr), + mCancelButton(nullptr), + mCommercialCheckbox(nullptr), + mFilterComboBox(nullptr), + mPublicGalleryCheckbox(nullptr) { mCommitCallbackRegistrar.add("SocialSharing.SendPhoto", [this](LLUICtrl*, const LLSD&) { onSend(); }); mCommitCallbackRegistrar.add("SocialSharing.RefreshPhoto", [this](LLUICtrl*, const LLSD&) { onClickNewSnapshot(); }); @@ -88,6 +93,16 @@ FSPrimfeedPhotoPanel::FSPrimfeedPhotoPanel() : LL_DEBUGS("primfeed") << "Info button clicked, opening " << url << LL_ENDL; LLWeb::loadURLExternal(url); }); + FSPrimfeedAuth::sPrimfeedAuthPump->listen("FSPrimfeedPhotoPanel", + [this](const LLSD& data) + { + if (data["responseType"].asString() == "primfeed_user_info") + { + this->loadPrimfeedInfo(data); + return true; + } + return false; + }); } FSPrimfeedPhotoPanel::~FSPrimfeedPhotoPanel() @@ -97,8 +112,6 @@ FSPrimfeedPhotoPanel::~FSPrimfeedPhotoPanel() mPreviewHandle.get()->die(); } - FSPrimfeedAuth::sPrimfeedAuthPump->stopListening("FSPrimfeedAccountPanel"); - gSavedSettings.setS32("FSLastSnapshotToPrimfeedResolution", getChild("resolution_combobox")->getCurrentIndex()); gSavedSettings.setS32("FSLastSnapshotToPrimfeedWidth", getChild("custom_snapshot_width")->getValue().asInteger()); gSavedSettings.setS32("FSLastSnapshotToPrimfeedHeight", getChild("custom_snapshot_height")->getValue().asInteger()); @@ -121,6 +134,7 @@ bool FSPrimfeedPhotoPanel::postBuild() mCommercialCheckbox = getChild("primfeed_commercial_content"); mPublicGalleryCheckbox = getChild("primfeed_add_to_public_gallery"); mRatingComboBox = getChild("rating_combobox"); + mStoresComboBox = getChild("stores_combobox"); mPostButton = getChild("post_photo_btn"); mCancelButton = getChild("cancel_photo_btn"); mBigPreviewFloater = dynamic_cast(LLFloaterReg::getInstance("big_preview")); @@ -192,6 +206,8 @@ void FSPrimfeedPhotoPanel::draw() mCancelButton->setEnabled(can_post); mDescriptionTextBox->setEnabled(can_post); mRatingComboBox->setEnabled(can_post); + // If the stores combo box is present, enable it only if we have stores to select from + mStoresComboBox->setEnabled(can_post && mStoresComboBox->getItemCount() > 1); mResolutionComboBox->setEnabled(can_post); mFilterComboBox->setEnabled(can_post); mRefreshBtn->setEnabled(can_post); @@ -327,7 +343,7 @@ bool FSPrimfeedPhotoPanel::onPrimfeedConnectStateChange(const LLSD& /*data*/) { if (FSPrimfeedAuth::isAuthorized()) { - sendPhoto(); + return true; } return false; @@ -352,10 +368,10 @@ void FSPrimfeedPhotoPanel::sendPhoto() std::string description = mDescriptionTextBox->getValue().asString(); // Get the content rating - int content_rating = mRatingComboBox->getValue().asInteger(); - bool post_to_public_gallery = mPublicGalleryCheckbox->getValue().asBoolean(); - bool commercial_content = mCommercialCheckbox->getValue().asBoolean(); - + int content_rating = mRatingComboBox->getValue().asInteger(); + bool post_to_public_gallery = mPublicGalleryCheckbox->getValue().asBoolean(); + bool commercial_content = mCommercialCheckbox->getValue().asBoolean(); + std::string store_id = mStoresComboBox->getValue().asString(); // Get the image LLSnapshotLivePreview* previewp = getPreviewView(); @@ -376,6 +392,11 @@ void FSPrimfeedPhotoPanel::sendPhoto() params["location"] = slurl_string; } + if (!store_id.empty()) + { + // Add the store ID if we have one selected + params["store_id"] = store_id; + } FSPrimfeedConnect::instance().uploadPhoto(params, previewp->getFormattedImage().get(), [this](bool success, const std::string& url) @@ -529,6 +550,35 @@ void FSPrimfeedPhotoPanel::checkAspectRatio(S32 index) } } +void FSPrimfeedPhotoPanel::loadPrimfeedInfo(const LLSD& data) +{ + if (!mStoresComboBox) + return; + + // Clear any existing entries + mStoresComboBox->clearRows(); + mStoresComboBox->add(LLTrans::getString("Personal"), LLSD("")); + + // Read the saved stores array + const LLSD& stores = data["stores"]; + if (!stores.isArray() || stores.size() == 0) + { + // No stores - disable the combobox + mStoresComboBox->setEnabled(false); + return; + } + + mStoresComboBox->setEnabled(true); + for (S32 i = 0; i < stores.size(); ++i) + { + const LLSD& store = stores[i]; + std::string id = store["id"].asString(); + std::string name = store["name"].asString(); + mStoresComboBox->add(name, LLSD(id)); + } + mStoresComboBox->setCurrentByIndex(0); +} + LLUICtrl* FSPrimfeedPhotoPanel::getRefreshBtn() { return mRefreshBtn; @@ -536,13 +586,10 @@ LLUICtrl* FSPrimfeedPhotoPanel::getRefreshBtn() void FSPrimfeedPhotoPanel::onOpen(const LLSD& key) { - if (!FSPrimfeedAuth::isAuthorized()) - { - // Reauthorise if necessary. - FSPrimfeedAuth::initiateAuthRequest(); - LLSD dummy; - onPrimfeedConnectStateChange(dummy); - } + // Reauthorise if necessary. + FSPrimfeedAuth::initiateAuthRequest(); + LLSD dummy; + onPrimfeedConnectStateChange(dummy); } void FSPrimfeedPhotoPanel::uploadCallback(bool success, const LLSD& response) @@ -639,9 +686,23 @@ FSPrimfeedAccountPanel::FSPrimfeedAccountPanel() : FSPrimfeedAuth::sPrimfeedAuthPump->listen("FSPrimfeedAccountPanel", [this](const LLSD& data) { - bool success = data["success"].asBoolean(); - primfeedAuthResponse(success, data); - return true; + if (data.has("responseType")) + { + auto response_type = data["responseType"].asString(); + if (response_type == "primfeed_auth_response") + { + bool success = data["success"].asBoolean(); + primfeedAuthResponse(success, data); + return true; + } + if (response_type == "primfeed_auth_reset") + { + bool success = data["success"].asBoolean(); + primfeedAuthResponse(success, data); + return true; + } + } + return false; }); setVisibleCallback([this](LLUICtrl*, bool visible) { onVisibilityChange(visible); }); @@ -663,8 +724,8 @@ bool FSPrimfeedAccountPanel::postBuild() void FSPrimfeedAccountPanel::draw() { - FSPrimfeedConnect::EConnectionState connection_state = FSPrimfeedConnect::instance().getConnectionState(); - static FSPrimfeedConnect::EConnectionState last_state = FSPrimfeedConnect::PRIMFEED_DISCONNECTED; + FSPrimfeedConnect::EConnectionState connection_state = FSPrimfeedConnect::instance().getConnectionState(); + static FSPrimfeedConnect::EConnectionState last_state = FSPrimfeedConnect::PRIMFEED_DISCONNECTED; // Update the connection state if it has changed if (connection_state != last_state) @@ -772,14 +833,20 @@ void FSPrimfeedAccountPanel::onConnect() { FSPrimfeedAuth::initiateAuthRequest(); LLSD dummy; - onPrimfeedConnectStateChange(dummy); + onPrimfeedConnectStateChange(dummy); } void FSPrimfeedAccountPanel::onDisconnect() { FSPrimfeedAuth::resetAuthStatus(); LLSD dummy; - onPrimfeedConnectStateChange(dummy); + onPrimfeedConnectStateChange(dummy); +} + +FSPrimfeedAccountPanel::~FSPrimfeedAccountPanel() +{ + // Disconnect the primfeed auth pump + FSPrimfeedAuth::sPrimfeedAuthPump->stopListening("FSPrimfeedAccountPanel"); } //////////////////////// @@ -789,10 +856,10 @@ void FSPrimfeedAccountPanel::onDisconnect() FSFloaterPrimfeed::FSFloaterPrimfeed(const LLSD& key) : LLFloater(key), mPrimfeedPhotoPanel(nullptr), + mPrimfeedAccountPanel(nullptr), mStatusErrorText(nullptr), mStatusLoadingText(nullptr), - mStatusLoadingIndicator(nullptr), - mPrimfeedAccountPanel(nullptr) + mStatusLoadingIndicator(nullptr) { mCommitCallbackRegistrar.add("SocialSharing.Cancel", [this](LLUICtrl*, const LLSD&) { onCancel(); }); } diff --git a/indra/newview/fsfloaterprimfeed.h b/indra/newview/fsfloaterprimfeed.h index 312f06c130..e1c33a6182 100644 --- a/indra/newview/fsfloaterprimfeed.h +++ b/indra/newview/fsfloaterprimfeed.h @@ -34,6 +34,7 @@ class LLIconCtrl; class LLCheckBoxCtrl; +class LLComboBox; class LLSnapshotLivePreview; class LLFloaterBigPreview; @@ -52,6 +53,7 @@ public: bool postBuild() override; S32 notify(const LLSD& info); void draw() override; + void loadPrimfeedInfo(const LLSD& data); LLSnapshotLivePreview* getPreviewView(); void onVisibilityChange(bool new_visibility); @@ -80,28 +82,30 @@ private: LLHandle mPreviewHandle; - LLUICtrl * mResolutionComboBox; - LLUICtrl * mFilterComboBox; - LLUICtrl * mRefreshBtn; - LLUICtrl * mWorkingLabel; - LLUICtrl * mThumbnailPlaceholder; - LLUICtrl * mDescriptionTextBox; - LLUICtrl * mLocationCheckbox; + LLUICtrl* mResolutionComboBox; + LLUICtrl* mFilterComboBox; + LLUICtrl* mRefreshBtn; + LLUICtrl* mWorkingLabel; + LLUICtrl* mThumbnailPlaceholder; + LLUICtrl* mDescriptionTextBox; + LLUICtrl* mLocationCheckbox; - LLUICtrl * mCommercialCheckbox; - LLUICtrl * mPublicGalleryCheckbox; - LLUICtrl * mRatingComboBox; - LLUICtrl * mPostButton; - LLUICtrl * mCancelButton; - LLButton * mBtnPreview; + LLUICtrl* mCommercialCheckbox; + LLUICtrl* mPublicGalleryCheckbox; + LLUICtrl* mRatingComboBox; + LLUICtrl* mPostButton; + LLUICtrl* mCancelButton; + LLButton* mBtnPreview; + LLComboBox* mStoresComboBox; - LLFloaterBigPreview * mBigPreviewFloater; + LLFloaterBigPreview* mBigPreviewFloater; }; class FSPrimfeedAccountPanel : public LLPanel { public: FSPrimfeedAccountPanel(); + ~FSPrimfeedAccountPanel(); bool postBuild() override; void draw() override; diff --git a/indra/newview/fsmaniprotatejoint.cpp b/indra/newview/fsmaniprotatejoint.cpp index da1bd33a90..dbd0d5c663 100644 --- a/indra/newview/fsmaniprotatejoint.cpp +++ b/indra/newview/fsmaniprotatejoint.cpp @@ -68,10 +68,10 @@ * @return void * */ -static void renderPulsingSphere(const LLVector3& joint_world_position, const LLColor4& color = LLColor4(1.f, 1.f, 1.f, 1.f)) +static void renderPulsingSphere(const LLVector3& joint_world_position, const LLColor4& color = LLColor4(0.f, 0.f, 1.f, 0.3f)) { - constexpr float MAX_SPHERE_RADIUS = 0.05f; // Base radius in agent-space units. - constexpr float PULSE_AMPLITUDE = 0.01f; // Additional radius variation. + constexpr float MAX_SPHERE_RADIUS = 0.02f; // Base radius in agent-space units. + constexpr float PULSE_AMPLITUDE = 0.005f; // Additional radius variation. constexpr float PULSE_FREQUENCY = 1.f; // Pulses per second. constexpr float PULSE_TIME_DOMAIN = 5.f; // Keep the time input small. @@ -104,9 +104,8 @@ static void renderPulsingSphere(const LLVector3& joint_world_position, const LLC LLGLDepthTest gls_depth(GL_FALSE); gGL.pushMatrix(); { - LLColor4 color; - gGL.color4f(0.f, 0.f, 1.f, 0.3f); - gGL.diffuseColor4f(0.f, 0.f, 1.f, 0.3f); + gGL.color4fv(color.mV); + gGL.diffuseColor4fv(color.mV); gGL.scalef(currentRadius, currentRadius, currentRadius); @@ -129,37 +128,82 @@ static void renderPulsingSphere(const LLVector3& joint_world_position, const LLC } } - -static bool isMouseOverJoint(S32 mouseX, S32 mouseY, const LLVector3& jointWorldPos, F32 jointRadius, F32& outDistanceFromCamera) +static void renderStaticSphere(const LLVector3& joint_world_position, const LLColor4& color = LLColor4(1.f, 1.f, 0.f, .6f), float radius=0.01f) { - LLViewerCamera* camera = LLViewerCamera::getInstance(); + LLGLSUIDefault gls_ui; + gGL.getTexUnit(0)->bind(LLViewerFetchedTexture::sWhiteImagep); + LLGLDepthTest gls_depth(GL_TRUE); + LLGLEnable gl_blend(GL_BLEND); - // Transform joint world position to screen coordinates - LLCoordGL jointScreenPos; - camera->projectPosAgentToScreen(jointWorldPos, jointScreenPos); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + { - // Get the world view rect - LLRect world_view_rect = gViewerWindow->getWorldViewRectScaled(); - F32 half_width = (F32)world_view_rect.getWidth() / 2.f; - F32 half_height = (F32)world_view_rect.getHeight() / 2.f; + // Translate to the joint's position + gGL.translatef(joint_world_position.mV[VX], joint_world_position.mV[VY], joint_world_position.mV[VZ]); + gGL.pushMatrix(); + { + gDebugProgram.bind(); - // Convert mouse coordinates to be relative to the center of the screen - LLVector2 mousePos((F32)mouseX - half_width, (F32)mouseY - half_height); + LLGLEnable cull_face(GL_CULL_FACE); + LLGLDepthTest gls_depth(GL_FALSE); + gGL.pushMatrix(); + { + gGL.color4fv(color.mV); + gGL.diffuseColor4fv(color.mV); - // Convert joint screen position to be relative to the center of the screen - LLVector2 joint2d(jointScreenPos.mX - half_width, jointScreenPos.mY - half_height); + gGL.scalef(radius, radius, radius); - // Calculate the distance between mouse and joint in screen space - LLVector2 delta = joint2d - mousePos; + gSphere.render(); + gGL.flush(); + } + gGL.popMatrix(); - // Calculate the distance from the camera to the joint - outDistanceFromCamera = (jointWorldPos - camera->getOrigin()).magVec(); + gUIProgram.bind(); + } + gGL.popMatrix(); + } + gGL.popMatrix(); - // Calculate the apparent radius of the joint on the screen - F32 apparentRadius = jointRadius * camera->getPixelMeterRatio() / outDistanceFromCamera; + // Check for OpenGL errors + GLenum err; + while ((err = glGetError()) != GL_NO_ERROR) + { + LL_INFOS() << "OpenGL Error: " << err << LL_ENDL; + } +} - // Check if the mouse is within the joint's radius - return (delta.magVecSquared() < apparentRadius * apparentRadius); + +bool FSManipRotateJoint::isMouseOverJoint(S32 mouseX, S32 mouseY, const LLVector3& jointWorldPos, F32 jointRadius, F32& outDistanceFromCamera, F32& outRayDistanceFromCenter) const +{ + // LL_INFOS("FSManipRotateJoint") << "Checking mouse("<< mouseX << "," << mouseY << ") over joint at: " << jointWorldPos << LL_ENDL; + + auto joint_center = gAgent.getPosGlobalFromAgent( jointWorldPos ); + + // centre in *agent* space + LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(joint_center); + LLVector3 ray_pt, ray_dir; + LLManipRotate::mouseToRay(mouseX, mouseY, &ray_pt, &ray_dir); + + // Vector from ray origin to sphere center + LLVector3 to_center = agent_space_center - ray_pt; + // Project that onto the ray direction + F32 proj_len = ray_dir * to_center; + + if (proj_len > 0.f) + { + // Closest approach squared = |to_center|^2 – (proj_len)^2 + F32 closest_dist_sq = to_center.magVecSquared() - (proj_len * proj_len); + if (closest_dist_sq <= jointRadius * jointRadius) + { + // ray *does* hit the sphere; compute the entrance intersection distance + F32 offset = sqrtf(jointRadius*jointRadius - closest_dist_sq); + outDistanceFromCamera = proj_len - offset; // distance along the ray to the front intersection + outRayDistanceFromCenter = offset; + return true; + } + } + return (false); } //static @@ -285,8 +329,12 @@ void FSManipRotateJoint::highlightHoverSpheres(S32 mouseX, S32 mouseY) mHighlightedJoint = nullptr; // reset the highlighted joint // Iterate through the avatar's joint map. - for (const auto& entry : getSelectableJoints()) + F32 nearest_hit_distance = 0.f; + F32 nearest_ray_distance = 0.f; + LLJoint * nearest_joint = nullptr; + for ( const auto& entry : getSelectableJoints()) { + LLJoint* joint = mAvatar->getJoint(std::string(entry)); if (!joint) continue; @@ -297,18 +345,24 @@ void FSManipRotateJoint::highlightHoverSpheres(S32 mouseX, S32 mouseY) // Retrieve the joint's world position (in agent space). LLVector3 jointWorldPos = joint->getWorldPosition(); - LLCachedControl target_radius(gSavedSettings, "FSManipRotateJointTargetSize", 0.2f); + LLCachedControl target_radius(gSavedSettings, "FSManipRotateJointTargetSize", 0.03f); F32 distance_from_camera; - if (isMouseOverJoint(mouseX, mouseY, jointWorldPos, target_radius, distance_from_camera) == true) + F32 distance_from_joint; + if (isMouseOverJoint(mouseX, mouseY, jointWorldPos, target_radius, distance_from_camera, distance_from_joint) == true) { // we want to highlight the closest - if (!mHighlightedJoint || mHighlightedPartDistance > distance_from_camera) + // If there is no joint or + // this joint is a closer hit than the previous one + if (!nearest_joint || nearest_ray_distance > distance_from_camera || + (nearest_ray_distance == distance_from_camera && nearest_hit_distance > distance_from_joint)) { - mHighlightedJoint = joint; - mHighlightedPartDistance = distance_from_camera; + nearest_joint = joint; + nearest_hit_distance = distance_from_joint; + nearest_ray_distance = distance_from_camera; } } } + mHighlightedJoint = nearest_joint; } FSManipRotateJoint::FSManipRotateJoint(LLToolComposite* composite) @@ -375,6 +429,7 @@ bool FSManipRotateJoint::updateVisiblity() if (!hasMouseCapture()) { mRotationCenter = gAgent.getPosGlobalFromAgent( mJoint->getWorldPosition() ); + mCamEdgeOn = false; } bool visible = false; @@ -413,13 +468,6 @@ bool FSManipRotateJoint::updateVisiblity() } } - mCamEdgeOn = false; - F32 axis_onto_cam = mManipPart >= LL_ROT_X ? llabs( getConstraintAxis() * mCenterToCamNorm ) : 0.f; - if (axis_onto_cam < AXIS_ONTO_CAM_TOLERANCE) - { - mCamEdgeOn = true; - } - return visible; } @@ -654,22 +702,43 @@ void FSManipRotateJoint::render() } // update visibility and rotation center. - if (!updateVisiblity()) - { - return; - } + bool activeJointVisible = updateVisiblity(); // Setup GL state. LLGLSUIDefault gls_ui; gGL.getTexUnit(0)->bind(LLViewerFetchedTexture::sWhiteImagep); LLGLDepthTest gls_depth(GL_TRUE); LLGLEnable gl_blend(GL_BLEND); - // Optionally, if another joint is highlighted, render a pulsing sphere. - if (mHighlightedJoint && mJoint != mHighlightedJoint) + // Iterate through the avatar's joint map. + // If a joint other than the currently selected is highlighted, render a pulsing sphere. + // otherwise a small static sphere + for (const auto& entry : getSelectableJoints()) { - mHighlightedJoint->updateWorldMatrixParent(); - mHighlightedJoint->updateWorldMatrix(); - renderPulsingSphere(mHighlightedJoint->getWorldPosition()); + LLJoint* joint = mAvatar->getJoint(std::string(entry)); + if (!joint) + continue; + // Update the joint's world matrix to ensure its position is current. + joint->updateWorldMatrixParent(); + joint->updateWorldMatrix(); + + if( joint == mHighlightedJoint && joint != mJoint ) + { + renderPulsingSphere(joint->getWorldPosition()); + } + else if( joint != mJoint ) + { + // Render a static sphere for the joint being manipulated. + LLCachedControl show_joint_markers(gSavedSettings, "FSManipShowJointMarkers", true); + if(show_joint_markers) + { + renderStaticSphere(joint->getWorldPosition(), LLColor4(1.f, 0.5f, 0.f, 0.5f), 0.01f); + } + } + } + + if (!activeJointVisible) + { + return; } // Update joint world matrices. @@ -683,30 +752,21 @@ void FSManipRotateJoint::render() LLQuaternion currentLocalRot = mJoint->getRotation(); LLQuaternion rotatedNaturalAlignment = mNaturalAlignmentQuat * currentLocalRot; - rotatedNaturalAlignment.normalize(); // Compute the final world alignment: LLQuaternion final_world_alignment = rotatedNaturalAlignment * parentWorldRot; - final_world_alignment.normalize(); const LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(mRotationCenter); LLCachedControl use_natural_direction(gSavedSettings, "FSManipRotateJointUseNaturalDirection", true); LLQuaternion active_rotation = use_natural_direction? final_world_alignment : joint_world_rotation; + active_rotation.normalize(); // Render the manipulator rings in a separate function. gGL.matrixMode(LLRender::MM_MODELVIEW); renderAxes(agent_space_center, mRadiusMeters * 1.5f, active_rotation); renderManipulatorRings(agent_space_center, active_rotation); // Debug: render joint's Euler angles for diagnostic purposes. - LLVector3 euler_angles; - active_rotation.getEulerAngles(&euler_angles.mV[0], - &euler_angles.mV[1], - &euler_angles.mV[2]); - euler_angles *= RAD_TO_DEG; - euler_angles.mV[0] = ll_round(fmodf(euler_angles.mV[0] + 360.f, 360.f), 0.05f); - euler_angles.mV[1] = ll_round(fmodf(euler_angles.mV[1] + 360.f, 360.f), 0.05f); - euler_angles.mV[2] = ll_round(fmodf(euler_angles.mV[2] + 360.f, 360.f), 0.05f); - renderNameXYZ(euler_angles); + renderNameXYZ(active_rotation); } void FSManipRotateJoint::renderAxes(const LLVector3& agent_space_center, F32 size, const LLQuaternion& rotation) @@ -804,13 +864,28 @@ std::string FSManipRotateJoint::getManipPartString(EManipPart part) * @note This function assumes the existence of class member variables such as mLastAngle, mJoint, and mManipPart. * It also uses global functions and objects like gViewerWindow, LLUI, and LLFontGL. */ -void FSManipRotateJoint::renderNameXYZ(const LLVector3 &vec) +void FSManipRotateJoint::renderNameXYZ(const LLQuaternion& rot) { constexpr S32 PAD = 10; S32 window_center_x = gViewerWindow->getWorldViewRectScaled().getWidth() / 2; S32 window_center_y = gViewerWindow->getWorldViewRectScaled().getHeight() / 2; S32 vertical_offset = window_center_y - VERTICAL_OFFSET; + LLVector3 euler_angles; + rot.getEulerAngles(&euler_angles.mV[0], + &euler_angles.mV[1], + &euler_angles.mV[2]); + euler_angles *= RAD_TO_DEG; + for (S32 i = 0; i < 3; ++i) + { + // Ensure angles are in the range [0, 360) and rounded to 0.05f + euler_angles.mV[i] = ll_round(fmodf(euler_angles.mV[i] + 360.f, 360.f), 0.05f); + F32 rawDelta = euler_angles.mV[i] - mLastEuler.mV[i]; + if (rawDelta > 180.f) rawDelta -= 360.f; + else if (rawDelta < -180.f) rawDelta += 360.f; + mLastEuler[i] += rawDelta; + } + gGL.pushMatrix(); { LLUIImagePtr imagep = LLUI::getUIImage("Rounded_Square"); @@ -840,13 +915,22 @@ void FSManipRotateJoint::renderNameXYZ(const LLVector3 &vec) }; F32 base_y = (F32)(window_center_y + vertical_offset); - renderTextWithShadow(llformat("X: %.3f", vec.mV[VX]), window_center_x - 122.f, base_y, LLColor4(1.f, 0.5f, 0.5f, 1.f)); - renderTextWithShadow(llformat("Y: %.3f", vec.mV[VY]), window_center_x - 47.f, base_y, LLColor4(0.5f, 1.f, 0.5f, 1.f)); - renderTextWithShadow(llformat("Z: %.3f", vec.mV[VZ]), window_center_x + 28.f, base_y, LLColor4(0.5f, 0.5f, 1.f, 1.f)); - renderTextWithShadow(llformat("Δ: %.3f", mLastAngle * RAD_TO_DEG), window_center_x + 103.f, base_y, LLColor4(1.f, 0.65f, 0.f, 1.f)); + renderTextWithShadow(llformat("X: %.3f", mLastEuler.mV[VX]), window_center_x - 122.f, base_y, LLColor4(1.f, 0.5f, 0.5f, 1.f)); + renderTextWithShadow(llformat("Y: %.3f", mLastEuler.mV[VY]), window_center_x - 47.f, base_y, LLColor4(0.5f, 1.f, 0.5f, 1.f)); + renderTextWithShadow(llformat("Z: %.3f", mLastEuler.mV[VZ]), window_center_x + 28.f, base_y, LLColor4(0.5f, 0.5f, 1.f, 1.f)); + renderTextWithShadow(llformat("⟳: %.3f", mLastAngle * RAD_TO_DEG), window_center_x + 103.f, base_y, LLColor4(1.f, 0.65f, 0.f, 1.f)); base_y += 20.f; renderTextWithShadow(llformat("Joint: %s", mJoint->getName().c_str()), window_center_x - 130.f, base_y, LLColor4(1.f, 0.1f, 1.f, 1.f)); - renderTextWithShadow(llformat("Manip: %s", getManipPartString(mManipPart).c_str()), window_center_x + 30.f, base_y, LLColor4(1.f, 1.f, .1f, 1.f)); + renderTextWithShadow(llformat("Manip: %s%c", getManipPartString(mManipPart).c_str(), mCamEdgeOn?'*':' '), window_center_x + 30.f, base_y, LLColor4(1.f, 1.f, .1f, 1.f)); + if (mManipPart != LL_NO_PART) + { + LL_INFOS("FSManipRotateJoint") << "Joint: " << mJoint->getName() + << ", Manip: " << getManipPartString(mManipPart) + << ", Quaternion: " << rot + << ", Euler Angles: " << mLastEuler + << ", Delta Angle: " << mLastAngle * RAD_TO_DEG + << LL_ENDL; + } } gGL.popMatrix(); @@ -960,9 +1044,11 @@ bool FSManipRotateJoint::handleMouseDownOnPart(S32 x, S32 y, MASK mask) { // Constrained rotation. LLVector3 axis = setConstraintAxis(); // set the axis based on the manipulator part + + mLastEuler = LLVector3::zero; + F32 axis_onto_cam = llabs(axis * mCenterToCamNorm); - const F32 AXIS_ONTO_CAM_TOL = cos(85.f * DEG_TO_RAD); - if (axis_onto_cam < AXIS_ONTO_CAM_TOL) + if (axis_onto_cam < AXIS_ONTO_CAM_TOLERANCE) { LLVector3 up_from_axis = mCenterToCamNorm % axis; up_from_axis.normalize(); @@ -979,11 +1065,13 @@ bool FSManipRotateJoint::handleMouseDownOnPart(S32 x, S32 y, MASK mask) } LLVector3 projected_center_to_cam = mCenterToCamNorm - projected_vec(mCenterToCamNorm, axis); mMouseDown += mouse_depth * projected_center_to_cam; + mCamEdgeOn = true; // We are in edge mode, so we can use the mouse depth. } else { mMouseDown = findNearestPointOnRing(x, y, agent_space_center, axis) - agent_space_center; mMouseDown.normalize(); + mCamEdgeOn = false; // Not in edge mode, so we don't use the mouse depth. } mInitialIntersection = mMouseDown; } @@ -1021,6 +1109,7 @@ bool FSManipRotateJoint::handleMouseUp(S32 x, S32 y, MASK mask) setMouseCapture(false); mManipPart = LL_NO_PART; mLastAngle = 0.0f; + mCamEdgeOn = false; return true; } else if(mHighlightedJoint) @@ -1280,13 +1369,43 @@ LLQuaternion FSManipRotateJoint::dragUnconstrained(S32 x, S32 y) return sphere_rot * LLQuaternion(extra_angle, axis); } } + +static LLQuaternion extractTwist(const LLQuaternion& rot, const LLVector3& axis) +{ + // Copy and normalise the input (defensive) + LLQuaternion qnorm = rot; + qnorm.normalize(); + + // Extract vector part and scalar part + LLVector3 v(qnorm.mQ[VX], qnorm.mQ[VY], qnorm.mQ[VZ]); + F32 w = qnorm.mQ[VW]; + + // Project v onto the axis (removing any perpendicular component) + F32 dot = v * axis; + LLVector3 proj = axis * dot; // proj is now purely along 'axis' + + // Build the “twist” quaternion from (proj, w), then renormalize + LLQuaternion twist(proj.mV[VX], + proj.mV[VY], + proj.mV[VZ], + w); + if (w < 0.f) + { + twist = -twist; + } + twist.normalize(); + return twist; +} LLQuaternion FSManipRotateJoint::dragConstrained(S32 x, S32 y) { // Get the constraint axis from our joint manipulator. - // (See the adjusted getConstraintAxis() below.) LLVector3 constraint_axis = getConstraintAxis(); LLVector3 agent_space_center = gAgent.getPosAgentFromGlobal(mRotationCenter); - + if (mCamEdgeOn) + { + LLQuaternion freeRot = dragUnconstrained(x, y); + return extractTwist(freeRot, constraint_axis); + } // Project the current mouse position onto the plane defined by the constraint axis. LLVector3 projected_mouse; bool hit = getMousePointOnPlaneAgent(projected_mouse, x, y, agent_space_center, constraint_axis); diff --git a/indra/newview/fsmaniprotatejoint.h b/indra/newview/fsmaniprotatejoint.h index 5a8a05a03c..f260b16b02 100644 --- a/indra/newview/fsmaniprotatejoint.h +++ b/indra/newview/fsmaniprotatejoint.h @@ -35,7 +35,7 @@ class LLJoint; class LLVOAvatar; // or LLVOAvatarSelf, etc. namespace { - const F32 AXIS_ONTO_CAM_TOLERANCE = cos( 80.f * DEG_TO_RAD ); // cos() is not constexpr til c++26 + const F32 AXIS_ONTO_CAM_TOLERANCE = cos( 85.f * DEG_TO_RAD ); // cos() is not constexpr til c++26 constexpr F32 RADIUS_PIXELS = 100.f; // size in screen space constexpr S32 CIRCLE_STEPS = 100; constexpr F32 CIRCLE_STEP_SIZE = 2.0f * F_PI / CIRCLE_STEPS; @@ -83,7 +83,7 @@ public: void handleSelect() override; bool updateVisiblity(); void render() override; - void renderNameXYZ(const LLVector3 &vec); + void renderNameXYZ(const LLQuaternion& rot); bool handleMouseDown(S32 x, S32 y, MASK mask) override; bool handleMouseUp(S32 x, S32 y, MASK mask) override; bool handleHover(S32 x, S32 y, MASK mask) override; @@ -94,7 +94,6 @@ public: void highlightHoverSpheres(S32 mouseX, S32 mouseY); protected: - // void renderNameXYZ(const std::string name, const LLVector3 &vec); LLQuaternion dragUnconstrained( S32 x, S32 y ); LLQuaternion dragConstrained( S32 x, S32 y ); LLVector3 getConstraintAxis() const { return mConstraintAxis; }; @@ -109,10 +108,12 @@ protected: LLQuaternion mSavedJointRot; LLJoint * mHighlightedJoint = nullptr; F32 mHighlightedPartDistance = 0.f; + LLVector3 mLastEuler = LLVector3::zero; // last euler angles in degrees LLVector3 mInitialIntersection; // The initial point on the manipulator’s sphere (in agent space) const std::vector getSelectableJoints(){ return sSelectableJoints; }; private: + bool isMouseOverJoint(S32 mouseX, S32 mouseY, const LLVector3& jointWorldPos, F32 jointRadius, F32& outDistanceFromCamera, F32& outDistanceFromCenter) const; static const std::vector sSelectableJoints; // Structure holding parameters needed to render one manipulator ring. diff --git a/indra/newview/fspanelface.cpp b/indra/newview/fspanelface.cpp index e041218009..f860e2a343 100644 --- a/indra/newview/fspanelface.cpp +++ b/indra/newview/fspanelface.cpp @@ -33,6 +33,7 @@ #include "llagent.h" #include "llagentdata.h" +#include "llavatarappearancedefines.h" #include "llbutton.h" #include "llcalc.h" #include "llcheckboxctrl.h" @@ -760,7 +761,7 @@ void FSPanelFace::onMatTabChange() // Since we allow both PBR and BP textures to be applied at the same time, // we need to hide or show the GLTF material only locally based on the current tab. gSavedSettings.setBOOL("FSShowSelectedInBlinnPhong", (curr_mat == MATMEDIA_MATERIAL)); - if (curr_mat != MATMEDIA_PBR) + if (curr_mat == MATMEDIA_MATERIAL) LLSelectMgr::getInstance()->hideGLTFMaterial(); else LLSelectMgr::getInstance()->showGLTFMaterial(); @@ -1154,6 +1155,16 @@ void FSPanelFace::onVisibilityChange(bool new_visibility) gAgent.showLatestFeatureNotification("gltf"); } LLPanel::onVisibilityChange(new_visibility); + + // Since we allow both PBR and BP textures to be applied at the same time, + // we need to keep FSShowSelectedInBlinnPhong in sync in case we open or close the texture panel. + static LLCachedControl showSelectedinBP(gSavedSettings, "FSShowSelectedInBlinnPhong"); + bool should_hide_gltf = new_visibility && !showSelectedinBP && getCurrentMaterialType() == MATMEDIA_MATERIAL; + gSavedSettings.setBOOL("FSShowSelectedInBlinnPhong", should_hide_gltf); + if (should_hide_gltf) + { + LLSelectMgr::getInstance()->hideGLTFMaterial(); + } } void FSPanelFace::draw() @@ -6004,13 +6015,9 @@ void FSPanelFace::LLSelectedTE::getTexId(LLUUID& id, bool& identical) LLUUID get(LLViewerObject* object, S32 te_index) { LLTextureEntry *te = object->getTE(te_index); - if (te) + if (te && LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(te->getID())) { - if ((te->getID() == IMG_USE_BAKED_EYES) || (te->getID() == IMG_USE_BAKED_HAIR) || (te->getID() == IMG_USE_BAKED_HEAD) || (te->getID() == IMG_USE_BAKED_LOWER) || (te->getID() == IMG_USE_BAKED_SKIRT) || (te->getID() == IMG_USE_BAKED_UPPER) - || (te->getID() == IMG_USE_BAKED_LEFTARM) || (te->getID() == IMG_USE_BAKED_LEFTLEG) || (te->getID() == IMG_USE_BAKED_AUX1) || (te->getID() == IMG_USE_BAKED_AUX2) || (te->getID() == IMG_USE_BAKED_AUX3)) - { - return te->getID(); - } + return te->getID(); } LLUUID id; diff --git a/indra/newview/fsprimfeedauth.cpp b/indra/newview/fsprimfeedauth.cpp index c264c3b8dc..20b9b6a042 100644 --- a/indra/newview/fsprimfeedauth.cpp +++ b/indra/newview/fsprimfeedauth.cpp @@ -112,31 +112,25 @@ void FSPrimfeedAuth::initiateAuthRequest() // It should be called when the user clicks the "Authenticate" button. // Also triggered on opening the floater. // The actual implementation is in the create() method. - - if (!isAuthorized()) + + if (sPrimfeedAuth) { - if (sPrimfeedAuth) + LLNotificationsUtil::add("PrimfeedAuthorizationAlreadyInProgress"); + return; + } + // If no token stored, begin the login request; otherwise check user status. + sPrimfeedAuth = FSPrimfeedAuth::create( + [](bool success, const LLSD &response) { - LLNotificationsUtil::add("PrimfeedAuthorizationAlreadyInProgress"); - return; + LLSD event_data = response; + event_data["responseType"] = "primfeed_auth_response"; + event_data["success"] = success; + sPrimfeedAuthPump->post(event_data); + // Now that auth is complete, clear the static pointer. + sPrimfeedAuth.reset(); } - // If no token stored, begin the login request; otherwise check user status. - sPrimfeedAuth = FSPrimfeedAuth::create( - [](bool success, const LLSD &response) - { - LLSD event_data = response; - event_data["success"] = success; - sPrimfeedAuthPump->post(event_data); - // Now that auth is complete, clear the static pointer. - sPrimfeedAuth.reset(); - } - ); - FSPrimfeedConnect::instance().setConnectionState(FSPrimfeedConnect::PRIMFEED_CONNECTING); - } - else - { - LLNotificationsUtil::add("PrimfeedAlreadyAuthorized"); - } + ); + FSPrimfeedConnect::instance().setConnectionState(FSPrimfeedConnect::PRIMFEED_CONNECTING); } void FSPrimfeedAuth::resetAuthStatus() @@ -147,7 +141,7 @@ void FSPrimfeedAuth::resetAuthStatus() gSavedPerAccountSettings.setString("FSPrimfeedPlan", ""); gSavedPerAccountSettings.setString("FSPrimfeedUsername", ""); LLSD event_data; - event_data["status"] = "reset"; + event_data["responseType"] = "primfeed_auth_reset"; event_data["success"] = "false"; sPrimfeedAuthPump->post(event_data); FSPrimfeedConnect::instance().setConnectionState(FSPrimfeedConnect::PRIMFEED_DISCONNECTED); @@ -201,7 +195,8 @@ std::shared_ptr FSPrimfeedAuth::create(authorized_callback_t cal FSPrimfeedConnect::instance().setConnectionState(FSPrimfeedConnect::PRIMFEED_CONNECTING); // If no token stored, begin the login request; otherwise check user status. - if (gSavedPerAccountSettings.getString("FSPrimfeedOAuthToken").empty()) + auth->mOauthToken = gSavedPerAccountSettings.getString("FSPrimfeedOAuthToken"); + if (auth->mOauthToken.empty()) { auth->beginLoginRequest(); } @@ -225,6 +220,7 @@ void FSPrimfeedAuth::beginLoginRequest() LLCore::HttpHeaders::ptr_t pHeader(new LLCore::HttpHeaders()); LLCore::HttpOptions::ptr_t options(new LLCore::HttpOptions()); + pHeader->append(HTTP_OUT_HEADER_USER_AGENT, FS_PF_USER_AGENT); pHeader->append("pf-viewer-api-key", viewer_api_key); pHeader->append("pf-user-uuid", user_uuid); @@ -326,6 +322,7 @@ void FSPrimfeedAuth::validateRequest() // Create and populate the headers. LLCore::HttpHeaders::ptr_t pHeader(new LLCore::HttpHeaders()); + pHeader->append(HTTP_OUT_HEADER_USER_AGENT, FS_PF_USER_AGENT); pHeader->append("Authorization", "Bearer " + mOauthToken); pHeader->append("pf-viewer-api-key", viewer_api_key); pHeader->append("pf-viewer-request-id", mRequestId); @@ -398,6 +395,7 @@ void FSPrimfeedAuth::checkUserStatus() // Create and populate the headers. LLCore::HttpHeaders::ptr_t pHeader(new LLCore::HttpHeaders()); + pHeader->append(HTTP_OUT_HEADER_USER_AGENT, FS_PF_USER_AGENT); pHeader->append("Authorization", "Bearer " + mOauthToken); pHeader->append("pf-viewer-api-key", viewer_api_key); @@ -429,22 +427,23 @@ void FSPrimfeedAuth::checkUserStatus() } -void FSPrimfeedAuth::gotUserStatus(bool success, const LLSD &response) +void FSPrimfeedAuth::gotUserStatus(bool success, const LLSD &response) const { LL_INFOS("Primfeed") << "User status: " << response << "(" << success << ")" << LL_ENDL; - if (success && response.has("plan")) + if (success && response.has("plan") && response.has("username") && response.has("link")) { gSavedPerAccountSettings.setString("FSPrimfeedOAuthToken", mOauthToken); gSavedPerAccountSettings.setString("FSPrimfeedPlan", response["plan"].asString()); gSavedPerAccountSettings.setString("FSPrimfeedProfileLink", response["link"].asString()); gSavedPerAccountSettings.setString("FSPrimfeedUsername", response["username"].asString()); FSPrimfeedConnect::instance().setConnectionState(FSPrimfeedConnect::PRIMFEED_CONNECTED); + LLSD event_data = response; + event_data["responseType"] = "primfeed_user_info"; + sPrimfeedAuthPump->post(event_data); mCallback(true, response); + return; } - else - { - LLNotificationsUtil::add("PrimfeedUserStatusFailed"); - FSPrimfeedConnect::instance().setConnectionState(FSPrimfeedConnect::PRIMFEED_DISCONNECTED); - mCallback(false, response); - } + LLNotificationsUtil::add("PrimfeedUserStatusFailed"); + FSPrimfeedConnect::instance().setConnectionState(FSPrimfeedConnect::PRIMFEED_DISCONNECTED); + mCallback(false, response); } diff --git a/indra/newview/fsprimfeedauth.h b/indra/newview/fsprimfeedauth.h index 43fc36a937..5e3934269f 100644 --- a/indra/newview/fsprimfeedauth.h +++ b/indra/newview/fsprimfeedauth.h @@ -79,7 +79,7 @@ private: // Callback when the validate response is received. void gotValidateResponse(bool success, const LLSD &response); // Callback when the user status response is received. - void gotUserStatus(bool success, const LLSD &response); + void gotUserStatus(bool success, const LLSD &response) const; boost::signals2::connection mInstantMessageConnection; boost::signals2::connection mChatMessageConnection; diff --git a/indra/newview/fsprimfeedconnect.cpp b/indra/newview/fsprimfeedconnect.cpp index f6d91480a2..442a4789e5 100644 --- a/indra/newview/fsprimfeedconnect.cpp +++ b/indra/newview/fsprimfeedconnect.cpp @@ -89,6 +89,7 @@ void FSPrimfeedConnect::uploadPhotoCoro(const LLSD& params, LLImageFormatted* im addPart("location", params["location"].asString()); } + LL_DEBUGS("primfeed") << "Adding image file header" << LL_ENDL; body << dash << sep << "Content-Disposition: form-data; name=\"image\"; filename=\"snapshot." << fmt << "\"" << sep @@ -119,14 +120,14 @@ void FSPrimfeedConnect::uploadPhotoCoro(const LLSD& params, LLImageFormatted* im LLCore::HttpHeaders::ptr_t headers(new LLCore::HttpHeaders); std::string token = gSavedPerAccountSettings.getString("FSPrimfeedOAuthToken"); std::string apiKey = gSavedSettings.getString("FSPrimfeedViewerApiKey"); + headers->append(HTTP_OUT_HEADER_USER_AGENT, FS_PF_USER_AGENT); headers->append("Authorization", "Bearer " + token); headers->append("pf-viewer-api-key", apiKey); - headers->append("Content-Type", "multipart/form-data; boundary=" + boundary); - LL_DEBUGS("primfeed") << "Dumping HTTP headers for POST:" << LL_ENDL; - for (auto it = headers->begin(); it != headers->end(); ++it) + if (params.has("store_id") && !params["store_id"].asString().empty()) { - LL_DEBUGS("primfeed") << it->first << ": " << it->second << LL_ENDL; + headers->append("kynno-selected-store", params["store_id"].asString()); } + headers->append("Content-Type", "multipart/form-data; boundary=" + boundary); LL_DEBUGS("primfeed") << "Headers set" << LL_ENDL; LL_DEBUGS("primfeed") << "Starting HTTP POST" << LL_ENDL; @@ -181,9 +182,3 @@ bool FSPrimfeedConnect::isTransactionOngoing() const mConnectionState == PRIMFEED_DISCONNECTING); } -void FSPrimfeedConnect::loadPrimfeedInfo() -{ - LL_DEBUGS("primfeed") << "loadPrimfeedInfo() called" << LL_ENDL; - // Nothing to do here for Primfeed - setConnectionState(PRIMFEED_CONNECTED); -} \ No newline at end of file diff --git a/indra/newview/fsprimfeedconnect.h b/indra/newview/fsprimfeedconnect.h index 409a93faed..6ccd24c6e4 100644 --- a/indra/newview/fsprimfeedconnect.h +++ b/indra/newview/fsprimfeedconnect.h @@ -39,7 +39,9 @@ #include // Coro based connector designed to interface with floater designed along the same principles as LLFloaterFlickr.cpp - +#ifndef FS_PF_USER_AGENT +#define FS_PF_USER_AGENT "Firestorm-PF" +#endif // FS_PF_USER_AGENT class FSPrimfeedConnect : public LLSingleton { LLSINGLETON(FSPrimfeedConnect); diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp index d825423b39..3839fb6cde 100644 --- a/indra/newview/llfloaterland.cpp +++ b/indra/newview/llfloaterland.cpp @@ -68,6 +68,7 @@ #include "lluiconstants.h" #include "lluictrlfactory.h" #include "llviewertexturelist.h" // LLUIImageList +#include "llviewermenufile.h" // Ban and access lists export/import #include "llviewermessage.h" #include "llviewerparcelmgr.h" #include "llviewerregion.h" @@ -2680,6 +2681,17 @@ bool LLPanelLandAccess::postBuild() mBtnRemoveBanned = getChild("remove_banned"); mBtnRemoveBanned->setCommitCallback(boost::bind(&LLPanelLandAccess::onClickRemoveBanned, this)); + // Ban and access lists export/import + mBtnExportAccess = getChild("export_allowed"); + mBtnExportAccess->setCommitCallback(boost::bind(&LLPanelLandAccess::onClickExportAccess, this)); + mBtnExportBanned = getChild("export_banned"); + mBtnExportBanned->setCommitCallback(boost::bind(&LLPanelLandAccess::onClickExportBanned, this)); + mBtnImportAccess = getChild("import_allowed"); + mBtnImportAccess->setCommitCallback(boost::bind(&LLPanelLandAccess::onClickImportAccess, this)); + mBtnImportBanned = getChild("import_banned"); + mBtnImportBanned->setCommitCallback(boost::bind(&LLPanelLandAccess::onClickImportBanned, this)); + // Ban and access lists export/import + mListAccess = getChild("AccessList"); if (mListAccess) { @@ -2926,6 +2938,13 @@ void LLPanelLandAccess::refresh_ui() mBtnAddBanned->setEnabled(false); mBtnRemoveBanned->setEnabled(false); + // Ban and access lists export/import + mBtnExportAccess->setEnabled(false); + mBtnExportBanned->setEnabled(false); + mBtnImportAccess->setEnabled(false); + mBtnImportBanned->setEnabled(false); + // Ban and access lists export/import + LLParcel *parcel = mParcel->getParcel(); if (parcel && !gDisconnected) { @@ -2995,6 +3014,14 @@ void LLPanelLandAccess::refresh_ui() mBtnAddBanned->setEnabled(can_manage_banned && banned_list_count < PARCEL_MAX_ACCESS_LIST); has_selected = (mListBanned && mListBanned->getSelectionInterface()->getFirstSelectedIndex() >= 0); mBtnRemoveBanned->setEnabled(can_manage_banned && has_selected); + + // Ban and access lists export/import + mBtnExportAccess->setEnabled(can_manage_allowed && allowed_list_count > 0); + mBtnExportBanned->setEnabled(can_manage_banned && banned_list_count > 0); + mBtnImportAccess->setEnabled(can_manage_allowed && allowed_list_count < PARCEL_MAX_ACCESS_LIST); + mBtnImportBanned->setEnabled(can_manage_banned && banned_list_count < PARCEL_MAX_ACCESS_LIST); + // Ban and access lists export/import + } } @@ -3268,6 +3295,171 @@ void LLPanelLandAccess::onClickRemoveBanned() } } +// Ban and access lists export/import +void LLPanelLandAccess::onClickExportList(LLNameListCtrl* list, const std::string& filename) +{ + if (!list || list->getItemCount() == 0) + { + LLSD args; + args["MESSAGE"] = LLTrans::getString("ListEmpty"); + LLNotificationsUtil::add("GenericAlert", args); + return; + } + LLFilePickerReplyThread::startPicker(boost::bind(&LLPanelLandAccess::exportListCallback, this, list, _1), LLFilePicker::FFSAVE_CSV, filename); +} + +void LLPanelLandAccess::onClickExportAccess() +{ + onClickExportList(mListAccess, "land_access_list.csv"); +} + +void LLPanelLandAccess::onClickExportBanned() +{ + onClickExportList(mListBanned, "land_banned_list.csv"); +} + +void LLPanelLandAccess::exportListCallback(LLNameListCtrl* list, const std::vector& filenames) +{ + if (filenames.empty()) + { + return; + } + + std::string filename = filenames[0]; + std::ofstream file(filename.c_str()); + if (!file.is_open()) + { + LLNotificationsUtil::add("ExportFailed"); + return; + } + + file << "Name,UUID\n"; + std::vector items = list->getAllData(); + for (std::vector::iterator it = items.begin(); it != items.end(); ++it) + { + LLScrollListItem* item = *it; + if (item) + { + const LLUUID& id = item->getUUID(); + std::string name = item->getColumn(0)->getValue().asString(); + file << name << "," << id.asString() << "\n"; + } + } + file.close(); + + LLSD args; + args["FILENAME"] = filename; + LLNotificationsUtil::add("ExportFinished", args); +} + +void LLPanelLandAccess::onClickImportList(LLNameListCtrl* list) +{ + if (!list) + { + LLSD args; + args["MESSAGE"] = LLTrans::getString("ListEmpty"); + LLNotificationsUtil::add("GenericAlert", args); + return; + } + LLFilePickerReplyThread::startPicker(boost::bind(&LLPanelLandAccess::importListCallback, this, list, _1), LLFilePicker::FFLOAD_ALL, false); +} + +void LLPanelLandAccess::onClickImportAccess() +{ + onClickImportList(mListAccess); +} + +void LLPanelLandAccess::onClickImportBanned() +{ + onClickImportList(mListBanned); +} + +void LLPanelLandAccess::importListCallback(LLNameListCtrl* list, const std::vector& filenames) +{ + if (filenames.empty()) + { + return; + } + + std::string filename = filenames[0]; + + std::ifstream file(filename.c_str()); + if (!file.is_open()) + { + return; + } + + LLParcel* parcel = mParcel->getParcel(); + if (!parcel) + { + return; + } + + std::string line; + std::vector uuids; + + while (std::getline(file, line)) + { + LLStringUtil::trim(line); + if (line.empty()) + { + continue; + } + + LLUUID uuid; + if (uuid.set(line)) + { + uuids.push_back(uuid); + } + } + file.close(); + + if (uuids.empty()) + { + LLSD args; + args["MESSAGE"] = LLTrans::getString("NoValidUUIDs"); + LLNotificationsUtil::add("GenericAlert", args); + return; + } + + std::string listname = list->getName(); + S32 max_entries = PARCEL_MAX_ACCESS_LIST; + S32 current_count = list->getItemCount(); + S32 available_slots = max_entries - current_count; + + if (uuids.size() > available_slots) + { + LLSD args; + args["MAX"] = llformat("%d", available_slots); + args["COUNT"] = llformat("%d", uuids.size()); + args["MESSAGE"] = LLTrans::getString("ImportListTooLarge", args).c_str(); + LLNotificationsUtil::add("GenericAlert", args); + return; + } + + for (const LLUUID& uuid : uuids) + { + if (listname == "BannedList") + { + parcel->addToBanList(uuid, 0); + } + else + { + parcel->addToAccessList(uuid, 0); + } + } + + U32 flags = (listname == "BannedList") ? AL_BAN : AL_ACCESS; + LLViewerParcelMgr::getInstance()->sendParcelAccessListUpdate(flags); + refresh(); + + LLSD args; + args["COUNT"] = llformat("%d", uuids.size()); + args["MESSAGE"] = LLTrans::getString("ImportSuccessful", args).c_str(); + LLNotificationsUtil::add("GenericAlert", args); +} +// Ban and access lists export/import + //--------------------------------------------------------------------------- // LLPanelLandCovenant //--------------------------------------------------------------------------- diff --git a/indra/newview/llfloaterland.h b/indra/newview/llfloaterland.h index 501f7a6a0f..f55a997ad1 100644 --- a/indra/newview/llfloaterland.h +++ b/indra/newview/llfloaterland.h @@ -405,6 +405,17 @@ public: void callbackAvatarCBBanned2(const uuid_vec_t& ids, S32 duration); void callbackAvatarCBAccess(const uuid_vec_t& ids); + // Ban and access lists export/import + void onClickExportAccess(); + void onClickExportBanned(); + void onClickExportList(LLNameListCtrl* list, const std::string& filename); + void exportListCallback(LLNameListCtrl* list, const std::vector& filenames); + void onClickImportAccess(); + void onClickImportBanned(); + void onClickImportList(LLNameListCtrl* list); + void importListCallback(LLNameListCtrl* list, const std::vector& filenames); + // Ban and access lists export/import + protected: LLNameListCtrl* mListAccess; LLNameListCtrl* mListBanned; @@ -423,6 +434,13 @@ protected: LLButton* mBtnAddBanned = nullptr; LLButton* mBtnRemoveBanned = nullptr; + // Ban and access lists export/import + LLButton* mBtnExportAccess = nullptr; + LLButton* mBtnExportBanned = nullptr; + LLButton* mBtnImportAccess = nullptr; + LLButton* mBtnImportBanned = nullptr; + // Ban and access lists export/import + LLSafeHandle& mParcel; }; diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp index af9f29a2bd..e3aead4883 100644 --- a/indra/newview/llfloaterregioninfo.cpp +++ b/indra/newview/llfloaterregioninfo.cpp @@ -3509,6 +3509,10 @@ bool LLPanelEstateAccess::postBuild() childSetAction("add_allowed_avatar_btn", boost::bind(&LLPanelEstateAccess::onClickAddAllowedAgent, this)); childSetAction("remove_allowed_avatar_btn", boost::bind(&LLPanelEstateAccess::onClickRemoveAllowedAgent, this)); childSetAction("copy_allowed_list_btn", boost::bind(&LLPanelEstateAccess::onClickCopyAllowedList, this)); + // Ban and access lists export/import + childSetAction("export_allowed_list_btn", boost::bind(&LLPanelEstateAccess::onClickExportAllowedList, this)); + childSetAction("import_allowed_list_btn", boost::bind(&LLPanelEstateAccess::onClickImportAllowedList, this)); + // Ban and access lists export/import getChild("allowed_group_name_list")->setCommitCallback(boost::bind(&LLPanelEstateInfo::onChangeChildCtrl, this, _1)); LLNameListCtrl* group_name_list = getChild("allowed_group_name_list"); @@ -3522,6 +3526,10 @@ bool LLPanelEstateAccess::postBuild() getChild("add_allowed_group_btn")->setCommitCallback(boost::bind(&LLPanelEstateAccess::onClickAddAllowedGroup, this)); childSetAction("remove_allowed_group_btn", boost::bind(&LLPanelEstateAccess::onClickRemoveAllowedGroup, this)); childSetAction("copy_allowed_group_list_btn", boost::bind(&LLPanelEstateAccess::onClickCopyAllowedGroupList, this)); + // Ban and access lists export/import + childSetAction("export_allowed_group_btn", boost::bind(&LLPanelEstateAccess::onClickExportAllowedGroupList, this)); + childSetAction("import_allowed_group_btn", boost::bind(&LLPanelEstateAccess::onClickImportAllowedGroupList, this)); + // Ban and access lists export/import getChild("banned_avatar_name_list")->setCommitCallback(boost::bind(&LLPanelEstateInfo::onChangeChildCtrl, this, _1)); LLNameListCtrl* banned_name_list = getChild("banned_avatar_name_list"); @@ -3535,6 +3543,10 @@ bool LLPanelEstateAccess::postBuild() childSetAction("add_banned_avatar_btn", boost::bind(&LLPanelEstateAccess::onClickAddBannedAgent, this)); childSetAction("remove_banned_avatar_btn", boost::bind(&LLPanelEstateAccess::onClickRemoveBannedAgent, this)); childSetAction("copy_banned_list_btn", boost::bind(&LLPanelEstateAccess::onClickCopyBannedList, this)); + // Ban and access lists export/import + childSetAction("export_banned_avatar_btn", boost::bind(&LLPanelEstateAccess::onClickExportBannedList, this)); + childSetAction("import_banned_avatar_btn", boost::bind(&LLPanelEstateAccess::onClickImportBannedList, this)); + // Ban and access lists export/import getChild("estate_manager_name_list")->setCommitCallback(boost::bind(&LLPanelEstateInfo::onChangeChildCtrl, this, _1)); LLNameListCtrl* manager_name_list = getChild("estate_manager_name_list"); @@ -3546,6 +3558,10 @@ bool LLPanelEstateAccess::postBuild() childSetAction("add_estate_manager_btn", boost::bind(&LLPanelEstateAccess::onClickAddEstateManager, this)); childSetAction("remove_estate_manager_btn", boost::bind(&LLPanelEstateAccess::onClickRemoveEstateManager, this)); + // Ban and access lists export/import + childSetAction("export_estate_manager_btn", boost::bind(&LLPanelEstateAccess::onClickExportEstateManagerList, this)); + childSetAction("import_estate_manager_btn", boost::bind(&LLPanelEstateAccess::onClickImportEstateManagerList, this)); + // Ban and access lists export/import return true; } @@ -3590,6 +3606,17 @@ void LLPanelEstateAccess::updateControls(LLViewerRegion* region) getChildView("remove_estate_manager_btn")->setEnabled(has_estate_manager && (god || owner)); estateManagers->setEnabled(god || owner); + // Ban and access lists export/import + getChildView("export_estate_manager_btn")->setEnabled(god || owner); + getChildView("export_allowed_list_btn")->setEnabled(enable_cotrols); + getChildView("export_allowed_group_btn")->setEnabled(enable_cotrols); + getChildView("export_banned_avatar_btn")->setEnabled(enable_cotrols); + getChildView("import_estate_manager_btn")->setEnabled(god || owner); + getChildView("import_allowed_list_btn")->setEnabled(enable_cotrols); + getChildView("import_allowed_group_btn")->setEnabled(enable_cotrols); + getChildView("import_banned_avatar_btn")->setEnabled(enable_cotrols); + // Ban and access lists export/import + if (enable_cotrols != mCtrlsEnabled) { mCtrlsEnabled = enable_cotrols; @@ -4609,3 +4636,208 @@ void LLPanelRegionEnvironment::onChkAllowOverride(bool value) } } + +// Ban and access lists export/import +void LLPanelEstateAccess::onClickExportList(LLNameListCtrl* list, const std::string& filename) +{ + if (!list || list->getItemCount() == 0) + { + LLSD args; + args["MESSAGE"] = LLTrans::getString("ListEmpty"); + LLNotificationsUtil::add("GenericAlert", args); + return; + } + LLFilePickerReplyThread::startPicker(boost::bind(&LLPanelEstateAccess::exportListCallback, this, list, _1), LLFilePicker::FFSAVE_CSV, filename); +} + +void LLPanelEstateAccess::onClickExportEstateManagerList() +{ + onClickExportList(getChild("estate_manager_name_list"), "estate_manager_list.csv"); +} + +void LLPanelEstateAccess::onClickExportAllowedList() +{ + onClickExportList(getChild("allowed_avatar_name_list"), "estate_allowed_residents.csv"); +} + +void LLPanelEstateAccess::onClickExportAllowedGroupList() +{ + onClickExportList(getChild("allowed_group_name_list"), "estate_allowed_groups.csv"); +} + +void LLPanelEstateAccess::onClickExportBannedList() +{ + onClickExportList(getChild("banned_avatar_name_list"), "estate_banned_residents.csv"); +} + +void LLPanelEstateAccess::exportListCallback(LLNameListCtrl* list, const std::vector& filenames) +{ + if (filenames.empty()) + { + return; + } + + std::string filename = filenames[0]; + std::ofstream file(filename.c_str()); + if (!file.is_open()) + { + LLNotificationsUtil::add("ExportFailed"); + return; + } + + std::string listname = list->getName(); + + file << "Name,UUID"; + if (listname == "banned_avatar_name_list") + { + file << ",Last Login,Ban Date,Banned By"; + } + file << "\n"; + + std::vector items = list->getAllData(); + for (std::vector::iterator it = items.begin(); it != items.end(); ++it) + { + LLScrollListItem* item = *it; + if (item) + { + const LLUUID& id = item->getUUID(); + std::string name = item->getColumn(0)->getValue().asString(); + file << name << "," << id.asString(); + if (listname == "banned_avatar_name_list") + { + std::string last_login = item->getColumn(1)->getValue().asString(); + std::string ban_date = item->getColumn(2)->getValue().asString(); + std::string banned_by = item->getColumn(3)->getValue().asString(); + file << "," << last_login << "," << ban_date << "," << banned_by; + } + file << "\n"; + } + } + file.close(); + + LLSD args; + args["FILENAME"] = filename; + LLNotificationsUtil::add("ExportFinished", args); +} + +void LLPanelEstateAccess::onClickImportList(LLNameListCtrl* list) +{ + if (!list) + { + LLSD args; + args["MESSAGE"] = LLTrans::getString("ListEmpty"); + LLNotificationsUtil::add("GenericAlert", args); + return; + } + LLFilePickerReplyThread::startPicker(boost::bind(&LLPanelEstateAccess::importListCallback, this, list, _1), LLFilePicker::FFLOAD_ALL, false); +} + +void LLPanelEstateAccess::onClickImportEstateManagerList() +{ + onClickImportList(getChild("estate_manager_name_list")); +} + +void LLPanelEstateAccess::onClickImportAllowedList() +{ + onClickImportList(getChild("allowed_avatar_name_list")); +} + +void LLPanelEstateAccess::onClickImportAllowedGroupList() +{ + onClickImportList(getChild("allowed_group_name_list")); +} + +void LLPanelEstateAccess::onClickImportBannedList() +{ + onClickImportList(getChild("banned_avatar_name_list")); +} + +void LLPanelEstateAccess::importListCallback(LLNameListCtrl* list, const std::vector& filenames) +{ + if (filenames.empty()) + { + return; + } + + std::string filename = filenames[0]; + + std::ifstream file(filename.c_str()); + if (!file.is_open()) + { + return; + } + + std::string line; + std::vector uuids; + + while (std::getline(file, line)) + { + LLStringUtil::trim(line); + if (line.empty()) + { + continue; + } + + LLUUID uuid; + if (uuid.set(line)) + { + uuids.push_back(uuid); + } + } + file.close(); + + if (uuids.empty()) + { + LLSD args; + args["MESSAGE"] = LLTrans::getString("NoValidUUIDs"); + LLNotificationsUtil::add("GenericAlert", args); + return; + } + + std::string listname = list->getName(); + S32 max_entries = list->getMaxItemCount(); + S32 current_count = list->getItemCount(); + S32 available_slots = max_entries - current_count; + + if (uuids.size() > available_slots) + { + LLSD args; + args["MAX"] = llformat("%d", available_slots); + args["COUNT"] = llformat("%d", uuids.size()); + args["MESSAGE"] = LLTrans::getString("ImportListTooLarge", args).c_str(); + LLNotificationsUtil::add("GenericAlert", args); + return; + } + + for (const LLUUID& uuid : uuids) + { + if (listname == "banned_avatar_name_list") + { + sendEstateAccessDelta(ESTATE_ACCESS_BANNED_AGENT_ADD, uuid); + } + else if (listname == "estate_manager_name_list") + { + sendEstateAccessDelta(ESTATE_ACCESS_MANAGER_ADD, uuid); + } + else if (listname == "allowed_avatar_name_list") + { + sendEstateAccessDelta(ESTATE_ACCESS_ALLOWED_AGENT_ADD, uuid); + } + else if (listname == "allowed_group_name_list") + { + sendEstateAccessDelta(ESTATE_ACCESS_ALLOWED_GROUP_ADD, uuid); + } + } + + LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); + if (panel) + { + panel->setPendingUpdate(true); + } + + LLSD args; + args["COUNT"] = llformat("%d", uuids.size()); + args["MESSAGE"] = LLTrans::getString("ImportSuccessful", args).c_str(); + LLNotificationsUtil::add("GenericAlert", args); +} +// Ban and access lists export/import diff --git a/indra/newview/llfloaterregioninfo.h b/indra/newview/llfloaterregioninfo.h index 2937a0e607..482fca1d60 100644 --- a/indra/newview/llfloaterregioninfo.h +++ b/indra/newview/llfloaterregioninfo.h @@ -508,6 +508,21 @@ public: // Moved to public static void sendEstateAccessDelta(U32 flags, const LLUUID& agent_id); + // Ban and access lists export/import + void onClickExportEstateManagerList(); + void onClickExportAllowedList(); + void onClickExportAllowedGroupList(); + void onClickExportBannedList(); + void onClickExportList(LLNameListCtrl* list, const std::string& filename); + void exportListCallback(LLNameListCtrl* list, const std::vector& filenames); + void onClickImportEstateManagerList(); + void onClickImportAllowedList(); + void onClickImportAllowedGroupList(); + void onClickImportBannedList(); + void onClickImportList(LLNameListCtrl* list); + void importListCallback(LLNameListCtrl* list, const std::vector& filenames); + // Ban and access lists export/import + private: void onClickAddAllowedAgent(); void onClickRemoveAllowedAgent(); diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp index f29bc9782b..3b5ed6ec68 100644 --- a/indra/newview/lllogininstance.cpp +++ b/indra/newview/lllogininstance.cpp @@ -254,6 +254,16 @@ void LLLoginInstance::constructAuthParams(LLPointer user_credentia } std::string mfa_hash = gSavedSettings.getString("MFAHash"); //non-persistent to enable testing + // add some debug output for mfa_hash + if (mfa_hash.empty()) + { + LL_DEBUGS("MFA") << "MFA hash from settings is empty (expected)" << LL_ENDL; + } + else + { + LL_DEBUGS("MFA") << "MFA hash from settings is set to: " << mfa_hash << LL_ENDL; + } + // std::string grid(LLGridManager::getInstance()->getGridId()); std::string user_id = user_credential->userID(); if (gSecAPIHandler) @@ -266,9 +276,20 @@ void LLLoginInstance::constructAuthParams(LLPointer user_credentia { mfa_hash = data_map[user_id].asString(); } + // add some debug output for mfa_hash + if (mfa_hash.empty()) + { + LL_DEBUGS("MFA") << "MFA hash from secuire store is empty" << LL_ENDL; + } + else + { + LL_DEBUGS("MFA") << "MFA hash is from secure store set to: " << mfa_hash << LL_ENDL; + } + // } else { + LL_DEBUGS("MFA") << "MFA hash written to protected map" << LL_ENDL; // add some debug output for mfa_hash // SL-16888 the mfa_hash is being overridden for testing so save it for consistency for future login requests gSecAPIHandler->addToProtectedMap("mfa_hash", grid, user_id, mfa_hash); } @@ -277,8 +298,8 @@ void LLLoginInstance::constructAuthParams(LLPointer user_credentia { LL_WARNS() << "unable to access protected store for mfa_hash" << LL_ENDL; } - request_params["mfa_hash"] = mfa_hash; + LL_DEBUGS("MFA") << "mfa_hash in request params is set to: " << request_params["mfa_hash"] << LL_ENDL; // add some debug output for mfa_hash // Specify desired timeout/retry options LLSD http_params; @@ -622,17 +643,47 @@ void LLLoginInstance::saveMFAHash(LLSD const& response) { std::string grid(LLGridManager::getInstance()->getGridId()); std::string user_id(LLStartUp::getUserId()); - // Only save mfa_hash for future logins if the user wants their info remembered. + // add some debug output for mfa_hash + LL_DEBUGS("MFA") << "In saveMFAHagsh, grid: " << grid + << ", user_id: " << user_id + << ", remember user: " << (gSavedSettings.getBOOL("RememberUser")? "true" : "false") + << ", saveMFA: " << (LLLoginInstance::getInstance()->saveMFA()? "true" : "false") + << LL_ENDL; + if (response.has("mfa_hash")) + { + LL_DEBUGS("MFA") << "In saveMFAHash, mfa_hash: " << response["mfa_hash"].asString() + << LL_ENDL; + } + else + { + LL_DEBUGS("MFA") << "In saveMFAHash, no mfa_hash in response" + << LL_ENDL; + } + // if (response.has("mfa_hash") && gSavedSettings.getBOOL("RememberUser") && LLLoginInstance::getInstance()->saveMFA()) { + // add some debug output for mfa_hash + LL_DEBUGS("MFA") << "In saveMFAHash, mfa_hash: " << response["mfa_hash"].asString() + << LL_ENDL; + // gSecAPIHandler->addToProtectedMap("mfa_hash", grid, user_id, response["mfa_hash"]); } else if (!LLLoginInstance::getInstance()->saveMFA()) { + // add some debug output for mfa_hash + LL_DEBUGS("MFA") << "In saveMFAHash, removing mfa_hash from protected map" + << LL_ENDL; + + // User does not want to save mfa_hash, remove it from the protected map gSecAPIHandler->removeFromProtectedMap("mfa_hash", grid, user_id); } + // TODO(brad) - related to SL-17223 consider building a better interface that sync's automatically + // add some debug output for mfa_hash + LL_DEBUGS("MFA") << "In saveMFAHash, syncing protected map" + << LL_ENDL; + gSecAPIHandler->syncProtectedMap(); } diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp index 9df85051e8..b41e2fd452 100644 --- a/indra/newview/llmaterialeditor.cpp +++ b/indra/newview/llmaterialeditor.cpp @@ -546,6 +546,13 @@ bool LLMaterialEditor::postBuild() getChild("total_upload_fee")->setTextArg("[FEE]", llformat("%d", 0)); } + // [FIRE-35544] For disabling texture previews for no-mod materials + mBaseColorTextureCtrl->setIsPreviewDisabled(true); + mMetallicTextureCtrl->setIsPreviewDisabled(true); + mEmissiveTextureCtrl->setIsPreviewDisabled(true); + mNormalTextureCtrl->setIsPreviewDisabled(true); + // + // Todo: // Disable/enable setCanApplyImmediately() based on // working from inventory, upload or editing inworld diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp index a4224638ca..2ce29b2fa0 100644 --- a/indra/newview/llselectmgr.cpp +++ b/indra/newview/llselectmgr.cpp @@ -102,6 +102,7 @@ // [RLVa:KB] - Checked: 2011-05-22 (RLVa-1.3.1a) #include "rlvactions.h" #include "rlvhandler.h" +#include "rlvlocks.h" #include "rlvmodifiers.h" // [/RLVa:KB] // Aurora Sim @@ -5302,6 +5303,18 @@ void LLSelectMgr::sendDetach() return; } +// [RLVa:KB] + if ( (rlv_handler_t::isEnabled()) && (gRlvAttachmentLocks.hasLockedAttachmentPoint(RLV_LOCK_REMOVE)) ) + { + LLObjectSelectionHandle hSelect = LLSelectMgr::getInstance()->getSelection(); + RlvSelectHasLockedAttach f; + if ( (hSelect->isAttachment()) && (hSelect->getFirstRootNode(&f, false) != NULL) ) + { + return; + } + } +// [/RLVa:KB] + sendListToRegions( "ObjectDetach", packAgentAndSessionID, diff --git a/indra/newview/lltexturectrl.cpp b/indra/newview/lltexturectrl.cpp index 6f2400a577..d34175dde9 100644 --- a/indra/newview/lltexturectrl.cpp +++ b/indra/newview/lltexturectrl.cpp @@ -1818,7 +1818,9 @@ LLTextureCtrl::LLTextureCtrl(const LLTextureCtrl::Params& p) mTextDisabledColor(p.text_disabled_color), // Add label/caption colors mLabel(p.label), // FIRE-34300 - Fix label not showing in texture picker floater title // Mask texture if desired - mIsMasked(false) + mIsMasked(false), + // [FIRE-35544] For disabling texture previews for no-mod materials + mIsPreviewDisabled(false) { mCaptionHeight = p.show_caption ? BTN_HEIGHT_SMALL : 0; // leave some room underneath the image for the caption // Default of defaults is white image for diff tex @@ -1984,7 +1986,7 @@ void LLTextureCtrl::setEnabled( bool enabled ) // Texture preview mode //LLView::setEnabled( enabled ); - LLView::setEnabled( (enabled || getValue().asUUID().notNull()) ); + LLView::setEnabled(enabled || (getValue().asUUID().notNull() && !mIsPreviewDisabled)); mOpenTexPreview = !enabled; // } @@ -2642,7 +2644,7 @@ void LLTextureCtrl::setValue( const LLSD& value ) //setImageAssetID(value.asUUID()); LLUUID uuid = value.asUUID(); setImageAssetID(uuid); - LLView::setEnabled( (!mOpenTexPreview || uuid.notNull()) ); + LLView::setEnabled(!mOpenTexPreview || (uuid.notNull() && !mIsPreviewDisabled)); // } diff --git a/indra/newview/lltexturectrl.h b/indra/newview/lltexturectrl.h index 0a142e3980..ad8461fbb1 100644 --- a/indra/newview/lltexturectrl.h +++ b/indra/newview/lltexturectrl.h @@ -262,6 +262,8 @@ public: // Mask texture if desired void setIsMasked(bool masked) { mIsMasked = masked; } + // [FIRE-35544] For disabling texture previews for no-mod materials + void setIsPreviewDisabled(bool is_preview_disabled) { mIsPreviewDisabled = is_preview_disabled; } void setLabelColor(const LLColor4& c) { mTextEnabledColor = c; updateLabelColor(); } // Add label/caption colors void setDisabledLabelColor(const LLColor4& c) { mTextDisabledColor = c; updateLabelColor(); } // Add label/caption colors @@ -313,6 +315,8 @@ private: // Mask texture if desired bool mIsMasked; + // [FIRE-35544] For disabling texture previews for no-mod materials + bool mIsPreviewDisabled; LLUIColor mTextEnabledColor; // Add label/caption colors LLUIColor mTextDisabledColor; // Add label/caption colors diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp index f1d2c7759b..933ba12b53 100644 --- a/indra/newview/lltextureview.cpp +++ b/indra/newview/lltextureview.cpp @@ -713,7 +713,7 @@ void LLGLTexMemBar::draw() //text = llformat("Textures: %d Fetch: %d(%d) Pkts:%d(%d) Cache R/W: %d/%d LFS:%d RAW:%d HTP:%d DEC:%d CRE:%d ", text = llformat("Tex: %d Fetch: %d(%d) Pkts:%d(%d) CAC R/W: %d/%d LFS:%d RAW:%d HTP:%d DEC:%d CRE:%d FCA:%d ", // - // Fixed up the missing variables and converted 64bit size_t's to S32's to allow proper numbers to appear + // Fixed up the missing variables and converted 64bit size_t's to S32's to allow proper numbers to appear gTextureList.getNumImages(), LLAppViewer::getTextureFetch()->getNumRequests(), LLAppViewer::getTextureFetch()->getNumDeletes(), LLAppViewer::getTextureFetch()->mPacketCount, LLAppViewer::getTextureFetch()->mBadPacketCount, @@ -727,7 +727,7 @@ void LLGLTexMemBar::draw() (S32)gTextureList.mCreateTextureList.size(), (S32)gTextureList.mFastCacheList.size()); // - // + // x_right = 550.0f; LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0.f, (F32)(v_offset + line_height*3), text_color, LLFontGL::LEFT, LLFontGL::TOP, diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index dbba65666f..9e0ec0fb49 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -3476,6 +3476,15 @@ void handle_object_tex_refresh(LLViewerObject* object, LLSelectNode* node) LLViewerTexture* spec_img = object->getTESpecularMap(i); faces_per_texture[spec_img->getID()].push_back(i); } + + LLPointer mat = object->getTE(i)->getGLTFRenderMaterial(); + if (mat.notNull()) + { + for (U32 j = 0; j < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++j) + { + faces_per_texture[mat->mTextureId[j]].push_back(i); + } + } } map_t::iterator it; diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 309aa9b92f..fd3aa94931 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -1498,6 +1498,10 @@ F32 LLViewerTextureList::updateImagesLoadingFastCache(F32 max_time) // Fast cache stats sNumFastCacheReads++; // + // Fix fast cache + if (timer.getElapsedTimeF32() > max_time) + break; + // } mFastCacheList.erase(mFastCacheList.begin(), enditer); return timer.getElapsedTimeF32(); @@ -1609,7 +1613,9 @@ void LLViewerTextureList::decodeAllImages(F32 max_time) LLTimer timer; //loading from fast cache - updateImagesLoadingFastCache(max_time); + // Fix fast cache + //updateImagesLoadingFastCache(max_time); + max_time -= updateImagesLoadingFastCache(max_time); // Update texture stats and priorities std::vector > image_list; diff --git a/indra/newview/skins/default/xui/az/floater_world_map.xml b/indra/newview/skins/default/xui/az/floater_world_map.xml index 6f75d79374..8dd1cfd788 100644 --- a/indra/newview/skins/default/xui/az/floater_world_map.xml +++ b/indra/newview/skins/default/xui/az/floater_world_map.xml @@ -59,20 +59,17 @@ + + Post as: + + + + + width="100" />