Replace URLs everywhere in text editors, like upstream.

This works for notifications, and profiles, and chats, and even updates!
Amazing, right?
SinguReplaceLinks is on by default, URL highlighting works regardless, no rogue labels with this off.

Select, right click, Copy Raw lets you copy the link with its hidden url shown
and you can paste it back the same way if you're wanting to paste it all over!

LLTextEditor overhaul synced the code with upstream LLTextBase as much as we could.
appendStyledText is now just appendText
Every setText appendText's now and adds a style based on the texteditor's color and font, this could slightly increase the weight of text editors (one extra segment) but it fixes a nasty bug with running past segmentation.
Also no longer update the utf8
Removed append() which was no longer being used
LLTextEditor and LLViewerTextEditor now have an extra boolean at the end of their constructors to have this replacement turned on at construction time (this also sets them read only)
Update TextSegments to have a Tooltip string, like upstream.
Hardened notecard previews so they don't accidentally replace the text when they go read only.

Thanks to Deltek and Router Gray for helping me test and debug this commit!
This commit is contained in:
Lirusaito
2019-01-16 01:01:19 -05:00
parent 0b52275e53
commit 269576805b
13 changed files with 468 additions and 409 deletions

View File

@@ -43,6 +43,7 @@
#include "lluictrlfactory.h"
#include "lluiimage.h"
#include "llurlaction.h"
#include "llurlregistry.h"
#include "llrect.h"
#include "llfocusmgr.h"
#include "lltimer.h"
@@ -246,46 +247,47 @@ private:
///////////////////////////////////////////////////////////////////
LLTextEditor::LLTextEditor(
const std::string& name,
const LLRect& rect,
LLTextEditor::LLTextEditor(
const std::string& name,
const LLRect& rect,
S32 max_length, // In bytes
const std::string &default_text,
const std::string &default_text,
const LLFontGL* font,
BOOL allow_embedded_items)
:
LLUICtrl( name, rect, TRUE, NULL, FOLLOWS_TOP | FOLLOWS_LEFT ),
BOOL allow_embedded_items,
bool parse_html)
:
LLUICtrl(name, rect, TRUE, NULL, FOLLOWS_TOP | FOLLOWS_LEFT),
mTextIsUpToDate(TRUE),
mMaxTextByteLength( max_length ),
mMaxTextByteLength(max_length),
mPopupMenuHandle(),
mBaseDocIsPristine(TRUE),
mPristineCmd( NULL ),
mLastCmd( NULL ),
mCursorPos( 0 ),
mIsSelecting( FALSE ),
mSelectionStart( 0 ),
mSelectionEnd( 0 ),
mScrolledToBottom( TRUE ),
mOnScrollEndCallback( NULL ),
mOnScrollEndData( NULL ),
mCursorColor( LLUI::sColorsGroup->getColor( "TextCursorColor" ) ),
mFgColor( LLUI::sColorsGroup->getColor( "TextFgColor" ) ),
mDefaultColor( LLUI::sColorsGroup->getColor( "TextDefaultColor" ) ),
mReadOnlyFgColor( LLUI::sColorsGroup->getColor( "TextFgReadOnlyColor" ) ),
mWriteableBgColor( LLUI::sColorsGroup->getColor( "TextBgWriteableColor" ) ),
mReadOnlyBgColor( LLUI::sColorsGroup->getColor( "TextBgReadOnlyColor" ) ),
mFocusBgColor( LLUI::sColorsGroup->getColor( "TextBgFocusColor" ) ),
mReadOnly(FALSE),
mWordWrap( FALSE ),
mShowLineNumbers ( FALSE ),
mTabsToNextField( TRUE ),
mCommitOnFocusLost( FALSE ),
mHideScrollbarForShortDocs( FALSE ),
mTakesNonScrollClicks( TRUE ),
mTrackBottom( FALSE ),
mAllowEmbeddedItems( allow_embedded_items ),
mPristineCmd(NULL),
mLastCmd(NULL),
mCursorPos(0),
mIsSelecting(FALSE),
mSelectionStart(0),
mSelectionEnd(0),
mScrolledToBottom(TRUE),
mOnScrollEndCallback(NULL),
mOnScrollEndData(NULL),
mCursorColor(LLUI::sColorsGroup->getColor("TextCursorColor")),
mFgColor(LLUI::sColorsGroup->getColor("TextFgColor")),
mDefaultColor(LLUI::sColorsGroup->getColor("TextDefaultColor")),
mReadOnlyFgColor(LLUI::sColorsGroup->getColor("TextFgReadOnlyColor")),
mWriteableBgColor(LLUI::sColorsGroup->getColor("TextBgWriteableColor")),
mReadOnlyBgColor(LLUI::sColorsGroup->getColor("TextBgReadOnlyColor")),
mFocusBgColor(LLUI::sColorsGroup->getColor("TextBgFocusColor")),
mReadOnly(parse_html),
mWordWrap(FALSE),
mShowLineNumbers(FALSE),
mTabsToNextField(TRUE),
mCommitOnFocusLost(FALSE),
mHideScrollbarForShortDocs(FALSE),
mTakesNonScrollClicks(TRUE),
mTrackBottom(FALSE),
mAllowEmbeddedItems(allow_embedded_items),
mAcceptCallingCardNames(FALSE),
mHandleEditKeysDirectly( FALSE ),
mHandleEditKeysDirectly(FALSE),
mMouseDownX(0),
mMouseDownY(0),
mLastSelectionX(-1),
@@ -312,39 +314,39 @@ LLTextEditor::LLTextEditor(
updateTextRect();
S32 line_height = ll_round( mGLFont->getLineHeight() );
S32 line_height = ll_round(mGLFont->getLineHeight());
S32 page_size = mTextRect.getHeight() / line_height;
// Init the scrollbar
LLRect scroll_rect;
scroll_rect.setOriginAndSize(
scroll_rect.setOriginAndSize(
getRect().getWidth() - SCROLLBAR_SIZE,
1,
SCROLLBAR_SIZE,
getRect().getHeight() - 1);
S32 lines_in_doc = getLineCount();
mScrollbar = new LLScrollbar( std::string("Scrollbar"), scroll_rect,
mScrollbar = new LLScrollbar(std::string("Scrollbar"), scroll_rect,
LLScrollbar::VERTICAL,
lines_in_doc,
0,
lines_in_doc,
0,
page_size,
NULL);
mScrollbar->setFollowsRight();
mScrollbar->setFollowsTop();
mScrollbar->setFollowsBottom();
mScrollbar->setEnabled( TRUE );
mScrollbar->setVisible( TRUE );
mScrollbar->setEnabled(TRUE);
mScrollbar->setVisible(TRUE);
mScrollbar->setOnScrollEndCallback(mOnScrollEndCallback, mOnScrollEndData);
addChild(mScrollbar);
mBorder = new LLViewBorder( std::string("text ed border"), LLRect(0, getRect().getHeight(), getRect().getWidth(), 0), LLViewBorder::BEVEL_IN, LLViewBorder::STYLE_LINE, UI_TEXTEDITOR_BORDER );
addChild( mBorder );
mBorder = new LLViewBorder(std::string("text ed border"), LLRect(0, getRect().getHeight(), getRect().getWidth(), 0), LLViewBorder::BEVEL_IN, LLViewBorder::STYLE_LINE, UI_TEXTEDITOR_BORDER);
addChild(mBorder);
mParseHTML = parse_html;
appendText(default_text, FALSE, FALSE);
resetDirty(); // Update saved text state
mParseHTML=FALSE;
mHTML.clear();
// make the popup menu available
//LLMenuGL* menu = LLUICtrlFactory::getInstance()->buildMenu("menu_texteditor.xml", parent_view);
@@ -355,6 +357,9 @@ LLTextEditor::LLTextEditor(
}*/
menu->addChild(new LLMenuItemCallGL("Cut", context_cut, NULL, this));
menu->addChild(new LLMenuItemCallGL("Copy", context_copy, NULL, this));
menu->addChild(new LLMenuItemCallGL("Copy Raw", [](void* data) {
if (LLTextEditor* line = static_cast<LLTextEditor*>(data)) line->copyRaw();
}, NULL, this));
menu->addChild(new LLMenuItemCallGL("Paste", context_paste, NULL, this));
menu->addChild(new LLMenuItemCallGL("Delete", context_delete, NULL, this));
menu->addChild(new LLMenuItemCallGL("Select All", context_selectall, NULL, this));
@@ -639,38 +644,30 @@ BOOL LLTextEditor::truncate()
void LLTextEditor::setText(const LLStringExplicit &utf8str)
{
// LLStringUtil::removeCRLF(utf8str);
mUTF8Text = utf8str_removeCRLF(utf8str);
// mUTF8Text = utf8str;
mWText = utf8str_to_wstring(mUTF8Text);
mTextIsUpToDate = TRUE;
// clear out the existing text and segments
mWText.clear();
truncate();
blockUndo();
clearSegments();
// createDefaultSegment();
setCursorPos(0);
deselect();
needsReflow();
// append the new text (supports Url linking)
std::string text(utf8str);
//LLStringUtil::removeCRLF(text);
// appendText modifies mCursorPos...
LLStyleSP style(new LLStyle(TRUE, mReadOnly ? mReadOnlyFgColor : mFgColor, LLFontGL::nameFromFont(mGLFont)));
appendText(utf8str, false, false, style);
// ...so move cursor to top after appending text
setCursorPos(0);
resetDirty();
}
void LLTextEditor::setWText(const LLWString &wtext)
void LLTextEditor::setWText(const LLWString& text)
{
mWText = wtext;
mUTF8Text.clear();
mTextIsUpToDate = FALSE;
truncate();
blockUndo();
setCursorPos(0);
deselect();
needsReflow();
resetDirty();
setText(wstring_to_utf8str(text));
}
// virtual
@@ -766,7 +763,7 @@ void LLTextEditor::selectNext(const std::string& search_text_in, BOOL case_insen
}
// If still -1, then search_text just isn't found.
if (-1 == loc)
if (-1 == loc)
{
mIsSelecting = FALSE;
mSelectionEnd = 0;
@@ -889,9 +886,9 @@ S32 LLTextEditor::getLineStart( S32 line ) const
{
S32 num_lines = getLineCount();
if (num_lines == 0)
{
{
return 0;
}
}
line = llclamp(line, 0, num_lines-1);
S32 segidx = mLineStartList[line].mSegment;
@@ -1446,11 +1443,11 @@ BOOL LLTextEditor::handleRightMouseDown( S32 x, S32 y, MASK mask )
if (glggHunSpell->getSpellCheckHighlight())
{
tempStruct->word = "Hide Misspellings";
}
}
else
{
{
tempStruct->word = "Show Misspellings";
}
}
LLMenuItemCallGL * suggMenuItem = new LLMenuItemCallGL(
tempStruct->word, spell_show, NULL, tempStruct);
@@ -1733,11 +1730,6 @@ S32 LLTextEditor::remove(const S32 pos, const S32 length, const BOOL group_with_
return execute( new LLTextCmdRemove( pos, group_with_next_op, length ) );
}
S32 LLTextEditor::append(const LLWString &wstr, const BOOL group_with_next_op)
{
return insert(mWText.length(), wstr, group_with_next_op);
}
S32 LLTextEditor::overwriteChar(S32 pos, llwchar wc)
{
if ((S32)mWText.length() == pos)
@@ -2170,7 +2162,7 @@ BOOL LLTextEditor::canCopy() const
}
// copy selection to clipboard
void LLTextEditor::copy()
void LLTextEditor::copy(bool raw)
{
if( !canCopy() )
{
@@ -2178,7 +2170,55 @@ void LLTextEditor::copy()
}
S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
S32 length = llabs( mSelectionStart - mSelectionEnd );
gClipboard.copyFromSubstring(mWText, left_pos, length, mSourceID);
// Does our selection include any Segments with links?
if (mParseHTML && raw)
{
auto begin = std::find_if(mSegments.begin(), mSegments.end(), [left_pos](const LLTextSegmentPtr& ptr) {
return ptr->getEnd() > left_pos;
});
auto last = mSegments.end();
if (begin == last || begin->isNull())
{
gClipboard.copyFromSubstring(mWText, left_pos, length, mSourceID);
return;
}
S32 right_pos = left_pos + length, offset = 0;
{
// If our selection starts in the middle of a link, set our left_pos to the beginning of its segment.
auto segment = **begin;
if (auto style = segment.getStyle())
if (!style->getLinkHREF().empty())
left_pos = llmin(segment.getStart(), left_pos);
}
auto text = mWText.substr(left_pos, length);
for (; begin->notNull() && begin != last && (*begin)->getStart() <= right_pos; ++begin)
{
auto segment = **begin;
//llassert(segment->getStyle()); // If someone is stores the result of the S32 constructor, they're in so much trouble!!
const auto& link = segment.getStyle()->getLinkHREF();
if (!link.empty())
{
const S32 label_length = (segment.getEnd() - segment.getStart());
const S32 start = (segment.getStart()+offset)-left_pos;
const auto label = text.substr(start, label_length);
LL_INFOS() << "Our label is " << wstring_to_utf8str(label) << LL_ENDL;
const auto wlink = utf8str_to_wstring(link);
const auto pos = wlink.find(label);
// Do not replace if normal link, or contains normal link (but may omit protocol) but ends the same way
// (i.e. [http://foo.bar/baz foo.bar] should still be restored here but not foo.bar/baz or foo.bar
if (pos == std::string::npos // Label is not (part of) the url
|| (pos != 0 && wlink[pos-1] != '/') || pos+label.size() != wlink.size()) // Label is part of the url but there's more on either side of the url after the protocol
{
constexpr llwchar startchar = '[', space = ' ', endchar = ']';
const auto raw_html = startchar + wlink + space + label + endchar;
text.replace(start, label_length, raw_html);
offset += raw_html.size() - label_length; // Track how much we've offset the string by replacing labels with their raw html and thus adding characters
}
}
}
gClipboard.copyFromSubstring(text, 0, text.length(), mSourceID);
}
else gClipboard.copyFromSubstring(mWText, left_pos, length, mSourceID);
}
BOOL LLTextEditor::canPaste() const
@@ -4077,31 +4117,61 @@ void LLTextEditor::appendColoredText(const std::string &new_text,
style->setVisible(true);
style->setColor(lcolor);
style->setFontName(font_name);
appendStyledText(new_text, allow_undo, prepend_newline, style);
appendText(new_text, allow_undo, prepend_newline, style);
}
void LLTextEditor::appendStyledText(const std::string &new_text,
bool allow_undo,
bool prepend_newline,
LLStyleSP stylep)
static LLTrace::BlockTimerStatHandle FTM_APPEND_TEXT("Append Text");
void LLTextEditor::appendText(const std::string &new_text, bool allow_undo, bool prepend_newline,
const LLStyleSP style)
{
S32 part = (S32)LLTextParser::WHOLE;
if(mParseHTML)
LL_RECORD_BLOCK_TIME(FTM_APPEND_TEXT);
if (new_text.empty())
return;
std::string text = prepend_newline && !mWText.empty() ? ('\n' + new_text) : new_text;
appendTextImpl(text, style);
if (!allow_undo)
{
blockUndo();
}
}
static LLTrace::BlockTimerStatHandle FTM_PARSE_HTML("Parse HTML");
// Appends new text to end of document
void LLTextEditor::appendTextImpl(const std::string &new_text, const LLStyleSP style)
{
std::string text = new_text;
static LLUICachedControl<bool> replace_links("SinguReplaceLinks");
bool is_link = style && !style->getLinkHREF().empty(); // Don't search for URLs inside a link segment (STORM-358).
S32 part = (S32)LLTextParser::WHOLE;
if (mReadOnly && mParseHTML && !is_link) // Singu Note: Do not replace html if the user is going to edit it. (Like in profiles)
{
LL_RECORD_BLOCK_TIME(FTM_PARSE_HTML);
S32 start=0,end=0;
std::string text = new_text;
while ( findHTML(text, &start, &end) )
LLUrlMatch match;
LLStyleSP link_style(new LLStyle);
if (style) *link_style = *style;
link_style->setColor(LLUI::sConfigGroup->getColor4("HTMLLinkColor"));
auto append_substr = [&](const size_t& pos, const size_t& count)
{
LLStyleSP html(new LLStyle);
html->setVisible(true);
html->setColor(mLinkColor);
if (stylep)
{
html->setFontName(stylep->getFontString());
}
html->mUnderline = TRUE;
appendAndHighlightText(text.substr(pos, count), part, style);
};
auto append_link = [&](const std::string& link)
{
link_style->setLinkHREF(match.getUrl());
appendAndHighlightText(link, part, link_style);
};
while (!text.empty() && LLUrlRegistry::instance().findUrl(text, match,
boost::bind(&LLTextEditor::replaceUrl, this, _1, _2, _3)))
{
start = match.getStart();
end = match.getEnd()+1;
// output the text before the Url
if (start > 0)
{
if (part == (S32)LLTextParser::WHOLE ||
@@ -4113,15 +4183,51 @@ void LLTextEditor::appendStyledText(const std::string &new_text,
{
part = (S32)LLTextParser::MIDDLE;
}
std::string subtext=text.substr(0,start);
appendHighlightedText(subtext,allow_undo, prepend_newline, part, stylep);
append_substr(0, start);
}
html->setLinkHREF(text.substr(start,end-start));
appendText(text.substr(start, end-start),allow_undo, prepend_newline, html);
if (end < (S32)text.length())
auto url = match.getUrl();
const auto& label = match.getLabel();
if (replace_links || url == label)
{
text = text.substr(end,text.length() - end);
// add icon before url if need
/* Singu TODO: Icons next to links?
LLTextUtil::processUrlMatch(&match, this, isContentTrusted() || match.isTrusted());
if ((isContentTrusted() || match.isTrusted()) && !match.getIcon().empty() )
{
link_style->setImage(match.getIcon());
setLastSegmentToolTip(LLTrans::getString("TooltipSLIcon"));
}*/
// output the styled url
append_link(label);
bool tooltip_required = !match.getTooltip().empty();
// set the tooltip for the Url label
if (tooltip_required)
{
setLastSegmentToolTip(match.getTooltip());
}
}
else if (!replace_links) // Still link the link itself
{
const auto pos = text.find(url);
bool fallback(pos == std::string::npos); // In special cases like no protocol and brackets
bool brackets(fallback && text.find('[') == start); // Brackets
if (fallback == brackets && start < pos) // Leading text, only in special case if brackets present
append_substr(start, brackets ? 1 : pos-start);
// In the special cases, only link exactly the url, this might not have a protocol so calculate the exact string
if (fallback) url = brackets ? text.substr(start+1, text.find(' ', start+2)-start) : text.substr(start, end-start);
append_link(url); // Append the link
const auto url_end = pos + url.size();
if (fallback == brackets && end > url_end) // Ending text, only in special case if brackets present
append_substr(url_end, end-url_end);
}
// move on to the rest of the text after the Url
if (end < (S32)text.length())
{
text = text.substr(end, text.length() - end);
end=0;
part=(S32)LLTextParser::END;
}
@@ -4130,51 +4236,126 @@ void LLTextEditor::appendStyledText(const std::string &new_text,
break;
}
}
if (part != (S32)LLTextParser::WHOLE) part=(S32)LLTextParser::END;
if (end < (S32)text.length()) appendHighlightedText(text,allow_undo, prepend_newline, part, stylep);
if (part != (S32)LLTextParser::WHOLE)
part=(S32)LLTextParser::END;
if (end < (S32)text.length())
appendAndHighlightText(text, part, style);
}
else
{
appendHighlightedText(new_text, allow_undo, prepend_newline, part, stylep);
appendAndHighlightText(text, part, style);
}
}
void LLTextEditor::appendHighlightedText(const std::string &new_text,
bool allow_undo,
bool prepend_newline,
S32 highlight_part,
LLStyleSP stylep)
void LLTextEditor::setLastSegmentToolTip(const std::string &tooltip)
{
// If LindenUserDir is empty then we didn't login yet.
// In that case we can't instantiate LLTextParser, which
// is initialized per user.
if (mParseHighlights && !gDirUtilp->getLindenUserDir(true).empty())
{
LLTextParser* highlight = LLTextParser::getInstance();
if (highlight && stylep)
{
LLSD pieces = highlight->parsePartialLineHighlights(new_text, stylep->getColor(), (LLTextParser::EHighlightPosition)highlight_part);
bool lprepend=prepend_newline;
for (S32 i=0;i<pieces.size();i++)
{
LLSD color_llsd = pieces[i]["color"];
LLColor4 lcolor;
lcolor.setValue(color_llsd);
LLStyleSP lstylep(new LLStyle(*stylep));
lstylep->setColor(lcolor);
if (i != 0 && (pieces.size() > 1) ) lprepend=FALSE;
appendText((std::string)pieces[i]["text"], allow_undo, lprepend, lstylep);
}
return;
}
}
appendText(new_text, allow_undo, prepend_newline, stylep);
LLTextSegmentPtr segment = mSegments.back();
segment->setToolTip(tooltip);
}
// Appends new text to end of document
void LLTextEditor::appendText(const std::string &new_text, bool allow_undo, bool prepend_newline,
const LLStyleSP stylep)
void LLTextEditor::appendLineBreakSegment(/*const LLStyle::Params& style_params*/)
{
/*
segment_vec_t segments;
LLStyleSP sp(new LLStyle(style_params));
const auto& length = getLength();
segments.push_back(new LLTextSegment(sp, length, length + 1));*/
insertStringNoUndo(getLength(), LLWString(1, '\n'));
}
void LLTextEditor::appendAndHighlightText(const std::string& new_text, S32 highlight_part, const LLStyleSP stylep)
{
if (new_text.empty()) return;
std::string::size_type start = 0;
/*std::string::size_type pos = new_text.find('\n',start);
while(pos!=-1)
{
if(pos!=start)
{
std::string str = std::string(new_text,start,pos-start);
appendAndHighlightTextImpl(str,highlight_part, stylep);
}
appendLineBreakSegment();
start = pos+1;
pos = new_text.find('\n',start);
}*/
std::string str = std::string(new_text,start,new_text.length()-start);
appendAndHighlightTextImpl(str,highlight_part, stylep);
}
void LLTextEditor::replaceUrl(const std::string &url,
const std::string &label,
const std::string &icon)
{
static LLUICachedControl<bool> replace_links("SinguReplaceLinks");
if (!replace_links) return;
// get the full (wide) text for the editor so we can change it
LLWString text = getWText();
LLWString wlabel = utf8str_to_wstring(label);
bool modified = false;
S32 seg_start = 0;
// iterate through each segment looking for ones styled as links
for (auto it = mSegments.begin(); it != mSegments.end(); ++it)
{
LLTextSegment *seg = *it;
LLStyleSP style = seg->getStyle();
// update segment start/end length in case we replaced text earlier
S32 seg_length = seg->getEnd() - seg->getStart();
seg->setStart(seg_start);
seg->setEnd(seg_start + seg_length);
// if we find a link with our Url, then replace the label
if (style->getLinkHREF() == url)
{
S32 start = seg->getStart();
S32 end = seg->getEnd();
text = text.substr(0, start) + wlabel + text.substr(end, text.size() - end + 1);
seg->setEnd(start + wlabel.size());
modified = true;
}
/* Singu TODO: Icons with Urls?
// Icon might be updated when more avatar or group info
// becomes available
if (style->isImage() && style->getLinkHREF() == url)
{
LLUIImagePtr image = image_from_icon_name( icon );
if (image)
{
LLStyle::Params icon_params;
icon_params.image = image;
LLStyleConstSP new_style(new LLStyle(icon_params));
seg->setStyle(new_style);
modified = true;
}
}
*/
// work out the character offset for the next segment
seg_start = seg->getEnd();
}
// update the editor with the new (wide) text string
if (modified)
{
mWText = text;
mTextIsUpToDate = FALSE;
deselect();
setCursorPos(mCursorPos);
needsReflow();
}
}
void LLTextEditor::appendAndHighlightTextImpl(const std::string& new_text, S32 highlight_part, const LLStyleSP stylep)
{
// Save old state
BOOL was_scrolled_to_bottom = (mScrollbar->getDocPos() == mScrollbar->getDocPosMax());
@@ -4189,29 +4370,53 @@ void LLTextEditor::appendText(const std::string &new_text, bool allow_undo, bool
setCursorPos(old_length);
// Add carriage return if not first line
if (getLength() != 0
&& prepend_newline)
// This is where we appendHighlightedText
// If LindenUserDir is empty then we didn't login yet.
// In that case we can't instantiate LLTextParser, which is initialized per user.
if (mParseHighlights && !gDirUtilp->getLindenUserDir(true).empty())
{
std::string final_text = "\n";
final_text += new_text;
append(utf8str_to_wstring(final_text), TRUE);
LLTextParser* highlight = LLTextParser::getInstance();
if (highlight && stylep)
{
LLStyleSP highlight_params(new LLStyle(*stylep));
LLSD pieces = highlight->parsePartialLineHighlights(new_text, highlight_params->getColor(), (LLTextParser::EHighlightPosition)highlight_part);
for (S32 i=0;i<pieces.size();i++)
{
LLSD color_llsd = pieces[i]["color"];
LLColor4 lcolor;
lcolor.setValue(color_llsd);
highlight_params->setColor(lcolor);
LLWString wide_text;
wide_text = utf8str_to_wstring(pieces[i]["text"].asString());
S32 cur_length = getLength();
insertStringNoUndo(cur_length, wide_text);
LLStyleSP sp(new LLStyle(*highlight_params));
LLTextSegmentPtr segmentp;
segmentp = new LLTextSegment(sp, cur_length, cur_length + wide_text.size());
mSegments.push_back(segmentp);
}
return;
}
}
else
//else
{
append(utf8str_to_wstring(new_text), TRUE );
LLWString wide_text;
wide_text = utf8str_to_wstring(new_text);
insertStringNoUndo(getLength(), utf8str_to_wstring(new_text));
if (stylep)
{
S32 segment_start = old_length;
S32 segment_end = old_length + wide_text.size();
LLTextSegmentPtr segment = new LLTextSegment(stylep, segment_start, segment_end);
mSegments.push_back(segment);
}
}
if (stylep)
{
S32 segment_start = old_length;
S32 segment_end = getLength();
LLTextSegmentPtr segment = new LLTextSegment(stylep, segment_start, segment_end );
mSegments.push_back(segment);
}
needsReflow();
// Set the cursor and scroll position
// Maintain the scroll position unless the scroll was at the end of the doc (in which
// case, move it to the new end of the doc) or unless the user was doing actively selecting
@@ -4230,9 +4435,6 @@ void LLTextEditor::appendText(const std::string &new_text, bool allow_undo, bool
mSelectionStart = selection_start;
mSelectionEnd = selection_end;
mIsSelecting = was_selecting;
setCursorPos(cursor_pos);
}
@@ -4244,11 +4446,6 @@ void LLTextEditor::appendText(const std::string &new_text, bool allow_undo, bool
{
setCursorPos(cursor_pos);
}
if( !allow_undo )
{
blockUndo();
}
}
void LLTextEditor::removeTextFromEnd(S32 num_chars)
@@ -4281,13 +4478,16 @@ S32 LLTextEditor::insertStringNoUndo(const S32 pos, const LLWString &wstr)
mWText.insert(pos, wstr);
mTextIsUpToDate = FALSE;
if ( truncate() )
//HACK: If we are readonly we shouldn't need to truncate
if (!mReadOnly && truncate())
{
// The user's not getting everything he's hoping for
make_ui_sound("UISndBadKeystroke");
insert_len = mWText.length() - old_len;
}
needsReflow(/*pos*/);
return insert_len;
}
@@ -4409,6 +4609,24 @@ void LLTextEditor::loadKeywords(const std::string& filename,
static LLTrace::BlockTimerStatHandle FTM_SYNTAX_HIGHLIGHTING("Syntax Highlighting");
static LLTrace::BlockTimerStatHandle FTM_UPDATE_TEXT_SEGMENTS("Update Text Segments");
void LLTextEditor::createDefaultSegment()
{
if (mSegments.empty())
{
LLColor4& text_color = (mReadOnly ? mReadOnlyFgColor : mFgColor);
LLTextSegmentPtr default_segment = new LLTextSegment(text_color, 0, mWText.length());
default_segment->setIsDefault(TRUE);
mSegments.push_back(default_segment);
}
}
void LLTextEditor::clearSegments()
{
mSegments.clear();
createDefaultSegment();
}
void LLTextEditor::updateSegments()
{
{
@@ -4428,15 +4646,9 @@ void LLTextEditor::updateSegments()
// Make sure we have at least one segment
if (mSegments.size() == 1 && mSegments[0]->getIsDefault())
{
mSegments.clear(); // create default segment
}
if (mSegments.empty())
{
LLColor4& text_color = ( mReadOnly ? mReadOnlyFgColor : mFgColor );
LLTextSegmentPtr default_segment = new LLTextSegment( text_color, 0, mWText.length() );
default_segment->setIsDefault(TRUE);
mSegments.push_back(default_segment);
clearSegments(); // create default segment
}
else createDefaultSegment();
}
// Only effective if text was removed from the end of the editor
@@ -4740,15 +4952,34 @@ LLTextSegment::LLTextSegment( const LLColor3& color, S32 start, S32 end ) :
BOOL LLTextSegment::getToolTip(std::string& msg) const
{
// do we have a tooltip for a loaded keyword (for script editor)?
if (mToken && !mToken->getToolTip().empty())
{
const LLWString& wmsg = mToken->getToolTip();
msg = wstring_to_utf8str(wmsg);
return TRUE;
}
// or do we have an explicitly set tooltip (e.g., for Urls)
if (!mTooltip.empty())
{
msg = mTooltip;
return TRUE;
}
return FALSE;
}
void LLTextSegment::setToolTip(const std::string& tooltip)
{
// we cannot replace a keyword tooltip that's loaded from a file
if (mToken)
{
LL_WARNS() << "LLTextSegment::setToolTip: cannot replace keyword tooltip." << LL_ENDL;
return;
}
mTooltip = tooltip;
}
void LLTextSegment::dump() const
@@ -4827,173 +5058,6 @@ void LLTextEditor::setTextEditorParameters(LLXMLNodePtr node)
}
}
///////////////////////////////////////////////////////////////////
// Refactoring note: We may eventually want to replace this with boost::regex or
// boost::tokenizer capabilities since we've already fixed at least two JIRAs
// concerning logic issues associated with this function.
S32 LLTextEditor::findHTMLToken(const std::string &line, S32 pos, BOOL reverse) const
{
std::string openers=" \t\n('\"[{<>";
std::string closers=" \t\n)'\"]}><;";
if (reverse)
{
for (int index=pos; index >= 0; index--)
{
char c = line[index];
S32 m2 = openers.find(c);
if (m2 >= 0)
{
return index+1;
}
}
return 0; // index is -1, don't want to return that.
}
else
{
// adjust the search slightly, to allow matching parenthesis inside the URL
S32 paren_count = 0;
for (int index=pos; index<(S32)line.length(); index++)
{
char c = line[index];
if (c == '(')
{
paren_count++;
}
else if (c == ')')
{
if (paren_count <= 0)
{
return index;
}
else
{
paren_count--;
}
}
else
{
S32 m2 = closers.find(c);
if (m2 >= 0)
{
return index;
}
}
}
return line.length();
}
}
BOOL LLTextEditor::findHTML(const std::string &line, S32 *begin, S32 *end) const
{
S32 m1,m2,m3;
BOOL matched = FALSE;
m1=line.find("://",*end);
if (m1 >= 0) //Easy match.
{
*begin = findHTMLToken(line, m1, TRUE);
*end = findHTMLToken(line, m1, FALSE);
//Load_url only handles http and https so don't hilite ftp, smb, etc.
m2 = line.substr(*begin,(m1 - *begin)).find("http");
m3 = line.substr(*begin,(m1 - *begin)).find("secondlife");
std::string badneighbors=".,<>?';\"][}{=-+_)(*&^%$#@!~`\t\r\n\\";
if (m2 >= 0 || m3>=0)
{
S32 bn = badneighbors.find(line.substr(m1+3,1));
if (bn < 0)
{
matched = TRUE;
}
}
}
/* matches things like secondlife.com (no http://) needs a whitelist to really be effective.
else //Harder match.
{
m1 = line.find(".",*end);
if (m1 >= 0)
{
*end = findHTMLToken(line, m1, FALSE);
*begin = findHTMLToken(line, m1, TRUE);
m1 = line.rfind(".",*end);
if ( ( *end - m1 ) > 2 && m1 > *begin)
{
std::string badneighbors=".,<>/?';\"][}{=-+_)(*&^%$#@!~`";
m2 = badneighbors.find(line.substr(m1+1,1));
m3 = badneighbors.find(line.substr(m1-1,1));
if (m3<0 && m2<0)
{
matched = TRUE;
}
}
}
}
*/
if (matched)
{
S32 strpos, strpos2;
std::string url = line.substr(*begin,*end - *begin);
std::string slurlID = "slurl.com/secondlife/";
strpos = url.find(slurlID);
if (strpos < 0)
{
slurlID="maps.secondlife.com/secondlife/";
strpos = url.find(slurlID);
}
if (strpos < 0)
{
slurlID="secondlife://";
strpos = url.find(slurlID);
}
if (strpos < 0)
{
slurlID="sl://";
strpos = url.find(slurlID);
}
if (strpos >= 0)
{
strpos+=slurlID.length();
while ( ( strpos2=url.find("/",strpos) ) == -1 )
{
if ((*end+2) >= (S32)line.length() || line.substr(*end,1) != " " )
{
matched=FALSE;
break;
}
strpos = (*end + 1) - *begin;
*end = findHTMLToken(line,(*begin + strpos),FALSE);
url = line.substr(*begin,*end - *begin);
}
}
}
if (!matched)
{
*begin=*end=0;
}
return matched;
}
void LLTextEditor::updateAllowingLanguageInput()

View File

@@ -65,9 +65,10 @@ public:
LLTextEditor(const std::string& name,
const LLRect& rect,
S32 max_length,
const std::string &default_text,
const std::string &default_text,
const LLFontGL* glfont = NULL,
BOOL allow_embedded_items = FALSE);
BOOL allow_embedded_items = FALSE,
bool parse_html = false);
virtual ~LLTextEditor();
@@ -131,7 +132,9 @@ public:
virtual BOOL canRedo() const;
virtual void cut();
virtual BOOL canCut() const;
virtual void copy();
void copy(bool raw);
void copyRaw() { copy(true); }
virtual void copy() { copy(false); }
virtual BOOL canCopy() const;
virtual void paste();
virtual BOOL canPaste() const;
@@ -177,19 +180,23 @@ public:
// appends text at end
void appendText(const std::string &wtext, bool allow_undo, bool prepend_newline,
const LLStyleSP stylep = NULL);
void appendTextImpl(const std::string& new_text, const LLStyleSP style);
void setLastSegmentToolTip(const std::string& tooltip);
void appendLineBreakSegment();
void appendAndHighlightText(const std::string& new_text, S32 highlight_part, const LLStyleSP stylep);
void replaceUrl(const std::string& url, const std::string& label, const std::string& icon);
void appendColoredText(const std::string &wtext, bool allow_undo,
bool prepend_newline,
const LLColor4 &color,
const std::string& font_name = LLStringUtil::null);
// if styled text starts a line, you need to prepend a newline.
void appendStyledText(const std::string &new_text, bool allow_undo,
bool prepend_newline,
LLStyleSP stylep = NULL);
void appendHighlightedText(const std::string &new_text, bool allow_undo,
bool prepend_newline, S32 highlight_part,
LLStyleSP stylep);
void appendAndHighlightTextImpl(const std::string& new_text, S32 highlight_part, const LLStyleSP stylep);
// Removes text from the end of document
// Does not change highlight or cursor position.
void removeTextFromEnd(S32 num_chars);
@@ -308,7 +315,6 @@ protected:
void updateTextRect();
const LLRect& getTextRect() const { return mTextRect; }
void assignEmbedded(const std::string &s);
BOOL truncate(); // Returns true if truncation occurs
void removeCharOrTab();
@@ -364,9 +370,6 @@ protected:
virtual llwchar pasteEmbeddedItem(llwchar ext_char) { return ext_char; }
virtual void bindEmbeddedChars(const LLFontGL* font) const {}
virtual void unbindEmbeddedChars(const LLFontGL* font) const {}
S32 findHTMLToken(const std::string &line, S32 pos, BOOL reverse) const;
BOOL findHTML(const std::string &line, S32 *begin, S32 *end) const;
// Abstract inner base class representing an undoable editor command.
// Concrete sub-classes can be defined for operations such as insert, remove, etc.
@@ -408,7 +411,6 @@ protected:
void removeWord(bool prev);
S32 insert(const S32 pos, const LLWString &wstr, const BOOL group_with_next_op);
S32 remove(const S32 pos, const S32 length, const BOOL group_with_next_op);
S32 append(const LLWString &wstr, const BOOL group_with_next_op);
// Direct operations
S32 insertStringNoUndo(S32 pos, const LLWString &wstr); // returns num of chars actually inserted
@@ -480,6 +482,8 @@ private:
void pasteHelper(bool is_primary);
void onKeyStroke();
void createDefaultSegment();
void clearSegments();
void updateSegments();
void pruneSegments();
@@ -615,6 +619,7 @@ public:
LLTextSegment( const LLColor3& color, S32 start, S32 end );
S32 getStart() const { return mStart; }
void setStart(S32 start) { mStart = start; }
S32 getEnd() const { return mEnd; }
void setEnd( S32 end ) { mEnd = end; }
const LLColor4& getColor() const { return mStyle->getColor(); }
@@ -626,6 +631,7 @@ public:
void setToken( LLKeywordToken* token ) { mToken = token; }
LLKeywordToken* getToken() const { return mToken; }
BOOL getToolTip( std::string& msg ) const;
void setToolTip(const std::string& tooltip);
void dump() const;
@@ -643,6 +649,7 @@ private:
S32 mEnd;
LLKeywordToken* mToken;
BOOL mIsDefault;
std::string mTooltip;
};

View File

@@ -907,6 +907,20 @@ RIP Latif Khalifa.</string>
<key>Value</key>
<boolean>0</boolean>
</map>
<key>SinguReplaceLinks</key>
<map>
<key>Comment</key>
<string>Whether or not to visually replace special links like SLURLs with labels where applicable.
While having this on allows people to mislead you with links that look like other links, it also allows you to use SLURLs that appear as people's names and increasingly more scripts use this to run faster.
You can always select the text, right click and select Copy Raw to copy the hidden contents of the link.
Changing this setting only affects new text.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<boolean>1</boolean>
</map>
<key>WarnRecommendedUpdate</key>
<map>
<key>Comment</key>

View File

@@ -144,7 +144,7 @@ LLFloaterAbout::LLFloaterAbout()
__DATE__, __TIME__,
LLVersionInfo::getChannel().c_str()));
support_widget->appendColoredText(version, FALSE, FALSE, gColors.getColor("TextFgReadOnlyColor"));
support_widget->appendStyledText(LLTrans::getString("ReleaseNotes"), false, false, viewer_link_style);
support_widget->appendText(LLTrans::getString("ReleaseNotes"), false, false, viewer_link_style);
std::string support;
support.append("\n\n");
@@ -209,7 +209,7 @@ LLFloaterAbout::LLFloaterAbout()
{
LLStyleSP server_link_style(new LLStyle(*viewer_link_style));
server_link_style->setLinkHREF(url);
support_widget->appendStyledText(LLTrans::getString("ReleaseNotes"), false, false, server_link_style);
support_widget->appendText(LLTrans::getString("ReleaseNotes"), false, false, server_link_style);
}
support = "\n\n";

View File

@@ -61,7 +61,6 @@
#include "llspeakers.h"
#include "llstylemap.h"
#include "lluictrlfactory.h"
#include "llurlregistry.h"
#include "llviewermessage.h"
#include "llviewertexteditor.h"
#include "llviewerwindow.h"
@@ -177,31 +176,6 @@ void LLFloaterChat::updateConsoleVisibility()
|| (getHost() && getHost()->isMinimized() )); // are we hosted in a minimized floater?
}
void handle_registered_urls(std::string ui_msg, bool prepend_newline, LLStyleSP style, LLViewerTextEditor* edit, const LLColor4& color)
{
LLUrlMatch match;
auto& registry = LLUrlRegistry::instance();
while (!ui_msg.empty() && registry.findUrl(ui_msg, match))
{
if (match.getStart() > 0)
{
style->setColor(color);
edit->appendStyledText(ui_msg.substr(0, match.getStart()), false, prepend_newline, style);
prepend_newline = false;
}
style->setColor(gSavedSettings.getColor4("HTMLLinkColor"));
style->setLinkHREF(match.getUrl());
edit->appendStyledText(match.getLabel(), false, prepend_newline, style);
style->setLinkHREF(LLStringUtil::null);
prepend_newline = false;
ui_msg = (ui_msg.size() <= match.getEnd() + 1) ? LLStringUtil::null : ui_msg.substr(match.getEnd() + 1);
}
style->setColor(color);
if (!ui_msg.empty()) edit->appendStyledText(ui_msg, false, prepend_newline, style);
}
void add_timestamped_line(LLViewerTextEditor* edit, LLChat chat, const LLColor4& color)
{
std::string line = chat.mText;
@@ -244,12 +218,13 @@ void add_timestamped_line(LLViewerTextEditor* edit, LLChat chat, const LLColor4&
line = line.substr(chat.mFromName.length() + 1);
LLStyleSP sourceStyle = LLStyleMap::instance().lookup(chat.mFromID, chat.mURL);
sourceStyle->mItalic = is_irc;
edit->appendStyledText(start_line, false, prepend_newline, sourceStyle);
edit->appendText(start_line, false, prepend_newline, sourceStyle);
prepend_newline = false;
}
LLStyleSP style(new LLStyle);
style->setColor(color);
style->mItalic = is_irc;
handle_registered_urls(line, prepend_newline, style, edit, color);
edit->appendText(line, false, prepend_newline, style);
}
void log_chat_text(const LLChat& chat)

View File

@@ -458,7 +458,7 @@ void LLFloaterOutbox::updateView()
LLStyleSP subs_link_style(new LLStyle);
subs_link_style->setLinkHREF(subs_link);
subs_link_style->setColor(gSavedSettings.getColor4("HTMLLinkColor"));
mInventoryText->appendStyledText(subs_text, false, false, subs_link_style);
mInventoryText->appendText(subs_text, false, false, subs_link_style);
mInventoryText->appendColoredText(outbox_text2, false, false, color);
mInventoryTitle->setValue(outbox_title);
mInventoryPlaceholder->getParent()->setToolTip(outbox_tooltip);

View File

@@ -75,7 +75,7 @@ BOOL LLFloaterVoiceEffect::postBuild()
LLStyleSP link(new LLStyle);
link->setLinkHREF(LLTrans::getString("voice_morphing_url"));
link->setColor(gSavedSettings.getColor4("HTMLLinkColor"));
editor->appendStyledText(text, false, false, link);
editor->appendText(text, false, false, link);
}
mVoiceEffectList = getChild<LLScrollListCtrl>("voice_effect_list");

