SL-19575 Create emoji gallery (fix bug with drawing emojis in chat history)

master
Alexander Gavriliuk 2023-05-17 14:28:36 +02:00 committed by Guru
parent bb96ac2621
commit 671978e392
12 changed files with 123 additions and 107 deletions

View File

@ -837,10 +837,19 @@ std::string LLStringOps::sPM;
// static
bool LLStringOps::isEmoji(llwchar wch)
{
switch (ublock_getCode(wch))
int ublock = ublock_getCode(wch);
switch (ublock)
{
case UBLOCK_GENERAL_PUNCTUATION:
case UBLOCK_LETTERLIKE_SYMBOLS:
case UBLOCK_ARROWS:
case UBLOCK_MISCELLANEOUS_TECHNICAL:
case UBLOCK_ENCLOSED_ALPHANUMERICS:
case UBLOCK_GEOMETRIC_SHAPES:
case UBLOCK_MISCELLANEOUS_SYMBOLS:
case UBLOCK_DINGBATS:
case UBLOCK_CJK_SYMBOLS_AND_PUNCTUATION:
case UBLOCK_ENCLOSED_CJK_LETTERS_AND_MONTHS:
case UBLOCK_MISCELLANEOUS_SYMBOLS_AND_PICTOGRAPHS:
case UBLOCK_EMOTICONS:
case UBLOCK_TRANSPORT_AND_MAP_SYMBOLS:

View File

@ -199,6 +199,17 @@ std::string LLEmojiDictionary::getNameFromEmoji(llwchar ch) const
return (mEmoji2Descr.end() != it) ? it->second->Name : LLStringUtil::null;
}
bool LLEmojiDictionary::isEmoji(llwchar ch) const
{
// Currently used codes: A9,AE,203C,2049,2122,...,2B55,3030,303D,3297,3299,1F004,...,1FAF6
if (ch == 0xA9 || ch == 0xAE || (ch >= 0x2000 && ch < 0x3300) || (ch >= 0x1F000 && ch < 0x20000))
{
return mEmoji2Descr.find(ch) != mEmoji2Descr.end();
}
return false;
}
void LLEmojiDictionary::addEmoji(LLEmojiDescriptor&& descr)
{
mEmojis.push_back(descr);

View File

@ -68,6 +68,7 @@ public:
const LLEmojiDescriptor* getDescriptorFromEmoji(llwchar emoji) const;
const LLEmojiDescriptor* getDescriptorFromShortCode(const std::string& short_code) const;
std::string getNameFromEmoji(llwchar ch) const;
bool isEmoji(llwchar ch) const;
const emoji2descr_map_t& getEmoji2Descr() const { return mEmoji2Descr; }
const code2descr_map_t& getShortCode2Descr() const { return mShortCode2Descr; }

View File

@ -29,6 +29,7 @@
#include "lltextbase.h"
#include "llemojidictionary.h"
#include "llemojihelper.h"
#include "lllocalcliprect.h"
#include "llmenugl.h"
@ -366,7 +367,7 @@ void LLTextBase::onValueChange(S32 start, S32 end)
{
}
std::vector<LLRect> LLTextBase::getSelctionRects()
std::vector<LLRect> LLTextBase::getSelectionRects()
{
// Nor supposed to be called without selection
llassert(hasSelection());
@ -463,7 +464,7 @@ void LLTextBase::drawSelectionBackground()
// Draw selection even if we don't have keyboard focus for search/replace
if (hasSelection() && !mLineInfoList.empty())
{
std::vector<LLRect> selection_rects = getSelctionRects();
std::vector<LLRect> selection_rects = getSelectionRects();
// Draw the selection box (we're using a box instead of reversing the colors on the selected text).
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
@ -904,9 +905,12 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s
// Insert special segments where necessary (insertSegment takes care of splitting normal text segments around them for us)
{
LLStyleSP emoji_style;
LLEmojiDictionary* ed = LLEmojiDictionary::instanceExists() ? LLEmojiDictionary::getInstance() : NULL;
for (S32 text_kitty = 0, text_len = wstr.size(); text_kitty < text_len; text_kitty++)
{
if (LLStringOps::isEmoji(wstr[text_kitty]))
llwchar code = wstr[text_kitty];
bool isEmoji = ed ? ed->isEmoji(code) : LLStringOps::isEmoji(code);
if (isEmoji)
{
if (!emoji_style)
{
@ -2181,8 +2185,8 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para
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))
while (LLUrlRegistry::instance().findUrl(text, match,
boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3), isContentTrusted() || mAlwaysShowIcons))
{
start = match.getStart();
end = match.getEnd()+1;
@ -2430,18 +2434,18 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig
LLStyle::Params normal_style_params(style_params);
normal_style_params.font.style("NORMAL");
LLStyleConstSP normal_sp(new LLStyle(normal_style_params));
segments.push_back(new LLOnHoverChangeableTextSegment(sp, normal_sp, segment_start, segment_end, *this ));
segments.push_back(new LLOnHoverChangeableTextSegment(sp, normal_sp, segment_start, segment_end, *this));
}
else
{
segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this ));
segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this));
}
insertStringNoUndo(getLength(), wide_text, &segments);
}
// Set the cursor and scroll position
if( selection_start != selection_end )
if (selection_start != selection_end)
{
mSelectionStart = selection_start;
mSelectionEnd = selection_end;
@ -2449,7 +2453,7 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig
mIsSelecting = was_selecting;
setCursorPos(cursor_pos);
}
else if( cursor_was_at_end )
else if (cursor_was_at_end)
{
setCursorPos(getLength());
}
@ -2461,25 +2465,28 @@ 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)
{
if (new_text.empty()) return;
if (new_text.empty())
{
return;
}
std::string::size_type start = 0;
std::string::size_type pos = new_text.find("\n",start);
std::string::size_type pos = new_text.find("\n", start);
while(pos!=-1)
while (pos != std::string::npos)
{
if(pos!=start)
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_on_hover_only);
}
appendLineBreakSegment(style_params);
start = pos+1;
pos = new_text.find("\n",start);
pos = new_text.find("\n", start);
}
std::string str = std::string(new_text,start,new_text.length()-start);
appendAndHighlightTextImpl(str,highlight_part, style_params, underline_on_hover_only);
std::string str = std::string(new_text, start, new_text.length() - start);
appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only);
}

