mirror of
https://github.com/FirestormViewer/phoenix-firestorm.git
synced 2026-08-14 08:53:53 +00:00
Merge branch 'master' of https://github.com/FirestormViewer/phoenix-firestorm
# Conflicts: # .github/workflows/build.yaml # indra/newview/app_settings/settings.xml
This commit is contained in:
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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) */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1011,6 +1011,10 @@ void LLLineEditor::removeWord(bool prev)
|
||||
{
|
||||
mText.erase(pos, diff);
|
||||
}
|
||||
|
||||
mFontBufferPreSelection.reset();
|
||||
mFontBufferSelection.reset();
|
||||
mFontBufferPostSelection.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 > Sound/Media > 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 > Sound/Media > 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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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())
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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));
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)) )
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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 */
|
||||
/************************************************************************/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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")));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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}"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 > Privatsphäre > 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
Reference in New Issue
Block a user