View File

@@ -170,19 +170,16 @@ LLGroupNotifyBox::LLGroupNotifyBox(const std::string& subject,
DB_GROUP_NOTICE_MSG_STR_LEN,
LLStringUtil::null,
LLFontGL::getFontSansSerif(),
FALSE);
FALSE,
true);
static const LLStyleSP headerstyle(new LLStyle(true,LLColor4::black,"SansSerifBig"));
static const LLStyleSP datestyle(new LLStyle(true,LLColor4::black,"serif"));
text->appendStyledText(subject + "\n",false,false,headerstyle);
text->appendText(subject + '\n',false,false,headerstyle);
text->appendStyledText(time_buf,false,false,datestyle);
// Sadly, our LLTextEditor can't handle both styled and unstyled text
// at the same time. Hence this space must be styled. JC
text->appendColoredText(std::string(" "),false,false,LLColor4::grey4);
text->setParseHTML(TRUE);
text->appendColoredText(std::string("\n\n") + message,false,false,LLColor4::grey4);
text->appendText(time_buf,false,false,datestyle);
text->appendColoredText(std::string(" \n\n") + message,false,false,LLColor4::grey4);
LLColor4 semi_transparent(1.0f,1.0f,1.0f,0.8f);
text->setCursor(0,0);

View File

