Merge remote-tracking branch 'origin/main' into geenz/2025.04-to-develop
|
|
@ -0,0 +1 @@
|
|||
qatest.yaml -text eol=crlf
|
||||
|
|
@ -572,4 +572,4 @@ jobs:
|
|||
# uses: actions/upload-artifact@v4
|
||||
# with:
|
||||
# name: test-results-${{ matrix.runner }}
|
||||
# path: ${{ matrix.install-path }}/regressionTest/test_results.html
|
||||
# path: ${{ matrix.install-path }}/regressionTest/test_results.html
|
||||
|
|
@ -67,7 +67,6 @@ elseif (WINDOWS)
|
|||
legacy_stdio_definitions
|
||||
)
|
||||
else()
|
||||
include(CMakeFindFrameworks)
|
||||
find_library(COREFOUNDATION_LIBRARY CoreFoundation)
|
||||
find_library(CARBON_LIBRARY Carbon)
|
||||
find_library(COCOA_LIBRARY Cocoa)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ elseif (WINDOWS)
|
|||
foreach(hive HKEY_CURRENT_USER HKEY_LOCAL_MACHINE)
|
||||
# prefer more recent Python versions to older ones, if multiple versions
|
||||
# are installed
|
||||
foreach(pyver 3.12 3.11 3.10 3.9 3.8 3.7)
|
||||
foreach(pyver 3.13 3.12 3.11 3.10 3.9 3.8 3.7)
|
||||
list(APPEND regpaths "[${hive}\\SOFTWARE\\Python\\PythonCore\\${pyver}\\InstallPath]")
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
|
|
|||
|
|
@ -553,6 +553,61 @@ LLSD shallow(LLSD value, LLSD filter=LLSD()) { return llsd_shallow(value, filter
|
|||
|
||||
} // namespace llsd
|
||||
|
||||
/*****************************************************************************
|
||||
* toArray(), toMap()
|
||||
*****************************************************************************/
|
||||
namespace llsd
|
||||
{
|
||||
|
||||
// For some T convertible to LLSD, given std::vector<T> myVec,
|
||||
// toArray(myVec) returns an LLSD array whose entries correspond to the
|
||||
// items in myVec.
|
||||
// For some U convertible to LLSD, given function U xform(const T&),
|
||||
// toArray(myVec, xform) returns an LLSD array whose every entry is
|
||||
// xform(item) of the corresponding item in myVec.
|
||||
// toArray() actually works with any container<C> usable with range
|
||||
// 'for', not just std::vector.
|
||||
// (Once we get C++20 we can use std::identity instead of this default lambda.)
|
||||
template<typename C, typename FUNC>
|
||||
LLSD toArray(const C& container, FUNC&& func = [](const auto& arg) { return arg; })
|
||||
{
|
||||
LLSD array;
|
||||
for (const auto& item : container)
|
||||
{
|
||||
array.append(std::forward<FUNC>(func)(item));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
// For some T convertible to LLSD, given std::map<std::string, T> myMap,
|
||||
// toMap(myMap) returns an LLSD map whose entries correspond to the
|
||||
// (key, value) pairs in myMap.
|
||||
// For some U convertible to LLSD, given function
|
||||
// std::pair<std::string, U> xform(const std::pair<std::string, T>&),
|
||||
// toMap(myMap, xform) returns an LLSD map whose every entry is
|
||||
// xform(pair) of the corresponding (key, value) pair in myMap.
|
||||
// toMap() actually works with any container usable with range 'for', not
|
||||
// just std::map. It need not even be an associative container, as long as
|
||||
// you pass an xform function that returns std::pair<std::string, U>.
|
||||
// (Once we get C++20 we can use std::identity instead of this default lambda.)
|
||||
template<typename C, typename FUNC>
|
||||
LLSD toMap(const C& container, FUNC&& func = [](const auto& arg) { return arg; })
|
||||
{
|
||||
LLSD map;
|
||||
for (const auto& pair : container)
|
||||
{
|
||||
const auto& [key, value] = std::forward<FUNC>(func)(pair);
|
||||
map[key] = value;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
} // namespace llsd
|
||||
|
||||
/*****************************************************************************
|
||||
* boost::hash<LLSD>
|
||||
*****************************************************************************/
|
||||
|
||||
// Specialization for generating a hash value from an LLSD block.
|
||||
namespace boost
|
||||
{
|
||||
|
|
|
|||
|
|
@ -174,14 +174,6 @@ void LLUUID::toString(std::string& out) const
|
|||
(U8)(mData[15]));
|
||||
}
|
||||
|
||||
// *TODO: deprecate
|
||||
void LLUUID::toString(char* out) const
|
||||
{
|
||||
std::string buffer;
|
||||
toString(buffer);
|
||||
strcpy(out, buffer.c_str()); /* Flawfinder: ignore */
|
||||
}
|
||||
|
||||
void LLUUID::toCompressedString(std::string& out) const
|
||||
{
|
||||
char bytes[UUID_BYTES + 1];
|
||||
|
|
@ -190,13 +182,6 @@ void LLUUID::toCompressedString(std::string& out) const
|
|||
out.assign(bytes, UUID_BYTES);
|
||||
}
|
||||
|
||||
// *TODO: deprecate
|
||||
void LLUUID::toCompressedString(char* out) const
|
||||
{
|
||||
memcpy(out, mData, UUID_BYTES); /* Flawfinder: ignore */
|
||||
out[UUID_BYTES] = '\0';
|
||||
}
|
||||
|
||||
std::string LLUUID::getString() const
|
||||
{
|
||||
return asString();
|
||||
|
|
|
|||
|
|
@ -103,9 +103,7 @@ public:
|
|||
friend LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLUUID &uuid);
|
||||
friend LL_COMMON_API std::istream& operator>>(std::istream& s, LLUUID &uuid);
|
||||
|
||||
void toString(char *out) const; // Does not allocate memory, needs 36 characters (including \0)
|
||||
void toString(std::string& out) const;
|
||||
void toCompressedString(char *out) const; // Does not allocate memory, needs 17 characters (including \0)
|
||||
void toCompressedString(std::string& out) const;
|
||||
|
||||
std::string asString() const;
|
||||
|
|
|
|||
|
|
@ -398,7 +398,7 @@ protected:
|
|||
|
||||
private:
|
||||
bool mLLSDDirty;
|
||||
bool mDirty;
|
||||
bool mDirty; // gates updateSettings
|
||||
bool mReplaced; // super dirty!
|
||||
|
||||
static LLSD combineSDMaps(const LLSD &first, const LLSD &other);
|
||||
|
|
|
|||
|
|
@ -1932,6 +1932,7 @@ LLUUID LLSettingsSky::getCloudNoiseTextureId() const
|
|||
void LLSettingsSky::setCloudNoiseTextureId(const LLUUID &id)
|
||||
{
|
||||
mCloudTextureId = id;
|
||||
setDirtyFlag(true);
|
||||
setLLSDDirty();
|
||||
}
|
||||
|
||||
|
|
@ -1976,6 +1977,7 @@ LLVector2 LLSettingsSky::getCloudScrollRate() const
|
|||
void LLSettingsSky::setCloudScrollRate(const LLVector2 &val)
|
||||
{
|
||||
mScrollRate = val;
|
||||
setDirtyFlag(true);
|
||||
setLLSDDirty();
|
||||
}
|
||||
|
||||
|
|
@ -2134,6 +2136,7 @@ LLUUID LLSettingsSky::getMoonTextureId() const
|
|||
void LLSettingsSky::setMoonTextureId(LLUUID id)
|
||||
{
|
||||
mMoonTextureId = id;
|
||||
setDirtyFlag(true);
|
||||
setLLSDDirty();
|
||||
}
|
||||
|
||||
|
|
@ -2218,6 +2221,7 @@ LLUUID LLSettingsSky::getSunTextureId() const
|
|||
void LLSettingsSky::setSunTextureId(LLUUID id)
|
||||
{
|
||||
mSunTextureId = id;
|
||||
setDirtyFlag(true);
|
||||
setLLSDDirty();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -209,8 +209,14 @@ S32 LLPacketRing::receiveOrDropBufferedPacket(char *datap, bool drop)
|
|||
|
||||
if (!drop)
|
||||
{
|
||||
assert(packet_size > 0);
|
||||
memcpy(datap, packet->getData(), packet_size);
|
||||
if (packet_size > 0)
|
||||
{
|
||||
memcpy(datap, packet->getData(), packet_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -506,6 +506,7 @@ static apr_status_t tcp_blocking_handshake(LLSocket::ptr_t handle, char * dataou
|
|||
rv = apr_socket_recv(apr_socket, datain, &maxinlen);
|
||||
if (rv != APR_SUCCESS)
|
||||
{
|
||||
// if rv == 70060 it's WSAETIMEDOUT
|
||||
char buf[MAX_STRING];
|
||||
LL_WARNS("Proxy") << "Error receiving data from proxy control channel, status: " << rv << " " << apr_strerror(rv, buf, MAX_STRING) << LL_ENDL;
|
||||
ll_apr_warn_status(rv);
|
||||
|
|
|
|||
|
|
@ -654,7 +654,14 @@ LLFontGlyphInfo* LLFontFreetype::addGlyphFromFont(const LLFontFreetype *fontp, l
|
|||
|
||||
LLImageGL *image_gl = mFontBitmapCachep->getImageGL(bitmap_glyph_type, bitmap_num);
|
||||
LLImageRaw *image_raw = mFontBitmapCachep->getImageRaw(bitmap_glyph_type, bitmap_num);
|
||||
image_gl->setSubImage(image_raw, 0, 0, image_gl->getWidth(), image_gl->getHeight());
|
||||
if (image_gl && image_raw)
|
||||
{
|
||||
image_gl->setSubImage(image_raw, 0, 0, image_gl->getWidth(), image_gl->getHeight());
|
||||
}
|
||||
else
|
||||
{
|
||||
llassert(false); //images were just inserted by nextOpenPos, they shouldn't be missing
|
||||
}
|
||||
|
||||
return gi;
|
||||
}
|
||||
|
|
@ -838,7 +845,12 @@ bool LLFontFreetype::setSubImageBGRA(U32 x, U32 y, U32 bitmap_num, U16 width, U1
|
|||
{
|
||||
LLImageRaw* image_raw = mFontBitmapCachep->getImageRaw(EFontGlyphType::Color, bitmap_num);
|
||||
llassert(!mIsFallback);
|
||||
llassert(image_raw && (image_raw->getComponents() == 4));
|
||||
if (!image_raw)
|
||||
{
|
||||
llassert(false);
|
||||
return false;
|
||||
}
|
||||
llassert(image_raw->getComponents() == 4);
|
||||
|
||||
// NOTE: inspired by LLImageRaw::setSubImage()
|
||||
U32* image_data = (U32*)image_raw->getData();
|
||||
|
|
@ -866,10 +878,17 @@ bool LLFontFreetype::setSubImageBGRA(U32 x, U32 y, U32 bitmap_num, U16 width, U1
|
|||
void LLFontFreetype::setSubImageLuminanceAlpha(U32 x, U32 y, U32 bitmap_num, U32 width, U32 height, U8 *data, S32 stride) const
|
||||
{
|
||||
LLImageRaw *image_raw = mFontBitmapCachep->getImageRaw(EFontGlyphType::Grayscale, bitmap_num);
|
||||
LLImageDataLock lock(image_raw);
|
||||
|
||||
llassert(!mIsFallback);
|
||||
llassert(image_raw && (image_raw->getComponents() == 2));
|
||||
if (!image_raw)
|
||||
{
|
||||
llassert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
LLImageDataLock lock(image_raw);
|
||||
|
||||
llassert(image_raw->getComponents() == 2);
|
||||
|
||||
U8 *target = image_raw->getData();
|
||||
llassert(target);
|
||||
|
|
|
|||
|
|
@ -1228,28 +1228,9 @@ bool LLGLManager::initGL()
|
|||
}
|
||||
#endif
|
||||
|
||||
#if LL_WINDOWS
|
||||
if (mVRAM < 256)
|
||||
{
|
||||
// Something likely went wrong using the above extensions
|
||||
// try WMI first and fall back to old method (from dxdiag) if all else fails
|
||||
// Function will check all GPUs WMI knows of and will pick up the one with most
|
||||
// memory. We need to check all GPUs because system can switch active GPU to
|
||||
// weaker one, to preserve power when not under load.
|
||||
U32 mem = LLDXHardware::getMBVideoMemoryViaWMI();
|
||||
if (mem != 0)
|
||||
{
|
||||
mVRAM = mem;
|
||||
LL_WARNS("RenderInit") << "VRAM Detected (WMI):" << mVRAM<< LL_ENDL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (mVRAM < 256 && old_vram > 0)
|
||||
{
|
||||
// fall back to old method
|
||||
// Note: on Windows value will be from LLDXHardware.
|
||||
// Either received via dxdiag or via WMI by id from dxdiag.
|
||||
mVRAM = old_vram;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1076,8 +1076,8 @@ void LLGLSLShader::bind()
|
|||
|
||||
void LLGLSLShader::bind(U8 variant)
|
||||
{
|
||||
llassert(mGLTFVariants.size() == LLGLSLShader::NUM_GLTF_VARIANTS);
|
||||
llassert(variant < LLGLSLShader::NUM_GLTF_VARIANTS);
|
||||
llassert_always(mGLTFVariants.size() == LLGLSLShader::NUM_GLTF_VARIANTS);
|
||||
llassert_always(variant < LLGLSLShader::NUM_GLTF_VARIANTS);
|
||||
mGLTFVariants[variant].bind();
|
||||
}
|
||||
|
||||
|
|
@ -1085,7 +1085,7 @@ void LLGLSLShader::bind(bool rigged)
|
|||
{
|
||||
if (rigged)
|
||||
{
|
||||
llassert(mRiggedVariant);
|
||||
llassert_always(mRiggedVariant);
|
||||
mRiggedVariant->bind();
|
||||
}
|
||||
else
|
||||
|
|
@ -1247,23 +1247,40 @@ S32 LLGLSLShader::disableTexture(S32 uniform, LLTexUnit::eTextureType mode)
|
|||
llassert(false);
|
||||
return -1;
|
||||
}
|
||||
|
||||
S32 index = mTexture[uniform];
|
||||
if (index != -1 && gGL.getTexUnit(index)->getCurrType() != LLTexUnit::TT_NONE)
|
||||
if (index < 0)
|
||||
{
|
||||
if (gDebugGL && gGL.getTexUnit(index)->getCurrType() != mode)
|
||||
// Invalid texture index - nothing to disable
|
||||
return index;
|
||||
}
|
||||
|
||||
LLTexUnit* tex_unit = gGL.getTexUnit(index);
|
||||
if (!tex_unit)
|
||||
{
|
||||
// Invalid texture unit
|
||||
LL_WARNS_ONCE("Shader") << "Invalid texture unit at index: " << index << LL_ENDL;
|
||||
return index;
|
||||
}
|
||||
|
||||
LLTexUnit::eTextureType curr_type = tex_unit->getCurrType();
|
||||
if (curr_type != LLTexUnit::TT_NONE)
|
||||
{
|
||||
if (gDebugGL && curr_type != mode)
|
||||
{
|
||||
if (gDebugSession)
|
||||
{
|
||||
gFailLog << "Texture channel " << index << " texture type corrupted." << std::endl;
|
||||
gFailLog << "Texture channel " << index << " texture type corrupted. Expected: " << mode << ", Found: " << curr_type << std::endl;
|
||||
ll_fail("LLGLSLShader::disableTexture failed");
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_ERRS() << "Texture channel " << index << " texture type corrupted." << LL_ENDL;
|
||||
LL_ERRS() << "Texture channel " << index << " texture type corrupted. Expected: " << mode << ", Found: " << curr_type << LL_ENDL;
|
||||
}
|
||||
}
|
||||
gGL.getTexUnit(index)->disable();
|
||||
tex_unit->disable();
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ set(llui_SOURCE_FILES
|
|||
llbadgeowner.cpp
|
||||
llbutton.cpp
|
||||
llchatentry.cpp
|
||||
llchatmentionhelper.cpp
|
||||
llcheckboxctrl.cpp
|
||||
llclipboard.cpp
|
||||
llcombobox.cpp
|
||||
|
|
@ -130,6 +131,7 @@ set(llui_HEADER_FILES
|
|||
llcallbackmap.h
|
||||
llchatentry.h
|
||||
llchat.h
|
||||
llchatmentionhelper.h
|
||||
llcheckboxctrl.h
|
||||
llclipboard.h
|
||||
llcombobox.h
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ LLChatEntry::LLChatEntry(const Params& p)
|
|||
mCurrentHistoryLine = mLineHistory.begin();
|
||||
|
||||
mAutoIndent = false;
|
||||
mShowChatMentionPicker = true;
|
||||
keepSelectionOnReturn(true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
* @file llchatmentionhelper.cpp
|
||||
*
|
||||
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2025, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#include "linden_common.h"
|
||||
|
||||
#include "llchatmentionhelper.h"
|
||||
#include "llfloater.h"
|
||||
#include "llfloaterreg.h"
|
||||
#include "lluictrl.h"
|
||||
|
||||
constexpr char CHAT_MENTION_HELPER_FLOATER[] = "chat_mention_picker";
|
||||
|
||||
bool LLChatMentionHelper::isActive(const LLUICtrl* ctrl) const
|
||||
{
|
||||
return mHostHandle.get() == ctrl;
|
||||
}
|
||||
|
||||
bool LLChatMentionHelper::isCursorInNameMention(const LLWString& wtext, S32 cursor_pos, S32* mention_start_pos) const
|
||||
{
|
||||
if (cursor_pos <= 0 || cursor_pos > static_cast<S32>(wtext.size()))
|
||||
return false;
|
||||
|
||||
// Find the beginning of the current word
|
||||
S32 start = cursor_pos - 1;
|
||||
while (start > 0 && wtext[start - 1] != U32(' ') && wtext[start - 1] != U32('\n'))
|
||||
{
|
||||
--start;
|
||||
}
|
||||
|
||||
if (wtext[start] != U32('@'))
|
||||
return false;
|
||||
|
||||
if (mention_start_pos)
|
||||
*mention_start_pos = start;
|
||||
|
||||
S32 word_length = cursor_pos - start;
|
||||
|
||||
if (word_length == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the name after '@'
|
||||
std::string name = wstring_to_utf8str(wtext.substr(start + 1, word_length - 1));
|
||||
LLStringUtil::toLower(name);
|
||||
for (const auto& av_name : mAvatarNames)
|
||||
{
|
||||
if (av_name == name || av_name.find(name) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void LLChatMentionHelper::showHelper(LLUICtrl* host_ctrl, S32 local_x, S32 local_y, const std::string& av_name, std::function<void(std::string)> cb)
|
||||
{
|
||||
if (mHelperHandle.isDead())
|
||||
{
|
||||
LLFloater* av_picker_floater = LLFloaterReg::getInstance(CHAT_MENTION_HELPER_FLOATER);
|
||||
mHelperHandle = av_picker_floater->getHandle();
|
||||
mHelperCommitConn = av_picker_floater->setCommitCallback([&](LLUICtrl* ctrl, const LLSD& param) { onCommitName(param.asString()); });
|
||||
}
|
||||
setHostCtrl(host_ctrl);
|
||||
mNameCommitCb = cb;
|
||||
|
||||
S32 floater_x, floater_y;
|
||||
if (!host_ctrl->localPointToOtherView(local_x, local_y, &floater_x, &floater_y, gFloaterView))
|
||||
{
|
||||
LL_WARNS() << "Cannot show helper for non-floater controls." << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
|
||||
LLFloater* av_picker_floater = mHelperHandle.get();
|
||||
LLRect rect = av_picker_floater->getRect();
|
||||
rect.setLeftTopAndSize(floater_x, floater_y + rect.getHeight(), rect.getWidth(), rect.getHeight());
|
||||
av_picker_floater->setRect(rect);
|
||||
if (av_picker_floater->isShown())
|
||||
{
|
||||
av_picker_floater->onOpen(LLSD().with("av_name", av_name));
|
||||
}
|
||||
else
|
||||
{
|
||||
av_picker_floater->openFloater(LLSD().with("av_name", av_name));
|
||||
}
|
||||
}
|
||||
|
||||
void LLChatMentionHelper::hideHelper(const LLUICtrl* ctrl)
|
||||
{
|
||||
if ((ctrl && !isActive(ctrl)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
setHostCtrl(nullptr);
|
||||
}
|
||||
|
||||
bool LLChatMentionHelper::handleKey(const LLUICtrl* ctrl, KEY key, MASK mask)
|
||||
{
|
||||
if (mHelperHandle.isDead() || !isActive(ctrl))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return mHelperHandle.get()->handleKey(key, mask, true);
|
||||
}
|
||||
|
||||
void LLChatMentionHelper::onCommitName(std::string name_url)
|
||||
{
|
||||
if (!mHostHandle.isDead() && mNameCommitCb)
|
||||
{
|
||||
mNameCommitCb(name_url);
|
||||
}
|
||||
}
|
||||
|
||||
void LLChatMentionHelper::setHostCtrl(LLUICtrl* host_ctrl)
|
||||
{
|
||||
const LLUICtrl* pCurHostCtrl = mHostHandle.get();
|
||||
if (pCurHostCtrl != host_ctrl)
|
||||
{
|
||||
mHostCtrlFocusLostConn.disconnect();
|
||||
mHostHandle.markDead();
|
||||
mNameCommitCb = {};
|
||||
|
||||
if (!mHelperHandle.isDead())
|
||||
{
|
||||
mHelperHandle.get()->closeFloater();
|
||||
}
|
||||
|
||||
if (host_ctrl)
|
||||
{
|
||||
mHostHandle = host_ctrl->getHandle();
|
||||
mHostCtrlFocusLostConn = host_ctrl->setFocusLostCallback(std::bind([&]() { hideHelper(getHostCtrl()); }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* @file llchatmentionhelper.h
|
||||
* @brief Header file for LLChatMentionHelper
|
||||
*
|
||||
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2025, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "llhandle.h"
|
||||
#include "llsingleton.h"
|
||||
|
||||
#include <boost/signals2.hpp>
|
||||
|
||||
class LLFloater;
|
||||
class LLUICtrl;
|
||||
|
||||
class LLChatMentionHelper : public LLSingleton<LLChatMentionHelper>
|
||||
{
|
||||
LLSINGLETON(LLChatMentionHelper) {}
|
||||
~LLChatMentionHelper() override {}
|
||||
|
||||
public:
|
||||
|
||||
bool isActive(const LLUICtrl* ctrl) const;
|
||||
bool isCursorInNameMention(const LLWString& wtext, S32 cursor_pos, S32* mention_start_pos = nullptr) const;
|
||||
void showHelper(LLUICtrl* host_ctrl, S32 local_x, S32 local_y, const std::string& av_name, std::function<void(std::string)> commit_cb);
|
||||
void hideHelper(const LLUICtrl* ctrl = nullptr);
|
||||
|
||||
bool handleKey(const LLUICtrl* ctrl, KEY key, MASK mask);
|
||||
void onCommitName(std::string name_url);
|
||||
|
||||
void updateAvatarList(std::vector<std::string> av_names) { mAvatarNames = av_names; }
|
||||
|
||||
protected:
|
||||
void setHostCtrl(LLUICtrl* host_ctrl);
|
||||
LLUICtrl* getHostCtrl() const { return mHostHandle.get(); }
|
||||
|
||||
private:
|
||||
LLHandle<LLUICtrl> mHostHandle;
|
||||
LLHandle<LLFloater> mHelperHandle;
|
||||
boost::signals2::connection mHostCtrlFocusLostConn;
|
||||
boost::signals2::connection mHelperCommitConn;
|
||||
std::function<void(std::string)> mNameCommitCb;
|
||||
|
||||
std::vector<std::string> mAvatarNames;
|
||||
};
|
||||
|
|
@ -99,6 +99,7 @@ void LLEmojiHelper::showHelper(LLUICtrl* hostctrl_p, S32 local_x, S32 local_y, c
|
|||
LLFloater* pHelperFloater = LLFloaterReg::getInstance(DEFAULT_EMOJI_HELPER_FLOATER);
|
||||
mHelperHandle = pHelperFloater->getHandle();
|
||||
mHelperCommitConn = pHelperFloater->setCommitCallback(std::bind([&](const LLSD& sdValue) { onCommitEmoji(utf8str_to_wstring(sdValue.asStringRef())[0]); }, std::placeholders::_2));
|
||||
mHelperCloseConn = pHelperFloater->setCloseCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCloseHelper(ctrl, param); });
|
||||
}
|
||||
setHostCtrl(hostctrl_p);
|
||||
mEmojiCommitCb = cb;
|
||||
|
|
@ -148,6 +149,16 @@ void LLEmojiHelper::onCommitEmoji(llwchar emoji)
|
|||
}
|
||||
}
|
||||
|
||||
void LLEmojiHelper::onCloseHelper(LLUICtrl* ctrl, const LLSD& param)
|
||||
{
|
||||
mCloseSignal(ctrl, param);
|
||||
}
|
||||
|
||||
boost::signals2::connection LLEmojiHelper::setCloseCallback(const commit_signal_t::slot_type& cb)
|
||||
{
|
||||
return mCloseSignal.connect(cb);
|
||||
}
|
||||
|
||||
void LLEmojiHelper::setHostCtrl(LLUICtrl* hostctrl_p)
|
||||
{
|
||||
const LLUICtrl* pCurHostCtrl = mHostHandle.get();
|
||||
|
|
|
|||
|
|
@ -51,16 +51,23 @@ public:
|
|||
// Eventing
|
||||
bool handleKey(const LLUICtrl* ctrl_p, KEY key, MASK mask);
|
||||
void onCommitEmoji(llwchar emoji);
|
||||
void onCloseHelper(LLUICtrl* ctrl, const LLSD& param);
|
||||
|
||||
typedef boost::signals2::signal<void(LLUICtrl* ctrl, const LLSD& param)> commit_signal_t;
|
||||
boost::signals2::connection setCloseCallback(const commit_signal_t::slot_type& cb);
|
||||
|
||||
protected:
|
||||
LLUICtrl* getHostCtrl() const { return mHostHandle.get(); }
|
||||
void setHostCtrl(LLUICtrl* hostctrl_p);
|
||||
|
||||
private:
|
||||
commit_signal_t mCloseSignal;
|
||||
|
||||
LLHandle<LLUICtrl> mHostHandle;
|
||||
LLHandle<LLFloater> mHelperHandle;
|
||||
boost::signals2::connection mHostCtrlFocusLostConn;
|
||||
boost::signals2::connection mHelperCommitConn;
|
||||
boost::signals2::connection mHelperCloseConn;
|
||||
std::function<void(llwchar)> mEmojiCommitCb;
|
||||
bool mIsHideDisabled;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -459,6 +459,7 @@ LLFlatListView::LLFlatListView(const LLFlatListView::Params& p)
|
|||
, mNoItemsCommentTextbox(NULL)
|
||||
, mIsConsecutiveSelection(false)
|
||||
, mKeepSelectionVisibleOnReshape(p.keep_selection_visible_on_reshape)
|
||||
, mFocusOnItemClicked(true)
|
||||
{
|
||||
mBorderThickness = getBorderWidth();
|
||||
|
||||
|
|
@ -610,7 +611,10 @@ void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask)
|
|||
return;
|
||||
}
|
||||
|
||||
setFocus(true);
|
||||
if (mFocusOnItemClicked)
|
||||
{
|
||||
setFocus(true);
|
||||
}
|
||||
|
||||
bool select_item = !isSelected(item_pair);
|
||||
|
||||
|
|
|
|||
|
|
@ -299,6 +299,8 @@ public:
|
|||
|
||||
virtual S32 notify(const LLSD& info) override;
|
||||
|
||||
void setFocusOnItemClicked(bool b) { mFocusOnItemClicked = b; }
|
||||
|
||||
virtual ~LLFlatListView();
|
||||
|
||||
protected:
|
||||
|
|
@ -423,6 +425,8 @@ private:
|
|||
|
||||
bool mKeepSelectionVisibleOnReshape;
|
||||
|
||||
bool mFocusOnItemClicked;
|
||||
|
||||
/** All pairs of the list */
|
||||
pairs_list_t mItemPairs;
|
||||
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ protected:
|
|||
virtual bool isHighlightActive();
|
||||
virtual bool isFadeItem();
|
||||
virtual bool isFlashing() { return false; }
|
||||
virtual void setFlashState(bool) { }
|
||||
virtual void setFlashState(bool, bool) { }
|
||||
|
||||
static LLFontGL* getLabelFontForStyle(U8 style);
|
||||
const LLFontGL* getLabelFont();
|
||||
|
|
|
|||
|
|
@ -2508,9 +2508,24 @@ void LLLineEditor::resetPreedit()
|
|||
if (hasPreeditString())
|
||||
{
|
||||
const S32 preedit_pos = mPreeditPositions.front();
|
||||
mText.erase(preedit_pos, mPreeditPositions.back() - preedit_pos);
|
||||
mText.insert(preedit_pos, mPreeditOverwrittenWString);
|
||||
setCursor(preedit_pos);
|
||||
const S32 end = mPreeditPositions.back();
|
||||
const S32 len = end - preedit_pos;
|
||||
const S32 size = mText.length();
|
||||
if (preedit_pos < size
|
||||
&& end <= size
|
||||
&& preedit_pos >= 0
|
||||
&& len > 0)
|
||||
{
|
||||
mText.erase(preedit_pos, len);
|
||||
mText.insert(preedit_pos, mPreeditOverwrittenWString);
|
||||
setCursor(preedit_pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS() << "Index out of bounds. Start: " << preedit_pos
|
||||
<< ", end:" << end
|
||||
<< ", full string length: " << size << LL_ENDL;
|
||||
}
|
||||
|
||||
mPreeditWString.clear();
|
||||
mPreeditOverwrittenWString.clear();
|
||||
|
|
|
|||
|
|
@ -38,11 +38,13 @@ LLStyle::Params::Params()
|
|||
color("color", LLColor4::black),
|
||||
readonly_color("readonly_color", LLColor4::black),
|
||||
selected_color("selected_color", LLColor4::black),
|
||||
highlight_bg_color("highlight_bg_color", LLColor4::green),
|
||||
alpha("alpha", 1.f),
|
||||
font("font", LLStyle::getDefaultFont()),
|
||||
image("image"),
|
||||
link_href("href"),
|
||||
is_link("is_link")
|
||||
is_link("is_link"),
|
||||
draw_highlight_bg("draw_highlight_bg", false)
|
||||
{}
|
||||
|
||||
|
||||
|
|
@ -51,12 +53,14 @@ LLStyle::LLStyle(const LLStyle::Params& p)
|
|||
mColor(p.color),
|
||||
mReadOnlyColor(p.readonly_color),
|
||||
mSelectedColor(p.selected_color),
|
||||
mHighlightBgColor(p.highlight_bg_color),
|
||||
mFont(p.font()),
|
||||
mLink(p.link_href),
|
||||
mIsLink(p.is_link.isProvided() ? p.is_link : !p.link_href().empty()),
|
||||
mDropShadow(p.drop_shadow),
|
||||
mImagep(p.image()),
|
||||
mAlpha(p.alpha)
|
||||
mAlpha(p.alpha),
|
||||
mDrawHighlightBg(p.draw_highlight_bg)
|
||||
{}
|
||||
|
||||
void LLStyle::setFont(const LLFontGL* font)
|
||||
|
|
|
|||
|
|
@ -43,15 +43,25 @@ public:
|
|||
Optional<LLFontGL::ShadowType> drop_shadow;
|
||||
Optional<LLUIColor> color,
|
||||
readonly_color,
|
||||
selected_color;
|
||||
selected_color,
|
||||
highlight_bg_color;
|
||||
Optional<F32> alpha;
|
||||
Optional<const LLFontGL*> font;
|
||||
Optional<LLUIImage*> image;
|
||||
Optional<std::string> link_href;
|
||||
Optional<bool> is_link;
|
||||
Optional<bool> draw_highlight_bg;
|
||||
Params();
|
||||
};
|
||||
LLStyle(const Params& p = Params());
|
||||
|
||||
enum EUnderlineLink
|
||||
{
|
||||
UNDERLINE_ALWAYS = 0,
|
||||
UNDERLINE_ON_HOVER,
|
||||
UNDERLINE_NEVER
|
||||
};
|
||||
|
||||
public:
|
||||
const LLUIColor& getColor() const { return mColor; }
|
||||
void setColor(const LLUIColor &color) { mColor = color; }
|
||||
|
|
@ -84,6 +94,9 @@ public:
|
|||
|
||||
bool isImage() const { return mImagep.notNull(); }
|
||||
|
||||
bool getDrawHighlightBg() const { return mDrawHighlightBg; }
|
||||
const LLUIColor& getHighlightBgColor() const { return mHighlightBgColor; }
|
||||
|
||||
bool operator==(const LLStyle &rhs) const
|
||||
{
|
||||
return
|
||||
|
|
@ -91,11 +104,13 @@ public:
|
|||
&& mColor == rhs.mColor
|
||||
&& mReadOnlyColor == rhs.mReadOnlyColor
|
||||
&& mSelectedColor == rhs.mSelectedColor
|
||||
&& mHighlightBgColor == rhs.mHighlightBgColor
|
||||
&& mFont == rhs.mFont
|
||||
&& mLink == rhs.mLink
|
||||
&& mImagep == rhs.mImagep
|
||||
&& mDropShadow == rhs.mDropShadow
|
||||
&& mAlpha == rhs.mAlpha;
|
||||
&& mAlpha == rhs.mAlpha
|
||||
&& mDrawHighlightBg == rhs.mDrawHighlightBg;
|
||||
}
|
||||
|
||||
bool operator!=(const LLStyle& rhs) const { return !(*this == rhs); }
|
||||
|
|
@ -112,11 +127,13 @@ private:
|
|||
LLUIColor mColor;
|
||||
LLUIColor mReadOnlyColor;
|
||||
LLUIColor mSelectedColor;
|
||||
LLUIColor mHighlightBgColor;
|
||||
const LLFontGL* mFont;
|
||||
LLPointer<LLUIImage> mImagep;
|
||||
F32 mAlpha;
|
||||
bool mVisible;
|
||||
bool mIsLink;
|
||||
bool mDrawHighlightBg;
|
||||
};
|
||||
|
||||
typedef LLPointer<LLStyle> LLStyleSP;
|
||||
|
|
|
|||
|
|
@ -460,6 +460,62 @@ std::vector<LLRect> LLTextBase::getSelectionRects()
|
|||
return selection_rects;
|
||||
}
|
||||
|
||||
std::vector<std::pair<LLRect, LLUIColor>> LLTextBase::getHighlightedBgRects()
|
||||
{
|
||||
std::vector<std::pair<LLRect, LLUIColor>> highlight_rects;
|
||||
|
||||
LLRect content_display_rect = getVisibleDocumentRect();
|
||||
|
||||
// binary search for line that starts before top of visible buffer
|
||||
line_list_t::const_iterator line_iter =
|
||||
std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mTop, compare_bottom());
|
||||
line_list_t::const_iterator end_iter =
|
||||
std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mBottom, compare_top());
|
||||
|
||||
for (; line_iter != end_iter; ++line_iter)
|
||||
{
|
||||
segment_set_t::iterator segment_iter;
|
||||
S32 segment_offset;
|
||||
getSegmentAndOffset(line_iter->mDocIndexStart, &segment_iter, &segment_offset);
|
||||
|
||||
// Use F32 otherwise a string of multiple segments
|
||||
// will accumulate a large error
|
||||
F32 left_precise = (F32)line_iter->mRect.mLeft;
|
||||
F32 right_precise = (F32)line_iter->mRect.mLeft;
|
||||
|
||||
for (; segment_iter != mSegments.end(); ++segment_iter, segment_offset = 0)
|
||||
{
|
||||
LLTextSegmentPtr segmentp = *segment_iter;
|
||||
|
||||
S32 segment_line_start = segmentp->getStart() + segment_offset;
|
||||
S32 segment_line_end = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd);
|
||||
|
||||
if (segment_line_start > segment_line_end)
|
||||
break;
|
||||
|
||||
F32 segment_width = 0;
|
||||
S32 segment_height = 0;
|
||||
|
||||
S32 num_chars = segment_line_end - segment_line_start;
|
||||
segmentp->getDimensionsF32(segment_offset, num_chars, segment_width, segment_height);
|
||||
right_precise += segment_width;
|
||||
|
||||
if (segmentp->getStyle()->getDrawHighlightBg())
|
||||
{
|
||||
LLRect selection_rect;
|
||||
selection_rect.mLeft = (S32)left_precise;
|
||||
selection_rect.mRight = (S32)right_precise;
|
||||
selection_rect.mBottom = line_iter->mRect.mBottom;
|
||||
selection_rect.mTop = line_iter->mRect.mTop;
|
||||
|
||||
highlight_rects.push_back(std::pair(selection_rect, segmentp->getStyle()->getHighlightBgColor()));
|
||||
}
|
||||
left_precise += segment_width;
|
||||
}
|
||||
}
|
||||
return highlight_rects;
|
||||
}
|
||||
|
||||
// Draws the black box behind the selected text
|
||||
void LLTextBase::drawSelectionBackground()
|
||||
{
|
||||
|
|
@ -529,6 +585,71 @@ void LLTextBase::drawSelectionBackground()
|
|||
}
|
||||
}
|
||||
|
||||
void LLTextBase::drawHighlightedBackground()
|
||||
{
|
||||
if (!mLineInfoList.empty())
|
||||
{
|
||||
std::vector<std::pair<LLRect, LLUIColor>> highlight_rects = getHighlightedBgRects();
|
||||
|
||||
if (highlight_rects.empty())
|
||||
return;
|
||||
|
||||
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
||||
|
||||
LLRect content_display_rect = getVisibleDocumentRect();
|
||||
|
||||
for (std::vector<std::pair<LLRect, LLUIColor>>::iterator rect_it = highlight_rects.begin();
|
||||
rect_it != highlight_rects.end(); ++rect_it)
|
||||
{
|
||||
LLRect selection_rect = rect_it->first;
|
||||
const LLColor4& color = rect_it->second;
|
||||
if (mScroller)
|
||||
{
|
||||
// If scroller is On content_display_rect has correct rect and safe to use as is
|
||||
// Note: we might need to account for border
|
||||
selection_rect.translate(mVisibleTextRect.mLeft - content_display_rect.mLeft, mVisibleTextRect.mBottom - content_display_rect.mBottom);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If scroller is Off content_display_rect will have rect from document, adjusted to text width, heigh and position
|
||||
// and we have to acount for offset depending on position
|
||||
S32 v_delta = 0;
|
||||
S32 h_delta = 0;
|
||||
switch (mVAlign)
|
||||
{
|
||||
case LLFontGL::TOP:
|
||||
v_delta = mVisibleTextRect.mTop - content_display_rect.mTop - mVPad;
|
||||
break;
|
||||
case LLFontGL::VCENTER:
|
||||
v_delta = (llmax(mVisibleTextRect.getHeight() - content_display_rect.mTop, -content_display_rect.mBottom) + (mVisibleTextRect.mBottom - content_display_rect.mBottom)) / 2;
|
||||
break;
|
||||
case LLFontGL::BOTTOM:
|
||||
v_delta = mVisibleTextRect.mBottom - content_display_rect.mBottom;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (mHAlign)
|
||||
{
|
||||
case LLFontGL::LEFT:
|
||||
h_delta = mVisibleTextRect.mLeft - content_display_rect.mLeft + mHPad;
|
||||
break;
|
||||
case LLFontGL::HCENTER:
|
||||
h_delta = (llmax(mVisibleTextRect.getWidth() - content_display_rect.mLeft, -content_display_rect.mRight) + (mVisibleTextRect.mRight - content_display_rect.mRight)) / 2;
|
||||
break;
|
||||
case LLFontGL::RIGHT:
|
||||
h_delta = mVisibleTextRect.mRight - content_display_rect.mRight;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
selection_rect.translate(h_delta, v_delta);
|
||||
}
|
||||
gl_rect_2d(selection_rect, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLTextBase::drawCursor()
|
||||
{
|
||||
F32 alpha = getDrawContext().mAlpha;
|
||||
|
|
@ -1399,6 +1520,7 @@ void LLTextBase::draw()
|
|||
drawChild(mDocumentView);
|
||||
}
|
||||
|
||||
drawHighlightedBackground();
|
||||
drawSelectionBackground();
|
||||
drawText();
|
||||
drawCursor();
|
||||
|
|
@ -2200,20 +2322,20 @@ static LLUIImagePtr image_from_icon_name(const std::string& icon_name)
|
|||
}
|
||||
|
||||
|
||||
void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params)
|
||||
void LLTextBase::appendTextImpl(const std::string& new_text, const LLStyle::Params& input_params, bool force_slurl)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
|
||||
LLStyle::Params style_params(getStyleParams());
|
||||
style_params.overwriteFrom(input_params);
|
||||
|
||||
S32 part = (S32)LLTextParser::WHOLE;
|
||||
if (mParseHTML && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358).
|
||||
if ((mParseHTML || force_slurl) && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358).
|
||||
{
|
||||
S32 start=0,end=0;
|
||||
LLUrlMatch match;
|
||||
std::string text = new_text;
|
||||
while (LLUrlRegistry::instance().findUrl(text, match,
|
||||
boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3), isContentTrusted() || mAlwaysShowIcons))
|
||||
boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3), isContentTrusted() || mAlwaysShowIcons, force_slurl))
|
||||
{
|
||||
start = match.getStart();
|
||||
end = match.getEnd()+1;
|
||||
|
|
@ -2245,7 +2367,7 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para
|
|||
}
|
||||
|
||||
// output the styled Url
|
||||
appendAndHighlightTextImpl(match.getLabel(), part, link_params, match.underlineOnHoverOnly());
|
||||
appendAndHighlightTextImpl(match.getLabel(), part, link_params, match.getUnderline());
|
||||
bool tooltip_required = !match.getTooltip().empty();
|
||||
|
||||
// set the tooltip for the Url label
|
||||
|
|
@ -2260,7 +2382,7 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para
|
|||
{
|
||||
link_params.color = LLColor4::grey;
|
||||
link_params.readonly_color = LLColor4::grey;
|
||||
appendAndHighlightTextImpl(label, part, link_params, match.underlineOnHoverOnly());
|
||||
appendAndHighlightTextImpl(label, part, link_params, match.getUnderline());
|
||||
|
||||
// set the tooltip for the query part of url
|
||||
if (tooltip_required)
|
||||
|
|
@ -2428,7 +2550,7 @@ void LLTextBase::appendWidget(const LLInlineViewSegment::Params& params, const s
|
|||
insertStringNoUndo(getLength(), widget_wide_text, &segments);
|
||||
}
|
||||
|
||||
void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only)
|
||||
void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, e_underline underline_link)
|
||||
{
|
||||
// Save old state
|
||||
S32 selection_start = mSelectionStart;
|
||||
|
|
@ -2458,7 +2580,7 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig
|
|||
S32 cur_length = getLength();
|
||||
LLStyleConstSP sp(new LLStyle(highlight_params));
|
||||
LLTextSegmentPtr segmentp;
|
||||
if (underline_on_hover_only || mSkipLinkUnderline)
|
||||
if ((underline_link == e_underline::UNDERLINE_ON_HOVER) || mSkipLinkUnderline)
|
||||
{
|
||||
highlight_params.font.style("NORMAL");
|
||||
LLStyleConstSP normal_sp(new LLStyle(highlight_params));
|
||||
|
|
@ -2482,7 +2604,7 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig
|
|||
S32 segment_start = old_length;
|
||||
S32 segment_end = old_length + static_cast<S32>(wide_text.size());
|
||||
LLStyleConstSP sp(new LLStyle(style_params));
|
||||
if (underline_on_hover_only || mSkipLinkUnderline)
|
||||
if ((underline_link == e_underline::UNDERLINE_ON_HOVER) || mSkipLinkUnderline)
|
||||
{
|
||||
LLStyle::Params normal_style_params(style_params);
|
||||
normal_style_params.font.style("NORMAL");
|
||||
|
|
@ -2516,7 +2638,7 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig
|
|||
}
|
||||
}
|
||||
|
||||
void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only)
|
||||
void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, e_underline underline_link)
|
||||
{
|
||||
if (new_text.empty())
|
||||
{
|
||||
|
|
@ -2531,7 +2653,7 @@ void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlig
|
|||
if (pos != start)
|
||||
{
|
||||
std::string str = std::string(new_text,start,pos-start);
|
||||
appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only);
|
||||
appendAndHighlightTextImpl(str, highlight_part, style_params, underline_link);
|
||||
}
|
||||
appendLineBreakSegment(style_params);
|
||||
start = pos+1;
|
||||
|
|
@ -2539,7 +2661,7 @@ void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlig
|
|||
}
|
||||
|
||||
std::string str = std::string(new_text, start, new_text.length() - start);
|
||||
appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only);
|
||||
appendAndHighlightTextImpl(str, highlight_part, style_params, underline_link);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -3336,6 +3458,7 @@ LLNormalTextSegment::LLNormalTextSegment( LLStyleConstSP style, S32 start, S32 e
|
|||
mLastGeneration(-1)
|
||||
{
|
||||
mFontHeight = mStyle->getFont()->getLineHeight();
|
||||
mCanEdit = !mStyle->getDrawHighlightBg();
|
||||
|
||||
LLUIImagePtr image = mStyle->getImage();
|
||||
if (image.notNull())
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
#include "llstyle.h"
|
||||
#include "llkeywords.h"
|
||||
#include "llpanel.h"
|
||||
#include "llurlmatch.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
|
@ -139,7 +140,7 @@ public:
|
|||
/*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const;
|
||||
/*virtual*/ void updateLayout(const class LLTextBase& editor);
|
||||
/*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect);
|
||||
/*virtual*/ bool canEdit() const { return true; }
|
||||
/*virtual*/ bool canEdit() const { return mCanEdit; }
|
||||
/*virtual*/ const LLUIColor& getColor() const { return mStyle->getColor(); }
|
||||
/*virtual*/ LLStyleConstSP getStyle() const { return mStyle; }
|
||||
/*virtual*/ void setStyle(LLStyleConstSP style) { mStyle = style; }
|
||||
|
|
@ -161,6 +162,8 @@ protected:
|
|||
virtual const LLWString& getWText() const;
|
||||
virtual const S32 getLength() const;
|
||||
|
||||
void setAllowEdit(bool can_edit) { mCanEdit = can_edit; }
|
||||
|
||||
protected:
|
||||
class LLTextBase& mEditor;
|
||||
LLStyleConstSP mStyle;
|
||||
|
|
@ -169,6 +172,8 @@ protected:
|
|||
std::string mTooltip;
|
||||
boost::signals2::connection mImageLoadedConnection;
|
||||
|
||||
bool mCanEdit { true };
|
||||
|
||||
// font rendering
|
||||
LLFontVertexBuffer mFontBufferPreSelection;
|
||||
LLFontVertexBuffer mFontBufferSelection;
|
||||
|
|
@ -606,6 +611,7 @@ protected:
|
|||
bool operator()(const LLTextSegmentPtr& a, const LLTextSegmentPtr& b) const;
|
||||
};
|
||||
typedef std::multiset<LLTextSegmentPtr, compare_segment_end> segment_set_t;
|
||||
typedef LLStyle::EUnderlineLink e_underline;
|
||||
|
||||
// member functions
|
||||
LLTextBase(const Params &p);
|
||||
|
|
@ -619,12 +625,13 @@ protected:
|
|||
virtual void drawSelectionBackground(); // draws the black box behind the selected text
|
||||
void drawCursor();
|
||||
void drawText();
|
||||
void drawHighlightedBackground();
|
||||
|
||||
// modify contents
|
||||
S32 insertStringNoUndo(S32 pos, const LLWString &wstr, segment_vec_t* segments = NULL); // returns num of chars actually inserted
|
||||
S32 removeStringNoUndo(S32 pos, S32 length);
|
||||
S32 overwriteCharNoUndo(S32 pos, llwchar wc);
|
||||
void appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& stylep, bool underline_on_hover_only = false);
|
||||
void appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& stylep, e_underline underline_link = e_underline::UNDERLINE_ALWAYS);
|
||||
|
||||
|
||||
// manage segments
|
||||
|
|
@ -672,8 +679,9 @@ protected:
|
|||
// avatar names are looked up.
|
||||
void replaceUrl(const std::string &url, const std::string &label, const std::string& icon);
|
||||
|
||||
void appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params = LLStyle::Params());
|
||||
void appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only = false);
|
||||
void appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params = LLStyle::Params(), bool force_slurl = false);
|
||||
void appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, e_underline underline_link = e_underline::UNDERLINE_ALWAYS);
|
||||
S32 normalizeUri(std::string& uri);
|
||||
|
||||
protected:
|
||||
// virtual
|
||||
|
|
@ -683,6 +691,7 @@ protected:
|
|||
}
|
||||
|
||||
std::vector<LLRect> getSelectionRects();
|
||||
std::vector<std::pair<LLRect, LLUIColor>> getHighlightedBgRects();
|
||||
|
||||
protected:
|
||||
// text segmentation and flow
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@
|
|||
#include "llurlregistry.h"
|
||||
#include "lltooltip.h"
|
||||
#include "llmenugl.h"
|
||||
#include "llchatmentionhelper.h"
|
||||
|
||||
#include <queue>
|
||||
#include "llcombobox.h"
|
||||
|
|
@ -270,6 +271,7 @@ LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) :
|
|||
mPrevalidator(p.prevalidator()),
|
||||
mShowContextMenu(p.show_context_menu),
|
||||
mShowEmojiHelper(p.show_emoji_helper),
|
||||
mShowChatMentionPicker(false),
|
||||
mEnableTooltipPaste(p.enable_tooltip_paste),
|
||||
mPassDelete(false),
|
||||
mKeepSelectionOnReturn(false)
|
||||
|
|
@ -714,6 +716,30 @@ void LLTextEditor::handleEmojiCommit(llwchar emoji)
|
|||
}
|
||||
}
|
||||
|
||||
void LLTextEditor::handleMentionCommit(std::string name_url)
|
||||
{
|
||||
S32 mention_start_pos;
|
||||
if (LLChatMentionHelper::instance().isCursorInNameMention(getWText(), mCursorPos, &mention_start_pos))
|
||||
{
|
||||
remove(mention_start_pos, mCursorPos - mention_start_pos, true);
|
||||
insert(mention_start_pos, utf8str_to_wstring(name_url), false, LLTextSegmentPtr());
|
||||
|
||||
std::string new_text(wstring_to_utf8str(getConvertedText()));
|
||||
clear();
|
||||
appendTextImpl(new_text, LLStyle::Params(), true);
|
||||
|
||||
segment_set_t::const_iterator it = getSegIterContaining(mention_start_pos);
|
||||
if (it != mSegments.end())
|
||||
{
|
||||
setCursorPos((*it)->getEnd() + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
setCursorPos(mention_start_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask)
|
||||
{
|
||||
bool handled = false;
|
||||
|
|
@ -1103,6 +1129,7 @@ void LLTextEditor::removeCharOrTab()
|
|||
}
|
||||
|
||||
tryToShowEmojiHelper();
|
||||
tryToShowMentionHelper();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1128,6 +1155,7 @@ void LLTextEditor::removeChar()
|
|||
setCursorPos(mCursorPos - 1);
|
||||
removeChar(mCursorPos);
|
||||
tryToShowEmojiHelper();
|
||||
tryToShowMentionHelper();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1189,6 +1217,7 @@ void LLTextEditor::addChar(llwchar wc)
|
|||
|
||||
setCursorPos(mCursorPos + addChar( mCursorPos, wc ));
|
||||
tryToShowEmojiHelper();
|
||||
tryToShowMentionHelper();
|
||||
|
||||
if (!mReadOnly && mAutoreplaceCallback != NULL)
|
||||
{
|
||||
|
|
@ -1218,6 +1247,14 @@ void LLTextEditor::showEmojiHelper()
|
|||
LLEmojiHelper::instance().showHelper(this, cursorRect.mLeft, cursorRect.mTop, LLStringUtil::null, cb);
|
||||
}
|
||||
|
||||
void LLTextEditor::hideEmojiHelper()
|
||||
{
|
||||
if (mShowEmojiHelper)
|
||||
{
|
||||
LLEmojiHelper::instance().hideHelper(this);
|
||||
}
|
||||
}
|
||||
|
||||
void LLTextEditor::tryToShowEmojiHelper()
|
||||
{
|
||||
if (mReadOnly || !mShowEmojiHelper)
|
||||
|
|
@ -1239,6 +1276,31 @@ void LLTextEditor::tryToShowEmojiHelper()
|
|||
}
|
||||
}
|
||||
|
||||
void LLTextEditor::tryToShowMentionHelper()
|
||||
{
|
||||
if (mReadOnly || !mShowChatMentionPicker)
|
||||
return;
|
||||
|
||||
S32 mention_start_pos;
|
||||
LLWString text(getWText());
|
||||
if (LLChatMentionHelper::instance().isCursorInNameMention(text, mCursorPos, &mention_start_pos))
|
||||
{
|
||||
const LLRect cursor_rect(getLocalRectFromDocIndex(mention_start_pos));
|
||||
std::string name_part(wstring_to_utf8str(text.substr(mention_start_pos, mCursorPos - mention_start_pos)));
|
||||
name_part.erase(0, 1);
|
||||
auto cb = [this](std::string name_url)
|
||||
{
|
||||
handleMentionCommit(name_url);
|
||||
};
|
||||
LLChatMentionHelper::instance().showHelper(this, cursor_rect.mLeft, cursor_rect.mTop, name_part, cb);
|
||||
}
|
||||
else
|
||||
{
|
||||
LLChatMentionHelper::instance().hideHelper();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LLTextEditor::addLineBreakChar(bool group_together)
|
||||
{
|
||||
if( !getEnabled() )
|
||||
|
|
@ -1865,7 +1927,7 @@ bool LLTextEditor::handleKeyHere(KEY key, MASK mask )
|
|||
// not handled and let the parent take care of field movement.
|
||||
if (KEY_TAB == key && mTabsToNextField)
|
||||
{
|
||||
return false;
|
||||
return mShowChatMentionPicker && LLChatMentionHelper::instance().handleKey(this, key, mask);
|
||||
}
|
||||
|
||||
if (mReadOnly && mScroller)
|
||||
|
|
@ -1876,9 +1938,13 @@ bool LLTextEditor::handleKeyHere(KEY key, MASK mask )
|
|||
}
|
||||
else
|
||||
{
|
||||
if (!mReadOnly && mShowEmojiHelper && LLEmojiHelper::instance().handleKey(this, key, mask))
|
||||
if (!mReadOnly)
|
||||
{
|
||||
return true;
|
||||
if ((mShowEmojiHelper && LLEmojiHelper::instance().handleKey(this, key, mask)) ||
|
||||
(mShowChatMentionPicker && LLChatMentionHelper::instance().handleKey(this, key, mask)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (mEnableTooltipPaste &&
|
||||
|
|
@ -3075,3 +3141,21 @@ S32 LLTextEditor::spacesPerTab()
|
|||
{
|
||||
return SPACES_PER_TAB;
|
||||
}
|
||||
|
||||
LLWString LLTextEditor::getConvertedText() const
|
||||
{
|
||||
LLWString text = getWText();
|
||||
S32 diff = 0;
|
||||
for (auto segment : mSegments)
|
||||
{
|
||||
if (segment && segment->getStyle() && segment->getStyle()->getDrawHighlightBg())
|
||||
{
|
||||
S32 seg_length = segment->getEnd() - segment->getStart();
|
||||
std::string slurl = segment->getStyle()->getLinkHREF();
|
||||
|
||||
text.replace(segment->getStart() + diff, seg_length, utf8str_to_wstring(slurl));
|
||||
diff += (S32)slurl.size() - seg_length;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,8 @@ public:
|
|||
void insertEmoji(llwchar emoji);
|
||||
void handleEmojiCommit(llwchar emoji);
|
||||
|
||||
void handleMentionCommit(std::string name_url);
|
||||
|
||||
// mousehandler overrides
|
||||
virtual bool handleMouseDown(S32 x, S32 y, MASK mask);
|
||||
virtual bool handleMouseUp(S32 x, S32 y, MASK mask);
|
||||
|
|
@ -206,11 +208,14 @@ public:
|
|||
bool getShowContextMenu() const { return mShowContextMenu; }
|
||||
|
||||
void showEmojiHelper();
|
||||
void hideEmojiHelper();
|
||||
void setShowEmojiHelper(bool show);
|
||||
bool getShowEmojiHelper() const { return mShowEmojiHelper; }
|
||||
|
||||
void setPassDelete(bool b) { mPassDelete = b; }
|
||||
|
||||
LLWString getConvertedText() const;
|
||||
|
||||
protected:
|
||||
void showContextMenu(S32 x, S32 y);
|
||||
void drawPreeditMarker();
|
||||
|
|
@ -253,6 +258,7 @@ protected:
|
|||
S32 remove(S32 pos, S32 length, bool group_with_next_op);
|
||||
|
||||
void tryToShowEmojiHelper();
|
||||
void tryToShowMentionHelper();
|
||||
void focusLostHelper();
|
||||
void updateAllowingLanguageInput();
|
||||
bool hasPreeditString() const;
|
||||
|
|
@ -290,6 +296,7 @@ protected:
|
|||
|
||||
bool mAutoIndent;
|
||||
bool mParseOnTheFly;
|
||||
bool mShowChatMentionPicker;
|
||||
|
||||
void updateLinkSegments();
|
||||
void keepSelectionOnReturn(bool keep) { mKeepSelectionOnReturn = keep; }
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@
|
|||
#include "llurlentry.h"
|
||||
#include "lluictrl.h"
|
||||
#include "lluri.h"
|
||||
#include "llurlmatch.h"
|
||||
#include "llurlregistry.h"
|
||||
#include "lluriparser.h"
|
||||
|
||||
|
|
@ -48,7 +47,7 @@
|
|||
// Utility functions
|
||||
std::string localize_slapp_label(const std::string& url, const std::string& full_name);
|
||||
|
||||
|
||||
LLUUID LLUrlEntryBase::sAgentID(LLUUID::null);
|
||||
LLUrlEntryBase::LLUrlEntryBase()
|
||||
{
|
||||
}
|
||||
|
|
@ -68,7 +67,7 @@ std::string LLUrlEntryBase::getIcon(const std::string &url)
|
|||
return mIcon;
|
||||
}
|
||||
|
||||
LLStyle::Params LLUrlEntryBase::getStyle() const
|
||||
LLStyle::Params LLUrlEntryBase::getStyle(const std::string &url) const
|
||||
{
|
||||
LLStyle::Params style_params;
|
||||
style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor");
|
||||
|
|
@ -221,6 +220,16 @@ bool LLUrlEntryBase::isWikiLinkCorrect(const std::string &labeled_url) const
|
|||
},
|
||||
L'\u002F'); // Solidus
|
||||
|
||||
std::replace_if(wlabel.begin(),
|
||||
wlabel.end(),
|
||||
[](const llwchar& chr)
|
||||
{
|
||||
return // Not a decomposition, but suficiently similar
|
||||
(chr == L'\u04BA') // "Cyrillic Capital Letter Shha"
|
||||
|| (chr == L'\u04BB'); // "Cyrillic Small Letter Shha"
|
||||
},
|
||||
L'\u0068'); // "Latin Small Letter H"
|
||||
|
||||
std::string label = wstring_to_utf8str(wlabel);
|
||||
if ((label.find(".com") != std::string::npos
|
||||
|| label.find("www.") != std::string::npos)
|
||||
|
|
@ -621,6 +630,11 @@ LLUUID LLUrlEntryAgent::getID(const std::string &string) const
|
|||
return LLUUID(getIDStringFromUrl(string));
|
||||
}
|
||||
|
||||
bool LLUrlEntryAgent::isAgentID(const std::string& url) const
|
||||
{
|
||||
return sAgentID == getID(url);
|
||||
}
|
||||
|
||||
std::string LLUrlEntryAgent::getTooltip(const std::string &string) const
|
||||
{
|
||||
// return a tooltip corresponding to the URL type instead of the generic one
|
||||
|
|
@ -657,10 +671,14 @@ std::string LLUrlEntryAgent::getTooltip(const std::string &string) const
|
|||
return LLTrans::getString("TooltipAgentUrl");
|
||||
}
|
||||
|
||||
bool LLUrlEntryAgent::underlineOnHoverOnly(const std::string &string) const
|
||||
LLStyle::EUnderlineLink LLUrlEntryAgent::getUnderline(const std::string& string) const
|
||||
{
|
||||
std::string url = getUrl(string);
|
||||
return LLStringUtil::endsWith(url, "/about") || LLStringUtil::endsWith(url, "/inspect");
|
||||
if (LLStringUtil::endsWith(url, "/about") || LLStringUtil::endsWith(url, "/inspect"))
|
||||
{
|
||||
return LLStyle::EUnderlineLink::UNDERLINE_ON_HOVER;
|
||||
}
|
||||
return LLStyle::EUnderlineLink::UNDERLINE_ALWAYS;
|
||||
}
|
||||
|
||||
std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCallback &cb)
|
||||
|
|
@ -702,11 +720,12 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa
|
|||
}
|
||||
}
|
||||
|
||||
LLStyle::Params LLUrlEntryAgent::getStyle() const
|
||||
LLStyle::Params LLUrlEntryAgent::getStyle(const std::string &url) const
|
||||
{
|
||||
LLStyle::Params style_params = LLUrlEntryBase::getStyle();
|
||||
LLStyle::Params style_params = LLUrlEntryBase::getStyle(url);
|
||||
style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor");
|
||||
style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor");
|
||||
|
||||
return style_params;
|
||||
}
|
||||
|
||||
|
|
@ -741,6 +760,10 @@ std::string localize_slapp_label(const std::string& url, const std::string& full
|
|||
{
|
||||
return LLTrans::getString("SLappAgentRemoveFriend") + " " + full_name;
|
||||
}
|
||||
if (LLStringUtil::endsWith(url, "/mention"))
|
||||
{
|
||||
return "@" + full_name;
|
||||
}
|
||||
return full_name;
|
||||
}
|
||||
|
||||
|
|
@ -752,6 +775,36 @@ std::string LLUrlEntryAgent::getIcon(const std::string &url)
|
|||
return mIcon;
|
||||
}
|
||||
|
||||
///
|
||||
/// LLUrlEntryAgentMention Describes a chat mention Url, e.g.,
|
||||
/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/mention
|
||||
///
|
||||
LLUrlEntryAgentMention::LLUrlEntryAgentMention()
|
||||
{
|
||||
mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/mention", boost::regex::perl | boost::regex::icase);
|
||||
mMenuName = "menu_url_agent.xml";
|
||||
mIcon = std::string();
|
||||
}
|
||||
|
||||
LLStyle::EUnderlineLink LLUrlEntryAgentMention::getUnderline(const std::string& string) const
|
||||
{
|
||||
return LLStyle::EUnderlineLink::UNDERLINE_NEVER;
|
||||
}
|
||||
|
||||
LLStyle::Params LLUrlEntryAgentMention::getStyle(const std::string& url) const
|
||||
{
|
||||
LLStyle::Params style_params = LLUrlEntryAgent::getStyle(url);
|
||||
style_params.color = LLUIColorTable::instance().getColor("ChatMentionFont");
|
||||
style_params.readonly_color = LLUIColorTable::instance().getColor("ChatMentionFont");
|
||||
style_params.font.style = "NORMAL";
|
||||
style_params.draw_highlight_bg = true;
|
||||
|
||||
LLUUID agent_id(getIDStringFromUrl(url));
|
||||
style_params.highlight_bg_color = LLUIColorTable::instance().getColor((agent_id == sAgentID) ? "ChatSelfMentionHighlight" : "ChatMentionHighlight");
|
||||
|
||||
return style_params;
|
||||
}
|
||||
|
||||
//
|
||||
// LLUrlEntryAgentName describes a Second Life agent name Url, e.g.,
|
||||
// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username)
|
||||
|
|
@ -813,7 +866,7 @@ std::string LLUrlEntryAgentName::getLabel(const std::string &url, const LLUrlLab
|
|||
}
|
||||
}
|
||||
|
||||
LLStyle::Params LLUrlEntryAgentName::getStyle() const
|
||||
LLStyle::Params LLUrlEntryAgentName::getStyle(const std::string &url) const
|
||||
{
|
||||
// don't override default colors
|
||||
return LLStyle::Params().is_link(false);
|
||||
|
|
@ -949,9 +1002,9 @@ std::string LLUrlEntryGroup::getLabel(const std::string &url, const LLUrlLabelCa
|
|||
}
|
||||
}
|
||||
|
||||
LLStyle::Params LLUrlEntryGroup::getStyle() const
|
||||
LLStyle::Params LLUrlEntryGroup::getStyle(const std::string &url) const
|
||||
{
|
||||
LLStyle::Params style_params = LLUrlEntryBase::getStyle();
|
||||
LLStyle::Params style_params = LLUrlEntryBase::getStyle(url);
|
||||
style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor");
|
||||
style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor");
|
||||
return style_params;
|
||||
|
|
@ -1027,7 +1080,6 @@ std::string LLUrlEntryChat::getLabel(const std::string &url, const LLUrlLabelCal
|
|||
}
|
||||
|
||||
// LLUrlEntryParcel statics.
|
||||
LLUUID LLUrlEntryParcel::sAgentID(LLUUID::null);
|
||||
LLUUID LLUrlEntryParcel::sSessionID(LLUUID::null);
|
||||
LLHost LLUrlEntryParcel::sRegionHost;
|
||||
bool LLUrlEntryParcel::sDisconnected(false);
|
||||
|
|
@ -1361,17 +1413,17 @@ std::string LLUrlEntrySLLabel::getTooltip(const std::string &string) const
|
|||
return LLUrlEntryBase::getTooltip(string);
|
||||
}
|
||||
|
||||
bool LLUrlEntrySLLabel::underlineOnHoverOnly(const std::string &string) const
|
||||
LLStyle::EUnderlineLink LLUrlEntrySLLabel::getUnderline(const std::string& string) const
|
||||
{
|
||||
std::string url = getUrl(string);
|
||||
LLUrlMatch match;
|
||||
LLUrlMatch match;
|
||||
if (LLUrlRegistry::instance().findUrl(url, match))
|
||||
{
|
||||
return match.underlineOnHoverOnly();
|
||||
return match.getUnderline();
|
||||
}
|
||||
|
||||
// unrecognized URL? should not happen
|
||||
return LLUrlEntryBase::underlineOnHoverOnly(string);
|
||||
return LLUrlEntryBase::getUnderline(string);
|
||||
}
|
||||
|
||||
//
|
||||
|
|
@ -1435,7 +1487,7 @@ std::string LLUrlEntryNoLink::getLabel(const std::string &url, const LLUrlLabelC
|
|||
return getUrl(url);
|
||||
}
|
||||
|
||||
LLStyle::Params LLUrlEntryNoLink::getStyle() const
|
||||
LLStyle::Params LLUrlEntryNoLink::getStyle(const std::string &url) const
|
||||
{
|
||||
// Don't render as URL (i.e. no context menu or hand cursor).
|
||||
return LLStyle::Params().is_link(false);
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ public:
|
|||
virtual std::string getIcon(const std::string &url);
|
||||
|
||||
/// Return the style to render the displayed text
|
||||
virtual LLStyle::Params getStyle() const;
|
||||
virtual LLStyle::Params getStyle(const std::string &url) const;
|
||||
|
||||
/// Given a matched Url, return a tooltip string for the hyperlink
|
||||
virtual std::string getTooltip(const std::string &string) const { return mTooltip; }
|
||||
|
|
@ -96,12 +96,14 @@ public:
|
|||
/// Return the name of a SL location described by this Url, if any
|
||||
virtual std::string getLocation(const std::string &url) const { return ""; }
|
||||
|
||||
/// Should this link text be underlined only when mouse is hovered over it?
|
||||
virtual bool underlineOnHoverOnly(const std::string &string) const { return false; }
|
||||
virtual LLStyle::EUnderlineLink getUnderline(const std::string& string) const { return LLStyle::EUnderlineLink::UNDERLINE_ALWAYS; }
|
||||
|
||||
virtual bool isTrusted() const { return false; }
|
||||
|
||||
virtual bool getSkipProfileIcon(const std::string& string) const { return false; }
|
||||
|
||||
virtual LLUUID getID(const std::string &string) const { return LLUUID::null; }
|
||||
virtual bool isAgentID(const std::string& url) const { return false; }
|
||||
|
||||
bool isLinkDisabled() const;
|
||||
|
||||
|
|
@ -109,6 +111,8 @@ public:
|
|||
|
||||
virtual bool isSLURLvalid(const std::string &url) const { return true; };
|
||||
|
||||
static void setAgentID(const LLUUID& id) { sAgentID = id; }
|
||||
|
||||
protected:
|
||||
std::string getIDStringFromUrl(const std::string &url) const;
|
||||
std::string escapeUrl(const std::string &url) const;
|
||||
|
|
@ -130,6 +134,8 @@ protected:
|
|||
std::string mMenuName;
|
||||
std::string mTooltip;
|
||||
std::multimap<std::string, LLUrlEntryObserver> mObservers;
|
||||
|
||||
static LLUUID sAgentID;
|
||||
};
|
||||
|
||||
///
|
||||
|
|
@ -224,9 +230,13 @@ public:
|
|||
/*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
|
||||
/*virtual*/ std::string getIcon(const std::string &url);
|
||||
/*virtual*/ std::string getTooltip(const std::string &string) const;
|
||||
/*virtual*/ LLStyle::Params getStyle() const;
|
||||
/*virtual*/ LLStyle::Params getStyle(const std::string &url) const;
|
||||
/*virtual*/ LLUUID getID(const std::string &string) const;
|
||||
/*virtual*/ bool underlineOnHoverOnly(const std::string &string) const;
|
||||
|
||||
bool isAgentID(const std::string& url) const;
|
||||
|
||||
LLStyle::EUnderlineLink getUnderline(const std::string& string) const;
|
||||
|
||||
protected:
|
||||
/*virtual*/ void callObservers(const std::string &id, const std::string &label, const std::string& icon);
|
||||
private:
|
||||
|
|
@ -236,6 +246,19 @@ private:
|
|||
avatar_name_cache_connection_map_t mAvatarNameCacheConnections;
|
||||
};
|
||||
|
||||
///
|
||||
/// LLUrlEntryAgentMention Describes a chat mention Url, e.g.,
|
||||
/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/mention
|
||||
class LLUrlEntryAgentMention : public LLUrlEntryAgent
|
||||
{
|
||||
public:
|
||||
LLUrlEntryAgentMention();
|
||||
|
||||
LLStyle::Params getStyle(const std::string& url) const;
|
||||
LLStyle::EUnderlineLink getUnderline(const std::string& string) const;
|
||||
bool getSkipProfileIcon(const std::string& string) const { return true; };
|
||||
};
|
||||
|
||||
///
|
||||
/// LLUrlEntryAgentName Describes a Second Life agent name Url, e.g.,
|
||||
/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username)
|
||||
|
|
@ -257,7 +280,7 @@ public:
|
|||
mAvatarNameCacheConnections.clear();
|
||||
}
|
||||
/*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
|
||||
/*virtual*/ LLStyle::Params getStyle() const;
|
||||
/*virtual*/ LLStyle::Params getStyle(const std::string &url) const;
|
||||
protected:
|
||||
// override this to pull out relevant name fields
|
||||
virtual std::string getName(const LLAvatarName& avatar_name) = 0;
|
||||
|
|
@ -339,7 +362,7 @@ class LLUrlEntryGroup : public LLUrlEntryBase
|
|||
public:
|
||||
LLUrlEntryGroup();
|
||||
/*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
|
||||
/*virtual*/ LLStyle::Params getStyle() const;
|
||||
/*virtual*/ LLStyle::Params getStyle(const std::string &url) const;
|
||||
/*virtual*/ LLUUID getID(const std::string &string) const;
|
||||
private:
|
||||
void onGroupNameReceived(const LLUUID& id, const std::string& name, bool is_group);
|
||||
|
|
@ -411,17 +434,15 @@ public:
|
|||
// Processes parcel label and triggers notifying observers.
|
||||
static void processParcelInfo(const LLParcelData& parcel_data);
|
||||
|
||||
// Next 4 setters are used to update agent and viewer connection information
|
||||
// Next setters are used to update agent and viewer connection information
|
||||
// upon events like user login, viewer disconnect and user changing region host.
|
||||
// These setters are made public to be accessible from newview and should not be
|
||||
// used in other cases.
|
||||
static void setAgentID(const LLUUID& id) { sAgentID = id; }
|
||||
static void setSessionID(const LLUUID& id) { sSessionID = id; }
|
||||
static void setRegionHost(const LLHost& host) { sRegionHost = host; }
|
||||
static void setDisconnected(bool disconnected) { sDisconnected = disconnected; }
|
||||
|
||||
private:
|
||||
static LLUUID sAgentID;
|
||||
static LLUUID sSessionID;
|
||||
static LLHost sRegionHost;
|
||||
static bool sDisconnected;
|
||||
|
|
@ -486,7 +507,7 @@ public:
|
|||
/*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
|
||||
/*virtual*/ std::string getUrl(const std::string &string) const;
|
||||
/*virtual*/ std::string getTooltip(const std::string &string) const;
|
||||
/*virtual*/ bool underlineOnHoverOnly(const std::string &string) const;
|
||||
LLStyle::EUnderlineLink getUnderline(const std::string& string) const;
|
||||
};
|
||||
|
||||
///
|
||||
|
|
@ -510,7 +531,7 @@ public:
|
|||
LLUrlEntryNoLink();
|
||||
/*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
|
||||
/*virtual*/ std::string getUrl(const std::string &string) const;
|
||||
/*virtual*/ LLStyle::Params getStyle() const;
|
||||
/*virtual*/ LLStyle::Params getStyle(const std::string &url) const;
|
||||
};
|
||||
|
||||
///
|
||||
|
|
|
|||
|
|
@ -37,8 +37,9 @@ LLUrlMatch::LLUrlMatch() :
|
|||
mIcon(""),
|
||||
mMenuName(""),
|
||||
mLocation(""),
|
||||
mUnderlineOnHoverOnly(false),
|
||||
mTrusted(false)
|
||||
mUnderline(e_underline::UNDERLINE_ALWAYS),
|
||||
mTrusted(false),
|
||||
mSkipProfileIcon(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -46,7 +47,7 @@ void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, const std
|
|||
const std::string& query, const std::string &tooltip,
|
||||
const std::string &icon, const LLStyle::Params& style,
|
||||
const std::string &menu, const std::string &location,
|
||||
const LLUUID& id, bool underline_on_hover_only, bool trusted)
|
||||
const LLUUID& id, e_underline underline, bool trusted, bool skip_icon)
|
||||
{
|
||||
mStart = start;
|
||||
mEnd = end;
|
||||
|
|
@ -60,6 +61,7 @@ void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, const std
|
|||
mMenuName = menu;
|
||||
mLocation = location;
|
||||
mID = id;
|
||||
mUnderlineOnHoverOnly = underline_on_hover_only;
|
||||
mUnderline = underline;
|
||||
mTrusted = trusted;
|
||||
mSkipProfileIcon = skip_icon;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,18 +79,20 @@ public:
|
|||
/// return the SL location that this Url describes, or "" if none.
|
||||
std::string getLocation() const { return mLocation; }
|
||||
|
||||
/// Should this link text be underlined only when mouse is hovered over it?
|
||||
bool underlineOnHoverOnly() const { return mUnderlineOnHoverOnly; }
|
||||
typedef LLStyle::EUnderlineLink e_underline;
|
||||
e_underline getUnderline() const { return mUnderline; }
|
||||
|
||||
/// Return true if Url is trusted.
|
||||
bool isTrusted() const { return mTrusted; }
|
||||
|
||||
bool getSkipProfileIcon() const { return mSkipProfileIcon; }
|
||||
|
||||
/// Change the contents of this match object (used by LLUrlRegistry)
|
||||
void setValues(U32 start, U32 end, const std::string &url, const std::string &label,
|
||||
const std::string& query, const std::string &tooltip, const std::string &icon,
|
||||
const LLStyle::Params& style, const std::string &menu,
|
||||
const std::string &location, const LLUUID& id,
|
||||
bool underline_on_hover_only = false, bool trusted = false);
|
||||
e_underline underline = e_underline::UNDERLINE_ALWAYS, bool trusted = false, bool skip_icon = false);
|
||||
|
||||
const LLUUID& getID() const { return mID; }
|
||||
private:
|
||||
|
|
@ -105,8 +107,9 @@ private:
|
|||
std::string mLocation;
|
||||
LLUUID mID;
|
||||
LLStyle::Params mStyle;
|
||||
bool mUnderlineOnHoverOnly;
|
||||
e_underline mUnderline;
|
||||
bool mTrusted;
|
||||
bool mSkipProfileIcon;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ LLUrlRegistry::LLUrlRegistry()
|
|||
registerUrl(new LLUrlEntryAgentUserName());
|
||||
// LLUrlEntryAgent*Name must appear before LLUrlEntryAgent since
|
||||
// LLUrlEntryAgent is a less specific (catchall for agent urls)
|
||||
mUrlEntryAgentMention = new LLUrlEntryAgentMention();
|
||||
registerUrl(mUrlEntryAgentMention);
|
||||
registerUrl(new LLUrlEntryAgent());
|
||||
registerUrl(new LLUrlEntryChat());
|
||||
registerUrl(new LLUrlEntryGroup());
|
||||
|
|
@ -155,7 +157,7 @@ static bool stringHasUrl(const std::string &text)
|
|||
text.find("@") != std::string::npos);
|
||||
}
|
||||
|
||||
bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LLUrlLabelCallback &cb, bool is_content_trusted)
|
||||
bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LLUrlLabelCallback &cb, bool is_content_trusted, bool skip_non_mentions)
|
||||
{
|
||||
// avoid costly regexes if there is clearly no URL in the text
|
||||
if (! stringHasUrl(text))
|
||||
|
|
@ -176,6 +178,11 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL
|
|||
continue;
|
||||
}
|
||||
|
||||
if (skip_non_mentions && (mUrlEntryAgentMention != *it))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
LLUrlEntryBase *url_entry = *it;
|
||||
|
||||
U32 start = 0, end = 0;
|
||||
|
|
@ -233,12 +240,13 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL
|
|||
match_entry->getQuery(url),
|
||||
match_entry->getTooltip(url),
|
||||
match_entry->getIcon(url),
|
||||
match_entry->getStyle(),
|
||||
match_entry->getStyle(url),
|
||||
match_entry->getMenuName(),
|
||||
match_entry->getLocation(url),
|
||||
match_entry->getID(url),
|
||||
match_entry->underlineOnHoverOnly(url),
|
||||
match_entry->isTrusted());
|
||||
match_entry->getUnderline(url),
|
||||
match_entry->isTrusted(),
|
||||
match_entry->getSkipProfileIcon(url));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -274,7 +282,9 @@ bool LLUrlRegistry::findUrl(const LLWString &text, LLUrlMatch &match, const LLUr
|
|||
match.getMenuName(),
|
||||
match.getLocation(),
|
||||
match.getID(),
|
||||
match.underlineOnHoverOnly());
|
||||
match.getUnderline(),
|
||||
false,
|
||||
match.getSkipProfileIcon());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -317,3 +327,30 @@ void LLUrlRegistry::setKeybindingHandler(LLKeyBindingToStringHandler* handler)
|
|||
LLUrlEntryKeybinding *entry = (LLUrlEntryKeybinding*)mUrlEntryKeybinding;
|
||||
entry->setHandler(handler);
|
||||
}
|
||||
|
||||
bool LLUrlRegistry::containsAgentMention(const std::string& text)
|
||||
{
|
||||
// avoid costly regexes if there is clearly no URL in the text
|
||||
if (!stringHasUrl(text))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
boost::sregex_iterator it(text.begin(), text.end(), mUrlEntryAgentMention->getPattern());
|
||||
boost::sregex_iterator end;
|
||||
for (; it != end; ++it)
|
||||
{
|
||||
if (mUrlEntryAgentMention->isAgentID(it->str()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (boost::regex_error&)
|
||||
{
|
||||
LL_INFOS() << "Regex error for: " << text << LL_ENDL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ public:
|
|||
/// your callback is invoked if the matched Url's label changes in the future
|
||||
bool findUrl(const std::string &text, LLUrlMatch &match,
|
||||
const LLUrlLabelCallback &cb = &LLUrlRegistryNullCallback,
|
||||
bool is_content_trusted = false);
|
||||
bool is_content_trusted = false, bool skip_non_mentions = false);
|
||||
|
||||
/// a slightly less efficient version of findUrl for wide strings
|
||||
bool findUrl(const LLWString &text, LLUrlMatch &match,
|
||||
|
|
@ -92,6 +92,8 @@ public:
|
|||
// Set handler for url registry to be capable of parsing and populating keybindings
|
||||
void setKeybindingHandler(LLKeyBindingToStringHandler* handler);
|
||||
|
||||
bool containsAgentMention(const std::string& text);
|
||||
|
||||
private:
|
||||
std::vector<LLUrlEntryBase *> mUrlEntry;
|
||||
LLUrlEntryBase* mUrlEntryTrusted;
|
||||
|
|
@ -101,6 +103,7 @@ private:
|
|||
LLUrlEntryBase* mUrlEntrySLLabel;
|
||||
LLUrlEntryBase* mUrlEntryNoLink;
|
||||
LLUrlEntryBase* mUrlEntryKeybinding;
|
||||
LLUrlEntryBase* mUrlEntryAgentMention;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -430,9 +430,7 @@ void ll_set_device_module_capture_device(rtc::scoped_refptr<webrtc::AudioDeviceM
|
|||
// has it at 0
|
||||
device_module->SetRecordingDevice(device + 1);
|
||||
#endif
|
||||
device_module->SetStereoRecording(false);
|
||||
device_module->InitMicrophone();
|
||||
device_module->InitRecording();
|
||||
}
|
||||
|
||||
void LLWebRTCImpl::setCaptureDevice(const std::string &id)
|
||||
|
|
@ -473,6 +471,8 @@ void LLWebRTCImpl::setCaptureDevice(const std::string &id)
|
|||
ll_set_device_module_capture_device(mPeerDeviceModule, recordingDevice);
|
||||
if (recording)
|
||||
{
|
||||
mPeerDeviceModule->SetStereoRecording(false);
|
||||
mPeerDeviceModule->InitRecording();
|
||||
mPeerDeviceModule->StartRecording();
|
||||
}
|
||||
});
|
||||
|
|
@ -494,9 +494,7 @@ void ll_set_device_module_render_device(rtc::scoped_refptr<webrtc::AudioDeviceMo
|
|||
#else
|
||||
device_module->SetPlayoutDevice(device + 1);
|
||||
#endif
|
||||
device_module->SetStereoPlayout(true);
|
||||
device_module->InitSpeaker();
|
||||
device_module->InitPlayout();
|
||||
}
|
||||
|
||||
void LLWebRTCImpl::setRenderDevice(const std::string &id)
|
||||
|
|
@ -540,6 +538,8 @@ void LLWebRTCImpl::setRenderDevice(const std::string &id)
|
|||
ll_set_device_module_render_device(mPeerDeviceModule, playoutDevice);
|
||||
if (playing)
|
||||
{
|
||||
mPeerDeviceModule->SetStereoPlayout(true);
|
||||
mPeerDeviceModule->InitPlayout();
|
||||
mPeerDeviceModule->StartPlayout();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -182,7 +182,6 @@ endif (SDL_FOUND)
|
|||
target_include_directories(llwindow INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
if (DARWIN)
|
||||
include(CMakeFindFrameworks)
|
||||
find_library(CARBON_LIBRARY Carbon)
|
||||
target_link_libraries(llwindow ${CARBON_LIBRARY})
|
||||
endif (DARWIN)
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@
|
|||
#include "llstl.h"
|
||||
#include "lltimer.h"
|
||||
|
||||
void (*gWriteDebug)(const char* msg) = NULL;
|
||||
LLDXHardware gDXHardware;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
@ -61,170 +60,6 @@ typedef BOOL ( WINAPI* PfnCoSetProxyBlanket )( IUnknown* pProxy, DWORD dwAuthnSv
|
|||
OLECHAR* pServerPrincName, DWORD dwAuthnLevel, DWORD dwImpLevel,
|
||||
RPC_AUTH_IDENTITY_HANDLE pAuthInfo, DWORD dwCapabilities );
|
||||
|
||||
HRESULT GetVideoMemoryViaWMI(WCHAR* strInputDeviceID, DWORD* pdwAdapterRam)
|
||||
{
|
||||
HRESULT hr;
|
||||
bool bGotMemory = false;
|
||||
IWbemLocator* pIWbemLocator = nullptr;
|
||||
IWbemServices* pIWbemServices = nullptr;
|
||||
BSTR pNamespace = nullptr;
|
||||
|
||||
*pdwAdapterRam = 0;
|
||||
CoInitializeEx(0, COINIT_APARTMENTTHREADED);
|
||||
|
||||
hr = CoCreateInstance( CLSID_WbemLocator,
|
||||
nullptr,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
IID_IWbemLocator,
|
||||
( LPVOID* )&pIWbemLocator );
|
||||
#ifdef PRINTF_DEBUGGING
|
||||
if( FAILED( hr ) ) wprintf( L"WMI: CoCreateInstance failed: 0x%0.8x\n", hr );
|
||||
#endif
|
||||
|
||||
if( SUCCEEDED( hr ) && pIWbemLocator )
|
||||
{
|
||||
// Using the locator, connect to WMI in the given namespace.
|
||||
pNamespace = SysAllocString( L"\\\\.\\root\\cimv2" );
|
||||
|
||||
hr = pIWbemLocator->ConnectServer( pNamespace, nullptr, nullptr, 0L,
|
||||
0L, nullptr, nullptr, &pIWbemServices );
|
||||
#ifdef PRINTF_DEBUGGING
|
||||
if( FAILED( hr ) ) wprintf( L"WMI: pIWbemLocator->ConnectServer failed: 0x%0.8x\n", hr );
|
||||
#endif
|
||||
if( SUCCEEDED( hr ) && pIWbemServices != 0 )
|
||||
{
|
||||
HINSTANCE hinstOle32 = nullptr;
|
||||
|
||||
hinstOle32 = LoadLibraryW( L"ole32.dll" );
|
||||
if( hinstOle32 )
|
||||
{
|
||||
PfnCoSetProxyBlanket pfnCoSetProxyBlanket = nullptr;
|
||||
|
||||
pfnCoSetProxyBlanket = ( PfnCoSetProxyBlanket )GetProcAddress( hinstOle32, "CoSetProxyBlanket" );
|
||||
if( pfnCoSetProxyBlanket != 0 )
|
||||
{
|
||||
// Switch security level to IMPERSONATE.
|
||||
pfnCoSetProxyBlanket( pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr,
|
||||
RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, 0 );
|
||||
}
|
||||
|
||||
FreeLibrary( hinstOle32 );
|
||||
}
|
||||
|
||||
IEnumWbemClassObject* pEnumVideoControllers = nullptr;
|
||||
BSTR pClassName = nullptr;
|
||||
|
||||
pClassName = SysAllocString( L"Win32_VideoController" );
|
||||
|
||||
hr = pIWbemServices->CreateInstanceEnum( pClassName, 0,
|
||||
nullptr, &pEnumVideoControllers );
|
||||
#ifdef PRINTF_DEBUGGING
|
||||
if( FAILED( hr ) ) wprintf( L"WMI: pIWbemServices->CreateInstanceEnum failed: 0x%0.8x\n", hr );
|
||||
#endif
|
||||
|
||||
if( SUCCEEDED( hr ) && pEnumVideoControllers )
|
||||
{
|
||||
IWbemClassObject* pVideoControllers[10] = {0};
|
||||
DWORD uReturned = 0;
|
||||
BSTR pPropName = nullptr;
|
||||
|
||||
// Get the first one in the list
|
||||
pEnumVideoControllers->Reset();
|
||||
hr = pEnumVideoControllers->Next( 5000, // timeout in 5 seconds
|
||||
10, // return the first 10
|
||||
pVideoControllers,
|
||||
&uReturned );
|
||||
#ifdef PRINTF_DEBUGGING
|
||||
if( FAILED( hr ) ) wprintf( L"WMI: pEnumVideoControllers->Next failed: 0x%0.8x\n", hr );
|
||||
if( uReturned == 0 ) wprintf( L"WMI: pEnumVideoControllers uReturned == 0\n" );
|
||||
#endif
|
||||
|
||||
VARIANT var;
|
||||
if( SUCCEEDED( hr ) )
|
||||
{
|
||||
bool bFound = false;
|
||||
for( UINT iController = 0; iController < uReturned; iController++ )
|
||||
{
|
||||
if ( !pVideoControllers[iController] )
|
||||
continue;
|
||||
|
||||
// if strInputDeviceID is set find this specific device and return memory or specific device
|
||||
// if strInputDeviceID is not set return the best device
|
||||
if (strInputDeviceID)
|
||||
{
|
||||
pPropName = SysAllocString( L"PNPDeviceID" );
|
||||
hr = pVideoControllers[iController]->Get( pPropName, 0L, &var, nullptr, nullptr );
|
||||
#ifdef PRINTF_DEBUGGING
|
||||
if( FAILED( hr ) )
|
||||
wprintf( L"WMI: pVideoControllers[iController]->Get PNPDeviceID failed: 0x%0.8x\n", hr );
|
||||
#endif
|
||||
if( SUCCEEDED( hr ) && strInputDeviceID)
|
||||
{
|
||||
if( wcsstr( var.bstrVal, strInputDeviceID ) != 0 )
|
||||
bFound = true;
|
||||
}
|
||||
VariantClear( &var );
|
||||
if( pPropName ) SysFreeString( pPropName );
|
||||
}
|
||||
|
||||
if( bFound || !strInputDeviceID )
|
||||
{
|
||||
pPropName = SysAllocString( L"AdapterRAM" );
|
||||
hr = pVideoControllers[iController]->Get( pPropName, 0L, &var, nullptr, nullptr );
|
||||
#ifdef PRINTF_DEBUGGING
|
||||
if( FAILED( hr ) )
|
||||
wprintf( L"WMI: pVideoControllers[iController]->Get AdapterRAM failed: 0x%0.8x\n",
|
||||
hr );
|
||||
#endif
|
||||
if( SUCCEEDED( hr ) )
|
||||
{
|
||||
bGotMemory = true;
|
||||
*pdwAdapterRam = llmax(var.ulVal, *pdwAdapterRam);
|
||||
}
|
||||
VariantClear( &var );
|
||||
if( pPropName ) SysFreeString( pPropName );
|
||||
}
|
||||
|
||||
SAFE_RELEASE( pVideoControllers[iController] );
|
||||
|
||||
if (bFound)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( pClassName )
|
||||
SysFreeString( pClassName );
|
||||
SAFE_RELEASE( pEnumVideoControllers );
|
||||
}
|
||||
|
||||
if( pNamespace )
|
||||
SysFreeString( pNamespace );
|
||||
SAFE_RELEASE( pIWbemServices );
|
||||
}
|
||||
|
||||
SAFE_RELEASE( pIWbemLocator );
|
||||
|
||||
CoUninitialize();
|
||||
|
||||
if( bGotMemory )
|
||||
return S_OK;
|
||||
else
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
//static
|
||||
U32 LLDXHardware::getMBVideoMemoryViaWMI()
|
||||
{
|
||||
DWORD vram = 0;
|
||||
if (SUCCEEDED(GetVideoMemoryViaWMI(NULL, &vram)))
|
||||
{
|
||||
return vram / (1024 * 1024);;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//Getting the version of graphics controller driver via WMI
|
||||
std::string LLDXHardware::getDriverVersionWMI(EGPUVendor vendor)
|
||||
|
|
@ -480,495 +315,14 @@ std::string get_string(IDxDiagContainer *containerp, const WCHAR *wszPropName)
|
|||
return utf16str_to_utf8str(wszPropValue);
|
||||
}
|
||||
|
||||
|
||||
LLVersion::LLVersion()
|
||||
{
|
||||
mValid = false;
|
||||
S32 i;
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
mFields[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool LLVersion::set(const std::string &version_string)
|
||||
{
|
||||
S32 i;
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
mFields[i] = 0;
|
||||
}
|
||||
// Split the version string.
|
||||
std::string str(version_string);
|
||||
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
|
||||
boost::char_separator<char> sep(".", "", boost::keep_empty_tokens);
|
||||
tokenizer tokens(str, sep);
|
||||
|
||||
tokenizer::iterator iter = tokens.begin();
|
||||
S32 count = 0;
|
||||
for (;(iter != tokens.end()) && (count < 4);++iter)
|
||||
{
|
||||
mFields[count] = atoi(iter->c_str());
|
||||
count++;
|
||||
}
|
||||
if (count < 4)
|
||||
{
|
||||
//LL_WARNS() << "Potentially bogus version string!" << version_string << LL_ENDL;
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
mFields[i] = 0;
|
||||
}
|
||||
mValid = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
mValid = true;
|
||||
}
|
||||
return mValid;
|
||||
}
|
||||
|
||||
S32 LLVersion::getField(const S32 field_num)
|
||||
{
|
||||
if (!mValid)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return mFields[field_num];
|
||||
}
|
||||
}
|
||||
|
||||
std::string LLDXDriverFile::dump()
|
||||
{
|
||||
if (gWriteDebug)
|
||||
{
|
||||
gWriteDebug("Filename:");
|
||||
gWriteDebug(mName.c_str());
|
||||
gWriteDebug("\n");
|
||||
gWriteDebug("Ver:");
|
||||
gWriteDebug(mVersionString.c_str());
|
||||
gWriteDebug("\n");
|
||||
gWriteDebug("Date:");
|
||||
gWriteDebug(mDateString.c_str());
|
||||
gWriteDebug("\n");
|
||||
}
|
||||
LL_INFOS() << mFilepath << LL_ENDL;
|
||||
LL_INFOS() << mName << LL_ENDL;
|
||||
LL_INFOS() << mVersionString << LL_ENDL;
|
||||
LL_INFOS() << mDateString << LL_ENDL;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
LLDXDevice::~LLDXDevice()
|
||||
{
|
||||
for_each(mDriverFiles.begin(), mDriverFiles.end(), DeletePairedPointer());
|
||||
mDriverFiles.clear();
|
||||
}
|
||||
|
||||
std::string LLDXDevice::dump()
|
||||
{
|
||||
if (gWriteDebug)
|
||||
{
|
||||
gWriteDebug("StartDevice\n");
|
||||
gWriteDebug("DeviceName:");
|
||||
gWriteDebug(mName.c_str());
|
||||
gWriteDebug("\n");
|
||||
gWriteDebug("PCIString:");
|
||||
gWriteDebug(mPCIString.c_str());
|
||||
gWriteDebug("\n");
|
||||
}
|
||||
LL_INFOS() << LL_ENDL;
|
||||
LL_INFOS() << "DeviceName:" << mName << LL_ENDL;
|
||||
LL_INFOS() << "PCIString:" << mPCIString << LL_ENDL;
|
||||
LL_INFOS() << "Drivers" << LL_ENDL;
|
||||
LL_INFOS() << "-------" << LL_ENDL;
|
||||
for (driver_file_map_t::iterator iter = mDriverFiles.begin(),
|
||||
end = mDriverFiles.end();
|
||||
iter != end; iter++)
|
||||
{
|
||||
LLDXDriverFile *filep = iter->second;
|
||||
filep->dump();
|
||||
}
|
||||
if (gWriteDebug)
|
||||
{
|
||||
gWriteDebug("EndDevice\n");
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
LLDXDriverFile *LLDXDevice::findDriver(const std::string &driver)
|
||||
{
|
||||
for (driver_file_map_t::iterator iter = mDriverFiles.begin(),
|
||||
end = mDriverFiles.end();
|
||||
iter != end; iter++)
|
||||
{
|
||||
LLDXDriverFile *filep = iter->second;
|
||||
if (!utf8str_compare_insensitive(filep->mName,driver))
|
||||
{
|
||||
return filep;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LLDXHardware::LLDXHardware()
|
||||
{
|
||||
mVRAM = 0;
|
||||
gWriteDebug = NULL;
|
||||
}
|
||||
|
||||
void LLDXHardware::cleanup()
|
||||
{
|
||||
// for_each(mDevices.begin(), mDevices.end(), DeletePairedPointer());
|
||||
// mDevices.clear();
|
||||
}
|
||||
|
||||
/*
|
||||
std::string LLDXHardware::dumpDevices()
|
||||
{
|
||||
if (gWriteDebug)
|
||||
{
|
||||
gWriteDebug("\n");
|
||||
gWriteDebug("StartAllDevices\n");
|
||||
}
|
||||
for (device_map_t::iterator iter = mDevices.begin(),
|
||||
end = mDevices.end();
|
||||
iter != end; iter++)
|
||||
{
|
||||
LLDXDevice *devicep = iter->second;
|
||||
devicep->dump();
|
||||
}
|
||||
if (gWriteDebug)
|
||||
{
|
||||
gWriteDebug("EndAllDevices\n\n");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
LLDXDevice *LLDXHardware::findDevice(const std::string &vendor, const std::string &devices)
|
||||
{
|
||||
// Iterate through different devices tokenized in devices string
|
||||
std::string str(devices);
|
||||
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
|
||||
boost::char_separator<char> sep("|", "", boost::keep_empty_tokens);
|
||||
tokenizer tokens(str, sep);
|
||||
|
||||
tokenizer::iterator iter = tokens.begin();
|
||||
for (;iter != tokens.end();++iter)
|
||||
{
|
||||
std::string dev_str = *iter;
|
||||
for (device_map_t::iterator iter = mDevices.begin(),
|
||||
end = mDevices.end();
|
||||
iter != end; iter++)
|
||||
{
|
||||
LLDXDevice *devicep = iter->second;
|
||||
if ((devicep->mVendorID == vendor)
|
||||
&& (devicep->mDeviceID == dev_str))
|
||||
{
|
||||
return devicep;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
*/
|
||||
|
||||
bool LLDXHardware::getInfo(bool vram_only)
|
||||
{
|
||||
LLTimer hw_timer;
|
||||
bool ok = false;
|
||||
HRESULT hr;
|
||||
|
||||
// CLSID_DxDiagProvider does not work with Multithreaded?
|
||||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
|
||||
IDxDiagProvider *dx_diag_providerp = NULL;
|
||||
IDxDiagContainer *dx_diag_rootp = NULL;
|
||||
IDxDiagContainer *devices_containerp = NULL;
|
||||
// IDxDiagContainer *system_device_containerp= NULL;
|
||||
IDxDiagContainer *device_containerp = NULL;
|
||||
IDxDiagContainer *file_containerp = NULL;
|
||||
IDxDiagContainer *driver_containerp = NULL;
|
||||
DWORD dw_device_count;
|
||||
|
||||
mVRAM = 0;
|
||||
|
||||
// CoCreate a IDxDiagProvider*
|
||||
LL_DEBUGS("AppInit") << "CoCreateInstance IID_IDxDiagProvider" << LL_ENDL;
|
||||
hr = CoCreateInstance(CLSID_DxDiagProvider,
|
||||
NULL,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
IID_IDxDiagProvider,
|
||||
(LPVOID*) &dx_diag_providerp);
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LL_WARNS("AppInit") << "No DXDiag provider found! DirectX 9 not installed!" << LL_ENDL;
|
||||
gWriteDebug("No DXDiag provider found! DirectX 9 not installed!\n");
|
||||
goto LCleanup;
|
||||
}
|
||||
if (SUCCEEDED(hr)) // if FAILED(hr) then dx9 is not installed
|
||||
{
|
||||
// Fill out a DXDIAG_INIT_PARAMS struct and pass it to IDxDiagContainer::Initialize
|
||||
// Passing in TRUE for bAllowWHQLChecks, allows dxdiag to check if drivers are
|
||||
// digital signed as logo'd by WHQL which may connect via internet to update
|
||||
// WHQL certificates.
|
||||
DXDIAG_INIT_PARAMS dx_diag_init_params;
|
||||
ZeroMemory(&dx_diag_init_params, sizeof(DXDIAG_INIT_PARAMS));
|
||||
|
||||
dx_diag_init_params.dwSize = sizeof(DXDIAG_INIT_PARAMS);
|
||||
dx_diag_init_params.dwDxDiagHeaderVersion = DXDIAG_DX9_SDK_VERSION;
|
||||
dx_diag_init_params.bAllowWHQLChecks = TRUE;
|
||||
dx_diag_init_params.pReserved = NULL;
|
||||
|
||||
LL_DEBUGS("AppInit") << "dx_diag_providerp->Initialize" << LL_ENDL;
|
||||
hr = dx_diag_providerp->Initialize(&dx_diag_init_params);
|
||||
if(FAILED(hr))
|
||||
{
|
||||
goto LCleanup;
|
||||
}
|
||||
|
||||
LL_DEBUGS("AppInit") << "dx_diag_providerp->GetRootContainer" << LL_ENDL;
|
||||
hr = dx_diag_providerp->GetRootContainer( &dx_diag_rootp );
|
||||
if(FAILED(hr) || !dx_diag_rootp)
|
||||
{
|
||||
goto LCleanup;
|
||||
}
|
||||
|
||||
HRESULT hr;
|
||||
|
||||
// Get display driver information
|
||||
LL_DEBUGS("AppInit") << "dx_diag_rootp->GetChildContainer" << LL_ENDL;
|
||||
hr = dx_diag_rootp->GetChildContainer(L"DxDiag_DisplayDevices", &devices_containerp);
|
||||
if(FAILED(hr) || !devices_containerp)
|
||||
{
|
||||
// do not release 'dirty' devices_containerp at this stage, only dx_diag_rootp
|
||||
devices_containerp = NULL;
|
||||
goto LCleanup;
|
||||
}
|
||||
|
||||
// make sure there is something inside
|
||||
hr = devices_containerp->GetNumberOfChildContainers(&dw_device_count);
|
||||
if (FAILED(hr) || dw_device_count == 0)
|
||||
{
|
||||
goto LCleanup;
|
||||
}
|
||||
|
||||
// Get device 0
|
||||
// By default 0 device is the primary one, howhever in case of various hybrid graphics
|
||||
// like itegrated AMD and PCI AMD GPUs system might switch.
|
||||
LL_DEBUGS("AppInit") << "devices_containerp->GetChildContainer" << LL_ENDL;
|
||||
hr = devices_containerp->GetChildContainer(L"0", &device_containerp);
|
||||
if(FAILED(hr) || !device_containerp)
|
||||
{
|
||||
goto LCleanup;
|
||||
}
|
||||
|
||||
DWORD vram = 0;
|
||||
|
||||
WCHAR deviceID[512];
|
||||
|
||||
get_wstring(device_containerp, L"szDeviceID", deviceID, 512);
|
||||
// Example: searches id like 1F06 in pnp string (aka VEN_10DE&DEV_1F06)
|
||||
// doesn't seem to work on some systems since format is unrecognizable
|
||||
// but in such case keyDeviceID works
|
||||
if (SUCCEEDED(GetVideoMemoryViaWMI(deviceID, &vram)))
|
||||
{
|
||||
mVRAM = vram/(1024*1024);
|
||||
}
|
||||
else
|
||||
{
|
||||
get_wstring(device_containerp, L"szKeyDeviceID", deviceID, 512);
|
||||
LL_WARNS() << "szDeviceID" << deviceID << LL_ENDL;
|
||||
// '+9' to avoid ENUM\\PCI\\ prefix
|
||||
// Returns string like Enum\\PCI\\VEN_10DE&DEV_1F06&SUBSYS...
|
||||
// and since GetVideoMemoryViaWMI searches by PNPDeviceID it is sufficient
|
||||
if (SUCCEEDED(GetVideoMemoryViaWMI(deviceID + 9, &vram)))
|
||||
{
|
||||
mVRAM = vram / (1024 * 1024);
|
||||
}
|
||||
}
|
||||
|
||||
if (mVRAM == 0)
|
||||
{ // Get the English VRAM string
|
||||
std::string ram_str = get_string(device_containerp, L"szDisplayMemoryEnglish");
|
||||
|
||||
// We don't need the device any more
|
||||
SAFE_RELEASE(device_containerp);
|
||||
|
||||
// Dump the string as an int into the structure
|
||||
char *stopstring;
|
||||
mVRAM = strtol(ram_str.c_str(), &stopstring, 10);
|
||||
LL_INFOS("AppInit") << "VRAM Detected: " << mVRAM << " DX9 string: " << ram_str << LL_ENDL;
|
||||
}
|
||||
|
||||
if (vram_only)
|
||||
{
|
||||
ok = true;
|
||||
goto LCleanup;
|
||||
}
|
||||
|
||||
|
||||
/* for now, we ONLY do vram_only the rest of this
|
||||
is commented out, to ensure no-one is tempted
|
||||
to use it
|
||||
|
||||
// Now let's get device and driver information
|
||||
// Get the IDxDiagContainer object called "DxDiag_SystemDevices".
|
||||
// This call may take some time while dxdiag gathers the info.
|
||||
DWORD num_devices = 0;
|
||||
WCHAR wszContainer[256];
|
||||
LL_DEBUGS("AppInit") << "dx_diag_rootp->GetChildContainer DxDiag_SystemDevices" << LL_ENDL;
|
||||
hr = dx_diag_rootp->GetChildContainer(L"DxDiag_SystemDevices", &system_device_containerp);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
goto LCleanup;
|
||||
}
|
||||
|
||||
hr = system_device_containerp->GetNumberOfChildContainers(&num_devices);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
goto LCleanup;
|
||||
}
|
||||
|
||||
LL_DEBUGS("AppInit") << "DX9 iterating over devices" << LL_ENDL;
|
||||
S32 device_num = 0;
|
||||
for (device_num = 0; device_num < (S32)num_devices; device_num++)
|
||||
{
|
||||
hr = system_device_containerp->EnumChildContainerNames(device_num, wszContainer, 256);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
goto LCleanup;
|
||||
}
|
||||
|
||||
hr = system_device_containerp->GetChildContainer(wszContainer, &device_containerp);
|
||||
if (FAILED(hr) || device_containerp == NULL)
|
||||
{
|
||||
goto LCleanup;
|
||||
}
|
||||
|
||||
std::string device_name = get_string(device_containerp, L"szDescription");
|
||||
|
||||
std::string device_id = get_string(device_containerp, L"szDeviceID");
|
||||
|
||||
LLDXDevice *dxdevicep = new LLDXDevice;
|
||||
dxdevicep->mName = device_name;
|
||||
dxdevicep->mPCIString = device_id;
|
||||
mDevices[dxdevicep->mPCIString] = dxdevicep;
|
||||
|
||||
// Split the PCI string based on vendor, device, subsys, rev.
|
||||
std::string str(device_id);
|
||||
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
|
||||
boost::char_separator<char> sep("&\\", "", boost::keep_empty_tokens);
|
||||
tokenizer tokens(str, sep);
|
||||
|
||||
tokenizer::iterator iter = tokens.begin();
|
||||
S32 count = 0;
|
||||
bool valid = true;
|
||||
for (;(iter != tokens.end()) && (count < 3);++iter)
|
||||
{
|
||||
switch (count)
|
||||
{
|
||||
case 0:
|
||||
if (strcmp(iter->c_str(), "PCI"))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
dxdevicep->mVendorID = iter->c_str();
|
||||
break;
|
||||
case 2:
|
||||
dxdevicep->mDeviceID = iter->c_str();
|
||||
break;
|
||||
default:
|
||||
// Ignore it
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Now, iterate through the related drivers
|
||||
hr = device_containerp->GetChildContainer(L"Drivers", &driver_containerp);
|
||||
if (FAILED(hr) || !driver_containerp)
|
||||
{
|
||||
goto LCleanup;
|
||||
}
|
||||
|
||||
DWORD num_files = 0;
|
||||
hr = driver_containerp->GetNumberOfChildContainers(&num_files);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
goto LCleanup;
|
||||
}
|
||||
|
||||
S32 file_num = 0;
|
||||
for (file_num = 0; file_num < (S32)num_files; file_num++ )
|
||||
{
|
||||
|
||||
hr = driver_containerp->EnumChildContainerNames(file_num, wszContainer, 256);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
goto LCleanup;
|
||||
}
|
||||
|
||||
hr = driver_containerp->GetChildContainer(wszContainer, &file_containerp);
|
||||
if (FAILED(hr) || file_containerp == NULL)
|
||||
{
|
||||
goto LCleanup;
|
||||
}
|
||||
|
||||
std::string driver_path = get_string(file_containerp, L"szPath");
|
||||
std::string driver_name = get_string(file_containerp, L"szName");
|
||||
std::string driver_version = get_string(file_containerp, L"szVersion");
|
||||
std::string driver_date = get_string(file_containerp, L"szDatestampEnglish");
|
||||
|
||||
LLDXDriverFile *dxdriverfilep = new LLDXDriverFile;
|
||||
dxdriverfilep->mName = driver_name;
|
||||
dxdriverfilep->mFilepath= driver_path;
|
||||
dxdriverfilep->mVersionString = driver_version;
|
||||
dxdriverfilep->mVersion.set(driver_version);
|
||||
dxdriverfilep->mDateString = driver_date;
|
||||
|
||||
dxdevicep->mDriverFiles[driver_name] = dxdriverfilep;
|
||||
|
||||
SAFE_RELEASE(file_containerp);
|
||||
}
|
||||
SAFE_RELEASE(device_containerp);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// dumpDevices();
|
||||
ok = true;
|
||||
|
||||
LCleanup:
|
||||
if (!ok)
|
||||
{
|
||||
LL_WARNS("AppInit") << "DX9 probe failed" << LL_ENDL;
|
||||
gWriteDebug("DX9 probe failed\n");
|
||||
}
|
||||
|
||||
SAFE_RELEASE(file_containerp);
|
||||
SAFE_RELEASE(driver_containerp);
|
||||
SAFE_RELEASE(device_containerp);
|
||||
SAFE_RELEASE(devices_containerp);
|
||||
SAFE_RELEASE(dx_diag_rootp);
|
||||
SAFE_RELEASE(dx_diag_providerp);
|
||||
|
||||
CoUninitialize();
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
LLSD LLDXHardware::getDisplayInfo()
|
||||
{
|
||||
LLTimer hw_timer;
|
||||
|
|
@ -995,7 +349,6 @@ LLSD LLDXHardware::getDisplayInfo()
|
|||
if (FAILED(hr))
|
||||
{
|
||||
LL_WARNS() << "No DXDiag provider found! DirectX 9 not installed!" << LL_ENDL;
|
||||
gWriteDebug("No DXDiag provider found! DirectX 9 not installed!\n");
|
||||
goto LCleanup;
|
||||
}
|
||||
if (SUCCEEDED(hr)) // if FAILED(hr) then dx9 is not installed
|
||||
|
|
@ -1111,9 +464,4 @@ LCleanup:
|
|||
return ret;
|
||||
}
|
||||
|
||||
void LLDXHardware::setWriteDebugFunc(void (*func)(const char*))
|
||||
{
|
||||
gWriteDebug = func;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -30,64 +30,16 @@
|
|||
#include <map>
|
||||
|
||||
#include "stdtypes.h"
|
||||
#include "llstring.h"
|
||||
#include "llsd.h"
|
||||
|
||||
class LLVersion
|
||||
{
|
||||
public:
|
||||
LLVersion();
|
||||
bool set(const std::string &version_string);
|
||||
S32 getField(const S32 field_num);
|
||||
protected:
|
||||
std::string mVersionString;
|
||||
S32 mFields[4];
|
||||
bool mValid;
|
||||
};
|
||||
|
||||
class LLDXDriverFile
|
||||
{
|
||||
public:
|
||||
std::string dump();
|
||||
|
||||
public:
|
||||
std::string mFilepath;
|
||||
std::string mName;
|
||||
std::string mVersionString;
|
||||
LLVersion mVersion;
|
||||
std::string mDateString;
|
||||
};
|
||||
|
||||
class LLDXDevice
|
||||
{
|
||||
public:
|
||||
~LLDXDevice();
|
||||
std::string dump();
|
||||
|
||||
LLDXDriverFile *findDriver(const std::string &driver);
|
||||
public:
|
||||
std::string mName;
|
||||
std::string mPCIString;
|
||||
std::string mVendorID;
|
||||
std::string mDeviceID;
|
||||
|
||||
typedef std::map<std::string, LLDXDriverFile *> driver_file_map_t;
|
||||
driver_file_map_t mDriverFiles;
|
||||
};
|
||||
|
||||
|
||||
class LLDXHardware
|
||||
{
|
||||
public:
|
||||
LLDXHardware();
|
||||
|
||||
void setWriteDebugFunc(void (*func)(const char*));
|
||||
void cleanup();
|
||||
|
||||
// Returns true on success.
|
||||
// vram_only true does a "light" probe.
|
||||
bool getInfo(bool vram_only);
|
||||
|
||||
// WMI can return multiple GPU drivers
|
||||
// specify which one to output
|
||||
typedef enum {
|
||||
|
|
@ -98,29 +50,9 @@ public:
|
|||
} EGPUVendor;
|
||||
std::string getDriverVersionWMI(EGPUVendor vendor);
|
||||
|
||||
S32 getVRAM() const { return mVRAM; }
|
||||
|
||||
LLSD getDisplayInfo();
|
||||
|
||||
// Will get memory of best GPU in MB, return memory on sucsess, 0 on failure
|
||||
// Note: WMI is not accurate in some cases
|
||||
static U32 getMBVideoMemoryViaWMI();
|
||||
|
||||
// Find a particular device that matches the following specs.
|
||||
// Empty strings indicate that you don't care.
|
||||
// You can separate multiple devices with '|' chars to indicate you want
|
||||
// ANY of them to match and return.
|
||||
// LLDXDevice *findDevice(const std::string &vendor, const std::string &devices);
|
||||
|
||||
// std::string dumpDevices();
|
||||
public:
|
||||
typedef std::map<std::string, LLDXDevice *> device_map_t;
|
||||
// device_map_t mDevices;
|
||||
protected:
|
||||
S32 mVRAM;
|
||||
};
|
||||
|
||||
extern void (*gWriteDebug)(const char* msg);
|
||||
extern LLDXHardware gDXHardware;
|
||||
|
||||
#endif // LL_LLDXHARDWARE_H
|
||||
|
|
|
|||
|
|
@ -4685,9 +4685,18 @@ void LLWindowWin32::LLWindowWin32Thread::checkDXMem()
|
|||
|
||||
if (phys_mb > 0)
|
||||
{
|
||||
// Intel uses 'shared' vram, cap it to 25% of total memory
|
||||
// Todo: consider caping all adapters at least to 50% ram
|
||||
budget_mb = llmin(budget_mb, (UINT64)(phys_mb * 0.25));
|
||||
if (gGLManager.mIsIntel)
|
||||
{
|
||||
// Intel uses 'shared' vram, cap it to 25% of total memory
|
||||
// Todo: consider a way of detecting integrated Intel and AMD
|
||||
budget_mb = llmin(budget_mb, (UINT64)(phys_mb * 0.25));
|
||||
}
|
||||
else
|
||||
{
|
||||
// More budget is generally better, but the way viewer
|
||||
// utilizes even dedicated VRAM leaves a footprint in RAM
|
||||
budget_mb = llmin(budget_mb, (UINT64)(phys_mb * 0.75));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ set(viewer_SOURCE_FILES
|
|||
llagentwearables.cpp
|
||||
llanimstatelabels.cpp
|
||||
llappcorehttp.cpp
|
||||
llappearancelistener.cpp
|
||||
llappearancemgr.cpp
|
||||
llappviewer.cpp
|
||||
llappviewerlistener.cpp
|
||||
|
|
@ -200,6 +201,7 @@ set(viewer_SOURCE_FILES
|
|||
llfloatercamera.cpp
|
||||
llfloatercamerapresets.cpp
|
||||
llfloaterchangeitemthumbnail.cpp
|
||||
llfloaterchatmentionpicker.cpp
|
||||
llfloaterchatvoicevolume.cpp
|
||||
llfloaterclassified.cpp
|
||||
llfloatercolorpicker.cpp
|
||||
|
|
@ -761,6 +763,7 @@ set(viewer_HEADER_FILES
|
|||
llanimstatelabels.h
|
||||
llappcorehttp.h
|
||||
llappearance.h
|
||||
llappearancelistener.h
|
||||
llappearancemgr.h
|
||||
llappviewer.h
|
||||
llappviewerlistener.h
|
||||
|
|
@ -868,6 +871,7 @@ set(viewer_HEADER_FILES
|
|||
llfloaterbuyland.h
|
||||
llfloatercamerapresets.h
|
||||
llfloaterchangeitemthumbnail.h
|
||||
llfloaterchatmentionpicker.h
|
||||
llfloatercamera.h
|
||||
llfloaterchatvoicevolume.h
|
||||
llfloaterclassified.h
|
||||
|
|
|
|||
|
|
@ -345,6 +345,17 @@
|
|||
<string>F32</string>
|
||||
<key>Value</key>
|
||||
<real>0.5</real>
|
||||
</map>
|
||||
<key>AudioLevelWind</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Audio level of wind noise when standing still</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>F32</string>
|
||||
<key>Value</key>
|
||||
<real>0.5</real>
|
||||
</map>
|
||||
<key>AudioStreamingMedia</key>
|
||||
<map>
|
||||
|
|
@ -6350,6 +6361,17 @@
|
|||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>PlaySoundChatMention</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Plays a sound when got mentioned in a chat</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>PluginAttachDebuggerToPlugins</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
|
|
@ -7819,7 +7841,7 @@
|
|||
<key>RenderMinFreeMainMemoryThreshold</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Minimum of available physical memory in MB before textures get scaled down</string>
|
||||
<string>If available free physical memory is below this value textures get agresively scaled down</string>
|
||||
<key>Persist</key>
|
||||
<integer>0</integer>
|
||||
<key>Type</key>
|
||||
|
|
@ -9562,6 +9584,17 @@
|
|||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>RenderBalanceInSnapshot</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Display L$ balance in snapshot</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>1</integer>
|
||||
</map>
|
||||
<key>RenderUIBuffer</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
|
|
@ -12373,6 +12406,28 @@
|
|||
<key>Value</key>
|
||||
<string>2ca849ba-2885-4bc3-90ef-d4987a5b983a</string>
|
||||
</map>
|
||||
<key>UISndChatMention</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Sound file for chat mention(uuid for sound asset)</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>String</string>
|
||||
<key>Value</key>
|
||||
<string>03e77cb5-592c-5b33-d271-2e46497c3fb3</string>
|
||||
</map>
|
||||
<key>UISndChatPing</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Sound file for chat ping(uuid for sound asset)</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>String</string>
|
||||
<key>Value</key>
|
||||
<string>7dd36df6-2624-5438-f988-fdf8588a0ad9</string>
|
||||
</map>
|
||||
<key>UISndClick</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
|
|
|
|||
|
|
@ -643,6 +643,12 @@ void GLTFSceneManager::render(Asset& asset, U8 variant)
|
|||
return;
|
||||
}
|
||||
|
||||
if (gGLTFPBRMetallicRoughnessProgram.mGLTFVariants.size() <= variant)
|
||||
{
|
||||
llassert(false); // mGLTFVariants should have been initialized
|
||||
return;
|
||||
}
|
||||
|
||||
for (U32 ds = 0; ds < 2; ++ds)
|
||||
{
|
||||
RenderData& rd = asset.mRenderData[ds];
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
* @file groupchatlistener.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2011-04-11
|
||||
* @brief Implementation for groupchatlistener.
|
||||
* @brief Implementation for LLGroupChatListener.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2011&license=viewerlgpl$
|
||||
* $LicenseInfo:firstyear=2024&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2011, Linden Research, Inc.
|
||||
* Copyright (C) 2024, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
|
|
@ -34,43 +34,69 @@
|
|||
// std headers
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
#include "llchat.h"
|
||||
#include "llgroupactions.h"
|
||||
#include "llimview.h"
|
||||
|
||||
|
||||
namespace {
|
||||
void startIm_wrapper(LLSD const & event)
|
||||
{
|
||||
LLUUID session_id = LLGroupActions::startIM(event["id"].asUUID());
|
||||
sendReply(LLSDMap("session_id", LLSD(session_id)), event);
|
||||
}
|
||||
|
||||
void send_message_wrapper(const std::string& text, const LLUUID& session_id, const LLUUID& group_id)
|
||||
{
|
||||
LLIMModel::sendMessage(text, session_id, group_id, IM_SESSION_GROUP_START);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
GroupChatListener::GroupChatListener():
|
||||
LLGroupChatListener::LLGroupChatListener():
|
||||
LLEventAPI("GroupChat",
|
||||
"API to enter, leave, send and intercept group chat messages")
|
||||
{
|
||||
add("startIM",
|
||||
"Enter a group chat in group with UUID [\"id\"]\n"
|
||||
add("startGroupChat",
|
||||
"Enter a group chat in group with UUID [\"group_id\"]\n"
|
||||
"Assumes the logged-in agent is already a member of this group.",
|
||||
&startIm_wrapper);
|
||||
add("endIM",
|
||||
"Leave a group chat in group with UUID [\"id\"]\n"
|
||||
&LLGroupChatListener::startGroupChat,
|
||||
llsd::map("group_id", LLSD()));
|
||||
add("leaveGroupChat",
|
||||
"Leave a group chat in group with UUID [\"group_id\"]\n"
|
||||
"Assumes a prior successful startIM request.",
|
||||
&LLGroupActions::endIM,
|
||||
llsd::array("id"));
|
||||
add("sendIM",
|
||||
"send a groupchat IM",
|
||||
&send_message_wrapper,
|
||||
llsd::array("text", "session_id", "group_id"));
|
||||
&LLGroupChatListener::leaveGroupChat,
|
||||
llsd::map("group_id", LLSD()));
|
||||
add("sendGroupIM",
|
||||
"send a [\"message\"] to group with UUID [\"group_id\"]",
|
||||
&LLGroupChatListener::sendGroupIM,
|
||||
llsd::map("message", LLSD(), "group_id", LLSD()));
|
||||
}
|
||||
|
||||
bool is_in_group(LLEventAPI::Response &response, const LLSD &data)
|
||||
{
|
||||
if (!LLGroupActions::isInGroup(data["group_id"]))
|
||||
{
|
||||
response.error(stringize("You are not the member of the group:", std::quoted(data["group_id"].asString())));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LLGroupChatListener::startGroupChat(LLSD const &data)
|
||||
{
|
||||
Response response(LLSD(), data);
|
||||
if (!is_in_group(response, data))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (LLGroupActions::startIM(data["group_id"]).isNull())
|
||||
{
|
||||
return response.error(stringize("Failed to start group chat session ", std::quoted(data["group_id"].asString())));
|
||||
}
|
||||
}
|
||||
|
||||
void LLGroupChatListener::leaveGroupChat(LLSD const &data)
|
||||
{
|
||||
Response response(LLSD(), data);
|
||||
if (is_in_group(response, data))
|
||||
{
|
||||
LLGroupActions::endIM(data["group_id"].asUUID());
|
||||
}
|
||||
}
|
||||
|
||||
void LLGroupChatListener::sendGroupIM(LLSD const &data)
|
||||
{
|
||||
Response response(LLSD(), data);
|
||||
if (!is_in_group(response, data))
|
||||
{
|
||||
return;
|
||||
}
|
||||
LLUUID group_id(data["group_id"]);
|
||||
LLIMModel::sendMessage(data["message"], gIMMgr->computeSessionID(IM_SESSION_GROUP_START, group_id), group_id, IM_SESSION_SEND);
|
||||
}
|
||||
/*
|
||||
static void sendMessage(const std::string& utf8_text, const LLUUID& im_session_id,
|
||||
const LLUUID& other_participant_id, EInstantMessage dialog);
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -26,15 +26,20 @@
|
|||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_GROUPCHATLISTENER_H)
|
||||
#define LL_GROUPCHATLISTENER_H
|
||||
#if ! defined(LL_LLGROUPCHATLISTENER_H)
|
||||
#define LL_LLGROUPCHATLISTENER_H
|
||||
|
||||
#include "lleventapi.h"
|
||||
|
||||
class GroupChatListener: public LLEventAPI
|
||||
class LLGroupChatListener: public LLEventAPI
|
||||
{
|
||||
public:
|
||||
GroupChatListener();
|
||||
LLGroupChatListener();
|
||||
|
||||
private:
|
||||
void startGroupChat(LLSD const &data);
|
||||
void leaveGroupChat(LLSD const &data);
|
||||
void sendGroupIM(LLSD const &data);
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_GROUPCHATLISTENER_H) */
|
||||
#endif /* ! defined(LL_LLGROUPCHATLISTENER_H) */
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 681 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 669 B |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
|
@ -31,19 +31,25 @@
|
|||
#include "llagentlistener.h"
|
||||
|
||||
#include "llagent.h"
|
||||
#include "llagentcamera.h"
|
||||
#include "llavatarname.h"
|
||||
#include "llavatarnamecache.h"
|
||||
#include "llvoavatar.h"
|
||||
#include "llcommandhandler.h"
|
||||
#include "llinventorymodel.h"
|
||||
#include "llslurl.h"
|
||||
#include "llurldispatcher.h"
|
||||
#include "llviewercontrol.h"
|
||||
#include "llviewernetwork.h"
|
||||
#include "llviewerobject.h"
|
||||
#include "llviewerobjectlist.h"
|
||||
#include "llviewerregion.h"
|
||||
#include "llvoavatarself.h"
|
||||
#include "llsdutil.h"
|
||||
#include "llsdutil_math.h"
|
||||
#include "lltoolgrab.h"
|
||||
#include "llhudeffectlookat.h"
|
||||
#include "llagentcamera.h"
|
||||
#include "llviewercamera.h"
|
||||
|
||||
LLAgentListener::LLAgentListener(LLAgent &agent)
|
||||
: LLEventAPI("LLAgent",
|
||||
|
|
@ -69,13 +75,6 @@ LLAgentListener::LLAgentListener(LLAgent &agent)
|
|||
add("resetAxes",
|
||||
"Set the agent to a fixed orientation (optionally specify [\"lookat\"] = array of [x, y, z])",
|
||||
&LLAgentListener::resetAxes);
|
||||
add("getAxes",
|
||||
"Obsolete - use getPosition instead\n"
|
||||
"Send information about the agent's orientation on [\"reply\"]:\n"
|
||||
"[\"euler\"]: map of {roll, pitch, yaw}\n"
|
||||
"[\"quat\"]: array of [x, y, z, w] quaternion values",
|
||||
&LLAgentListener::getAxes,
|
||||
LLSDMap("reply", LLSD()));
|
||||
add("getPosition",
|
||||
"Send information about the agent's position and orientation on [\"reply\"]:\n"
|
||||
"[\"region\"]: array of region {x, y, z} position\n"
|
||||
|
|
@ -87,33 +86,34 @@ LLAgentListener::LLAgentListener(LLAgent &agent)
|
|||
add("startAutoPilot",
|
||||
"Start the autopilot system using the following parameters:\n"
|
||||
"[\"target_global\"]: array of target global {x, y, z} position\n"
|
||||
"[\"stop_distance\"]: target maxiumum distance from target [default: autopilot guess]\n"
|
||||
"[\"stop_distance\"]: maximum stop distance from target [default: autopilot guess]\n"
|
||||
"[\"target_rotation\"]: array of [x, y, z, w] quaternion values [default: no target]\n"
|
||||
"[\"rotation_threshold\"]: target maximum angle from target facing rotation [default: 0.03 radians]\n"
|
||||
"[\"behavior_name\"]: name of the autopilot behavior [default: \"\"]"
|
||||
"[\"allow_flying\"]: allow flying during autopilot [default: True]",
|
||||
//"[\"callback_pump\"]: pump to send success/failure and callback data to [default: none]\n"
|
||||
//"[\"callback_data\"]: data to send back during a callback [default: none]",
|
||||
&LLAgentListener::startAutoPilot);
|
||||
"[\"behavior_name\"]: name of the autopilot behavior [default: \"\"]\n"
|
||||
"[\"allow_flying\"]: allow flying during autopilot [default: True]\n"
|
||||
"event with [\"success\"] flag is sent to 'LLAutopilot' event pump, when auto pilot is terminated",
|
||||
&LLAgentListener::startAutoPilot,
|
||||
llsd::map("target_global", LLSD()));
|
||||
add("getAutoPilot",
|
||||
"Send information about current state of the autopilot system to [\"reply\"]:\n"
|
||||
"[\"enabled\"]: boolean indicating whether or not autopilot is enabled\n"
|
||||
"[\"target_global\"]: array of target global {x, y, z} position\n"
|
||||
"[\"leader_id\"]: uuid of target autopilot is following\n"
|
||||
"[\"stop_distance\"]: target maximum distance from target\n"
|
||||
"[\"stop_distance\"]: maximum stop distance from target\n"
|
||||
"[\"target_distance\"]: last known distance from target\n"
|
||||
"[\"use_rotation\"]: boolean indicating if autopilot has a target facing rotation\n"
|
||||
"[\"target_facing\"]: array of {x, y} target direction to face\n"
|
||||
"[\"rotation_threshold\"]: target maximum angle from target facing rotation\n"
|
||||
"[\"behavior_name\"]: name of the autopilot behavior",
|
||||
&LLAgentListener::getAutoPilot,
|
||||
LLSDMap("reply", LLSD()));
|
||||
llsd::map("reply", LLSD()));
|
||||
add("startFollowPilot",
|
||||
"[\"leader_id\"]: uuid of target to follow using the autopilot system (optional with avatar_name)\n"
|
||||
"[\"avatar_name\"]: avatar name to follow using the autopilot system (optional with leader_id)\n"
|
||||
"[\"allow_flying\"]: allow flying during autopilot [default: True]\n"
|
||||
"[\"stop_distance\"]: target maxiumum distance from target [default: autopilot guess]",
|
||||
&LLAgentListener::startFollowPilot);
|
||||
"[\"stop_distance\"]: maximum stop distance from target [default: autopilot guess]",
|
||||
&LLAgentListener::startFollowPilot,
|
||||
llsd::map("reply", LLSD()));
|
||||
add("setAutoPilotTarget",
|
||||
"Update target for currently running autopilot:\n"
|
||||
"[\"target_global\"]: array of target global {x, y, z} position",
|
||||
|
|
@ -138,6 +138,69 @@ LLAgentListener::LLAgentListener(LLAgent &agent)
|
|||
"[\"contrib\"]: user's land contribution to this group\n",
|
||||
&LLAgentListener::getGroups,
|
||||
LLSDMap("reply", LLSD()));
|
||||
//camera params are similar to LSL, see https://wiki.secondlife.com/wiki/LlSetCameraParams
|
||||
add("setCameraParams",
|
||||
"Set Follow camera params, and then activate it:\n"
|
||||
"[\"camera_pos\"]: vector3, camera position in region coordinates\n"
|
||||
"[\"focus_pos\"]: vector3, what the camera is aimed at (in region coordinates)\n"
|
||||
"[\"focus_offset\"]: vector3, adjusts the camera focus position relative to the target, default is (1, 0, 0)\n"
|
||||
"[\"distance\"]: float (meters), distance the camera wants to be from its target, default is 3\n"
|
||||
"[\"focus_threshold\"]: float (meters), sets the radius of a sphere around the camera's target position within which its focus is not affected by target motion, default is 1\n"
|
||||
"[\"camera_threshold\"]: float (meters), sets the radius of a sphere around the camera's ideal position within which it is not affected by target motion, default is 1\n"
|
||||
"[\"focus_lag\"]: float (seconds), how much the camera lags as it tries to aim towards the target, default is 0.1\n"
|
||||
"[\"camera_lag\"]: float (seconds), how much the camera lags as it tries to move towards its 'ideal' position, default is 0.1\n"
|
||||
"[\"camera_pitch\"]: float (degrees), adjusts the angular amount that the camera aims straight ahead vs. straight down, maintaining the same distance, default is 0\n"
|
||||
"[\"behindness_angle\"]: float (degrees), sets the angle in degrees within which the camera is not constrained by changes in target rotation, default is 10\n"
|
||||
"[\"behindness_lag\"]: float (seconds), sets how strongly the camera is forced to stay behind the target if outside of behindness angle, default is 0\n"
|
||||
"[\"camera_locked\"]: bool, locks the camera position so it will not move\n"
|
||||
"[\"focus_locked\"]: bool, locks the camera focus so it will not move",
|
||||
&LLAgentListener::setFollowCamParams);
|
||||
add("setFollowCamActive",
|
||||
"Turns on or off scripted control of the camera using boolean [\"active\"]",
|
||||
&LLAgentListener::setFollowCamActive,
|
||||
llsd::map("active", LLSD()));
|
||||
add("removeCameraParams",
|
||||
"Reset Follow camera params",
|
||||
&LLAgentListener::removeFollowCamParams);
|
||||
|
||||
add("playAnimation",
|
||||
"Play [\"item_id\"] animation locally (by default) or [\"inworld\"] (when set to true)",
|
||||
&LLAgentListener::playAnimation,
|
||||
llsd::map("item_id", LLSD(), "reply", LLSD()));
|
||||
add("stopAnimation",
|
||||
"Stop playing [\"item_id\"] animation",
|
||||
&LLAgentListener::stopAnimation,
|
||||
llsd::map("item_id", LLSD(), "reply", LLSD()));
|
||||
add("getAnimationInfo",
|
||||
"Return information about [\"item_id\"] animation",
|
||||
&LLAgentListener::getAnimationInfo,
|
||||
llsd::map("item_id", LLSD(), "reply", LLSD()));
|
||||
|
||||
add("getID",
|
||||
"Return your own avatar ID",
|
||||
&LLAgentListener::getID,
|
||||
llsd::map("reply", LLSD()));
|
||||
|
||||
add("getNearbyAvatarsList",
|
||||
"Return result set key [\"result\"] for nearby avatars in a range of [\"dist\"]\n"
|
||||
"if [\"dist\"] is not specified, 'RenderFarClip' setting is used\n"
|
||||
"reply contains \"result\" table with \"id\", \"name\", \"global_pos\", \"region_pos\", \"region_id\" fields",
|
||||
&LLAgentListener::getNearbyAvatarsList,
|
||||
llsd::map("reply", LLSD()));
|
||||
|
||||
add("getNearbyObjectsList",
|
||||
"Return result set key [\"result\"] for nearby objects in a range of [\"dist\"]\n"
|
||||
"if [\"dist\"] is not specified, 'RenderFarClip' setting is used\n"
|
||||
"reply contains \"result\" table with \"id\", \"global_pos\", \"region_pos\", \"region_id\" fields",
|
||||
&LLAgentListener::getNearbyObjectsList,
|
||||
llsd::map("reply", LLSD()));
|
||||
|
||||
add("getAgentScreenPos",
|
||||
"Return screen position of the [\"avatar_id\"] avatar or own avatar if not specified\n"
|
||||
"reply contains \"x\", \"y\" coordinates and \"onscreen\" flag to indicate if it's actually in within the current window\n"
|
||||
"avatar render position is used as the point",
|
||||
&LLAgentListener::getAgentScreenPos,
|
||||
llsd::map("reply", LLSD()));
|
||||
}
|
||||
|
||||
void LLAgentListener::requestTeleport(LLSD const & event_data) const
|
||||
|
|
@ -168,7 +231,7 @@ void LLAgentListener::requestSit(LLSD const & event_data) const
|
|||
//mAgent.getAvatarObject()->sitOnObject();
|
||||
// shamelessly ripped from llviewermenu.cpp:handle_sit_or_stand()
|
||||
// *TODO - find a permanent place to share this code properly.
|
||||
|
||||
Response response(LLSD(), event_data);
|
||||
LLViewerObject *object = NULL;
|
||||
if (event_data.has("obj_uuid"))
|
||||
{
|
||||
|
|
@ -177,7 +240,13 @@ void LLAgentListener::requestSit(LLSD const & event_data) const
|
|||
else if (event_data.has("position"))
|
||||
{
|
||||
LLVector3 target_position = ll_vector3_from_sd(event_data["position"]);
|
||||
object = findObjectClosestTo(target_position);
|
||||
object = findObjectClosestTo(target_position, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
//just sit on the ground
|
||||
mAgent.setControlFlags(AGENT_CONTROL_SIT_ON_GROUND);
|
||||
return;
|
||||
}
|
||||
|
||||
if (object && object->getPCode() == LL_PCODE_VOLUME)
|
||||
|
|
@ -194,8 +263,7 @@ void LLAgentListener::requestSit(LLSD const & event_data) const
|
|||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS() << "LLAgent requestSit could not find the sit target: "
|
||||
<< event_data << LL_ENDL;
|
||||
response.error("requestSit could not find the sit target");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -205,7 +273,7 @@ void LLAgentListener::requestStand(LLSD const & event_data) const
|
|||
}
|
||||
|
||||
|
||||
LLViewerObject * LLAgentListener::findObjectClosestTo( const LLVector3 & position ) const
|
||||
LLViewerObject * LLAgentListener::findObjectClosestTo(const LLVector3 & position, bool sit_target) const
|
||||
{
|
||||
LLViewerObject *object = NULL;
|
||||
|
||||
|
|
@ -216,8 +284,13 @@ LLViewerObject * LLAgentListener::findObjectClosestTo( const LLVector3 & positio
|
|||
while (cur_index < num_objects)
|
||||
{
|
||||
LLViewerObject * cur_object = gObjectList.getObject(cur_index++);
|
||||
if (cur_object)
|
||||
{ // Calculate distance from the target position
|
||||
if (cur_object && !cur_object->isAttachment())
|
||||
{
|
||||
if(sit_target && (cur_object->getPCode() != LL_PCODE_VOLUME))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Calculate distance from the target position
|
||||
LLVector3 target_diff = cur_object->getPositionRegion() - position;
|
||||
F32 distance_to_target = target_diff.length();
|
||||
if (distance_to_target < min_distance)
|
||||
|
|
@ -296,22 +369,6 @@ void LLAgentListener::resetAxes(const LLSD& event_data) const
|
|||
}
|
||||
}
|
||||
|
||||
void LLAgentListener::getAxes(const LLSD& event_data) const
|
||||
{
|
||||
LLQuaternion quat(mAgent.getQuat());
|
||||
F32 roll, pitch, yaw;
|
||||
quat.getEulerAngles(&roll, &pitch, &yaw);
|
||||
// The official query API for LLQuaternion's [x, y, z, w] values is its
|
||||
// public member mQ...
|
||||
LLSD reply = LLSD::emptyMap();
|
||||
reply["quat"] = llsd_copy_array(boost::begin(quat.mQ), boost::end(quat.mQ));
|
||||
reply["euler"] = LLSD::emptyMap();
|
||||
reply["euler"]["roll"] = roll;
|
||||
reply["euler"]["pitch"] = pitch;
|
||||
reply["euler"]["yaw"] = yaw;
|
||||
sendReply(reply, event_data);
|
||||
}
|
||||
|
||||
void LLAgentListener::getPosition(const LLSD& event_data) const
|
||||
{
|
||||
F32 roll, pitch, yaw;
|
||||
|
|
@ -333,14 +390,13 @@ void LLAgentListener::getPosition(const LLSD& event_data) const
|
|||
|
||||
void LLAgentListener::startAutoPilot(LLSD const & event_data)
|
||||
{
|
||||
LLQuaternion target_rotation_value;
|
||||
LLQuaternion* target_rotation = NULL;
|
||||
if (event_data.has("target_rotation"))
|
||||
{
|
||||
target_rotation_value = ll_quaternion_from_sd(event_data["target_rotation"]);
|
||||
LLQuaternion target_rotation_value = ll_quaternion_from_sd(event_data["target_rotation"]);
|
||||
target_rotation = &target_rotation_value;
|
||||
}
|
||||
// *TODO: Use callback_pump and callback_data
|
||||
|
||||
F32 rotation_threshold = 0.03f;
|
||||
if (event_data.has("rotation_threshold"))
|
||||
{
|
||||
|
|
@ -360,13 +416,24 @@ void LLAgentListener::startAutoPilot(LLSD const & event_data)
|
|||
stop_distance = (F32)event_data["stop_distance"].asReal();
|
||||
}
|
||||
|
||||
std::string behavior_name = LLCoros::getName();
|
||||
if (event_data.has("behavior_name"))
|
||||
{
|
||||
behavior_name = event_data["behavior_name"].asString();
|
||||
}
|
||||
|
||||
// Clear follow target, this is doing a path
|
||||
mFollowTarget.setNull();
|
||||
|
||||
auto finish_cb = [](bool success, void*)
|
||||
{
|
||||
LLEventPumps::instance().obtain("LLAutopilot").post(llsd::map("success", success));
|
||||
};
|
||||
|
||||
mAgent.startAutoPilotGlobal(ll_vector3d_from_sd(event_data["target_global"]),
|
||||
event_data["behavior_name"],
|
||||
behavior_name,
|
||||
target_rotation,
|
||||
NULL, NULL,
|
||||
finish_cb, NULL,
|
||||
stop_distance,
|
||||
rotation_threshold,
|
||||
allow_flying);
|
||||
|
|
@ -374,7 +441,7 @@ void LLAgentListener::startAutoPilot(LLSD const & event_data)
|
|||
|
||||
void LLAgentListener::getAutoPilot(const LLSD& event_data) const
|
||||
{
|
||||
LLSD reply = LLSD::emptyMap();
|
||||
Response reply(LLSD(), event_data);
|
||||
|
||||
LLSD::Boolean enabled = mAgent.getAutoPilot();
|
||||
reply["enabled"] = enabled;
|
||||
|
|
@ -403,12 +470,11 @@ void LLAgentListener::getAutoPilot(const LLSD& event_data) const
|
|||
reply["rotation_threshold"] = mAgent.getAutoPilotRotationThreshold();
|
||||
reply["behavior_name"] = mAgent.getAutoPilotBehaviorName();
|
||||
reply["fly"] = (LLSD::Boolean) mAgent.getFlying();
|
||||
|
||||
sendReply(reply, event_data);
|
||||
}
|
||||
|
||||
void LLAgentListener::startFollowPilot(LLSD const & event_data)
|
||||
{
|
||||
Response response(LLSD(), event_data);
|
||||
LLUUID target_id;
|
||||
|
||||
bool allow_flying = true;
|
||||
|
|
@ -442,6 +508,10 @@ void LLAgentListener::startFollowPilot(LLSD const & event_data)
|
|||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return response.error("'leader_id' or 'avatar_name' should be specified");
|
||||
}
|
||||
|
||||
F32 stop_distance = 0.f;
|
||||
if (event_data.has("stop_distance"))
|
||||
|
|
@ -449,13 +519,16 @@ void LLAgentListener::startFollowPilot(LLSD const & event_data)
|
|||
stop_distance = (F32)event_data["stop_distance"].asReal();
|
||||
}
|
||||
|
||||
if (target_id.notNull())
|
||||
if (!gObjectList.findObject(target_id))
|
||||
{
|
||||
mAgent.setFlying(allow_flying);
|
||||
mFollowTarget = target_id; // Save follow target so we can report distance later
|
||||
|
||||
mAgent.startFollowPilot(target_id, allow_flying, stop_distance);
|
||||
std::string target_info = event_data.has("leader_id") ? event_data["leader_id"] : event_data["avatar_name"];
|
||||
return response.error(stringize("Target ", std::quoted(target_info), " was not found"));
|
||||
}
|
||||
|
||||
mAgent.setFlying(allow_flying);
|
||||
mFollowTarget = target_id; // Save follow target so we can report distance later
|
||||
|
||||
mAgent.startFollowPilot(target_id, allow_flying, stop_distance);
|
||||
}
|
||||
|
||||
void LLAgentListener::setAutoPilotTarget(LLSD const & event_data) const
|
||||
|
|
@ -519,3 +592,209 @@ void LLAgentListener::getGroups(const LLSD& event) const
|
|||
}
|
||||
sendReply(LLSDMap("groups", reply), event);
|
||||
}
|
||||
|
||||
/*----------------------------- camera control -----------------------------*/
|
||||
// specialize LLSDParam to support (const LLVector3&) arguments -- this
|
||||
// wouldn't even be necessary except that the relevant LLVector3 constructor
|
||||
// is explicitly explicit
|
||||
template <>
|
||||
class LLSDParam<const LLVector3&>: public LLSDParamBase
|
||||
{
|
||||
public:
|
||||
LLSDParam(const LLSD& value): value(LLVector3(value)) {}
|
||||
|
||||
operator const LLVector3&() const { return value; }
|
||||
|
||||
private:
|
||||
LLVector3 value;
|
||||
};
|
||||
|
||||
// accept any of a number of similar LLFollowCamMgr methods with different
|
||||
// argument types, and return a wrapper lambda that accepts LLSD and converts
|
||||
// to the target argument type
|
||||
template <typename T>
|
||||
auto wrap(void (LLFollowCamMgr::*method)(const LLUUID& source, T arg))
|
||||
{
|
||||
return [method](LLFollowCamMgr& followcam, const LLUUID& source, const LLSD& arg)
|
||||
{ (followcam.*method)(source, LLSDParam<T>(arg)); };
|
||||
}
|
||||
|
||||
// table of supported LLFollowCamMgr methods,
|
||||
// with the corresponding setFollowCamParams() argument keys
|
||||
static std::pair<std::string, std::function<void(LLFollowCamMgr&, const LLUUID&, const LLSD&)>>
|
||||
cam_params[] =
|
||||
{
|
||||
{ "camera_pos", wrap(&LLFollowCamMgr::setPosition) },
|
||||
{ "focus_pos", wrap(&LLFollowCamMgr::setFocus) },
|
||||
{ "focus_offset", wrap(&LLFollowCamMgr::setFocusOffset) },
|
||||
{ "camera_locked", wrap(&LLFollowCamMgr::setPositionLocked) },
|
||||
{ "focus_locked", wrap(&LLFollowCamMgr::setFocusLocked) },
|
||||
{ "distance", wrap(&LLFollowCamMgr::setDistance) },
|
||||
{ "focus_threshold", wrap(&LLFollowCamMgr::setFocusThreshold) },
|
||||
{ "camera_threshold", wrap(&LLFollowCamMgr::setPositionThreshold) },
|
||||
{ "focus_lag", wrap(&LLFollowCamMgr::setFocusLag) },
|
||||
{ "camera_lag", wrap(&LLFollowCamMgr::setPositionLag) },
|
||||
{ "camera_pitch", wrap(&LLFollowCamMgr::setPitch) },
|
||||
{ "behindness_lag", wrap(&LLFollowCamMgr::setBehindnessLag) },
|
||||
{ "behindness_angle", wrap(&LLFollowCamMgr::setBehindnessAngle) },
|
||||
};
|
||||
|
||||
void LLAgentListener::setFollowCamParams(const LLSD& event) const
|
||||
{
|
||||
auto& followcam{ LLFollowCamMgr::instance() };
|
||||
for (const auto& pair : cam_params)
|
||||
{
|
||||
if (event.has(pair.first))
|
||||
{
|
||||
pair.second(followcam, gAgentID, event[pair.first]);
|
||||
}
|
||||
}
|
||||
followcam.setCameraActive(gAgentID, true);
|
||||
}
|
||||
|
||||
void LLAgentListener::setFollowCamActive(LLSD const & event) const
|
||||
{
|
||||
LLFollowCamMgr::getInstance()->setCameraActive(gAgentID, event["active"]);
|
||||
}
|
||||
|
||||
void LLAgentListener::removeFollowCamParams(LLSD const & event) const
|
||||
{
|
||||
LLFollowCamMgr::getInstance()->removeFollowCamParams(gAgentID);
|
||||
}
|
||||
|
||||
LLViewerInventoryItem* get_anim_item(LLEventAPI::Response &response, const LLSD &event_data)
|
||||
{
|
||||
LLViewerInventoryItem* item = gInventory.getItem(event_data["item_id"].asUUID());
|
||||
if (!item || (item->getInventoryType() != LLInventoryType::IT_ANIMATION))
|
||||
{
|
||||
response.error(stringize("Animation item ", std::quoted(event_data["item_id"].asString()), " was not found"));
|
||||
return NULL;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
void LLAgentListener::playAnimation(LLSD const &event_data)
|
||||
{
|
||||
Response response(LLSD(), event_data);
|
||||
if (LLViewerInventoryItem* item = get_anim_item(response, event_data))
|
||||
{
|
||||
if (event_data["inworld"].asBoolean())
|
||||
{
|
||||
mAgent.sendAnimationRequest(item->getAssetUUID(), ANIM_REQUEST_START);
|
||||
}
|
||||
else
|
||||
{
|
||||
gAgentAvatarp->startMotion(item->getAssetUUID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLAgentListener::stopAnimation(LLSD const &event_data)
|
||||
{
|
||||
Response response(LLSD(), event_data);
|
||||
if (LLViewerInventoryItem* item = get_anim_item(response, event_data))
|
||||
{
|
||||
gAgentAvatarp->stopMotion(item->getAssetUUID());
|
||||
mAgent.sendAnimationRequest(item->getAssetUUID(), ANIM_REQUEST_STOP);
|
||||
}
|
||||
}
|
||||
|
||||
void LLAgentListener::getAnimationInfo(LLSD const &event_data)
|
||||
{
|
||||
Response response(LLSD(), event_data);
|
||||
if (LLViewerInventoryItem* item = get_anim_item(response, event_data))
|
||||
{
|
||||
// if motion exists, will return existing one
|
||||
LLMotion* motion = gAgentAvatarp->createMotion(item->getAssetUUID());
|
||||
response["anim_info"] = llsd::map("duration", motion->getDuration(),
|
||||
"is_loop", motion->getLoop(),
|
||||
"num_joints", motion->getNumJointMotions(),
|
||||
"asset_id", item->getAssetUUID(),
|
||||
"priority", motion->getPriority());
|
||||
}
|
||||
}
|
||||
|
||||
void LLAgentListener::getID(LLSD const& event_data)
|
||||
{
|
||||
Response response(llsd::map("id", gAgentID), event_data);
|
||||
}
|
||||
|
||||
F32 get_search_radius(LLSD const& event_data)
|
||||
{
|
||||
static LLCachedControl<F32> render_far_clip(gSavedSettings, "RenderFarClip", 64);
|
||||
F32 dist = render_far_clip;
|
||||
if (event_data.has("dist"))
|
||||
{
|
||||
dist = llclamp((F32)event_data["dist"].asReal(), 1, 512);
|
||||
}
|
||||
return dist * dist;
|
||||
}
|
||||
|
||||
void LLAgentListener::getNearbyAvatarsList(LLSD const& event_data)
|
||||
{
|
||||
Response response(LLSD(), event_data);
|
||||
F32 radius = get_search_radius(event_data);
|
||||
LLVector3d agent_pos = gAgent.getPositionGlobal();
|
||||
for (LLCharacter* character : LLCharacter::sInstances)
|
||||
{
|
||||
LLVOAvatar* avatar = (LLVOAvatar*)character;
|
||||
if (avatar && !avatar->isDead() && !avatar->isControlAvatar() && !avatar->isSelf())
|
||||
{
|
||||
if ((dist_vec_squared(avatar->getPositionGlobal(), agent_pos) <= radius))
|
||||
{
|
||||
LLAvatarName av_name;
|
||||
LLAvatarNameCache::get(avatar->getID(), &av_name);
|
||||
LLVector3 region_pos = avatar->getCharacterPosition();
|
||||
response["result"].append(llsd::map("id", avatar->getID(), "global_pos", ll_sd_from_vector3d(avatar->getPosGlobalFromAgent(region_pos)),
|
||||
"region_pos", ll_sd_from_vector3(region_pos), "name", av_name.getUserName(), "region_id", avatar->getRegion()->getRegionID()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLAgentListener::getNearbyObjectsList(LLSD const& event_data)
|
||||
{
|
||||
Response response(LLSD(), event_data);
|
||||
F32 radius = get_search_radius(event_data);
|
||||
S32 num_objects = gObjectList.getNumObjects();
|
||||
LLVector3d agent_pos = gAgent.getPositionGlobal();
|
||||
for (S32 i = 0; i < num_objects; ++i)
|
||||
{
|
||||
LLViewerObject* object = gObjectList.getObject(i);
|
||||
if (object && object->getVolume() && !object->isAttachment())
|
||||
{
|
||||
if ((dist_vec_squared(object->getPositionGlobal(), agent_pos) <= radius))
|
||||
{
|
||||
response["result"].append(llsd::map("id", object->getID(), "global_pos", ll_sd_from_vector3d(object->getPositionGlobal()), "region_pos",
|
||||
ll_sd_from_vector3(object->getPositionRegion()), "region_id", object->getRegion()->getRegionID()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLAgentListener::getAgentScreenPos(LLSD const& event_data)
|
||||
{
|
||||
Response response(LLSD(), event_data);
|
||||
LLVector3 render_pos;
|
||||
if (event_data.has("avatar_id") && (event_data["avatar_id"].asUUID() != gAgentID))
|
||||
{
|
||||
LLUUID avatar_id(event_data["avatar_id"]);
|
||||
for (LLCharacter* character : LLCharacter::sInstances)
|
||||
{
|
||||
LLVOAvatar* avatar = (LLVOAvatar*)character;
|
||||
if (!avatar->isDead() && (avatar->getID() == avatar_id))
|
||||
{
|
||||
render_pos = avatar->getRenderPosition();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (gAgentAvatarp.notNull() && gAgentAvatarp->isValid())
|
||||
{
|
||||
render_pos = gAgentAvatarp->getRenderPosition();
|
||||
}
|
||||
LLCoordGL screen_pos;
|
||||
response["onscreen"] = LLViewerCamera::getInstance()->projectPosAgentToScreen(render_pos, screen_pos, false);
|
||||
response["x"] = screen_pos.mX;
|
||||
response["y"] = screen_pos.mY;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ private:
|
|||
void requestStand(LLSD const & event_data) const;
|
||||
void requestTouch(LLSD const & event_data) const;
|
||||
void resetAxes(const LLSD& event_data) const;
|
||||
void getAxes(const LLSD& event_data) const;
|
||||
void getGroups(const LLSD& event) const;
|
||||
void getPosition(const LLSD& event_data) const;
|
||||
void startAutoPilot(const LLSD& event_data);
|
||||
|
|
@ -58,7 +57,20 @@ private:
|
|||
void stopAutoPilot(const LLSD& event_data) const;
|
||||
void lookAt(LLSD const & event_data) const;
|
||||
|
||||
LLViewerObject * findObjectClosestTo( const LLVector3 & position ) const;
|
||||
void setFollowCamParams(LLSD const & event_data) const;
|
||||
void setFollowCamActive(LLSD const & event_data) const;
|
||||
void removeFollowCamParams(LLSD const & event_data) const;
|
||||
|
||||
void playAnimation(LLSD const &event_data);
|
||||
void stopAnimation(LLSD const &event_data);
|
||||
void getAnimationInfo(LLSD const &event_data);
|
||||
|
||||
void getID(LLSD const& event_data);
|
||||
void getNearbyAvatarsList(LLSD const& event_data);
|
||||
void getNearbyObjectsList(LLSD const& event_data);
|
||||
void getAgentScreenPos(LLSD const& event_data);
|
||||
|
||||
LLViewerObject * findObjectClosestTo( const LLVector3 & position, bool sit_target = false ) const;
|
||||
|
||||
private:
|
||||
LLAgent & mAgent;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
* @file llappearancelistener.cpp
|
||||
*
|
||||
* $LicenseInfo:firstyear=2024&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2024, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#include "llviewerprecompiledheaders.h"
|
||||
|
||||
#include "llappearancelistener.h"
|
||||
|
||||
#include "llappearancemgr.h"
|
||||
#include "llinventoryfunctions.h"
|
||||
#include "lltransutil.h"
|
||||
#include "llwearableitemslist.h"
|
||||
#include "stringize.h"
|
||||
|
||||
LLAppearanceListener::LLAppearanceListener()
|
||||
: LLEventAPI("LLAppearance",
|
||||
"API to wear a specified outfit and wear/remove individual items")
|
||||
{
|
||||
add("wearOutfit",
|
||||
"Wear outfit by folder id: [\"folder_id\"] OR by folder name: [\"folder_name\"]\n"
|
||||
"When [\"append\"] is true, outfit will be added to COF\n"
|
||||
"otherwise it will replace current oufit",
|
||||
&LLAppearanceListener::wearOutfit);
|
||||
|
||||
add("wearItems",
|
||||
"Wear items by id: [items_id]",
|
||||
&LLAppearanceListener::wearItems,
|
||||
llsd::map("items_id", LLSD(), "replace", LLSD()));
|
||||
|
||||
add("detachItems",
|
||||
"Detach items by id: [items_id]",
|
||||
&LLAppearanceListener::detachItems,
|
||||
llsd::map("items_id", LLSD()));
|
||||
|
||||
add("getOutfitsList",
|
||||
"Return the table with Outfits info(id and name)",
|
||||
&LLAppearanceListener::getOutfitsList);
|
||||
|
||||
add("getOutfitItems",
|
||||
"Return the table of items with info(id : name, wearable_type, is_worn) inside specified outfit folder",
|
||||
&LLAppearanceListener::getOutfitItems);
|
||||
}
|
||||
|
||||
|
||||
void LLAppearanceListener::wearOutfit(LLSD const &data)
|
||||
{
|
||||
Response response(LLSD(), data);
|
||||
if (!data.has("folder_id") && !data.has("folder_name"))
|
||||
{
|
||||
return response.error("Either [folder_id] or [folder_name] is required");
|
||||
}
|
||||
|
||||
bool append = data.has("append") ? data["append"].asBoolean() : false;
|
||||
if (!LLAppearanceMgr::instance().wearOutfit(data, append))
|
||||
{
|
||||
response.error("Failed to wear outfit");
|
||||
}
|
||||
}
|
||||
|
||||
void LLAppearanceListener::wearItems(LLSD const &data)
|
||||
{
|
||||
const LLSD& items_id{ data["items_id"] };
|
||||
uuid_vec_t ids;
|
||||
if (!items_id.isArray())
|
||||
{
|
||||
ids.push_back(items_id.asUUID());
|
||||
}
|
||||
else // array
|
||||
{
|
||||
for (const auto& id : llsd::inArray(items_id))
|
||||
{
|
||||
ids.push_back(id);
|
||||
}
|
||||
}
|
||||
LLAppearanceMgr::instance().wearItemsOnAvatar(ids, true, data["replace"].asBoolean());
|
||||
}
|
||||
|
||||
void LLAppearanceListener::detachItems(LLSD const &data)
|
||||
{
|
||||
const LLSD& items_id{ data["items_id"] };
|
||||
uuid_vec_t ids;
|
||||
if (!items_id.isArray())
|
||||
{
|
||||
ids.push_back(items_id.asUUID());
|
||||
}
|
||||
else // array
|
||||
{
|
||||
for (const auto& id : llsd::inArray(items_id))
|
||||
{
|
||||
ids.push_back(id);
|
||||
}
|
||||
}
|
||||
LLAppearanceMgr::instance().removeItemsFromAvatar(ids);
|
||||
}
|
||||
|
||||
void LLAppearanceListener::getOutfitsList(LLSD const &data)
|
||||
{
|
||||
Response response(LLSD(), data);
|
||||
const LLUUID outfits_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS);
|
||||
|
||||
LLInventoryModel::cat_array_t cat_array;
|
||||
LLInventoryModel::item_array_t item_array;
|
||||
|
||||
LLIsFolderType is_category(LLFolderType::FT_OUTFIT);
|
||||
gInventory.collectDescendentsIf(outfits_id, cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH, is_category);
|
||||
|
||||
response["outfits"] = llsd::toMap(cat_array,
|
||||
[](const LLPointer<LLViewerInventoryCategory> &cat)
|
||||
{ return std::make_pair(cat->getUUID().asString(), cat->getName()); });
|
||||
}
|
||||
|
||||
void LLAppearanceListener::getOutfitItems(LLSD const &data)
|
||||
{
|
||||
Response response(LLSD(), data);
|
||||
LLUUID outfit_id(data["outfit_id"].asUUID());
|
||||
LLViewerInventoryCategory *cat = gInventory.getCategory(outfit_id);
|
||||
if (!cat || cat->getPreferredType() != LLFolderType::FT_OUTFIT)
|
||||
{
|
||||
return response.error(stringize("Couldn't find outfit ", outfit_id.asString()));
|
||||
}
|
||||
LLInventoryModel::cat_array_t cat_array;
|
||||
LLInventoryModel::item_array_t item_array;
|
||||
|
||||
LLFindOutfitItems collector = LLFindOutfitItems();
|
||||
gInventory.collectDescendentsIf(outfit_id, cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH, collector);
|
||||
|
||||
response["items"] = llsd::toMap(item_array,
|
||||
[](const LLPointer<LLViewerInventoryItem> &it)
|
||||
{
|
||||
return std::make_pair(
|
||||
it->getUUID().asString(),
|
||||
llsd::map(
|
||||
"name", it->getName(),
|
||||
"wearable_type", LLWearableType::getInstance()->getTypeName(it->isWearableType() ? it->getWearableType() : LLWearableType::WT_NONE),
|
||||
"is_worn", get_is_item_worn(it)));
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* @file llappearancelistener.h
|
||||
*
|
||||
* $LicenseInfo:firstyear=2024&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2024, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
|
||||
#ifndef LL_LLAPPEARANCELISTENER_H
|
||||
#define LL_LLAPPEARANCELISTENER_H
|
||||
|
||||
#include "lleventapi.h"
|
||||
|
||||
class LLAppearanceListener : public LLEventAPI
|
||||
{
|
||||
public:
|
||||
LLAppearanceListener();
|
||||
|
||||
private:
|
||||
void wearOutfit(LLSD const &data);
|
||||
void wearItems(LLSD const &data);
|
||||
void detachItems(LLSD const &data);
|
||||
void getOutfitsList(LLSD const &data);
|
||||
void getOutfitItems(LLSD const &data);
|
||||
};
|
||||
|
||||
#endif // LL_LLAPPEARANCELISTENER_H
|
||||
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
#include "llagent.h"
|
||||
#include "llagentcamera.h"
|
||||
#include "llagentwearables.h"
|
||||
#include "llappearancelistener.h"
|
||||
#include "llappearancemgr.h"
|
||||
#include "llattachmentsmgr.h"
|
||||
#include "llcommandhandler.h"
|
||||
|
|
@ -66,6 +67,8 @@
|
|||
|
||||
#include "llavatarpropertiesprocessor.h"
|
||||
|
||||
LLAppearanceListener sAppearanceListener;
|
||||
|
||||
namespace
|
||||
{
|
||||
const S32 BAKE_RETRY_MAX_COUNT = 5;
|
||||
|
|
@ -4762,6 +4765,11 @@ bool wear_category(const LLSD& query_map, bool append)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool LLAppearanceMgr::wearOutfit(const LLSD& query_map, bool append)
|
||||
{
|
||||
return wear_category(query_map, append);
|
||||
}
|
||||
|
||||
class LLWearFolderHandler : public LLCommandHandler
|
||||
{
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ public:
|
|||
void wearInventoryCategoryOnAvatar(LLInventoryCategory* category, bool append);
|
||||
void wearCategoryFinal(const LLUUID& cat_id, bool copy_items, bool append);
|
||||
void wearOutfitByName(const std::string& name);
|
||||
bool wearOutfit(const LLSD& query_map, bool append = false);
|
||||
void changeOutfit(bool proceed, const LLUUID& category, bool append);
|
||||
void replaceCurrentOutfit(const LLUUID& new_outfit);
|
||||
void renameOutfit(const LLUUID& outfit_id);
|
||||
|
|
|
|||
|
|
@ -475,7 +475,7 @@ static void deferred_ui_audio_callback(const LLUUID& uuid)
|
|||
|
||||
bool create_text_segment_icon_from_url_match(LLUrlMatch* match,LLTextBase* base)
|
||||
{
|
||||
if(!match || !base || base->getPlainText())
|
||||
if (!match || match->getSkipProfileIcon() || !base || base->getPlainText())
|
||||
return false;
|
||||
|
||||
LLUUID match_id = match->getID();
|
||||
|
|
@ -4248,7 +4248,7 @@ U32 LLAppViewer::getTextureCacheVersion()
|
|||
U32 LLAppViewer::getDiskCacheVersion()
|
||||
{
|
||||
// Viewer disk cache version intorduced in Simple Cache Viewer, change if the cache format changes.
|
||||
const U32 DISK_CACHE_VERSION = 2;
|
||||
const U32 DISK_CACHE_VERSION = 3;
|
||||
|
||||
return DISK_CACHE_VERSION ;
|
||||
}
|
||||
|
|
@ -4540,6 +4540,7 @@ void LLAppViewer::saveFinalSnapshot()
|
|||
false,
|
||||
gSavedSettings.getBOOL("RenderHUDInSnapshot"),
|
||||
true,
|
||||
false,
|
||||
LLSnapshotModel::SNAPSHOT_TYPE_COLOR,
|
||||
LLSnapshotModel::SNAPSHOT_FORMAT_PNG);
|
||||
mSavedFinalSnapshot = true;
|
||||
|
|
@ -5254,6 +5255,8 @@ void LLAppViewer::sendLogoutRequest()
|
|||
msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
|
||||
gAgent.sendReliableMessage();
|
||||
|
||||
LL_INFOS("Agent") << "Logging out as agent: " << gAgent.getID() << " Session: " << gAgent.getSessionID() << LL_ENDL;
|
||||
|
||||
gLogoutTimer.reset();
|
||||
gLogoutMaxTime = LOGOUT_REQUEST_TIME;
|
||||
mLogoutRequestSent = true;
|
||||
|
|
|
|||
|
|
@ -865,69 +865,11 @@ void write_debug_dx(const std::string& str)
|
|||
|
||||
bool LLAppViewerWin32::initHardwareTest()
|
||||
{
|
||||
//
|
||||
// Do driver verification and initialization based on DirectX
|
||||
// hardware polling and driver versions
|
||||
//
|
||||
if (true == gSavedSettings.getBOOL("ProbeHardwareOnStartup") && false == gSavedSettings.getBOOL("NoHardwareProbe"))
|
||||
{
|
||||
// per DEV-11631 - disable hardware probing for everything
|
||||
// but vram.
|
||||
bool vram_only = true;
|
||||
|
||||
LLSplashScreen::update(LLTrans::getString("StartupDetectingHardware"));
|
||||
|
||||
LL_DEBUGS("AppInit") << "Attempting to poll DirectX for hardware info" << LL_ENDL;
|
||||
gDXHardware.setWriteDebugFunc(write_debug_dx);
|
||||
bool probe_ok = gDXHardware.getInfo(vram_only);
|
||||
|
||||
if (!probe_ok
|
||||
&& gWarningSettings.getBOOL("AboutDirectX9"))
|
||||
{
|
||||
LL_WARNS("AppInit") << "DirectX probe failed, alerting user." << LL_ENDL;
|
||||
|
||||
// Warn them that runnin without DirectX 9 will
|
||||
// not allow us to tell them about driver issues
|
||||
std::ostringstream msg;
|
||||
msg << LLTrans::getString ("MBNoDirectX");
|
||||
S32 button = OSMessageBox(
|
||||
msg.str(),
|
||||
LLTrans::getString("MBWarning"),
|
||||
OSMB_YESNO);
|
||||
if (OSBTN_NO== button)
|
||||
{
|
||||
LL_INFOS("AppInit") << "User quitting after failed DirectX 9 detection" << LL_ENDL;
|
||||
LLWeb::loadURLExternal("http://secondlife.com/support/", false);
|
||||
return false;
|
||||
}
|
||||
gWarningSettings.setBOOL("AboutDirectX9", false);
|
||||
}
|
||||
LL_DEBUGS("AppInit") << "Done polling DirectX for hardware info" << LL_ENDL;
|
||||
|
||||
// Only probe once after installation
|
||||
gSavedSettings.setBOOL("ProbeHardwareOnStartup", false);
|
||||
|
||||
// Disable so debugger can work
|
||||
std::string splash_msg;
|
||||
LLStringUtil::format_map_t args;
|
||||
args["[APP_NAME]"] = LLAppViewer::instance()->getSecondLifeTitle();
|
||||
splash_msg = LLTrans::getString("StartupLoading", args);
|
||||
|
||||
LLSplashScreen::update(splash_msg);
|
||||
}
|
||||
|
||||
if (!restoreErrorTrap())
|
||||
{
|
||||
LL_WARNS("AppInit") << " Someone took over my exception handler (post hardware probe)!" << LL_ENDL;
|
||||
LL_WARNS("AppInit") << " Someone took over my exception handler!" << LL_ENDL;
|
||||
}
|
||||
|
||||
if (gGLManager.mVRAM == 0)
|
||||
{
|
||||
gGLManager.mVRAM = gDXHardware.getVRAM();
|
||||
}
|
||||
|
||||
LL_INFOS("AppInit") << "Detected VRAM: " << gGLManager.mVRAM << LL_ENDL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ LLAvatarList::LLAvatarList(const Params& p)
|
|||
, mShowSpeakingIndicator(p.show_speaking_indicator)
|
||||
, mShowPermissions(p.show_permissions_granted)
|
||||
, mShowCompleteName(false)
|
||||
, mForceCompleteName(false)
|
||||
{
|
||||
setCommitOnSelectionChange(true);
|
||||
|
||||
|
|
@ -177,7 +178,7 @@ void LLAvatarList::setShowIcons(std::string param_name)
|
|||
|
||||
std::string LLAvatarList::getAvatarName(LLAvatarName av_name)
|
||||
{
|
||||
return mShowCompleteName? av_name.getCompleteName(false) : av_name.getDisplayName();
|
||||
return mShowCompleteName? av_name.getCompleteName(false, mForceCompleteName) : av_name.getDisplayName();
|
||||
}
|
||||
|
||||
// virtual
|
||||
|
|
@ -364,7 +365,7 @@ void LLAvatarList::updateAvatarNames()
|
|||
for( std::vector<LLPanel*>::const_iterator it = items.begin(); it != items.end(); it++)
|
||||
{
|
||||
LLAvatarListItem* item = static_cast<LLAvatarListItem*>(*it);
|
||||
item->setShowCompleteName(mShowCompleteName);
|
||||
item->setShowCompleteName(mShowCompleteName, mForceCompleteName);
|
||||
item->updateAvatarName();
|
||||
}
|
||||
mNeedUpdateNames = false;
|
||||
|
|
@ -404,6 +405,11 @@ boost::signals2::connection LLAvatarList::setItemDoubleClickCallback(const mouse
|
|||
return mItemDoubleClickSignal.connect(cb);
|
||||
}
|
||||
|
||||
boost::signals2::connection LLAvatarList::setItemClickedCallback(const mouse_signal_t::slot_type& cb)
|
||||
{
|
||||
return mItemClickedSignal.connect(cb);
|
||||
}
|
||||
|
||||
//virtual
|
||||
S32 LLAvatarList::notifyParent(const LLSD& info)
|
||||
{
|
||||
|
|
@ -418,7 +424,7 @@ S32 LLAvatarList::notifyParent(const LLSD& info)
|
|||
void LLAvatarList::addNewItem(const LLUUID& id, const std::string& name, bool is_online, EAddPosition pos)
|
||||
{
|
||||
LLAvatarListItem* item = new LLAvatarListItem();
|
||||
item->setShowCompleteName(mShowCompleteName);
|
||||
item->setShowCompleteName(mShowCompleteName, mForceCompleteName);
|
||||
// This sets the name as a side effect
|
||||
item->setAvatarId(id, mSessionID, mIgnoreOnlineStatus);
|
||||
item->setOnline(mIgnoreOnlineStatus ? true : is_online);
|
||||
|
|
@ -432,6 +438,7 @@ void LLAvatarList::addNewItem(const LLUUID& id, const std::string& name, bool is
|
|||
|
||||
|
||||
item->setDoubleClickCallback(boost::bind(&LLAvatarList::onItemDoubleClicked, this, _1, _2, _3, _4));
|
||||
item->setMouseDownCallback(boost::bind(&LLAvatarList::onItemClicked, this, _1, _2, _3, _4));
|
||||
|
||||
addItem(item, id, pos);
|
||||
}
|
||||
|
|
@ -550,6 +557,11 @@ void LLAvatarList::onItemDoubleClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask)
|
|||
mItemDoubleClickSignal(ctrl, x, y, mask);
|
||||
}
|
||||
|
||||
void LLAvatarList::onItemClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask)
|
||||
{
|
||||
mItemClickedSignal(ctrl, x, y, mask);
|
||||
}
|
||||
|
||||
bool LLAvatarItemComparator::compare(const LLPanel* item1, const LLPanel* item2) const
|
||||
{
|
||||
const LLAvatarListItem* avatar_item1 = dynamic_cast<const LLAvatarListItem*>(item1);
|
||||
|
|
|
|||
|
|
@ -96,11 +96,13 @@ public:
|
|||
|
||||
boost::signals2::connection setItemDoubleClickCallback(const mouse_signal_t::slot_type& cb);
|
||||
|
||||
boost::signals2::connection setItemClickedCallback(const mouse_signal_t::slot_type& cb);
|
||||
|
||||
virtual S32 notifyParent(const LLSD& info);
|
||||
|
||||
void handleDisplayNamesOptionChanged();
|
||||
|
||||
void setShowCompleteName(bool show) { mShowCompleteName = show;};
|
||||
void setShowCompleteName(bool show, bool force = false) { mShowCompleteName = show; mForceCompleteName = force; };
|
||||
|
||||
protected:
|
||||
void refresh();
|
||||
|
|
@ -113,6 +115,7 @@ protected:
|
|||
void updateLastInteractionTimes();
|
||||
void rebuildNames();
|
||||
void onItemDoubleClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask);
|
||||
void onItemClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask);
|
||||
void updateAvatarNames();
|
||||
|
||||
private:
|
||||
|
|
@ -127,6 +130,7 @@ private:
|
|||
bool mShowSpeakingIndicator;
|
||||
bool mShowPermissions;
|
||||
bool mShowCompleteName;
|
||||
bool mForceCompleteName;
|
||||
|
||||
LLTimer* mLITUpdateTimer; // last interaction time update timer
|
||||
std::string mIconParamName;
|
||||
|
|
@ -138,6 +142,7 @@ private:
|
|||
|
||||
commit_signal_t mRefreshCompleteSignal;
|
||||
mouse_signal_t mItemDoubleClickSignal;
|
||||
mouse_signal_t mItemClickedSignal;
|
||||
};
|
||||
|
||||
/** Abstract comparator for avatar items */
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ LLAvatarListItem::LLAvatarListItem(bool not_from_ui_factory/* = true*/)
|
|||
mShowProfileBtn(true),
|
||||
mShowPermissions(false),
|
||||
mShowCompleteName(false),
|
||||
mForceCompleteName(false),
|
||||
mHovered(false),
|
||||
mAvatarNameCacheConnection(),
|
||||
mGreyOutUsername("")
|
||||
|
|
@ -324,13 +325,12 @@ void LLAvatarListItem::setShowProfileBtn(bool show)
|
|||
|
||||
void LLAvatarListItem::showSpeakingIndicator(bool visible)
|
||||
{
|
||||
// Already done? Then do nothing.
|
||||
if (mSpeakingIndicator->getVisible() == (bool)visible)
|
||||
return;
|
||||
// Disabled to not contradict with SpeakingIndicatorManager functionality. EXT-3976
|
||||
// probably this method should be totally removed.
|
||||
// mSpeakingIndicator->setVisible(visible);
|
||||
// updateChildren();
|
||||
// used only to hide indicator to not contradict with SpeakingIndicatorManager functionality
|
||||
if (mSpeakingIndicator && !visible)
|
||||
{
|
||||
mSpeakingIndicator->setIsActiveChannel(visible);
|
||||
mSpeakingIndicator->setShowParticipantsSpeaking(visible);
|
||||
}
|
||||
}
|
||||
|
||||
void LLAvatarListItem::setAvatarIconVisible(bool visible)
|
||||
|
|
@ -417,8 +417,8 @@ void LLAvatarListItem::onAvatarNameCache(const LLAvatarName& av_name)
|
|||
mAvatarNameCacheConnection.disconnect();
|
||||
|
||||
mGreyOutUsername = "";
|
||||
std::string name_string = mShowCompleteName? av_name.getCompleteName(false) : av_name.getDisplayName();
|
||||
if(av_name.getCompleteName() != av_name.getUserName())
|
||||
std::string name_string = mShowCompleteName? av_name.getCompleteName(false, mForceCompleteName) : av_name.getDisplayName();
|
||||
if(av_name.getCompleteName(false, mForceCompleteName) != av_name.getUserName())
|
||||
{
|
||||
mGreyOutUsername = "[ " + av_name.getUserName(true) + " ]";
|
||||
LLStringUtil::toLower(mGreyOutUsername);
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ public:
|
|||
void setShowPermissions(bool show) { mShowPermissions = show; };
|
||||
void showLastInteractionTime(bool show);
|
||||
void setAvatarIconVisible(bool visible);
|
||||
void setShowCompleteName(bool show) { mShowCompleteName = show;};
|
||||
void setShowCompleteName(bool show, bool force = false) { mShowCompleteName = show; mForceCompleteName = force;};
|
||||
|
||||
const LLUUID& getAvatarId() const;
|
||||
std::string getAvatarName() const;
|
||||
|
|
@ -220,6 +220,7 @@ private:
|
|||
bool mHovered;
|
||||
|
||||
bool mShowCompleteName;
|
||||
bool mForceCompleteName;
|
||||
std::string mGreyOutUsername;
|
||||
|
||||
void fetchAvatarName();
|
||||
|
|
|
|||
|
|
@ -86,7 +86,8 @@ LLConversationViewSession::LLConversationViewSession(const LLConversationViewSes
|
|||
mHasArrow(true),
|
||||
mIsInActiveVoiceChannel(false),
|
||||
mFlashStateOn(false),
|
||||
mFlashStarted(false)
|
||||
mFlashStarted(false),
|
||||
mIsAltFlashColor(false)
|
||||
{
|
||||
mFlashTimer = new LLFlashTimer();
|
||||
mAreChildrenInited = true; // inventory only
|
||||
|
|
@ -157,7 +158,7 @@ void LLConversationViewSession::destroyView()
|
|||
LLFolderViewFolder::destroyView();
|
||||
}
|
||||
|
||||
void LLConversationViewSession::setFlashState(bool flash_state)
|
||||
void LLConversationViewSession::setFlashState(bool flash_state, bool alternate_color)
|
||||
{
|
||||
if (flash_state && !mFlashStateOn)
|
||||
{
|
||||
|
|
@ -170,6 +171,7 @@ void LLConversationViewSession::setFlashState(bool flash_state)
|
|||
|
||||
mFlashStateOn = flash_state;
|
||||
mFlashStarted = false;
|
||||
mIsAltFlashColor = mFlashStateOn && (alternate_color || mIsAltFlashColor);
|
||||
mFlashTimer->stopFlashing();
|
||||
}
|
||||
|
||||
|
|
@ -288,7 +290,8 @@ void LLConversationViewSession::draw()
|
|||
startFlashing();
|
||||
|
||||
// draw highlight for selected items
|
||||
drawHighlight(show_context, true, sHighlightBgColor, sFlashBgColor, sFocusOutlineColor, sMouseOverColor);
|
||||
static LLUIColor alt_color = LLUIColorTable::instance().getColor("MentionFlashBgColor", DEFAULT_WHITE);
|
||||
drawHighlight(show_context, true, sHighlightBgColor, mIsAltFlashColor ? alt_color : sFlashBgColor, sFocusOutlineColor, sMouseOverColor);
|
||||
|
||||
// Draw children if root folder, or any other folder that is open. Do not draw children when animating to closed state or you get rendering overlap.
|
||||
bool draw_children = getRoot() == static_cast<LLFolderViewFolder*>(this) || isOpen();
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ public:
|
|||
|
||||
virtual void refresh();
|
||||
|
||||
/*virtual*/ void setFlashState(bool flash_state);
|
||||
/*virtual*/ void setFlashState(bool flash_state, bool alternate_color = false);
|
||||
void setHighlightState(bool hihglight_state);
|
||||
|
||||
LLFloater* getSessionFloater();
|
||||
|
|
@ -111,6 +111,7 @@ private:
|
|||
LLFlashTimer* mFlashTimer;
|
||||
bool mFlashStateOn;
|
||||
bool mFlashStarted;
|
||||
bool mIsAltFlashColor;
|
||||
|
||||
bool mCollapsedMode;
|
||||
bool mHasArrow;
|
||||
|
|
|
|||
|
|
@ -491,7 +491,6 @@ void LLDrawPoolAvatar::beginImpostor()
|
|||
|
||||
if (!LLPipeline::sReflectionRender)
|
||||
{
|
||||
LLVOAvatar::sRenderDistance = llclamp(LLVOAvatar::sRenderDistance, 16.f, 256.f);
|
||||
LLVOAvatar::sNumVisibleAvatars = 0;
|
||||
}
|
||||
|
||||
|
|
@ -547,7 +546,6 @@ void LLDrawPoolAvatar::beginDeferredImpostor()
|
|||
|
||||
if (!LLPipeline::sReflectionRender)
|
||||
{
|
||||
LLVOAvatar::sRenderDistance = llclamp(LLVOAvatar::sRenderDistance, 16.f, 256.f);
|
||||
LLVOAvatar::sNumVisibleAvatars = 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ public:
|
|||
VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX |
|
||||
LLVertexBuffer::MAP_NORMAL |
|
||||
LLVertexBuffer::MAP_TANGENT | // Only PBR terrain uses this currently
|
||||
LLVertexBuffer::MAP_TEXCOORD0 | // Ownership overlay
|
||||
LLVertexBuffer::MAP_TEXCOORD1
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2563,7 +2563,6 @@ void LLEnvironment::setSharedEnvironment()
|
|||
{
|
||||
clearEnvironment(LLEnvironment::ENV_LOCAL);
|
||||
setSelectedEnvironment(LLEnvironment::ENV_LOCAL);
|
||||
updateEnvironment();
|
||||
}
|
||||
|
||||
void LLEnvironment::setExperienceEnvironment(LLUUID experience_id, LLUUID asset_id, F32 transition_time)
|
||||
|
|
|
|||
|
|
@ -89,9 +89,17 @@ bool LLFloaterBulkPermission::postBuild()
|
|||
{
|
||||
mBulkChangeNextOwnerTransfer = true;
|
||||
}
|
||||
|
||||
mQueueOutputList = getChild<LLScrollListCtrl>("queue output");
|
||||
return true;
|
||||
}
|
||||
|
||||
void LLFloaterBulkPermission::onClose(bool app_quitting)
|
||||
{
|
||||
removeVOInventoryListener();
|
||||
LLFloater::onClose(app_quitting);
|
||||
}
|
||||
|
||||
void LLFloaterBulkPermission::doApply()
|
||||
{
|
||||
// Inspects a stream of selected object contents and adds modifiable ones to the given array.
|
||||
|
|
@ -216,7 +224,7 @@ void LLFloaterBulkPermission::onCommitCopy()
|
|||
bool LLFloaterBulkPermission::start()
|
||||
{
|
||||
// note: number of top-level objects to modify is mObjectIDs.size().
|
||||
getChild<LLScrollListCtrl>("queue output")->setCommentText(getString("start_text"));
|
||||
mQueueOutputList->setCommentText(getString("start_text"));
|
||||
return nextObject();
|
||||
}
|
||||
|
||||
|
|
@ -239,7 +247,7 @@ bool LLFloaterBulkPermission::nextObject()
|
|||
|
||||
if(isDone() && !mDone)
|
||||
{
|
||||
getChild<LLScrollListCtrl>("queue output")->setCommentText(getString("done_text"));
|
||||
mQueueOutputList->setCommentText(getString("done_text"));
|
||||
mDone = true;
|
||||
}
|
||||
return successful_start;
|
||||
|
|
@ -294,8 +302,6 @@ void LLFloaterBulkPermission::doCheckUncheckAll(bool check)
|
|||
|
||||
void LLFloaterBulkPermission::handleInventory(LLViewerObject* viewer_obj, LLInventoryObject::object_list_t* inv)
|
||||
{
|
||||
LLScrollListCtrl* list = getChild<LLScrollListCtrl>("queue output");
|
||||
|
||||
LLInventoryObject::object_list_t::const_iterator it = inv->begin();
|
||||
LLInventoryObject::object_list_t::const_iterator end = inv->end();
|
||||
for ( ; it != end; ++it)
|
||||
|
|
@ -362,7 +368,7 @@ void LLFloaterBulkPermission::handleInventory(LLViewerObject* viewer_obj, LLInve
|
|||
status_text.setArg("[STATUS]", "");
|
||||
}
|
||||
|
||||
list->setCommentText(status_text.getString());
|
||||
mQueueOutputList->setCommentText(status_text.getString());
|
||||
|
||||
//TODO if we are an object inside an object we should check a recuse flag and if set
|
||||
//open the inventory of the object and recurse - Michelle2 Zenovka
|
||||
|
|
|
|||
|
|
@ -41,7 +41,8 @@ class LLFloaterBulkPermission : public LLFloater, public LLVOInventoryListener
|
|||
friend class LLFloaterReg;
|
||||
public:
|
||||
|
||||
bool postBuild();
|
||||
bool postBuild() override;
|
||||
void onClose(bool app_quitting) override;
|
||||
|
||||
private:
|
||||
|
||||
|
|
@ -57,7 +58,7 @@ private:
|
|||
/*virtual*/ void inventoryChanged(LLViewerObject* obj,
|
||||
LLInventoryObject::object_list_t* inv,
|
||||
S32 serial_num,
|
||||
void* queue);
|
||||
void* queue) override;
|
||||
|
||||
// This is called by inventoryChanged
|
||||
void handleInventory(LLViewerObject* viewer_obj,
|
||||
|
|
@ -85,7 +86,7 @@ private:
|
|||
|
||||
private:
|
||||
// UI
|
||||
LLScrollListCtrl* mMessages;
|
||||
LLScrollListCtrl* mQueueOutputList = nullptr;
|
||||
LLButton* mCloseBtn;
|
||||
|
||||
// Object Queue
|
||||
|
|
|
|||
|
|
@ -0,0 +1,184 @@
|
|||
/**
|
||||
* @file llfloaterchatmentionpicker.cpp
|
||||
*
|
||||
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2025, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#include "llviewerprecompiledheaders.h"
|
||||
|
||||
#include "llfloaterchatmentionpicker.h"
|
||||
|
||||
#include "llavatarlist.h"
|
||||
#include "llfloaterimcontainer.h"
|
||||
#include "llchatmentionhelper.h"
|
||||
#include "llparticipantlist.h"
|
||||
|
||||
LLUUID LLFloaterChatMentionPicker::sSessionID(LLUUID::null);
|
||||
|
||||
LLFloaterChatMentionPicker::LLFloaterChatMentionPicker(const LLSD& key)
|
||||
: LLFloater(key), mAvatarList(NULL)
|
||||
{
|
||||
// This floater should hover on top of our dependent (with the dependent having the focus)
|
||||
setFocusStealsFrontmost(false);
|
||||
setBackgroundVisible(false);
|
||||
setAutoFocus(false);
|
||||
}
|
||||
|
||||
bool LLFloaterChatMentionPicker::postBuild()
|
||||
{
|
||||
mAvatarList = getChild<LLAvatarList>("avatar_list");
|
||||
mAvatarList->setShowCompleteName(true, true);
|
||||
mAvatarList->setFocusOnItemClicked(false);
|
||||
mAvatarList->setItemClickedCallback([this](LLUICtrl* ctrl, S32 x, S32 y, MASK mask)
|
||||
{
|
||||
if (LLAvatarListItem* item = dynamic_cast<LLAvatarListItem*>(ctrl))
|
||||
{
|
||||
selectResident(item->getAvatarId());
|
||||
}
|
||||
});
|
||||
mAvatarList->setRefreshCompleteCallback([this](LLUICtrl* ctrl, const LLSD& param)
|
||||
{
|
||||
if (mAvatarList->numSelected() == 0)
|
||||
{
|
||||
mAvatarList->selectFirstItem();
|
||||
}
|
||||
});
|
||||
|
||||
return LLFloater::postBuild();
|
||||
}
|
||||
|
||||
void LLFloaterChatMentionPicker::onOpen(const LLSD& key)
|
||||
{
|
||||
buildAvatarList();
|
||||
mAvatarList->setNameFilter(key.has("av_name") ? key["av_name"].asString() : "");
|
||||
|
||||
gFloaterView->adjustToFitScreen(this, false);
|
||||
}
|
||||
|
||||
uuid_vec_t LLFloaterChatMentionPicker::getParticipantIds()
|
||||
{
|
||||
LLParticipantList* item = dynamic_cast<LLParticipantList*>(LLFloaterIMContainer::getInstance()->getSessionModel(sSessionID));
|
||||
if (!item)
|
||||
{
|
||||
LL_WARNS() << "Participant list is missing" << LL_ENDL;
|
||||
return {};
|
||||
}
|
||||
|
||||
uuid_vec_t avatar_ids;
|
||||
LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = item->getChildrenBegin();
|
||||
LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = item->getChildrenEnd();
|
||||
while (current_participant_model != end_participant_model)
|
||||
{
|
||||
LLConversationItem* participant_model = dynamic_cast<LLConversationItem*>(*current_participant_model);
|
||||
if (participant_model)
|
||||
{
|
||||
avatar_ids.push_back(participant_model->getUUID());
|
||||
}
|
||||
current_participant_model++;
|
||||
}
|
||||
return avatar_ids;
|
||||
}
|
||||
|
||||
void LLFloaterChatMentionPicker::buildAvatarList()
|
||||
{
|
||||
uuid_vec_t& avatar_ids = mAvatarList->getIDs();
|
||||
avatar_ids = getParticipantIds();
|
||||
updateAvatarList(avatar_ids);
|
||||
mAvatarList->setDirty();
|
||||
}
|
||||
|
||||
void LLFloaterChatMentionPicker::selectResident(const LLUUID& id)
|
||||
{
|
||||
if (id.isNull())
|
||||
return;
|
||||
|
||||
setValue(stringize("secondlife:///app/agent/", id.asString(), "/mention "));
|
||||
onCommit();
|
||||
LLChatMentionHelper::instance().hideHelper();
|
||||
}
|
||||
|
||||
void LLFloaterChatMentionPicker::onClose(bool app_quitting)
|
||||
{
|
||||
if (!app_quitting)
|
||||
{
|
||||
LLChatMentionHelper::instance().hideHelper();
|
||||
}
|
||||
}
|
||||
|
||||
bool LLFloaterChatMentionPicker::handleKey(KEY key, MASK mask, bool called_from_parent)
|
||||
{
|
||||
if (mask == MASK_NONE)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case KEY_UP:
|
||||
case KEY_DOWN:
|
||||
return mAvatarList->handleKey(key, mask, called_from_parent);
|
||||
case KEY_RETURN:
|
||||
case KEY_TAB:
|
||||
selectResident(mAvatarList->getSelectedUUID());
|
||||
return true;
|
||||
case KEY_ESCAPE:
|
||||
LLChatMentionHelper::instance().hideHelper();
|
||||
return true;
|
||||
case KEY_LEFT:
|
||||
case KEY_RIGHT:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return LLFloater::handleKey(key, mask, called_from_parent);
|
||||
}
|
||||
|
||||
void LLFloaterChatMentionPicker::goneFromFront()
|
||||
{
|
||||
LLChatMentionHelper::instance().hideHelper();
|
||||
}
|
||||
|
||||
void LLFloaterChatMentionPicker::updateSessionID(LLUUID session_id)
|
||||
{
|
||||
sSessionID = session_id;
|
||||
|
||||
LLParticipantList* item = dynamic_cast<LLParticipantList*>(LLFloaterIMContainer::getInstance()->getSessionModel(sSessionID));
|
||||
if (!item)
|
||||
{
|
||||
LL_WARNS() << "Participant list is missing" << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
|
||||
uuid_vec_t avatar_ids = getParticipantIds();
|
||||
updateAvatarList(avatar_ids);
|
||||
}
|
||||
|
||||
void LLFloaterChatMentionPicker::updateAvatarList(uuid_vec_t& avatar_ids)
|
||||
{
|
||||
std::vector<std::string> av_names;
|
||||
for (auto& id : avatar_ids)
|
||||
{
|
||||
LLAvatarName av_name;
|
||||
LLAvatarNameCache::get(id, &av_name);
|
||||
av_names.push_back(utf8str_tolower(av_name.getAccountName()));
|
||||
av_names.push_back(utf8str_tolower(av_name.getDisplayName()));
|
||||
}
|
||||
LLChatMentionHelper::instance().updateAvatarList(av_names);
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* @file llfloaterchatmentionpicker.h
|
||||
*
|
||||
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2025, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#ifndef LLFLOATERCHATMENTIONPICKER_H
|
||||
#define LLFLOATERCHATMENTIONPICKER_H
|
||||
|
||||
#include "llfloater.h"
|
||||
|
||||
class LLAvatarList;
|
||||
|
||||
class LLFloaterChatMentionPicker : public LLFloater
|
||||
{
|
||||
public:
|
||||
LLFloaterChatMentionPicker(const LLSD& key);
|
||||
|
||||
virtual bool postBuild() override;
|
||||
virtual void goneFromFront() override;
|
||||
|
||||
void buildAvatarList();
|
||||
|
||||
static uuid_vec_t getParticipantIds();
|
||||
static void updateSessionID(LLUUID session_id);
|
||||
static void updateAvatarList(uuid_vec_t& avatar_ids);
|
||||
|
||||
private:
|
||||
|
||||
void onOpen(const LLSD& key) override;
|
||||
void onClose(bool app_quitting) override;
|
||||
virtual bool handleKey(KEY key, MASK mask, bool called_from_parent) override;
|
||||
void selectResident(const LLUUID& id);
|
||||
|
||||
static LLUUID sSessionID;
|
||||
LLAvatarList* mAvatarList;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -495,7 +495,6 @@ void LLFloaterEditExtDayCycle::setEditDayCycle(const LLSettingsDay::ptr_t &pday)
|
|||
|
||||
updateEditEnvironment();
|
||||
LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_EDIT, LLEnvironment::TRANSITION_INSTANT);
|
||||
LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT);
|
||||
synchronizeTabs();
|
||||
updateTabs();
|
||||
refresh();
|
||||
|
|
@ -824,7 +823,6 @@ void LLFloaterEditExtDayCycle::onClearTrack()
|
|||
|
||||
updateEditEnvironment();
|
||||
LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_EDIT, LLEnvironment::TRANSITION_INSTANT);
|
||||
LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT);
|
||||
synchronizeTabs();
|
||||
updateTabs();
|
||||
refresh();
|
||||
|
|
|
|||
|
|
@ -242,9 +242,7 @@ void LLFloaterEnvironmentAdjust::captureCurrentEnvironment()
|
|||
environment.setEnvironment(LLEnvironment::ENV_LOCAL, mLiveSky, FLOATER_ENVIRONMENT_UPDATE);
|
||||
environment.setEnvironment(LLEnvironment::ENV_LOCAL, mLiveWater, FLOATER_ENVIRONMENT_UPDATE);
|
||||
}
|
||||
environment.setSelectedEnvironment(LLEnvironment::ENV_LOCAL);
|
||||
environment.updateEnvironment(LLEnvironment::TRANSITION_INSTANT);
|
||||
|
||||
environment.setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
|
||||
}
|
||||
|
||||
void LLFloaterEnvironmentAdjust::onButtonReset()
|
||||
|
|
@ -258,7 +256,6 @@ void LLFloaterEnvironmentAdjust::onButtonReset()
|
|||
this->closeFloater();
|
||||
LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_LOCAL);
|
||||
LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL);
|
||||
LLEnvironment::instance().updateEnvironment();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2302,14 +2302,14 @@ bool LLFloaterIMContainer::isConversationLoggingAllowed()
|
|||
return gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 0;
|
||||
}
|
||||
|
||||
void LLFloaterIMContainer::flashConversationItemWidget(const LLUUID& session_id, bool is_flashes)
|
||||
void LLFloaterIMContainer::flashConversationItemWidget(const LLUUID& session_id, bool is_flashes, bool alternate_color)
|
||||
{
|
||||
//Finds the conversation line item to flash using the session_id
|
||||
LLConversationViewSession * widget = dynamic_cast<LLConversationViewSession *>(get_ptr_in_map(mConversationsWidgets,session_id));
|
||||
|
||||
if (widget)
|
||||
{
|
||||
widget->setFlashState(is_flashes);
|
||||
widget->setFlashState(is_flashes, alternate_color);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ public:
|
|||
void reSelectConversation();
|
||||
void updateSpeakBtnState();
|
||||
static bool isConversationLoggingAllowed();
|
||||
void flashConversationItemWidget(const LLUUID& session_id, bool is_flashes);
|
||||
void flashConversationItemWidget(const LLUUID& session_id, bool is_flashes, bool alternate_color = false);
|
||||
void highlightConversationItemWidget(const LLUUID& session_id, bool is_highlighted);
|
||||
bool isScrolledOutOfSight(LLConversationViewSession* conversation_item_widget);
|
||||
boost::signals2::connection mMicroChangedSignal;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@
|
|||
|
||||
#include "llfirstuse.h"
|
||||
#include "llfloaterimnearbychat.h"
|
||||
#include "llfloaterimnearbychatlistener.h"
|
||||
#include "llagent.h" // gAgent
|
||||
#include "llgesturemgr.h"
|
||||
#include "llmultigesture.h"
|
||||
|
|
@ -71,6 +72,8 @@
|
|||
|
||||
S32 LLFloaterIMNearbyChat::sLastSpecialChatChannel = 0;
|
||||
|
||||
static LLFloaterIMNearbyChatListener sChatListener;
|
||||
|
||||
constexpr S32 EXPANDED_HEIGHT = 266;
|
||||
constexpr S32 COLLAPSED_HEIGHT = 60;
|
||||
constexpr S32 EXPANDED_MIN_HEIGHT = 150;
|
||||
|
|
@ -583,7 +586,7 @@ void LLFloaterIMNearbyChat::sendChat( EChatType type )
|
|||
{
|
||||
if (mInputEditor)
|
||||
{
|
||||
LLWString text = mInputEditor->getWText();
|
||||
LLWString text = mInputEditor->getConvertedText();
|
||||
LLWStringUtil::trim(text);
|
||||
LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines.
|
||||
if (!text.empty())
|
||||
|
|
|
|||
|
|
@ -34,12 +34,12 @@
|
|||
#include "llagent.h"
|
||||
#include "llchat.h"
|
||||
#include "llviewercontrol.h"
|
||||
#include "stringize.h"
|
||||
|
||||
static const F32 CHAT_THROTTLE_PERIOD = 1.f;
|
||||
|
||||
LLFloaterIMNearbyChatListener::LLFloaterIMNearbyChatListener(LLFloaterIMNearbyChat & chatbar)
|
||||
: LLEventAPI("LLChatBar",
|
||||
"LLChatBar listener to (e.g.) sendChat, etc."),
|
||||
mChatbar(chatbar)
|
||||
LLFloaterIMNearbyChatListener::LLFloaterIMNearbyChatListener() :
|
||||
LLEventAPI("LLChatBar", "LLChatBar listener to (e.g.) sendChat, etc.")
|
||||
{
|
||||
add("sendChat",
|
||||
"Send chat to the simulator:\n"
|
||||
|
|
@ -49,10 +49,18 @@ LLFloaterIMNearbyChatListener::LLFloaterIMNearbyChatListener(LLFloaterIMNearbyCh
|
|||
&LLFloaterIMNearbyChatListener::sendChat);
|
||||
}
|
||||
|
||||
|
||||
// "sendChat" command
|
||||
void LLFloaterIMNearbyChatListener::sendChat(LLSD const & chat_data) const
|
||||
void LLFloaterIMNearbyChatListener::sendChat(LLSD const& chat_data)
|
||||
{
|
||||
F64 cur_time = LLTimer::getElapsedSeconds();
|
||||
|
||||
if (cur_time < mLastThrottleTime + CHAT_THROTTLE_PERIOD)
|
||||
{
|
||||
LL_WARNS("LLFloaterIMNearbyChatListener") << "'sendChat' was throttled" << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
mLastThrottleTime = cur_time;
|
||||
|
||||
// Extract the data
|
||||
std::string chat_text = chat_data["message"].asString();
|
||||
|
||||
|
|
@ -81,20 +89,12 @@ void LLFloaterIMNearbyChatListener::sendChat(LLSD const & chat_data) const
|
|||
}
|
||||
|
||||
// Have to prepend /42 style channel numbers
|
||||
std::string chat_to_send;
|
||||
if (channel == 0)
|
||||
if (channel)
|
||||
{
|
||||
chat_to_send = chat_text;
|
||||
}
|
||||
else
|
||||
{
|
||||
chat_to_send += "/";
|
||||
chat_to_send += chat_data["channel"].asString();
|
||||
chat_to_send += " ";
|
||||
chat_to_send += chat_text;
|
||||
chat_text = stringize("/", chat_data["channel"].asString(), " ", chat_text);
|
||||
}
|
||||
|
||||
// Send it as if it was typed in
|
||||
mChatbar.sendChatFromViewer(chat_to_send, type_o_chat, ((bool)(channel == 0)) && gSavedSettings.getBOOL("PlayChatAnim"));
|
||||
LLFloaterIMNearbyChat::sendChatFromViewer(chat_text, type_o_chat, (channel == 0) && gSavedSettings.getBOOL("PlayChatAnim"));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,12 +38,12 @@ class LLFloaterIMNearbyChat;
|
|||
class LLFloaterIMNearbyChatListener : public LLEventAPI
|
||||
{
|
||||
public:
|
||||
LLFloaterIMNearbyChatListener(LLFloaterIMNearbyChat & chatbar);
|
||||
LLFloaterIMNearbyChatListener();
|
||||
|
||||
private:
|
||||
void sendChat(LLSD const & chat_data) const;
|
||||
void sendChat(LLSD const & chat_data);
|
||||
|
||||
LLFloaterIMNearbyChat & mChatbar;
|
||||
F64 mLastThrottleTime{0};
|
||||
};
|
||||
|
||||
#endif // LL_LLFLOATERIMNEARBYCHATLISTENER_H
|
||||
|
|
|
|||
|
|
@ -251,7 +251,7 @@ void LLFloaterIMSession::sendMsgFromInputEditor()
|
|||
{
|
||||
if (mInputEditor)
|
||||
{
|
||||
LLWString text = mInputEditor->getWText();
|
||||
LLWString text = mInputEditor->getConvertedText();
|
||||
LLWStringUtil::trim(text);
|
||||
LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines.
|
||||
if(!text.empty())
|
||||
|
|
|
|||
|
|
@ -35,10 +35,12 @@
|
|||
#include "llavatariconctrl.h"
|
||||
#include "llchatentry.h"
|
||||
#include "llchathistory.h"
|
||||
#include "llfloaterchatmentionpicker.h"
|
||||
#include "llchiclet.h"
|
||||
#include "llchicletbar.h"
|
||||
#include "lldraghandle.h"
|
||||
#include "llemojidictionary.h"
|
||||
#include "llemojihelper.h"
|
||||
#include "llfloaterreg.h"
|
||||
#include "llfloateremojipicker.h"
|
||||
#include "llfloaterimsession.h"
|
||||
|
|
@ -104,6 +106,7 @@ LLFloaterIMSessionTab::~LLFloaterIMSessionTab()
|
|||
{
|
||||
delete mRefreshTimer;
|
||||
LLIMMgr::instance().removeSessionObserver(this);
|
||||
mEmojiCloseConn.disconnect();
|
||||
|
||||
LLFloaterIMContainer* im_container = LLFloaterIMContainer::findInstance();
|
||||
if (im_container)
|
||||
|
|
@ -300,6 +303,8 @@ bool LLFloaterIMSessionTab::postBuild()
|
|||
|
||||
mEmojiPickerShowBtn = getChild<LLButton>("emoji_picker_show_btn");
|
||||
mEmojiPickerShowBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiPickerShowBtnClicked(); });
|
||||
mEmojiPickerShowBtn->setMouseDownCallback([this](LLUICtrl*, const LLSD&) { onEmojiPickerShowBtnDown(); });
|
||||
mEmojiCloseConn = LLEmojiHelper::instance().setCloseCallback([this](LLUICtrl*, const LLSD&) { onEmojiPickerClosed(); });
|
||||
|
||||
mGearBtn = getChild<LLButton>("gear_btn");
|
||||
mAddBtn = getChild<LLButton>("add_btn");
|
||||
|
|
@ -482,6 +487,7 @@ void LLFloaterIMSessionTab::onFocusReceived()
|
|||
LLIMModel::instance().sendNoUnreadMessages(mSessionID);
|
||||
}
|
||||
|
||||
LLFloaterChatMentionPicker::updateSessionID(mSessionID);
|
||||
super::onFocusReceived();
|
||||
}
|
||||
|
||||
|
|
@ -532,8 +538,43 @@ void LLFloaterIMSessionTab::onEmojiRecentPanelToggleBtnClicked()
|
|||
|
||||
void LLFloaterIMSessionTab::onEmojiPickerShowBtnClicked()
|
||||
{
|
||||
mInputEditor->setFocus(true);
|
||||
mInputEditor->showEmojiHelper();
|
||||
if (!mEmojiPickerShowBtn->getToggleState())
|
||||
{
|
||||
mInputEditor->hideEmojiHelper();
|
||||
mInputEditor->setFocus(true);
|
||||
mInputEditor->showEmojiHelper();
|
||||
mEmojiPickerShowBtn->setToggleState(true); // in case hideEmojiHelper closed a visible instance
|
||||
}
|
||||
else
|
||||
{
|
||||
mInputEditor->hideEmojiHelper();
|
||||
mEmojiPickerShowBtn->setToggleState(false);
|
||||
}
|
||||
}
|
||||
|
||||
void LLFloaterIMSessionTab::onEmojiPickerShowBtnDown()
|
||||
{
|
||||
if (mEmojiHelperLastCallbackFrame == LLFrameTimer::getFrameCount())
|
||||
{
|
||||
// Helper gets closed by focus lost event on Down before before onEmojiPickerShowBtnDown
|
||||
// triggers.
|
||||
// If this condition is true, user pressed button and it was 'toggled' during press,
|
||||
// restore 'toggled' state so that button will not reopen helper.
|
||||
mEmojiPickerShowBtn->setToggleState(true);
|
||||
}
|
||||
}
|
||||
|
||||
void LLFloaterIMSessionTab::onEmojiPickerClosed()
|
||||
{
|
||||
if (mEmojiPickerShowBtn->getToggleState())
|
||||
{
|
||||
mEmojiPickerShowBtn->setToggleState(false);
|
||||
// Helper gets closed by focus lost event on Down before onEmojiPickerShowBtnDown
|
||||
// triggers. If mEmojiHelperLastCallbackFrame is set and matches Down, means close
|
||||
// was triggered by user's press.
|
||||
// A bit hacky, but I can't think of a better way to handle this without rewriting helper.
|
||||
mEmojiHelperLastCallbackFrame = LLFrameTimer::getFrameCount();
|
||||
}
|
||||
}
|
||||
|
||||
void LLFloaterIMSessionTab::initEmojiRecentPanel()
|
||||
|
|
|
|||
|
|
@ -235,6 +235,8 @@ private:
|
|||
|
||||
void onEmojiRecentPanelToggleBtnClicked();
|
||||
void onEmojiPickerShowBtnClicked();
|
||||
void onEmojiPickerShowBtnDown();
|
||||
void onEmojiPickerClosed();
|
||||
void initEmojiRecentPanel();
|
||||
void onEmojiRecentPanelFocusReceived();
|
||||
void onEmojiRecentPanelFocusLost();
|
||||
|
|
@ -249,6 +251,9 @@ private:
|
|||
S32 mInputEditorPad;
|
||||
S32 mChatLayoutPanelHeight;
|
||||
S32 mFloaterHeight;
|
||||
|
||||
boost::signals2::connection mEmojiCloseConn;
|
||||
U32 mEmojiHelperLastCallbackFrame = { 0 };
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -43,12 +43,28 @@ bool LLFloaterNewFeatureNotification::postBuild()
|
|||
setCanDrag(false);
|
||||
getChild<LLButton>("close_btn")->setCommitCallback(boost::bind(&LLFloaterNewFeatureNotification::onCloseBtn, this));
|
||||
|
||||
const std::string title_txt = "title_txt";
|
||||
const std::string dsc_txt = "description_txt";
|
||||
std::string feature = "_" + getKey().asString();
|
||||
if (getKey().isString())
|
||||
{
|
||||
const std::string title_txt = "title_txt";
|
||||
const std::string dsc_txt = "description_txt";
|
||||
|
||||
getChild<LLUICtrl>(title_txt)->setValue(getString(title_txt + feature));
|
||||
getChild<LLUICtrl>(dsc_txt)->setValue(getString(dsc_txt + feature));
|
||||
std::string feature = "_" + getKey().asString();
|
||||
if (hasString(title_txt + feature))
|
||||
{
|
||||
getChild<LLUICtrl>(title_txt)->setValue(getString(title_txt + feature));
|
||||
getChild<LLUICtrl>(dsc_txt)->setValue(getString(dsc_txt + feature));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Show blank
|
||||
LL_WARNS() << "Feature \"" << getKey().asString() << "\" not found for feature notification" << LL_ENDL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Show blank
|
||||
LL_WARNS() << "Feature notification without a feature" << LL_ENDL;
|
||||
}
|
||||
|
||||
if (getKey().asString() == "gltf")
|
||||
{
|
||||
|
|
|
|||
|
|
@ -60,12 +60,13 @@ LLPanelSnapshot* LLFloaterSnapshot::Impl::getActivePanel(LLFloaterSnapshotBase*
|
|||
{
|
||||
LLSideTrayPanelContainer* panel_container = floater->getChild<LLSideTrayPanelContainer>("panel_container");
|
||||
LLPanelSnapshot* active_panel = dynamic_cast<LLPanelSnapshot*>(panel_container->getCurrentPanel());
|
||||
if (!active_panel)
|
||||
{
|
||||
LL_WARNS() << "No snapshot active panel, current panel index: " << panel_container->getCurrentPanelIndex() << LL_ENDL;
|
||||
}
|
||||
|
||||
if (!ok_if_not_found)
|
||||
{
|
||||
if (!active_panel)
|
||||
{
|
||||
LL_WARNS() << "No snapshot active panel, current panel index: " << panel_container->getCurrentPanelIndex() << LL_ENDL;
|
||||
}
|
||||
llassert_always(active_panel != NULL);
|
||||
}
|
||||
return active_panel;
|
||||
|
|
@ -516,34 +517,13 @@ void LLFloaterSnapshotBase::ImplBase::onClickFilter(LLUICtrl *ctrl, void* data)
|
|||
}
|
||||
|
||||
// static
|
||||
void LLFloaterSnapshotBase::ImplBase::onClickUICheck(LLUICtrl *ctrl, void* data)
|
||||
void LLFloaterSnapshotBase::ImplBase::onClickDisplaySetting(LLUICtrl* ctrl, void* data)
|
||||
{
|
||||
LLCheckBoxCtrl *check = (LLCheckBoxCtrl *)ctrl;
|
||||
gSavedSettings.setBOOL( "RenderUIInSnapshot", check->get() );
|
||||
|
||||
LLFloaterSnapshot *view = (LLFloaterSnapshot *)data;
|
||||
LLFloaterSnapshot* view = (LLFloaterSnapshot*)data;
|
||||
if (view)
|
||||
{
|
||||
LLSnapshotLivePreview* previewp = view->getPreviewView();
|
||||
if(previewp)
|
||||
{
|
||||
previewp->updateSnapshot(true, true);
|
||||
}
|
||||
view->impl->updateControls(view);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void LLFloaterSnapshotBase::ImplBase::onClickHUDCheck(LLUICtrl *ctrl, void* data)
|
||||
{
|
||||
LLCheckBoxCtrl *check = (LLCheckBoxCtrl *)ctrl;
|
||||
gSavedSettings.setBOOL( "RenderHUDInSnapshot", check->get() );
|
||||
|
||||
LLFloaterSnapshot *view = (LLFloaterSnapshot *)data;
|
||||
if (view)
|
||||
{
|
||||
LLSnapshotLivePreview* previewp = view->getPreviewView();
|
||||
if(previewp)
|
||||
if (previewp)
|
||||
{
|
||||
previewp->updateSnapshot(true, true);
|
||||
}
|
||||
|
|
@ -1002,11 +982,9 @@ bool LLFloaterSnapshot::postBuild()
|
|||
mSucceessLblPanel = getChild<LLUICtrl>("succeeded_panel");
|
||||
mFailureLblPanel = getChild<LLUICtrl>("failed_panel");
|
||||
|
||||
childSetCommitCallback("ui_check", ImplBase::onClickUICheck, this);
|
||||
getChild<LLUICtrl>("ui_check")->setValue(gSavedSettings.getBOOL("RenderUIInSnapshot"));
|
||||
|
||||
childSetCommitCallback("hud_check", ImplBase::onClickHUDCheck, this);
|
||||
getChild<LLUICtrl>("hud_check")->setValue(gSavedSettings.getBOOL("RenderHUDInSnapshot"));
|
||||
childSetCommitCallback("ui_check", ImplBase::onClickDisplaySetting, this);
|
||||
childSetCommitCallback("balance_check", ImplBase::onClickDisplaySetting, this);
|
||||
childSetCommitCallback("hud_check", ImplBase::onClickDisplaySetting, this);
|
||||
|
||||
((Impl*)impl)->setAspectRatioCheckboxValue(this, gSavedSettings.getBOOL("KeepAspectForSnapshot"));
|
||||
|
||||
|
|
|
|||
|
|
@ -103,8 +103,7 @@ public:
|
|||
static void onClickAutoSnap(LLUICtrl *ctrl, void* data);
|
||||
static void onClickNoPost(LLUICtrl *ctrl, void* data);
|
||||
static void onClickFilter(LLUICtrl *ctrl, void* data);
|
||||
static void onClickUICheck(LLUICtrl *ctrl, void* data);
|
||||
static void onClickHUDCheck(LLUICtrl *ctrl, void* data);
|
||||
static void onClickDisplaySetting(LLUICtrl *ctrl, void* data);
|
||||
static void onCommitFreezeFrame(LLUICtrl* ctrl, void* data);
|
||||
|
||||
virtual LLPanelSnapshot* getActivePanel(LLFloaterSnapshotBase* floater, bool ok_if_not_found = true) = 0;
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
//
|
||||
// Globals
|
||||
//
|
||||
static GroupChatListener sGroupChatListener;
|
||||
static LLGroupChatListener sGroupChatListener;
|
||||
|
||||
class LLGroupHandler : public LLCommandHandler
|
||||
{
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@
|
|||
#include "llviewerregion.h"
|
||||
#include "llcorehttputil.h"
|
||||
#include "lluiusage.h"
|
||||
#include "llurlregistry.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
|
|
@ -197,6 +198,9 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg)
|
|||
LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(session_id);
|
||||
bool store_dnd_message = false; // flag storage of a dnd message
|
||||
bool is_session_focused = session_floater->isTornOff() && session_floater->hasFocus();
|
||||
bool contains_mention = LLUrlRegistry::getInstance()->containsAgentMention(msg["message"].asString());
|
||||
static LLCachedControl<bool> play_snd_mention_pref(gSavedSettings, "PlaySoundChatMention", false);
|
||||
bool play_snd_mention = contains_mention && play_snd_mention_pref && (msg["source_type"].asInteger() != CHAT_SOURCE_OBJECT);
|
||||
if (!LLFloater::isVisible(im_box) || im_box->isMinimized())
|
||||
{
|
||||
conversations_floater_status = CLOSED;
|
||||
|
|
@ -230,7 +234,7 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg)
|
|||
else
|
||||
{
|
||||
user_preferences = gSavedSettings.getString("NotificationNearbyChatOptions");
|
||||
if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNearbyChatIM")))
|
||||
if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNearbyChatIM")) && !play_snd_mention)
|
||||
{
|
||||
make_ui_sound("UISndNewIncomingIMSession");
|
||||
}
|
||||
|
|
@ -241,7 +245,7 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg)
|
|||
if (LLAvatarTracker::instance().isBuddy(participant_id))
|
||||
{
|
||||
user_preferences = gSavedSettings.getString("NotificationFriendIMOptions");
|
||||
if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundFriendIM")))
|
||||
if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundFriendIM")) && !play_snd_mention)
|
||||
{
|
||||
make_ui_sound("UISndNewIncomingIMSession");
|
||||
}
|
||||
|
|
@ -249,7 +253,7 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg)
|
|||
else
|
||||
{
|
||||
user_preferences = gSavedSettings.getString("NotificationNonFriendIMOptions");
|
||||
if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNonFriendIM")))
|
||||
if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNonFriendIM")) && !play_snd_mention)
|
||||
{
|
||||
make_ui_sound("UISndNewIncomingIMSession");
|
||||
}
|
||||
|
|
@ -258,7 +262,7 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg)
|
|||
else if (session->isAdHocSessionType())
|
||||
{
|
||||
user_preferences = gSavedSettings.getString("NotificationConferenceIMOptions");
|
||||
if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundConferenceIM")))
|
||||
if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundConferenceIM")) && !play_snd_mention)
|
||||
{
|
||||
make_ui_sound("UISndNewIncomingIMSession");
|
||||
}
|
||||
|
|
@ -266,11 +270,18 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg)
|
|||
else if(session->isGroupSessionType())
|
||||
{
|
||||
user_preferences = gSavedSettings.getString("NotificationGroupChatOptions");
|
||||
if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundGroupChatIM")))
|
||||
if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundGroupChatIM")) && !play_snd_mention)
|
||||
{
|
||||
make_ui_sound("UISndNewIncomingIMSession");
|
||||
}
|
||||
}
|
||||
if (play_snd_mention)
|
||||
{
|
||||
if (!gAgent.isDoNotDisturb())
|
||||
{
|
||||
make_ui_sound("UISndChatMention");
|
||||
}
|
||||
}
|
||||
|
||||
// actions:
|
||||
|
||||
|
|
@ -323,7 +334,7 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg)
|
|||
if ("openconversations" == user_preferences
|
||||
|| ON_TOP == conversations_floater_status
|
||||
|| ("toast" == user_preferences && ON_TOP != conversations_floater_status)
|
||||
|| ("flash" == user_preferences && (CLOSED == conversations_floater_status
|
||||
|| (("flash" == user_preferences || contains_mention) && (CLOSED == conversations_floater_status
|
||||
|| NOT_ON_TOP == conversations_floater_status))
|
||||
|| is_dnd_msg)
|
||||
{
|
||||
|
|
@ -343,7 +354,7 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg)
|
|||
}
|
||||
else
|
||||
{
|
||||
im_box->flashConversationItemWidget(session_id, true);
|
||||
im_box->flashConversationItemWidget(session_id, true, contains_mention);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3249,7 +3260,11 @@ void LLIMMgr::addMessage(
|
|||
//Play sound for new conversations
|
||||
if (!skip_message && !gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNewConversation")))
|
||||
{
|
||||
make_ui_sound("UISndNewIncomingIMSession");
|
||||
static LLCachedControl<bool> play_snd_mention_pref(gSavedSettings, "PlaySoundChatMention", false);
|
||||
if (!play_snd_mention_pref || !LLUrlRegistry::getInstance()->containsAgentMention(msg))
|
||||
{
|
||||
make_ui_sound("UISndNewIncomingIMSession");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -3269,7 +3284,7 @@ void LLIMMgr::addMessage(
|
|||
{
|
||||
LLFloaterReg::showInstance("im_container");
|
||||
LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container")->
|
||||
flashConversationItemWidget(new_session_id, true);
|
||||
flashConversationItemWidget(new_session_id, true, LLUrlRegistry::getInstance()->containsAgentMention(msg));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@ static bool check_item(const LLUUID& item_id,
|
|||
LLInventoryFilter* filter);
|
||||
|
||||
// Helper functions
|
||||
|
||||
bool isAddAction(const std::string& action)
|
||||
{
|
||||
return ("wear" == action || "attach" == action || "activate" == action);
|
||||
|
|
@ -2665,6 +2664,7 @@ bool LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat,
|
|||
//
|
||||
|
||||
bool is_movable = true;
|
||||
bool create_outfit = false;
|
||||
|
||||
if (is_movable && (marketplacelistings_id == cat_id))
|
||||
{
|
||||
|
|
@ -2697,14 +2697,24 @@ bool LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat,
|
|||
U32 max_items_to_wear = gSavedSettings.getU32("WearFolderLimit");
|
||||
if (is_movable && move_is_into_outfit)
|
||||
{
|
||||
if (mUUID == my_outifts_id)
|
||||
if ((inv_cat->getPreferredType() != LLFolderType::FT_NONE) && (inv_cat->getPreferredType() != LLFolderType::FT_OUTFIT))
|
||||
{
|
||||
tooltip_msg = LLTrans::getString("TooltipCantCreateOutfit");
|
||||
is_movable = false;
|
||||
}
|
||||
else if (mUUID == my_outifts_id)
|
||||
{
|
||||
if (source != LLToolDragAndDrop::SOURCE_AGENT || move_is_from_marketplacelistings)
|
||||
{
|
||||
tooltip_msg = LLTrans::getString("TooltipOutfitNotInInventory");
|
||||
is_movable = false;
|
||||
}
|
||||
else if (can_move_to_my_outfits(model, inv_cat, max_items_to_wear))
|
||||
else if (can_move_to_my_outfits_as_outfit(model, inv_cat, max_items_to_wear))
|
||||
{
|
||||
is_movable = true;
|
||||
create_outfit = true;
|
||||
}
|
||||
else if (can_move_to_my_outfits_as_subfolder(model, inv_cat))
|
||||
{
|
||||
is_movable = true;
|
||||
}
|
||||
|
|
@ -2714,13 +2724,44 @@ bool LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat,
|
|||
is_movable = false;
|
||||
}
|
||||
}
|
||||
else if(getCategory() && getCategory()->getPreferredType() == LLFolderType::FT_NONE)
|
||||
else if (!getCategory())
|
||||
{
|
||||
is_movable = ((inv_cat->getPreferredType() == LLFolderType::FT_NONE) || (inv_cat->getPreferredType() == LLFolderType::FT_OUTFIT));
|
||||
is_movable = false;
|
||||
tooltip_msg = LLTrans::getString("TooltipCantCreateOutfit");
|
||||
}
|
||||
else
|
||||
{
|
||||
is_movable = false;
|
||||
EMyOutfitsSubfolderType dest_res = myoutfit_object_subfolder_type(model, mUUID, my_outifts_id);
|
||||
EMyOutfitsSubfolderType inv_res = myoutfit_object_subfolder_type(model, cat_id, my_outifts_id);
|
||||
if ((dest_res == MY_OUTFITS_OUTFIT || dest_res == MY_OUTFITS_SUBOUTFIT) && inv_res == MY_OUTFITS_OUTFIT)
|
||||
{
|
||||
is_movable = false;
|
||||
tooltip_msg = LLTrans::getString("TooltipCantMoveOutfitIntoOutfit");
|
||||
}
|
||||
else if (dest_res == MY_OUTFITS_OUTFIT || dest_res == MY_OUTFITS_SUBOUTFIT)
|
||||
{
|
||||
is_movable = false;
|
||||
tooltip_msg = LLTrans::getString("TooltipCantCreateOutfit");
|
||||
}
|
||||
else if (dest_res == MY_OUTFITS_SUBFOLDER && inv_res == MY_OUTFITS_SUBOUTFIT)
|
||||
{
|
||||
is_movable = false;
|
||||
tooltip_msg = LLTrans::getString("TooltipCantCreateOutfit");
|
||||
}
|
||||
else if (can_move_to_my_outfits_as_outfit(model, inv_cat, max_items_to_wear))
|
||||
{
|
||||
is_movable = true;
|
||||
create_outfit = true;
|
||||
}
|
||||
else if (can_move_to_my_outfits_as_subfolder(model, inv_cat))
|
||||
{
|
||||
is_movable = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
is_movable = false;
|
||||
tooltip_msg = LLTrans::getString("TooltipCantCreateOutfit");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_movable && move_is_into_current_outfit && is_link)
|
||||
|
|
@ -2912,9 +2953,81 @@ bool LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat,
|
|||
|
||||
if (mUUID == my_outifts_id)
|
||||
{
|
||||
// Category can contains objects,
|
||||
// create a new folder and populate it with links to original objects
|
||||
dropToMyOutfits(inv_cat, cb);
|
||||
EMyOutfitsSubfolderType inv_res = myoutfit_object_subfolder_type(model, cat_id, my_outifts_id);
|
||||
if (inv_res == MY_OUTFITS_SUBFOLDER || inv_res == MY_OUTFITS_OUTFIT || !create_outfit)
|
||||
{
|
||||
LLInvFVBridge::changeCategoryParent(
|
||||
model,
|
||||
(LLViewerInventoryCategory*)inv_cat,
|
||||
mUUID,
|
||||
false);
|
||||
if (cb) cb->fire(inv_cat->getUUID());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Moving from inventory
|
||||
// create a new folder and populate it with links to original objects
|
||||
dropToMyOutfits(inv_cat, cb);
|
||||
}
|
||||
}
|
||||
else if (move_is_into_my_outfits)
|
||||
{
|
||||
EMyOutfitsSubfolderType dest_res = myoutfit_object_subfolder_type(model, mUUID, my_outifts_id);
|
||||
EMyOutfitsSubfolderType inv_res = myoutfit_object_subfolder_type(model, cat_id, my_outifts_id);
|
||||
switch (inv_res)
|
||||
{
|
||||
case MY_OUTFITS_NO:
|
||||
// Moning from outside outfits into outfits
|
||||
if (dest_res == MY_OUTFITS_SUBFOLDER && create_outfit)
|
||||
{
|
||||
// turn it into outfit
|
||||
dropToMyOutfitsSubfolder(inv_cat, mUUID, cb);
|
||||
}
|
||||
else
|
||||
{
|
||||
LLInvFVBridge::changeCategoryParent(
|
||||
model,
|
||||
(LLViewerInventoryCategory*)inv_cat,
|
||||
mUUID,
|
||||
move_is_into_trash);
|
||||
if (cb) cb->fire(inv_cat->getUUID());
|
||||
}
|
||||
break;
|
||||
case MY_OUTFITS_SUBFOLDER:
|
||||
case MY_OUTFITS_OUTFIT:
|
||||
// only permit moving subfodlers and outfits into other subfolders
|
||||
if (dest_res == MY_OUTFITS_SUBFOLDER)
|
||||
{
|
||||
LLInvFVBridge::changeCategoryParent(
|
||||
model,
|
||||
(LLViewerInventoryCategory*)inv_cat,
|
||||
mUUID,
|
||||
false);
|
||||
if (cb) cb->fire(inv_cat->getUUID());
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false); // mot permitted, shouldn't have accepted
|
||||
}
|
||||
break;
|
||||
case MY_OUTFITS_SUBOUTFIT:
|
||||
if (dest_res == MY_OUTFITS_SUBOUTFIT || dest_res == MY_OUTFITS_OUTFIT)
|
||||
{
|
||||
LLInvFVBridge::changeCategoryParent(
|
||||
model,
|
||||
(LLViewerInventoryCategory*)inv_cat,
|
||||
mUUID,
|
||||
false);
|
||||
if (cb) cb->fire(inv_cat->getUUID());
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false); // mot permitted, shouldn't have accepted
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if target is current outfit folder we use link
|
||||
else if (move_is_into_current_outfit &&
|
||||
|
|
@ -3996,7 +4109,6 @@ void LLFolderBridge::perform_pasteFromClipboard()
|
|||
LLInventoryObject *obj = model->getObject(item_id);
|
||||
if (obj)
|
||||
{
|
||||
|
||||
if (move_is_into_lost_and_found)
|
||||
{
|
||||
if (LLAssetType::AT_CATEGORY == obj->getType())
|
||||
|
|
@ -4006,24 +4118,57 @@ void LLFolderBridge::perform_pasteFromClipboard()
|
|||
}
|
||||
if (move_is_into_outfit)
|
||||
{
|
||||
if (!move_is_into_my_outfits && item && can_move_to_outfit(item, move_is_into_current_outfit))
|
||||
bool handled = false;
|
||||
if (mUUID != my_outifts_id
|
||||
&& dest_folder->getPreferredType() == LLFolderType::FT_OUTFIT
|
||||
&& item
|
||||
&& can_move_to_outfit(item, move_is_into_current_outfit))
|
||||
{
|
||||
dropToOutfit(item, move_is_into_current_outfit, cb);
|
||||
handled = true;
|
||||
}
|
||||
else if (move_is_into_my_outfits && LLAssetType::AT_CATEGORY == obj->getType())
|
||||
{
|
||||
LLInventoryCategory* cat = model->getCategory(item_id);
|
||||
LLViewerInventoryCategory* cat = model->getCategory(item_id);
|
||||
U32 max_items_to_wear = gSavedSettings.getU32("WearFolderLimit");
|
||||
if (cat && can_move_to_my_outfits(model, cat, max_items_to_wear))
|
||||
if (cat && can_move_to_my_outfits_as_outfit(model, cat, max_items_to_wear))
|
||||
{
|
||||
dropToMyOutfits(cat, cb);
|
||||
if (mUUID == my_outifts_id)
|
||||
{
|
||||
dropToMyOutfits(cat, cb);
|
||||
handled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
EMyOutfitsSubfolderType dest_res = myoutfit_object_subfolder_type(model, mUUID, my_outifts_id);
|
||||
if (dest_res == MY_OUTFITS_SUBFOLDER)
|
||||
{
|
||||
// turn it into outfit
|
||||
dropToMyOutfitsSubfolder(cat, mUUID, cb);
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
if (!handled && cat && can_move_to_my_outfits_as_subfolder(model, cat))
|
||||
{
|
||||
LLNotificationsUtil::add("MyOutfitsPasteFailed");
|
||||
EMyOutfitsSubfolderType dest_res = myoutfit_object_subfolder_type(model, mUUID, my_outifts_id);
|
||||
if (dest_res == MY_OUTFITS_SUBFOLDER || mUUID == my_outifts_id)
|
||||
{
|
||||
if (LLClipboard::instance().isCutMode())
|
||||
{
|
||||
changeCategoryParent(model, cat, parent_id, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
copy_inventory_category(model, cat, parent_id);
|
||||
}
|
||||
if (cb) cb->fire(item_id);
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if (!handled)
|
||||
{
|
||||
LLNotificationsUtil::add("MyOutfitsPasteFailed");
|
||||
}
|
||||
|
|
@ -4066,7 +4211,7 @@ void LLFolderBridge::perform_pasteFromClipboard()
|
|||
// move_inventory_item() is not enough, as we have to update inventory locally too
|
||||
if (LLAssetType::AT_CATEGORY == obj->getType())
|
||||
{
|
||||
LLViewerInventoryCategory* vicat = (LLViewerInventoryCategory *) model->getCategory(item_id);
|
||||
LLViewerInventoryCategory* vicat = model->getCategory(item_id);
|
||||
llassert(vicat);
|
||||
if (vicat)
|
||||
{
|
||||
|
|
@ -4256,6 +4401,7 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items
|
|||
|
||||
if (outfits_id == mUUID)
|
||||
{
|
||||
items.push_back(std::string("New Outfit Folder"));
|
||||
items.push_back(std::string("New Outfit"));
|
||||
}
|
||||
|
||||
|
|
@ -4349,63 +4495,83 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items
|
|||
else if(isAgentInventory()) // do not allow creating in library
|
||||
{
|
||||
LLViewerInventoryCategory *cat = getCategory();
|
||||
// BAP removed protected check to re-enable standard ops in untyped folders.
|
||||
// Not sure what the right thing is to do here.
|
||||
if (!isCOFFolder() && cat && (cat->getPreferredType() != LLFolderType::FT_OUTFIT))
|
||||
|
||||
if (cat)
|
||||
{
|
||||
if (!isInboxFolder() // don't allow creation in inbox
|
||||
&& outfits_id != mUUID)
|
||||
{
|
||||
bool menu_items_added = false;
|
||||
// Do not allow to create 2-level subfolder in the Calling Card/Friends folder. EXT-694.
|
||||
if (!LLFriendCardsManager::instance().isCategoryInFriendFolder(cat))
|
||||
{
|
||||
items.push_back(std::string("New Folder"));
|
||||
menu_items_added = true;
|
||||
}
|
||||
if (!isMarketplaceListingsFolder())
|
||||
{
|
||||
items.push_back(std::string("upload_def"));
|
||||
items.push_back(std::string("create_new"));
|
||||
items.push_back(std::string("New Script"));
|
||||
items.push_back(std::string("New Note"));
|
||||
items.push_back(std::string("New Gesture"));
|
||||
items.push_back(std::string("New Material"));
|
||||
items.push_back(std::string("New Clothes"));
|
||||
items.push_back(std::string("New Body Parts"));
|
||||
items.push_back(std::string("New Settings"));
|
||||
if (!LLEnvironment::instance().isInventoryEnabled())
|
||||
{
|
||||
disabled_items.push_back("New Settings");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
items.push_back(std::string("New Listing Folder"));
|
||||
}
|
||||
if (menu_items_added)
|
||||
{
|
||||
items.push_back(std::string("Create Separator"));
|
||||
}
|
||||
}
|
||||
getClipboardEntries(false, items, disabled_items, flags);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Want some but not all of the items from getClipboardEntries for outfits.
|
||||
if (cat && (cat->getPreferredType() == LLFolderType::FT_OUTFIT))
|
||||
if (cat->getPreferredType() == LLFolderType::FT_OUTFIT)
|
||||
{
|
||||
// Want some but not all of the items from getClipboardEntries for outfits.
|
||||
items.push_back(std::string("Rename"));
|
||||
items.push_back(std::string("thumbnail"));
|
||||
|
||||
addDeleteContextMenuOptions(items, disabled_items);
|
||||
// EXT-4030: disallow deletion of currently worn outfit
|
||||
const LLViewerInventoryItem *base_outfit_link = LLAppearanceMgr::instance().getBaseOutfitLink();
|
||||
const LLViewerInventoryItem* base_outfit_link = LLAppearanceMgr::instance().getBaseOutfitLink();
|
||||
if (base_outfit_link && (cat == base_outfit_link->getLinkedCategory()))
|
||||
{
|
||||
disabled_items.push_back(std::string("Delete"));
|
||||
}
|
||||
}
|
||||
else if (outfits_id == mUUID)
|
||||
{
|
||||
getClipboardEntries(false, items, disabled_items, flags);
|
||||
}
|
||||
else if (!isCOFFolder())
|
||||
{
|
||||
EMyOutfitsSubfolderType in_my_outfits = myoutfit_object_subfolder_type(model, mUUID, outfits_id);
|
||||
if (in_my_outfits != MY_OUTFITS_NO)
|
||||
{
|
||||
if (in_my_outfits == MY_OUTFITS_SUBFOLDER)
|
||||
{
|
||||
// Not inside an outfit, but inside 'my outfits'
|
||||
items.push_back(std::string("New Outfit"));
|
||||
items.push_back(std::string("New Outfit Folder"));
|
||||
}
|
||||
items.push_back(std::string("Rename"));
|
||||
items.push_back(std::string("thumbnail"));
|
||||
|
||||
addDeleteContextMenuOptions(items, disabled_items);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isInboxFolder() // don't allow creation in inbox
|
||||
&& outfits_id != mUUID)
|
||||
{
|
||||
bool menu_items_added = false;
|
||||
// Do not allow to create 2-level subfolder in the Calling Card/Friends folder. EXT-694.
|
||||
if (!LLFriendCardsManager::instance().isCategoryInFriendFolder(cat))
|
||||
{
|
||||
items.push_back(std::string("New Folder"));
|
||||
menu_items_added = true;
|
||||
}
|
||||
if (!isMarketplaceListingsFolder())
|
||||
{
|
||||
items.push_back(std::string("upload_def"));
|
||||
items.push_back(std::string("create_new"));
|
||||
items.push_back(std::string("New Script"));
|
||||
items.push_back(std::string("New Note"));
|
||||
items.push_back(std::string("New Gesture"));
|
||||
items.push_back(std::string("New Material"));
|
||||
items.push_back(std::string("New Clothes"));
|
||||
items.push_back(std::string("New Body Parts"));
|
||||
items.push_back(std::string("New Settings"));
|
||||
if (!LLEnvironment::instance().isInventoryEnabled())
|
||||
{
|
||||
disabled_items.push_back("New Settings");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
items.push_back(std::string("New Listing Folder"));
|
||||
}
|
||||
if (menu_items_added)
|
||||
{
|
||||
items.push_back(std::string("Create Separator"));
|
||||
}
|
||||
}
|
||||
getClipboardEntries(false, items, disabled_items, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT) == mUUID)
|
||||
|
|
@ -4558,7 +4724,11 @@ void LLFolderBridge::buildContextMenuFolderOptions(U32 flags, menuentry_vec_t&
|
|||
|
||||
if (((flags & ITEM_IN_MULTI_SELECTION) == 0) && hasChildren() && (type != LLFolderType::FT_OUTFIT))
|
||||
{
|
||||
items.push_back(std::string("Ungroup folder items"));
|
||||
const LLUUID my_outfits = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS);
|
||||
if (!gInventory.isObjectDescendentOf(mUUID, my_outfits))
|
||||
{
|
||||
items.push_back(std::string("Ungroup folder items"));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -5331,13 +5501,23 @@ void LLFolderBridge::dropToMyOutfits(LLInventoryCategory* inv_cat, LLPointer<LLI
|
|||
// Note: creation will take time, so passing folder id to callback is slightly unreliable,
|
||||
// but so is collecting and passing descendants' ids
|
||||
inventory_func_type func = boost::bind(outfitFolderCreatedCallback, inv_cat->getUUID(), _1, cb, mInventoryPanel);
|
||||
gInventory.createNewCategory(dest_id,
|
||||
getInventoryModel()->createNewCategory(dest_id,
|
||||
LLFolderType::FT_OUTFIT,
|
||||
inv_cat->getName(),
|
||||
func,
|
||||
inv_cat->getThumbnailUUID());
|
||||
}
|
||||
|
||||
void LLFolderBridge::dropToMyOutfitsSubfolder(LLInventoryCategory* inv_cat, const LLUUID& dest_id, LLPointer<LLInventoryCallback> cb)
|
||||
{
|
||||
inventory_func_type func = boost::bind(outfitFolderCreatedCallback, inv_cat->getUUID(), _1, cb, mInventoryPanel);
|
||||
getInventoryModel()->createNewCategory(dest_id,
|
||||
LLFolderType::FT_OUTFIT,
|
||||
inv_cat->getName(),
|
||||
func,
|
||||
inv_cat->getThumbnailUUID());
|
||||
}
|
||||
|
||||
void LLFolderBridge::outfitFolderCreatedCallback(LLUUID cat_source_id,
|
||||
LLUUID cat_dest_id,
|
||||
LLPointer<LLInventoryCallback> cb,
|
||||
|
|
@ -5511,7 +5691,9 @@ bool LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item,
|
|||
}
|
||||
else if (user_confirm && (move_is_into_current_outfit || move_is_into_outfit))
|
||||
{
|
||||
accept = can_move_to_outfit(inv_item, move_is_into_current_outfit);
|
||||
EMyOutfitsSubfolderType res = myoutfit_object_subfolder_type(model, mUUID, my_outifts_id);
|
||||
// don't allow items in my outfits' subfodlers, only in outfits and outfit's subfolders
|
||||
accept = res != MY_OUTFITS_SUBFOLDER && can_move_to_outfit(inv_item, move_is_into_current_outfit);
|
||||
}
|
||||
else if (user_confirm && (move_is_into_favorites || move_is_into_landmarks))
|
||||
{
|
||||
|
|
|
|||