# Conflicts:
#	.github/workflows/build.yaml
#	indra/newview/app_settings/settings.xml
This commit is contained in:
Ansariel
2026-01-06 12:55:49 +01:00
243 changed files with 6547 additions and 729 deletions
+10 -10
View File
@@ -112,7 +112,7 @@ jobs:
- name: Cache autobuild packages
id: cache-installables
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: .autobuild-installables
key: ${{ runner.os }}-64-${{ matrix.configuration }}-${{ hashFiles('autobuild.xml') }}
@@ -265,7 +265,7 @@ jobs:
- name: Upload executable
if: steps.build.outputs.viewer_app
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: "${{ steps.build.outputs.artifact }}-app"
path: |
@@ -275,13 +275,13 @@ jobs:
# artifact for that too.
- name: Upload symbol file
if: steps.build.outputs.symbolfile
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: "${{ steps.build.outputs.artifact }}-symbols"
path: ${{ steps.build.outputs.symbolfile }}
- name: Upload metadata
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: "${{ steps.build.outputs.artifact }}-metadata"
# emitted by build.sh, possibly multiple lines
@@ -289,7 +289,7 @@ jobs:
${{ steps.build.outputs.metadata }}
- name: Upload physics package
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
# should only be set for viewer-private
if: matrix.configuration == 'Release' && steps.build.outputs.physicstpv
with:
@@ -370,13 +370,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download viewer exe
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
name: Windows-app
path: _artifacts
- name: Download Windows Symbols
if: env.BUGSPLAT_DATABASE && env.SYMBOL_UPLOAD_CLIENT_ID
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
name: Windows-symbols
- name: Extract viewer pdb
@@ -409,7 +409,7 @@ jobs:
steps:
- name: Download Mac Symbols
if: env.BUGSPLAT_DATABASE && env.SYMBOL_UPLOAD_CLIENT_ID
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
name: macOS-symbols
- name: Post Mac symbols
@@ -431,11 +431,11 @@ jobs:
runs-on: ubuntu-latest
if: needs.setup.outputs.release_run
steps:
- uses: actions/download-artifact@v6
- uses: actions/download-artifact@v7
with:
pattern: "*-installer"
- uses: actions/download-artifact@v6
- uses: actions/download-artifact@v7
with:
pattern: "*-metadata"
+5 -5
View File
@@ -486,7 +486,7 @@ jobs:
dumpSyms: false
- name: Publish artifacts
if: runner.os == 'Windows'
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ env.FS_RELEASE_TYPE }}-${{ matrix.os }}-${{ matrix.variant }}-${{ matrix.grid }}-artifacts.zip
path: |
@@ -495,7 +495,7 @@ jobs:
- name: publish Linux artifacts
if: runner.os == 'Linux'
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ env.FS_RELEASE_TYPE }}-${{ matrix.os }}-${{ matrix.variant }}-${{matrix.grid}}-artifacts.zip
path: |
@@ -504,7 +504,7 @@ jobs:
- name: publish MacOS artifacts
if: runner.os == 'macOS'
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ env.FS_RELEASE_TYPE }}-${{ matrix.os }}-${{ matrix.variant }}-${{matrix.grid}}-artifacts.zip
path: |
@@ -533,7 +533,7 @@ jobs:
run: pip install discord-webhook
- name: Download artifacts
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
id: download
with:
path: to_deploy
@@ -585,7 +585,7 @@ jobs:
# Upload Build Info Artifact (note that this file is expected to be identical for each matrix run, so items like OS and VARIANT cannot be in this file.)
- name: Upload Tag Info
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: build_info
path: build_info.json
+5 -5
View File
@@ -488,7 +488,7 @@ jobs:
dumpSyms: false
- name: Publish artifacts
if: runner.os == 'Windows'
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ env.FS_RELEASE_TYPE }}-${{ matrix.os }}-${{ matrix.variant }}-${{ matrix.grid }}-artifacts.zip
path: |
@@ -497,7 +497,7 @@ jobs:
- name: publish Linux artifacts
if: runner.os == 'Linux'
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ env.FS_RELEASE_TYPE }}-${{ matrix.os }}-${{ matrix.variant }}-${{matrix.grid}}-artifacts.zip
path: |
@@ -506,7 +506,7 @@ jobs:
- name: publish MacOS artifacts
if: runner.os == 'macOS'
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ env.FS_RELEASE_TYPE }}-${{ matrix.os }}-${{ matrix.variant }}-${{matrix.grid}}-artifacts.zip
path: |
@@ -535,7 +535,7 @@ jobs:
run: pip install discord-webhook
- name: Download artifacts
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
id: download
with:
path: to_deploy
@@ -587,7 +587,7 @@ jobs:
# Upload Build Info Artifact (note that this file is expected to be identical for each matrix run, so items like OS and VARIANT cannot be in this file.)
- name: Upload Tag Info
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: build_info
path: build_info.json
+1 -1
View File
@@ -45,7 +45,7 @@ jobs:
run: pip install discord-webhook
- name: Download Build Artifacts
uses: dawidd6/action-download-artifact@v11
uses: dawidd6/action-download-artifact@v12
id: download
with:
workflow: build_viewer.yml
+7 -7
View File
@@ -41,7 +41,7 @@ jobs:
setup_files: ${{ steps.get-files.outputs.setup_files }}
steps:
- name: Download Build Artifacts
uses: dawidd6/action-download-artifact@v11
uses: dawidd6/action-download-artifact@v12
id: download
with:
workflow: build_viewer.yml
@@ -62,7 +62,7 @@ jobs:
files_json=$(ls setup_exe_files | jq -R . | jq -s -c .)
echo "setup_files=$files_json" >> $GITHUB_OUTPUT
- name: Upload Setup.exe Files
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: setup-exe-files
path: setup_exe_files/
@@ -86,7 +86,7 @@ jobs:
shell: bash
- name: Download Setup.exe Files Artifact
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
name: setup-exe-files
path: setup_exe_files
@@ -97,7 +97,7 @@ jobs:
shell: bash
# - name: Upload unsigned artifact
# id: upload-unsigned-artifact
# uses: actions/upload-artifact@v5
# uses: actions/upload-artifact@v6
# with:
# name: unsigned-artifact-${{ matrix.file }}
# path: to_sign/${{ matrix.file }}
@@ -115,7 +115,7 @@ jobs:
# wait-for-completion: true
# output-artifact-directory: 'application-signed'
- name: Azure Trusted Signing
uses: azure/trusted-signing-action@v0.5.10
uses: azure/trusted-signing-action@v0.5.11
with:
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
@@ -129,14 +129,14 @@ jobs:
files-folder-filter: exe
# - name: Upload signed artifact
# uses: actions/upload-artifact@v5
# uses: actions/upload-artifact@v6
# with:
# name: signed-artifact-${{ matrix.file }}
# path: ./application-signed
# - name: Upload unsigned artifact
- name: Upload signed artifact
id: upload-unsigned-artifact
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: unsigned-artifact-${{ matrix.file }}
path: to_sign\${{ matrix.file }}
+1 -1
View File
@@ -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@v11
uses: dawidd6/action-download-artifact@v12
id: download_build_info
with:
workflow: build_viewer.yml
+1 -1
View File
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2025&license=fsviewerlgpl$
* Phoenix Firestorm Viewer Source Code
* Copyright (C) 2025, The Phoenix Firestorm Project, Inc.
* Copyright (C) 2026, The Phoenix Firestorm Project, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
+1 -1
View File
@@ -4161,7 +4161,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>license_file</key>
<string>docs/LICENSE-source.txt</string>
<key>copyright</key>
<string>Copyright (c) 2025, The Phoenix Firestorm Project, Inc.</string>
<string>Copyright (c) 2026, The Phoenix Firestorm Project, Inc.</string>
<key>version_file</key>
<string>newview/viewer_version.txt</string>
<key>name</key>
+2 -20
View File
@@ -9,7 +9,7 @@
* make an instance with arguments of arbitrary type. llmake()
* eliminates the need to declare a new helper function for every such
* class template.
*
*
* also relevant:
*
* Template argument deduction for class templates (C++17)
@@ -62,22 +62,4 @@ POINTER_TEMPLATE<CLASS_TEMPLATE<ARGS...>> llmake_heap(ARGS&&... args)
new CLASS_TEMPLATE<ARGS...>(std::forward<ARGS>(args)...));
}
#endif // VS 2013 workaround
/// dumb pointer template just in case that's what's wanted
/**
* Same as llmake(), but returns a pointer to a new heap instance of
* SomeTemplate<T...>(args...) using the pointer of your choice.
*
* @code
* auto* dumb = llmake_heap<SomeTemplate>(args...);
* auto shared = llmake_heap<SomeTemplate, std::shared_ptr>(args...);
* auto unique = llmake_heap<SomeTemplate, std::unique_ptr>(args...);
* @endcode
*/
// POINTER_TEMPLATE is characterized as template<typename...> rather than as
// template<typename T> because (e.g.) std::unique_ptr has multiple template
// arguments. Even though we only engage one, std::unique_ptr doesn't match a
// template template parameter that itself takes only one template parameter.
#endif /* ! defined(LL_LLMAKE_H) */
+11 -1
View File
@@ -798,7 +798,17 @@ void LLFloater::closeFloater(bool app_quitting)
&& !app_quitting
&& floaterName != "toast") // <FS:PP> FIRE-10373 / BUG-6437
{
make_ui_sound("UISndWindowClose");
// <FS:Zi> UI Sounds connection
// make_ui_sound("UISndWindowClose");
if (getName() == "script_floater")
{
make_ui_sound("UISndScriptFloaterClose");
}
else
{
make_ui_sound("UISndWindowClose");
}
// </FS:Zi>
}
gFocusMgr.clearLastFocusForGroup(this);
+4
View File
@@ -1011,6 +1011,10 @@ void LLLineEditor::removeWord(bool prev)
{
mText.erase(pos, diff);
}
mFontBufferPreSelection.reset();
mFontBufferSelection.reset();
mFontBufferPostSelection.reset();
}
else
{
+1
View File
@@ -3658,6 +3658,7 @@ LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLS
if (cell_p.width.isProvided())
{
new_column.width.pixel_width = cell_p.width;
new_column.width.pixel_width.choose(); // <FS:Zi> fix multi column comboboxes not honoring width parameter
}
addColumn(new_column);
columnp = mColumns[column];
+1 -1
View File
@@ -2063,7 +2063,7 @@ void LLWindowSDL::gatherInput()
break;
}
case SDL_QUIT:
if(mCallbacks->handleCloseRequest(this))
if(mCallbacks->handleCloseRequest(this, true))
{
// Get the app to initiate cleanup.
mCallbacks->handleQuit(this);
+9 -6
View File
@@ -114,6 +114,7 @@ set(viewer_SOURCE_FILES
fsdroptarget.cpp
fsexportperms.cpp
fsfloateraddtocontactset.cpp
fsfavoritegroups.cpp
fsfloaterassetblacklist.cpp
fsfloateravatarrendersettings.cpp
fsfloaterblocklist.cpp
@@ -149,6 +150,7 @@ set(viewer_SOURCE_FILES
fslslpreproc.cpp
fslslpreprocviewer.cpp
fsmaniprotatejoint.cpp
fsmaniptranslatejoint.cpp
fsmoneytracker.cpp
fsnamelistavatarmenu.cpp
fsnearbychatbarlistener.cpp
@@ -186,6 +188,8 @@ set(viewer_SOURCE_FILES
lfsimfeaturehandler.cpp
llflickrconnect.cpp
llfloaterflickr.cpp
omnifilter.cpp
omnifilterengine.cpp
fsprimfeedconnect.cpp
fsfloaterprimfeed.cpp
llpanelopenregionsettings.cpp
@@ -965,6 +969,7 @@ set(viewer_HEADER_FILES
fsdroptarget.h
fsexportperms.h
fsfloateraddtocontactset.h
fsfavoritegroups.h
fsfloaterassetblacklist.h
fsfloateravatarrendersettings.h
fsfloaterblocklist.h
@@ -1002,6 +1007,7 @@ set(viewer_HEADER_FILES
fslslpreprocviewer.h
fsmoneytracker.h
fsmaniprotatejoint.h
fsmaniptranslatejoint.h
fsnamelistavatarmenu.h
fsnearbychatbarlistener.h
fsnearbychatcontrol.h
@@ -1043,6 +1049,8 @@ set(viewer_HEADER_FILES
fsfloaterprimfeed.h
# <FS:Ansariel> [Legacy Bake]
llagentwearablesfetch.h
omnifilter.h
omnifilterengine.h
vjlocalmesh.h
vjfloaterlocalmesh.h
vjlocalmeshimportdae.h
@@ -2518,11 +2526,6 @@ elseif (DARWIN)
PROPERTIES
RESOURCE Firestorm.xib
LINK_FLAGS_RELEASE "${LINK_FLAGS_RELEASE} -Xlinker -dead_strip"
#<FS:TS> Force the SDK version in the linked executable to be 10.12. This will fool
# macOS into using the pre-Mojave display system, avoiding the blurry display that
# otherwise occurs when upscaling the viewer to Retina resolution levels.
LINK_FLAGS_RELEASE "${LINK_FLAGS_RELEASE} -Xlinker -platform_version -Xlinker macos -Xlinker ${CMAKE_OSX_DEPLOYMENT_TARGET} -Xlinker 10.12"
)
if(HAVOK)
set_target_properties(${VIEWER_BINARY_NAME}
@@ -2791,7 +2794,7 @@ if (DARWIN)
set(MACOSX_BUNDLE_BUNDLE_NAME "Firestorm")
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}")
set(MACOSX_BUNDLE_BUNDLE_VERSION "${VIEWER_SHORT_VERSION}${VIEWER_MACOSX_PHASE}${VIEWER_REVISION}")
set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2010-2025 The Phoenix Firestorm Project, Inc.")
set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2010-2026 The Phoenix Firestorm Project, Inc.")
set(MACOSX_BUNDLE_NSMAIN_NIB_FILE "Firestorm.nib")
set(MACOSX_BUNDLE_NSPRINCIPAL_CLASS "LLApplication")
@@ -3,4 +3,4 @@
CFBundleName = "Firestorm";
CFBundleShortVersionString = "Firestorm version %%VERSION%%";
CFBundleGetInfoString = "Firestorm version %%VERSION%%, Copyright 2010-2025 The Phoenix Firestorm Project, Inc.";
CFBundleGetInfoString = "Firestorm version %%VERSION%%, Copyright 2010-2026 The Phoenix Firestorm Project, Inc.";
+11
View File
@@ -680,4 +680,15 @@
is_running_parameters="performance"
checkbox_control="AutoTuneFPS"
/>
<command name="omnifilter"
available_in_toybox="true"
icon="Command_Omnifilter_Icon"
label_ref="Command_Omnifilter_Label"
tooltip_ref="Command_Omnifilter_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
execute_parameters="omnifilter"
is_running_function="Floater.IsOpen"
is_running_parameters="omnifilter"
checkbox_control="OmnifilterEnabled"
/>
</commands>
+45 -1
View File
@@ -17001,6 +17001,17 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>Value</key>
<string>c80260ba-41fd-8a46-768a-6bf236360e3a</string>
</map>
<key>UISndScriptFloaterClose</key>
<map>
<key>Comment</key>
<string>Sound file for closing a script dialog (uuid for sound asset)</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>String</string>
<key>Value</key>
<string>2c346eda-b60c-ab33-1119-b8941916a499</string>
</map>
<key>UISpinctrlBtnHeight</key>
<map>
<key>Comment</key>
@@ -17287,7 +17298,7 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>0</integer>
<integer>1</integer>
</map>
<key>EnvironmentPersistAcrossLogin</key>
<map>
@@ -22769,6 +22780,17 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>Value</key>
<integer>1</integer>
</map>
<key>PlayModeUISndScriptFloaterClose</key>
<map>
<key>Comment</key>
<string>Holds state for Prefs &gt; Sound/Media &gt; UI Sounds - UISndScriptFloaterClose.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>1</integer>
</map>
<!-- End: Prefs > Sound/Media => UI Sounds -->
<key>FSShowMyOwnVoiceVisualizer</key>
<map>
@@ -26293,6 +26315,17 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>Value</key>
<integer>1</integer>
</map>
<key>FSShowChatRangeSpheres</key>
<map>
<key>Comment</key>
<string>Show chat range spheres (whisper, say, shout) in 3D world around avatar</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>0</integer>
</map>
<key>FSUseReadOfflineMsgsCap</key>
<map>
<key>Comment</key>
@@ -27215,6 +27248,17 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>Value</key>
<integer>0</integer>
</map>
<key>OmnifilterEnabled</key>
<map>
<key>Comment</key>
<string>The operating state of the Omnifilter.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>1</integer>
</map>
<key>FSUseFSLegacySearch</key>
<map>
<key>Comment</key>
@@ -1374,6 +1374,17 @@
<key>Value</key>
<integer>0</integer>
</map>
<key>FSFavoriteGroups</key>
<map>
<key>Comment</key>
<string>List of favorite/pinned group UUIDs</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>LLSD</string>
<key>Value</key>
<array></array>
</map>
<key>FSMaxSharedMaturity</key>
<map>
<key>Comment</key>
@@ -507,6 +507,18 @@
<integer>0</integer>
</map>
<key>PlayModeUISndScriptFloaterClose</key>
<map>
<key>Comment</key>
<string>Holds state for Prefs &gt; Sound/Media &gt; UI Sounds - UISndScriptFloaterClose.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>0</integer>
</map>
<key>FSLegacyNotificationWell</key>
<map>
<key>Comment</key>
+5 -31
View File
@@ -26,39 +26,17 @@
#include "daeexport.h"
//colladadom includes
#if LL_MSVC
#pragma warning (push)
#pragma warning (disable : 4068)
#pragma warning (disable : 4263)
#pragma warning (disable : 4264)
#endif
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Woverloaded-virtual"
#endif
#include "fix_macros.h"
#include "dae.h"
//#include "dom.h"
#include "dom/domAsset.h"
#include "dom/domBind_material.h"
#include "dom/domCOLLADA.h"
#include "dom/domConstants.h"
#include "dom/domController.h"
#include "dom/domEffect.h"
#include "dom/domGeometry.h"
#include "dom/domInstance_geometry.h"
#include "dom/domInstance_material.h"
#include "dom/domInstance_node.h"
#include "dom/domInstance_effect.h"
#include "dom/domMaterial.h"
#include "dom/domMatrix.h"
#include "dom/domNode.h"
#include "dom/domProfile_COMMON.h"
#include "dom/domRotate.h"
#include "dom/domScale.h"
#include "dom/domTranslate.h"
#include "dom/domVisual_scene.h"
#if LL_MSVC
#pragma warning (pop)
#endif
// llimage includes
#include "llimagej2c.h"
@@ -66,7 +44,6 @@
#include "llimagetga.h"
// llui includes
#include "llscrollcontainer.h"
#include "lltexturectrl.h"
// newview includes
@@ -77,21 +54,18 @@
#include "llnotificationsutil.h"
#include "llselectmgr.h"
#include "lltexturecache.h"
#include "lltrans.h"
#include "llversioninfo.h"
#include "llviewercontrol.h"
#include "llviewermenufile.h"
#include "llviewernetwork.h"
#include "llviewerregion.h"
#include "llviewertexturelist.h"
#include "llvovolume.h"
#include "fsexportperms.h"
static const F32 TEXTURE_DOWNLOAD_TIMEOUT = 60.f;
static constexpr F32 TEXTURE_DOWNLOAD_TIMEOUT = 60.f;
// *FIXME: Don't hard code these and allow the floater to resize. Right now, I'm too lazy. <FS:CR>
static const S32 EXPANDED_WIDTH = 500;
static const S32 COLLAPSED_WIDTH = 250;
static constexpr S32 EXPANDED_WIDTH = 500;
static constexpr S32 COLLAPSED_WIDTH = 250;
namespace DAEExportUtil
{
+3 -4
View File
@@ -28,10 +28,10 @@
#include "llfloater.h"
#include "lltextureentry.h"
#include "lltexturecache.h"
#include <dom/domElements.h>
class LLViewerObject;
class LLObjectSelection;
class daeElement;
class DAESaver
{
@@ -43,7 +43,7 @@ public:
LLColor4 color;
std::string name;
bool matches(LLTextureEntry* te)
bool matches(LLTextureEntry* te) const
{
return (textureID == te->getID()) && (color == te->getColor());
}
@@ -58,7 +58,7 @@ public:
return !(*this == rhs);
}
MaterialInfo() {}
MaterialInfo() = default;
MaterialInfo(const MaterialInfo& rhs)
{
@@ -133,7 +133,6 @@ private:
void onClickExport();
void onExportFileSelected(const std::vector<std::string>& filenames);
void onTextureExportCheck();
void onCommitTextureType();
void saveTextures();
void addSelectedObjects();
void addTexturePreview();
+1
View File
@@ -368,6 +368,7 @@ void FSData::downloadAgents()
// TODO: Let the opensim devs and opensim group figure out the best way
// to add "agents.xml" URL to the gridinfo protocol.
//getAgentsURL();
mAgentsDone = true; // Avoid +15s llstartup STATE_AGENT_WAIT delay --ht
// there is no need for assets.xml URL for opensim grids as the grid owner can just delete
// the bad asset itself.
+133
View File
@@ -0,0 +1,133 @@
/**
* @file fsfavoritegroups.cpp
* @brief Favorite groups storage management
*
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
* Phoenix Firestorm Viewer Source Code
* Copyright (C) 2025, The Phoenix Firestorm Project, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
* http://www.firestormviewer.org
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "fsfavoritegroups.h"
#include "llagent.h"
#include "llviewercontrol.h"
FSFavoriteGroups::FSFavoriteGroups()
{
}
FSFavoriteGroups::~FSFavoriteGroups()
{
saveFavorites();
}
bool FSFavoriteGroups::isFavorite(const LLUUID& group_id) const
{
return mFavoriteGroups.contains(group_id);
}
void FSFavoriteGroups::addFavorite(const LLUUID& group_id)
{
if (group_id.isNull())
{
return;
}
if (mFavoriteGroups.insert(group_id).second)
{
saveFavorites();
mFavoritesChangedSignal();
}
}
void FSFavoriteGroups::removeFavorite(const LLUUID& group_id)
{
if (mFavoriteGroups.erase(group_id) > 0)
{
saveFavorites();
mFavoritesChangedSignal();
}
}
void FSFavoriteGroups::toggleFavorite(const LLUUID& group_id)
{
if (isFavorite(group_id))
{
removeFavorite(group_id);
}
else
{
addFavorite(group_id);
}
}
void FSFavoriteGroups::loadFavorites()
{
mFavoriteGroups.clear();
if (LLSD favorites = gSavedPerAccountSettings.getLLSD("FSFavoriteGroups"); favorites.isArray())
{
for (const auto& groupId : llsd::inArray(favorites))
{
if (groupId.isUUID())
{
mFavoriteGroups.insert(groupId.asUUID());
}
}
}
LL_INFOS("FavoriteGroups") << "Loaded " << mFavoriteGroups.size() << " favorite groups from per-account settings" << LL_ENDL;
}
void FSFavoriteGroups::saveFavorites()
{
uuid_set_t stale_groups;
for (const auto& group_id : mFavoriteGroups)
{
if (!gAgent.isInGroup(group_id))
{
stale_groups.insert(group_id);
}
}
if (!stale_groups.empty())
{
for (const auto& group_id : stale_groups)
{
mFavoriteGroups.erase(group_id);
}
LL_INFOS("FavoriteGroups") << "Removed " << stale_groups.size() << " stale group(s) from favorites (no longer a member)" << LL_ENDL;
}
LLSD favorites_array = LLSD::emptyArray();
for (const auto& group_id : mFavoriteGroups)
{
favorites_array.append(group_id);
}
gSavedPerAccountSettings.setLLSD("FSFavoriteGroups", favorites_array);
LL_DEBUGS("FavoriteGroups") << "Saved " << mFavoriteGroups.size() << " favorite groups to per-account settings" << LL_ENDL;
}
+60
View File
@@ -0,0 +1,60 @@
/**
* @file fsfavoritegroups.h
* @brief Favorite groups storage management
*
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
* Phoenix Firestorm Viewer Source Code
* Copyright (C) 2025, The Phoenix Firestorm Project, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
* http://www.firestormviewer.org
* $/LicenseInfo$
*/
#ifndef FS_FAVORITEGROUPS_H
#define FS_FAVORITEGROUPS_H
#include "llsingleton.h"
#include "lluuid.h"
class FSFavoriteGroups : public LLSingleton<FSFavoriteGroups>
{
LLSINGLETON(FSFavoriteGroups);
~FSFavoriteGroups();
public:
bool isFavorite(const LLUUID& group_id) const;
void addFavorite(const LLUUID& group_id);
void removeFavorite(const LLUUID& group_id);
void toggleFavorite(const LLUUID& group_id);
const uuid_set_t& getFavorites() const { return mFavoriteGroups; }
bool hasFavorites() const { return !mFavoriteGroups.empty(); }
void loadFavorites();
void saveFavorites();
typedef boost::signals2::signal<void()> favorites_changed_signal_t;
boost::signals2::connection setFavoritesChangedCallback(const favorites_changed_signal_t::slot_type& cb)
{
return mFavoritesChangedSignal.connect(cb);
}
private:
uuid_set_t mFavoriteGroups;
favorites_changed_signal_t mFavoritesChangedSignal;
};
#endif // FS_FAVORITEGROUPS_H
+18
View File
@@ -31,6 +31,7 @@
#include "fscommon.h"
#include "fscontactsfriendsmenu.h"
#include "fsfavoritegroups.h"
#include "fsfloaterimcontainer.h"
#include "fsscrolllistctrl.h"
#include "llagent.h"
@@ -147,6 +148,7 @@ bool FSFloaterContacts::postBuild()
mGroupsChatBtn = mGroupsTab->getChild<LLButton>("chat_btn");
mGroupsInfoBtn = mGroupsTab->getChild<LLButton>("info_btn");
mGroupsActivateBtn = mGroupsTab->getChild<LLButton>("activate_btn");
mGroupsFavoriteBtn = mGroupsTab->getChild<LLButton>("favorite_btn");
mGroupsLeaveBtn = mGroupsTab->getChild<LLButton>("leave_btn");
mGroupsCreateBtn = mGroupsTab->getChild<LLButton>("create_btn");
mGroupsSearchBtn = mGroupsTab->getChild<LLButton>("search_btn");
@@ -156,6 +158,7 @@ bool FSFloaterContacts::postBuild()
mGroupsChatBtn->setCommitCallback(boost::bind(&FSFloaterContacts::onGroupChatButtonClicked, this));
mGroupsInfoBtn->setCommitCallback(boost::bind(&FSFloaterContacts::onGroupInfoButtonClicked, this));
mGroupsActivateBtn->setCommitCallback(boost::bind(&FSFloaterContacts::onGroupActivateButtonClicked, this));
mGroupsFavoriteBtn->setCommitCallback(boost::bind(&FSFloaterContacts::onGroupFavoriteButtonClicked, this));
mGroupsLeaveBtn->setCommitCallback(boost::bind(&FSFloaterContacts::onGroupLeaveButtonClicked, this));
mGroupsCreateBtn->setCommitCallback(boost::bind(&FSFloaterContacts::onGroupCreateButtonClicked, this));
mGroupsSearchBtn->setCommitCallback(boost::bind(&FSFloaterContacts::onGroupSearchButtonClicked, this));
@@ -247,6 +250,12 @@ void FSFloaterContacts::updateGroupButtons()
mGroupsLeaveBtn->setEnabled(isGroup);
mGroupsCreateBtn->setEnabled((!gMaxAgentGroups) || (gAgent.mGroups.size() < gMaxAgentGroups));
mGroupsInviteBtn->setEnabled(isGroup && gAgent.hasPowerInGroup(groupId, GP_MEMBER_INVITE));
mGroupsFavoriteBtn->setEnabled(isGroup);
if (isGroup)
{
bool is_favorite = FSFavoriteGroups::getInstance()->isFavorite(groupId);
mGroupsFavoriteBtn->setLabel(is_favorite ? getString("unfavorite_label") : getString("favorite_label"));
}
}
void FSFloaterContacts::onOpen(const LLSD& key)
@@ -432,6 +441,15 @@ void FSFloaterContacts::onGroupActivateButtonClicked()
LLGroupActions::activate(mGroupList->getSelectedUUID());
}
void FSFloaterContacts::onGroupFavoriteButtonClicked()
{
if (LLUUID group_id = getCurrentItemID(); group_id.notNull())
{
FSFavoriteGroups::getInstance()->toggleFavorite(group_id);
updateGroupButtons();
}
}
void FSFloaterContacts::onGroupLeaveButtonClicked()
{
if (LLUUID group_id = getCurrentItemID(); group_id.notNull())
+2
View File
@@ -141,6 +141,7 @@ private:
void onGroupChatButtonClicked();
void onGroupInfoButtonClicked();
void onGroupActivateButtonClicked();
void onGroupFavoriteButtonClicked();
void onGroupLeaveButtonClicked();
void onGroupCreateButtonClicked();
void onGroupSearchButtonClicked();
@@ -167,6 +168,7 @@ private:
LLButton* mGroupsChatBtn{ nullptr };
LLButton* mGroupsInfoBtn{ nullptr };
LLButton* mGroupsActivateBtn{ nullptr };
LLButton* mGroupsFavoriteBtn{ nullptr };
LLButton* mGroupsLeaveBtn{ nullptr };
LLButton* mGroupsCreateBtn{ nullptr };
LLButton* mGroupsSearchBtn{ nullptr };
+17
View File
@@ -1589,6 +1589,23 @@ void FSFloaterIM::updateMessages()
S32 is_history = msg["is_history"].asInteger();
bool is_region_msg = msg["is_region_msg"].asBoolean();
// LL removed updates to the participants list on bigger groups, so at least keep track of
// users who chatted in the group, so mentions and moderatoin tools can work better. This can
// be removed once participants lists work as designed once again.
// See also LLSpeakers::updateSpeakers() for own "has_spoken" extension. -Zi
if (!is_history && !pIMSession->mSpeakers->findSpeaker(from_id))
{
const std::string from_id_str = from_id.asString();
LLSD new_speaker;
new_speaker["session_id"] = mSessionID;
new_speaker["agent_updates"][from_id_str]["transition"] = "ENTER";
// add our own flag so the sorting by last chatters works
new_speaker["agent_updates"][from_id_str]["has_spoken"] = true;
pIMSession->mSpeakers->updateSpeakers(new_speaker);
}
LLChat chat;
chat.mFromID = from_id;
chat.mSessionID = mSessionID;
+31 -9
View File
@@ -276,10 +276,8 @@ void FSFloaterPoser::onFocusReceived()
void FSFloaterPoser::onFocusLost()
{
if( LLEditMenuHandler::gEditMenuHandler == this )
{
if (LLEditMenuHandler::gEditMenuHandler == this)
LLEditMenuHandler::gEditMenuHandler = nullptr;
}
}
void FSFloaterPoser::draw()
@@ -361,22 +359,33 @@ void FSFloaterPoser::enableVisualManipulators()
}
if (LLToolMgr::getInstance()->getCurrentToolset() != gCameraToolset)
{
mLastToolset = LLToolMgr::getInstance()->getCurrentToolset();
}
LLToolMgr::getInstance()->setCurrentToolset(gPoserToolset);
LLToolMgr::getInstance()->getCurrentToolset()->selectTool(FSToolCompPose::getInstance());
FSToolCompPose::getInstance()->setAvatar(gAgentAvatarp);
LLVOAvatar* avatar = getUiSelectedAvatar();
setVisualManipulators(avatar);
}
void FSFloaterPoser::disableVisualManipulators()
{
if (mLastToolset)
{
LLToolMgr::getInstance()->setCurrentToolset(mLastToolset);
}
FSToolCompPose::getInstance()->setAvatar(nullptr);
setVisualManipulators(nullptr);
}
void FSFloaterPoser::setVisualManipulators(LLVOAvatar* avatar)
{
FSToolCompPose::getInstance()->setAvatar(avatar);
FSToolCompPoseTranslate::getInstance()->setAvatar(avatar);
if (!avatar)
return;
auto selectedJoints = getUiSelectedPoserJoints();
updateManipWithFirstSelectedJoint(selectedJoints);
}
void FSFloaterPoser::onClose(bool app_quitting)
@@ -1657,6 +1666,7 @@ void FSFloaterPoser::onToggleRotationFrameButton(const LLUICtrl* toggleButton)
}
FSToolCompPose::getInstance()->setReferenceFrame(getReferenceFrame());
FSToolCompPoseTranslate::getInstance()->setReferenceFrame(getReferenceFrame());
refreshRotationSlidersAndSpinners();
}
@@ -1949,9 +1959,15 @@ void FSFloaterPoser::updateManipWithFirstSelectedJoint(const std::vector<FSPoser
bool iCanPoseThem = havePermissionToAnimateOtherAvatar(avatarp);
if ((joints.size() >= 1) && (haveImplicitPermission || iCanPoseThem))
{
FSToolCompPose::getInstance()->setJoint(avatarp->getJoint(joints[0]->jointName()));
FSToolCompPoseTranslate::getInstance()->setJoint(avatarp->getJoint(joints[0]->jointName()));
}
else
{
FSToolCompPose::getInstance()->setJoint(nullptr);
FSToolCompPoseTranslate::getInstance()->setJoint(nullptr);
}
}
E_RotationStyle FSFloaterPoser::getUiSelectedBoneRotationStyle(const std::string& jointName) const
@@ -2476,9 +2492,15 @@ void FSFloaterPoser::onAvatarSelect()
bool haveExplicitPermission = havePermissionToAnimateOtherAvatar(avatar); // as permissions allow
if (haveImplicitPermission || haveExplicitPermission)
{
FSToolCompPose::getInstance()->setAvatar(avatar);
FSToolCompPoseTranslate::getInstance()->setAvatar(avatar);
}
else
{
FSToolCompPose::getInstance()->setAvatar(nullptr);
FSToolCompPoseTranslate::getInstance()->setAvatar(nullptr);
}
bool arePosingSelected = mPoserAnimator.isPosingAvatar(avatar);
+1
View File
@@ -252,6 +252,7 @@ public:
// visual manipulators control
void enableVisualManipulators();
void disableVisualManipulators();
void setVisualManipulators(LLVOAvatar* avatar);
// Visual cue for which bone is under the mouse-cursor
void drawOnHoverJointHint();
+11 -7
View File
@@ -2975,7 +2975,11 @@ FSPanelSearchWeb::FSPanelSearchWeb() : FSSearchPanelBase()
bool FSPanelSearchWeb::postBuild()
{
mWebBrowser = getChild<LLMediaCtrl>("search_contents");
mWebBrowser = getChild<LLMediaCtrl>("webbrowser");
mWebBrowser->setErrorPageURL(gSavedSettings.getString("GenericErrorPageURL"));
LLViewerMedia::getInstance()->getOpenIDCookie(mWebBrowser);
loadURL(SearchQuery());
return true;
}
@@ -3053,12 +3057,16 @@ void FSPanelSearchWeb::loadURL(const SearchQuery &p)
// Get the search URL and expand all of the substitutions
// (also adds things like [LANGUAGE], [VERSION], [OS], etc.)
std::string url;
std::string url = LFSimFeatureHandler::instance().searchURL();
if (url.empty())
{
url = LLGridManager::getInstance()->isInSecondLife() ? gSavedSettings.getString("SearchURL") : gSavedSettings.getString("SearchURLOpenSim");
}
// add the maturity and category variables to the new Second Life search URL
if (LLGridManager::getInstance()->isInSecondLife())
{
url = LFSimFeatureHandler::instance().searchURL() + "&maturity=" + maturity + "&" + mCategoryPaths[p.category()].asString();
url += "&maturity=" + maturity + "&" + mCategoryPaths[p.category()].asString();
}
// for OpenSim, do the same as in earlier versions
else
@@ -3068,10 +3076,6 @@ void FSPanelSearchWeb::loadURL(const SearchQuery &p)
{
url = debug_url;
}
else
{
url = LFSimFeatureHandler::instance().searchURL();
}
}
url = LLWeb::expandURLSubstitutions(url, subs);
+14 -12
View File
@@ -55,47 +55,49 @@ void FSJointPose::setPublicPosition(const LLVector3& pos)
{
addStateToUndo(mCurrentState);
mCurrentState.mPosition.set(pos);
mCurrentState.mLastChangeWasRotational = false;
mCurrentState.mPoserChangeType = POSER_CHANGE_DEFAULT;
}
void FSJointPose::setPublicRotation(bool zeroBase, const LLQuaternion& rot)
void FSJointPose::setPublicRotation(bool zeroBase, E_PoserChangeType changeType, const LLQuaternion& rot)
{
addStateToUndo(mCurrentState);
if (zeroBase)
zeroBaseRotation(true);
else
mCurrentState.mUserSpecifiedBaseZero = false;
mCurrentState.mUserSpecifiedBaseZero = POSER_CHANGE_DEFAULT;
mCurrentState.mRotation.set(rot);
mCurrentState.mLastChangeWasRotational = true;
mCurrentState.mPoserChangeType = changeType;
}
void FSJointPose::setPublicScale(const LLVector3& scale)
{
addStateToUndo(mCurrentState);
mCurrentState.mScale.set(scale);
mCurrentState.mLastChangeWasRotational = false;
mCurrentState.mPoserChangeType = POSER_CHANGE_DEFAULT;
}
bool FSJointPose::undoLastChange()
E_PoserChangeType FSJointPose::undoLastChange()
{
bool changeType = mCurrentState.mLastChangeWasRotational;
E_PoserChangeType changeType = mCurrentState.mPoserChangeType;
mCurrentState = undoLastStateChange(mCurrentState);
return changeType;
}
void FSJointPose::redoLastChange()
E_PoserChangeType FSJointPose::redoLastChange()
{
mCurrentState = redoLastStateChange(mCurrentState);
return mCurrentState.mPoserChangeType;
}
void FSJointPose::resetJoint()
{
addStateToUndo(mCurrentState);
mCurrentState.resetJoint();
mCurrentState.mLastChangeWasRotational = true;
mCurrentState.mPoserChangeType = POSER_CHANGE_DEFAULT;
}
void FSJointPose::addStateToUndo(const FSJointState& stateToAddToUndo)
@@ -169,13 +171,13 @@ void FSJointPose::recaptureJoint()
}
mCurrentState = FSJointState(joint);
mCurrentState.mLastChangeWasRotational = true;
mCurrentState.mPoserChangeType = POSER_CHANGE_DEFAULT;
}
LLQuaternion FSJointPose::updateJointAsDelta(bool zeroBase, const LLQuaternion& rotation, const LLVector3& position, const LLVector3& scale)
{
addStateToUndo(mCurrentState);
mCurrentState.mLastChangeWasRotational = true;
mCurrentState.mPoserChangeType = POSER_CHANGE_DEFAULT;
return mCurrentState.updateFromJointProperties(zeroBase, rotation, position, scale);
}
@@ -231,7 +233,7 @@ void FSJointPose::cloneRotationFrom(FSJointPose* fromJoint)
addStateToUndo(FSJointState(mCurrentState));
mCurrentState.cloneRotationFrom(fromJoint->mCurrentState);
mCurrentState.mLastChangeWasRotational = true;
mCurrentState.mPoserChangeType = POSER_CHANGE_DEFAULT;
}
void FSJointPose::mirrorRotationFrom(FSJointPose* fromJoint)
+23 -11
View File
@@ -32,6 +32,17 @@
//-----------------------------------------------------------------------------
#include "llmotion.h"
/// <summary>
/// Describes the kind of pose-change.
/// Useful for undoing complex changes.
/// </summary>
typedef enum E_PoserChangeType
{
POSER_CHANGE_DEFAULT = 0, // a simple change to pos/rot/scale
POSER_CHANGE_CHILDMOVE = 1, // position change of a child by rotating its 2 antecedent joints
POSER_CHANGE_ROTATION = 2, // a rotation change
} E_PoserChangeType;
//-----------------------------------------------------------------------------
// class FSJointPose
//-----------------------------------------------------------------------------
@@ -70,13 +81,14 @@ class FSJointPose
/// <summary>
/// Undoes the last position set, if any.
/// </summary>
/// <returns>true if the change we un-did was rotational.</returns>
bool undoLastChange();
/// <returns>A value indicating what kind of change was undone (important for undo).</returns>
E_PoserChangeType undoLastChange();
/// <summary>
/// Undoes the last position set, if any.
/// </summary>
void redoLastChange();
/// <returns>A value indicating what kind of change was redone.</returns>
E_PoserChangeType redoLastChange();
/// <summary>
/// Resets the joint to its conditions when posing started.
@@ -100,7 +112,7 @@ class FSJointPose
/// If zeroBase is true, we treat rotations as if in BVH mode: user work.
/// If zeroBase is false, we treat as NOT BVH: some existing pose and user work.
/// </remarks>
void setPublicRotation(bool zeroBase, const LLQuaternion& rot);
void setPublicRotation(bool zeroBase, E_PoserChangeType changeType, const LLQuaternion& rot);
/// <summary>
/// Reflects the base and delta rotation of the represented joint left-right.
@@ -319,7 +331,7 @@ class FSJointPose
mUserSpecifiedBaseZero = false;
mRotationIsWorldLocked = false;
mJointRotationIsMirrored = false;
mLastChangeWasRotational = true;
mPoserChangeType = POSER_CHANGE_DEFAULT;
mBaseRotation.set(mStartingRotation);
mRotation.set(LLQuaternion::DEFAULT);
mPosition.setZero();
@@ -417,15 +429,15 @@ class FSJointPose
mRotationIsWorldLocked = state->mRotationIsWorldLocked;
mBasePriority = state->mBasePriority;
mJointRotationIsMirrored = state->mJointRotationIsMirrored;
mLastChangeWasRotational = state->mLastChangeWasRotational;
mPoserChangeType = state->mPoserChangeType;
}
public:
LLQuaternion mRotation;
LLVector3 mPosition;
LLVector3 mScale;
bool mRotationIsWorldLocked = false;
bool mLastChangeWasRotational = false;
LLQuaternion mRotation;
LLVector3 mPosition;
LLVector3 mScale;
bool mRotationIsWorldLocked = false;
E_PoserChangeType mPoserChangeType = POSER_CHANGE_DEFAULT;
/// <summary>
/// Whether the joint has been mirrored.
+7
View File
@@ -37,6 +37,13 @@ FSLSLPreProcViewer::FSLSLPreProcViewer(const Params& p)
bool FSLSLPreProcViewer::handleKeyHere(KEY key, MASK mask )
{
// Handle Ctrl/Cmd+A for select all
if ((key == 'A') && (MASK_CONTROL == (mask & MASK_MODIFIERS)))
{
selectAll();
return true;
}
// Normal key handling
bool handled = handleNavigationKey( key, mask )
|| handleSelectionKey(key, mask)
File diff suppressed because it is too large Load Diff
+99
View File
@@ -0,0 +1,99 @@
/**
* @file fsmaniptranslatejoint.h
* @brief custom manipulator for moving joints
*
* $LicenseInfo:firstyear=2024&license=viewerlgpl$
* Phoenix Firestorm Viewer Source Code
* Copyright (c) 2025 Angeldark Raymaker @ Second Life
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
* http://www.firestormviewer.org
* $/LicenseInfo$
*/
#ifndef FS_MANIP_TRANSLATE_JOINT_H
#define FS_MANIP_TRANSLATE_JOINT_H
#include "fsmaniprotatejoint.h"
#include "llmaniptranslate.h"
class LLJoint;
class LLVOAvatar; // for LLVOAvatarSelf, etc.
class FSManipTranslateJoint : public LLManipTranslate
{
public:
FSManipTranslateJoint(LLToolComposite* composite);
virtual ~FSManipTranslateJoint() = default;
/// <summary>
/// Sets the joint we are going to manipulate.
/// </summary>
/// <param name="joint">The joint to interact with.</param>
void setJoint(LLJoint* joint);
/// <summary>
/// Sets the avatar the manip should interact with.
/// </summary>
/// <param name="avatar">The avatar to interact with.</param>
void setAvatar(LLVOAvatar* avatar);
/// <summary>
/// Sets the avatar the manip should interact with.
/// </summary>
/// <param name="avatar">The avatar to interact with.</param>
void setReferenceFrame(const E_PoserReferenceFrame frame) { mReferenceFrame = frame; };
static U32 getGridTexName();
static void destroyGL();
static void restoreGL();
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;
void render() override;
void handleSelect() override;
void highlightManipulators(S32 x, S32 y) override;
bool handleMouseDownOnPart(S32 x, S32 y, MASK mask) override;
bool canAffectSelection() override;
protected:
void renderArrow(S32 which_arrow, S32 selected_arrow, F32 box_size, F32 arrow_size, F32 handle_size, bool reverse_direction);
void renderTranslationHandles();
void renderText();
private:
bool isAvatarJointSafeToUse();
/// <summary>
/// Determines if the currently selected joint is one to move with manip.
/// </summary>
/// <returns>True if we can move the joint with manip, otherwise false.</returns>
bool isMoveableJoint();
void getManipJointNormal(LLJoint* joint, EManipPart manip, LLVector3& normal) { getManipNormal(nullptr, manip, normal); };
bool getManipJointAxis(LLJoint* joint, EManipPart manip, LLVector3& axis) { return getManipAxis(nullptr, manip, axis); };
LLVector3 getChangeInPosition(const LLVector3& newPosition) const;
LLVector3 mDragCursorLastGlobal;
LLJoint* mJoint = nullptr;
LLVOAvatar* mAvatar = nullptr;
E_PoserReferenceFrame mReferenceFrame = POSER_FRAME_BONE;
};
#endif
+314 -36
View File
@@ -24,7 +24,6 @@
* $/LicenseInfo$
*/
#include <deque>
#include <boost/algorithm/string.hpp>
#include "fsposeranimator.h"
#include "llcharacter.h"
@@ -114,9 +113,26 @@ void FSPoserAnimator::undoLastJointChange(LLVOAvatar* avatar, const FSPoserJoint
if (!jointPose)
return;
if (jointPose->undoLastChange())
E_PoserChangeType changeType = jointPose->undoLastChange();
if (changeType == POSER_CHANGE_ROTATION)
undoOrRedoWorldLockedDescendants(joint, posingMotion, false);
if (changeType == POSER_CHANGE_CHILDMOVE)
{
undoOrRedoRotatedParents(joint, posingMotion, false);
FSJointPose* parentJointPose = findParentJointPose(posingMotion, joint.jointName());
if (parentJointPose)
{
FSJointPose* grandParentJointPose = findParentJointPose(posingMotion, parentJointPose->jointName());
if (grandParentJointPose)
{
auto grandParentPoserJoint = getPoserJointByName(grandParentJointPose->jointName());
undoOrRedoWorldLockedDescendants(*grandParentPoserJoint, posingMotion, false);
}
}
}
if (style == NONE || style == DELTAMODE)
return;
@@ -124,7 +140,8 @@ void FSPoserAnimator::undoLastJointChange(LLVOAvatar* avatar, const FSPoserJoint
if (!oppositeJointPose)
return;
if (!oppositeJointPose->undoLastChange())
changeType = oppositeJointPose->undoLastChange();
if (changeType != POSER_CHANGE_ROTATION)
return;
auto oppositePoserJoint = getPoserJointByName(joint.mirrorJointName());
@@ -198,8 +215,10 @@ void FSPoserAnimator::redoLastJointChange(LLVOAvatar* avatar, const FSPoserJoint
if (!jointPose)
return;
jointPose->redoLastChange();
E_PoserChangeType changeType = jointPose->redoLastChange();
undoOrRedoWorldLockedDescendants(joint, posingMotion, true);
if (changeType == POSER_CHANGE_CHILDMOVE)
undoOrRedoRotatedParents(joint, posingMotion, true);
if (style == NONE || style == DELTAMODE)
return;
@@ -360,7 +379,7 @@ void FSPoserAnimator::setRotationIsWorldLocked(LLVOAvatar* avatar, const FSPoser
bool FSPoserAnimator::exportRotationWillLockJoint(LLVOAvatar* avatar, const FSPoserJoint& joint) const
{
const F32 ROTATION_KEYFRAME_THRESHOLD = 0.01f; // this is a guestimate: see BVH loader
constexpr F32 ROTATION_KEYFRAME_THRESHOLD = 0.01f; // this is a guestimate: see BVH loader
if (!isAvatarSafeToUse(avatar))
return false;
@@ -465,6 +484,15 @@ void FSPoserAnimator::recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joi
void FSPoserAnimator::updateJointFromManip(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero,
E_BoneDeflectionStyles style, E_PoserReferenceFrame frame, const LLQuaternion& rotation,
const LLVector3& position, const LLVector3& scale)
{
if (!position.isExactlyZero())
updateJointPositionFromManip(avatar, joint, resetBaseRotationToZero, style, position);
else
updateJointRotationFromManip(avatar, joint, resetBaseRotationToZero, style, frame, rotation);
}
void FSPoserAnimator::updateJointRotationFromManip(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero,
E_BoneDeflectionStyles style, E_PoserReferenceFrame frame, const LLQuaternion& rotation)
{
if (!isAvatarSafeToUse(avatar))
return;
@@ -478,7 +506,7 @@ void FSPoserAnimator::updateJointFromManip(LLVOAvatar* avatar, const FSPoserJoin
return;
LLQuaternion framedRotation = changeToRotationFrame(avatar, rotation, frame, jointPose);
jointPose->setPublicRotation(resetBaseRotationToZero, framedRotation * jointPose->getPublicRotation());
jointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_ROTATION, framedRotation * jointPose->getPublicRotation());
deRotateWorldLockedDescendants(joint, posingMotion, framedRotation);
@@ -495,14 +523,16 @@ void FSPoserAnimator::updateJointFromManip(LLVOAvatar* avatar, const FSPoserJoin
{
case SYMPATHETIC:
case SYMPATHETIC_DELTA:
oppositeJointPose->setPublicRotation(resetBaseRotationToZero, framedRotation * oppositeJointPose->getPublicRotation());
oppositeJointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_ROTATION,
framedRotation * oppositeJointPose->getPublicRotation());
if (oppositePoserJoint)
deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, framedRotation);
break;
case MIRROR:
case MIRROR_DELTA:
oppositeJointPose->setPublicRotation(resetBaseRotationToZero, mirroredRotation * oppositeJointPose->getPublicRotation());
oppositeJointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_ROTATION,
mirroredRotation * oppositeJointPose->getPublicRotation());
if (oppositePoserJoint)
deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, mirroredRotation);
break;
@@ -512,6 +542,207 @@ void FSPoserAnimator::updateJointFromManip(LLVOAvatar* avatar, const FSPoserJoin
}
}
void FSPoserAnimator::updateJointPositionFromManip(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero,
E_BoneDeflectionStyles style, const LLVector3& position)
{
if (!isAvatarSafeToUse(avatar))
return;
FSPosingMotion* posingMotion = getPosingMotion(avatar);
if (!posingMotion)
return;
LLJoint* movedJoint = avatar->getJoint(joint->jointName());
if (!movedJoint)
return;
LLJoint* parentJoint = movedJoint->getParent();
if (!parentJoint)
return;
if (jointIsPelvis(movedJoint))
{
auto movedPoserJoint = getPoserJointByName(joint->jointName());
LLVector3 currentPosition = getJointPosition(avatar, *movedPoserJoint);
LLQuaternion rootWorldRot = parentJoint->getWorldRotation();
currentPosition += position * ~rootWorldRot;
setJointPosition(avatar, movedPoserJoint, currentPosition, POSER_FRAME_WORLD, NONE);
return;
}
if (jointIsPelvis(parentJoint))
return;
LLJoint* grandParentJoint = parentJoint->getParent();
if (!grandParentJoint || jointIsPelvis(grandParentJoint))
return;
LLVector3 oldMovedJointPosition = movedJoint->getWorldPosition();
LLVector3 parentPosition = parentJoint->getWorldPosition();
LLVector3 grandParentPosition = grandParentJoint->getWorldPosition();
LLVector3 newMovedJointPosition = oldMovedJointPosition + position;
LLQuaternion grandParentWorldRot = grandParentJoint->getWorldRotation();
LLQuaternion parentWorldRot = parentJoint->getWorldRotation();
FSJointPose* grandParentJointPose = posingMotion->getJointPoseByJointName(grandParentJoint->getName());
FSJointPose* parentJointPose = posingMotion->getJointPoseByJointName(parentJoint->getName());
FSJointPose* movedJointPose = posingMotion->getJointPoseByJointName(movedJoint->getName());
if (!grandParentJointPose || !parentJointPose)
return;
LLQuaternion grandParentFrameRotChange, grandParentLengthRotChange, parentLengthRotChange;
if (parentJointIsHinge(parentJoint))
{
if (!canMoveToNewPosition(grandParentPosition, parentPosition, oldMovedJointPosition, newMovedJointPosition))
return;
grandParentFrameRotChange = getAbcFrameRotation(grandParentPosition, parentPosition, oldMovedJointPosition, position);
if (!getPlanarRotationAtAandB(grandParentPosition, parentPosition, oldMovedJointPosition, position, grandParentLengthRotChange,
parentLengthRotChange))
return;
}
else
{
grandParentFrameRotChange.loadIdentity(); // unused here
if (!getFreeRotationAtAandB(grandParentPosition, parentPosition, oldMovedJointPosition, position, grandParentLengthRotChange,
parentLengthRotChange))
return;
}
// convert rotations to local frame
grandParentFrameRotChange = grandParentWorldRot * grandParentFrameRotChange * ~grandParentWorldRot;
grandParentLengthRotChange = grandParentWorldRot * grandParentLengthRotChange * ~grandParentWorldRot;
parentLengthRotChange = parentWorldRot * parentLengthRotChange * ~parentWorldRot;
movedJointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_CHILDMOVE, movedJointPose->getPublicRotation()); // set up undo
parentJointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_ROTATION,
parentLengthRotChange * parentJointPose->getPublicRotation());
grandParentJointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_ROTATION,
grandParentLengthRotChange * grandParentJointPose->getPublicRotation());
grandParentJointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_ROTATION,
grandParentFrameRotChange * grandParentJointPose->getPublicRotation());
auto grandParentPoserJoint = getPoserJointByName(grandParentJoint->getName());
deRotateWorldLockedDescendants(grandParentPoserJoint, posingMotion, grandParentLengthRotChange);
}
bool FSPoserAnimator::getFreeRotationAtAandB(const LLVector3& posA, const LLVector3& posB, const LLVector3& posC, const LLVector3& changeAtC,
LLQuaternion& changeAtA, LLQuaternion& changeAtB)
{
LLVector3 vectorAC = posC - posA;
LLVector3 vectorAB = posB - posA;
F32 lengthAC = vectorAC.length();
F32 lengthAB = vectorAB.length();
F32 lengthBC = (posC - posB).length();
F32 lengthMove = changeAtC.length();
LLVector3 rotationAxis = vectorAC % changeAtC;
rotationAxis.normalize();
F32 angle = asin(lengthMove / lengthAC);
if (llisnan(angle))
return false;
changeAtA = LLQuaternion(angle, rotationAxis);
changeAtB = LLQuaternion(-angle, rotationAxis);
return true;
}
bool FSPoserAnimator::getPlanarRotationAtAandB(const LLVector3& posA, const LLVector3& posB, const LLVector3& posC, const LLVector3& changeAtC,
LLQuaternion& changeAtA, LLQuaternion& changeAtB)
{
LLVector3 vectorAC = posC - posA;
LLVector3 vectorAB = posB - posA;
F32 lengthAC = vectorAC.length();
F32 lengthAB = vectorAB.length();
F32 lengthBC = (posC - posB).length();
F32 lengthACbeforeRot = vectorAC.length();
F32 lengthACafterRot = (posC + changeAtC - posA).length();
LLVector3 rotationAxis = vectorAB % vectorAC;
rotationAxis.normalize();
// Distance from posA, along AC, to where B is perpendicular to AC (right angle triangle), before and after moving C; intersecting spheres
F32 dist1 = (lengthAB * lengthAB - lengthBC * lengthBC + lengthACbeforeRot * lengthACbeforeRot) / (2.f * lengthACbeforeRot);
F32 dist2 = (lengthAB * lengthAB - lengthBC * lengthBC + lengthACafterRot * lengthACafterRot) / (2.f * lengthACafterRot);
if (dist1 == dist2)
return false;
F32 angle1 = acos(dist1 / lengthAB);
F32 angle2 = acos(dist2 / lengthAB);
changeAtA = LLQuaternion((angle1 - angle2), rotationAxis);
angle1 = acos((lengthACbeforeRot - dist1) / lengthBC);
angle2 = acos((lengthACafterRot - dist2) / lengthBC);
changeAtB = LLQuaternion((angle2 - angle1), rotationAxis);
return true;
}
LLQuaternion FSPoserAnimator::getAbcFrameRotation(const LLVector3& posA, const LLVector3& posB, const LLVector3& posC, const LLVector3& changeAtC)
{
LLVector3 vectorAC = posC - posA;
LLVector3 vectorAnewC = vectorAC + changeAtC;
F32 angle = acos((vectorAC * vectorAnewC) / (vectorAC.length() * vectorAnewC.length()));
LLVector3 rotationAxis = vectorAC % changeAtC;
rotationAxis.normalize();
if (llisnan(angle))
angle = 0.f;
return LLQuaternion(angle, rotationAxis);
}
bool FSPoserAnimator::parentJointIsHinge(LLJoint* parentJoint)
{
if (!parentJoint)
return false;
std::string jointName = parentJoint->getName();
if (jointName.find("Elbow") != std::string::npos)
return true;
if (jointName.find("Knee") != std::string::npos)
return true;
if (jointName.find("Hand") != std::string::npos && jointName.find("2") != std::string::npos) // eg: mHandIndex2Right, the second 'knuckle'
return true;
return false;
}
bool FSPoserAnimator::jointIsPelvis(const LLJoint* joint)
{
if (!joint)
return false;
std::string jointName = joint->getName();
return jointName == "mPelvis" || jointName == "mRoot";
}
bool FSPoserAnimator::canMoveToNewPosition(const LLVector3& posA, const LLVector3& posB, const LLVector3& posC, const LLVector3& posNewC)
{
F32 lengthAC = (posNewC - posA).length();
if (lengthAC < 0.04f)
return false; // don't allow C and A to converge
F32 lengthAB = (posB - posA).length();
F32 lengthBC = (posC - posB).length();
if (lengthAC > lengthAB + lengthBC - 0.002f)
return false; // if the limbs become fully extended then moving back is non-trivial because the 3 joints are not a triangle
if (lengthAC < fabs(lengthAB - lengthBC))
return false; // absurdity
return true;
}
LLQuaternion FSPoserAnimator::getManipGimbalRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, E_PoserReferenceFrame frame)
{
LLQuaternion globalRot(-1.f, 0.f, 0.f, 0.f);
@@ -548,16 +779,19 @@ LLQuaternion FSPoserAnimator::getManipGimbalRotation(LLVOAvatar* avatar, const F
if (frame == POSER_FRAME_BONE)
return llJoint->getWorldRotation();
return getQuaternionFromWorldVector(llJoint->getWorldPosition() - gAgentCamera.getCameraPositionAgent());
}
LLQuaternion FSPoserAnimator::getQuaternionFromWorldVector(const LLVector3& worldVector)
{
LLVector3 skyward(0.f, 0.f, 1.f);
LLVector3 left(1.f, 0.f, 0.f);
LLVector3 up, jointToCameraPosition, jointPosition;
jointPosition = llJoint->getWorldPosition();
jointToCameraPosition = jointPosition - gAgentCamera.getCameraPositionAgent();
jointToCameraPosition.normalize();
left.setVec(skyward % jointToCameraPosition);
up.setVec(jointToCameraPosition % left);
LLVector3 up, normVector(worldVector);
normVector.normalize();
left.setVec(skyward % normVector);
up.setVec(normVector % left);
return LLQuaternion(jointToCameraPosition, left, up);
return LLQuaternion(normVector, left, up);
}
LLVector3 FSPoserAnimator::getJointExportRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, bool lockWholeAvatar) const
@@ -633,28 +867,28 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j
case SYMPATHETIC:
case MIRROR:
if (rotationStyle == DELTAIC_ROT || translationRequiresDelta)
jointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * jointPose->getPublicRotation());
jointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_DEFAULT, deltaRot * jointPose->getPublicRotation());
else
jointPose->setPublicRotation(resetBaseRotationToZero, absRot);
jointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_DEFAULT, absRot);
break;
case SYMPATHETIC_DELTA:
case MIRROR_DELTA:
jointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * jointPose->getPublicRotation());
jointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_DEFAULT, deltaRot * jointPose->getPublicRotation());
break;
case DELTAMODE:
jointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * jointPose->getPublicRotation());
jointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_DEFAULT, deltaRot * jointPose->getPublicRotation());
deRotateWorldLockedDescendants(joint, posingMotion, deltaRot);
return;
case NONE:
default:
if (rotationStyle == DELTAIC_ROT || translationRequiresDelta)
jointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * jointPose->getPublicRotation());
jointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_DEFAULT, deltaRot * jointPose->getPublicRotation());
else
jointPose->setPublicRotation(resetBaseRotationToZero, absRot);
jointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_DEFAULT, absRot);
deRotateWorldLockedDescendants(joint, posingMotion, deltaRot);
return;
@@ -677,7 +911,8 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j
break;
case SYMPATHETIC_DELTA:
oppositeJointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * oppositeJointPose->getPublicRotation());
oppositeJointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_DEFAULT,
deltaRot * oppositeJointPose->getPublicRotation());
if (oppositePoserJoint)
deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, deltaRot);
break;
@@ -689,7 +924,8 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j
break;
case MIRROR_DELTA:
oppositeJointPose->setPublicRotation(resetBaseRotationToZero, mirroredRotation * oppositeJointPose->getPublicRotation());
oppositeJointPose->setPublicRotation(resetBaseRotationToZero, POSER_CHANGE_DEFAULT,
mirroredRotation * oppositeJointPose->getPublicRotation());
if (oppositePoserJoint)
deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, mirroredRotation);
break;
@@ -1071,7 +1307,7 @@ void FSPoserAnimator::loadJointRotation(LLVOAvatar* avatar, const FSPoserJoint*
jointPose->purgeUndoQueue();
LLQuaternion rot = translateRotationToQuaternion(avatar, jointPose, POSER_FRAME_BONE, SWAP_NOTHING, NEGATE_NOTHING, rotation);
jointPose->setPublicRotation(setBaseToZero, rot);
jointPose->setPublicRotation(setBaseToZero, POSER_CHANGE_DEFAULT, rot);
}
void FSPoserAnimator::loadJointPosition(LLVOAvatar* avatar, const FSPoserJoint* joint, bool loadPositionAsDelta, LLVector3 position)
@@ -1332,10 +1568,10 @@ bool FSPoserAnimator::isAvatarSafeToUse(LLVOAvatar* avatar) const
int FSPoserAnimator::getChildJointDepth(const FSPoserJoint* joint, S32 depth) const
{
size_t numberOfBvhChildNodes = joint->bvhChildren().size();
if (numberOfBvhChildNodes < 1)
if (joint->bvhChildren().empty())
return depth;
size_t numberOfBvhChildNodes = joint->bvhChildren().size();
depth++;
for (size_t index = 0; index != numberOfBvhChildNodes; ++index)
@@ -1352,10 +1588,10 @@ int FSPoserAnimator::getChildJointDepth(const FSPoserJoint* joint, S32 depth) co
void FSPoserAnimator::deRotateWorldLockedDescendants(const FSPoserJoint* joint, FSPosingMotion* posingMotion, LLQuaternion rotationChange)
{
size_t numberOfBvhChildNodes = joint->bvhChildren().size();
if (numberOfBvhChildNodes < 1)
if (joint->bvhChildren().empty())
return;
size_t numberOfBvhChildNodes = joint->bvhChildren().size();
FSJointPose* parentJoint = posingMotion->getJointPoseByJointName(joint->jointName());
if (!parentJoint)
return;
@@ -1385,14 +1621,14 @@ void FSPoserAnimator::deRotateJointOrFirstLockedChild(const FSPoserJoint* joint,
LLQuaternion rotDiffInChildFrame = differenceInWorldRot * rotationChange * ~differenceInWorldRot;
rotDiffInChildFrame.conjugate();
jointPose->setPublicRotation(false, rotDiffInChildFrame * jointPose->getPublicRotation());
jointPose->setPublicRotation(false, POSER_CHANGE_ROTATION, rotDiffInChildFrame * jointPose->getPublicRotation());
return;
}
size_t numberOfBvhChildNodes = joint->bvhChildren().size();
if (numberOfBvhChildNodes < 1)
if (joint->bvhChildren().empty())
return;
size_t numberOfBvhChildNodes = joint->bvhChildren().size();
for (size_t index = 0; index != numberOfBvhChildNodes; ++index)
{
auto nextJoint = getPoserJointByName(joint->bvhChildren().at(index));
@@ -1403,12 +1639,54 @@ void FSPoserAnimator::deRotateJointOrFirstLockedChild(const FSPoserJoint* joint,
}
}
void FSPoserAnimator::undoOrRedoWorldLockedDescendants(const FSPoserJoint& joint, FSPosingMotion* posingMotion, bool redo)
void FSPoserAnimator::undoOrRedoRotatedParents(const FSPoserJoint& joint, FSPosingMotion* posingMotion, bool redo)
{
size_t numberOfBvhChildNodes = joint.bvhChildren().size();
if (numberOfBvhChildNodes < 1)
if (!posingMotion)
return;
FSJointPose* parentJointPose = findParentJointPose(posingMotion, joint.jointName());
if (!parentJointPose)
return;
if (redo)
parentJointPose->redoLastChange();
else
parentJointPose->undoLastChange();
FSJointPose* grandParentJointPose = findParentJointPose(posingMotion, parentJointPose->jointName());
if (!grandParentJointPose)
return;
if (redo)
grandParentJointPose->redoLastChange();
else
grandParentJointPose->undoLastChange();
}
FSJointPose* FSPoserAnimator::findParentJointPose(FSPosingMotion* posingMotion, std::string jointName)
{
if (jointName.empty() || !posingMotion)
return nullptr;
for (size_t index = 0; index != PoserJoints.size(); ++index)
{
auto children = PoserJoints[index].bvhChildren();
auto it = std::find(children.begin(), children.end(), jointName);
if (it == children.end())
continue;
return posingMotion->getJointPoseByJointName(PoserJoints[index].jointName());
}
return nullptr;
}
void FSPoserAnimator::undoOrRedoWorldLockedDescendants(const FSPoserJoint& joint, FSPosingMotion* posingMotion, bool redo)
{
if (joint.bvhChildren().empty())
return;
size_t numberOfBvhChildNodes = joint.bvhChildren().size();
for (size_t index = 0; index != numberOfBvhChildNodes; ++index)
{
auto nextJoint = getPoserJointByName(joint.bvhChildren().at(index));
@@ -1438,10 +1716,10 @@ void FSPoserAnimator::undoOrRedoJointOrFirstLockedChild(const FSPoserJoint& join
return;
}
size_t numberOfBvhChildNodes = joint.bvhChildren().size();
if (numberOfBvhChildNodes < 1)
if (joint.bvhChildren().empty())
return;
size_t numberOfBvhChildNodes = joint.bvhChildren().size();
for (size_t index = 0; index != numberOfBvhChildNodes; ++index)
{
auto nextJoint = getPoserJointByName(joint.bvhChildren().at(index));
+95 -13
View File
@@ -792,19 +792,19 @@ public:
void applyJointMirrorToBaseRotations(FSPosingMotion* posingMotion);
private:
/// <summary>
/// Translates the supplied rotation vector from UI to a Quaternion for the bone.
/// Also performs the axis-swapping and other transformations for up/down/left/right to make sense.
/// </summary>
/// <param name="avatar">The avatar whose joint is being manipulated.</param>
/// <param name="joint">The joint which is being altered.</param>
/// <param name="frame">The frame of reference the translation should be performed in.</param>
/// <param name="translation">The axis translation to perform.</param>
/// <param name="negation">The style of axis-negation.</param>
/// <param name="rotation">The rotation to translate and transform to quaternion.</param>
/// <returns>The translated rotation quaternion.</returns>
LLQuaternion translateRotationToQuaternion(LLVOAvatar* avatar, FSJointPose* joint, E_PoserReferenceFrame frame,
E_BoneAxisTranslation translation, S32 negation, LLVector3 rotation);
/// <summary>
/// Translates the supplied rotation vector from UI to a Quaternion for the bone.
/// Also performs the axis-swapping and other transformations for up/down/left/right to make sense.
/// </summary>
/// <param name="avatar">The avatar whose joint is being manipulated.</param>
/// <param name="joint">The joint which is being altered.</param>
/// <param name="frame">The frame of reference the translation should be performed in.</param>
/// <param name="translation">The axis translation to perform.</param>
/// <param name="negation">The style of axis-negation.</param>
/// <param name="rotation">The rotation to translate and transform to quaternion.</param>
/// <returns>The translated rotation quaternion.</returns>
LLQuaternion translateRotationToQuaternion(LLVOAvatar* avatar, FSJointPose* joint, E_PoserReferenceFrame frame,
E_BoneAxisTranslation translation, S32 negation, LLVector3 rotation);
/// <summary>
/// Translates a bone-rotation quaternion to a vector usable easily on the UI.
@@ -900,6 +900,88 @@ public:
/// </remarks>
LLQuaternion changeToRotationFrame(LLVOAvatar* avatar, const LLQuaternion& rotation, E_PoserReferenceFrame frame, FSJointPose* joint);
void updateJointRotationFromManip(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero,
E_BoneDeflectionStyles style, E_PoserReferenceFrame frame, const LLQuaternion& rotation);
void updateJointPositionFromManip(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero,
E_BoneDeflectionStyles style, const LLVector3& position);
/// <summary>
/// Undoes or re-does the parent and grandparent joint rotations from a updateJointPositionFromManip(...)
/// </summary>
/// <param name="joint">The joint whose parent and grandparent need to undo.</param>
/// <param name="posingMotion">The posing motion.</param>
/// <param name="redo">Whether to redo the rotation.</param>
void undoOrRedoRotatedParents(const FSPoserJoint& joint, FSPosingMotion* posingMotion, bool redo);
/// <summary>
/// Finds a jointPose using the supplied name.
/// </summary>
/// <param name="posingMotion">The posing motion.</param>
/// <param name="jointName">The name of the joint.</param>
/// <returns>The jointPose if found, otherwise nullptr.</returns>
FSJointPose* findParentJointPose(FSPosingMotion* posingMotion, std::string jointName);
/// <summary>
/// Determines the rotation of the grand-parent joint to move a system containing a 'hinge' joint.
/// </summary>
/// <param name="posA">The world position of the grand-parent joint.</param>
/// <param name="posC">The world position of the parent joint.</param>
/// <param name="posC">The world position of the moved joint.</param>
/// <param name="changeAtC">The world-referenced movement of the moved joint.</param>
/// <returns>The rotation required for the system of moved-joint, parent and grandparent.</returns>
/// <remarks>
/// The first of a 2-part treatment to move a joint by changing the rotation of its two parents.
/// This method determines the rotation for the entire frame (a triangle) of joint-parent-grandparent.
/// It finds the vector of the grand-parent to child (vectorAC) before and after moment, then uses
/// dot-product to determine the angle change.
/// Cross product of the change-vector and AC determines the axis of rotation.
/// </remarks>
LLQuaternion getAbcFrameRotation(const LLVector3& posA, const LLVector3& posB, const LLVector3& posC, const LLVector3& changeAtC);
/// <summary>
/// Determines the rotation changes for the grandparent and parent joints to position the child when the parent joint is a hinge.
/// </summary>
/// <param name="posA">The world position of the grand-parent joint.</param>
/// <param name="posB">The world position of the parent joint.</param>
/// <param name="posC">The world position of the moved joint.</param>
/// <param name="changeAtC">The world-referenced movement of the moved joint.</param>
/// <param name="changeAtA">The rotation at the grandparent to extend or retract the moved joint.</param>
/// <param name="changeAtB">The rotation at the parent to extend or retract the moved joint.</param>
/// <returns>True if the rotation can be made, otherwise false.</returns>
/// <remarks>
/// The second of a 2-part treatment to move a joint by changing the rotation of its two parents.
/// This method calculates rotations at grandparent and parent so that the length AC (grandparent to child) changes.
/// Both rotations are in the plane created by grandparent-parent-child.
/// This emulates the behaviour of a 'hinge' joint, like 'flexing' an elbow or knee.
/// Child movement is along vector AC (grandparent-child), and does not account for 'world movement' differences in C because of changeAtC,
/// as that is handled by getAbcFrameRotation.
/// </remarks>
bool getPlanarRotationAtAandB(const LLVector3& posA, const LLVector3& posB, const LLVector3& posC, const LLVector3& changeAtC,
LLQuaternion& changeAtA, LLQuaternion& changeAtB);
/// <summary>
/// Determines the rotation changes for the grandparent and parent joints to position the child when all joints are 'socket' joints.
/// </summary>
/// <param name="posA">The world position of the grand-parent joint.</param>
/// <param name="posB">The world position of the parent joint.</param>
/// <param name="posC">The world position of the moved joint.</param>
/// <param name="changeAtC">The world-referenced movement of the moved joint.</param>
/// <param name="changeAtA">The rotation at the grandparent to extend or retract the moved joint.</param>
/// <param name="changeAtB">The rotation at the parent to extend or retract the moved joint.</param>
/// <returns>True if the rotation can be made, otherwise false.</returns>
/// <remarks>
/// Moves the child spherically in the specified direction by rotating the parent and grandparent.
/// </remarks>
bool getFreeRotationAtAandB(const LLVector3& posA, const LLVector3& posB, const LLVector3& posC, const LLVector3& changeAtC,
LLQuaternion& changeAtA, LLQuaternion& changeAtB);
LLQuaternion getQuaternionFromWorldVector(const LLVector3& worldVector);
bool parentJointIsHinge(LLJoint* parentJoint);
bool jointIsPelvis(const LLJoint* joint);
bool canMoveToNewPosition(const LLVector3& posA, const LLVector3& posB, const LLVector3& posC, const LLVector3& posNewC);
/// <summary>
/// Maps the avatar's ID to the animation registered to them.
/// Thus we start/stop the same animation, and get/set the same rotations etc.
+1 -1
View File
@@ -279,7 +279,7 @@ void FSPosingMotion::setAllRotationsToZeroAndClearUndo()
for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter)
{
poserJoint_iter->purgeUndoQueue();
poserJoint_iter->setPublicRotation(true, LLQuaternion::DEFAULT);
poserJoint_iter->setPublicRotation(true, POSER_CHANGE_ROTATION, LLQuaternion::DEFAULT);
}
}
+12
View File
@@ -1039,6 +1039,12 @@ void LLAgentCamera::cameraZoomIn(const F32 fraction)
new_distance = llclamp(new_distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM);
}
}
else
{
// <FS:TJ> FIRE-23470: Fix camera controls zoom glitch
//new_distance = llmin(new_distance, getCameraMaxZoomDistance());
new_distance = llmin(new_distance, getCameraMaxZoomDistance(true));
}
// [RLVa:KB] - Checked: 2.0.0
if ( (RlvActions::isRlvEnabled()) && (!allowFocusOffsetChange(new_distance * camera_offset_unit)) )
@@ -1109,6 +1115,12 @@ void LLAgentCamera::cameraOrbitIn(const F32 meters)
new_distance = llclamp(new_distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM);
}
}
else
{
// <FS:TJ> FIRE-23470: Fix camera controls zoom glitch
//new_distance = llmin(new_distance, getCameraMaxZoomDistance());
new_distance = llmin(new_distance, getCameraMaxZoomDistance(true));
}
// [RLVa:KB] - Checked: 2.0.0
if ( (RlvActions::isRlvEnabled()) && (!allowFocusOffsetChange(new_distance * camera_offset_unit)) )
+11 -2
View File
@@ -32,6 +32,7 @@
#include "llgroupactions.h"
#include "llconversationloglist.h"
#include "llconversationloglistitem.h"
#include "llnotificationsutil.h" // <FS:TJ/>
#include "llviewermenu.h"
#include "lltrans.h"
#include "llviewercontrol.h" // <FS:CR>
@@ -360,8 +361,16 @@ void LLConversationLogList::onCustomAction(const LLSD& userdata)
// <FS:CR> Open conversation history externally
else if ("chat_history_external" == command_name)
{
gViewerWindow->getWindow()->openFile(LLLogChat::makeLogFileName(
LLConversationLog::getInstance()->getConversation(selected_conversation_session_id)->getHistoryFileName()));
std::string log_file_name = LLLogChat::makeLogFileName(
LLConversationLog::getInstance()->getConversation(selected_conversation_session_id)->getHistoryFileName());
if (gDirUtilp->fileExists(log_file_name))
{
gViewerWindow->getWindow()->openFile(log_file_name);
}
else
{
LLNotificationsUtil::add("ChatHistoryIsMissing");
}
}
// </FS:CR>
}
@@ -307,7 +307,15 @@ void LLFloaterConversationPreview::onMoreHistoryBtnClick()
// <FS:CR> Open chat history externally
void LLFloaterConversationPreview::onBtnOpenExternal()
{
gViewerWindow->getWindow()->openFile(LLLogChat::makeLogFileName(mChatHistoryFileName));
std::string log_file_name = LLLogChat::makeLogFileName(mChatHistoryFileName);
if (gDirUtilp->fileExists(log_file_name))
{
gViewerWindow->getWindow()->openFile(log_file_name);
}
else
{
LLNotificationsUtil::add("ChatHistoryIsMissing");
}
}
// [FS:CR] FIRE-6545
+35
View File
@@ -39,6 +39,26 @@ LLFloaterMarketplace::~LLFloaterMarketplace()
{
}
void LLFloaterMarketplace::onOpen(const LLSD& key)
{
Params params(key);
if (!params.validateBlock())
{
closeFloater();
return;
}
if (params.url().empty())
{
openMarketplace();
}
else
{
openMarketplaceURL(params.url);
}
}
// just to override LLFloaterWebContent
void LLFloaterMarketplace::onClose(bool app_quitting)
{
@@ -68,3 +88,18 @@ void LLFloaterMarketplace::openMarketplace()
mWebBrowser->navigateTo(url, HTTP_CONTENT_TEXT_HTML);
}
}
void LLFloaterMarketplace::openMarketplaceURL(const std::string& url)
{
if (mCurrentURL != url)
{
mWebBrowser->navigateTo(url, HTTP_CONTENT_TEXT_HTML);
}
}
// static
bool LLFloaterMarketplace::isMarketplaceURL(const std::string& url)
{
static LLCachedControl<std::string> marketplace_url(gSavedSettings, "MarketplaceURL", "https://marketplace.secondlife.com/");
return url.starts_with(marketplace_url());
}
+3
View File
@@ -36,11 +36,14 @@ class LLFloaterMarketplace:
public:
void openMarketplace();
void openMarketplaceURL(const std::string& url);
bool static isMarketplaceURL(const std::string& url);
private:
LLFloaterMarketplace(const LLSD& key);
~LLFloaterMarketplace();
bool postBuild() override;
void onOpen(const LLSD& key) override;
void onClose(bool app_quitting) override;
};
+184 -7
View File
@@ -46,6 +46,7 @@
// [RLVa:KB] - Checked: RLVa-2.0.3
#include "rlvactions.h"
// [/RLVa:KB]
#include "fsfavoritegroups.h" // <FS:PP> Group favorites / pinning
#include "llslurl.h"
#include "llurlaction.h"
@@ -59,8 +60,37 @@ public:
/** Returns true if item1 < item2, false otherwise */
/*virtual*/ bool compare(const LLPanel* item1, const LLPanel* item2) const
{
std::string name1 = static_cast<const LLGroupListItem*>(item1)->getGroupName();
std::string name2 = static_cast<const LLGroupListItem*>(item2)->getGroupName();
// <FS:PP> Group favorites / pinning
// std::string name1 = static_cast<const LLGroupListItem*>(item1)->getGroupName();
// std::string name2 = static_cast<const LLGroupListItem*>(item2)->getGroupName();
const LLGroupListItem* group_item1 = dynamic_cast<const LLGroupListItem*>(item1);
const LLGroupListItem* group_item2 = dynamic_cast<const LLGroupListItem*>(item2);
if (!group_item1 || !group_item2)
{
if (!group_item1 && group_item2)
{
return group_item2->isFavorite() ? false : true;
}
if (group_item1 && !group_item2)
{
return group_item1->isFavorite() ? true : false;
}
return false;
}
bool fav1 = group_item1->isFavorite();
bool fav2 = group_item2->isFavorite();
if (fav1 != fav2)
{
return fav1 > fav2;
}
std::string name1 = group_item1->getGroupName();
std::string name2 = group_item2->getGroupName();
// </FS:PP>
LLStringUtil::toUpper(name1);
LLStringUtil::toUpper(name2);
@@ -76,11 +106,22 @@ public:
/*virtual*/ bool compare(const LLPanel* item1, const LLPanel* item2) const
{
const LLGroupListItem* group_item1 = static_cast<const LLGroupListItem*>(item1);
// <FS:PP> Group favorites / pinning
const LLGroupListItem* group_item1 = dynamic_cast<const LLGroupListItem*>(item1);
const LLGroupListItem* group_item2 = dynamic_cast<const LLGroupListItem*>(item2);
if (!group_item1 || !group_item2)
{
// Sorting doesn't matter, as we hit a separator that shouldn't be visible in this view
return false;
}
// const LLGroupListItem* group_item1 = static_cast<const LLGroupListItem*>(item1);
// </FS:PP>
std::string name1 = group_item1->getGroupName();
bool item1_shared = gAgent.isInGroup(group_item1->getGroupID(), true);
const LLGroupListItem* group_item2 = static_cast<const LLGroupListItem*>(item2);
// const LLGroupListItem* group_item2 = static_cast<const LLGroupListItem*>(item2); - <FS:PP> Group favorites / pinning
std::string name2 = group_item2->getGroupName();
bool item2_shared = gAgent.isInGroup(group_item2->getGroupID(), true);
@@ -134,6 +175,12 @@ LLGroupList::~LLGroupList()
{
if (mForAgent) gAgent.removeListener(this);
if (mContextMenuHandle.get()) mContextMenuHandle.get()->die();
// <FS:PP> Group favorites / pinning
if (mFavoritesChangedConnection.connected())
{
mFavoritesChangedConnection.disconnect();
}
// </FS:PP>
}
void LLGroupList::enableForAgent(bool show_icons)
@@ -152,6 +199,11 @@ void LLGroupList::enableForAgent(bool show_icons)
registrar.add("People.Groups.Action", boost::bind(&LLGroupList::onContextMenuItemClick, this, _2));
enable_registrar.add("People.Groups.Enable", boost::bind(&LLGroupList::onContextMenuItemEnable, this, _2));
// <FS:PP> Group favorites / pinning
mFavoritesChangedConnection = FSFavoriteGroups::getInstance()->setFavoritesChangedCallback(boost::bind(&LLGroupList::onFavoritesChanged, this));
enable_registrar.add("People.Groups.Visible", boost::bind(&LLGroupList::onContextMenuItemVisible, this, _2));
// </FS:PP>
LLToggleableMenu* context_menu = LLUICtrlFactory::getInstance()->createFromFile<LLToggleableMenu>("menu_people_groups.xml",
gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
if(context_menu)
@@ -174,6 +226,13 @@ bool LLGroupList::handleRightMouseDown(S32 x, S32 y, MASK mask)
if (mForAgent)
{
// <FS:PP> Group favorites / pinning
LLPanel* selected_item = getSelectedItem();
if (dynamic_cast<LLGroupListSeparator*>(selected_item))
{
return handled;
}
// </FS:PP>
LLToggleableMenu* context_menu = mContextMenuHandle.get();
if (context_menu && size() > 0)
{
@@ -234,24 +293,53 @@ void LLGroupList::refresh()
clear();
// <FS:PP> Group favorites / pinning
bool has_favorites = false;
bool has_non_favorites = false;
// </FS:PP>
for(S32 i = 0; i < count; ++i)
{
id = gAgent.mGroups.at(i).mID;
const LLGroupData& group_data = gAgent.mGroups.at(i);
if (have_filter && !findInsensitive(group_data.mName, mNameFilter))
continue;
addNewItem(id, group_data.mName, group_data.mInsigniaID, ADD_BOTTOM, group_data.mListInProfile);
// <FS:PP> Group favorites / pinning
// addNewItem(id, group_data.mName, group_data.mInsigniaID, ADD_BOTTOM, group_data.mListInProfile);
bool is_favorite = FSFavoriteGroups::getInstance()->isFavorite(id);
if (is_favorite)
{
has_favorites = true;
}
else
{
has_non_favorites = true;
}
addNewItem(id, group_data.mName, group_data.mInsigniaID, ADD_BOTTOM, group_data.mListInProfile, is_favorite);
// </FS:PP>
}
// Sort the list.
sort();
// <FS:PP> Group favorites / pinning
if (has_favorites && has_non_favorites && !have_filter)
{
addFavoritesSeparator();
}
// </FS:PP>
// Add "none" to list at top if filter not set (what's the point of filtering "none"?).
// but only if some real groups exists. EXT-4838
if (!have_filter && count > 0 && mShowNone)
{
std::string loc_none = LLTrans::getString("GroupsNone");
addNewItem(LLUUID::null, loc_none, LLUUID::null, ADD_TOP);
// <FS:PP> Group favorites / pinning
// addNewItem(LLUUID::null, loc_none, LLUUID::null, ADD_TOP);
addNewItem(LLUUID::null, loc_none, LLUUID::null, ADD_TOP, true, false);
// </FS:PP>
}
selectItemByUUID(highlight_id);
@@ -298,13 +386,17 @@ void LLGroupList::setGroups(const std::map< std::string,LLUUID> group_list)
// PRIVATE Section
//////////////////////////////////////////////////////////////////////////
void LLGroupList::addNewItem(const LLUUID& id, const std::string& name, const LLUUID& icon_id, EAddPosition pos, bool visible_in_profile)
// <FS:PP> Group favorites / pinning
// void LLGroupList::addNewItem(const LLUUID& id, const std::string& name, const LLUUID& icon_id, EAddPosition pos, bool visible_in_profile)
void LLGroupList::addNewItem(const LLUUID& id, const std::string& name, const LLUUID& icon_id, EAddPosition pos, bool visible_in_profile, bool is_favorite)
// </FS:PP>
{
LLGroupListItem* item = new LLGroupListItem(mForAgent, mShowIcons);
item->setGroupID(id);
item->setName(name, mNameFilter);
item->setGroupIconID(icon_id);
item->setFavorite(is_favorite); // <FS:PP> Group favorites / pinning
item->getChildView("info_btn")->setVisible( false);
item->getChildView("profile_btn")->setVisible( false);
@@ -390,6 +482,16 @@ bool LLGroupList::onContextMenuItemClick(const LLSD& userdata)
LLUrlAction::copyURLToClipboard(LLSLURL("group", selected_group, "about").getSLURLString());
}
// </FS:Ansariel>
// <FS:PP> Group favorites / pinning
else if (action == "favorite")
{
FSFavoriteGroups::getInstance()->addFavorite(selected_group);
}
else if (action == "unfavorite")
{
FSFavoriteGroups::getInstance()->removeFavorite(selected_group);
}
// </FS:PP>
return true;
}
@@ -412,9 +514,84 @@ bool LLGroupList::onContextMenuItemEnable(const LLSD& userdata)
if (userdata.asString() == "call")
return real_group_selected && LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking();
// <FS:PP> Group favorites / pinning
if (userdata.asString() == "favorite" || userdata.asString() == "unfavorite")
return real_group_selected; // Just in case
// </FS:PP>
return real_group_selected;
}
// <FS:PP> Group favorites / pinning
bool LLGroupList::onContextMenuItemVisible(const LLSD& userdata)
{
std::string action = userdata.asString();
LLUUID selected_group_id = getSelectedUUID();
if (action == "favorite")
{
return selected_group_id.notNull() && !FSFavoriteGroups::getInstance()->isFavorite(selected_group_id);
}
else if (action == "unfavorite")
{
return selected_group_id.notNull() && FSFavoriteGroups::getInstance()->isFavorite(selected_group_id);
}
return true;
}
void LLGroupList::onFavoritesChanged()
{
setDirty();
}
void LLGroupList::refreshFavorites()
{
setDirty();
}
void LLGroupList::addFavoritesSeparator()
{
// Find the position to insert the separator (after the last favorite)
std::vector<LLPanel*> items;
getItems(items);
// Count favorite items to find where separator should go
S32 favorite_count = 0;
for (auto* panel : items)
{
LLGroupListItem* item = dynamic_cast<LLGroupListItem*>(panel);
if (item && item->isFavorite())
{
++favorite_count;
}
}
if (favorite_count > 0)
{
LLGroupListSeparator* separator = new LLGroupListSeparator();
static const LLUUID SEPARATOR_UUID("00000000-0000-0000-0000-000000000001");
addItem(separator, SEPARATOR_UUID, ADD_BOTTOM, false);
sort();
}
}
/************************************************************************/
/* LLGroupListSeparator implementation */
/************************************************************************/
LLGroupListSeparator::LLGroupListSeparator()
: LLPanel()
{
buildFromFile("panel_group_list_separator.xml");
}
bool LLGroupListSeparator::postBuild()
{
return true;
}
// </FS:PP>
/************************************************************************/
/* LLGroupListItem implementation */
/************************************************************************/
+31 -1
View File
@@ -37,6 +37,8 @@
#include "llgroupmgr.h"
#include <boost/signals2.hpp> // <FS:PP> Group favorites / pinning
/**
* Auto-updating list of agent groups.
*
@@ -72,14 +74,22 @@ public:
LLToggleableMenu* getContextMenu() const { return mContextMenuHandle.get(); }
void refreshFavorites(); // <FS:PP> Group favorites / pinning
private:
void setDirty(bool val = true) { mDirty = val; }
void refresh();
void addNewItem(const LLUUID& id, const std::string& name, const LLUUID& icon_id, EAddPosition pos = ADD_BOTTOM, bool visible_in_profile = true);
// <FS:PP> Group favorites / pinning
// void addNewItem(const LLUUID& id, const std::string& name, const LLUUID& icon_id, EAddPosition pos = ADD_BOTTOM, bool visible_in_profile = true);
void addNewItem(const LLUUID& id, const std::string& name, const LLUUID& icon_id, EAddPosition pos = ADD_BOTTOM, bool visible_in_profile = true, bool is_favorite = false);
void addFavoritesSeparator();
void onFavoritesChanged();
// </FS:PP>
bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata); // called on agent group list changes
bool onContextMenuItemClick(const LLSD& userdata);
bool onContextMenuItemEnable(const LLSD& userdata);
bool onContextMenuItemVisible(const LLSD& userdata); // <FS:PP> Group favorites / pinning
LLHandle<LLToggleableMenu> mContextMenuHandle;
@@ -91,12 +101,29 @@ private:
bool mShowNone;
typedef std::map< std::string,LLUUID> group_map_t;
group_map_t mGroups;
boost::signals2::connection mFavoritesChangedConnection; // <FS:PP> Group favorites / pinning
};
class LLButton;
class LLGroupIconCtrl;
class LLTextBox;
// <FS:PP> Group favorites / pinning
class LLGroupListSeparator : public LLPanel
{
public:
LLGroupListSeparator();
/*virtual*/ bool postBuild();
// Ignore all mouse interactions on the separator
/*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask) { return true; }
/*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask) { return true; }
/*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask) { return true; }
/*virtual*/ bool handleRightMouseUp(S32 x, S32 y, MASK mask) { return true; }
/*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask) { return true; }
};
// </FS:PP>
class LLGroupListItem : public LLPanel
, public LLGroupMgrObserver
{
@@ -110,11 +137,13 @@ public:
const LLUUID& getGroupID() const { return mGroupID; }
const std::string& getGroupName() const { return mGroupName; }
bool isFavorite() const { return mIsFavorite; } // <FS:PP> Group favorites / pinning
void setName(const std::string& name, const std::string& highlight = LLStringUtil::null);
void setGroupID(const LLUUID& group_id);
void setGroupIconID(const LLUUID& group_icon_id);
void setGroupIconVisible(bool visible);
void setFavorite(bool favorite) { mIsFavorite = favorite; } // <FS:PP> Group favorites / pinning
virtual void changed(LLGroupChange gc);
@@ -137,6 +166,7 @@ private:
std::string mGroupName;
bool mForAgent;
bool mIsFavorite{ false }; // <FS:PP> Group favorites / pinning
LLStyle::Params mGroupNameStyle;
S32 mIconWidth;
+101
View File
@@ -79,6 +79,7 @@
#include "llgiveinventory.h"
#include "lllandmarkactions.h"
#include "llviewernetwork.h"
#include "omnifilterengine.h" // <FS:Zi> Omnifilter support
#include "sound_ids.h"
#include "NACLantispam.h"
@@ -731,6 +732,106 @@ void LLIMProcessing::processNewMessage(LLUUID from_id,
*-----------------------------------------------------
*/
// <FS:Zi> Omnifilter support
static LLCachedControl<bool> use_omnifilter(gSavedSettings, "OmnifilterEnabled");
if (use_omnifilter)
{
OmnifilterEngine::Haystack haystack;
haystack.mContent = message;
haystack.mSenderName = agentName;
haystack.mOwnerID = from_id;
switch (dialog)
{
case IM_NOTHING_SPECIAL: // this is the type for regular IMs
{
haystack.mType = OmnifilterEngine::eType::InstantMessage;
break;
}
case IM_SESSION_SEND: // this is the type for regular group IMs
case IM_SESSION_INVITE: // this is the type for new group IM sessions - see LLIMMgr::addMessage()
case IM_DO_NOT_DISTURB_AUTO_RESPONSE:
{
haystack.mType = OmnifilterEngine::eType::GroupChat;
break;
}
case IM_FROM_TASK:
{
// haystack.mSenderName = agentName; // Object name
// haystack.mOwnerID = from_id; // object owner UUID
// haystack.mContent = message; // IM content
haystack.mType = OmnifilterEngine::eType::ObjectInstantMessage;
break;
}
case IM_GROUP_INVITATION:
{
// haystack.mSenderName = agentName; // Inviter "first.last"
// haystack.mOwnerID = from_id; // group ID
haystack.mType = OmnifilterEngine::eType::GroupInvite;
break;
}
case IM_INVENTORY_OFFERED:
{
// haystack.mSenderName = agentName; // Sender "First Last"
// haystack.mOwnerID = from_id; // Sender UUID
// haystack.mContent = message; // item/folder name
haystack.mType = OmnifilterEngine::eType::InventoryOffer;
break;
}
case IM_FRIENDSHIP_OFFERED:
{
// haystack.mSenderName = agentName; // Sender "First Last"
// haystack.mOwnerID = from_id; // Sender UUID
// haystack.mContent = message; // friendship message
haystack.mType = OmnifilterEngine::eType::FriendshipOffer;
break;
}
case IM_LURE_USER:
{
// haystack.mSenderName = agentName; // Sender "First Last"
// haystack.mOwnerID = from_id; // Sender UUID
// haystack.mContent = message; // teleport message
haystack.mType = OmnifilterEngine::eType::Lure;
break;
}
case IM_TELEPORT_REQUEST:
{
// haystack.mSenderName = agentName; // Sender "First Last"
// haystack.mOwnerID = from_id; // Sender UUID
// haystack.mContent = message; // teleport message
haystack.mType = OmnifilterEngine::eType::TeleportRequest;
break;
}
case IM_GROUP_NOTICE:
{
// haystack.mSenderName = agentName; // Notice Sender "First Last"
// haystack.mOwnerID = from_id; // Notice Sender UUID
// haystack.mContent = message; // notice message
haystack.mType = OmnifilterEngine::eType::GroupNotice;
break;
}
default:
{
LL_DEBUGS("Omnifilter") << "unhandled IM type " << (U32)dialog << LL_ENDL;
break;
}
}
const OmnifilterEngine::Needle* needle = OmnifilterEngine::getInstance()->match(haystack);
if (needle)
{
if (needle->mChatReplace.empty()) // no replacement defined, just suppress the message
{
return;
}
LLSD args;
args["REPLACEMENT"] = needle->mChatReplace;
message = LLTrans::getString("OmnifilterReplacement", args);
}
}
// </FS:Zi>
std::string notice_name;
LLSD notice_args;
if (metadata.has("notice"))
+38 -1
View File
@@ -94,6 +94,8 @@
#include "llgiveinventory.h"
#include "llinventoryfunctions.h"
#include "omnifilterengine.h" // <FS:Zi> Omnifilter support
#include <array>
const static std::string ADHOC_NAME_SUFFIX(" Conference");
@@ -3460,7 +3462,10 @@ void LLIMMgr::addMessage(
const LLUUID& session_id,
const LLUUID& target_id,
const std::string& from,
const std::string& msg,
// </FS:Zi> Omnifilter support
// const std::string& msg,
const std::string& original_msg,
// </FS:Zi>
bool is_offline_msg,
const std::string& session_name,
EInstantMessage dialog,
@@ -3474,6 +3479,38 @@ void LLIMMgr::addMessage(
bool is_announcement, // <FS:Ansariel> Special parameter indicating announcements
bool keyword_alert_performed) // <FS:Ansariel> Pass info if keyword alert has been performed
{
// <FS:Zi> Omnifilter support
std::string msg = original_msg;
static LLCachedControl<bool> use_omnifilter(gSavedSettings, "OmnifilterEnabled");
if (use_omnifilter)
{
// this is the type for new group IM sessions started - apparently this is the only place
// we get this type of message
if (dialog == IM_SESSION_INVITE)
{
OmnifilterEngine::Haystack haystack;
haystack.mContent = original_msg;
haystack.mSenderName = from;
haystack.mOwnerID = target_id;
haystack.mType = OmnifilterEngine::eType::GroupChat;
const OmnifilterEngine::Needle* needle = OmnifilterEngine::getInstance()->match(haystack);
if (needle)
{
if (needle->mChatReplace.empty()) // no replacement defined, just suppress the message
{
return;
}
LLSD args;
args["REPLACEMENT"] = needle->mChatReplace;
msg = LLTrans::getString("OmnifilterReplacement", args);
}
}
}
// </FS:Zi>
LLUUID other_participant_id = target_id;
std::string message_display_name = (display_name.empty()) ? from : std::string(display_name);
if (display_id.isNull() && (display_name.empty()))
+1 -1
View File
@@ -89,7 +89,7 @@ const static boost::regex TIMESTAMP("^(\\[\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\
// <FS:Ansariel> Timestamps in chat
// Note: In contrast to the TIMESTAMP_AND_STUFF regex, for the TIMESTAMP_AND_STUFF_SEC regex the match
// on the timestamp is NOT optional. We need this to decide during parsing if we have timestamps or not.
const static boost::regex TIMESTAMP_AND_STUFF_SEC("^(\\[\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{2}:\\d{2}\\s[AaPp][Mm]\\]\\s+|\\[\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{2}:\\d{2}\\]\\s+|\\[\\d{1,2}:\\d{2}:\\d{2}\\s[AaPp][Mm]\\]\\s+|\\[\\d{1,2}:\\d{2}:\\d{2}\\]\\s+)?(.*)$");
const static boost::regex TIMESTAMP_AND_STUFF_SEC("^(\\[\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{2}:\\d{2}\\s[AaPp][Mm]\\]\\s+|\\[\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{2}:\\d{2}\\]\\s+|\\[\\d{1,2}:\\d{2}:\\d{2}\\s[AaPp][Mm]\\]\\s+|\\[\\d{1,2}:\\d{2}:\\d{2}\\]\\s+)(.*)$");
const static boost::regex TIMESTAMP_AND_SEC("^(\\[\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{2}:\\d{2}(\\s[AaPp][Mm])?\\]|\\[\\d{1,2}:\\d{2}:\\d{2}(\\s[AaPp][Mm])?\\]).*");
// </FS:Ansariel>
+4 -1
View File
@@ -82,7 +82,10 @@ protected:
LLColor4 inner_color);
F32 getMinGridScale();
private:
// <FS:Ansariel> Changed to protected for poser
//private:
protected:
// </FS:Ansariel>
S32 mLastHoverMouseX;
S32 mLastHoverMouseY;
bool mMouseOutsideSlop; // true after mouse goes outside slop region
+5
View File
@@ -2606,6 +2606,11 @@ bool LLPanelProfileWeb::postBuild()
mWebBrowser->addObserver(this);
mWebBrowser->setHomePageUrl("about:blank");
// <FS:PP> Load cookies in the profile, in feed tab
mWebBrowser->setErrorPageURL(gSavedSettings.getString("GenericErrorPageURL"));
LLViewerMedia::getInstance()->getOpenIDCookie(mWebBrowser);
// </FS:PP>
return true;
}
+92
View File
@@ -50,6 +50,7 @@
#include "dialogstack.h"
#include "llbutton.h"
#include "llnavigationbar.h"
#include "omnifilterengine.h" // Omnifilter support
// </FS:Zi>
//////////////////////////////////////////////////////////////////////////
@@ -473,6 +474,97 @@ void LLScriptFloaterManager::onAddNotification(const LLUUID& notification_id)
// get scripted Object's ID
LLUUID object_id = notification_id_to_object_id(notification_id);
// <FS:Zi> Omnifilter support
static LLCachedControl<bool> use_omnifilter(gSavedSettings, "OmnifilterEnabled");
if (use_omnifilter)
{
LLNotificationPtr notification = LLNotifications::instance().find(notification_id);
OmnifilterEngine::Haystack haystack;
haystack.mContent = notification->getMessage();
if (notification->getName() == "ScriptDialog") // ScriptDialogGroup seems not to be in use anymore?
{
haystack.mType = OmnifilterEngine::eType::ScriptDialog;
haystack.mSenderName = notification->getPayload()["object_name"].asString();
haystack.mOwnerID = notification->getPayload()["owner_id"];
}
else if (notification->getName() == "ObjectGiveItem") // what about OwnObjectGiveItem?
{
// "description":"\'Object Name\' ( http://slurl.com/secondlife/Region%20Name/x/y/z )"
std::vector<std::string> params;
std::string description = notification->asLLSD()["responder_sd"]["description"].asString();
if (!description.empty())
{
if (LLGridManager::instance().isInSecondLife())
{
LLStringUtil::getTokens(description, params, "/");
haystack.mRegionName = LLURI::unescape(params.at(params.size() - 4));
}
else
{
// OpenSim handles notification descriptions a little differently
constexpr std::string_view location_key = "located at ";
auto location_pos_start = description.find(location_key);
auto location_pos_end = description.find(" <", location_pos_start);
if (location_pos_start != std::string_view::npos && location_pos_end != std::string_view::npos)
{
location_pos_start += location_key.size();
haystack.mRegionName = description.substr(location_pos_start, location_pos_end - location_pos_start);
}
else
{
haystack.mRegionName.clear();
}
}
}
haystack.mType = OmnifilterEngine::eType::URLRequest;
haystack.mSenderName = notification->asLLSD()["responder_sd"]["from_name"].asString();
haystack.mOwnerID = notification->getPayload()["from_id"];
}
else if (notification->getName() == "LoadWebPage")
{
haystack.mType = OmnifilterEngine::eType::ScriptDialog;
haystack.mSenderName = notification->getPayload()["object_name"].asString();
haystack.mOwnerID = notification->getPayload()["owner_id"];
}
else
{
LL_WARNS("Omnifilter") << "unknown notification name: " << notification->getName() << LL_ENDL;
}
const OmnifilterEngine::Needle* needle = OmnifilterEngine::getInstance()->match(haystack);
if (needle)
{
LLSD response = notification->getResponseTemplate();
if (response.has(TEXTBOX_MAGIC_TOKEN))
{
response[TEXTBOX_MAGIC_TOKEN] = needle->mTextBoxReply;
if (response[TEXTBOX_MAGIC_TOKEN].asString().empty())
{
// so we can distinguish between a successfully
// submitted blank textbox, and an ignored toast
response[TEXTBOX_MAGIC_TOKEN] = true;
}
}
else
{
if (!needle->mButtonReply.empty())
{
response[needle->mButtonReply] = true;
}
}
// this will result in DialogStack complaining that there is no matching dialog to remove
// but that should not break anything
notification->respond(response);
return;
}
}
// </FS:Zi>
// Need to indicate of "new message" for object chiclets according to requirements
// specified in the Message Bar design specification. See EXT-3142.
bool set_new_message = false;
+8
View File
@@ -763,6 +763,14 @@ void LLIMSpeakerMgr::updateSpeakers(const LLSD& update)
{
// add or update speaker
speakerp = setSpeaker(agent_id);
// <FS:Zi> add new speakers to participants list
if (speakerp.notNull() && agent_data.has("has_spoken"))
{
speakerp->mLastSpokeTime = mSpeechTimer.getElapsedTimeF32();
speakerp->mHasSpoken = true;
}
// </FS:Zi>
}
else
{
+8
View File
@@ -234,6 +234,7 @@
#include "fscommon.h"
#include "fscorehttputil.h"
#include "fsdata.h"
#include "fsfavoritegroups.h" // <FS:PP> Group favorites / pinning
#include "fsfloatercontacts.h"
#include "fsfloaterimcontainer.h"
#include "fsfloaternearbychat.h"
@@ -252,6 +253,7 @@
#include "llnotificationmanager.h"
#include "llprogressview.h"
#include "NACLantispam.h"
#include "omnifilterengine.h" // <FS:Zi> Omnifilter support
#include "streamtitledisplay.h"
#include "tea.h"
@@ -1305,6 +1307,8 @@ bool idle_startup()
}
show_release_notes_if_required();
OmnifilterEngine::getInstance()->init(); // <FS:Zi> Omnifilter support
if (show_connect_box)
{
LL_DEBUGS("AppInit") << "show_connect_box on" << LL_ENDL;
@@ -3340,6 +3344,10 @@ bool idle_startup()
// Clean up the userauth stuff.
// LLUserAuth::getInstance()->reset();
// <FS:PP> Group favorites / pinning
FSFavoriteGroups::getInstance()->loadFavorites();
// </FS:PP>
LLStartUp::setStartupState( STATE_STARTED );
do_startup_frame();
+148 -5
View File
@@ -1017,13 +1017,11 @@ bool FSToolCompPose::handleMouseUp(S32 x, S32 y, MASK mask)
// If you want SHIFT+CTRL combos to do something else
LLTool* FSToolCompPose::getOverrideTool(MASK mask)
{
// Example from LLToolCompRotate that calls scale if SHIFT+CTRL
if (mask == (MASK_CONTROL | MASK_SHIFT))
if (mask == MASK_CONTROL)
{
// If you have a scale tool, return that. Or else remove
// this if you don't want an override.
return LLToolCompScale::getInstance();
return FSToolCompPoseTranslate::getInstance();
}
// Otherwise fallback
return LLToolComposite::getOverrideTool(mask);
}
@@ -1061,3 +1059,148 @@ void FSToolCompPose::render()
LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE);
}
}
FSToolCompPoseTranslate* FSToolCompPoseTranslate::getInstance()
{
// Meyers singleton pattern
static FSToolCompPoseTranslate instance;
return &instance;
}
//-----------------------------------
// Constructor
FSToolCompPoseTranslate::FSToolCompPoseTranslate() : LLToolComposite(std::string("PoseTranslate"))
{
// Create a joint manipulator
mManip = new FSManipTranslateJoint(this);
// Set the default and current subtool
mCur = mManip;
mDefault = mManip;
}
//-----------------------------------
// Destructor
FSToolCompPoseTranslate::~FSToolCompPoseTranslate()
{
delete mManip;
mManip = nullptr;
delete mSelectRect;
mSelectRect = nullptr;
}
//-----------------------------------
// Handle Hover
bool FSToolCompPoseTranslate::handleHover(S32 x, S32 y, MASK mask)
{
// If the current subtool hasn't captured the mouse,
// switch to your manip subtool (like LLToolCompRotate).
if (!mCur->hasMouseCapture())
{
setCurrentTool(mManip);
}
return mCur->handleHover(x, y, mask);
}
//-----------------------------------
// Handle MouseDown
bool FSToolCompPoseTranslate::handleMouseDown(S32 x, S32 y, MASK mask)
{
mMouseDown = true;
// Kick off an async pick, which calls pickCallback when complete
// so we can see if user clicked on a manip ring or not
gViewerWindow->pickAsync(x, y, mask, pickCallback);
return true;
}
//-----------------------------------
// The pickCallback
void FSToolCompPoseTranslate::pickCallback(const LLPickInfo& pick_info)
{
FSToolCompPoseTranslate* self = FSToolCompPoseTranslate::getInstance();
FSManipTranslateJoint* manip = self->mManip;
if (!manip) return; // No manipulator available, exit
// Highlight the manipulator based on the mouse position
manip->highlightManipulators(pick_info.mMousePt.mX, pick_info.mMousePt.mY);
if (!self->mMouseDown)
{
// No action needed if mouse is up; interaction is handled by highlight logic
return;
}
// Check if a manipulator ring is highlighted
if (manip->getHighlightedPart() != LLManip::LL_NO_PART)
{
// Switch to the manipulator tool for dragging
self->setCurrentTool(manip);
manip->handleMouseDownOnPart(pick_info.mMousePt.mX, pick_info.mMousePt.mY, pick_info.mKeyMask);
}
else
{
// If no ring is highlighted, reset interaction or do nothing
LL_DEBUGS("FSToolCompPose") << "No manipulator ring selected" << LL_ENDL;
}
}
//-----------------------------------
// Handle MouseUp
bool FSToolCompPoseTranslate::handleMouseUp(S32 x, S32 y, MASK mask)
{
mMouseDown = false;
// The base LLToolComposite sets mCur->handleMouseUp(...)
// and does other management
return LLToolComposite::handleMouseUp(x, y, mask);
}
//-----------------------------------
// getOverrideTool
// If you want SHIFT+CTRL combos to do something else
LLTool* FSToolCompPoseTranslate::getOverrideTool(MASK mask)
{
if (mask == (MASK_CONTROL | MASK_SHIFT))
{
return LLToolCompScale::getInstance();
}
// Otherwise fallback
return LLToolComposite::getOverrideTool(mask);
}
//-----------------------------------
// Handle DoubleClick
bool FSToolCompPoseTranslate::handleDoubleClick(S32 x, S32 y, MASK mask)
{
if (!mManip->getSelection()->isEmpty() &&
mManip->getHighlightedPart() == LLManip::LL_NO_PART)
{
// Possibly show some pose properties or open the pose floater
mPoser = dynamic_cast<FSFloaterPoser*>(LLFloaterReg::showInstance("fs_poser"));
return true;
}
else
{
// If nothing selected, try a mouse down again
return handleMouseDown(x, y, mask);
}
}
//-----------------------------------
// render
void FSToolCompPoseTranslate::render()
{
// Render the current subtool
mCur->render();
// If the current subtool is not the manip, we can still
// optionally draw manip guidelines in the background
if (mCur != mManip)
{
mManip->renderGuidelines(); // or something similar if your manip has it
LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE);
}
}
+36
View File
@@ -254,6 +254,7 @@ protected:
// Subclass of LLToolComposite
#include "fsmaniprotatejoint.h" // For FSManipRotateJoint
#include "fsmaniptranslatejoint.h"
#include "fsfloaterposer.h"
class FSToolCompPose : public LLToolComposite
{
@@ -292,5 +293,40 @@ protected:
bool mMouseDown = false;
};
class FSToolCompPoseTranslate : public LLToolComposite
{
public:
// Typical pattern: pass a name like "Pose"
FSToolCompPoseTranslate();
virtual ~FSToolCompPoseTranslate();
// For some viewer patterns, we create a static singleton:
static FSToolCompPoseTranslate* getInstance();
// Overriding base events:
virtual bool handleHover(S32 x, S32 y, MASK mask) override;
virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override;
virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override;
virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override;
virtual void render() override;
void setAvatar(LLVOAvatar* avatar) { mManip->setAvatar(avatar); };
void setJoint(LLJoint* joint) { mManip->setJoint(joint); };
void setReferenceFrame(E_PoserReferenceFrame frame) { mManip->setReferenceFrame(frame); };
// Optional override if you have SHIFT/CTRL combos
virtual LLTool* getOverrideTool(MASK mask) override;
// The pick callback invoked on async pick
static void pickCallback(const LLPickInfo& pick_info);
void setPoserFloater(FSFloaterPoser* poser){ mPoser = poser; };
FSFloaterPoser* getPoserFloater(){ return mPoser; };
protected:
// Tools within this composite
FSManipTranslateJoint* mManip = nullptr;
LLToolSelectRect* mSelectRect = nullptr;
FSFloaterPoser* mPoser = nullptr;
bool mMouseDown = false;
};
#endif // LL_TOOLCOMP_H
+1
View File
@@ -125,6 +125,7 @@ void LLToolMgr::initTools()
gBasicToolset->addTool( LLToolCompInspect::getInstance() );
gFaceEditToolset->addTool( LLToolCamera::getInstance() );
gPoserToolset->addTool( FSToolCompPose::getInstance() );
gPoserToolset->addTool( FSToolCompPoseTranslate::getInstance() );
// On startup, use "select" tool
setCurrentToolset(gBasicToolset);
+1
View File
@@ -435,6 +435,7 @@ void init_audio()
gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndChatMention")));
gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndRestartOpenSim"))); // <FS:Ansariel> Preload OpenSim restart sound
gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndScriptFloaterOpen"))); // <FS:PP> Separate sound for opening script dialogs
gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndScriptFloaterClose"))); // <FS:Zi> Separate sound for closing script dialogs
// <FS:Zi> Pie menu
gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndPieMenuAppear")));
gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndPieMenuHide")));
+6 -1
View File
@@ -913,6 +913,11 @@ void handleUsernameFormatOptionChanged(const LLSD& newvalue)
// <FS:Ansariel> Global online status toggle
void handleGlobalOnlineStatusChanged(const LLSD& newvalue)
{
if (LLStartUp::getStartupState() < STATE_STARTED)
{
// Skip the checks if not inworld; Ignore startup-time setting application, if it somehow fires
return;
}
if (gSavedPerAccountSettings.getBOOL("GlobalOnlineStatusCurrentlyReverting"))
{
gSavedPerAccountSettings.setBOOL("GlobalOnlineStatusCurrentlyReverting", false);
@@ -1495,7 +1500,7 @@ void settings_setup_listeners()
setting_setup_signal_listener(gSavedPerAccountSettings, "AvatarHoverOffsetZ", handleAvatarHoverOffsetChanged);
// [RLVa:KB] - Checked: 2015-12-27 (RLVa-1.5.0)
setting_setup_signal_listener(gSavedSettings, RlvSettingNames::Main, RlvSettings::onChangedSettingMain);
setting_setup_signal_listener(gSavedSettings, static_cast<std::string>(RlvSettingNames::Main), RlvSettings::onChangedSettingMain);
// [/RLVa:KB]
// NaCl - Antispam Registry
setting_setup_signal_listener(gSavedSettings, "_NACL_AntiSpamGlobalQueue", handleNaclAntiSpamGlobalQueueChanged);
+62
View File
@@ -96,6 +96,12 @@
#include "llpresetsmanager.h"
#include "fsdata.h"
// <FS:PP> Render chat range spheres in 3D world
#include "lfsimfeaturehandler.h"
#include "llrendersphere.h"
#include "lluicolortable.h"
// </FS:PP>
#include <filesystem>
#include <iomanip>
#include <sstream>
@@ -1767,6 +1773,54 @@ void draw_axes()
gGL.popMatrix();
}
// <FS:PP> Render chat range spheres in 3D world
static void renderChatRangeSphere(const LLVector3& center, F32 radius, const LLColor4& color)
{
gGL.matrixMode(LLRender::MM_MODELVIEW);
gGL.pushMatrix();
{
gGL.translatef(center.mV[VX], center.mV[VY], center.mV[VZ]);
gDebugProgram.bind();
LLGLEnable blend(GL_BLEND);
LLGLDepthTest depth(GL_TRUE, GL_TRUE);
gGL.color4fv(color.mV);
gGL.diffuseColor4fv(color.mV);
gGL.pushMatrix();
{
gGL.scalef(radius, radius, radius);
glCullFace(GL_FRONT);
gSphere.render();
glCullFace(GL_BACK);
gSphere.render();
}
gGL.popMatrix();
gUIProgram.bind();
}
gGL.popMatrix();
}
static void drawChatRangeSpheres()
{
if (!isAgentAvatarValid())
{
return;
}
LFSimFeatureHandler& simfeatures = LFSimFeatureHandler::instance();
F32 whisper_range = (F32)simfeatures.whisperRange();
F32 say_range = (F32)simfeatures.sayRange();
F32 shout_range = (F32)simfeatures.shoutRange();
LLVector3 avatar_pos = gAgent.getPositionAgent();
static LLUIColor whisper_color = LLUIColorTable::instance().getColor("MapWhisperRingColor", LLColor4::blue);
static LLUIColor say_color = LLUIColorTable::instance().getColor("MapChatRingColor", LLColor4::yellow);
static LLUIColor shout_color = LLUIColorTable::instance().getColor("MapShoutRingColor", LLColor4::red);
renderChatRangeSphere(avatar_pos, whisper_range, whisper_color);
renderChatRangeSphere(avatar_pos, say_range, say_color);
renderChatRangeSphere(avatar_pos, shout_range, shout_color);
}
// </FS:PP>
void render_ui_3d()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
@@ -1799,6 +1853,14 @@ void render_ui_3d()
draw_axes();
}
// <FS:PP> Render chat range spheres in 3D world
static LLCachedControl<bool> show_spheres(gSavedSettings, "FSShowChatRangeSpheres", false);
if (show_spheres())
{
drawChatRangeSpheres();
}
// </FS:PP>
gViewerWindow->renderSelections(false, false, true); // Non HUD call in render_hud_elements
if (gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI))
+2 -1
View File
@@ -241,7 +241,7 @@
#include "quickprefs.h"
#include "vjfloaterlocalmesh.h" // local mesh
#include "fsfloaterwhitelisthelper.h" // fs whitelist helper
#include "omnifilter.h" // Omnifilter support
// handle secondlife:///app/openfloater/{NAME} URLs
const std::string FLOATER_PROFILE("profile");
@@ -671,6 +671,7 @@ void LLViewerFloaterReg::registerFloaters()
LLFloaterReg::add("lgg_beamshape", "floater_beamshape.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<lggBeamMapFloater>);
LLFloaterReg::add("media_lists", "floater_media_lists.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<FloaterMediaLists>);
LLFloaterReg::add("money_tracker", "floater_fs_money_tracker.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<FSMoneyTracker>);
LLFloaterReg::add("omnifilter", "floater_omnifilter.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<Omnifilter>); // <FS:Zi> Omnifilter support
LLFloaterReg::add("particle_editor","floater_particle_editor.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<ParticleEditor>);
LLFloaterReg::add("performance", "floater_fs_performance.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<FSFloaterPerformance>);
// <FS:William_W:FixPhototoolsTypo> [PhotoTools] Corrected typo in Phototools floater registration - using string literal instead of PHOTOTOOLS_FLOATER constant (likely intended).
+1
View File
@@ -1375,6 +1375,7 @@ void LLViewerMedia::getOpenIDCookieCoro(std::string url)
{"search", "webbrowser" },
{"marketplace", "webbrowser" },
{"destinations", "destination_guide_contents" },
{"avatarinfo", "profile_html" }, // <FS:PP> Load openId cookie in the profile, in feed tab
};
for (MediaCookieInstance mci : media_cookie_instances)
{
+35
View File
@@ -173,6 +173,7 @@
#include "llavatarpropertiesprocessor.h"
#include "llcheckboxctrl.h"
#include "llfloatergridstatus.h"
#include "llfloatermarketplace.h"
#include "llfloaterpreference.h"
#include "llkeyconflict.h"
#include "lllogininstance.h"
@@ -8572,6 +8573,13 @@ void handle_edit_shape()
LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_shape"));
}
// <FS:Zi> Omnifilter support
void handle_omnifilter()
{
LLFloaterReg::showInstance("omnifilter");
}
// </FS:Zi>
void handle_hover_height()
{
LLFloaterReg::showInstance("edit_hover_height");
@@ -8804,6 +8812,29 @@ class LLPromptShowURL : public view_listener_t
}
};
// <FS:TJ> For the SL Marketplace, use the already preloaded CEF instance
class FSLoadSLMarketplace : public view_listener_t
{
bool handleEvent(const LLSD& userdata)
{
static LLCachedControl<std::string> marketplace_url(gSavedSettings, "MarketplaceURL", "https://marketplace.secondlife.com/");
if (LLWeb::useExternalBrowser(marketplace_url))
{
LLWeb::loadURL(marketplace_url);
}
else
{
LLFloaterReg::showInstanceOrBringToFront("marketplace");
if (LLFloaterMarketplace* marketplace = LLFloaterReg::getTypedInstance<LLFloaterMarketplace>("marketplace"))
{
marketplace->openMarketplace();
}
}
return true;
}
};
// </FS:TJ>
bool callback_show_file(const LLSD& notification, const LLSD& response)
{
S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
@@ -12662,6 +12693,7 @@ void initialize_menus()
commit.add("NowWearing", boost::bind(&handle_now_wearing));
commit.add("EditOutfit", boost::bind(&handle_edit_outfit));
commit.add("EditShape", boost::bind(&handle_edit_shape));
commit.add("Omnifilter", boost::bind(&handle_omnifilter)); // <FS:Zi> Omnifilter support
commit.add("HoverHeight", boost::bind(&handle_hover_height));
commit.add("EditPhysics", boost::bind(&handle_edit_physics));
// <FS:TT> Client LSL Bridge
@@ -13259,6 +13291,9 @@ void initialize_menus()
commit.add("Camera.LoadView", boost::bind(&LLAgentCamera::loadCameraPosition, &gAgentCamera));
// </FS:Ansariel>
// <FS:TJ> For the SL Marketplace, use the already preloaded CEF instance
view_listener_t::addMenu(new FSLoadSLMarketplace(), "LoadSLMarketplace");
// <FS:Ansariel> Script debug floater
commit.add("ShowScriptDebug", boost::bind(&LLFloaterScriptDebug::show, LLUUID::null));
+81 -11
View File
@@ -152,6 +152,7 @@
#include "llfloaterbump.h"
#include "llfloaterreg.h"
#include "llfriendcard.h"
#include "omnifilterengine.h" // <FS:Zi> Omnifilter support
#include "permissionstracker.h" // <FS:Zi> Permissions Tracker
#include "tea.h" // <FS:AW opensim currency support>
#include "NACLantispam.h"
@@ -1842,10 +1843,10 @@ void LLOfferInfo::handleRespond(const LLSD& notification, const LLSD& response)
mRespondFunctions[name](notification, response);
}
void inventory_offer_name_callback(const LLAvatarName& av_name, std::string message)
void inventory_offer_name_callback(const LLAvatarName& av_name, const std::string& from_name, const std::string& message)
{
LLSD args;
args["MESSAGE"] = llformat(message.c_str(), av_name.getUserName().c_str());
args["MESSAGE"] = llformat(from_name.c_str(), av_name.getUserName().c_str()) + message;
LLNotificationsUtil::add("SystemMessageTip", args);
}
@@ -1994,8 +1995,7 @@ bool LLOfferInfo::inventory_offer_callback(const LLSD& notification, const LLSD&
size_t separator_idx = mFromName.find('|');
if (separator_idx != std::string::npos && LLUUID::parseUUID(mFromName.substr(0, separator_idx), &inv_sender_id) && mFromName.size() > (++separator_idx))
{
log_message = mFromName.substr(separator_idx) + log_message;
LLAvatarNameCache::instance().get(inv_sender_id, boost::bind(&inventory_offer_name_callback, _2, log_message));
LLAvatarNameCache::instance().get(inv_sender_id, boost::bind(&inventory_offer_name_callback, _2, mFromName.substr(separator_idx), log_message));
}
else
{
@@ -3254,6 +3254,75 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data)
chat.mText += mesg;
}
// <FS:Zi> Omnifilter support
static LLCachedControl<bool> use_omnifilter(gSavedSettings, "OmnifilterEnabled");
if (use_omnifilter)
{
OmnifilterEngine::Haystack haystack;
haystack.mContent = chat.mText;
haystack.mSenderName = from_name; // we don't use chat.mFromName here because that will include display names etc.
haystack.mOwnerID = chat.mFromID;
switch (chat.mChatType)
{
case CHAT_TYPE_WHISPER:
case CHAT_TYPE_NORMAL:
case CHAT_TYPE_SHOUT:
case CHAT_TYPE_DEBUG_MSG:
case CHAT_TYPE_REGION:
case CHAT_TYPE_OWNER:
case CHAT_TYPE_DIRECT:
{
switch (chat.mSourceType)
{
case CHAT_SOURCE_AGENT: // this is the type for regular chat
{
haystack.mType = OmnifilterEngine::eType::NearbyChat;
break;
}
case CHAT_SOURCE_OBJECT: // this is the type for object chat
{
haystack.mType = OmnifilterEngine::eType::ObjectChat;
break;
}
default:
{
LL_DEBUGS("Omnifilter") << "unhandled source type " << (U32)chat.mSourceType << LL_ENDL;
break;
}
}
const OmnifilterEngine::Needle* needle = OmnifilterEngine::getInstance()->match(haystack);
if (needle)
{
// we need to make sure to put the typing stopped flag on the chatting avatar (if it is an avatar)
if (chatter && chatter->isAvatar())
{
LLLocalSpeakerMgr::getInstance()->setSpeakerTyping(from_id, false);
((LLVOAvatar*)chatter)->stopTyping();
}
if (needle->mChatReplace.empty()) // no replacement defined, just suppress the message
{
return;
}
LLSD args;
args["REPLACEMENT"] = needle->mChatReplace;
chat.mText = LLTrans::getString("OmnifilterReplacement", args);
}
break;
}
default:
{
LL_DEBUGS("Omnifilter") << "unhandled chat type " << (U32)chat.mChatType << LL_ENDL;
break;
}
}
}
// </FS:Zi>
// We have a real utterance now, so can stop showing "..." and proceed.
if (chatter && chatter->isAvatar())
{
@@ -4035,13 +4104,13 @@ void process_crossed_region(LLMessageSystem* msg, void**)
if (isAgentAvatarValid())
{
gAgentAvatarp->resetRegionCrossingTimer();
// <FS:Ansariel> FIRE-12004: Attachments getting lost on TP; this is apparently the place to
// hook in for region crossings - we get an info from the simulator that we
// crossed a region and then the viewer starts the handover process. We only
// receive this message if we can actually cross the region and aren't blocked
// for some reason (e.g. banned, group access...)
gAgentAvatarp->setIsCrossingRegion(true);
}
// <FS:Ansariel> FIRE-12004: Attachments getting lost on TP; this is apparently the place to
// hook in for region crossings - we get an info from the simulator that we
// crossed a region and then the viewer starts the handover process. We only
// receive this message if we can actually cross the region and aren't blocked
// for some reason (e.g. banned, group access...)
gAgentAvatarp->setIsCrossingRegion(true);
U32 sim_ip;
msg->getIPAddrFast(_PREHASH_RegionData, _PREHASH_SimIP, sim_ip);
@@ -7209,7 +7278,7 @@ bool script_question_cb(const LLSD& notification, const LLSD& response)
// [RLVa:KB] - Checked: 2012-07-28 (RLVa-1.4.7)
if ( (allowed) && (notification["payload"].has("rlv_blocked")) )
{
RlvUtil::notifyBlocked(notification["payload"]["rlv_blocked"], LLSD().with("OBJECT", notification["payload"]["object_name"]));
RlvUtil::notifyBlocked(notification["payload"]["rlv_blocked"].asString(), LLSD().with("OBJECT", notification["payload"]["object_name"]));
}
// [/RLVa:KB]
@@ -8287,6 +8356,7 @@ void process_script_dialog(LLMessageSystem* msg, void**)
payload["object_id"] = object_id;
payload["chat_channel"] = chat_channel;
payload["object_name"] = object_name;
payload["owner_id"] = owner_id; // <FS:Zi> Omnifilter support
// <FS:Ansariel> FIRE-17158: Remove "block" button for script dialog of own objects
bool own_object = false;
+17 -1
View File
@@ -644,6 +644,18 @@ LLViewerFetchedTexture* LLViewerTextureList::createImage(const LLUUID &image_id,
imagep->setExplicitFormat(internal_format, primary_format);
}
// <FS:minerjr> [FIRE-35428] - Mega prim issue - fix compressed sculpted textures
// Sculpted textures use the RGBA data for coodinates, so any compression can cause artifacts.
if (boost_priority == LLViewerFetchedTexture::BOOST_SCULPTED)
{
// Disable the compression of BOOST_SCULPTED textures
if (imagep->getGLTexture())
{
imagep->getGLTexture()->setAllowCompression(false);
}
}
// </FS:minerjr> [FIRE-35428]
addImage(imagep, get_element_type(boost_priority));
if (boost_priority != 0)
@@ -671,7 +683,11 @@ LLViewerFetchedTexture* LLViewerTextureList::createImage(const LLUUID &image_id,
}
// <FS:Ansariel> Keep Fast Cache option
if (fast_cache_fetching_enabled)
// <FS:minerjr> [FIRE-35428] - Mega prim issue - fix compressed sculpted textures
//if(fast_cache_fetching_enabled)
// If the texture is Sculpted, don't allow it to be added to fast cache as it can affect the texture.
if(fast_cache_fetching_enabled && boost_priority != LLViewerFetchedTexture::BOOST_SCULPTED)
// </FS:minerjr> [FIRE-35428]
{
mFastCacheList.insert(imagep);
imagep->setInFastCacheList(true);
+6 -3
View File
@@ -3734,7 +3734,7 @@ void LLViewerWindow::clearPopups()
void LLViewerWindow::moveCursorToCenter()
{
bool mouse_warp = false;
LLCachedControl<S32> mouse_warp_mode(gSavedSettings, "MouseWarpMode", 1);
static LLCachedControl<S32> mouse_warp_mode(gSavedSettings, "MouseWarpMode", 1);
switch (mouse_warp_mode())
{
@@ -5109,9 +5109,12 @@ void LLViewerWindow::renderSelections( bool for_gl_pick, bool pick_parcel_walls,
}
// <FS:Beq> render the poser manipulator guides
// if we have something selected those toosl should override
if ( (!for_hud) && (selection->isEmpty()) && (LLToolMgr::getInstance()->getCurrentTool() == FSToolCompPose::getInstance()) )
if (!for_hud && selection->isEmpty())
{
FSToolCompPose::getInstance()->render();
if (LLToolMgr::getInstance()->getCurrentTool() == FSToolCompPose::getInstance())
FSToolCompPose::getInstance()->render();
if (LLToolMgr::getInstance()->getCurrentTool() == FSToolCompPoseTranslate::getInstance())
FSToolCompPoseTranslate::getInstance()->render();
}
// </FS:Beq>
+11 -2
View File
@@ -34,6 +34,7 @@
#include "llagent.h"
#include "llappviewer.h"
#include "llfloatermarketplace.h"
#include "llfloaterwebcontent.h"
#include "llfloaterreg.h"
#include "lllogininstance.h"
@@ -74,12 +75,20 @@ void LLWeb::loadURL(const std::string& url, const std::string& target, const std
}
// static
// Explicitly open a Web URL using the Web content floater
// Explicitly open a Web URL using the Web content floater or Marketplace floater
void LLWeb::loadURLInternal(const std::string &url, const std::string& target, const std::string& uuid, bool dev_mode)
{
LLFloaterWebContent::Params p;
p.url(url).target(target).id(uuid).dev_mode(dev_mode);
LLFloaterReg::showInstance("web_content", p);
if (LLFloaterMarketplace::isMarketplaceURL(url))
{
LLFloaterReg::showInstance("marketplace", p);
}
else
{
LLFloaterReg::showInstance("web_content", p);
}
}
// static
+404
View File
@@ -0,0 +1,404 @@
/**
* @file omnifilter.cpp
* @brief The Omnifilter editor
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2025, Zi Ree @ Second Life
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* $/LicenseInfo$
*/
#include "omnifilter.h"
#include "llbutton.h"
#include "llcheckboxctrl.h"
#include "llcombobox.h"
#include "lllineeditor.h"
#include "lltexteditor.h"
#include "fsscrolllistctrl.h"
static constexpr S32 NEEDLE_CHECK_COLUMN = 0;
static constexpr S32 NEEDLE_NAME_COLUMN = 1;
static constexpr S32 LOG_DATE_COLUMN = 0;
static constexpr S32 LOG_CONTENT_COLUMN = 1;
Omnifilter::Omnifilter(const LLSD& key) :
LLFloater(key)
{
}
LLScrollListItem* Omnifilter::addNeedle(const std::string& needle_name, const OmnifilterEngine::Needle& needle)
{
LLSD row;
row["columns"][NEEDLE_CHECK_COLUMN]["column"] = "enabled";
row["columns"][NEEDLE_CHECK_COLUMN]["type"] = "checkbox";
row["columns"][NEEDLE_CHECK_COLUMN]["value"] = false;
row["columns"][NEEDLE_NAME_COLUMN]["column"] = "needle_name";
row["columns"][NEEDLE_NAME_COLUMN]["type"] = "text";
row["columns"][NEEDLE_NAME_COLUMN]["value"] = needle_name;
LLScrollListItem* item = mNeedleListCtrl->addElement(row);
item->getColumn(NEEDLE_CHECK_COLUMN)->setValue(needle.mEnabled);
LLScrollListCheck* scroll_list_check = dynamic_cast<LLScrollListCheck*>(item->getColumn(NEEDLE_CHECK_COLUMN));
if (scroll_list_check)
{
LLCheckBoxCtrl* check_box = scroll_list_check->getCheckBox();
check_box->setCommitCallback(boost::bind(&Omnifilter::onNeedleCheckboxChanged, this, _1));
}
mNeedleListCtrl->refreshLineHeight();
return item;
}
OmnifilterEngine::Needle* Omnifilter::getSelectedNeedle()
{
const LLScrollListItem* needle_item = mNeedleListCtrl->getFirstSelected();
if (needle_item)
{
const LLScrollListCell* needle_name_cell = needle_item->getColumn(NEEDLE_NAME_COLUMN);
if (needle_name_cell)
{
const std::string& needle_name = needle_name_cell->getValue().asString();
if (!needle_name.empty())
{
return &OmnifilterEngine::getInstance()->getNeedleList().at(needle_name);
}
}
}
mPanelDetails->setVisible(false);
mRemoveNeedleBtn->setEnabled(false);
return nullptr;
}
void Omnifilter::onSelectNeedle()
{
const OmnifilterEngine::Needle* needle = getSelectedNeedle();
if (!needle)
{
mPanelDetails->setVisible(false);
mRemoveNeedleBtn->setEnabled(false);
return;
}
if (!mNeedleListCtrl->getItemCount())
{
mPanelDetails->setVisible(false);
mRemoveNeedleBtn->setEnabled(false);
return;
}
mPanelDetails->setVisible(true);
mRemoveNeedleBtn->setEnabled(true);
mNeedleNameCtrl->setText(mNeedleListCtrl->getSelectedItemLabel(NEEDLE_NAME_COLUMN));
mSenderNameCtrl->setText(needle->mSenderName);
mSenderCaseSensitiveCheck->setValue(!needle->mSenderNameCaseInsensitive);
mSenderMatchTypeCombo->selectByValue(needle->mSenderNameMatchType);
mContentCtrl->setText(needle->mContent);
mContentCaseSensitiveCheck->setValue(!needle->mContentCaseInsensitive);
mContentMatchTypeCombo->selectByValue(needle->mContentMatchType);
mRegionNameCtrl->setText(needle->mRegionName);
mOwnerCtrl->clear();
if (needle->mOwnerID.notNull())
{
mOwnerCtrl->setText(needle->mOwnerID.asString());
}
onOwnerChanged();
mTypeNearbyBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::NearbyChat) != needle->mTypes.end());
mTypeIMBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::InstantMessage) != needle->mTypes.end());
mTypeGroupIMBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::GroupChat) != needle->mTypes.end());
mTypeObjectChatBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::ObjectChat) != needle->mTypes.end());
mTypeObjectIMBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::ObjectInstantMessage) != needle->mTypes.end());
mTypeScriptErrorBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::ScriptError) != needle->mTypes.end());
mTypeDialogBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::ScriptDialog) != needle->mTypes.end());
mTypeOfferBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::InventoryOffer) != needle->mTypes.end());
mTypeInviteBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::GroupInvite) != needle->mTypes.end());
mTypeLureBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::Lure) != needle->mTypes.end());
mTypeLoadURLBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::URLRequest) != needle->mTypes.end());
mTypeFriendshipOfferBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::FriendshipOffer) != needle->mTypes.end());
mTypeTeleportRequestBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::TeleportRequest) != needle->mTypes.end());
mTypeGroupNoticeBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::GroupNotice) != needle->mTypes.end());
mChatReplaceCtrl->setText(needle->mChatReplace);
mButtonReplyCtrl->setText(needle->mButtonReply);
mTextBoxReplyCtrl->setText(needle->mTextBoxReply);
mContentCtrl->setFocus(true);
}
void Omnifilter::onNeedleChanged()
{
OmnifilterEngine::Needle* needle = getSelectedNeedle();
if (!needle)
{
return;
}
needle->mSenderName = mSenderNameCtrl->getValue().asString();
needle->mSenderNameCaseInsensitive = !mSenderCaseSensitiveCheck->getValue();
needle->mSenderNameMatchType = static_cast<OmnifilterEngine::eMatchType>(mSenderMatchTypeCombo->getValue().asInteger());
needle->mContent = mContentCtrl->getValue().asString();
needle->mContentCaseInsensitive = !mContentCaseSensitiveCheck->getValue();
needle->mContentMatchType = static_cast<OmnifilterEngine::eMatchType>(mContentMatchTypeCombo->getValue().asInteger());
needle->mRegionName = mRegionNameCtrl->getValue().asString();
needle->mOwnerID.set(mOwnerCtrl->getValue().asString());
needle->mTypes.clear();
if (mTypeNearbyBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::NearbyChat);
if (mTypeIMBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::InstantMessage);
if (mTypeGroupIMBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::GroupChat);
if (mTypeObjectChatBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::ObjectChat);
if (mTypeObjectIMBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::ObjectInstantMessage);
if (mTypeScriptErrorBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::ScriptError);
if (mTypeDialogBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::ScriptDialog);
if (mTypeOfferBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::InventoryOffer);
if (mTypeInviteBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::GroupInvite);
if (mTypeLureBtn->getToggleState()) needle->mTypes.insert(OmnifilterEngine::eType::Lure);
if (mTypeLoadURLBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::URLRequest);
if (mTypeFriendshipOfferBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::FriendshipOffer);
if (mTypeTeleportRequestBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::TeleportRequest);
if (mTypeGroupNoticeBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::GroupNotice);
needle->mChatReplace = mChatReplaceCtrl->getValue().asString();
needle->mButtonReply = mButtonReplyCtrl->getValue().asString();
needle->mTextBoxReply = mTextBoxReplyCtrl->getValue().asString();
OmnifilterEngine::getInstance()->setDirty(true);
}
void Omnifilter::onAddNeedleClicked()
{
std::string new_needle_string = getString("OmnifilterNewNeedle");
if (!mNeedleListCtrl->selectItemByLabel(new_needle_string, true, NEEDLE_NAME_COLUMN))
{
OmnifilterEngine::Needle& new_needle = OmnifilterEngine::getInstance()->newNeedle(new_needle_string);
addNeedle(new_needle_string, new_needle)->setSelected(true);
}
onSelectNeedle();
mNeedleNameCtrl->setFocus(true);
}
void Omnifilter::onRemoveNeedleClicked()
{
S32 index = mNeedleListCtrl->getItemIndex(mNeedleListCtrl->getFirstSelected());
OmnifilterEngine::getInstance()->deleteNeedle(mNeedleListCtrl->getSelectedItemLabel(NEEDLE_NAME_COLUMN));
mNeedleListCtrl->selectPrevItem();
mNeedleListCtrl->deleteSingleItem(index);
if (!mNeedleListCtrl->getNumSelected())
{
mNeedleListCtrl->selectFirstItem();
}
onSelectNeedle();
}
void Omnifilter::onNeedleNameChanged()
{
const std::string& old_name = mNeedleListCtrl->getSelectedItemLabel(NEEDLE_NAME_COLUMN);
const std::string& new_name = mNeedleNameCtrl->getValue().asString();
if (old_name == new_name)
{
return;
}
const LLScrollListItem* needle_item = mNeedleListCtrl->getFirstSelected();
const LLScrollListItem* name_check_item = mNeedleListCtrl->getItemByLabel(new_name, true, NEEDLE_NAME_COLUMN);
if (name_check_item && name_check_item != needle_item)
{
mNeedleNameCtrl->setValue(old_name);
return;
}
OmnifilterEngine::getInstance()->renameNeedle(old_name, new_name);
LLScrollListCell* needle_name_column = needle_item->getColumn(NEEDLE_NAME_COLUMN);
needle_name_column->setValue(new_name);
onSelectNeedle();
}
void Omnifilter::onNeedleCheckboxChanged(LLUICtrl* ctrl)
{
const LLCheckBoxCtrl* check = static_cast<LLCheckBoxCtrl*>(ctrl);
OmnifilterEngine::Needle* needle = getSelectedNeedle();
if (!needle)
{
return;
}
needle->mEnabled = check->getValue();
OmnifilterEngine::getInstance()->setDirty(true);
}
void Omnifilter::onOwnerChanged()
{
static LLColor4 default_text_color = mOwnerCtrl->getFgColor();
if (LLUUID::validate(mOwnerCtrl->getValue().asString()))
{
mOwnerCtrl->setFgColor(default_text_color);
}
else
{
mOwnerCtrl->setFgColor(LLUIColorTable::getInstance()->getColor("EmphasisColor"));
}
}
void Omnifilter::onLogLine(time_t time, const std::string& log_line)
{
LLDate date((F64)time);
LLSD substitution;
substitution["datetime"] = date;
std::string time_str = "[hour,datetime,slt]:[min,datetime,slt]:[second,datetime,slt]";
LLStringUtil::format(time_str, substitution);
LLSD row;
row["columns"][LOG_DATE_COLUMN]["column"] = "timestamp";
row["columns"][LOG_DATE_COLUMN]["type"] = "text";
row["columns"][LOG_DATE_COLUMN]["value"] = time_str;
row["columns"][LOG_CONTENT_COLUMN]["column"] = "log_entry";
row["columns"][LOG_CONTENT_COLUMN]["type"] = "text";
row["columns"][LOG_CONTENT_COLUMN]["value"] = log_line;
bool scroll_to_end;
scroll_to_end = mFilterLogCtrl->getScrollbar()->isAtEnd();
LLScrollListItem* item = mFilterLogCtrl->addElement(row);
if (scroll_to_end)
{
mFilterLogCtrl->setScrollPos(INT32_MAX);
}
time_str = "[year, datetime, slt]-[mthnum, datetime, slt]-[day, datetime, slt] [hour,datetime,slt]:[min,datetime,slt]:[second,datetime,slt] SLT";
LLStringUtil::format(time_str, substitution);
item->getColumn(LOG_DATE_COLUMN)->setToolTip(time_str);
}
bool Omnifilter::postBuild()
{
mNeedleListCtrl = getChild<FSScrollListCtrl>("needle_list");
mAddNeedleBtn = getChild<LLButton>("add_needle");
mRemoveNeedleBtn = getChild<LLButton>("remove_needle");
mFilterLogCtrl = getChild<FSScrollListCtrl>("filter_log");
mPanelDetails = getChild<LLPanel>("panel_details");
mNeedleNameCtrl = getChild<LLLineEditor>("needle_name");
mSenderNameCtrl = getChild<LLLineEditor>("sender_name");
mSenderCaseSensitiveCheck = getChild<LLCheckBoxCtrl>("sender_case");
mSenderMatchTypeCombo = getChild<LLComboBox>("sender_match_type");
mContentCtrl = getChild<LLTextEditor>("content");
mContentCaseSensitiveCheck = getChild<LLCheckBoxCtrl>("content_case");
mContentMatchTypeCombo = getChild<LLComboBox>("content_match_type");
mRegionNameCtrl = getChild<LLLineEditor>("region_name");
mOwnerCtrl = getChild<LLLineEditor>("owner_uuid");
mTypeNearbyBtn = getChild<LLButton>("type_nearby");
mTypeIMBtn = getChild<LLButton>("type_im");
mTypeGroupIMBtn = getChild<LLButton>("type_group_im");
mTypeObjectChatBtn = getChild<LLButton>("type_object_chat");
mTypeObjectIMBtn = getChild<LLButton>("type_object_im");
mTypeScriptErrorBtn = getChild<LLButton>("type_script_error");
mTypeDialogBtn = getChild<LLButton>("type_dialog");
mTypeOfferBtn = getChild<LLButton>("type_inventory_offer");
mTypeInviteBtn = getChild<LLButton>("type_invite");
mTypeLureBtn = getChild<LLButton>("type_lure");
mTypeLoadURLBtn = getChild<LLButton>("type_load_url");
mTypeFriendshipOfferBtn = getChild<LLButton>("type_friendship");
mTypeTeleportRequestBtn = getChild<LLButton>("type_tp_request");
mTypeGroupNoticeBtn = getChild<LLButton>("type_group_notice");
mChatReplaceCtrl = getChild<LLLineEditor>("chat_replace");
mButtonReplyCtrl = getChild<LLLineEditor>("button_reply");
mTextBoxReplyCtrl = getChild<LLTextEditor>("text_box_reply");
mNeedleListCtrl->setSearchColumn(NEEDLE_NAME_COLUMN);
mNeedleListCtrl->deleteAllItems();
mNeedleListCtrl->setCommitOnSelectionChange(true);
mFilterLogCtrl->deleteAllItems();
auto& instance = OmnifilterEngine::instance();
for (const auto& [needle_name, needle] : instance.getNeedleList())
{
addNeedle(needle_name, needle);
}
for (const auto& [log_time, log_message] : instance.mLog)
{
onLogLine(log_time, log_message);
}
instance.mLogSignal.connect(boost::bind(&Omnifilter::onLogLine, this, _1, _2));
if (mNeedleListCtrl->getItemCount())
{
mNeedleListCtrl->selectFirstItem();
}
mContentCtrl->setCommitOnFocusLost(true);
mTextBoxReplyCtrl->setCommitOnFocusLost(true);
mNeedleListCtrl->setCommitCallback(boost::bind(&Omnifilter::onSelectNeedle, this));
mAddNeedleBtn->setCommitCallback(boost::bind(&Omnifilter::onAddNeedleClicked, this));
mRemoveNeedleBtn->setCommitCallback(boost::bind(&Omnifilter::onRemoveNeedleClicked, this));
mNeedleNameCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleNameChanged, this));
mSenderNameCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mSenderCaseSensitiveCheck->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mSenderMatchTypeCombo->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mContentCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mContentCaseSensitiveCheck->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mContentMatchTypeCombo->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mRegionNameCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mChatReplaceCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mButtonReplyCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mTextBoxReplyCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mOwnerCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mOwnerCtrl->setKeystrokeCallback(boost::bind(&Omnifilter::onOwnerChanged, this), nullptr);
mTypeNearbyBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mTypeIMBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mTypeGroupIMBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mTypeObjectChatBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mTypeObjectIMBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mTypeScriptErrorBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mTypeDialogBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mTypeOfferBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mTypeInviteBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mTypeLureBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mTypeLoadURLBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mTypeFriendshipOfferBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mTypeTeleportRequestBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
mTypeGroupNoticeBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
onSelectNeedle();
return true;
}
+99
View File
@@ -0,0 +1,99 @@
/**
* @file omnifilter.h
* @brief The Omnifilter editor
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2025, Zi Ree @ Second Life
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* $/LicenseInfo$
*/
#ifndef OMNIFILTER_H
#define OMNIFILTER_H
#include "omnifilterengine.h"
#include "llfloater.h"
class FSScrollListCtrl;
class LLButton;
class LLCheckBoxCtrl;
class LLComboBox;
class LLLineEditor;
class LLPanel;
class LLTextEditor;
class Omnifilter
: public LLFloater
{
friend class LLFloaterReg;
private:
Omnifilter(const LLSD& key);
public:
bool postBuild() override final;
LLScrollListItem* addNeedle(const std::string& name, const OmnifilterEngine::Needle& needle);
protected:
OmnifilterEngine::Needle* getSelectedNeedle();
void onSelectNeedle();
void onNeedleChanged();
void onAddNeedleClicked();
void onRemoveNeedleClicked();
void onNeedleNameChanged();
void onNeedleCheckboxChanged(LLUICtrl* ctrl);
void onOwnerChanged();
void onLogLine(time_t time, const std::string& logLine);
FSScrollListCtrl* mNeedleListCtrl{ nullptr };
LLButton* mAddNeedleBtn{ nullptr };
LLButton* mRemoveNeedleBtn{ nullptr };
FSScrollListCtrl* mFilterLogCtrl{ nullptr };
LLPanel* mPanelDetails{ nullptr };
LLLineEditor* mNeedleNameCtrl{ nullptr };
LLLineEditor* mSenderNameCtrl{ nullptr };
LLCheckBoxCtrl* mSenderCaseSensitiveCheck{ nullptr };
LLComboBox* mSenderMatchTypeCombo{ nullptr };
LLTextEditor* mContentCtrl{ nullptr };
LLCheckBoxCtrl* mContentCaseSensitiveCheck{ nullptr };
LLComboBox* mContentMatchTypeCombo{ nullptr };
LLLineEditor* mRegionNameCtrl{ nullptr };
LLLineEditor* mOwnerCtrl{ nullptr };
LLButton* mTypeNearbyBtn{ nullptr };
LLButton* mTypeIMBtn{ nullptr };
LLButton* mTypeGroupIMBtn{ nullptr };
LLButton* mTypeObjectChatBtn{ nullptr };
LLButton* mTypeObjectIMBtn{ nullptr };
LLButton* mTypeScriptErrorBtn{ nullptr };
LLButton* mTypeDialogBtn{ nullptr };
LLButton* mTypeOfferBtn{ nullptr };
LLButton* mTypeInviteBtn{ nullptr };
LLButton* mTypeLureBtn{ nullptr };
LLButton* mTypeLoadURLBtn{ nullptr };
LLButton* mTypeFriendshipOfferBtn{ nullptr };
LLButton* mTypeTeleportRequestBtn{ nullptr };
LLButton* mTypeGroupNoticeBtn{ nullptr };
LLLineEditor* mChatReplaceCtrl{ nullptr };
LLLineEditor* mButtonReplyCtrl{ nullptr };
LLTextEditor* mTextBoxReplyCtrl{ nullptr };
};
#endif // OMNIFILTER_H
+402
View File
@@ -0,0 +1,402 @@
/**
* @file omnifilterengine.cpp
* @brief The core Omnifilter engine
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2025, Zi Ree @ Second Life
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "omnifilterengine.h"
#include "llnotificationsutil.h"
#include "llsdserialize.h"
#include "llviewercontrol.h"
#include <fstream>
#include <filesystem>
OmnifilterEngine::OmnifilterEngine()
: LLSingleton<OmnifilterEngine>()
, LLEventTimer(5.0f)
, mDirty(false)
{
mEventTimer.stop();
}
OmnifilterEngine::~OmnifilterEngine()
{
// delete Xxx;
}
void OmnifilterEngine::init()
{
mNeedlesXMLPath = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "omnifilter.xml");
if (mNeedlesXMLPath.empty())
{
LL_DEBUGS("Omnifilter") << "Got empty file name for omnifilter XML storage." << LL_ENDL;
}
loadNeedles();
}
const OmnifilterEngine::Needle* OmnifilterEngine::logMatch(const std::string& needle_name, const Needle& needle)
{
time_t now = (time_t)LLDate::now().secondsSinceEpoch();
mLog.emplace_back(now, needle_name);
mLogSignal(now, needle_name);
return &needle;
}
bool OmnifilterEngine::matchStrings(std::string_view needle_string, std::string_view haystack_string, eMatchType match_type, bool case_insensitive)
{
static LLCachedControl<bool> use_omnifilter(gSavedSettings, "OmnifilterEnabled");
if (!use_omnifilter)
{
return false;
}
std::string_view needle_content = needle_string;
std::string_view haystack_content = haystack_string;
std::string needle_lc_content;
std::string haystack_lc_content;
if (case_insensitive)
{
needle_lc_content = needle_string;
haystack_lc_content = haystack_string;
LLStringUtil::toLower(needle_lc_content);
LLStringUtil::toLower(haystack_lc_content);
needle_content = needle_lc_content;
haystack_content = haystack_lc_content;
}
switch (match_type)
{
case eMatchType::Regex:
{
boost::regbase::flag_type re_flags = boost::regex::normal;
if (case_insensitive)
{
re_flags |= boost::regex::icase;
}
boost::regex re(std::string(needle_string), re_flags);
if (boost::regex_match(std::string(haystack_string), re))
{
return true;
}
break;
}
case eMatchType::Substring:
{
if (haystack_content.find(needle_content) != haystack_content.npos)
{
return true;
}
break;
}
case eMatchType::Exact:
{
if (haystack_content == needle_content)
{
return true;
}
break;
}
default:
{
LL_DEBUGS("Omnifilter") << "match type " << match_type << " unknown!" << LL_ENDL;
break;
}
}
return false;
}
const OmnifilterEngine::Needle* OmnifilterEngine::match(const Haystack& haystack)
{
for (const auto& [needle_name, needle]: mNeedles)
{
if (!needle.mEnabled)
{
continue;
}
if (!needle.mSenderName.empty())
{
if (!matchStrings(needle.mSenderName, haystack.mSenderName, needle.mSenderNameMatchType, needle.mSenderNameCaseInsensitive))
{
continue;
}
}
if (needle.mOwnerID.notNull())
{
if (needle.mOwnerID != haystack.mOwnerID)
{
continue;
}
}
if (!needle.mRegionName.empty())
{
if (needle.mRegionName != haystack.mRegionName)
{
continue;
}
}
if (!needle.mTypes.empty() && !needle.mTypes.contains(haystack.mType))
{
continue;
}
if (needle.mContent.empty() || matchStrings(needle.mContent, haystack.mContent, needle.mContentMatchType, needle.mContentCaseInsensitive))
{
return logMatch(needle_name, needle);
}
}
return nullptr;
}
OmnifilterEngine::Needle& OmnifilterEngine::newNeedle(const std::string& needle_name)
{
if (!mNeedles.contains(needle_name))
{
Needle new_needle;
new_needle.mEnabled = false;
new_needle.mSenderNameCaseInsensitive = true;
new_needle.mSenderNameMatchType = eMatchType::Substring;
new_needle.mContentCaseInsensitive = true;
new_needle.mContentMatchType = eMatchType::Substring;
mNeedles[needle_name] = new_needle;
}
setDirty(true);
return mNeedles[needle_name];
}
void OmnifilterEngine::renameNeedle(const std::string& old_name, const std::string& new_name)
{
// https://stackoverflow.com/a/44883472
auto node_handler = mNeedles.extract(old_name);
node_handler.key() = new_name;
mNeedles.insert(std::move(node_handler));
setDirty(true);
}
void OmnifilterEngine::deleteNeedle(const std::string& needle_name)
{
mNeedles.erase(needle_name);
setDirty(true);
}
OmnifilterEngine::needle_list_t& OmnifilterEngine::getNeedleList()
{
return mNeedles;
}
void OmnifilterEngine::setDirty(bool dirty)
{
mDirty = dirty;
if (mDirty)
{
mEventTimer.start();
}
else
{
mEventTimer.stop();
}
}
void OmnifilterEngine::loadNeedles()
{
if (mNeedlesXMLPath.empty())
{
return;
}
if (!std::filesystem::exists(mNeedlesXMLPath))
{
// file does not exist (yet), just return empty
return;
}
if (!std::filesystem::is_regular_file(mNeedlesXMLPath))
{
LL_DEBUGS("Omnifilter") << "Omnifilter storage at '" << mNeedlesXMLPath << "' is not a regular file." << LL_ENDL;
LLSD args;
args["FILE_NAME"] = mNeedlesXMLPath;
LLNotificationsUtil::add("NotRegularFileError", args, LLSD());
return;
}
if (std::filesystem::file_size(mNeedlesXMLPath) == 0)
{
// file exists but is empty, this should not happen, so alert the user, they might have lost their needles
LL_DEBUGS("Omnifilter") << "Omnifilter storage file is empty." << mNeedlesXMLPath << LL_ENDL;
LLSD args;
args["ERROR_CODE"] = errno;
args["ERROR_MESSAGE"] = strerror(errno);
args["FILE_NAME"] = mNeedlesXMLPath;
LLNotificationsUtil::add("GenericFileEmptyError", args, LLSD());
return;
}
LL_DEBUGS("Omnifilter") << "Loading needles from: " << mNeedlesXMLPath << LL_ENDL;
llifstream file(mNeedlesXMLPath.c_str());
if (file.fail())
{
LL_DEBUGS("Omnifilter") << "Unable to open Omnifilter storage at '" << mNeedlesXMLPath << "' for reading." << LL_ENDL;
LLSD args;
args["ERROR_CODE"] = errno;
args["ERROR_MESSAGE"] = strerror(errno);
args["FILE_NAME"] = mNeedlesXMLPath;
LLNotificationsUtil::add("GenericFileOpenReadError", args, LLSD());
return;
}
LLSD needles_llsd;
if (file.is_open())
{
LLSDSerialize::fromXML(needles_llsd, file);
file.close();
}
if (file.fail())
{
LL_DEBUGS("Omnifilter") << "Unable to read Omnifilter needles from '" << mNeedlesXMLPath << "'." << LL_ENDL;
LLSD args;
args["ERROR_CODE"] = errno;
args["ERROR_MESSAGE"] = strerror(errno);
args["FILE_NAME"] = mNeedlesXMLPath;
LLNotificationsUtil::add("GenericFileReadError", args, LLSD());
return;
}
for (const auto& [new_needle_name, needle_data] : llsd::inMap(needles_llsd))
{
Needle new_needle;
new_needle.mSenderName = needle_data["sender_name"].asString();
new_needle.mContent = needle_data["content"].asString();
new_needle.mRegionName = needle_data["region_name"].asString();
new_needle.mChatReplace = needle_data["chat_replace"].asString();
new_needle.mButtonReply = needle_data["button_reply"].asString();
new_needle.mTextBoxReply = needle_data["textbox_reply"].asString();
new_needle.mSenderNameMatchType = static_cast<OmnifilterEngine::eMatchType>(needle_data["sender_name_match_type"].asInteger());
new_needle.mContentMatchType = static_cast<OmnifilterEngine::eMatchType>(needle_data["content_match_type"].asInteger());
LLSD types_llsd = needle_data["types"];
for (const auto& needle_type : llsd::inArray(types_llsd))
{
new_needle.mTypes.insert(static_cast<OmnifilterEngine::eType>(needle_type.asInteger()));
}
new_needle.mEnabled = needle_data["enabled"].asBoolean();
new_needle.mSenderNameCaseInsensitive = needle_data["sender_name_case_insensitive"].asBoolean();
new_needle.mContentCaseInsensitive = needle_data["content_case_insensitive"].asBoolean();
const std::string owner_id_str = needle_data["owner_id"].asString();
if (!owner_id_str.empty())
{
new_needle.mOwnerID.set(owner_id_str);
}
mNeedles[new_needle_name] = new_needle;
}
}
void OmnifilterEngine::saveNeedles()
{
if (mNeedlesXMLPath.empty())
{
return;
}
LL_DEBUGS("Omnifilter") << "Saving needles to: " << mNeedlesXMLPath << LL_ENDL;
llofstream file(mNeedlesXMLPath.c_str());
if (file.fail())
{
LL_DEBUGS("Omnifilter") << "Unable to open Omnifilter storage at '" << mNeedlesXMLPath << "' for writing." << LL_ENDL;
LLSD args;
args["ERROR_CODE"] = errno;
args["ERROR_MESSAGE"] = strerror(errno);
args["FILE_NAME"] = mNeedlesXMLPath;
LLNotificationsUtil::add("GenericFileOpenWriteError", args, LLSD());
return;
}
LLSD needles_llsd;
for (const auto& [needle_name, needle] : mNeedles)
{
needles_llsd[needle_name]["sender_name"] = needle.mSenderName;
needles_llsd[needle_name]["content"] = needle.mContent;
needles_llsd[needle_name]["region_name"] = needle.mRegionName;
needles_llsd[needle_name]["chat_replace"] = needle.mChatReplace;
needles_llsd[needle_name]["button_reply"] = needle.mButtonReply;
needles_llsd[needle_name]["textbox_reply"] = needle.mTextBoxReply;
needles_llsd[needle_name]["sender_name_match_type"] = needle.mSenderNameMatchType;
needles_llsd[needle_name]["content_match_type"] = needle.mContentMatchType;
needles_llsd[needle_name]["owner_id"] = needle.mOwnerID;
LLSD types_llsd;
for (auto type : needle.mTypes)
{
types_llsd.append(static_cast<S32>(type));
}
needles_llsd[needle_name]["types"] = types_llsd;
needles_llsd[needle_name]["enabled"] = needle.mEnabled;
needles_llsd[needle_name]["sender_name_case_insensitive"] = needle.mSenderNameCaseInsensitive;
needles_llsd[needle_name]["content_case_insensitive"] = needle.mContentCaseInsensitive;
}
LLSDSerialize::toXML(needles_llsd, file);
file.close();
if (file.fail())
{
LL_DEBUGS("Omnifilter") << "Unable to save Omnifilter needles at '" << mNeedlesXMLPath << "'." << LL_ENDL;
LLSD args;
args["ERROR_CODE"] = errno;
args["ERROR_MESSAGE"] = strerror(errno);
args["FILE_NAME"] = mNeedlesXMLPath;
LLNotificationsUtil::add("GenericFileWriteError", args, LLSD());
}
setDirty(false);
}
// virtual
bool OmnifilterEngine::tick()
{
saveNeedles();
setDirty(false);
return false;
}
+138
View File
@@ -0,0 +1,138 @@
/**
* @file omnifilterengine.h
* @brief The core Omnifilter engine
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2025, Zi Ree @ Second Life
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* $/LicenseInfo$
*/
#ifndef OMNIFILTERENGINE_H
#define OMNIFILTERENGINE_H
#include "lleventtimer.h"
#include "llsingleton.h"
#include <boost/bind.hpp>
#include <boost/signals2.hpp>
class OmnifilterEngine
: public LLSingleton<OmnifilterEngine>
, public LLEventTimer
{
LLSINGLETON(OmnifilterEngine);
~OmnifilterEngine();
public:
enum eType
{
NearbyChat,
GroupChat,
GroupNotice,
GroupInvite,
InstantMessage,
ObjectChat,
ObjectInstantMessage,
ScriptError,
ScriptDialog,
FriendshipOffer,
InventoryOffer,
Lure,
TeleportRequest,
URLRequest,
TYPES_MAX
};
enum eMatchType
{
Exact,
Substring,
Regex,
MATCH_TYPES_MAX
};
class Haystack
{
public:
std::string mSenderName;
std::string mContent;
std::string mRegionName;
LLUUID mOwnerID;
eType mType;
};
class Needle
{
public:
std::string mSenderName;
std::string mContent;
std::string mRegionName;
std::string mChatReplace;
std::string mButtonReply;
std::string mTextBoxReply;
eMatchType mSenderNameMatchType;
eMatchType mContentMatchType;
LLUUID mOwnerID;
std::set<eType> mTypes;
bool mEnabled;
bool mSenderNameCaseInsensitive = false;
bool mContentCaseInsensitive = false;
};
typedef std::map<std::string, OmnifilterEngine::OmnifilterEngine::Needle, std::less<>> needle_list_t;
needle_list_t& getNeedleList();
Needle& newNeedle(const std::string& needle_name);
void renameNeedle(const std::string& old_name, const std::string& new_name);
void deleteNeedle(const std::string& needle_name);
const Needle* match(const Haystack& haystack);
void setDirty(bool dirty);
void init();
typedef boost::signals2::signal<void(time_t, const std::string&)> log_signal_t;
log_signal_t mLogSignal;
std::vector<std::pair<time_t, std::string>> mLog;
protected:
const Needle* logMatch(const std::string& needle_name, const Needle& needle);
bool matchStrings(std::string_view needle_string, std::string_view haystack_string, eMatchType match_type, bool case_insensitive);
void loadNeedles();
void saveNeedles();
bool tick() override;
protected:
needle_list_t mNeedles;
std::string mNeedlesXMLPath;
bool mDirty;
};
#endif // OMNIFILTERENGINE_H
+1 -1
View File
@@ -162,7 +162,7 @@ BEGIN
VALUE "FileDescription", "Firestorm"
VALUE "FileVersion", "${VIEWER_VERSION_MAJOR}.${VIEWER_VERSION_MINOR}.${VIEWER_VERSION_PATCH}.${VIEWER_VERSION_REVISION}"
VALUE "InternalName", "Firestorm"
VALUE "LegalCopyright", "Copyright \251 2010-2025, The Phoenix Firestorm Project, Inc."
VALUE "LegalCopyright", "Copyright \251 2010-2026, The Phoenix Firestorm Project, Inc."
VALUE "OriginalFilename", "Firestorm.exe"
VALUE "ProductName", "Firestorm"
VALUE "ProductVersion", "${VIEWER_VERSION_MAJOR}.${VIEWER_VERSION_MINOR}.${VIEWER_VERSION_PATCH}.${VIEWER_VERSION_REVISION}"
+1 -1
View File
@@ -725,7 +725,7 @@ bool RlvActions::isRlvEnabled()
}
#ifdef CATZNIP_STRINGVIEW
void RlvActions::notifyBlocked(const boost::string_view& strNotifcation, const LLSD& sdArgs)
void RlvActions::notifyBlocked(const std::string_view& strNotifcation, const LLSD& sdArgs)
#else
void RlvActions::notifyBlocked(const std::string& strNotifcation, const LLSD& sdArgs)
#endif // CATZNIP_STRINGVIEW
+1 -1
View File
@@ -369,7 +369,7 @@ public:
* Shows one of the blocked toast notifications (see rlva_strings.xml)
*/
#ifdef CATZNIP_STRINGVIEW
static void notifyBlocked(const boost::string_view& strNotifcation, const LLSD& sdArgs = LLSD());
static void notifyBlocked(const std::string_view& strNotifcation, const LLSD& sdArgs = LLSD());
#else
static void notifyBlocked(const std::string& strNotifcation, const LLSD& sdArgs = LLSD());
#endif // CATZNIP_STRINGVIEW
+4 -4
View File
@@ -363,7 +363,7 @@ std::string RlvStrings::getAnonym(const std::string& strName)
// Checked: 2011-11-08 (RLVa-1.5.0)
#ifdef CATZNIP_STRINGVIEW
const std::string& RlvStrings::getString(const boost::string_view& strStringName)
const std::string& RlvStrings::getString(const std::string_view& strStringName)
#else
const std::string& RlvStrings::getString(const std::string& strStringName)
#endif // CATZNIP_STRINGVIEW
@@ -550,7 +550,7 @@ void RlvUtil::filterScriptQuestions(S32& nQuestions, LLSD& sdPayload)
if ( (!gRlvAttachmentLocks.canAttach()) && (SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_ATTACH].permbit & nQuestions) )
{
// Notify the user that we blocked it since they're not allowed to wear any new attachments
sdPayload["rlv_blocked"] = RlvStringKeys::Blocked::PermissionAttach;
sdPayload["rlv_blocked"] = static_cast<std::string>(RlvStringKeys::Blocked::PermissionAttach);
nQuestions &= ~SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_ATTACH].permbit;
}
@@ -558,7 +558,7 @@ void RlvUtil::filterScriptQuestions(S32& nQuestions, LLSD& sdPayload)
if ( (gRlvHandler.hasBehaviour(RLV_BHVR_TPLOC)) && (SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_TELEPORT].permbit & nQuestions) )
{
// Notify the user that we blocked it since they're not allowed to teleport
sdPayload["rlv_blocked"] = RlvStringKeys::Blocked::PermissionTeleport;
sdPayload["rlv_blocked"] = static_cast<std::string>(RlvStringKeys::Blocked::PermissionTeleport);
nQuestions &= ~SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_TELEPORT].permbit;
}
@@ -602,7 +602,7 @@ bool RlvUtil::isNearbyRegion(const std::string& strRegion)
// Checked: 2011-04-11 (RLVa-1.3.0h) | Modified: RLVa-1.3.0h
#ifdef CATZNIP_STRINGVIEW
void RlvUtil::notifyBlocked(const boost::string_view& strNotifcation, const LLSD& sdArgs, bool fLogToChat)
void RlvUtil::notifyBlocked(const std::string_view& strNotifcation, const LLSD& sdArgs, bool fLogToChat)
#else
void RlvUtil::notifyBlocked(const std::string& strNotifcation, const LLSD& sdArgs, bool fLogToChat)
#endif // CATZNIP_STRINGVIEW
+5 -5
View File
@@ -65,7 +65,7 @@ class RlvGCTimer;
//
#ifdef CATZNIP_STRINGVIEW
template<typename T> inline T rlvGetSetting(const boost::string_view& strSetting, const T& defaultValue)
template<typename T> inline T rlvGetSetting(const std::string_view& strSetting, const T& defaultValue)
#else
template<typename T> inline T rlvGetSetting(const std::string& strSetting, const T& defaultValue)
#endif // CATZNIP_STRINGVIEW
@@ -75,7 +75,7 @@ template<typename T> inline T rlvGetSetting(const std::string& strSetting, const
}
#ifdef CATZNIP_STRINGVIEW
template<typename T> inline T rlvGetPerUserSetting(const boost::string_view& strSetting, const T& defaultValue)
template<typename T> inline T rlvGetPerUserSetting(const std::string_view& strSetting, const T& defaultValue)
#else
template<typename T> inline T rlvGetPerUserSetting(const std::string& strSetting, const T& defaultValue)
#endif // CATZNIP_STRINGVIEW
@@ -155,7 +155,7 @@ public:
static std::string getAnonym(const LLAvatarName& avName); // @shownames
static std::string getAnonym(const std::string& strName); // @shownames
#ifdef CATZNIP_STRINGVIEW
static const std::string& getString(const boost::string_view& strStringName);
static const std::string& getString(const std::string_view& strStringName);
#else
static const std::string& getString(const std::string& strStringName);
#endif // CATZNIP_STRINGVIEW
@@ -194,8 +194,8 @@ public:
static void forceTp(const LLVector3d& posDest); // Ignores restrictions that might otherwise prevent tp'ing
#ifdef CATZNIP_STRINGVIEW
static void notifyBlocked(const std::string& strNotifcation, const LLSD& sdArgs = LLSD(), bool fLogToChat = false) { notifyBlocked(boost::string_view(strNotifcation), sdArgs, fLogToChat); }
static void notifyBlocked(const boost::string_view& strNotifcation, const LLSD& sdArgs = LLSD(), bool fLogToChat = false);
//static void notifyBlocked(const std::string& strNotifcation, const LLSD& sdArgs = LLSD(), bool fLogToChat = false) { notifyBlocked(std::string_view(strNotifcation), sdArgs, fLogToChat); }
static void notifyBlocked(const std::string_view& strNotifcation, const LLSD& sdArgs = LLSD(), bool fLogToChat = false);
#else
static void notifyBlocked(const std::string& strNotifcation, const LLSD& sdArgs = LLSD(), bool fLogToChat = false);
#endif // CATZNIP_STRINGVIEW
+61 -52
View File
@@ -16,9 +16,13 @@
#pragma once
#ifdef CATZNIP_STRINGVIEW
#include "llstringview.h"
#endif // CATZNIP_STRINGVIE
#ifndef CATZNIP_STRINGVIEW
#define CATZNIP_STRINGVIEW
#endif
//#ifdef CATZNIP_STRINGVIEW
//#include "llstringview.h"
//#endif // CATZNIP_STRINGVIE
// ============================================================================
// Defines
@@ -236,8 +240,8 @@ enum ERlvBehaviour {
RLV_BHVR_SETCAM_TEXTURES, // Replaces all textures with the specified texture (or the default unrezzed one)
RLV_BHVR_SETCAM_UNLOCK, // Forces the camera focus to the user's avatar
// Camera (behaviours - deprecated)
RLV_BHVR_CAMZOOMMIN, // Enforces a minimum - vertical - FOV angle of 60° / multiplier
RLV_BHVR_CAMZOOMMAX, // Enforces a maximum - vertical - FOV angle of 60° / multiplier
RLV_BHVR_CAMZOOMMIN, // Enforces a minimum - vertical - FOV angle of 60° / multiplier
RLV_BHVR_CAMZOOMMAX, // Enforces a maximum - vertical - FOV angle of 60° / multiplier
// Camera (reply)
RLV_BHVR_GETCAM_AVDIST, // Returns the current minimum distance between the camera and the user's avatar
RLV_BHVR_GETCAM_AVDISTMIN, // Returns the active (if any) minimum distance between the camera and the user's avatar
@@ -390,33 +394,38 @@ enum ERlvAttachGroupType
// Settings
//
#ifdef CATZNIP_STRINGVIEW
using namespace std::literals;
#define make_string_view(s) s##sv
#endif
namespace RlvSettingNames
{
#ifdef CATZNIP_STRINGVIEW
/*inline*/ constexpr boost::string_view Main = make_string_view("RestrainedLove");
/*inline*/ constexpr boost::string_view Debug = make_string_view("RestrainedLoveDebug");
/*inline*/ constexpr boost::string_view CanOoc = make_string_view("RestrainedLoveCanOOC");
/*inline*/ constexpr boost::string_view ForbidGiveToRlv = make_string_view("RestrainedLoveForbidGiveToRLV");
/*inline*/ constexpr boost::string_view NoSetEnv = make_string_view("RestrainedLoveNoSetEnv");
/*inline*/ constexpr boost::string_view ShowEllipsis = make_string_view("RestrainedLoveShowEllipsis");
/*inline*/ constexpr boost::string_view WearAddPrefix = make_string_view("RestrainedLoveStackWhenFolderBeginsWith");
/*inline*/ constexpr boost::string_view WearReplacePrefix = make_string_view("RestrainedLoveReplaceWhenFolderBeginsWith");
/*inline*/ constexpr std::string_view Main = make_string_view("RestrainedLove");
/*inline*/ constexpr std::string_view Debug = make_string_view("RestrainedLoveDebug");
/*inline*/ constexpr std::string_view CanOoc = make_string_view("RestrainedLoveCanOOC");
/*inline*/ constexpr std::string_view ForbidGiveToRlv = make_string_view("RestrainedLoveForbidGiveToRLV");
/*inline*/ constexpr std::string_view NoSetEnv = make_string_view("RestrainedLoveNoSetEnv");
/*inline*/ constexpr std::string_view ShowEllipsis = make_string_view("RestrainedLoveShowEllipsis");
/*inline*/ constexpr std::string_view WearAddPrefix = make_string_view("RestrainedLoveStackWhenFolderBeginsWith");
/*inline*/ constexpr std::string_view WearReplacePrefix = make_string_view("RestrainedLoveReplaceWhenFolderBeginsWith");
/*inline*/ constexpr boost::string_view DebugHideUnsetDup = make_string_view("RLVaDebugHideUnsetDuplicate");
/*inline*/ constexpr boost::string_view EnableIMQuery = make_string_view("RLVaEnableIMQuery");
/*inline*/ constexpr boost::string_view EnableLegacyNaming = make_string_view("RLVaEnableLegacyNaming");
/*inline*/ constexpr boost::string_view EnableSharedWear = make_string_view("RLVaEnableSharedWear");
/*inline*/ constexpr boost::string_view EnableTempAttach = make_string_view("RLVaEnableTemporaryAttachments");
/*inline*/ constexpr boost::string_view HideLockedLayer = make_string_view("RLVaHideLockedLayers");
/*inline*/ constexpr boost::string_view HideLockedAttach = make_string_view("RLVaHideLockedAttachments");
/*inline*/ constexpr boost::string_view HideLockedInventory = make_string_view("RLVaHideLockedInventory");
/*inline*/ constexpr boost::string_view LoginLastLocation = make_string_view("RLVaLoginLastLocation");
/*inline*/ constexpr boost::string_view SharedInvAutoRename = make_string_view("RLVaSharedInvAutoRename");
/*inline*/ constexpr boost::string_view ShowAssertionFail = make_string_view("RLVaShowAssertionFailures");
/*inline*/ constexpr boost::string_view ShowRedirectChatTyping = make_string_view("RLVaShowRedirectChatTyping");
/*inline*/ constexpr boost::string_view SplitRedirectChat = make_string_view("RLVaSplitRedirectChat");
/*inline*/ constexpr boost::string_view TopLevelMenu = make_string_view("RLVaTopLevelMenu");
/*inline*/ constexpr boost::string_view WearReplaceUnlocked = make_string_view("RLVaWearReplaceUnlocked");
/*inline*/ constexpr std::string_view DebugHideUnsetDup = make_string_view("RLVaDebugHideUnsetDuplicate");
/*inline*/ constexpr std::string_view EnableIMQuery = make_string_view("RLVaEnableIMQuery");
/*inline*/ constexpr std::string_view EnableLegacyNaming = make_string_view("RLVaEnableLegacyNaming");
/*inline*/ constexpr std::string_view EnableSharedWear = make_string_view("RLVaEnableSharedWear");
/*inline*/ constexpr std::string_view EnableTempAttach = make_string_view("RLVaEnableTemporaryAttachments");
/*inline*/ constexpr std::string_view HideLockedLayer = make_string_view("RLVaHideLockedLayers");
/*inline*/ constexpr std::string_view HideLockedAttach = make_string_view("RLVaHideLockedAttachments");
/*inline*/ constexpr std::string_view HideLockedInventory = make_string_view("RLVaHideLockedInventory");
/*inline*/ constexpr std::string_view LoginLastLocation = make_string_view("RLVaLoginLastLocation");
/*inline*/ constexpr std::string_view SharedInvAutoRename = make_string_view("RLVaSharedInvAutoRename");
/*inline*/ constexpr std::string_view ShowAssertionFail = make_string_view("RLVaShowAssertionFailures");
/*inline*/ constexpr std::string_view ShowRedirectChatTyping = make_string_view("RLVaShowRedirectChatTyping");
/*inline*/ constexpr std::string_view SplitRedirectChat = make_string_view("RLVaSplitRedirectChat");
/*inline*/ constexpr std::string_view TopLevelMenu = make_string_view("RLVaTopLevelMenu");
/*inline*/ constexpr std::string_view WearReplaceUnlocked = make_string_view("RLVaWearReplaceUnlocked");
#else
constexpr const char Main[] = "RestrainedLove";
constexpr const char Debug[] = "RestrainedLoveDebug";
@@ -454,24 +463,24 @@ namespace RlvStringKeys
namespace Blocked
{
#ifdef CATZNIP_STRINGVIEW
/*inline*/ constexpr boost::string_view AutoPilot = make_string_view("blocked_autopilot");
/*inline*/ constexpr boost::string_view Generic = make_string_view("blocked_generic");
/*inline*/ constexpr boost::string_view GroupChange = make_string_view("blocked_groupchange");
/*inline*/ constexpr boost::string_view InvFolder = make_string_view("blocked_invfolder");
/*inline*/ constexpr boost::string_view PermissionAttach = make_string_view("blocked_permattach");
/*inline*/ constexpr boost::string_view PermissionTeleport = make_string_view("blocked_permteleport");
/*inline*/ constexpr boost::string_view RecvIm = make_string_view("blocked_recvim");
/*inline*/ constexpr boost::string_view RecvImRemote = make_string_view("blocked_recvim_remote");
/*inline*/ constexpr boost::string_view SendIm = make_string_view("blocked_sendim");
/*inline*/ constexpr boost::string_view Share = make_string_view("blocked_share");
/*inline*/ constexpr boost::string_view ShareGeneric = make_string_view("blocked_share_generic");
/*inline*/ constexpr boost::string_view StartConference = make_string_view("blocked_startconf");
/*inline*/ constexpr boost::string_view StartIm = make_string_view("blocked_startim");
/*inline*/ constexpr boost::string_view Teleport = make_string_view("blocked_teleport");
/*inline*/ constexpr boost::string_view TeleportOffer = make_string_view("blocked_teleport_offer");
/*inline*/ constexpr boost::string_view TpLureRequestRemote = make_string_view("blocked_tplurerequest_remote");
/*inline*/ constexpr boost::string_view ViewXxx = make_string_view("blocked_viewxxx");
/*inline*/ constexpr boost::string_view Wireframe = make_string_view("blocked_wireframe");
/*inline*/ constexpr std::string_view AutoPilot = make_string_view("blocked_autopilot");
/*inline*/ constexpr std::string_view Generic = make_string_view("blocked_generic");
/*inline*/ constexpr std::string_view GroupChange = make_string_view("blocked_groupchange");
/*inline*/ constexpr std::string_view InvFolder = make_string_view("blocked_invfolder");
/*inline*/ constexpr std::string_view PermissionAttach = make_string_view("blocked_permattach");
/*inline*/ constexpr std::string_view PermissionTeleport = make_string_view("blocked_permteleport");
/*inline*/ constexpr std::string_view RecvIm = make_string_view("blocked_recvim");
/*inline*/ constexpr std::string_view RecvImRemote = make_string_view("blocked_recvim_remote");
/*inline*/ constexpr std::string_view SendIm = make_string_view("blocked_sendim");
/*inline*/ constexpr std::string_view Share = make_string_view("blocked_share");
/*inline*/ constexpr std::string_view ShareGeneric = make_string_view("blocked_share_generic");
/*inline*/ constexpr std::string_view StartConference = make_string_view("blocked_startconf");
/*inline*/ constexpr std::string_view StartIm = make_string_view("blocked_startim");
/*inline*/ constexpr std::string_view Teleport = make_string_view("blocked_teleport");
/*inline*/ constexpr std::string_view TeleportOffer = make_string_view("blocked_teleport_offer");
/*inline*/ constexpr std::string_view TpLureRequestRemote = make_string_view("blocked_tplurerequest_remote");
/*inline*/ constexpr std::string_view ViewXxx = make_string_view("blocked_viewxxx");
/*inline*/ constexpr std::string_view Wireframe = make_string_view("blocked_wireframe");
#else
constexpr const char AutoPilot[] = "blocked_autopilot";
constexpr const char Generic[] = "blocked_generic";
@@ -497,9 +506,9 @@ namespace RlvStringKeys
namespace Hidden
{
#ifdef CATZNIP_STRINGVIEW
/*inline*/ constexpr boost::string_view Generic = make_string_view("hidden_generic");
/*inline*/ constexpr boost::string_view Parcel = make_string_view("hidden_parcel");
/*inline*/ constexpr boost::string_view Region = make_string_view("hidden_region");
/*inline*/ constexpr std::string_view Generic = make_string_view("hidden_generic");
/*inline*/ constexpr std::string_view Parcel = make_string_view("hidden_parcel");
/*inline*/ constexpr std::string_view Region = make_string_view("hidden_region");
#else
constexpr const char Generic[] = "hidden_generic";
constexpr const char Parcel[] = "hidden_parcel";
@@ -510,9 +519,9 @@ namespace RlvStringKeys
namespace StopIm
{
#ifdef CATZNIP_STRINGVIEW
/*inline*/ constexpr boost::string_view NoSession = make_string_view("stopim_nosession");
/*inline*/ constexpr boost::string_view EndSessionRemote = make_string_view("stopim_endsession_remote");
/*inline*/ constexpr boost::string_view EndSessionLocal = make_string_view("stopim_endsession_local");
/*inline*/ constexpr std::string_view NoSession = make_string_view("stopim_nosession");
/*inline*/ constexpr std::string_view EndSessionRemote = make_string_view("stopim_endsession_remote");
/*inline*/ constexpr std::string_view EndSessionLocal = make_string_view("stopim_endsession_local");
#else
constexpr const char NoSession[] = "stopim_nosession";
constexpr const char EndSessionRemote[] = "stopim_endsession_remote";
+1 -1
View File
@@ -297,7 +297,7 @@ void RlvUIEnabler::onUpdateLoginLastLocation(bool fQuitting)
// ============================================================================
#ifdef CATZNIP_STRINGVIEW
bool RlvUIEnabler::addGenericFloaterFilter(const std::string& strFloaterName, const boost::string_view& strRlvNotification)
bool RlvUIEnabler::addGenericFloaterFilter(const std::string& strFloaterName, const std::string_view& strRlvNotification)
#else
bool RlvUIEnabler::addGenericFloaterFilter(const std::string& strFloaterName, const std::string& strRlvNotification)
#endif // CATZNIP_STRINGVIEW
+1 -1
View File
@@ -58,7 +58,7 @@ protected:
*/
public:
#ifdef CATZNIP_STRINGVIEW
bool addGenericFloaterFilter(const std::string& strFloaterName, const boost::string_view& strRlvNotification);
bool addGenericFloaterFilter(const std::string& strFloaterName, const std::string_view& strRlvNotification);
#else
bool addGenericFloaterFilter(const std::string& strFloaterName, const std::string& strRlvNotification);
#endif // CATZNIP_STRINGVIEW
@@ -7,7 +7,7 @@
複製到庫存
</floater.string>
<floater.string name="DateTime">
[weekday,datetime,slt], [day,datetime,slt]. [month,datetime,slt] [year,datetime,slt], [hour,datetime,slt]:[min,datetime,slt]:[second,datetime,slt] [timezone,datetime,slt]
[year,datetime,slt], [month,datetime,slt]. [day,datetime,slt] [weekday,datetime,slt] [hour,datetime,slt]:[min,datetime,slt]:[second,datetime,slt] [timezone,datetime,slt]
</floater.string>
<text name="desc txt">
描述:
@@ -165,6 +165,7 @@ with the same filename but different name
<texture name="Command_MiniMap_Icon" file_name="toolbar_icons/mini_map.png" preload="true" />
<texture name="Command_Move_Icon" file_name="toolbar_icons/move.png" preload="true" />
<texture name="Command_Environments_Icon" file_name="toolbar_icons/environments.png" preload="true" />
<texture name="Command_Omnifilter_Icon" file_name="toolbar_icons/omnifilter.png" preload="true" /> <!-- FS:Zi: Omnifilter -->
<texture name="Command_People_Icon" file_name="toolbar_icons/people.png" preload="true" />
<texture name="Command_Performance_Icon" file_name="toolbar_icons/performance.png" preload="true" />
<texture name="Command_Picks_Icon" file_name="toolbar_icons/picks.png" preload="true" />
@@ -886,6 +887,7 @@ with the same filename but different name
<texture name="Wearable_Favorites_Icon" file_name="toolbar_icons/wearable_favorites.png" preload="true" />
<texture name="beacons" file_name="toolbar_icons/beacons.png" preload="true" />
<texture name="Stop_Animations_Icon" file_name="toolbar_icons/stop_animations.png" preload="true" />
<texture name="omnifilter" file_name="toolbar_icons/omnifilter.png" preload="true" />
<texture name="skin ansastorm blood" file_name="skinspreview/ansa_blood.jpg" preload="true" />
<texture name="skin ansastorm bright blue" file_name="skinspreview/ansa_blue.jpg" preload="true" />
Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

@@ -2385,7 +2385,7 @@ Element(lər) daşınılsın?
</notification>
<notification name="PayConfirmation">
[TARGET] üçün L$[AMOUNT] ödəmək istədiyinizi təsdiq edin.
<usetemplate ignoretext="Ödənişdən əvvəl təsdiqləmə (200 L$-dan yuxarı məbləğ)" name="okcancelignore" notext="Ləğv et" yestext="Ödə"/>
<usetemplate name="okcancelbuttons" notext="Ləğv et" yestext="Ödə"/>
</notification>
<notification name="PayObjectFailed">
Ödəniş xətası: obyekt tapılmadı.
@@ -255,6 +255,13 @@ Susmaya görə dəyərə qayıtmaq üçün UUID yanındakı 'D' düyməsini bas
<button name="Def_UISndSnapshot" tool_tip="Susmaya görə olan UUID-yə sıfırla."/>
<check_box label="Səsin söndürülməsi" name="QuietSnapshotsToDiskCheckBox"/>
<text tool_tip="Gəzinti zamanı addımların səsləndirilən UUID səsi." name="textFSFootsteps">
Susmaya görə addımlar:
</text>
<button name="Prev_UISndFootsteps" tool_tip="Bu səsi oxut."/>
<button name="Def_UISndFootsteps" tool_tip="Susmaya görə olan UUID-yə sıfırla."/>
<check_box label="Səsi yandır" name="PlayModeUISndFootsteps"/>
</panel>
<panel label="İnteerfeys 2" name="UI Sounds tab 2">
<text name="textFSExplanation_tab2">
@@ -451,13 +458,6 @@ Susmaya görə dəyərə qayıtmaq üçün UUID yanındakı 'D' düyməsini bas
<button name="Def_UISndMovelockToggle" tool_tip="Susmaya görə olan UUID-yə sıfırla."/>
<check_box label="Səsi yandır" name="PlayModeUISndMovelockToggle"/>
<text tool_tip="Gəzinti zamanı addımların səsləndirilən UUID səsi." name="textFSFootsteps">
Susmaya görə addımlar:
</text>
<button name="Prev_UISndFootsteps" tool_tip="Bu səsi oxut."/>
<button name="Def_UISndFootsteps" tool_tip="Susmaya görə olan UUID-yə sıfırla."/>
<check_box label="Səsi yandır" name="PlayModeUISndFootsteps"/>
<text tool_tip="İzləyici mayak yaxın olduğu zaman səsləndirilən UUID səsi. Avatar təyinat yerinə yaxın olduqda artır." name="textFSTrackerBeacon">
İzləyici mayak:
</text>
@@ -5,6 +5,8 @@
Anzeigen:
</text>
<check_box label="Bounding Boxes für Lichtquellen" name="lights_bounding_boxes"/>
<check_box label="Regionsecken" name="fsregioncornerbeacons"/>
<check_box label="Chat-Reichweite" name="FSShowChatRangeSpheres"/>
<check_box label="Ortungssignale" name="beacons"/>
<check_box label="Hervorhebung" name="highlights"/>
<check_box label="Signale-Infotext im Viewerfenster" name="FSRenderBeaconText"/>
@@ -3,6 +3,8 @@
<string name="no_friends" value="keine Freunde"/>
<string name="no_filtered_groups_msg" value="Keine Gruppe „[SEARCH_TERM]“ gefunden."/>
<string name="no_groups_msg" value="Du suchst nach Gruppen zum Beitreten? Versuche die [secondlife:///app/search/groups Suche]."/>
<string name="favorite_label">Anheften</string>
<string name="unfavorite_label">Abheften</string>
<tab_container name="friends_and_groups">
<panel label="Freunde" name="friends_panel"/>
<panel label="Gruppen" name="groups_panel"/>
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<floater name="omnifilter" title="Omni-Filter Regel-Editor">
<floater.string name="OmnifilterNewNeedle">
NEUE REGEL
</floater.string>
<layout_stack name="needle_list_stack">
<layout_panel name="needle_list_layout">
<check_box name="enable_omnifilter" label="Omnifilter aktivieren"/>
<fs_scroll_list name="needle_list">
<fs_scroll_list.columns name="enabled" label="Aktiv"/>
<fs_scroll_list.columns name="needle_name" label="Regel"/>
</fs_scroll_list>
<button name="add_needle" label="Hinzufügen"/>
<button name="remove_needle" label="Entfernen"/>
</layout_panel>
<layout_panel name="filter_log_layout">
<fs_scroll_list name="filter_log">
<fs_scroll_list.columns name="timestamp" label="Zeit (SLT)"/>
<fs_scroll_list.columns name="log_entry" label="Regel"/>
</fs_scroll_list>
</layout_panel>
</layout_stack>
<panel name="panel_details">
<text name="needle_name_label" value="Regel-Name:"/>
<text name="sender_name_label" value="Sender-Name:"/>
<check_box name="sender_case" label="Aa"/>
<combo_box name="sender_match_type">
<combo_item name="sender_exact">
<column width="40" name="label" label="|Abc|"/>
<column name="longtext" label="Übereinstimmung ganzer Text"/>
</combo_item>
<combo_item name="sender_substring">
<column name="label" label="*Abc*"/>
<column name="longtext" label="Teilweise Übereinstimmung"/>
</combo_item>
<combo_item name="sender_regex">
<column name="label" label="/ *. /"/>
<column name="longtext" label="Regulärer Ausdruck"/>
</combo_item>
</combo_box>
<text name="content_label" value="Inhalt:"/>
<check_box name="content_case" label="Aa"/>
<combo_box name="content_match_type">
<combo_item name="content_exact">
<column name="label" label="|Abc|"/>
<column name="longtext" label="Übereinstimmung ganzer Text"/>
</combo_item>
<combo_item name="content_substring">
<column name="label" label="*Abc*"/>
<column name="longtext" label="Teilweise Übereinstimmung"/>
</combo_item>
<combo_item name="content_regex">
<column name="label" label="/ *. /"/>
<column name="longtext" label="Regulärer Ausdruck"/>
</combo_item>
</combo_box>
<text name="region_name_label" value="Name Region:" tool_tip="Der Regionsname der Quelle. Nur verfügbar für Inventar-Angebote."/>
<text name="owner_label" value="Eigentümer-UUID:" tool_tip="Für Gruppen-Einladungen: Die UUID der Gruppe. Für Objekt-Chat, Eigentümer-Nachrichten, Debug-Chat oder Direkt-Chat: Die UUID des Objekts. Für Objekt-IMs: Die UUID des Objekt-Eigentümers. Für alles andere: Die UUID des sendenden Einwohners."/>
<text name="match_source_label" value="Quelle:"/>
<layout_stack name="buttons_stack">
<layout_panel name="buttons_group_1">
<button name="type_nearby" label="Lok. Chat" tool_tip="Regulärer Chat von Avataren in der Nähe."/>
<button name="type_im" label="IM" tool_tip="Instant Messages von Einwohnern."/>
<button name="type_group_im" label="Gruppen" tool_tip="Gruppen-Nachrichten von Einwohnern."/>
</layout_panel>
<layout_panel name="buttons_group_2">
<button name="type_group_notice" label="Mitteilungen" tool_tip="Gruppen-Mitteilungen."/>
<button name="type_invite" label="Einladungen" tool_tip="Gruppen-Einladungen."/>
<button name="type_object_chat" label="Objekt-Chat" tool_tip="Chat in der Nähe von Objekten."/>
</layout_panel>
<layout_panel name="buttons_group_3">
<button name="type_object_im" label="Objekt-IM" tool_tip="Instant Messages von Objekten."/>
<button name="type_script_error" layout="topleft" label="Fehler" tool_tip="Debug-Chat von Skripten."/>
<button name="type_dialog" label="Dialoge" tool_tip="Übereinstimmung von Feldern in Skript-Dialogen."/>
</layout_panel>
<layout_panel name="buttons_group_4">
<button name="type_inventory_offer" label="Inv.-Angeb." tool_tip="Übereinstimmung von Feldern in Inventar-Angeboten."/>
<button name="type_friendship" label="Freundsch." tool_tip="Freundschatsanfragen von Einwohnern."/>
<button name="type_lure" label="TP-Angebot" tool_tip="Teleport-Angebote von Einwohnern."/>
</layout_panel>
<layout_panel name="buttons_group_5">
<button name="type_tp_request" label="TP-Anfrage" tool_tip="Anfragen von Einwohnern, ein Teleport-Angebot gesendet zu bekommen."/>
<button name="type_load_url" label="URL öffnen" tool_tip="Aufforderung von Skripten, eine Webseite zu besuchen."/>
</layout_panel>
</layout_stack>
<text name="chat_replace_label" value="Chat-Ersetzung:"/>
<text name="button_reply_label" value="Button-Antwort:"/>
<text name="text_box_reply_label" value="Textbox-Antwort:"/>
</panel>
</floater>
@@ -5,5 +5,7 @@
<menu_item_call label="Chat" name="Chat"/>
<menu_item_call label="Voice-Anruf" name="Call"/>
<menu_item_call label="Aktivieren" name="Activate"/>
<menu_item_call label="Gruppe anheften" name="Favorite"/>
<menu_item_call label="Gruppe abheften" name="Unfavorite"/>
<menu_item_call label="Verlassen" name="Leave"/>
</toggleable_menu>
@@ -87,11 +87,8 @@
<menu_item_call label="Gruppen" name="My Groups"/>
<menu_item_check label="Chat in der Nähe" name="Nearby Chat"/>
<menu_item_check label="Leute" name="People"/>
<!--
<menu_item_call label="Teleport-Liste" name="Teleport History"/>
<menu_item_check label="Orte" name="Places"/>
-->
<menu_item_check label="Gespräche" name="Conversations"/>
<menu_item_call label="Omni-Filter" name="Omnifilter"/>
<menu label="Online-Status" name="Status">
<menu_item_check label="Abwesend" name="Away"/>
<menu_item_check label="Nicht stören" name="Do Not Disturb"/>
@@ -5945,4 +5945,51 @@ https://wiki.firestormviewer.org/antivirus_whitelisting
<notification name="PrimfeedUserStatusFailed">
Primfeed-Benutzeranmeldung erfolgreich, allerdings ist die Statusprüfung fehlgeschlagen. Bitte prüfen Sie, ob Primfeed korrekt funktioniert.
</notification>
<notification name="NotRegularFileError">
Es wurde eine reguläre Datei erwartet:
[FILE_NAME]
</notification>
<notification name="NotFolderError">
Es wurde ein reguläres Verzeichnis erwartet:
[FILE_NAME]
</notification>
<notification name="GenericFileEmptyError">
Datei existiert, aber ist leer:
[FILE_NAME]
Fehlermeldung:
[ERROR_MESSAGE] ([ERROR_CODE])
</notification>
<notification name="GenericFileOpenReadError">
Datei konnte nicht zum Lesen geöffnet werden:
[FILE_NAME]
Fehlermeldung:
[ERROR_MESSAGE] ([ERROR_CODE])
</notification>
<notification name="GenericFileOpenWriteError">
Datei konnte nicht zum Schreiben geöffnet werden:
[FILE_NAME]
Fehlermeldung:
[ERROR_MESSAGE] ([ERROR_CODE])
</notification>
<notification name="GenericFileReadError">
Datei konnte nicht gelesen werden:
[FILE_NAME]
Fehlermeldung:
[ERROR_MESSAGE] ([ERROR_CODE])
</notification>
<notification name="GenericFileWriteError">
Datei konnte nicht geschrieben werden:
[FILE_NAME]
Fehlermeldung:
[ERROR_MESSAGE] ([ERROR_CODE])
</notification>
<notification name="ChatHistoryIsMissing">
Chat-Verlauf-Datei ist nicht vorhanden. Entweder existiert kein Verlauf für diesen Chat oder Verläufe werden nicht gespeichert.
Sie können das Speichern von Verläufen einschalten unter Einstellungen &gt; Privatsphäre &gt; Protokoll und Verläufe
</notification>
</notifications>
@@ -25,6 +25,11 @@
label="Aktivieren"
name="activate_btn"
width="85" />
<button
label="Anheften"
name="favorite_btn"
tool_tip="Gruppe zu Favoriten hinzufügen oder von Favoriten entfernen"
width="85" />
<button
label="Verlassen"
name="leave_btn"
@@ -43,6 +43,9 @@
<row name="restore_global_row_controls">
<column name="restore_global_files_label">Tastenbelegung</column>
</row>
<row name="restore_global_row_omnifilter">
<column name="restore_global_files_label">Omni-Filter</column>
</row>
</scroll_list>
</layout_panel>
<layout_panel name="lp_restore_per_account_files">
@@ -24,13 +24,12 @@
Uhrzeitformat:
</text>
<combo_box name="time_format_combobox">
<combo_box.item label="1:00 AM" name="12H"/>
<combo_box.item label="1:00 PM" name="12H"/>
<combo_box.item label="13:00" name="24H"/>
</combo_box>
<text name="time_format_textbox2">
(Erfordert Neustart)
</text>
<check_box label="IME-Texteingabe aktivieren" name="use_ime" tool_tip="Input Method Editor für Texteingabe aktivieren - nur für spezielle Sprachen wie z.B. Japanisch oder Chinesisch benötigt."/>
<text name="maturity_desired_prompt" width="320">
Auf Inhalte mit folgender Einstufung zugreifen:
</text>
@@ -40,6 +39,7 @@
<combo_box.item label="Generell und Moderat" name="Desired_Mature"/>
<combo_box.item label="Generell" name="Desired_PG"/>
</combo_box>
<check_box label="IME-Texteingabe aktivieren" name="use_ime" tool_tip="Input Method Editor für Texteingabe aktivieren - nur für spezielle Sprachen wie z.B. Japanisch oder Chinesisch benötigt."/>
<text name="start_location_textbox">
Startstandort:
</text>
@@ -306,6 +306,13 @@ Der jeweilige Standardwert wird im Hovertip des UUID-Feldes angezeigt.
<button label="V" name="Prev_UISndSnapshot" tool_tip="Vorschau zu diesem Klang."/>
<button label="S" name="Def_UISndSnapshot" tool_tip="Auf Standardwert zurücksetzen."/>
<check_box label="Stummschalten" name="QuietSnapshotsToDiskCheckBox"/>
<text tool_tip="UUID des Klangs für Schritte. Erfordert Neustart." name="textFSFootsteps">
Schritte:
</text>
<button label="V" name="Prev_UISndFootsteps" tool_tip="Vorschau zu diesem Klang."/>
<button label="S" name="Def_UISndFootsteps" tool_tip="Auf Standardwert zurücksetzen."/>
<check_box label="Abspielen" name="PlayModeUISndFootsteps"/>
</panel>
<!-- UI Sounds 2 -->
@@ -414,12 +421,19 @@ Der jeweilige Standardwert wird im Hovertip des UUID-Feldes angezeigt.
<check_box label="Abspielen" name="PlayModeUISndWindowOpen"/>
<text tool_tip="UUID des Klangs beim Öffnen eines Skript-Dialogs." name="textFSScriptFloaterOpen">
Skript-Dialog:
Öffnen Skript-Dialog:
</text>
<button label="V" name="Prev_UISndScriptFloaterOpen" tool_tip="Vorschau zu diesem Klang."/>
<button label="S" name="Def_UISndScriptFloaterOpen" tool_tip="Auf Standardwert zurücksetzen."/>
<check_box label="Abspielen" name="PlayModeUISndScriptFloaterOpen"/>
<text tool_tip="UUID des Klangs beim Schließen eines Skript-Dialogs." name="textFSScriptFloaterClose">
Schließen Skript-Dialog:
</text>
<button label="V" name="Prev_UISndScriptFloaterClose" tool_tip="Vorschau zu diesem Klang."/>
<button label="S" name="Def_UISndScriptFloaterClose" tool_tip="Auf Standardwert zurücksetzen."/>
<check_box label="Abspielen" name="PlayModeUISndScriptFloaterClose"/>
<text tool_tip="UUID des Klangs beim Einloggen eines Freundes." name="textFSFriendOnline">
Einloggen Freund:
</text>
@@ -505,13 +519,6 @@ Der jeweilige Standardwert wird im Hovertip des UUID-Feldes angezeigt.
<button label="S" name="Def_UISndMovelockToggle" tool_tip="Auf Standardwert zurücksetzen."/>
<check_box label="Abspielen" name="PlayModeUISndMovelockToggle"/>
<text tool_tip="UUID des Klangs für Schritte. Erfordert Neustart." name="textFSFootsteps">
Schritte:
</text>
<button label="V" name="Prev_UISndFootsteps" tool_tip="Vorschau zu diesem Klang."/>
<button label="S" name="Def_UISndFootsteps" tool_tip="Auf Standardwert zurücksetzen."/>
<check_box label="Abspielen" name="PlayModeUISndFootsteps"/>
<text tool_tip="UUID des Klangs für für den Leitstrahl, falls aktiv. Je näher das Ziel ist, desto häufiger ertönt der Klang." name="textFSTrackerBeacon">
Leitstrahl:
</text>
@@ -6114,6 +6114,9 @@ Setzen Sie den Editorpfad in Anführungszeichen
<string name="Command_Primfeed_Label">
Primfeed
</string>
<string name="Command_Omnifilter_Label">
Omni-Filter
</string>
<string name="Command_Appearance_Tooltip">
Avatar ändern
</string>
@@ -6318,6 +6321,9 @@ Setzen Sie den Editorpfad in Anführungszeichen
<string name="Command_Primfeed_Tooltip">
Auf Primfeed posten
</string>
<string name="Command_Omnifilter_Tooltip">
Regel für Omni-Filter bearbeiten
</string>
<string name="Toolbar_Bottom_Tooltip">
gegenwärtig in der unteren Symbolleiste
</string>
@@ -7324,4 +7330,7 @@ Ihre aktuelle Position: [AVATAR_POS]
<string name="Maximum">
Maximum
</string>
<string name="OmnifilterReplacement">
Omni-Filter-Ersetzung: [REPLACEMENT]
</string>
</strings>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<floater
legacy_header_height="18"
height="363"
height="381"
layout="topleft"
name="beacons"
help_topic="beacons"
@@ -12,7 +12,7 @@
width="240">
<panel
follows="left|top|right|bottom"
height="358"
height="376"
layout="topleft"
left="10"
name="beacons_panel"
@@ -49,6 +49,13 @@
<check_box.commit_callback
function="Beacons.UICheck" />
</check_box>
<check_box
control_name="FSShowChatRangeSpheres"
top_pad="2"
height="16"
layout="topleft"
label="Chat range spheres"
name="FSShowChatRangeSpheres" />
<view_border
bevel_style="in"
height="0"
@@ -24,6 +24,12 @@
<string
name="no_groups_msg"
value="Looking for Groups to join? Try [secondlife:///app/search/groups Search]." />
<string
name="favorite_label"
value="Pin" />
<string
name="unfavorite_label"
value="Unpin" />
<tab_container
follows="all"
height="390"
@@ -163,7 +163,7 @@
name="external_controls"
top_delta="0"
auto_resize="true"
width="585">
width="770">
<text
name="plugin_fail_text"
follows="all"
@@ -189,7 +189,9 @@ for possible steps to resolve this issue.
follows="all"
layout="topleft"
left="0"
initial_mime_type="text/html"
name="webbrowser"
width="770"
top="0"/>
</layout_panel>
<layout_panel name="status_bar"
@@ -206,7 +208,7 @@ for possible steps to resolve this issue.
parse_urls="false"
text_color="0.4 0.4 0.4 1"
top_pad="3"
width="495"/>
width="680"/>
<progress_bar
color_bar="0.3 1.0 0.3 1"
follows="bottom|right"
@@ -0,0 +1,589 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<floater
legacy_header_height="18"
can_resize="true"
can_minimize="true"
can_close="true"
height="400"
min_height="348"
min_width="668"
layout="topleft"
name="omnifilter"
help_topic="omnifilter"
save_rect="true"
save_visibility="true"
single_instance="true"
reuse_instance="true"
title="Omnifilter Rules Editor"
width="700">
<floater.string name="OmnifilterNewNeedle">
NEW RULE
</floater.string>
<layout_stack
name="needle_list_stack"
layout="topleft"
follows="left|top|bottom"
height="374"
top="20"
left="8"
width="190"
orientation="vertical"
show_drag_handle="true">
<layout_panel
name="needle_list_layout"
layout="topleft"
height="150"
min_height="94"
user_resize="true">
<check_box
name="enable_omnifilter"
layout="topleft"
follows="left|top"
height="20"
top="0"
right="-1"
label="Enable Omnifilter"
control_name="OmnifilterEnabled"/>
<fs_scroll_list
name="needle_list"
layout="topleft"
follows="all"
height="100"
top_pad="4"
draw_heading="true"
draw_stripes="true"
desired_line_height="18">
<fs_scroll_list.columns
name="enabled"
label="On"
width="24"/>
<fs_scroll_list.columns
name="needle_name"
label="Rule"
width="175"/>
<!-- example entries for UI editing -->
<row>
<column
name="enabled"
column="enabled"
type="checkbox"
value="true"/>
<column
name="needle_name"
column="needle_name">
Example Rule 1
</column>
</row>
<row>
<column
name="enabled"
column="enabled"
type="checkbox"/>
<column
name="needle_name"
column="needle_name">
Example Rule 2
</column>
</row>
</fs_scroll_list>
<button
name="add_needle"
layout="topleft"
follows="left|bottom"
height="20"
top_pad="4"
width="96"
label="Add"/>
<button
name="remove_needle"
layout="topleft"
follows="right|bottom"
height="20"
left_pad="0"
width="96"
label="Remove"/>
</layout_panel>
<layout_panel
name="filter_log_layout"
layout="topleft"
height="130"
min_height="50">
<fs_scroll_list
name="filter_log"
layout="topleft"
follows="all"
height="127"
top="0"
draw_heading="true"
draw_stripes="true"
can_sort="false">
<fs_scroll_list.columns
name="timestamp"
label="Time (SLT)"
width="64"/>
<fs_scroll_list.columns
name="log_entry"
label="Rule"/>
<!-- example entries for UI editing -->
<row>
<column
name="timestamp"
column="timestamp"
tool_tip="2015-11-24 00:01:02">
00:01:02
</column>
<column
name="log_entry"
column="log_entry">
Example Rule 1
</column>
</row>
<row>
<column
name="timestamp"
column="timestamp"
tool_tip="2015-11-24 01:02:03">
01:02:03
</column>
<column
name="log_entry"
column="log_entry">
Example Rule 2
</column>
</row>
</fs_scroll_list>
</layout_panel>
</layout_stack>
<panel
name="panel_details"
layout="topleft"
follows="all"
height="374"
left_pad="4"
right="-4">
<text
name="needle_name_label"
layout="topleft"
follows="left|top"
height="20"
width="100"
left="0"
top_pad="0"
valign="center"
value="Rule Name:"/>
<line_editor
name="needle_name"
layout="topleft"
follows="left|right|top"
height="20"
left_pad="4"
right="-4"/>
<text
name="sender_name_label"
layout="topleft"
follows="left|top"
height="20"
width="100"
left="0"
top_pad="2"
valign="center"
value="Sender Name:"/>
<line_editor
name="sender_name"
layout="topleft"
follows="left|right|top"
height="20"
left_pad="4"
right="-110"/>
<check_box
name="sender_case"
layout="topleft"
follows="right|top"
height="20"
left_pad="4"
width="30"
label="Aa"/>
<combo_box
name="sender_match_type"
layout="topleft"
follows="right|top"
height="20"
left_pad="8"
right="-4">
<combo_item
name="sender_exact"
value="0">
<column
width="40"
name="label"
label="|Abc|"/>
<column
name="longtext"
label="Match full text"/>
</combo_item>
<combo_item
name="sender_substring"
value="1">
<column
name="label"
label="*Abc*"/>
<column
name="longtext"
label="Match substring"/>
</combo_item>
<combo_item
name="sender_regex"
value="2">
<column
name="label"
label="/ *. /"/>
<column
name="longtext"
label="Match regular expression"
width.width="400"
width.pixel_width="300"/>
</combo_item>
</combo_box>
<text
name="content_label"
layout="topleft"
follows="left|top"
height="20"
left="0"
width="100"
top_pad="3"
valign="center"
value="Content:"/>
<text_editor
name="content"
layout="topleft"
follows="all"
height="74"
left_pad="4"
right="-4"
top_delta="0"/>
<check_box
name="content_case"
layout="topleft"
follows="right|bottom"
height="20"
width="30"
right="-76"
top_pad="4"
label="Aa"/>
<combo_box
name="content_match_type"
layout="topleft"
follows="right|bottom"
height="20"
left_pad="8"
right="-4">
<combo_item
name="content_exact"
value="0">
<column
width="40"
name="label"
label="|Abc|"/>
<column
name="longtext"
label="Match full text"/>
</combo_item>
<combo_item
name="content_substring"
value="1">
<column
name="label"
label="*Abc*"/>
<column
name="longtext"
label="Match substring"/>
</combo_item>
<combo_item
name="content_regex"
value="2">
<column
name="label"
label="/ *. /"/>
<column
name="longtext"
label="Match regular expression"/>
</combo_item>
</combo_box>
<text
name="region_name_label"
layout="topleft"
follows="left|bottom"
height="20"
width="100"
left="0"
top_pad="2"
valign="center"
value="Region Name:"/>
<line_editor
name="region_name"
layout="topleft"
follows="left|right|bottom"
height="20"
left_pad="4"
right="-4"
tool_tip="The region name of the source. Only available for inventory offers."/>
<text
name="owner_label"
layout="topleft"
follows="left|bottom"
height="20"
width="100"
left="0"
tool_tip="For group invites: The group's UUID. For object chat, owner say, debug chat or direct chat: The UUID of the object. For object IMs: The UUID of the object owner. For everything else, the UUID of the sender avatar."
top_pad="2"
valign="center"
value="Owner Key:"/>
<line_editor
name="owner_uuid"
layout="topleft"
follows="left|right|bottom"
height="20"
label="00000000-0000-0000-0000-000000000000"
left_pad="4"
right="-4"/>
<text
name="match_source_label"
layout="topleft"
follows="left|bottom"
width="100"
height="20"
top_pad="4"
left="0"
valign="center"
value="Match Source:"/>
<layout_stack
name="buttons_stack"
orientation="horizontal"
left="104"
top_delta="0"
right="-4"
height="68"
follows="left|right|bottom"
layout="topleft">
<layout_panel
width="72"
layout="topleft"
follows="all"
name="buttons_group_1">
<button
name="type_nearby"
layout="topleft"
follows="left|right|top"
width= "72"
height="20"
is_toggle="true"
top_pad="0"
label="Local Chat"
tool_tip="Regular public avatar chat nearby."/>
<button
name="type_im"
layout="topleft"
follows="left|right|top"
width= "72"
height="20"
is_toggle="true"
top_pad="4"
label="IM"
tool_tip="Instant messages from residents."/>
<button
name="type_group_im"
layout="topleft"
follows="left|right|top"
width= "72"
height="20"
is_toggle="true"
top_pad="4"
label="Group Chat"
tool_tip="Group chat from residents."/>
</layout_panel>
<layout_panel name="buttons_group_2">
<button
name="type_group_notice"
layout="topleft"
follows="left|right|top"
width= "72"
height="20"
is_toggle="true"
top="0"
label="Notice"
tool_tip="Group notices."/>
<button
name="type_invite"
layout="topleft"
follows="left|right|top"
width= "72"
height="20"
is_toggle="true"
top_pad="4"
label="Invite"
tool_tip="Group invitations."/>
<button
name="type_object_chat"
layout="topleft"
follows="left|right|top"
width= "72"
height="20"
is_toggle="true"
top_pad="4"
label="Object Chat"
tool_tip="Nearby chat from objects."/>
</layout_panel>
<layout_panel name="buttons_group_3">
<button
name="type_object_im"
layout="topleft"
follows="left|right|top"
width= "72"
height="20"
is_toggle="true"
top="0"
label="Object IM"
tool_tip="Instant messages from objects."/>
<button
name="type_script_error"
layout="topleft"
follows="left|right|top"
width= "72" height="20"
is_toggle="true"
top_pad="4"
label="Error"
tool_tip="Debug chat coming from scripts."/>
<button
name="type_dialog"
layout="topleft"
follows="left|right|top"
width= "72"
height="20"
is_toggle="true"
top_pad="4"
label="Dialog"
tool_tip="Matches fields in script dialogs."/>
</layout_panel>
<layout_panel name="buttons_group_4">
<button
name="type_inventory_offer"
layout="topleft"
follows="left|right|top"
width= "72"
height="20"
is_toggle="true"
top="0"
label="Offer"
tool_tip="Matches fields in inventory offers."/>
<button
name="type_friendship"
layout="topleft"
follows="left|right|top"
width= "72"
height="20"
is_toggle="true"
top_pad="4"
label="Friendship"
tool_tip="Friend requests from residents."/>
<button
name="type_lure"
layout="topleft"
follows="left|right|top"
width= "72"
height="20"
is_toggle="true"
top_pad="4"
label="TP Lure"
tool_tip="Teleport offers from residents."/>
</layout_panel>
<layout_panel name="buttons_group_5">
<button
name="type_tp_request"
layout="topleft"
follows="left|right|top"
width= "72"
height="20"
is_toggle="true"
top="0"
label="TP Request"
tool_tip="Requests from residents to send them a teleport offer."/>
<button
name="type_load_url"
layout="topleft"
follows="left|right|top"
width= "72"
height="20"
is_toggle="true"
top_pad="4"
label="Load URL"
tool_tip="Scripted requests to visit a website."/>
</layout_panel>
</layout_stack>
<view_border
layout="topleft"
right="-4"
follows="left|bottom|right"
left="0"
height="1"
top_pad="4"/>
<text
name="chat_replace_label"
layout="topleft"
follows="left|bottom"
left="0"
height="20"
width="100"
top_pad="2"
value="Chat Replace:"
valign="center"/>
<line_editor
name="chat_replace"
layout="topleft"
follows="left|right|bottom"
left_pad="4"
height="20"
right="-4"/>
<text
name="button_reply_label"
layout="topleft"
follows="left|bottom"
left="0"
height="20"
width="100"
top_pad="2"
value="Button Reply:"
valign="center"/>
<line_editor
name="button_reply"
layout="topleft"
follows="left|right|bottom"
left_pad="4"
height="20"
right="-4"/>
<text
name="text_box_reply_label"
layout="topleft"
follows="left|bottom"
left="0"
height="20"
width="100"
top_pad="3"
value="Text Box Reply:"
valign="center"/>
<text_editor
name="text_box_reply"
layout="topleft"
follows="left|right|bottom"
left_pad="4"
height="60"
right="-4"
top_delta="0"/>
</panel>
</floater>
@@ -52,6 +52,32 @@
function="People.Groups.Enable"
parameter="call" />
</menu_item_call>
<menu_item_call
label="Pin group"
name="Favorite">
<menu_item_call.on_click
function="People.Groups.Action"
parameter="favorite" />
<menu_item_call.on_enable
function="People.Groups.Enable"
parameter="favorite" />
<menu_item_call.on_visible
function="People.Groups.Visible"
parameter="favorite" />
</menu_item_call>
<menu_item_call
label="Unpin group"
name="Unfavorite">
<menu_item_call.on_click
function="People.Groups.Action"
parameter="unfavorite" />
<menu_item_call.on_enable
function="People.Groups.Enable"
parameter="unfavorite" />
<menu_item_call.on_visible
function="People.Groups.Visible"
parameter="unfavorite" />
</menu_item_call>
<menu_item_separator />
<menu_item_call
label="Leave"
@@ -832,6 +832,17 @@
parameter="fs_im_container" />
</menu_item_check>
<menu_item_check
label="Omnifilter"
name="Omnifilter">
<menu_item_check.on_click
function="Floater.Toggle"
parameter="omnifilter" />
<menu_item_check.on_check
function="Floater.Visible"
parameter="omnifilter" />
</menu_item_check>
<menu_item_separator/>
<menu_item_check
@@ -2505,9 +2516,7 @@
label="SL Marketplace"
name="SL Marketplace">
<menu_item_call.on_click
function="PromptShowURL"
name="Xstreet_url"
parameter="WebLaunchExternalTarget,https://marketplace.secondlife.com/" />
function="LoadSLMarketplace"/>
<menu_item_call.on_visible
function="GridCheck"
parameter="secondlife"/>

Some files were not shown because too many files have changed in this diff Show More