From ec23b4bc633b853223d8442f60e769d44a25fe2d Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Sun, 23 Oct 2022 16:06:41 +0200 Subject: Add the basic emoji dictionary class (responsible for loading them from disk and providing helpful lookup functions) --- indra/llui/llemojidictionary.cpp | 177 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 indra/llui/llemojidictionary.cpp (limited to 'indra/llui/llemojidictionary.cpp') diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp new file mode 100644 index 0000000000..e149832a8b --- /dev/null +++ b/indra/llui/llemojidictionary.cpp @@ -0,0 +1,177 @@ +/** +* @file llemojidictionary.cpp +* @brief Implementation of LLEmojiDictionary +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "linden_common.h" + +#include "lldir.h" +#include "llemojidictionary.h" +#include "llsdserialize.h" + +#include + +// ============================================================================ +// Constants +// + +constexpr char SKINNED_EMOJI_FILENAME[] = "emoji_characters.xml"; + +// ============================================================================ +// Helper functions +// + +template +std::list llsd_array_to_list(const LLSD& sd, std::function mutator = {}); + +template<> +std::list llsd_array_to_list(const LLSD& sd, std::function mutator) +{ + std::list result; + for (LLSD::array_const_iterator it = sd.beginArray(), end = sd.endArray(); it != end; ++it) + { + const LLSD& entry = *it; + if (!entry.isString()) + continue; + + result.push_back(entry.asStringRef()); + if (mutator) + { + mutator(result.back()); + } + } + return result; +} + +LLEmojiDescriptor::LLEmojiDescriptor(const LLSD& descriptor_sd) +{ + Name = descriptor_sd["Name"].asStringRef(); + + const LLWString emoji_string = utf8str_to_wstring(descriptor_sd["Character"].asString()); + Character = (1 == emoji_string.size()) ? emoji_string[0] : L'\0'; // We don't currently support character composition + + auto toLower = [](std::string& str) { LLStringUtil::toLower(str); }; + ShortCodes = llsd_array_to_list(descriptor_sd["ShortCodes"], toLower); + Categories = llsd_array_to_list(descriptor_sd["Categories"], toLower); + + if (Name.empty()) + { + Name = ShortCodes.front(); + } +} + +bool LLEmojiDescriptor::isValid() const +{ + return + Character && + !ShortCodes.empty() && + !Categories.empty(); +} + +// ============================================================================ +// LLEmojiDictionary class +// + +LLEmojiDictionary::LLEmojiDictionary() +{ +} + +// static +void LLEmojiDictionary::initClass() +{ + LLEmojiDictionary* pThis = &LLEmojiDictionary::initParamSingleton(); + + LLSD data; + + const std::string filename = gDirUtilp->findSkinnedFilenames(LLDir::XUI, SKINNED_EMOJI_FILENAME, LLDir::CURRENT_SKIN).front(); + llifstream file(filename.c_str()); + if (file.is_open()) + { + LL_DEBUGS() << "Loading emoji characters file at " << filename << LL_ENDL; + LLSDSerialize::fromXML(data, file); + } + + if (data.isUndefined()) + { + LL_WARNS() << "Emoji file characters missing or ill-formed" << LL_ENDL; + return; + } + + for (LLSD::array_const_iterator descriptor_it = data.beginArray(), descriptor_end = data.endArray(); descriptor_it != descriptor_end; ++descriptor_it) + { + LLEmojiDescriptor descriptor(*descriptor_it); + if (!descriptor.isValid()) + { + LL_WARNS() << "Skipping invalid emoji descriptor " << descriptor.Character << LL_ENDL; + continue; + } + pThis->addEmoji(std::move(descriptor)); + } +} + +LLWString LLEmojiDictionary::findMatchingEmojis(std::string needle) +{ + // Search without the colon (if present) so the user can type ':food' and see all emojis in the 'Food' category + LLStringUtil::toLower(needle); + const auto kitty_needle = boost::make_iterator_range((boost::starts_with(needle, ":")) ? needle.begin() + 1 : needle.begin(), needle.end()); + + LLWString wstr; + for (const auto& descr : mEmojis) + { + for (const auto& short_code : descr.ShortCodes) + { + if (boost::icontains(short_code, kitty_needle)) + { + wstr.push_back(descr.Character); + continue; + } + } + + for (const auto& category : descr.Categories) + { + if (boost::icontains(category, kitty_needle)) + { + wstr.push_back(descr.Character); + continue; + } + } + } + + return wstr; +} + +std::string LLEmojiDictionary::getNameFromEmoji(llwchar ch) +{ + const auto it = mEmoji2Descr.find(ch); + return (mEmoji2Descr.end() != it) ? it->second->Name : LLStringUtil::null; +} + +void LLEmojiDictionary::addEmoji(LLEmojiDescriptor&& descr) +{ + mEmojis.push_back(descr); + const LLEmojiDescriptor& back = mEmojis.back(); + mEmoji2Descr.insert(std::make_pair(descr.Character, &back)); +} + +// ============================================================================ -- cgit v1.2.3 From d1dbefc09b77990563d22aaf08d0c5708cc6cbbe Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Sun, 23 Oct 2022 19:05:19 +0200 Subject: Refactor emoji matching --- indra/llui/llemojidictionary.cpp | 70 ++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 28 deletions(-) (limited to 'indra/llui/llemojidictionary.cpp') diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp index e149832a8b..eefa4047a2 100644 --- a/indra/llui/llemojidictionary.cpp +++ b/indra/llui/llemojidictionary.cpp @@ -31,6 +31,8 @@ #include "llsdserialize.h" #include +#include +#include // ============================================================================ // Constants @@ -89,6 +91,41 @@ bool LLEmojiDescriptor::isValid() const !Categories.empty(); } +struct emoji_filter_base +{ + emoji_filter_base(const std::string& needle) + { + // Search without the colon (if present) so the user can type ':food' and see all emojis in the 'Food' category + mNeedle = (boost::starts_with(needle, ":")) ? needle.substr(1) : needle; + LLStringUtil::toLower(mNeedle); + } + +protected: + std::string mNeedle; +}; + +struct emoji_filter_shortcode_or_category_contains : public emoji_filter_base +{ + emoji_filter_shortcode_or_category_contains(const std::string& needle) : emoji_filter_base(needle) {} + + bool operator()(const LLEmojiDescriptor& descr) const + { + for (const auto& short_code : descr.ShortCodes) + { + if (boost::icontains(short_code, mNeedle)) + return true; + } + + for (const auto& category : descr.Categories) + { + if (boost::icontains(category, mNeedle)) + return true; + } + + return false; + } +}; + // ============================================================================ // LLEmojiDictionary class // @@ -130,35 +167,12 @@ void LLEmojiDictionary::initClass() } } -LLWString LLEmojiDictionary::findMatchingEmojis(std::string needle) +LLWString LLEmojiDictionary::findMatchingEmojis(const std::string& needle) const { - // Search without the colon (if present) so the user can type ':food' and see all emojis in the 'Food' category - LLStringUtil::toLower(needle); - const auto kitty_needle = boost::make_iterator_range((boost::starts_with(needle, ":")) ? needle.begin() + 1 : needle.begin(), needle.end()); - - LLWString wstr; - for (const auto& descr : mEmojis) - { - for (const auto& short_code : descr.ShortCodes) - { - if (boost::icontains(short_code, kitty_needle)) - { - wstr.push_back(descr.Character); - continue; - } - } - - for (const auto& category : descr.Categories) - { - if (boost::icontains(category, kitty_needle)) - { - wstr.push_back(descr.Character); - continue; - } - } - } - - return wstr; + LLWString result; + boost::transform(mEmojis | boost::adaptors::filtered(emoji_filter_shortcode_or_category_contains(needle)), + std::back_inserter(result), [](const auto& descr) { return descr.Character; }); + return result; } std::string LLEmojiDictionary::getNameFromEmoji(llwchar ch) -- cgit v1.2.3 From c40d3351d511b19f4468f7467be38499bf16588f Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Wed, 2 Nov 2022 19:05:24 +0100 Subject: Commit immediately if the user already typed a full shortcode --- indra/llui/llemojidictionary.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'indra/llui/llemojidictionary.cpp') diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp index eefa4047a2..c31638b0bf 100644 --- a/indra/llui/llemojidictionary.cpp +++ b/indra/llui/llemojidictionary.cpp @@ -175,17 +175,26 @@ LLWString LLEmojiDictionary::findMatchingEmojis(const std::string& needle) const return result; } -std::string LLEmojiDictionary::getNameFromEmoji(llwchar ch) +const LLEmojiDescriptor* LLEmojiDictionary::getDescriptorFromShortCode(const std::string& short_code) const +{ + const auto it = mShortCode2Descr.find(short_code); + return (mShortCode2Descr.end() != it) ? &it->second : nullptr; +} + +std::string LLEmojiDictionary::getNameFromEmoji(llwchar ch) const { const auto it = mEmoji2Descr.find(ch); - return (mEmoji2Descr.end() != it) ? it->second->Name : LLStringUtil::null; + return (mEmoji2Descr.end() != it) ? it->second.Name : LLStringUtil::null; } void LLEmojiDictionary::addEmoji(LLEmojiDescriptor&& descr) { mEmojis.push_back(descr); - const LLEmojiDescriptor& back = mEmojis.back(); - mEmoji2Descr.insert(std::make_pair(descr.Character, &back)); + mEmoji2Descr.insert(std::make_pair(descr.Character, mEmojis.back())); + for (const std::string& shortCode : descr.ShortCodes) + { + mShortCode2Descr.insert(std::make_pair(shortCode, mEmojis.back())); + } } // ============================================================================ -- cgit v1.2.3 From 81dd143d0d901e8e5234cff01fbda246e4621628 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Tue, 8 Nov 2022 00:16:58 +0100 Subject: [FIXED] Various minor issues - Typing :+1: doesn't replace the short code with the thumbs-up emoji - Moving the mouse over the emoji complete panel highlights the wrong emoji when mScrollPos > 0 - Emoji complete panel is missing attributes - Crash when attempting to show the tooltip for an emoji text segment - Emoji autocomplete panel can sometimes show empty (type ':cat', select the heart eyed one, Ctrl-Z and then type 2 which should show the emoji for :cat2 but shows an empty square instead) --- indra/llui/llemojidictionary.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indra/llui/llemojidictionary.cpp') diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp index c31638b0bf..b70a9b2e7a 100644 --- a/indra/llui/llemojidictionary.cpp +++ b/indra/llui/llemojidictionary.cpp @@ -178,22 +178,22 @@ LLWString LLEmojiDictionary::findMatchingEmojis(const std::string& needle) const const LLEmojiDescriptor* LLEmojiDictionary::getDescriptorFromShortCode(const std::string& short_code) const { const auto it = mShortCode2Descr.find(short_code); - return (mShortCode2Descr.end() != it) ? &it->second : nullptr; + return (mShortCode2Descr.end() != it) ? it->second : nullptr; } std::string LLEmojiDictionary::getNameFromEmoji(llwchar ch) const { const auto it = mEmoji2Descr.find(ch); - return (mEmoji2Descr.end() != it) ? it->second.Name : LLStringUtil::null; + return (mEmoji2Descr.end() != it) ? it->second->Name : LLStringUtil::null; } void LLEmojiDictionary::addEmoji(LLEmojiDescriptor&& descr) { mEmojis.push_back(descr); - mEmoji2Descr.insert(std::make_pair(descr.Character, mEmojis.back())); + mEmoji2Descr.insert(std::make_pair(descr.Character, &mEmojis.back())); for (const std::string& shortCode : descr.ShortCodes) { - mShortCode2Descr.insert(std::make_pair(shortCode, mEmojis.back())); + mShortCode2Descr.insert(std::make_pair(shortCode, &mEmojis.back())); } } -- cgit v1.2.3 From 97b0ba2a6d2596da867043077e32065653d44f6e Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Wed, 19 Apr 2023 01:39:42 +0200 Subject: SL-19575 LLFloaterEmojiPicker - Add filter by category --- indra/llui/llemojidictionary.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'indra/llui/llemojidictionary.cpp') diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp index b70a9b2e7a..d306407484 100644 --- a/indra/llui/llemojidictionary.cpp +++ b/indra/llui/llemojidictionary.cpp @@ -175,6 +175,12 @@ LLWString LLEmojiDictionary::findMatchingEmojis(const std::string& needle) const return result; } +const LLEmojiDescriptor* LLEmojiDictionary::getDescriptorFromEmoji(llwchar emoji) const +{ + const auto it = mEmoji2Descr.find(emoji); + return (mEmoji2Descr.end() != it) ? it->second : nullptr; +} + const LLEmojiDescriptor* LLEmojiDictionary::getDescriptorFromShortCode(const std::string& short_code) const { const auto it = mShortCode2Descr.find(short_code); @@ -195,6 +201,10 @@ void LLEmojiDictionary::addEmoji(LLEmojiDescriptor&& descr) { mShortCode2Descr.insert(std::make_pair(shortCode, &mEmojis.back())); } + for (const std::string& category : descr.Categories) + { + mCategory2Descrs[category].push_back(&mEmojis.back()); + } } // ============================================================================ -- cgit v1.2.3 From 8fcf691623c21dedd0e478fb8d013a28a2959811 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Sat, 22 Apr 2023 14:19:42 +0200 Subject: Load correct localized version of emoji dictionary and guard against access violation exception --- indra/llui/llemojidictionary.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'indra/llui/llemojidictionary.cpp') diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp index d306407484..bb5c94689a 100644 --- a/indra/llui/llemojidictionary.cpp +++ b/indra/llui/llemojidictionary.cpp @@ -141,7 +141,13 @@ void LLEmojiDictionary::initClass() LLSD data; - const std::string filename = gDirUtilp->findSkinnedFilenames(LLDir::XUI, SKINNED_EMOJI_FILENAME, LLDir::CURRENT_SKIN).front(); + auto filenames = gDirUtilp->findSkinnedFilenames(LLDir::XUI, SKINNED_EMOJI_FILENAME, LLDir::CURRENT_SKIN); + if (filenames.empty()) + { + LL_WARNS() << "Emoji file characters not found" << LL_ENDL; + return; + } + const std::string filename = filenames.back(); llifstream file(filename.c_str()); if (file.is_open()) { -- cgit v1.2.3 From 671978e3927bc3ba9fc34008bbb7efd6f07b6c81 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Wed, 17 May 2023 14:28:36 +0200 Subject: SL-19575 Create emoji gallery (fix bug with drawing emojis in chat history) --- indra/llui/llemojidictionary.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'indra/llui/llemojidictionary.cpp') diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp index bb5c94689a..179c5d25bf 100644 --- a/indra/llui/llemojidictionary.cpp +++ b/indra/llui/llemojidictionary.cpp @@ -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); -- cgit v1.2.3 From 9793308a600c1e1ce35ec727ed6341e7668848ea Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Thu, 6 Jul 2023 23:48:06 +0200 Subject: SL-19951 Organize emoji categories in groups --- indra/llui/llemojidictionary.cpp | 424 +++++++++++++++++++++++++++------------ 1 file changed, 301 insertions(+), 123 deletions(-) (limited to 'indra/llui/llemojidictionary.cpp') diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp index 179c5d25bf..cdcf5a93d6 100644 --- a/indra/llui/llemojidictionary.cpp +++ b/indra/llui/llemojidictionary.cpp @@ -38,7 +38,12 @@ // Constants // -constexpr char SKINNED_EMOJI_FILENAME[] = "emoji_characters.xml"; +static const std::string SKINNED_EMOJI_FILENAME("emoji_characters.xml"); +static const std::string SKINNED_CATEGORY_FILENAME("emoji_categories.xml"); +static const std::string COMMON_GROUP_FILENAME("emoji_groups.xml"); +static const std::string GROUP_NAME_ALL("all"); +static const std::string GROUP_NAME_OTHERS("others"); +static const std::string GROUP_NAME_SKIP("skip"); // ============================================================================ // Helper functions @@ -50,82 +55,68 @@ std::list llsd_array_to_list(const LLSD& sd, std::function mutator template<> std::list llsd_array_to_list(const LLSD& sd, std::function mutator) { - std::list result; - for (LLSD::array_const_iterator it = sd.beginArray(), end = sd.endArray(); it != end; ++it) - { - const LLSD& entry = *it; - if (!entry.isString()) - continue; - - result.push_back(entry.asStringRef()); - if (mutator) - { - mutator(result.back()); - } - } - return result; -} - -LLEmojiDescriptor::LLEmojiDescriptor(const LLSD& descriptor_sd) -{ - Name = descriptor_sd["Name"].asStringRef(); - - const LLWString emoji_string = utf8str_to_wstring(descriptor_sd["Character"].asString()); - Character = (1 == emoji_string.size()) ? emoji_string[0] : L'\0'; // We don't currently support character composition - - auto toLower = [](std::string& str) { LLStringUtil::toLower(str); }; - ShortCodes = llsd_array_to_list(descriptor_sd["ShortCodes"], toLower); - Categories = llsd_array_to_list(descriptor_sd["Categories"], toLower); - - if (Name.empty()) - { - Name = ShortCodes.front(); - } -} - -bool LLEmojiDescriptor::isValid() const -{ - return - Character && - !ShortCodes.empty() && - !Categories.empty(); + std::list result; + for (LLSD::array_const_iterator it = sd.beginArray(), end = sd.endArray(); it != end; ++it) + { + const LLSD& entry = *it; + if (!entry.isString()) + continue; + + result.push_back(entry.asStringRef()); + if (mutator) + { + mutator(result.back()); + } + } + return result; } struct emoji_filter_base { - emoji_filter_base(const std::string& needle) - { - // Search without the colon (if present) so the user can type ':food' and see all emojis in the 'Food' category - mNeedle = (boost::starts_with(needle, ":")) ? needle.substr(1) : needle; - LLStringUtil::toLower(mNeedle); - } + emoji_filter_base(const std::string& needle) + { + // Search without the colon (if present) so the user can type ':food' and see all emojis in the 'Food' category + mNeedle = (boost::starts_with(needle, ":")) ? needle.substr(1) : needle; + LLStringUtil::toLower(mNeedle); + } protected: - std::string mNeedle; + std::string mNeedle; }; struct emoji_filter_shortcode_or_category_contains : public emoji_filter_base { - emoji_filter_shortcode_or_category_contains(const std::string& needle) : emoji_filter_base(needle) {} + emoji_filter_shortcode_or_category_contains(const std::string& needle) : emoji_filter_base(needle) {} - bool operator()(const LLEmojiDescriptor& descr) const - { - for (const auto& short_code : descr.ShortCodes) - { - if (boost::icontains(short_code, mNeedle)) - return true; - } + bool operator()(const LLEmojiDescriptor& descr) const + { + for (const auto& short_code : descr.ShortCodes) + { + if (boost::icontains(short_code, mNeedle)) + return true; + } - for (const auto& category : descr.Categories) - { - if (boost::icontains(category, mNeedle)) - return true; - } + if (boost::icontains(descr.Category, mNeedle)) + return true; - return false; - } + return false; + } }; +std::string LLEmojiDescriptor::getShortCodes() const +{ + std::string result; + for (const std::string& shortCode : ShortCodes) + { + if (!result.empty()) + { + result += ", "; + } + result += shortCode; + } + return result; +} + // ============================================================================ // LLEmojiDictionary class // @@ -137,91 +128,278 @@ LLEmojiDictionary::LLEmojiDictionary() // static void LLEmojiDictionary::initClass() { - LLEmojiDictionary* pThis = &LLEmojiDictionary::initParamSingleton(); - - LLSD data; - - auto filenames = gDirUtilp->findSkinnedFilenames(LLDir::XUI, SKINNED_EMOJI_FILENAME, LLDir::CURRENT_SKIN); - if (filenames.empty()) - { - LL_WARNS() << "Emoji file characters not found" << LL_ENDL; - return; - } - const std::string filename = filenames.back(); - llifstream file(filename.c_str()); - if (file.is_open()) - { - LL_DEBUGS() << "Loading emoji characters file at " << filename << LL_ENDL; - LLSDSerialize::fromXML(data, file); - } - - if (data.isUndefined()) - { - LL_WARNS() << "Emoji file characters missing or ill-formed" << LL_ENDL; - return; - } - - for (LLSD::array_const_iterator descriptor_it = data.beginArray(), descriptor_end = data.endArray(); descriptor_it != descriptor_end; ++descriptor_it) - { - LLEmojiDescriptor descriptor(*descriptor_it); - if (!descriptor.isValid()) - { - LL_WARNS() << "Skipping invalid emoji descriptor " << descriptor.Character << LL_ENDL; - continue; - } - pThis->addEmoji(std::move(descriptor)); - } + LLEmojiDictionary* pThis = &LLEmojiDictionary::initParamSingleton(); + + pThis->loadTranslations(); + pThis->loadGroups(); + pThis->loadEmojis(); } LLWString LLEmojiDictionary::findMatchingEmojis(const std::string& needle) const { - LLWString result; - boost::transform(mEmojis | boost::adaptors::filtered(emoji_filter_shortcode_or_category_contains(needle)), - std::back_inserter(result), [](const auto& descr) { return descr.Character; }); - return result; + LLWString result; + boost::transform(mEmojis | boost::adaptors::filtered(emoji_filter_shortcode_or_category_contains(needle)), + std::back_inserter(result), [](const auto& descr) { return descr.Character; }); + return result; } const LLEmojiDescriptor* LLEmojiDictionary::getDescriptorFromEmoji(llwchar emoji) const { - const auto it = mEmoji2Descr.find(emoji); - return (mEmoji2Descr.end() != it) ? it->second : nullptr; + const auto it = mEmoji2Descr.find(emoji); + return (mEmoji2Descr.end() != it) ? it->second : nullptr; } const LLEmojiDescriptor* LLEmojiDictionary::getDescriptorFromShortCode(const std::string& short_code) const { - const auto it = mShortCode2Descr.find(short_code); - return (mShortCode2Descr.end() != it) ? it->second : nullptr; + const auto it = mShortCode2Descr.find(short_code); + return (mShortCode2Descr.end() != it) ? it->second : nullptr; } std::string LLEmojiDictionary::getNameFromEmoji(llwchar ch) const { - const auto it = mEmoji2Descr.find(ch); - return (mEmoji2Descr.end() != it) ? it->second->Name : LLStringUtil::null; + const auto it = mEmoji2Descr.find(ch); + return (mEmoji2Descr.end() != it) ? it->second->ShortCodes.front() : 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(); - } + // 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; + return false; +} + +void LLEmojiDictionary::loadTranslations() +{ + std::vector filenames = gDirUtilp->findSkinnedFilenames(LLDir::XUI, SKINNED_CATEGORY_FILENAME, LLDir::CURRENT_SKIN); + if (filenames.empty()) + { + LL_WARNS() << "Emoji file categories not found" << LL_ENDL; + return; + } + + const std::string filename = filenames.back(); + llifstream file(filename.c_str()); + if (!file.is_open()) + { + LL_WARNS() << "Emoji file categories failed to open" << LL_ENDL; + return; + } + + LL_DEBUGS() << "Loading emoji categories file at " << filename << LL_ENDL; + + LLSD data; + LLSDSerialize::fromXML(data, file); + if (data.isUndefined()) + { + LL_WARNS() << "Emoji file categories missing or ill-formed" << LL_ENDL; + return; + } + + // Register translations for all categories + for (LLSD::array_const_iterator it = data.beginArray(), end = data.endArray(); it != end; ++it) + { + const LLSD& sd = *it; + const std::string& name = sd["Name"].asStringRef(); + const std::string& category = sd["Category"].asStringRef(); + if (!name.empty() && !category.empty()) + { + mTranslations[name] = category; + } + else + { + LL_WARNS() << "Skipping invalid emoji category '" << name << "' => '" << category << "'" << LL_ENDL; + } + } +} + +void LLEmojiDictionary::loadGroups() +{ + const std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, COMMON_GROUP_FILENAME); + llifstream file(filename.c_str()); + if (!file.is_open()) + { + LL_WARNS() << "Emoji file groups failed to open" << LL_ENDL; + return; + } + + LL_DEBUGS() << "Loading emoji groups file at " << filename << LL_ENDL; + + LLSD data; + LLSDSerialize::fromXML(data, file); + if (data.isUndefined()) + { + LL_WARNS() << "Emoji file groups missing or ill-formed" << LL_ENDL; + return; + } + + mGroups.clear(); + // Add group "all" + mGroups.emplace_back(); + // https://www.compart.com/en/unicode/U+1F50D + mGroups.front().Character = 0x1F50D; + // https://www.compart.com/en/unicode/U+1F302 + llwchar iconOthers = 0x1F302; + + // Register all groups + for (LLSD::array_const_iterator it = data.beginArray(), end = data.endArray(); it != end; ++it) + { + const LLSD& sd = *it; + const std::string& name = sd["Name"].asStringRef(); + if (name == GROUP_NAME_ALL) + { + mGroups.front().Character = loadIcon(sd); + } + else if (name == GROUP_NAME_OTHERS) + { + iconOthers = loadIcon(sd); + } + else if (name == GROUP_NAME_SKIP) + { + mSkipCategories = loadCategories(sd); + translateCategories(mSkipCategories); + } + else + { + // Add new group + mGroups.emplace_back(); + LLEmojiGroup& group = mGroups.back(); + group.Character = loadIcon(sd); + group.Categories = loadCategories(sd); + translateCategories(group.Categories); + + for (const std::string& category : group.Categories) + { + mCategory2Group.insert(std::make_pair(category, &group)); + } + } + } + + // Add group "others" + mGroups.emplace_back(); + mGroups.back().Character = iconOthers; +} + +void LLEmojiDictionary::loadEmojis() +{ + std::vector filenames = gDirUtilp->findSkinnedFilenames(LLDir::XUI, SKINNED_EMOJI_FILENAME, LLDir::CURRENT_SKIN); + if (filenames.empty()) + { + LL_WARNS() << "Emoji file characters not found" << LL_ENDL; + return; + } + + const std::string filename = filenames.back(); + llifstream file(filename.c_str()); + if (!file.is_open()) + { + LL_WARNS() << "Emoji file characters failed to open" << LL_ENDL; + return; + } + + LL_DEBUGS() << "Loading emoji characters file at " << filename << LL_ENDL; + + LLSD data; + LLSDSerialize::fromXML(data, file); + if (data.isUndefined()) + { + LL_WARNS() << "Emoji file characters missing or ill-formed" << LL_ENDL; + return; + } + + for (LLSD::array_const_iterator it = data.beginArray(), end = data.endArray(); it != end; ++it) + { + const LLSD& sd = *it; + + llwchar icon = loadIcon(sd); + if (!icon) + { + LL_WARNS() << "Skipping invalid emoji descriptor (no icon)" << LL_ENDL; + continue; + } + + std::list categories = loadCategories(sd); + if (categories.empty()) + { + LL_WARNS() << "Skipping invalid emoji descriptor (no categories)" << LL_ENDL; + continue; + } + + std::string category = categories.front(); + + if (std::find(mSkipCategories.begin(), mSkipCategories.end(), category) != mSkipCategories.end()) + { + // This category is listed for skip + continue; + } + + std::list shortCodes = loadShortCodes(sd); + if (shortCodes.empty()) + { + LL_WARNS() << "Skipping invalid emoji descriptor (no shortCodes)" << LL_ENDL; + continue; + } + + if (mCategory2Group.find(category) == mCategory2Group.end()) + { + // Add unknown category to "others" group + mGroups.back().Categories.push_back(category); + mCategory2Group.insert(std::make_pair(category, &mGroups.back())); + } + + mEmojis.emplace_back(); + LLEmojiDescriptor& emoji = mEmojis.back(); + emoji.Character = icon; + emoji.Category = category; + emoji.ShortCodes = std::move(shortCodes); + + mEmoji2Descr.insert(std::make_pair(icon, &emoji)); + mCategory2Descrs[category].push_back(&emoji); + for (const std::string& shortCode : emoji.ShortCodes) + { + mShortCode2Descr.insert(std::make_pair(shortCode, &emoji)); + } + } +} + +llwchar LLEmojiDictionary::loadIcon(const LLSD& sd) +{ + // We don't currently support character composition + const LLWString icon = utf8str_to_wstring(sd["Character"].asString()); + return (1 == icon.size()) ? icon[0] : L'\0'; +} + +static inline std::list loadStrings(const LLSD& sd, const std::string key) +{ + auto toLower = [](std::string& str) { LLStringUtil::toLower(str); }; + return llsd_array_to_list(sd[key], toLower); +} + +std::list LLEmojiDictionary::loadCategories(const LLSD& sd) +{ + static const std::string categoriesKey("Categories"); + return loadStrings(sd, categoriesKey); +} + +std::list LLEmojiDictionary::loadShortCodes(const LLSD& sd) +{ + static const std::string shortCodesKey("ShortCodes"); + return loadStrings(sd, shortCodesKey); } -void LLEmojiDictionary::addEmoji(LLEmojiDescriptor&& descr) +void LLEmojiDictionary::translateCategories(std::list& categories) { - mEmojis.push_back(descr); - mEmoji2Descr.insert(std::make_pair(descr.Character, &mEmojis.back())); - for (const std::string& shortCode : descr.ShortCodes) - { - mShortCode2Descr.insert(std::make_pair(shortCode, &mEmojis.back())); - } - for (const std::string& category : descr.Categories) - { - mCategory2Descrs[category].push_back(&mEmojis.back()); - } + for (std::string& category : categories) + { + auto it = mTranslations.find(category); + if (it != mTranslations.end()) + { + category = it->second; + } + } } // ============================================================================ -- cgit v1.2.3 From c30208d598d43779cea7b5de50c44d4b21b16487 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Fri, 8 Sep 2023 01:02:06 +0200 Subject: SL-19951 Organize emoji categories in groups (fix for German) --- indra/llui/llemojidictionary.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indra/llui/llemojidictionary.cpp') diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp index cdcf5a93d6..89758f538b 100644 --- a/indra/llui/llemojidictionary.cpp +++ b/indra/llui/llemojidictionary.cpp @@ -204,7 +204,8 @@ void LLEmojiDictionary::loadTranslations() { const LLSD& sd = *it; const std::string& name = sd["Name"].asStringRef(); - const std::string& category = sd["Category"].asStringRef(); + std::string category = sd["Category"].asString(); + LLStringUtil::toLower(category); if (!name.empty() && !category.empty()) { mTranslations[name] = category; -- cgit v1.2.3 From 29a3295c82fb65143f8eea7e27c61436a19b4272 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Fri, 8 Sep 2023 12:19:21 +0200 Subject: SL-19951 EmojiPicker - preserve original character case for German categories --- indra/llui/llemojidictionary.cpp | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) (limited to 'indra/llui/llemojidictionary.cpp') diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp index 89758f538b..1ee97ea2d2 100644 --- a/indra/llui/llemojidictionary.cpp +++ b/indra/llui/llemojidictionary.cpp @@ -204,8 +204,7 @@ void LLEmojiDictionary::loadTranslations() { const LLSD& sd = *it; const std::string& name = sd["Name"].asStringRef(); - std::string category = sd["Category"].asString(); - LLStringUtil::toLower(category); + const std::string& category = sd["Category"].asStringRef(); if (!name.empty() && !category.empty()) { mTranslations[name] = category; @@ -373,22 +372,17 @@ llwchar LLEmojiDictionary::loadIcon(const LLSD& sd) return (1 == icon.size()) ? icon[0] : L'\0'; } -static inline std::list loadStrings(const LLSD& sd, const std::string key) -{ - auto toLower = [](std::string& str) { LLStringUtil::toLower(str); }; - return llsd_array_to_list(sd[key], toLower); -} - std::list LLEmojiDictionary::loadCategories(const LLSD& sd) { - static const std::string categoriesKey("Categories"); - return loadStrings(sd, categoriesKey); + static const std::string key("Categories"); + return llsd_array_to_list(sd[key]); } std::list LLEmojiDictionary::loadShortCodes(const LLSD& sd) { - static const std::string shortCodesKey("ShortCodes"); - return loadStrings(sd, shortCodesKey); + static const std::string key("ShortCodes"); + auto toLower = [](std::string& str) { LLStringUtil::toLower(str); }; + return llsd_array_to_list(sd[key], toLower); } void LLEmojiDictionary::translateCategories(std::list& categories) -- cgit v1.2.3 From 98214577c36d9c8dd0e13c7b678a399b35450bd3 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Thu, 5 Oct 2023 11:28:54 +0200 Subject: SL-20390 Emoji Completion floater - ignore symbols in shortcodes when searching by pattern --- indra/llui/llemojidictionary.cpp | 64 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) (limited to 'indra/llui/llemojidictionary.cpp') diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp index 1ee97ea2d2..bf7e53701d 100644 --- a/indra/llui/llemojidictionary.cpp +++ b/indra/llui/llemojidictionary.cpp @@ -143,6 +143,70 @@ LLWString LLEmojiDictionary::findMatchingEmojis(const std::string& needle) const return result; } +void LLEmojiDictionary::findByShortCode(std::vector& result, const std::string& needle) const +{ + result.clear(); + + if (needle.empty() || needle.front() != ':') + return; + + auto search = [needle](std::size_t& begin, std::size_t& end, const std::string& shortCode) -> bool + { + begin = 0; + end = 1; + std::size_t index = 1; + // Search for begin + char d = tolower(needle[index++]); + while (end < shortCode.size()) + { + char s = tolower(shortCode[end++]); + if (s == d) + { + begin = end - 1; + break; + } + } + if (!begin) + return false; + // Search for end + d = tolower(needle[index++]); + while (end < shortCode.size() && index <= needle.size()) + { + char s = tolower(shortCode[end++]); + if (s == d) + { + if (index == needle.size()) + return true; + d = tolower(needle[index++]); + continue; + } + switch (s) + { + case L'-': + case L'_': + case L'+': + continue; + } + break; + } + return false; + }; + + for (const LLEmojiDescriptor& d : mEmojis) + { + if (d.ShortCodes.empty()) + continue; + const std::string& shortCode = d.ShortCodes.front(); + if (shortCode.size() < needle.size() || shortCode.front() != needle.front()) + continue; + std::size_t begin, end; + if (search(begin, end, shortCode)) + { + result.emplace_back(d.Character, shortCode, begin, end); + } + } +} + const LLEmojiDescriptor* LLEmojiDictionary::getDescriptorFromEmoji(llwchar emoji) const { const auto it = mEmoji2Descr.find(emoji); -- cgit v1.2.3 From f9a4266e2ffd49e38d2d9bb536cd6af5009c4868 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Thu, 5 Oct 2023 18:30:34 +0200 Subject: SL-20355 Sort completion suggestions by position of the search pattern --- indra/llui/llemojidictionary.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'indra/llui/llemojidictionary.cpp') diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp index bf7e53701d..e29f3436cf 100644 --- a/indra/llui/llemojidictionary.cpp +++ b/indra/llui/llemojidictionary.cpp @@ -192,6 +192,8 @@ void LLEmojiDictionary::findByShortCode(std::vector& result return false; }; + std::map> results; + for (const LLEmojiDescriptor& d : mEmojis) { if (d.ShortCodes.empty()) @@ -202,9 +204,18 @@ void LLEmojiDictionary::findByShortCode(std::vector& result std::size_t begin, end; if (search(begin, end, shortCode)) { - result.emplace_back(d.Character, shortCode, begin, end); + results[begin].emplace_back(d.Character, shortCode, begin, end); } } + + for (const auto& it : results) + { +#ifdef __cpp_lib_containers_ranges + result.append_range(it.second); +#else + result.insert(result.end(), it.second.cbegin(), it.second.cend()); +#endif + } } const LLEmojiDescriptor* LLEmojiDictionary::getDescriptorFromEmoji(llwchar emoji) const -- cgit v1.2.3 From f6ceafee5bb26cf86d64959cabf68deefaf791a8 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Mon, 22 Jan 2024 06:40:41 +0100 Subject: SL-20416 Avoid of taking focus by EmojiPicker --- indra/llui/llemojidictionary.cpp | 132 +++++++++++++++++++-------------------- 1 file changed, 63 insertions(+), 69 deletions(-) (limited to 'indra/llui/llemojidictionary.cpp') diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp index e29f3436cf..f16c38a11a 100644 --- a/indra/llui/llemojidictionary.cpp +++ b/indra/llui/llemojidictionary.cpp @@ -41,9 +41,9 @@ static const std::string SKINNED_EMOJI_FILENAME("emoji_characters.xml"); static const std::string SKINNED_CATEGORY_FILENAME("emoji_categories.xml"); static const std::string COMMON_GROUP_FILENAME("emoji_groups.xml"); -static const std::string GROUP_NAME_ALL("all"); -static const std::string GROUP_NAME_OTHERS("others"); static const std::string GROUP_NAME_SKIP("skip"); +// https://www.compart.com/en/unicode/U+1F302 +static const S32 GROUP_OTHERS_IMAGE_INDEX = 0x1F302; // ============================================================================ // Helper functions @@ -143,68 +143,76 @@ LLWString LLEmojiDictionary::findMatchingEmojis(const std::string& needle) const return result; } -void LLEmojiDictionary::findByShortCode(std::vector& result, const std::string& needle) const +// static +bool LLEmojiDictionary::searchInShortCode(std::size_t& begin, std::size_t& end, const std::string& shortCode, const std::string& needle) +{ + begin = 0; + end = 1; + std::size_t index = 1; + // Search for begin + char d = tolower(needle[index++]); + while (end < shortCode.size()) + { + char s = tolower(shortCode[end++]); + if (s == d) + { + begin = end - 1; + break; + } + } + if (!begin) + return false; + // Search for end + d = tolower(needle[index++]); + if (!d) + return true; + while (end < shortCode.size() && index <= needle.size()) + { + char s = tolower(shortCode[end++]); + if (s == d) + { + if (index == needle.size()) + return true; + d = tolower(needle[index++]); + continue; + } + switch (s) + { + case L'-': + case L'_': + case L'+': + continue; + } + break; + } + return false; +} + +void LLEmojiDictionary::findByShortCode( + std::vector& result, + const std::string& needle +) const { result.clear(); if (needle.empty() || needle.front() != ':') return; - auto search = [needle](std::size_t& begin, std::size_t& end, const std::string& shortCode) -> bool + std::map> results; + + for (const LLEmojiDescriptor& d : mEmojis) + { + if (!d.ShortCodes.empty()) { - begin = 0; - end = 1; - std::size_t index = 1; - // Search for begin - char d = tolower(needle[index++]); - while (end < shortCode.size()) + const std::string& shortCode = d.ShortCodes.front(); + if (shortCode.size() >= needle.size() && shortCode.front() == needle.front()) { - char s = tolower(shortCode[end++]); - if (s == d) + std::size_t begin, end; + if (searchInShortCode(begin, end, shortCode, needle)) { - begin = end - 1; - break; + results[begin].emplace_back(d.Character, shortCode, begin, end); } } - if (!begin) - return false; - // Search for end - d = tolower(needle[index++]); - while (end < shortCode.size() && index <= needle.size()) - { - char s = tolower(shortCode[end++]); - if (s == d) - { - if (index == needle.size()) - return true; - d = tolower(needle[index++]); - continue; - } - switch (s) - { - case L'-': - case L'_': - case L'+': - continue; - } - break; - } - return false; - }; - - std::map> results; - - for (const LLEmojiDescriptor& d : mEmojis) - { - if (d.ShortCodes.empty()) - continue; - const std::string& shortCode = d.ShortCodes.front(); - if (shortCode.size() < needle.size() || shortCode.front() != needle.front()) - continue; - std::size_t begin, end; - if (search(begin, end, shortCode)) - { - results[begin].emplace_back(d.Character, shortCode, begin, end); } } @@ -312,27 +320,13 @@ void LLEmojiDictionary::loadGroups() } mGroups.clear(); - // Add group "all" - mGroups.emplace_back(); - // https://www.compart.com/en/unicode/U+1F50D - mGroups.front().Character = 0x1F50D; - // https://www.compart.com/en/unicode/U+1F302 - llwchar iconOthers = 0x1F302; // Register all groups for (LLSD::array_const_iterator it = data.beginArray(), end = data.endArray(); it != end; ++it) { const LLSD& sd = *it; const std::string& name = sd["Name"].asStringRef(); - if (name == GROUP_NAME_ALL) - { - mGroups.front().Character = loadIcon(sd); - } - else if (name == GROUP_NAME_OTHERS) - { - iconOthers = loadIcon(sd); - } - else if (name == GROUP_NAME_SKIP) + if (name == GROUP_NAME_SKIP) { mSkipCategories = loadCategories(sd); translateCategories(mSkipCategories); @@ -355,7 +349,7 @@ void LLEmojiDictionary::loadGroups() // Add group "others" mGroups.emplace_back(); - mGroups.back().Character = iconOthers; + mGroups.back().Character = GROUP_OTHERS_IMAGE_INDEX; } void LLEmojiDictionary::loadEmojis() -- cgit v1.2.3