diff --git a/.github/workflows/cla.yaml b/.github/workflows/cla.yaml index fa180c66c9..b4b2565889 100644 --- a/.github/workflows/cla.yaml +++ b/.github/workflows/cla.yaml @@ -23,3 +23,4 @@ jobs: path-to-signatures: signatures.json remote-organization-name: secondlife remote-repository-name: cla-signatures + allowlist: callum@mbp.localdomain diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index e047f2965d..2511ca00b3 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -717,49 +717,114 @@ std::string utf8str_removeCRLF(const std::string& utf8str) return out; } +llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t length) +{ + switch (length) + { + case 2: + return ((utf8str[offset] & 0x1F) << 6) + + (utf8str[offset + 1] & 0x3F); + case 3: + return ((utf8str[offset] & 0x0F) << 12) + + ((utf8str[offset + 1] & 0x3F) << 6) + + (utf8str[offset + 2] & 0x3F); + case 4: + return ((utf8str[offset] & 0x07) << 18) + + ((utf8str[offset + 1] & 0x3F) << 12) + + ((utf8str[offset + 2] & 0x3F) << 6) + + (utf8str[offset + 3] & 0x3F); + case 5: + return ((utf8str[offset] & 0x03) << 24) + + ((utf8str[offset + 1] & 0x3F) << 18) + + ((utf8str[offset + 2] & 0x3F) << 12) + + ((utf8str[offset + 3] & 0x3F) << 6) + + (utf8str[offset + 4] & 0x3F); + case 6: + return ((utf8str[offset] & 0x01) << 30) + + ((utf8str[offset + 1] & 0x3F) << 24) + + ((utf8str[offset + 2] & 0x3F) << 18) + + ((utf8str[offset + 3] & 0x3F) << 12) + + ((utf8str[offset + 4] & 0x3F) << 6) + + (utf8str[offset + 5] & 0x3F); + case 7: + return ((utf8str[offset + 1] & 0x03) << 30) + + ((utf8str[offset + 2] & 0x3F) << 24) + + ((utf8str[offset + 3] & 0x3F) << 18) + + ((utf8str[offset + 4] & 0x3F) << 12) + + ((utf8str[offset + 5] & 0x3F) << 6) + + (utf8str[offset + 6] & 0x3F); + } + return LL_UNKNOWN_CHAR; +} + std::string utf8str_showBytesUTF8(const std::string& utf8str) { std::string result; bool in_sequence = false; - for (U8 byte : utf8str) + size_t sequence_size = 0; + size_t byte_index = 0; + size_t source_length = utf8str.size(); + + auto open_sequence = [&]() + { + if (!result.empty() && result.back() != '\n') + result += '\n'; // Use LF as a separator before new UTF-8 sequence + result += '['; + in_sequence = true; + }; + + auto close_sequence = [&]() + { + llwchar unicode = utf8str_to_wchar(utf8str, byte_index - sequence_size, sequence_size); + if (unicode != LL_UNKNOWN_CHAR) + { + result += llformat("+%04X", unicode); + } + result += ']'; + in_sequence = false; + sequence_size = 0; + }; + + while (byte_index < source_length) { + U8 byte = utf8str[byte_index]; if (byte >= 0x80) // Part of an UTF-8 sequence { if (!in_sequence) // Start new UTF-8 sequence { - if (!result.empty() && result.back() != ' ') - result += ' '; // Use space as separator between ASCII and UTF-8 - result += '['; + open_sequence(); } else if (byte >= 0xC0) // Start another UTF-8 sequence { - result += "] ["; // Use space as separator between UTF-8 and UTF-8 + close_sequence(); + open_sequence(); } else // Continue the same UTF-8 sequence { result += '.'; } result += llformat("%02X", byte); // The byte is represented in hexadecimal form - in_sequence = true; + ++sequence_size; } else // ASCII symbol is represented as a character { if (in_sequence) // End of UTF-8 sequence { - result += ']'; - if (byte != ' ') + close_sequence(); + if (byte != '\n') { - result += ' '; // Use space as separator between UTF-8 and ASCII + result += '\n'; // Use LF as a separator between UTF-8 and ASCII } } result += byte; - in_sequence = false; } + ++byte_index; } + if (in_sequence) // End of UTF-8 sequence { - result += ']'; + close_sequence(); } return result; diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index e592f6f8ec..7b37600a81 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -757,6 +757,8 @@ LL_COMMON_API std::string mbcsstring_makeASCII(const std::string& str); LL_COMMON_API std::string utf8str_removeCRLF(const std::string& utf8str); +LL_COMMON_API llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t length); + LL_COMMON_API std::string utf8str_showBytesUTF8(const std::string& utf8str); #if LL_WINDOWS diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index 65265ecd44..6b49756df3 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -958,6 +958,43 @@ void LLFloaterEmojiPicker::selectFocusedIcon() } } +bool LLFloaterEmojiPicker::moveFocusedIconUp() +{ + for (S32 i = mFocusedIconRow - 1; i >= 0; --i) + { + LLScrollingPanel* panel = mEmojiGrid->getPanelList()[i]; + LLEmojiGridRow* row = dynamic_cast(panel); + if (row && row->mList->getPanelList().size() > mFocusedIconCol) + { + mEmojiScroll->scrollToShowRect(row->getBoundingRect()); + mFocusedIconRow = i; + selectFocusedIcon(); + return true; + } + } + + return false; +} + +bool LLFloaterEmojiPicker::moveFocusedIconDown() +{ + S32 rowCount = mEmojiGrid->getPanelList().size(); + for (S32 i = mFocusedIconRow + 1; i < rowCount; ++i) + { + LLScrollingPanel* panel = mEmojiGrid->getPanelList()[i]; + LLEmojiGridRow* row = dynamic_cast(panel); + if (row && row->mList->getPanelList().size() > mFocusedIconCol) + { + mEmojiScroll->scrollToShowRect(row->getBoundingRect()); + mFocusedIconRow = i; + selectFocusedIcon(); + return true; + } + } + + return false; +} + bool LLFloaterEmojiPicker::moveFocusedIconPrev() { if (mHoveredIcon) @@ -1036,6 +1073,28 @@ void LLFloaterEmojiPicker::unselectGridIcon(LLEmojiGridIcon* icon) BOOL LLFloaterEmojiPicker::handleKey(KEY key, MASK mask, BOOL called_from_parent) { if (mask == MASK_NONE) + { + switch (key) + { + case KEY_UP: + moveFocusedIconUp(); + return TRUE; + case KEY_DOWN: + moveFocusedIconDown(); + return TRUE; + case KEY_LEFT: + moveFocusedIconPrev(); + return TRUE; + case KEY_RIGHT: + moveFocusedIconNext(); + return TRUE; + case KEY_ESCAPE: + hideFloater(); + return TRUE; + } + } + + if (mask == MASK_ALT) { switch (key) { @@ -1045,15 +1104,6 @@ BOOL LLFloaterEmojiPicker::handleKey(KEY key, MASK mask, BOOL called_from_parent case KEY_RIGHT: selectEmojiGroup((mSelectedGroupIndex + 1) % mGroupButtons.size()); return TRUE; - case KEY_UP: - moveFocusedIconPrev(); - return TRUE; - case KEY_DOWN: - moveFocusedIconNext(); - return TRUE; - case KEY_ESCAPE: - hideFloater(); - return TRUE; } } diff --git a/indra/newview/llfloateremojipicker.h b/indra/newview/llfloateremojipicker.h index 518ed1b591..c04c0cfb07 100644 --- a/indra/newview/llfloateremojipicker.h +++ b/indra/newview/llfloateremojipicker.h @@ -93,6 +93,8 @@ private: void onEmojiMouseUp(LLUICtrl* ctrl); void selectFocusedIcon(); + bool moveFocusedIconUp(); + bool moveFocusedIconDown(); bool moveFocusedIconPrev(); bool moveFocusedIconNext(); diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index 04d35a032a..14e7cac4bd 100644 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -467,6 +467,7 @@ void LLFloaterIMSessionTab::onEmojiRecentPanelToggleBtnClicked() void LLFloaterIMSessionTab::onEmojiPickerShowBtnClicked() { + mInputEditor->setFocus(TRUE); mInputEditor->showEmojiHelper(); } diff --git a/indra/newview/skins/default/xui/en/floater_im_session.xml b/indra/newview/skins/default/xui/en/floater_im_session.xml index 1b6bc7025a..a6493c5e24 100644 --- a/indra/newview/skins/default/xui/en/floater_im_session.xml +++ b/indra/newview/skins/default/xui/en/floater_im_session.xml @@ -305,7 +305,6 @@ tool_tip="Shows/hides recent emojis" follows="right|bottom" font="EmojiLarge" - tab_stop="false" image_hover_unselected="Toolbar_Middle_Over" image_selected="Toolbar_Middle_Selected" image_unselected="Toolbar_Middle_Off" @@ -354,7 +353,6 @@ name="emoji_recent_icons_ctrl" follows="top|left|right" layout="topleft" - tab_stop="false" max_visible="20" top="0" left="1" @@ -366,7 +364,6 @@ tool_tip="Shows/hides emoji picker" follows="right|bottom" layout="topleft" - tab_stop="false" bottom="-5" right="-3" height="20"