@@ -804,7 +804,7 @@ void LLFloaterIMPanel::addHistoryLine(const std::string &utf8msg, LLColor4 incol
// Convert the name to a hotlink and add to message.
LLStyleSP source_style = LLStyleMap::instance().lookupAgent(source);
source_style->mItalic = is_irc;
mHistoryEditor->appendStyledText(show_name,false,prepend_newline,source_style);
mHistoryEditor->appendText(show_name,false,prepend_newline,source_style);
}
prepend_newline = false;
}
@@ -812,11 +812,10 @@ void LLFloaterIMPanel::addHistoryLine(const std::string &utf8msg, LLColor4 incol
// Append the chat message in style
{
LLStyleSP style(new LLStyle);
style->setColor(incolor);
style->mItalic = is_irc;
style->mBold = from_user && gSavedSettings.getBOOL("SingularityBoldGroupModerator") && isModerator(source);
void handle_registered_urls(std::string ui_msg, bool prepend_newline, LLStyleSP style, LLViewerTextEditor* edit, const LLColor4& color);
handle_registered_urls(utf8msg, prepend_newline, style, mHistoryEditor, incolor);
mHistoryEditor->appendText(utf8msg, false, prepend_newline, style);
}
if (log_to_file

View File

@@ -234,7 +234,7 @@ LLNotifyBox::LLNotifyBox(LLNotificationPtr notification)
const S32 MAX_LENGTH = 512 + 20 + DB_FIRST_NAME_BUF_SIZE + DB_LAST_NAME_BUF_SIZE + DB_INV_ITEM_NAME_BUF_SIZE; // For script dialogs: add space for title.
auto text = new LLTextEditor(std::string("box"), LLRect(x, y, getRect().getWidth()-2, mIsTip ? BOTTOM : BTN_TOP+16), MAX_LENGTH, message, sFont, FALSE);
auto text = new LLTextEditor(std::string("box"), LLRect(x, y, getRect().getWidth()-2, mIsTip ? BOTTOM : BTN_TOP+16), MAX_LENGTH, message, sFont, FALSE, true);
text->setWordWrap(TRUE);
text->setTabStop(FALSE);
text->setMouseOpaque(FALSE);

View File

@@ -286,6 +286,8 @@ void LLPreviewNotecard::loadAsset()
// request the asset.
if (const LLInventoryItem* item = getItem())
{
bool modify = gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE);
if (modify) editor->setParseHTML(false); // Don't do the url parsing or we'll lose text!
if (gAgent.allowOperation(PERM_COPY, item->getPermissions(),
GP_OBJECT_MANIPULATE)
|| gAgent.isGodlike())
@@ -344,8 +346,7 @@ void LLPreviewNotecard::loadAsset()
editor->setEnabled(FALSE);
mAssetStatus = PREVIEW_ASSET_LOADED;
}
if(!gAgent.allowOperation(PERM_MODIFY, item->getPermissions(),
GP_OBJECT_MANIPULATE))
if(!modify)
{
editor->setEnabled(FALSE);
// <edit> You can always save in task inventory

View File

@@ -578,8 +578,9 @@ LLViewerTextEditor::LLViewerTextEditor(const std::string& name,
S32 max_length,
const std::string& default_text,
const LLFontGL* font,
BOOL allow_embedded_items)
: LLTextEditor(name, rect, max_length, default_text, font, allow_embedded_items),
BOOL allow_embedded_items,
bool parse_html)
: LLTextEditor(name, rect, max_length, default_text, font, allow_embedded_items, parse_html),
mDragItemChar(0),
mDragItemSaved(FALSE),
mInventoryCallback(new LLEmbeddedNotecardOpener)
@@ -1679,7 +1680,7 @@ LLView* LLViewerTextEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlF
text_editor->initFromXML(node, parent);
// add text after all parameters have been set
text_editor->appendStyledText(text, FALSE, FALSE);
text_editor->appendText(text, FALSE, FALSE);
return text_editor;
}

View File

@@ -48,7 +48,8 @@ public:
S32 max_length,
const std::string& default_text = std::string(),
const LLFontGL* glfont = NULL,
BOOL allow_embedded_items = FALSE);
BOOL allow_embedded_items = FALSE,
bool parse_html = false);
virtual ~LLViewerTextEditor();