Merge remote-tracking branch 'origin/main' into geenz/2025.04-to-develop

master
Jonathan "Geenz" Goodman 2025-05-28 11:52:16 -04:00
commit fe4f85e587
192 changed files with 33134 additions and 11822 deletions

1
.github/workflows/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
qatest.yaml -text eol=crlf

View File

@ -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)

View File

@ -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()

View File

@ -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
{

View File

@ -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();

View File

@ -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;

View File

@ -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);

View File

@ -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();
}

View File

@ -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
{

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -52,6 +52,7 @@ LLChatEntry::LLChatEntry(const Params& p)
mCurrentHistoryLine = mLineHistory.begin();
mAutoIndent = false;
mShowChatMentionPicker = true;
keepSelectionOnReturn(true);
}

View File

@ -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()); }));
}
}
}

View File

@ -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;
};

View File

@ -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();

View File

@ -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;
};

View File

@ -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);

View File

@ -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;

View File

@ -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();

View File

@ -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();

View File

@ -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)

View File

@ -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;

View File

@ -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())

View File

@ -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

View File

@ -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;
}

View File

@ -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; }

View File

@ -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);

View File

@ -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;
};
///

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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();
}
});

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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
{

View File

@ -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

View File

@ -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>

View File

@ -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];

View File

@ -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);
*/

View File

@ -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) */

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 669 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -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;
}

View File

@ -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;

View File

@ -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)));
});
}

View File

@ -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

View File

@ -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:

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View File

@ -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 */

View File

@ -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);

View File

@ -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();

View File

@ -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();

View File

@ -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;

View File

@ -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;
}

View File

@ -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
};

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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();

View File

@ -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();
}
});

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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())

View File

@ -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"));
}

View File

@ -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

View File

@ -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())

View File

@ -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()

View File

@ -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 };
};

View File

@ -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")
{

View File

@ -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"));

View File

@ -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;

View File

@ -46,7 +46,7 @@
//
// Globals
//
static GroupChatListener sGroupChatListener;
static LLGroupChatListener sGroupChatListener;
class LLGroupHandler : public LLCommandHandler
{

View File

@ -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));
}
}

View File

@ -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))
{

View File

@ -369,6 +369,7 @@ protected:
void dropToFavorites(LLInventoryItem* inv_item, LLPointer<LLInventoryCallback> cb = NULL);
void dropToOutfit(LLInventoryItem* inv_item, bool move_is_into_current_outfit, LLPointer<LLInventoryCallback> cb = NULL);
void dropToMyOutfits(LLInventoryCategory* inv_cat, LLPointer<LLInventoryCallback> cb = NULL);
void dropToMyOutfitsSubfolder(LLInventoryCategory* inv_cat, const LLUUID& dest, LLPointer<LLInventoryCallback> cb = NULL);
//--------------------------------------------------------------------
// Messy hacks for handling folder options

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