summaryrefslogtreecommitdiff
path: root/indra/llui
diff options
context:
space:
mode:
authorErik Kundiman <erik@megapahit.org>2026-08-18 13:57:14 +0800
committerErik Kundiman <erik@megapahit.org>2026-08-18 13:57:14 +0800
commit2fa8281b2d8ce8bdbcacf139ec674480da6c9d81 (patch)
treeffb343fee97f21942123fd322d88fea3ef1a4811 /indra/llui
parent742d802f073e81abc6d4cd512e58a4d03b414b83 (diff)
parent214fa765986b4c1e1de1b917581f8508278abd8e (diff)
Merge branch '26.3'
Diffstat (limited to 'indra/llui')
-rw-r--r--indra/llui/llcombobox.cpp33
-rw-r--r--indra/llui/llcombobox.h4
-rw-r--r--indra/llui/llfloater.cpp2
-rw-r--r--indra/llui/llfloaterreglistener.cpp9
-rw-r--r--indra/llui/llfloaterreglistener.h1
-rw-r--r--indra/llui/llpanel.cpp43
-rw-r--r--indra/llui/llscrollcontainer.cpp1
-rw-r--r--indra/llui/lltextbase.cpp81
-rw-r--r--indra/llui/lltextbase.h14
-rw-r--r--indra/llui/lluictrlfactory.cpp2
-rw-r--r--indra/llui/lluictrlfactory.h11
-rw-r--r--indra/llui/llurlregistry.cpp77
12 files changed, 225 insertions, 53 deletions
diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp
index ae676251ff..01463b4962 100644
--- a/indra/llui/llcombobox.cpp
+++ b/indra/llui/llcombobox.cpp
@@ -1323,6 +1323,39 @@ bool LLComboBox::selectItemRange( S32 first, S32 last )
return mList->selectItemRange(first, last);
}
+void LLComboBox::addInfo(LLSD& info)
+{
+ LLUICtrl::addInfo(info);
+
+ if (mList && mList->getItemCount() > 0)
+ {
+ LLSD items_array;
+ std::vector<LLScrollListItem*> item_list = mList->getAllData();
+ for (std::vector<LLScrollListItem*>::iterator iter = item_list.begin(); iter != item_list.end(); ++iter)
+ {
+ if (LLScrollListItem* item = *iter)
+ {
+ LLSD item_info;
+ item_info["value"] = item->getValue();
+ if (item->getNumColumns() > 0)
+ {
+ if (LLScrollListCell* cell = item->getColumn(0))
+ {
+ item_info["label"] = cell->getValue();
+ }
+ }
+ items_array.append(item_info);
+ }
+ }
+ info["items"] = items_array;
+ info["item_count"] = mList->getItemCount();
+ info["current_selection"] = getSelectedItemLabel();
+ }
+ else
+ {
+ info["item_count"] = 0;
+ }
+}
static LLDefaultChildRegistry::Register<LLIconsComboBox> register_icons_combo_box("icons_combo_box");
diff --git a/indra/llui/llcombobox.h b/indra/llui/llcombobox.h
index d6ea1202d3..dad60ffb65 100644
--- a/indra/llui/llcombobox.h
+++ b/indra/llui/llcombobox.h
@@ -219,6 +219,10 @@ public:
void setButtonVisible(bool visible);
+ // Populates the provided LLSD with combo box-specific information(list of items, item count, current selection label)
+ // also includes base LLUICtrl information via parent class
+ void addInfo(LLSD & info);
+
void onButtonMouseDown();
void onListMouseUp();
void onItemSelected(const LLSD& data);
diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp
index 9361358ced..6de2c18620 100644
--- a/indra/llui/llfloater.cpp
+++ b/indra/llui/llfloater.cpp
@@ -569,6 +569,8 @@ void LLFloater::storeRectControl()
void LLFloater::storeVisibilityControl()
{
+ // Todo: this is a bit pricey, gets called each frame
+ // on LLAppViewer::idle(), optimize!
if( !sQuitting && mVisibilityControl.size() > 1 )
{
getControlGroup()->setBOOL( mVisibilityControl, getVisible() );
diff --git a/indra/llui/llfloaterreglistener.cpp b/indra/llui/llfloaterreglistener.cpp
index 17641b8375..7cf09369ec 100644
--- a/indra/llui/llfloaterreglistener.cpp
+++ b/indra/llui/llfloaterreglistener.cpp
@@ -60,6 +60,10 @@ LLFloaterRegListener::LLFloaterRegListener():
"Ask to toggle the state of the floater specified in [\"name\"]",
&LLFloaterRegListener::toggleInstance,
requiredName);
+ add("toggleInstanceOrBringToFront",
+ "Ask to toggle the state of the floater specified in [\"name\"] or bring it to front if already opened",
+ &LLFloaterRegListener::toggleInstance,
+ requiredName);
add("instanceVisible",
"Return on [\"reply\"] an event whose [\"visible\"] indicates the visibility "
"of the floater specified in [\"name\"]",
@@ -107,6 +111,11 @@ void LLFloaterRegListener::toggleInstance(const LLSD& event) const
LLFloaterReg::toggleInstance(event["name"].asString(), event["key"]);
}
+void LLFloaterRegListener::toggleInstanceOrBringToFront(const LLSD& event) const
+{
+ LLFloaterReg::toggleInstanceOrBringToFront(event["name"].asString(), event["key"]);
+}
+
void LLFloaterRegListener::instanceVisible(const LLSD& event) const
{
sendReply(LLSDMap("visible", LLFloaterReg::instanceVisible(event["name"].asString(), event["key"])),
diff --git a/indra/llui/llfloaterreglistener.h b/indra/llui/llfloaterreglistener.h
index 28f6e7c66b..610a2831dc 100644
--- a/indra/llui/llfloaterreglistener.h
+++ b/indra/llui/llfloaterreglistener.h
@@ -46,6 +46,7 @@ private:
void showInstance(const LLSD& event) const;
void hideInstance(const LLSD& event) const;
void toggleInstance(const LLSD& event) const;
+ void toggleInstanceOrBringToFront(const LLSD& event) const;
void instanceVisible(const LLSD& event) const;
void clickButton(const LLSD& event) const;
};
diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp
index 2100b23783..ff5a28b250 100644
--- a/indra/llui/llpanel.cpp
+++ b/indra/llui/llpanel.cpp
@@ -489,58 +489,69 @@ bool LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr outpu
LL_RECORD_BLOCK_TIME(FTM_PANEL_SETUP);
LLXMLNodePtr referenced_xml;
- std::string xml_filename = mXMLFilename;
// if the panel didn't provide a filename, check the node
- if (xml_filename.empty())
+ if (mXMLFilename.empty())
{
- node->getAttributeString("filename", xml_filename);
- setXMLFilename(xml_filename);
+ std::string temp_filename;
+ node->getAttributeString("filename", temp_filename);
+ setXMLFilename(temp_filename);
}
+ // Cache singleton and filename to avoid repeated calls
+ LLUICtrlFactory* factory = LLUICtrlFactory::getInstance();
+
+ // Cache node name pointer to avoid repeated dereferencing
+ const LLStringTableEntry* node_name = node->getName();
+
+ // Cache registry to avoid repeated singleton access
+ const child_registry_t& registry = child_registry_t::instance();
+
LLXUIParser parser;
- if (!xml_filename.empty())
+ if (!mXMLFilename.empty())
{
if (output_node)
{
//if we are exporting, we want to export the current xml
//not the referenced xml
- parser.readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName());
+ parser.readXUI(node, params, factory->getCurFileName());
Params output_params(params);
setupParamsForExport(output_params, parent);
- output_node->setName(node->getName()->mString);
+ output_node->setName(node_name->mString);
parser.writeXUI(output_node, output_params, LLInitParam::default_parse_rules(), &default_params);
return true;
}
- LLUICtrlFactory::instance().pushFileName(xml_filename);
+ factory->pushFileName(mXMLFilename);
LL_RECORD_BLOCK_TIME(FTM_EXTERNAL_PANEL_LOAD);
- if (!LLUICtrlFactory::getLayeredXMLNode(xml_filename, referenced_xml))
+ if (!LLUICtrlFactory::getLayeredXMLNode(mXMLFilename, referenced_xml))
{
- LL_WARNS() << "Couldn't parse panel from: " << xml_filename << LL_ENDL;
+ LL_WARNS() << "Couldn't parse panel from: " << mXMLFilename << LL_ENDL;
return false;
}
- parser.readXUI(referenced_xml, params, LLUICtrlFactory::getInstance()->getCurFileName());
+ // Get filename after pushFileName
+ const std::string& updated_filename = factory->getCurFileName();
+ parser.readXUI(referenced_xml, params, updated_filename);
// add children using dimensions from referenced xml for consistent layout
setShape(params.rect);
- LLUICtrlFactory::createChildren(this, referenced_xml, child_registry_t::instance());
+ LLUICtrlFactory::createChildren(this, referenced_xml, registry);
- LLUICtrlFactory::instance().popFileName();
+ factory->popFileName();
}
// ask LLUICtrlFactory for filename, since xml_filename might be empty
- parser.readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName());
+ parser.readXUI(node, params, factory->getCurFileName());
if (output_node)
{
Params output_params(params);
setupParamsForExport(output_params, parent);
- output_node->setName(node->getName()->mString);
+ output_node->setName(node_name->mString);
parser.writeXUI(output_node, output_params, LLInitParam::default_parse_rules(), &default_params);
}
@@ -552,7 +563,7 @@ bool LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr outpu
}
// add children
- LLUICtrlFactory::createChildren(this, node, child_registry_t::instance(), output_node);
+ LLUICtrlFactory::createChildren(this, node, registry, output_node);
// Connect to parent after children are built, because tab containers
// do a reshape() on their child panels, which requires that the children
diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp
index df99c4f636..e36fd45bb4 100644
--- a/indra/llui/llscrollcontainer.cpp
+++ b/indra/llui/llscrollcontainer.cpp
@@ -480,6 +480,7 @@ void LLScrollContainer::calcVisibleSize( S32 *visible_width, S32 *visible_height
void LLScrollContainer::draw()
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
static LLUICachedControl<S32> scrollbar_size_control ("UIScrollbarSize", 0);
S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize);
diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp
index 5882c1edbb..0521853b02 100644
--- a/indra/llui/lltextbase.cpp
+++ b/indra/llui/lltextbase.cpp
@@ -225,6 +225,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p)
mTrustedContent(p.trusted_content),
mAlwaysShowIcons(p.always_show_icons),
mTrackEnd( p.track_end ),
+ mTrackValueChange(true),
mScrollIndex(-1),
mSelectionStart( 0 ),
mSelectionEnd( 0 ),
@@ -967,7 +968,10 @@ void LLTextBase::drawText()
S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::segment_vec_t* segments )
{
- beforeValueChange();
+ if (mTrackValueChange)
+ {
+ beforeValueChange();
+ }
S32 old_len = getLength(); // length() returns character length
S32 insert_len = static_cast<S32>(wstr.length());
@@ -1090,12 +1094,14 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s
getViewModel()->getEditableDisplay().insert(pos, wstr);
- if ( truncate() )
+ if (mTrackValueChange)
{
- insert_len = getLength() - old_len;
+ if (truncate())
+ {
+ insert_len = getLength() - old_len;
+ }
+ onValueChange(pos, pos + insert_len);
}
-
- onValueChange(pos, pos + insert_len);
needsReflow(pos);
return insert_len;
@@ -1111,7 +1117,10 @@ S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length)
// Clamp length to not go past the end of the text
length = std::min(length, text_length - pos);
- beforeValueChange();
+ if (mTrackValueChange)
+ {
+ beforeValueChange();
+ }
segment_set_t::iterator seg_iter = getSegIterContaining(pos);
while(seg_iter != mSegments.end())
{
@@ -1162,7 +1171,10 @@ S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length)
// recreate default segment in case we erased everything
createDefaultSegment();
- onValueChange(pos, pos);
+ if (mTrackValueChange)
+ {
+ onValueChange(pos, pos);
+ }
needsReflow(pos);
return -length; // This will be wrong if someone calls removeStringNoUndo with an excessive length
@@ -1170,7 +1182,10 @@ S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length)
S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc)
{
- beforeValueChange();
+ if (mTrackValueChange)
+ {
+ beforeValueChange();
+ }
if (pos > (S32)getLength())
{
@@ -1178,7 +1193,10 @@ S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc)
}
getViewModel()->getEditableDisplay()[pos] = wc;
- onValueChange(pos, pos + 1);
+ if (mTrackValueChange)
+ {
+ onValueChange(pos, pos + 1);
+ }
needsReflow(pos);
return 1;
@@ -1625,7 +1643,7 @@ void LLTextBase::deselect()
bool LLTextBase::getSpellCheck() const
{
- return (LLSpellChecker::getUseSpellCheck()) && (!mReadOnly) && (mSpellCheck);
+ return (!mReadOnly) && (LLSpellChecker::getUseSpellCheck()) && (mSpellCheck);
}
const std::string& LLTextBase::getSuggestion(U32 index) const
@@ -2333,6 +2351,10 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url)
void LLTextBase::setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params)
{
+ beforeValueChange();
+ // Can insert a lot of different segments, don't want to spam events.
+ mTrackValueChange = false;
+
// clear out the existing text and segments
getViewModel()->setDisplay(LLWStringUtil::null);
@@ -2353,6 +2375,8 @@ void LLTextBase::setText(const LLStringExplicit &utf8str, const LLStyle::Params&
startOfDoc();
}
+ truncate(); // was postponed to avoid micro truncations and expensive checks
+ mTrackValueChange = true;
onValueChange(0, getLength());
}
@@ -2383,6 +2407,10 @@ void LLTextBase::appendTextImpl(const std::string& new_text, const LLStyle::Para
LLStyle::Params style_params(getStyleParams());
style_params.overwriteFrom(input_params);
+ // todo: this does not check for maximum size, might
+ // want to stop once maximum size was reached to avoid
+ // expensive findUrl, replaceUrl calls.
+
S32 part = (S32)LLTextParser::WHOLE;
if ((mParseHTML || force_slurl) && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358).
{
@@ -2556,6 +2584,10 @@ void LLTextBase::copyContents(const LLTextBase* source)
beforeValueChange();
deselect();
+ // Can insert a lot of different segments, don't want to spam events.
+ // Do one full length onValueChange() at the end of this function.
+ mTrackValueChange = false;
+
mSegments.clear();
for (const LLTextSegmentPtr& segp : source->mSegments)
{
@@ -2570,6 +2602,8 @@ void LLTextBase::copyContents(const LLTextBase* source)
getViewModel()->setDisplay(source->getViewModel()->getDisplay());
+ truncate(); // was postponed to avoid micro truncations and expensive checks
+ mTrackValueChange = true;
onValueChange(0, getLength());
needsReflow();
}
@@ -2824,7 +2858,7 @@ S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, bool round,
line_seg_iter != mSegments.end();
++line_seg_iter, line_seg_offset = 0)
{
- const LLTextSegmentPtr segmentp = *line_seg_iter;
+ LLTextSegmentPtr segmentp = *line_seg_iter;
S32 segment_line_start = segmentp->getStart() + line_seg_offset;
S32 segment_line_length = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd) - segment_line_start;
@@ -2915,7 +2949,7 @@ LLRect LLTextBase::getDocRectFromDocIndex(S32 pos) const
while(line_seg_iter != mSegments.end())
{
- const LLTextSegmentPtr segmentp = *line_seg_iter;
+ LLTextSegmentPtr segmentp = *line_seg_iter;
if (line_seg_iter == cursor_seg_iter)
{
@@ -3466,8 +3500,8 @@ LLStyleSP LLTextSegment::cloneStyle(LLTextBase& target, const LLStyle* source)
}
-bool LLTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const { width = 0; height = 0; return false; }
-bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const
+bool LLTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { width = 0; height = 0; return false; }
+bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height)
{
F32 fwidth = 0;
bool result = getDimensionsF32(first_char, num_chars, fwidth, height);
@@ -3564,6 +3598,7 @@ F32 LLNormalTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selec
mFontBufferPreSelection.reset();
mFontBufferSelection.reset();
mFontBufferPostSelection.reset();
+ mFontWidthBuffer.reset();
}
return draw_rect.mLeft;
}
@@ -3589,6 +3624,7 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele
mFontBufferPreSelection.reset();
mFontBufferSelection.reset();
mFontBufferPostSelection.reset();
+ mFontWidthBuffer.reset();
}
const LLFontGL* font = mStyle->getFont();
@@ -3820,17 +3856,19 @@ LLTextSegmentPtr LLNormalTextSegment::clone(LLTextBase& target) const
return new LLNormalTextSegment(sp, mStart, mEnd, target);
}
-bool LLNormalTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const
+bool LLNormalTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height)
{
height = 0;
width = 0;
if (num_chars > 0 && (mStart + first_char >= 0))
{
height = mFontHeight;
- const LLWString &text = getWText();
- // if last character is a newline, then return true, forcing line break
- width = mStyle->getFont()->getWidthF32(text.c_str(), mStart + first_char, num_chars, true);
+
+ const LLWString& text = getWText();
+ const LLFontGL* font = mStyle->getFont();
+ width += mFontWidthBuffer.getWidth(font, text.c_str(), mStart + first_char, num_chars, true);
}
+ // if last character is a newline, then return true, forcing line break
return false;
}
@@ -3912,6 +3950,7 @@ void LLNormalTextSegment::updateLayout(const class LLTextBase& editor)
mFontBufferPreSelection.reset();
mFontBufferSelection.reset();
mFontBufferPostSelection.reset();
+ mFontWidthBuffer.reset();
}
void LLNormalTextSegment::dump() const
@@ -4063,7 +4102,7 @@ LLTextSegmentPtr LLInlineViewSegment::clone(LLTextBase& target) const
return nullptr;
}
-bool LLInlineViewSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const
+bool LLInlineViewSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height)
{
if (first_char == 0 && num_chars == 0)
{
@@ -4155,7 +4194,7 @@ LLTextSegmentPtr LLLineBreakTextSegment::clone(LLTextBase& target) const
copy->mFontHeight = mFontHeight;
return copy;
}
-bool LLLineBreakTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const
+bool LLLineBreakTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height)
{
width = 0;
height = mFontHeight;
@@ -4192,7 +4231,7 @@ LLTextSegmentPtr LLImageTextSegment::clone(LLTextBase& target) const
static const S32 IMAGE_HPAD = 3;
// virtual
-bool LLImageTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const
+bool LLImageTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height)
{
width = 0;
height = mStyle->getFont()->getLineHeight();
diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h
index 35477bdea9..bc39d9732c 100644
--- a/indra/llui/lltextbase.h
+++ b/indra/llui/lltextbase.h
@@ -68,10 +68,10 @@ public:
virtual LLTextSegmentPtr clone(LLTextBase& terget) const { return new LLTextSegment(mStart, mEnd); }
static LLStyleSP cloneStyle(LLTextBase& target, const LLStyle* source);
- bool getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const;
+ bool getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height);
bool getPermitsEmoji() const { return mPermitsEmoji; };
- virtual bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const;
+ virtual bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height);
virtual S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const;
/**
@@ -139,7 +139,7 @@ public:
virtual ~LLNormalTextSegment();
/*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const;
- /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const;
+ /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height);
/*virtual*/ S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const;
/*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);
@@ -182,6 +182,7 @@ protected:
LLFontVertexBuffer mFontBufferPreSelection;
LLFontVertexBuffer mFontBufferSelection;
LLFontVertexBuffer mFontBufferPostSelection;
+ LLFontWidthBuffer mFontWidthBuffer;
S32 mLastGeneration = -1;
};
@@ -254,7 +255,7 @@ public:
~LLInlineViewSegment();
/*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const;
- /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const;
+ /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height);
/*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);
@@ -280,7 +281,7 @@ public:
LLLineBreakTextSegment(S32 pos);
~LLLineBreakTextSegment();
/*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const;
- /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const;
+ /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height);
S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const;
F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect);
@@ -295,7 +296,7 @@ public:
~LLImageTextSegment();
/*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const;
- /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const;
+ /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height);
S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 char_offset, S32 max_chars, S32 line_ind) const;
F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect);
@@ -762,6 +763,7 @@ protected:
bool mUseEmoji;
bool mUseColor;
bool mTrackEnd; // if true, keeps scroll position at end of document during resize
+ bool mTrackValueChange; // if true, send out onValueChange() from low level text modification methods
bool mReadOnly;
bool mBGVisible; // render background?
bool mClip; // clip text to widget rect
diff --git a/indra/llui/lluictrlfactory.cpp b/indra/llui/lluictrlfactory.cpp
index 33ffc3dfc6..740f187ed8 100644
--- a/indra/llui/lluictrlfactory.cpp
+++ b/indra/llui/lluictrlfactory.cpp
@@ -67,7 +67,7 @@ public:
static LLDefaultChildRegistry::Register<LLUICtrlLocate> r1("locate");
// Build time optimization, generate this once in .cpp file
-template class LLUICtrlFactory* LLSingleton<class LLUICtrlFactory>::getInstance();
+template class LLUICtrlFactory* LLSimpleton<class LLUICtrlFactory>::getInstance();
//-----------------------------------------------------------------------------
// LLUICtrlFactory()
diff --git a/indra/llui/lluictrlfactory.h b/indra/llui/lluictrlfactory.h
index f44b4ba4dc..75cee5f004 100644
--- a/indra/llui/lluictrlfactory.h
+++ b/indra/llui/lluictrlfactory.h
@@ -81,12 +81,13 @@ class LLWidgetNameRegistry
// Build time optimization, generate this once in .cpp file
#ifndef LLUICTRLFACTORY_CPP
-extern template class LLUICtrlFactory* LLSingleton<class LLUICtrlFactory>::getInstance();
+extern template class LLUICtrlFactory* LLSimpleton<class LLUICtrlFactory>::getInstance();
#endif
-class LLUICtrlFactory : public LLSingleton<LLUICtrlFactory>
+class LLUICtrlFactory : public LLSimpleton<LLUICtrlFactory>
{
- LLSINGLETON(LLUICtrlFactory);
+public:
+ LLUICtrlFactory();
~LLUICtrlFactory();
// only partial specialization allowed in inner classes, so use extra dummy parameter
@@ -313,6 +314,10 @@ template<typename T>
LLChildRegistry<DERIVED>::Register<T>::Register(const char* tag, LLWidgetCreatorFunc func)
: LLChildRegistry<DERIVED>::StaticRegistrar(tag, func == nullptr ? (LLWidgetCreatorFunc)&LLUICtrlFactory::defaultBuilder<T> : func)
{
+ if (!LLUICtrlFactory::instanceExists())
+ {
+ LLUICtrlFactory::createInstance();
+ }
// add this widget to various registries
LLUICtrlFactory::instance().registerWidget(typeid(T), typeid(typename T::Params), tag);
diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp
index cb101d325d..8a21222b3c 100644
--- a/indra/llui/llurlregistry.cpp
+++ b/indra/llui/llurlregistry.cpp
@@ -149,12 +149,77 @@ static bool stringHasUrl(const std::string &text)
// fast heuristic test for a URL in a string. This is used
// to avoid lots of costly regex calls, BUT it needs to be
// kept in sync with the LLUrlEntry regexes we support.
- return (text.find("://") != std::string::npos ||
- text.find("www.") != std::string::npos ||
- text.find(".com") != std::string::npos ||
- text.find("<nolink>") != std::string::npos ||
- text.find("<icon") != std::string::npos ||
- text.find("@") != std::string::npos);
+
+ // Early exit for empty or very short strings
+ // Smallest url is 5 characters
+ if (text.length() < 3)
+ {
+ return false;
+ }
+
+ // Single pass search for common URL indicators
+ for (size_t i = 0; i < text.length(); ++i)
+ {
+ char c = text[i];
+
+ // Check for @ (email or mention)
+ if (c == '@')
+ {
+ return true;
+ }
+
+ if (i + 3 >= text.length())
+ {
+ // Nothing else is going to match or fit if we don't
+ // have at least 4 characters left
+ // Ex: expectation is that there is something after protocol delimiter
+ // and .com takes 4 characters.
+ return false;
+ }
+
+ // Check for protocol delimiter
+ if (c == ':' && text[i + 1] == '/' && text[i + 2] == '/')
+ {
+ return true;
+ }
+
+ // Check for www. at start of word
+ if (c == 'w'
+ && text[i + 1] == 'w'
+ && text[i + 2] == 'w'
+ && text[i + 3] == '.')
+ {
+ return true;
+ }
+
+ // Check for .com (and similar)
+ if (c == '.')
+ {
+ const char* suffix = text.c_str() + i + 1;
+ if ((suffix[0] == 'c' && suffix[1] == 'o' && suffix[2] == 'm') ||
+ (suffix[0] == 'n' && suffix[1] == 'e' && suffix[2] == 't') ||
+ (suffix[0] == 'o' && suffix[1] == 'r' && suffix[2] == 'g') ||
+ (suffix[0] == 'e' && suffix[1] == 'd' && suffix[2] == 'u'))
+ {
+ return true;
+ }
+ }
+
+ // Check for <nolink> or <icon
+ if (c == '<')
+ {
+ if (i + 7 < text.length() && text.compare(i + 1, 6, "nolink") == 0)
+ {
+ return true;
+ }
+ if (i + 4 < text.length() && text.compare(i + 1, 4, "icon") == 0)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
}
bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LLUrlLabelCallback &cb, bool is_content_trusted, bool skip_non_mentions)