View File

@ -654,7 +654,7 @@ protected:
return mLabel.getString() + getToolTip();
}
std::vector<LLRect> getSelctionRects();
std::vector<LLRect> getSelectionRects();
protected:
// text segmentation and flow

View File

@ -1215,9 +1215,7 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
llassert(mEditor);
if (!mEditor)
{
return;
}
bool from_me = chat.mFromID == gAgent.getID();
mEditor->setPlainText(use_plain_text_chat_history);
@ -1227,26 +1225,16 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
mUnreadChatSources.insert(chat.mFromName);
mMoreChatPanel->setVisible(TRUE);
std::string chatters;
for (unread_chat_source_t::iterator it = mUnreadChatSources.begin();
it != mUnreadChatSources.end();)
for (const std::string& source : mUnreadChatSources)
{
chatters += *it;
if (++it != mUnreadChatSources.end())
{
chatters += ", ";
}
chatters += chatters.size() ? ", " + source : source;
}
LLStringUtil::format_map_t args;
args["SOURCES"] = chatters;
if (mUnreadChatSources.size() == 1)
{
mMoreChatText->setValue(LLTrans::getString("unread_chat_single", args));
}
else
{
mMoreChatText->setValue(LLTrans::getString("unread_chat_multiple", args));
}
std::string xml_desc = mUnreadChatSources.size() == 1 ?
"unread_chat_single" : "unread_chat_multiple";
mMoreChatText->setValue(LLTrans::getString(xml_desc, args));
S32 height = mMoreChatText->getTextPixelHeight() + 5;
mMoreChatPanel->reshape(mMoreChatPanel->getRect().getWidth(), height);
}
@ -1294,11 +1282,11 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
body_message_params.font.style = "ITALIC";
}
if(chat.mChatType == CHAT_TYPE_WHISPER)
if (chat.mChatType == CHAT_TYPE_WHISPER)
{
body_message_params.font.style = "ITALIC";
}
else if(chat.mChatType == CHAT_TYPE_SHOUT)
else if (chat.mChatType == CHAT_TYPE_SHOUT)
{
body_message_params.font.style = "BOLD";
}
@ -1345,10 +1333,10 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
}
// names showing
if (args["show_names_for_p2p_conv"].asBoolean() && utf8str_trim(chat.mFromName).size() != 0)
if (args["show_names_for_p2p_conv"].asBoolean() && utf8str_trim(chat.mFromName).size())
{
// Don't hotlink any messages from the system (e.g. "Second Life:"), so just add those in plain text.
if ( chat.mSourceType == CHAT_SOURCE_OBJECT && chat.mFromID.notNull())
if (chat.mSourceType == CHAT_SOURCE_OBJECT && chat.mFromID.notNull())
{
// for object IMs, create a secondlife:///app/objectim SLapp
std::string url = LLViewerChat::getSenderSLURL(chat, args);
@ -1408,36 +1396,27 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
&& mIsLastMessageFromLog == message_from_log) //distinguish between current and previous chat session's histories
{
view = getSeparator();
p.top_pad = mTopSeparatorPad;
p.bottom_pad = mBottomSeparatorPad;
if (!view)
{
// Might be wiser to make this LL_ERRS, getSeparator() should work in case of correct instalation.
LL_WARNS() << "Failed to create separator from " << mMessageSeparatorFilename << ": can't append to history" << LL_ENDL;
return;
}
p.top_pad = mTopSeparatorPad;
p.bottom_pad = mBottomSeparatorPad;
}
else
{
view = getHeader(chat, name_params, args);
if (mEditor->getLength() == 0)
p.top_pad = 0;
else
p.top_pad = mTopHeaderPad;
if (teleport_separator)
{
p.bottom_pad = mBottomSeparatorPad;
}
else
{
p.bottom_pad = mBottomHeaderPad;
}
if (!view)
{
LL_WARNS() << "Failed to create header from " << mMessageHeaderFilename << ": can't append to history" << LL_ENDL;
return;
}
if (!view)
{
LL_WARNS() << "Failed to create header from " << mMessageHeaderFilename << ": can't append to history" << LL_ENDL;
return;
}
p.top_pad = mEditor->getLength() ? mTopHeaderPad : 0;
p.bottom_pad = teleport_separator ? mBottomSeparatorPad : mBottomHeaderPad;
}
p.view = view;
@ -1510,11 +1489,10 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
}
}
// usual messages showing
else if(!teleport_separator)
else if (!teleport_separator)
{
std::string message = irc_me ? chat.mText.substr(3) : chat.mText;
//MESSAGE TEXT PROCESSING
//*HACK getting rid of redundant sender names in system notifications sent using sender name (see EXT-5010)
if (use_plain_text_chat_history && !from_me && chat.mFromID.notNull())

View File

@ -60,9 +60,25 @@ public:
{
LLScrollListItem::draw(rect, fg_color, hover_color, select_color, highlight_color, column_padding);
LLWString wstr(1, mEmoji);
S32 width = getColumn(0)->getWidth();
LLFontGL::getFontEmoji()->render(LLWString(1, mEmoji), 0, rect.mLeft + width / 2, rect.getCenterY(), LLColor4::white,
LLFontGL::HCENTER, LLFontGL::VCENTER, LLFontGL::NORMAL, LLFontGL::DROP_SHADOW_SOFT, 1, S32_MAX, nullptr, false, true);
F32 x = rect.mLeft + width / 2;
F32 y = rect.getCenterY();
LLFontGL::getFontEmoji()->render(
wstr, // wstr
0, // begin_offset
x, // x
y, // y
LLColor4::white, // color
LLFontGL::HCENTER, // halign
LLFontGL::VCENTER, // valign
LLFontGL::NORMAL, // style
LLFontGL::DROP_SHADOW_SOFT, // shadow
1, // max_chars
S32_MAX, // max_pixels
nullptr, // right_x
false, // use_ellipses
true); // use_color
}
private:
@ -101,13 +117,13 @@ BOOL LLFloaterEmojiPicker::postBuild()
{
// Should be initialized first
mPreviewEmoji = getChild<LLButton>("PreviewEmoji");
mPreviewEmoji->setClickedCallback(boost::bind(&LLFloaterEmojiPicker::onPreviewEmojiClick, this));
mPreviewEmoji->setClickedCallback([this](LLUICtrl*, const LLSD&) { onPreviewEmojiClick(); });
mCategory = getChild<LLComboBox>("Category");
mCategory->setCommitCallback(boost::bind(&LLFloaterEmojiPicker::onCategoryCommit, this));
mCategory->setCommitCallback([this](LLUICtrl*, const LLSD&) { onCategoryCommit(); });
const LLEmojiDictionary::cat2descrs_map_t& cat2Descrs = LLEmojiDictionary::instance().getCategory2Descrs();
mCategory->clearRows();
for (const LLEmojiDictionary::cat2descrs_item_t& item : cat2Descrs)
for (const LLEmojiDictionary::cat2descrs_item_t item : cat2Descrs)
{
std::string value = item.first;
std::string name = value;
@ -117,13 +133,13 @@ BOOL LLFloaterEmojiPicker::postBuild()
mCategory->setSelectedByValue(mSelectedCategory, true);
mSearch = getChild<LLLineEditor>("Search");
mSearch->setKeystrokeCallback(boost::bind(&LLFloaterEmojiPicker::onSearchKeystroke, this, _1, _2), NULL);
mSearch->setKeystrokeCallback([this](LLLineEditor*, void*) { onSearchKeystroke(); }, NULL);
mSearch->setFont(LLViewerChat::getChatFont());
mSearch->setText(mSearchPattern);
mEmojis = getChild<LLScrollListCtrl>("Emojis");
mEmojis->setCommitCallback(boost::bind(&LLFloaterEmojiPicker::onEmojiSelect, this));
mEmojis->setDoubleClickCallback(boost::bind(&LLFloaterEmojiPicker::onEmojiPick, this));
mEmojis->setCommitCallback([this](LLUICtrl*, const LLSD&) { onEmojiSelect(); });
mEmojis->setDoubleClickCallback([this]() { onEmojiPick(); });
fillEmojis();
return TRUE;
@ -139,7 +155,7 @@ void LLFloaterEmojiPicker::fillEmojis()
mEmojis->clearRows();
const LLEmojiDictionary::emoji2descr_map_t& emoji2Descr = LLEmojiDictionary::instance().getEmoji2Descr();
for (const LLEmojiDictionary::emoji2descr_item_t& it : emoji2Descr)
for (const LLEmojiDictionary::emoji2descr_item_t it : emoji2Descr)
{
const LLEmojiDescriptor* descr = it.second;
@ -150,6 +166,8 @@ void LLFloaterEmojiPicker::fillEmojis()
continue;
LLScrollListItem::Params params;
// The following line adds default monochrome view of the emoji (is shown as an example)
//params.columns.add().column("look").value(wstring_to_utf8str(LLWString(1, it.first)));
params.columns.add().column("name").value(descr->Name);
mEmojis->addRow(new LLEmojiScrollListItem(it.first, params), params);
}
@ -194,7 +212,7 @@ void LLFloaterEmojiPicker::onCategoryCommit()
fillEmojis();
}
void LLFloaterEmojiPicker::onSearchKeystroke(LLLineEditor* caller, void* user_data)
void LLFloaterEmojiPicker::onSearchKeystroke()
{
mSearchPattern = mSearch->getText();
mSelectedEmojiIndex = 0;

View File

@ -57,7 +57,7 @@ private:
bool matchesPattern(const LLEmojiDescriptor* descr);
void onCategoryCommit();
void onSearchKeystroke(class LLLineEditor* caller, void* user_data);
void onSearchKeystroke();
void onPreviewEmojiClick();
void onEmojiSelect();
void onEmojiEmpty();

View File

@ -435,8 +435,8 @@ void LLFloaterIMSessionTab::onEmojiPanelBtnClicked(LLFloaterIMSessionTab* self)
if (!picker->isShown())
{
picker->show(
boost::bind(&LLFloaterIMSessionTab::onEmojiPicked, self, _1),
boost::bind(&LLFloaterIMSessionTab::onEmojiPickerClosed, self));
[self](llwchar emoji) { self->onEmojiPicked(emoji); },
[self]() { self->onEmojiPickerClosed(); });
if (LLFloater* root_floater = gFloaterView->getParentFloater(self))
{
root_floater->addDependentFloater(picker, TRUE, TRUE);
@ -461,51 +461,43 @@ void LLFloaterIMSessionTab::onEmojiPickerClosed()
std::string LLFloaterIMSessionTab::appendTime()
{
time_t utc_time;
utc_time = time_corrected();
std::string timeStr ="["+ LLTrans::getString("TimeHour")+"]:["
+LLTrans::getString("TimeMin")+"]";
std::string timeStr = "[" + LLTrans::getString("TimeHour") + "]:"
"[" + LLTrans::getString("TimeMin") + "]";
LLSD substitution;
substitution["datetime"] = (S32) utc_time;
LLStringUtil::format (timeStr, substitution);
substitution["datetime"] = (S32)time_corrected();
LLStringUtil::format(timeStr, substitution);
return timeStr;
}
void LLFloaterIMSessionTab::appendMessage(const LLChat& chat, const LLSD &args)
void LLFloaterIMSessionTab::appendMessage(const LLChat& chat, const LLSD& args)
{
if (chat.mMuted || !mChatHistory)
return;
// Update the participant activity time
LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance();
if (im_box)
{
im_box->setTimeNow(mSessionID,chat.mFromID);
im_box->setTimeNow(mSessionID, chat.mFromID);
}
LLChat& tmp_chat = const_cast<LLChat&>(chat);
if(tmp_chat.mTimeStr.empty())
if (tmp_chat.mTimeStr.empty())
tmp_chat.mTimeStr = appendTime();
if (!chat.mMuted)
{
tmp_chat.mFromName = chat.mFromName;
LLSD chat_args;
if (args) chat_args = args;
chat_args["use_plain_text_chat_history"] =
gSavedSettings.getBOOL("PlainTextChatHistory");
chat_args["show_time"] = gSavedSettings.getBOOL("IMShowTime");
chat_args["show_names_for_p2p_conv"] =
!mIsP2PChat || gSavedSettings.getBOOL("IMShowNamesForP2PConv");
tmp_chat.mFromName = chat.mFromName;
if (mChatHistory)
{
mChatHistory->appendMessage(chat, chat_args);
}
}
LLSD chat_args = args;
chat_args["use_plain_text_chat_history"] =
gSavedSettings.getBOOL("PlainTextChatHistory");
chat_args["show_time"] = gSavedSettings.getBOOL("IMShowTime");
chat_args["show_names_for_p2p_conv"] = !mIsP2PChat ||
gSavedSettings.getBOOL("IMShowNamesForP2PConv");
mChatHistory->appendMessage(chat, chat_args);
}
static LLTrace::BlockTimerStatHandle FTM_BUILD_CONVERSATION_VIEW_PARTICIPANT("Build Conversation View");

View File

@ -140,7 +140,7 @@ protected:
/* virtual */ void onFocusReceived();
// prepare chat's params and out one message to chatHistory
void appendMessage(const LLChat& chat, const LLSD &args = 0);
void appendMessage(const LLChat& chat, const LLSD& args = LLSD());
std::string appendTime();
void assignResizeLimits();

View File

@ -187,7 +187,7 @@ void LLScriptEditor::drawSelectionBackground()
// Draw selection even if we don't have keyboard focus for search/replace
if( hasSelection() && !mLineInfoList.empty())
{
std::vector<LLRect> selection_rects = getSelctionRects();
std::vector<LLRect> selection_rects = getSelectionRects();
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
const LLColor4& color = mReadOnly ? mReadOnlyFgColor : mFgColor;

View File

@ -10,11 +10,11 @@
bottom_separator_pad="1"
top_header_pad="12"
bottom_header_pad="5"
max_length="2147483647"
track_bottom="true"
name="chat_history"
type="string"
word_wrap="true"
max_length="2147483647"
track_bottom="true"
name="chat_history"
type="string"
word_wrap="true"
line_spacing.multiple="1.0"
font="SansSerif">
<more_chat_text