summaryrefslogtreecommitdiff
path: root/indra/llui
diff options
context:
space:
mode:
authorAndrey Lihatskiy <alihatskiy@productengine.com>2024-03-01 18:02:48 +0200
committerAndrey Lihatskiy <alihatskiy@productengine.com>2024-03-01 18:02:48 +0200
commitfaf69f407fa95d9dc19f6d737f606e18ab32d052 (patch)
treec19376fa42061e5eee4e55d55cb8cb0a48e26fda /indra/llui
parent9f0faf5c892bba9703c713a29202a8ac48bfdf8b (diff)
parent701d1a33bb8227aa55a71f48caeb30a453e77ee0 (diff)
Merge branch 'main' into marchcat/yz-merge
# Conflicts: # indra/newview/llinventorygallery.cpp
Diffstat (limited to 'indra/llui')
-rw-r--r--indra/llui/CMakeLists.txt4
-rw-r--r--indra/llui/llbutton.cpp23
-rw-r--r--indra/llui/llbutton.h5
-rw-r--r--indra/llui/llemojidictionary.cpp469
-rw-r--r--indra/llui/llemojidictionary.h126
-rw-r--r--indra/llui/llemojihelper.cpp169
-rw-r--r--indra/llui/llemojihelper.h66
-rw-r--r--indra/llui/llfloater.cpp182
-rw-r--r--indra/llui/llfloater.h21
-rw-r--r--indra/llui/llfolderviewitem.cpp19
-rw-r--r--indra/llui/lllineeditor.cpp20
-rw-r--r--indra/llui/lllineeditor.h114
-rw-r--r--indra/llui/llmenugl.cpp5
-rw-r--r--indra/llui/llmenugl.h6
-rw-r--r--indra/llui/llnotifications.h6
-rw-r--r--indra/llui/llscrollbar.cpp6
-rw-r--r--indra/llui/llscrollcontainer.cpp23
-rw-r--r--indra/llui/llscrollcontainer.h4
-rw-r--r--indra/llui/llscrollingpanellist.cpp184
-rw-r--r--indra/llui/llscrollingpanellist.h39
-rw-r--r--indra/llui/llscrolllistctrl.cpp6
-rw-r--r--indra/llui/llscrolllistctrl.h30
-rw-r--r--indra/llui/llsearcheditor.cpp4
-rw-r--r--indra/llui/llspellcheck.h2
-rw-r--r--indra/llui/lltextbase.cpp152
-rw-r--r--indra/llui/lltextbase.h88
-rw-r--r--indra/llui/lltexteditor.cpp112
-rw-r--r--indra/llui/lltexteditor.h10
-rw-r--r--indra/llui/lluictrl.cpp25
-rw-r--r--indra/llui/lluictrl.h2
-rw-r--r--indra/llui/lluistring.h1
-rw-r--r--indra/llui/llview.cpp53
-rw-r--r--indra/llui/llview.h28
-rw-r--r--indra/llui/llviewquery.h12
34 files changed, 1638 insertions, 378 deletions
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index 9108c6143c..a0314cb5f2 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -29,6 +29,8 @@ set(llui_SOURCE_FILES
lldockcontrol.cpp
lldraghandle.cpp
lleditmenuhandler.cpp
+ llemojidictionary.cpp
+ llemojihelper.cpp
llf32uictrl.cpp
llfiltereditor.cpp
llflashtimer.cpp
@@ -139,6 +141,8 @@ set(llui_HEADER_FILES
lldockablefloater.h
lldockcontrol.h
lleditmenuhandler.h
+ llemojidictionary.h
+ llemojihelper.h
llf32uictrl.h
llfiltereditor.h
llflashtimer.h
diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp
index 49d275997a..9ef019840a 100644
--- a/indra/llui/llbutton.cpp
+++ b/indra/llui/llbutton.cpp
@@ -68,6 +68,7 @@ LLButton::Params::Params()
label_shadow("label_shadow", true),
auto_resize("auto_resize", false),
use_ellipses("use_ellipses", false),
+ use_font_color("use_font_color", true),
image_unselected("image_unselected"),
image_selected("image_selected"),
image_hover_selected("image_hover_selected"),
@@ -160,6 +161,7 @@ LLButton::LLButton(const LLButton::Params& p)
mDropShadowedText(p.label_shadow),
mAutoResize(p.auto_resize),
mUseEllipses( p.use_ellipses ),
+ mUseFontColor( p.use_font_color),
mHAlign(p.font_halign),
mLeftHPad(p.pad_left),
mRightHPad(p.pad_right),
@@ -961,7 +963,7 @@ void LLButton::draw()
LLFontGL::NORMAL,
mDropShadowedText ? LLFontGL::DROP_SHADOW_SOFT : LLFontGL::NO_SHADOW,
S32_MAX, text_width,
- NULL, mUseEllipses);
+ NULL, mUseEllipses, mUseFontColor);
}
LLUICtrl::draw();
@@ -1021,6 +1023,16 @@ BOOL LLButton::toggleState()
return flipped;
}
+void LLButton::setLabel( const std::string& label )
+{
+ mUnselectedLabel = mSelectedLabel = label;
+}
+
+void LLButton::setLabel( const LLUIString& label )
+{
+ mUnselectedLabel = mSelectedLabel = label;
+}
+
void LLButton::setLabel( const LLStringExplicit& label )
{
setLabelUnselected(label);
@@ -1052,14 +1064,7 @@ bool LLButton::labelIsTruncated() const
const LLUIString& LLButton::getCurrentLabel() const
{
- if( getToggleState() )
- {
- return mSelectedLabel;
- }
- else
- {
- return mUnselectedLabel;
- }
+ return getToggleState() ? mSelectedLabel : mUnselectedLabel;
}
void LLButton::setImageUnselected(LLPointer<LLUIImage> image)
diff --git a/indra/llui/llbutton.h b/indra/llui/llbutton.h
index ccd31e90c0..257159f64f 100644
--- a/indra/llui/llbutton.h
+++ b/indra/llui/llbutton.h
@@ -73,6 +73,7 @@ public:
Optional<bool> label_shadow;
Optional<bool> auto_resize;
Optional<bool> use_ellipses;
+ Optional<bool> use_font_color;
// images
Optional<LLUIImage*> image_unselected,
@@ -174,6 +175,7 @@ public:
void setUnselectedLabelColor( const LLColor4& c ) { mUnselectedLabelColor = c; }
void setSelectedLabelColor( const LLColor4& c ) { mSelectedLabelColor = c; }
void setUseEllipses( BOOL use_ellipses ) { mUseEllipses = use_ellipses; }
+ void setUseFontColor( BOOL use_font_color) { mUseFontColor = use_font_color; }
boost::signals2::connection setClickedCallback(const CommitCallbackParam& cb);
@@ -238,6 +240,8 @@ public:
void autoResize(); // resize with label of current btn state
void resize(LLUIString label); // resize with label input
+ void setLabel(const std::string& label);
+ void setLabel(const LLUIString& label);
void setLabel( const LLStringExplicit& label);
virtual BOOL setLabelArg( const std::string& key, const LLStringExplicit& text );
void setLabelUnselected(const LLStringExplicit& label);
@@ -353,6 +357,7 @@ protected:
bool mDropShadowedText;
bool mAutoResize;
bool mUseEllipses;
+ bool mUseFontColor;
bool mBorderEnabled;
bool mFlashing;
diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp
new file mode 100644
index 0000000000..f16c38a11a
--- /dev/null
+++ b/indra/llui/llemojidictionary.cpp
@@ -0,0 +1,469 @@
+/**
+* @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 <boost/algorithm/string.hpp>
+#include <boost/range/adaptor/filtered.hpp>
+#include <boost/range/algorithm/transform.hpp>
+
+// ============================================================================
+// Constants
+//
+
+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_SKIP("skip");
+// https://www.compart.com/en/unicode/U+1F302
+static const S32 GROUP_OTHERS_IMAGE_INDEX = 0x1F302;
+
+// ============================================================================
+// Helper functions
+//
+
+template<class T>
+std::list<T> llsd_array_to_list(const LLSD& sd, std::function<void(T&)> mutator = {});
+
+template<>
+std::list<std::string> llsd_array_to_list(const LLSD& sd, std::function<void(std::string&)> mutator)
+{
+ std::list<std::string> 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);
+ }
+
+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;
+ }
+
+ if (boost::icontains(descr.Category, mNeedle))
+ return true;
+
+ 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
+//
+
+LLEmojiDictionary::LLEmojiDictionary()
+{
+}
+
+// static
+void LLEmojiDictionary::initClass()
+{
+ 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;
+}
+
+// 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<LLEmojiSearchResult>& result,
+ const std::string& needle
+) const
+{
+ result.clear();
+
+ if (needle.empty() || needle.front() != ':')
+ return;
+
+ std::map<llwchar, std::vector<LLEmojiSearchResult>> results;
+
+ for (const LLEmojiDescriptor& d : mEmojis)
+ {
+ if (!d.ShortCodes.empty())
+ {
+ const std::string& shortCode = d.ShortCodes.front();
+ if (shortCode.size() >= needle.size() && shortCode.front() == needle.front())
+ {
+ std::size_t begin, end;
+ if (searchInShortCode(begin, end, shortCode, needle))
+ {
+ 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
+{
+ 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;
+}
+
+std::string LLEmojiDictionary::getNameFromEmoji(llwchar ch) const
+{
+ 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();
+ }
+
+ return false;
+}
+
+void LLEmojiDictionary::loadTranslations()
+{
+ std::vector<std::string> 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();
+
+ // 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_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 = GROUP_OTHERS_IMAGE_INDEX;
+}
+
+void LLEmojiDictionary::loadEmojis()
+{
+ std::vector<std::string> 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<std::string> 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<std::string> 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';
+}
+
+std::list<std::string> LLEmojiDictionary::loadCategories(const LLSD& sd)
+{
+ static const std::string key("Categories");
+ return llsd_array_to_list<std::string>(sd[key]);
+}
+
+std::list<std::string> LLEmojiDictionary::loadShortCodes(const LLSD& sd)
+{
+ static const std::string key("ShortCodes");
+ auto toLower = [](std::string& str) { LLStringUtil::toLower(str); };
+ return llsd_array_to_list<std::string>(sd[key], toLower);
+}
+
+void LLEmojiDictionary::translateCategories(std::list<std::string>& categories)
+{
+ for (std::string& category : categories)
+ {
+ auto it = mTranslations.find(category);
+ if (it != mTranslations.end())
+ {
+ category = it->second;
+ }
+ }
+}
+
+// ============================================================================
diff --git a/indra/llui/llemojidictionary.h b/indra/llui/llemojidictionary.h
new file mode 100644
index 0000000000..4af376df64
--- /dev/null
+++ b/indra/llui/llemojidictionary.h
@@ -0,0 +1,126 @@
+/**
+* @file llemojidictionary.h
+* @brief Header file for 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$
+*/
+
+#pragma once
+
+#include "lldictionary.h"
+#include "llinitdestroyclass.h"
+#include "llsingleton.h"
+
+// ============================================================================
+// LLEmojiDescriptor class
+//
+
+struct LLEmojiDescriptor
+{
+ llwchar Character;
+ std::string Category;
+ std::list<std::string> ShortCodes;
+ std::string getShortCodes() const;
+};
+
+// ============================================================================
+// LLEmojiGroup class
+//
+
+struct LLEmojiGroup
+{
+ llwchar Character;
+ std::list<std::string> Categories;
+};
+
+// ============================================================================
+// LLEmojiSearchResult class
+//
+
+struct LLEmojiSearchResult
+{
+ llwchar Character;
+ std::string String;
+ std::size_t Begin, End;
+
+ LLEmojiSearchResult(llwchar character, const std::string& string, std::size_t begin, std::size_t end)
+ : Character(character)
+ , String(string)
+ , Begin(begin)
+ , End(end)
+ {
+ }
+};
+
+// ============================================================================
+// LLEmojiDictionary class
+//
+
+class LLEmojiDictionary : public LLParamSingleton<LLEmojiDictionary>, public LLInitClass<LLEmojiDictionary>
+{
+ LLSINGLETON(LLEmojiDictionary);
+ ~LLEmojiDictionary() override {};
+
+public:
+ typedef std::map<std::string, std::string> cat2cat_map_t;
+ typedef std::map<std::string, const LLEmojiGroup*> cat2group_map_t;
+ typedef std::map<llwchar, const LLEmojiDescriptor*> emoji2descr_map_t;
+ typedef std::map<std::string, const LLEmojiDescriptor*> code2descr_map_t;
+ typedef std::map<std::string, std::vector<const LLEmojiDescriptor*>> cat2descrs_map_t;
+
+ static void initClass();
+ LLWString findMatchingEmojis(const std::string& needle) const;
+ static bool searchInShortCode(std::size_t& begin, std::size_t& end, const std::string& shortCode, const std::string& needle);
+ void findByShortCode(std::vector<LLEmojiSearchResult>& result, const std::string& needle) const;
+ 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 std::vector<LLEmojiGroup>& getGroups() const { return mGroups; }
+ const emoji2descr_map_t& getEmoji2Descr() const { return mEmoji2Descr; }
+ const cat2descrs_map_t& getCategory2Descrs() const { return mCategory2Descrs; }
+ const code2descr_map_t& getShortCode2Descr() const { return mShortCode2Descr; }
+
+private:
+ void loadTranslations();
+ void loadGroups();
+ void loadEmojis();
+
+ static llwchar loadIcon(const LLSD& sd);
+ static std::list<std::string> loadCategories(const LLSD& sd);
+ static std::list<std::string> loadShortCodes(const LLSD& sd);
+ void translateCategories(std::list<std::string>& categories);
+
+private:
+ std::vector<LLEmojiGroup> mGroups;
+ std::list<LLEmojiDescriptor> mEmojis;
+ std::list<std::string> mSkipCategories;
+
+ cat2cat_map_t mTranslations;
+ cat2group_map_t mCategory2Group;
+ emoji2descr_map_t mEmoji2Descr;
+ cat2descrs_map_t mCategory2Descrs;
+ code2descr_map_t mShortCode2Descr;
+};
+
+// ============================================================================
diff --git a/indra/llui/llemojihelper.cpp b/indra/llui/llemojihelper.cpp
new file mode 100644
index 0000000000..89e6ddf987
--- /dev/null
+++ b/indra/llui/llemojihelper.cpp
@@ -0,0 +1,169 @@
+/**
+* @file llemojihelper.h
+* @brief Header file for LLEmojiHelper
+*
+* $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 "llemojidictionary.h"
+#include "llemojihelper.h"
+#include "llfloater.h"
+#include "llfloaterreg.h"
+#include "lluictrl.h"
+
+// ============================================================================
+// Constants
+//
+
+constexpr char DEFAULT_EMOJI_HELPER_FLOATER[] = "emoji_picker";
+constexpr S32 HELPER_FLOATER_OFFSET_X = 0;
+constexpr S32 HELPER_FLOATER_OFFSET_Y = 0;
+
+// ============================================================================
+// LLEmojiHelper
+//
+
+std::string LLEmojiHelper::getToolTip(llwchar ch) const
+{
+ return LLEmojiDictionary::instance().getNameFromEmoji(ch);
+}
+
+bool LLEmojiHelper::isActive(const LLUICtrl* ctrl_p) const
+{
+ return mHostHandle.get() == ctrl_p;
+}
+
+// static
+bool LLEmojiHelper::isCursorInEmojiCode(const LLWString& wtext, S32 cursorPos, S32* pShortCodePos)
+{
+ // If the cursor is currently on a colon start the check one character further back
+ S32 shortCodePos = (cursorPos == 0 || L':' != wtext[cursorPos - 1]) ? cursorPos : cursorPos - 1;
+
+ auto isPartOfShortcode = [](llwchar ch) {
+ switch (ch)
+ {
+ case L'-':
+ case L'_':
+ case L'+':
+ return true;
+ default:
+ return LLStringOps::isAlnum(ch);
+ }
+ };
+ while (shortCodePos > 1 && isPartOfShortcode(wtext[shortCodePos - 1]))
+ {
+ shortCodePos--;
+ }
+
+ bool isShortCode = (L':' == wtext[shortCodePos - 1]) && (cursorPos - shortCodePos >= 2);
+ if (pShortCodePos)
+ *pShortCodePos = (isShortCode) ? shortCodePos - 1 : -1;
+ return isShortCode;
+}
+
+void LLEmojiHelper::showHelper(LLUICtrl* hostctrl_p, S32 local_x, S32 local_y, const std::string& short_code, std::function<void(llwchar)> cb)
+{
+ // Commit immediately if the user already typed a full shortcode
+ if (const auto* emojiDescrp = LLEmojiDictionary::instance().getDescriptorFromShortCode(short_code))
+ {
+ cb(emojiDescrp->Character);
+ hideHelper();
+ return;
+ }
+
+ if (mHelperHandle.isDead())
+ {
+ LLFloater* pHelperFloater = LLFloaterReg::getInstance(DEFAULT_EMOJI_HELPER_FLOATER);
+ mHelperHandle = pHelperFloater->getHandle();
+ mHelperCommitConn = pHelperFloater->setCommitCallback(std::bind([&](const LLSD& sdValue) { onCommitEmoji(utf8str_to_wstring(sdValue.asStringRef())[0]); }, std::placeholders::_2));
+ }
+ setHostCtrl(hostctrl_p);
+ mEmojiCommitCb = cb;
+
+ S32 floater_x, floater_y;
+ if (!hostctrl_p->localPointToOtherView(local_x, local_y, &floater_x, &floater_y, gFloaterView))
+ {
+ LL_ERRS() << "Cannot show emoji helper for non-floater controls." << LL_ENDL;
+ return;
+ }
+
+ LLFloater* pHelperFloater = mHelperHandle.get();
+ LLRect rect = pHelperFloater->getRect();
+ S32 left = floater_x - HELPER_FLOATER_OFFSET_X;
+ S32 top = floater_y - HELPER_FLOATER_OFFSET_Y + rect.getHeight();
+ rect.setLeftTopAndSize(left, top, rect.getWidth(), rect.getHeight());
+ pHelperFloater->setRect(rect);
+ pHelperFloater->openFloater(LLSD().with("hint", short_code));
+}
+
+void LLEmojiHelper::hideHelper(const LLUICtrl* ctrl_p, bool strict)
+{
+ mIsHideDisabled &= !strict;
+ if (mIsHideDisabled || (ctrl_p && !isActive(ctrl_p)))
+ {
+ return;
+ }
+
+ setHostCtrl(nullptr);
+}
+
+bool LLEmojiHelper::handleKey(const LLUICtrl* ctrl_p, KEY key, MASK mask)
+{
+ if (mHelperHandle.isDead() || !isActive(ctrl_p))
+ {
+ return false;
+ }
+
+ return mHelperHandle.get()->handleKey(key, mask, true);
+}
+
+void LLEmojiHelper::onCommitEmoji(llwchar emoji)
+{
+ if (!mHostHandle.isDead() && mEmojiCommitCb)
+ {
+ mEmojiCommitCb(emoji);
+ }
+}
+
+void LLEmojiHelper::setHostCtrl(LLUICtrl* hostctrl_p)
+{
+ const LLUICtrl* pCurHostCtrl = mHostHandle.get();
+ if (pCurHostCtrl != hostctrl_p)
+ {
+ mHostCtrlFocusLostConn.disconnect();
+ mHostHandle.markDead();
+ mEmojiCommitCb = {};
+
+ if (!mHelperHandle.isDead())
+ {
+ mHelperHandle.get()->closeFloater();
+ }
+
+ if (hostctrl_p)
+ {
+ mHostHandle = hostctrl_p->getHandle();
+ mHostCtrlFocusLostConn = hostctrl_p->setFocusLostCallback(std::bind([&]() { hideHelper(getHostCtrl()); }));
+ }
+ }
+}
diff --git a/indra/llui/llemojihelper.h b/indra/llui/llemojihelper.h
new file mode 100644
index 0000000000..e826ff93e6
--- /dev/null
+++ b/indra/llui/llemojihelper.h
@@ -0,0 +1,66 @@
+/**
+* @file llemojihelper.h
+* @brief Header file for LLEmojiHelper
+*
+* $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$
+*/
+
+#pragma once
+
+#include "llhandle.h"
+#include "llsingleton.h"
+
+#include <boost/signals2.hpp>
+
+class LLFloater;
+class LLUICtrl;
+
+class LLEmojiHelper : public LLSingleton<LLEmojiHelper>
+{
+ LLSINGLETON(LLEmojiHelper) {}
+ ~LLEmojiHelper() override {}
+
+public:
+ // General
+ std::string getToolTip(llwchar ch) const;
+ bool isActive(const LLUICtrl* ctrl_p) const;
+ static bool isCursorInEmojiCode(const LLWString& wtext, S32 cursor_pos, S32* short_code_pos_p = nullptr);
+ void showHelper(LLUICtrl* hostctrl_p, S32 local_x, S32 local_y, const std::string& short_code, std::function<void(llwchar)> commit_cb);
+ void hideHelper(const LLUICtrl* ctrl_p = nullptr, bool strict = false);
+ void setIsHideDisabled(bool disabled) { mIsHideDisabled = disabled; };
+
+ // Eventing
+ bool handleKey(const LLUICtrl* ctrl_p, KEY key, MASK mask);
+ void onCommitEmoji(llwchar emoji);
+
+protected:
+ LLUICtrl* getHostCtrl() const { return mHostHandle.get(); }
+ void setHostCtrl(LLUICtrl* hostctrl_p);
+
+private:
+ LLHandle<LLUICtrl> mHostHandle;
+ LLHandle<LLFloater> mHelperHandle;
+ boost::signals2::connection mHostCtrlFocusLostConn;
+ boost::signals2::connection mHelperCommitConn;
+ std::function<void(llwchar)> mEmojiCommitCb;
+ bool mIsHideDisabled;
+};
diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp
index 216f4a4ea5..04e8dbf874 100644
--- a/indra/llui/llfloater.cpp
+++ b/indra/llui/llfloater.cpp
@@ -181,6 +181,7 @@ LLFloater::Params::Params()
save_visibility("save_visibility", false),
can_dock("can_dock", false),
show_title("show_title", true),
+ auto_close("auto_close", false),
positioning("positioning", LLFloaterEnums::POSITIONING_RELATIVE),
header_height("header_height", 0),
legacy_header_height("legacy_header_height", 0),
@@ -253,6 +254,7 @@ LLFloater::LLFloater(const LLSD& key, const LLFloater::Params& p)
mCanClose(p.can_close),
mDragOnLeft(p.can_drag_on_left),
mResizable(p.can_resize),
+ mAutoClose(p.auto_close),
mPositioning(p.positioning),
mMinWidth(p.min_width),
mMinHeight(p.min_height),
@@ -503,6 +505,7 @@ void LLFloater::enableResizeCtrls(bool enable, bool width, bool height)
void LLFloater::destroy()
{
+ gFloaterView->onDestroyFloater(this);
// LLFloaterReg should be synchronized with "dead" floater to avoid returning dead instance before
// it was deleted via LLMortician::updateClass(). See EXT-8458.
LLFloaterReg::removeInstance(mInstanceName, mKey);
@@ -680,7 +683,7 @@ void LLFloater::openFloater(const LLSD& key)
if (getHost() != NULL)
{
getHost()->setMinimized(FALSE);
- getHost()->setVisibleAndFrontmost(mAutoFocus);
+ getHost()->setVisibleAndFrontmost(mAutoFocus && !getIsChrome());
getHost()->showFloater(this);
}
else
@@ -692,7 +695,7 @@ void LLFloater::openFloater(const LLSD& key)
}
applyControlsAndPosition(floater_to_stack);
setMinimized(FALSE);
- setVisibleAndFrontmost(mAutoFocus);
+ setVisibleAndFrontmost(mAutoFocus && !getIsChrome());
}
mOpenSignal(this, key);
@@ -828,6 +831,24 @@ void LLFloater::reshape(S32 width, S32 height, BOOL called_from_parent)
LLPanel::reshape(width, height, called_from_parent);
}
+// virtual
+void LLFloater::translate(S32 x, S32 y)
+{
+ LLView::translate(x, y);
+
+ if (!mTranslateWithDependents || mDependents.empty())
+ return;
+
+ for (const LLHandle<LLFloater>& handle : mDependents)
+ {
+ LLFloater* floater = handle.get();
+ if (floater && floater->getSnapTarget() == getHandle())
+ {
+ floater->LLView::translate(x, y);
+ }
+ }
+}
+
void LLFloater::releaseFocus()
{
LLUI::getInstance()->removePopup(this);
@@ -1116,9 +1137,9 @@ BOOL LLFloater::canSnapTo(const LLView* other_view)
if (other_view != getParent())
{
- const LLFloater* other_floaterp = dynamic_cast<const LLFloater*>(other_view);
- if (other_floaterp
- && other_floaterp->getSnapTarget() == getHandle()
+ const LLFloater* other_floaterp = dynamic_cast<const LLFloater*>(other_view);
+ if (other_floaterp
+ && other_floaterp->getSnapTarget() == getHandle()
&& mDependents.find(other_floaterp->getHandle()) != mDependents.end())
{
// this is a dependent that is already snapped to us, so don't snap back to it
@@ -1508,30 +1529,40 @@ BOOL LLFloater::isFrontmost()
&& floater_view->getFrontmost() == this);
}
-void LLFloater::addDependentFloater(LLFloater* floaterp, BOOL reposition)
+void LLFloater::addDependentFloater(LLFloater* floaterp, BOOL reposition, BOOL resize)
{
mDependents.insert(floaterp->getHandle());
floaterp->mDependeeHandle = getHandle();
if (reposition)
{
- floaterp->setRect(gFloaterView->findNeighboringPosition(this, floaterp));
+ LLRect rect = gFloaterView->findNeighboringPosition(this, floaterp);
+ if (resize)
+ {
+ const LLRect& base = getRect();
+ if (rect.mTop == base.mTop)
+ rect.mBottom = base.mBottom;
+ else if (rect.mLeft == base.mLeft)
+ rect.mRight = base.mRight;
+ floaterp->reshape(rect.getWidth(), rect.getHeight(), FALSE);
+ }
+ floaterp->setRect(rect);
floaterp->setSnapTarget(getHandle());
}
gFloaterView->adjustToFitScreen(floaterp, FALSE, TRUE);
if (floaterp->isFrontmost())
{
// make sure to bring self and sibling floaters to front
- gFloaterView->bringToFront(floaterp);
+ gFloaterView->bringToFront(floaterp, floaterp->getAutoFocus() && !getIsChrome());
}
}
-void LLFloater::addDependentFloater(LLHandle<LLFloater> dependent, BOOL reposition)
+void LLFloater::addDependentFloater(LLHandle<LLFloater> dependent, BOOL reposition, BOOL resize)
{
LLFloater* dependent_floaterp = dependent.get();
if(dependent_floaterp)
{
- addDependentFloater(dependent_floaterp, reposition);
+ addDependentFloater(dependent_floaterp, reposition, resize);
}
}
@@ -1541,6 +1572,44 @@ void LLFloater::removeDependentFloater(LLFloater* floaterp)
floaterp->mDependeeHandle = LLHandle<LLFloater>();
}
+void LLFloater::fitWithDependentsOnScreen(const LLRect& left, const LLRect& bottom, const LLRect& right, const LLRect& constraint, S32 min_overlap_pixels)
+{
+ LLRect total_rect = getRect();
+
+ for (const LLHandle<LLFloater>& handle : mDependents)
+ {
+ LLFloater* floater = handle.get();
+ if (floater && floater->getSnapTarget() == getHandle())
+ {
+ total_rect.unionWith(floater->getRect());
+ }
+ }
+
+ S32 delta_left = left.notEmpty() ? left.mRight - total_rect.mRight : 0;
+ S32 delta_bottom = bottom.notEmpty() ? bottom.mTop - total_rect.mTop : 0;
+ S32 delta_right = right.notEmpty() ? right.mLeft - total_rect.mLeft : 0;
+
+ // move floater with dependings fully onscreen
+ mTranslateWithDependents = true;
+ if (translateRectIntoRect(total_rect, constraint, min_overlap_pixels))
+ {
+ clearSnapTarget();
+ }
+ else if (delta_left > 0 && total_rect.mTop < left.mTop && total_rect.mBottom > left.mBottom)
+ {
+ translate(delta_left, 0);
+ }
+ else if (delta_bottom > 0 && total_rect.mLeft > bottom.mLeft && total_rect.mRight < bottom.mRight)
+ {
+ translate(0, delta_bottom);
+ }
+ else if (delta_right < 0 && total_rect.mTop < right.mTop && total_rect.mBottom > right.mBottom)
+ {
+ translate(delta_right, 0);
+ }
+ mTranslateWithDependents = false;
+}
+
BOOL LLFloater::offerClickToButton(S32 x, S32 y, MASK mask, EFloaterButton index)
{
if( mButtonsEnabled[index] )
@@ -1629,6 +1698,7 @@ BOOL LLFloater::handleDoubleClick(S32 x, S32 y, MASK mask)
return was_minimized || LLPanel::handleDoubleClick(x, y, mask);
}
+// virtual
void LLFloater::bringToFront( S32 x, S32 y )
{
if (getVisible() && pointInView(x, y))
@@ -1643,12 +1713,20 @@ void LLFloater::bringToFront( S32 x, S32 y )
LLFloaterView* parent = dynamic_cast<LLFloaterView*>( getParent() );
if (parent)
{
- parent->bringToFront( this );
+ parent->bringToFront(this, !getIsChrome());
}
}
}
}
+// virtual
+void LLFloater::goneFromFront()
+{
+ if (mAutoClose)
+ {
+ closeFloater();
+ }
+}
// virtual
void LLFloater::setVisibleAndFrontmost(BOOL take_focus,const LLSD& key)
@@ -2488,13 +2566,18 @@ void LLFloaterView::bringToFront(LLFloater* child, BOOL give_focus, BOOL restore
if (mFrontChild == child)
{
- if (give_focus && !gFocusMgr.childHasKeyboardFocus(child))
+ if (give_focus && child->canFocusStealFrontmost() && !gFocusMgr.childHasKeyboardFocus(child))
{
child->setFocus(TRUE);
}
return;
}
+ if (mFrontChild)
+ {
+ mFrontChild->goneFromFront();
+ }
+
mFrontChild = child;
// *TODO: make this respect floater's mAutoFocus value, instead of
@@ -2852,10 +2935,17 @@ void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_out
// floater is hosted elsewhere, so ignore
return;
}
+
+ if (floater->getDependee() &&
+ floater->getDependee() == floater->getSnapTarget().get())
+ {
+ // floater depends on other and snaps to it, so ignore
+ return;
+ }
+
LLRect::tCoordType screen_width = getSnapRect().getWidth();
LLRect::tCoordType screen_height = getSnapRect().getHeight();
-
// only automatically resize non-minimized, resizable floaters
if( floater->isResizable() && !floater->isMinimized() )
{
@@ -2897,29 +2987,10 @@ void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_out
}
}
- const LLRect& floater_rect = floater->getRect();
-
- S32 delta_left = mToolbarLeftRect.notEmpty() ? mToolbarLeftRect.mRight - floater_rect.mRight : 0;
- S32 delta_bottom = mToolbarBottomRect.notEmpty() ? mToolbarBottomRect.mTop - floater_rect.mTop : 0;
- S32 delta_right = mToolbarRightRect.notEmpty() ? mToolbarRightRect.mLeft - floater_rect.mLeft : 0;
+ const LLRect& constraint = snap_in_toolbars ? getSnapRect() : gFloaterView->getRect();
+ S32 min_overlap_pixels = allow_partial_outside ? FLOATER_MIN_VISIBLE_PIXELS : S32_MAX;
- // move window fully onscreen
- if (floater->translateIntoRect( snap_in_toolbars ? getSnapRect() : gFloaterView->getRect(), allow_partial_outside ? FLOATER_MIN_VISIBLE_PIXELS : S32_MAX ))
- {
- floater->clearSnapTarget();
- }
- else if (delta_left > 0 && floater_rect.mTop < mToolbarLeftRect.mTop && floater_rect.mBottom > mToolbarLeftRect.mBottom)
- {
- floater->translate(delta_left, 0);
- }
- else if (delta_bottom > 0 && floater_rect.mLeft > mToolbarBottomRect.mLeft && floater_rect.mRight < mToolbarBottomRect.mRight)
- {
- floater->translate(0, delta_bottom);
- }
- else if (delta_right < 0 && floater_rect.mTop < mToolbarRightRect.mTop && floater_rect.mBottom > mToolbarRightRect.mBottom)
- {
- floater->translate(delta_right, 0);
- }
+ floater->fitWithDependentsOnScreen(mToolbarLeftRect, mToolbarBottomRect, mToolbarRightRect, constraint, min_overlap_pixels);
}
void LLFloaterView::draw()
@@ -3006,6 +3077,9 @@ LLFloater *LLFloaterView::getBackmost() const
void LLFloaterView::syncFloaterTabOrder()
{
+ if (mFrontChild && !mFrontChild->isDead() && mFrontChild->getIsChrome())
+ return;
+
// look for a visible modal dialog, starting from first
LLModalDialog* modal_dialog = NULL;
for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it)
@@ -3041,7 +3115,34 @@ void LLFloaterView::syncFloaterTabOrder()
LLFloater* floaterp = dynamic_cast<LLFloater*>(*child_it);
if (gFocusMgr.childHasKeyboardFocus(floaterp))
{
- bringToFront(floaterp, FALSE);
+ if (mFrontChild != floaterp)
+ {
+ // Grab a list of the top floaters that want to stay on top of the focused floater
+ std::list<LLFloater*> listTop;
+ if (mFrontChild && !mFrontChild->canFocusStealFrontmost())
+ {
+ for (LLView* childp : *getChildList())
+ {
+ LLFloater* child_floaterp = static_cast<LLFloater*>(childp);
+ if (child_floaterp->canFocusStealFrontmost())
+ break;
+ listTop.push_back(child_floaterp);
+ }
+ }
+
+ bringToFront(floaterp, FALSE);
+
+ // Restore top floaters
+ if (!listTop.empty())
+ {
+ for (LLView* childp : listTop)
+ {
+ sendChildToFront(childp);
+ }
+ mFrontChild = listTop.back();
+ }
+ }
+
break;
}
}
@@ -3134,6 +3235,14 @@ void LLFloaterView::setToolbarRect(LLToolBarEnums::EToolBarLocation tb, const LL
}
}
+void LLFloaterView::onDestroyFloater(LLFloater* floater)
+{
+ if (mFrontChild == floater)
+ {
+ mFrontChild = nullptr;
+ }
+}
+
void LLFloater::setInstanceName(const std::string& name)
{
if (name != mInstanceName)
@@ -3226,6 +3335,7 @@ void LLFloater::initFromParams(const LLFloater::Params& p)
mDefaultRelativeY = p.rel_y;
mPositioning = p.positioning;
+ mAutoClose = p.auto_close;
mSaveRect = p.save_rect;
if (p.save_visibility)
diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h
index 3d15708295..88f9e77777 100644
--- a/indra/llui/llfloater.h
+++ b/indra/llui/llfloater.h
@@ -113,8 +113,6 @@ struct LLCoordFloater : LLCoord<LL_COORD_FLOATER>
bool operator!=(const LLCoordFloater& other) const { return !(*this == other); }
void setFloater(LLFloater& floater);
-
-
};
class LLFloater : public LLPanel, public LLInstanceTracker<LLFloater>
@@ -165,7 +163,8 @@ public:
save_visibility,
save_dock_state,
can_dock,
- show_title;
+ show_title,
+ auto_close;
Optional<LLFloaterEnums::EOpenPositioning> positioning;
@@ -238,6 +237,7 @@ public:
virtual void closeHostedFloater();
/*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE);
+ /*virtual*/ void translate(S32 x, S32 y);
// Release keyboard and mouse focus
void releaseFocus();
@@ -256,10 +256,11 @@ public:
std::string getShortTitle() const;
virtual void setMinimized(BOOL b);
void moveResizeHandlesToFront();
- void addDependentFloater(LLFloater* dependent, BOOL reposition = TRUE);
- void addDependentFloater(LLHandle<LLFloater> dependent_handle, BOOL reposition = TRUE);
+ void addDependentFloater(LLFloater* dependent, BOOL reposition = TRUE, BOOL resize = FALSE);
+ void addDependentFloater(LLHandle<LLFloater> dependent_handle, BOOL reposition = TRUE, BOOL resize = FALSE);
LLFloater* getDependee() { return (LLFloater*)mDependeeHandle.get(); }
- void removeDependentFloater(LLFloater* dependent);
+ void removeDependentFloater(LLFloater* dependent);
+ void fitWithDependentsOnScreen(const LLRect& left, const LLRect& bottom, const LLRect& right, const LLRect& constraint, S32 min_overlap_pixels);
BOOL isMinimized() const { return mMinimized; }
/// isShown() differs from getVisible() in that isShown() also considers
/// isMinimized(). isShown() is true only if visible and not minimized.
@@ -314,6 +315,9 @@ public:
/*virtual*/ void setVisible(BOOL visible); // do not override
/*virtual*/ void onVisibilityChange ( BOOL new_visibility ); // do not override
+ bool canFocusStealFrontmost() const { return mFocusStealsFrontmost; }
+ void setFocusStealsFrontmost(bool wants_frontmost) { mFocusStealsFrontmost = wants_frontmost; }
+
void setFrontmost(BOOL take_focus = TRUE, BOOL restore = TRUE);
virtual void setVisibleAndFrontmost(BOOL take_focus=TRUE, const LLSD& key = LLSD());
@@ -387,6 +391,7 @@ protected:
void setInstanceName(const std::string& name);
virtual void bringToFront(S32 x, S32 y);
+ virtual void goneFromFront();
void setExpandedRect(const LLRect& rect) { mExpandedRect = rect; } // size when not minimized
const LLRect& getExpandedRect() const { return mExpandedRect; }
@@ -482,8 +487,10 @@ private:
BOOL mCanTearOff;
BOOL mCanMinimize;
BOOL mCanClose;
+ bool mFocusStealsFrontmost = true; // FALSE if we don't want the currently focused floater to cover this floater without user interaction
BOOL mDragOnLeft;
BOOL mResizable;
+ BOOL mAutoClose;
LLFloaterEnums::EOpenPositioning mPositioning;
LLCoordFloater mPosition;
@@ -503,6 +510,7 @@ private:
typedef std::set<LLHandle<LLFloater> > handle_set_t;
typedef std::set<LLHandle<LLFloater> >::iterator handle_set_iter_t;
handle_set_t mDependents;
+ bool mTranslateWithDependents { false };
bool mButtonsEnabled[BUTTON_COUNT];
F32 mButtonScale;
@@ -599,6 +607,7 @@ public:
LLFloater* getFrontmostClosableFloater();
void setToolbarRect(LLToolBarEnums::EToolBarLocation tb, const LLRect& toolbar_rect);
+ void onDestroyFloater(LLFloater* floater);
private:
void hiddenFloaterClosed(LLFloater* floater);
diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp
index 0dc66bf37a..bc9469cfad 100644
--- a/indra/llui/llfolderviewitem.cpp
+++ b/indra/llui/llfolderviewitem.cpp
@@ -890,7 +890,7 @@ void LLFolderViewItem::drawLabel(const LLFontGL * font, const F32 x, const F32 y
//
font->renderUTF8(mLabel, 0, x, y, color,
LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
- S32_MAX, getRect().getWidth() - (S32) x - mLabelPaddingRight, &right_x, TRUE);
+ S32_MAX, getRect().getWidth() - (S32) x - mLabelPaddingRight, &right_x, /*use_ellipses*/TRUE);
}
void LLFolderViewItem::draw()
@@ -999,7 +999,7 @@ void LLFolderViewItem::draw()
{
suffix_font->renderUTF8( mLabelSuffix, 0, right_x, y, isFadeItem() ? color : (LLColor4)sSuffixColor,
LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
- S32_MAX, S32_MAX, &right_x, FALSE );
+ S32_MAX, S32_MAX, &right_x);
}
//--------------------------------------------------------------------------------//
@@ -1011,9 +1011,9 @@ void LLFolderViewItem::draw()
{
F32 match_string_left = text_left + font->getWidthF32(combined_string, 0, filter_offset + filter_string_length) - font->getWidthF32(combined_string, filter_offset, filter_string_length);
F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD;
- font->renderUTF8( combined_string, filter_offset, match_string_left, yy,
- sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
- filter_string_length, S32_MAX, &right_x, FALSE );
+ font->renderUTF8(combined_string, filter_offset, match_string_left, yy,
+ sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
+ filter_string_length, S32_MAX, &right_x);
}
else
{
@@ -1022,8 +1022,9 @@ void LLFolderViewItem::draw()
{
F32 match_string_left = text_left + font->getWidthF32(mLabel, 0, filter_offset + label_filter_length) - font->getWidthF32(mLabel, filter_offset, label_filter_length);
F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD;
- font->renderUTF8( mLabel, filter_offset, match_string_left, yy,
- sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, label_filter_length, S32_MAX, &right_x, FALSE );
+ font->renderUTF8(mLabel, filter_offset, match_string_left, yy,
+ sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
+ label_filter_length, S32_MAX, &right_x);
}
S32 suffix_filter_length = label_filter_length > 0 ? filter_string_length - label_filter_length : filter_string_length;
@@ -1032,7 +1033,9 @@ void LLFolderViewItem::draw()
S32 suffix_offset = llmax(0, filter_offset - (S32)mLabel.size());
F32 match_string_left = text_left + font->getWidthF32(mLabel, 0, mLabel.size()) + suffix_font->getWidthF32(mLabelSuffix, 0, suffix_offset + suffix_filter_length) - suffix_font->getWidthF32(mLabelSuffix, suffix_offset, suffix_filter_length);
F32 yy = (F32)getRect().getHeight() - suffix_font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD;
- suffix_font->renderUTF8( mLabelSuffix, suffix_offset, match_string_left, yy, sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, suffix_filter_length, S32_MAX, &right_x, FALSE );
+ suffix_font->renderUTF8(mLabelSuffix, suffix_offset, match_string_left, yy, sFilterTextColor,
+ LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
+ suffix_filter_length, S32_MAX, &right_x);
}
}
diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp
index 60dbfd68c6..453fa29e7c 100644
--- a/indra/llui/lllineeditor.cpp
+++ b/indra/llui/lllineeditor.cpp
@@ -89,6 +89,7 @@ LLLineEditor::Params::Params()
background_image_disabled("background_image_disabled"),
background_image_focused("background_image_focused"),
bg_image_always_focused("bg_image_always_focused", false),
+ show_label_focused("show_label_focused", false),
select_on_focus("select_on_focus", false),
revert_on_esc("revert_on_esc", true),
spellcheck("spellcheck", false),
@@ -152,6 +153,7 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)
mBgImageDisabled( p.background_image_disabled ),
mBgImageFocused( p.background_image_focused ),
mShowImageFocused( p.bg_image_always_focused ),
+ mShowLabelFocused( p.show_label_focused ),
mUseBgColor(p.use_bg_color),
mHaveHistory(FALSE),
mReplaceNewlinesWithSpaces( TRUE ),
@@ -1737,6 +1739,20 @@ void LLLineEditor::drawBackground()
}
}
+//virtual
+const std::string LLLineEditor::getToolTip() const
+{
+ if (sDebugUnicode)
+ {
+ std::string text = getText();
+ std::string tooltip = utf8str_showBytesUTF8(text);
+ return tooltip;
+ }
+
+ return LLUICtrl::getToolTip();
+}
+
+//virtual
void LLLineEditor::draw()
{
F32 alpha = getDrawContext().mAlpha;
@@ -2069,7 +2085,7 @@ void LLLineEditor::draw()
//draw label if no text is provided
//but we should draw it in a different color
//to give indication that it is not text you typed in
- if (0 == mText.length() && mReadOnly)
+ if (0 == mText.length() && (mReadOnly || mShowLabelFocused))
{
mGLFont->render(mLabel.getWString(), 0,
mTextLeftEdge, (F32)text_bottom,
@@ -2105,7 +2121,7 @@ void LLLineEditor::draw()
LLFontGL::NO_SHADOW,
S32_MAX,
mTextRightEdge - ll_round(rendered_pixels_right),
- &rendered_pixels_right, FALSE);
+ &rendered_pixels_right);
}
// Draw children (border)
LLView::draw();
diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h
index f983828d2b..5794b3c35a 100644
--- a/indra/llui/lllineeditor.h
+++ b/indra/llui/lllineeditor.h
@@ -91,6 +91,7 @@ public:
commit_on_focus_lost,
ignore_tab,
bg_image_always_focused,
+ show_label_focused,
is_password,
use_bg_color;
@@ -118,54 +119,55 @@ protected:
friend class LLUICtrlFactory;
friend class LLFloaterEditUI;
void showContextMenu(S32 x, S32 y);
+
public:
virtual ~LLLineEditor();
// mousehandler overrides
- /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask);
- /*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask);
- /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask);
- /*virtual*/ BOOL handleDoubleClick(S32 x,S32 y,MASK mask);
- /*virtual*/ BOOL handleMiddleMouseDown(S32 x,S32 y,MASK mask);
- /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask);
- /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask );
- /*virtual*/ BOOL handleUnicodeCharHere(llwchar uni_char);
- /*virtual*/ void onMouseCaptureLost();
+ /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask) override;
+ /*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask) override;
+ /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask) override;
+ /*virtual*/ BOOL handleDoubleClick(S32 x,S32 y,MASK mask) override;
+ /*virtual*/ BOOL handleMiddleMouseDown(S32 x,S32 y,MASK mask) override;
+ /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask) override;
+ /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask) override;
+ /*virtual*/ BOOL handleUnicodeCharHere(llwchar uni_char) override;
+ /*virtual*/ void onMouseCaptureLost() override;
// LLEditMenuHandler overrides
- virtual void cut();
- virtual BOOL canCut() const;
- virtual void copy();
- virtual BOOL canCopy() const;
- virtual void paste();
- virtual BOOL canPaste() const;
+ /*virtual*/ void cut() override;
+ /*virtual*/ BOOL canCut() const override;
+ /*virtual*/ void copy() override;
+ /*virtual*/ BOOL canCopy() const override;
+ /*virtual*/ void paste() override;
+ /*virtual*/ BOOL canPaste() const override;
virtual void updatePrimary();
virtual void copyPrimary();
virtual void pastePrimary();
virtual BOOL canPastePrimary() const;
- virtual void doDelete();
- virtual BOOL canDoDelete() const;
+ /*virtual*/ void doDelete() override;
+ /*virtual*/ BOOL canDoDelete() const override;
- virtual void selectAll();
- virtual BOOL canSelectAll() const;
+ /*virtual*/ void selectAll() override;
+ /*virtual*/ BOOL canSelectAll() const override;
- virtual void deselect();
- virtual BOOL canDeselect() const;
+ /*virtual*/ void deselect() override;
+ /*virtual*/ BOOL canDeselect() const override;
// LLSpellCheckMenuHandler overrides
- /*virtual*/ bool getSpellCheck() const;
+ /*virtual*/ bool getSpellCheck() const override;
- /*virtual*/ const std::string& getSuggestion(U32 index) const;
- /*virtual*/ U32 getSuggestionCount() const;
- /*virtual*/ void replaceWithSuggestion(U32 index);
+ /*virtual*/ const std::string& getSuggestion(U32 index) const override;
+ /*virtual*/ U32 getSuggestionCount() const override;
+ /*virtual*/ void replaceWithSuggestion(U32 index) override;
- /*virtual*/ void addToDictionary();
- /*virtual*/ bool canAddToDictionary() const;
+ /*virtual*/ void addToDictionary() override;
+ /*virtual*/ bool canAddToDictionary() const override;
- /*virtual*/ void addToIgnore();
- /*virtual*/ bool canAddToIgnore() const;
+ /*virtual*/ void addToIgnore() override;
+ /*virtual*/ bool canAddToIgnore() const override;
// Spell checking helper functions
std::string getMisspelledWord(U32 pos) const;
@@ -173,27 +175,28 @@ public:
void onSpellCheckSettingsChange();
// view overrides
- virtual void draw();
- virtual void reshape(S32 width,S32 height,BOOL called_from_parent=TRUE);
- virtual void onFocusReceived();
- virtual void onFocusLost();
- virtual void setEnabled(BOOL enabled);
+ /*virtual*/ const std::string getToolTip() const override;
+ /*virtual*/ void draw() override;
+ /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE) override;
+ /*virtual*/ void onFocusReceived() override;
+ /*virtual*/ void onFocusLost() override;
+ /*virtual*/ void setEnabled(BOOL enabled) override;
// UI control overrides
- virtual void clear();
- virtual void onTabInto();
- virtual void setFocus( BOOL b );
- virtual void setRect(const LLRect& rect);
- virtual BOOL acceptsTextInput() const;
- virtual void onCommit();
- virtual BOOL isDirty() const; // Returns TRUE if user changed value at all
- virtual void resetDirty(); // Clear dirty state
+ /*virtual*/ void clear() override;
+ /*virtual*/ void onTabInto() override;
+ /*virtual*/ void setFocus(BOOL b) override;
+ /*virtual*/ void setRect(const LLRect& rect) override;
+ /*virtual*/ BOOL acceptsTextInput() const override;
+ /*virtual*/ void onCommit() override;
+ /*virtual*/ BOOL isDirty() const override; // Returns TRUE if user changed value at all
+ /*virtual*/ void resetDirty() override; // Clear dirty state
// assumes UTF8 text
- virtual void setValue(const LLSD& value );
- virtual LLSD getValue() const;
- virtual BOOL setTextArg( const std::string& key, const LLStringExplicit& text );
- virtual BOOL setLabelArg( const std::string& key, const LLStringExplicit& text );
+ /*virtual*/ void setValue(const LLSD& value) override;
+ /*virtual*/ LLSD getValue() const override;
+ /*virtual*/ BOOL setTextArg(const std::string& key, const LLStringExplicit& text) override;
+ /*virtual*/ BOOL setLabelArg(const std::string& key, const LLStringExplicit& text) override;
void setLabel(const LLStringExplicit &new_label) { mLabel = new_label; }
const std::string& getLabel() { return mLabel.getString(); }
@@ -215,7 +218,7 @@ public:
// Selects characters 'start' to 'end'.
void setSelection(S32 start, S32 end);
- virtual void getSelectionRange(S32 *position, S32 *length) const;
+ /*virtual*/ void getSelectionRange(S32 *position, S32 *length) const override;
void setCommitOnFocusLost( BOOL b ) { mCommitOnFocusLost = b; }
void setRevertOnEsc( BOOL b ) { mRevertOnEsc = b; }
@@ -314,14 +317,14 @@ public:
void updateAllowingLanguageInput();
BOOL hasPreeditString() const;
// Implementation (overrides) of LLPreeditor
- virtual void resetPreedit();
- virtual void updatePreedit(const LLWString &preedit_string,
- const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position);
- virtual void markAsPreedit(S32 position, S32 length);
- virtual void getPreeditRange(S32 *position, S32 *length) const;
- virtual BOOL getPreeditLocation(S32 query_position, LLCoordGL *coord, LLRect *bounds, LLRect *control) const;
- virtual S32 getPreeditFontSize() const;
- virtual LLWString getPreeditString() const { return getWText(); }
+ /*virtual*/ void resetPreedit() override;
+ /*virtual*/ void updatePreedit(const LLWString &preedit_string,
+ const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position) override;
+ /*virtual*/ void markAsPreedit(S32 position, S32 length) override;
+ /*virtual*/ void getPreeditRange(S32 *position, S32 *length) const override;
+ /*virtual*/ BOOL getPreeditLocation(S32 query_position, LLCoordGL *coord, LLRect *bounds, LLRect *control) const override;
+ /*virtual*/ S32 getPreeditFontSize() const override;
+ /*virtual*/ LLWString getPreeditString() const override { return getWText(); }
void setText(const LLStringExplicit &new_text, bool use_size_limit);
@@ -398,6 +401,7 @@ protected:
BOOL mReadOnly;
BOOL mShowImageFocused;
+ BOOL mShowLabelFocused;
bool mUseBgColor;
diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp
index 76b50e0cdd..3552e1c7b7 100644
--- a/indra/llui/llmenugl.cpp
+++ b/indra/llui/llmenugl.cpp
@@ -572,6 +572,11 @@ void LLMenuItemGL::onVisibilityChange(BOOL new_visibility)
//
// This class represents a separator.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+LLMenuItemSeparatorGL::Params::Params()
+ : on_visible("on_visible")
+{
+}
+
LLMenuItemSeparatorGL::LLMenuItemSeparatorGL(const LLMenuItemSeparatorGL::Params& p) :
LLMenuItemGL( p )
{
diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h
index 87e3f18ebc..5c51cf8465 100644
--- a/indra/llui/llmenugl.h
+++ b/indra/llui/llmenugl.h
@@ -235,10 +235,10 @@ public:
struct Params : public LLInitParam::Block<Params, LLMenuItemGL::Params>
{
Optional<EnableCallbackParam > on_visible;
- Params() : on_visible("on_visible")
- {}
+ Params();
};
- LLMenuItemSeparatorGL(const LLMenuItemSeparatorGL::Params& p = LLMenuItemSeparatorGL::Params());
+
+ LLMenuItemSeparatorGL(const LLMenuItemSeparatorGL::Params& p = LLMenuItemSeparatorGL::Params());
/*virtual*/ void draw( void );
/*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask);
diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h
index 98b4c9df85..8e61ff5259 100644
--- a/indra/llui/llnotifications.h
+++ b/indra/llui/llnotifications.h
@@ -919,7 +919,7 @@ public:
/* virtual */ LLNotificationPtr add(const std::string& name,
const LLSD& substitutions,
const LLSD& payload,
- LLNotificationFunctorRegistry::ResponseFunctor functor);
+ LLNotificationFunctorRegistry::ResponseFunctor functor) override;
LLNotificationPtr add(const LLNotification::Params& p);
void add(const LLNotificationPtr pNotif);
@@ -966,8 +966,8 @@ public:
bool isVisibleByRules(LLNotificationPtr pNotification);
private:
- /*virtual*/ void initSingleton();
- /*virtual*/ void cleanupSingleton();
+ /*virtual*/ void initSingleton() override;
+ /*virtual*/ void cleanupSingleton() override;
void loadPersistentNotifications();
diff --git a/indra/llui/llscrollbar.cpp b/indra/llui/llscrollbar.cpp
index 735e2d529e..b2be9fb1e1 100644
--- a/indra/llui/llscrollbar.cpp
+++ b/indra/llui/llscrollbar.cpp
@@ -475,13 +475,15 @@ void LLScrollbar::reshape(S32 width, S32 height, BOOL called_from_parent)
{
up_button->reshape(up_button->getRect().getWidth(), llmin(getRect().getHeight() / 2, mThickness));
down_button->reshape(down_button->getRect().getWidth(), llmin(getRect().getHeight() / 2, mThickness));
- up_button->setOrigin(up_button->getRect().mLeft, getRect().getHeight() - up_button->getRect().getHeight());
+ up_button->setOrigin(0, getRect().getHeight() - up_button->getRect().getHeight());
+ down_button->setOrigin(0, 0);
}
else
{
up_button->reshape(llmin(getRect().getWidth() / 2, mThickness), up_button->getRect().getHeight());
down_button->reshape(llmin(getRect().getWidth() / 2, mThickness), down_button->getRect().getHeight());
- down_button->setOrigin(getRect().getWidth() - down_button->getRect().getWidth(), down_button->getRect().mBottom);
+ up_button->setOrigin(0, 0);
+ down_button->setOrigin(getRect().getWidth() - down_button->getRect().getWidth(), 0);
}
updateThumbRect();
}
diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp
index ad32f7186c..22d27b1f2a 100644
--- a/indra/llui/llscrollcontainer.cpp
+++ b/indra/llui/llscrollcontainer.cpp
@@ -70,6 +70,7 @@ LLScrollContainer::Params::Params()
bg_color("color"),
border_visible("border_visible"),
hide_scrollbar("hide_scrollbar"),
+ ignore_arrow_keys("ignore_arrow_keys"),
min_auto_scroll_rate("min_auto_scroll_rate", 100),
max_auto_scroll_rate("max_auto_scroll_rate", 1000),
max_auto_scroll_zone("max_auto_scroll_zone", 16),
@@ -86,6 +87,7 @@ LLScrollContainer::LLScrollContainer(const LLScrollContainer::Params& p)
mBackgroundColor(p.bg_color()),
mIsOpaque(p.is_opaque),
mHideScrollbar(p.hide_scrollbar),
+ mIgnoreArrowKeys(p.ignore_arrow_keys),
mReserveScrollCorner(p.reserve_scroll_corner),
mMinAutoScrollRate(p.min_auto_scroll_rate),
mMaxAutoScrollRate(p.max_auto_scroll_rate),
@@ -204,10 +206,29 @@ void LLScrollContainer::reshape(S32 width, S32 height,
}
}
+// virtual
BOOL LLScrollContainer::handleKeyHere(KEY key, MASK mask)
{
+ if (mIgnoreArrowKeys)
+ {
+ switch(key)
+ {
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ case KEY_UP:
+ case KEY_DOWN:
+ case KEY_PAGE_UP:
+ case KEY_PAGE_DOWN:
+ case KEY_HOME:
+ case KEY_END:
+ return FALSE;
+ default:
+ break;
+ }
+ }
+
// allow scrolled view to handle keystrokes in case it delegated keyboard focus
- // to the scroll container.
+ // to the scroll container.
// NOTE: this should not recurse indefinitely as handleKeyHere
// should not propagate to parent controls, so mScrolledView should *not*
// call LLScrollContainer::handleKeyHere in turn
diff --git a/indra/llui/llscrollcontainer.h b/indra/llui/llscrollcontainer.h
index dacea2a987..79dc70cac9 100644
--- a/indra/llui/llscrollcontainer.h
+++ b/indra/llui/llscrollcontainer.h
@@ -63,7 +63,8 @@ public:
Optional<bool> is_opaque,
reserve_scroll_corner,
border_visible,
- hide_scrollbar;
+ hide_scrollbar,
+ ignore_arrow_keys;
Optional<F32> min_auto_scroll_rate,
max_auto_scroll_rate;
Optional<U32> max_auto_scroll_zone;
@@ -149,6 +150,7 @@ private:
F32 mMaxAutoScrollRate;
U32 mMaxAutoScrollZone;
bool mHideScrollbar;
+ bool mIgnoreArrowKeys;
};
diff --git a/indra/llui/llscrollingpanellist.cpp b/indra/llui/llscrollingpanellist.cpp
index b6f2eb8ba2..3a819e7d06 100644
--- a/indra/llui/llscrollingpanellist.cpp
+++ b/indra/llui/llscrollingpanellist.cpp
@@ -37,53 +37,44 @@ static LLDefaultChildRegistry::Register<LLScrollingPanelList> r("scrolling_panel
// This could probably be integrated with LLScrollContainer -SJB
+LLScrollingPanelList::Params::Params()
+ : is_horizontal("is_horizontal")
+ , padding("padding")
+ , spacing("spacing")
+{
+}
+
+LLScrollingPanelList::LLScrollingPanelList(const Params& p)
+ : LLUICtrl(p)
+ , mIsHorizontal(p.is_horizontal)
+ , mPadding(p.padding.isProvided() ? p.padding : DEFAULT_PADDING)
+ , mSpacing(p.spacing.isProvided() ? p.spacing : DEFAULT_SPACING)
+{
+}
+
void LLScrollingPanelList::clearPanels()
{
deleteAllChildren();
mPanelList.clear();
-
- LLRect rc = getRect();
- rc.setLeftTopAndSize(rc.mLeft, rc.mTop, 1, 1);
- setRect(rc);
-
- notifySizeChanged(rc.getHeight());
+ rearrange();
}
-S32 LLScrollingPanelList::addPanel( LLScrollingPanel* panel )
+S32 LLScrollingPanelList::addPanel(LLScrollingPanel* panel, bool back)
{
- addChildInBack( panel );
- mPanelList.push_front( panel );
-
- // Resize this view
- S32 total_height = 0;
- S32 max_width = 0;
- S32 cur_gap = 0;
- for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin();
- iter != mPanelList.end(); ++iter)
+ if (back)
{
- LLScrollingPanel *childp = *iter;
- total_height += childp->getRect().getHeight() + cur_gap;
- max_width = llmax( max_width, childp->getRect().getWidth() );
- cur_gap = GAP_BETWEEN_PANELS;
+ addChild(panel);
+ mPanelList.push_back(panel);
}
- LLRect rc = getRect();
- rc.setLeftTopAndSize(rc.mLeft, rc.mTop, max_width, total_height);
- setRect(rc);
-
- notifySizeChanged(rc.getHeight());
-
- // Reposition each of the child views
- S32 cur_y = total_height;
- for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin();
- iter != mPanelList.end(); ++iter)
+ else
{
- LLScrollingPanel *childp = *iter;
- cur_y -= childp->getRect().getHeight();
- childp->translate( -childp->getRect().mLeft, cur_y - childp->getRect().mBottom);
- cur_y -= GAP_BETWEEN_PANELS;
+ addChildInBack(panel);
+ mPanelList.push_front(panel);
}
- return total_height;
+ rearrange();
+
+ return mIsHorizontal ? getRect().getWidth() : getRect().getHeight();
}
void LLScrollingPanelList::removePanel(LLScrollingPanel* panel)
@@ -100,7 +91,7 @@ void LLScrollingPanelList::removePanel(LLScrollingPanel* panel)
break;
}
}
- if(iter != mPanelList.end())
+ if (iter != mPanelList.end())
{
removePanel(index);
}
@@ -120,62 +111,104 @@ void LLScrollingPanelList::removePanel( U32 panel_index )
mPanelList.erase( mPanelList.begin() + panel_index );
}
- const S32 GAP_BETWEEN_PANELS = 6;
+ rearrange();
+}
- // Resize this view
- S32 total_height = 0;
- S32 max_width = 0;
- S32 cur_gap = 0;
- for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin();
+void LLScrollingPanelList::updatePanels(BOOL allow_modify)
+{
+ for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin();
iter != mPanelList.end(); ++iter)
- {
+ {
LLScrollingPanel *childp = *iter;
- total_height += childp->getRect().getHeight() + cur_gap;
- max_width = llmax( max_width, childp->getRect().getWidth() );
- cur_gap = GAP_BETWEEN_PANELS;
+ childp->updatePanel(allow_modify);
+ }
+}
+
+void LLScrollingPanelList::rearrange()
+{
+ // Resize this view
+ S32 new_width, new_height;
+ if (!mPanelList.empty())
+ {
+ new_width = new_height = mPadding * 2;
+ for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin();
+ iter != mPanelList.end(); ++iter)
+ {
+ LLScrollingPanel* childp = *iter;
+ const LLRect& rect = childp->getRect();
+ if (mIsHorizontal)
+ {
+ new_width += rect.getWidth() + mSpacing;
+ new_height = llmax(new_height, rect.getHeight());
+ }
+ else
+ {
+ new_height += rect.getHeight() + mSpacing;
+ new_width = llmax(new_width, rect.getWidth());
+ }
+ }
+
+ if (mIsHorizontal)
+ {
+ new_width -= mSpacing;
+ }
+ else
+ {
+ new_height -= mSpacing;
+ }
}
+ else
+ {
+ new_width = new_height = 1;
+ }
+
LLRect rc = getRect();
- rc.setLeftTopAndSize(rc.mLeft, rc.mTop, max_width, total_height);
- setRect(rc);
+ if (mIsHorizontal || !followsRight())
+ {
+ rc.mRight = rc.mLeft + new_width;
+ }
+ if (!mIsHorizontal || !followsBottom())
+ {
+ rc.mBottom = rc.mTop - new_height;
+ }
- notifySizeChanged(rc.getHeight());
+ if (rc.mRight != getRect().mRight || rc.mBottom != getRect().mBottom)
+ {
+ setRect(rc);
+ notifySizeChanged();
+ }
// Reposition each of the child views
- S32 cur_y = total_height;
+ S32 pos = mIsHorizontal ? mPadding : rc.getHeight() - mPadding;
for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin();
- iter != mPanelList.end(); ++iter)
+ iter != mPanelList.end(); ++iter)
{
- LLScrollingPanel *childp = *iter;
- cur_y -= childp->getRect().getHeight();
- childp->translate( -childp->getRect().mLeft, cur_y - childp->getRect().mBottom);
- cur_y -= GAP_BETWEEN_PANELS;
+ LLScrollingPanel* childp = *iter;
+ const LLRect& rect = childp->getRect();
+ if (mIsHorizontal)
+ {
+ childp->translate(pos - rect.mLeft, rc.getHeight() - mPadding - rect.mTop);
+ pos += rect.getWidth() + mSpacing;
+ }
+ else
+ {
+ childp->translate(mPadding - rect.mLeft, pos - rect.mTop);
+ pos -= rect.getHeight() + mSpacing;
+ }
}
}
-void LLScrollingPanelList::updatePanels(BOOL allow_modify)
-{
- for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin();
- iter != mPanelList.end(); ++iter)
- {
- LLScrollingPanel *childp = *iter;
- childp->updatePanel(allow_modify);
- }
-}
-
void LLScrollingPanelList::updatePanelVisiblilty()
{
// Determine visibility of children.
- S32 BORDER_WIDTH = 2; // HACK
- LLRect parent_local_rect = getParent()->getRect();
- parent_local_rect.stretch( -BORDER_WIDTH );
-
LLRect parent_screen_rect;
- getParent()->localPointToScreen(
- BORDER_WIDTH, 0,
+ getParent()->localPointToScreen(
+ mPadding, mPadding,
&parent_screen_rect.mLeft, &parent_screen_rect.mBottom );
- getParent()->localPointToScreen(
- parent_local_rect.getWidth() - BORDER_WIDTH, parent_local_rect.getHeight() - BORDER_WIDTH,
+ getParent()->localPointToScreen(
+ getParent()->getRect().getWidth() - mPadding,
+ getParent()->getRect().getHeight() - mPadding,
&parent_screen_rect.mRight, &parent_screen_rect.mTop );
for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin();
@@ -207,11 +240,12 @@ void LLScrollingPanelList::draw()
LLUICtrl::draw();
}
-void LLScrollingPanelList::notifySizeChanged(S32 height)
+void LLScrollingPanelList::notifySizeChanged()
{
LLSD info;
info["action"] = "size_changes";
- info["height"] = height;
+ info["height"] = getRect().getHeight();
+ info["width"] = getRect().getWidth();
notifyParent(info);
}
diff --git a/indra/llui/llscrollingpanellist.h b/indra/llui/llscrollingpanellist.h
index e8df176ec3..d625039427 100644
--- a/indra/llui/llscrollingpanellist.h
+++ b/indra/llui/llscrollingpanellist.h
@@ -45,18 +45,24 @@ public:
/*
- * A set of panels that are displayed in a vertical sequence inside a scroll container.
+ * A set of panels that are displayed in a sequence inside a scroll container.
*/
class LLScrollingPanelList : public LLUICtrl
{
public:
struct Params : public LLInitParam::Block<Params, LLUICtrl::Params>
- {};
- LLScrollingPanelList(const Params& p)
- : LLUICtrl(p)
- {}
+ {
+ Optional<bool> is_horizontal;
+ Optional<S32> padding;
+ Optional<S32> spacing;
+
+ Params();
+ };
+
+ LLScrollingPanelList(const Params& p);
- static const S32 GAP_BETWEEN_PANELS = 6;
+ static const S32 DEFAULT_SPACING = 6;
+ static const S32 DEFAULT_PADDING = 2;
typedef std::deque<LLScrollingPanel*> panel_list_t;
@@ -65,11 +71,18 @@ public:
virtual void draw();
void clearPanels();
- S32 addPanel( LLScrollingPanel* panel );
- void removePanel( LLScrollingPanel* panel );
- void removePanel( U32 panel_index );
+ S32 addPanel(LLScrollingPanel* panel, bool back = false);
+ void removePanel(LLScrollingPanel* panel);
+ void removePanel(U32 panel_index);
void updatePanels(BOOL allow_modify);
- const panel_list_t& getPanelList() { return mPanelList; }
+ void rearrange();
+
+ const panel_list_t& getPanelList() const { return mPanelList; }
+ bool getIsHorizontal() const { return mIsHorizontal; }
+ void setPadding(S32 padding) { mPadding = padding; rearrange(); }
+ void setSpacing(S32 spacing) { mSpacing = spacing; rearrange(); }
+ S32 getPadding() const { return mPadding; }
+ S32 getSpacing() const { return mSpacing; }
private:
void updatePanelVisiblilty();
@@ -77,7 +90,11 @@ private:
/**
* Notify parent about size change, makes sense when used inside accordion
*/
- void notifySizeChanged(S32 height);
+ void notifySizeChanged();
+
+ bool mIsHorizontal;
+ S32 mPadding;
+ S32 mSpacing;
panel_list_t mPanelList;
};
diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp
index 219667f766..f982dc99e8 100644
--- a/indra/llui/llscrolllistctrl.cpp
+++ b/indra/llui/llscrolllistctrl.cpp
@@ -411,7 +411,7 @@ void LLScrollListCtrl::clearRows()
LLScrollListItem* LLScrollListCtrl::getFirstSelected() const
{
item_list::const_iterator iter;
- for(iter = mItemList.begin(); iter != mItemList.end(); iter++)
+ for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
{
LLScrollListItem* item = *iter;
if (item->getSelected())
@@ -1269,7 +1269,7 @@ BOOL LLScrollListCtrl::selectItemByLabel(const std::string& label, BOOL case_sen
LLScrollListItem* item = getItemByLabel(label, case_sensitive, column);
bool found = NULL != item;
- if(found)
+ if (found)
{
selectItem(item, -1);
}
@@ -2747,7 +2747,7 @@ BOOL LLScrollListCtrl::setSort(S32 column_idx, BOOL ascending)
S32 LLScrollListCtrl::getLinesPerPage()
{
//if mPageLines is NOT provided display all item
- if(mPageLines)
+ if (mPageLines)
{
return mPageLines;
}
diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h
index 73b4fb036a..326589a329 100644
--- a/indra/llui/llscrolllistctrl.h
+++ b/indra/llui/llscrolllistctrl.h
@@ -253,7 +253,7 @@ public:
S32 getItemIndex( LLScrollListItem* item ) const;
S32 getItemIndex( const LLUUID& item_id ) const;
- void setCommentText( const std::string& comment_text);
+ void setCommentText( const std::string& comment_text);
LLScrollListItem* addSeparator(EAddPosition pos);
// "Simple" interface: use this when you're creating a list that contains only unique strings, only
@@ -263,7 +263,7 @@ public:
BOOL selectItemByLabel( const std::string& item, BOOL case_sensitive = TRUE, S32 column = 0 ); // FALSE if item not found
BOOL selectItemByPrefix(const std::string& target, BOOL case_sensitive = TRUE, S32 column = -1);
BOOL selectItemByPrefix(const LLWString& target, BOOL case_sensitive = TRUE, S32 column = -1);
- LLScrollListItem* getItemByLabel( const std::string& item, BOOL case_sensitive = TRUE, S32 column = 0 );
+ LLScrollListItem* getItemByLabel(const std::string& item, BOOL case_sensitive = TRUE, S32 column = 0);
const std::string getSelectedItemLabel(S32 column = 0) const;
LLSD getSelectedValue();
@@ -322,7 +322,7 @@ public:
virtual S32 getScrollPos() const;
virtual void setScrollPos( S32 pos );
- S32 getSearchColumn();
+ S32 getSearchColumn();
void setSearchColumn(S32 column) { mSearchColumn = column; }
S32 getColumnIndexFromOffset(S32 x);
S32 getColumnOffsetFromIndex(S32 index);
@@ -371,13 +371,13 @@ public:
// Used "internally" by the scroll bar.
void onScrollChange( S32 new_pos, LLScrollbar* src );
- static void onClickColumn(void *userdata);
+ static void onClickColumn(void *userdata);
- virtual void updateColumns(bool force_update = false);
- S32 calcMaxContentWidth();
- bool updateColumnWidths();
+ virtual void updateColumns(bool force_update = false);
+ S32 calcMaxContentWidth();
+ bool updateColumnWidths();
- void setHeadingHeight(S32 heading_height);
+ void setHeadingHeight(S32 heading_height);
/**
* Sets max visible lines without scroolbar, if this value equals to 0,
* then display all items.
@@ -398,18 +398,20 @@ public:
virtual void deselect();
virtual BOOL canDeselect() const;
- void setNumDynamicColumns(S32 num) { mNumDynamicWidthColumns = num; }
- void updateStaticColumnWidth(LLScrollListColumn* col, S32 new_width);
- S32 getTotalStaticColumnWidth() { return mTotalStaticColumnWidth; }
+ void setNumDynamicColumns(S32 num) { mNumDynamicWidthColumns = num; }
+ void updateStaticColumnWidth(LLScrollListColumn* col, S32 new_width);
+ S32 getTotalStaticColumnWidth() { return mTotalStaticColumnWidth; }
std::string getSortColumnName();
BOOL getSortAscending() { return mSortColumns.empty() ? TRUE : mSortColumns.back().second; }
BOOL hasSortOrder() const;
void clearSortOrder();
- void setAlternateSort() { mAlternateSort = true; }
+ void setAlternateSort() { mAlternateSort = TRUE; }
- S32 selectMultiple( uuid_vec_t ids );
+ void selectPrevItem(BOOL extend_selection = FALSE);
+ void selectNextItem(BOOL extend_selection = FALSE);
+ S32 selectMultiple(uuid_vec_t ids);
// conceptually const, but mutates mItemList
void updateSort() const;
// sorts a list without affecting the permanent sort order (so further list insertions can be unsorted, for example)
@@ -454,8 +456,6 @@ protected:
void updateLineHeight();
private:
- void selectPrevItem(BOOL extend_selection);
- void selectNextItem(BOOL extend_selection);
void drawItems();
void updateLineHeightInsert(LLScrollListItem* item);
diff --git a/indra/llui/llsearcheditor.cpp b/indra/llui/llsearcheditor.cpp
index 7a88ecc76f..8bf135f10c 100644
--- a/indra/llui/llsearcheditor.cpp
+++ b/indra/llui/llsearcheditor.cpp
@@ -186,6 +186,10 @@ void LLSearchEditor::setFocus( BOOL b )
void LLSearchEditor::onClearButtonClick(const LLSD& data)
{
setText(LLStringUtil::null);
+ if (mTextChangedCallback)
+ {
+ mTextChangedCallback(this, getValue());
+ }
mSearchEditor->onCommit(); // force keystroke callback
}
diff --git a/indra/llui/llspellcheck.h b/indra/llui/llspellcheck.h
index 3da5e30955..14f9b44fe4 100644
--- a/indra/llui/llspellcheck.h
+++ b/indra/llui/llspellcheck.h
@@ -47,7 +47,7 @@ public:
protected:
void addToDictFile(const std::string& dict_path, const std::string& word);
void initHunspell(const std::string& dict_language);
- void initSingleton();
+ void initSingleton() override;
public:
typedef std::list<std::string> dict_list_t;
diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp
index e0697cb454..7ccf025a19 100644
--- a/indra/llui/lltextbase.cpp
+++ b/indra/llui/lltextbase.cpp
@@ -29,6 +29,8 @@
#include "lltextbase.h"
+#include "llemojidictionary.h"
+#include "llemojihelper.h"
#include "lllocalcliprect.h"
#include "llmenugl.h"
#include "llscrollcontainer.h"
@@ -161,10 +163,12 @@ LLTextBase::Params::Params()
line_spacing("line_spacing"),
max_text_length("max_length", 255),
font_shadow("font_shadow"),
+ text_valign("text_valign"),
wrap("wrap"),
trusted_content("trusted_content", true),
always_show_icons("always_show_icons", false),
use_ellipses("use_ellipses", false),
+ use_color("use_color", true),
parse_urls("parse_urls", false),
force_urls_external("force_urls_external", false),
parse_highlights("parse_highlights", false)
@@ -208,6 +212,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p)
mVPad(p.v_pad),
mHAlign(p.font_halign),
mVAlign(p.font_valign),
+ mTextVAlign(p.text_valign.isProvided() ? p.text_valign.getValue() : p.font_valign.getValue()),
mLineSpacingMult(p.line_spacing.multiple),
mLineSpacingPixels(p.line_spacing.pixels),
mClip(p.clip),
@@ -222,6 +227,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p)
mPlainText ( p.plain_text ),
mWordWrap(p.wrap),
mUseEllipses( p.use_ellipses ),
+ mUseColor(p.use_color),
mParseHTML(p.parse_urls),
mForceUrlsExternal(p.force_urls_external),
mParseHighlights(p.parse_highlights),
@@ -582,7 +588,7 @@ void LLTextBase::drawCursor()
fontp = segmentp->getStyle()->getFont();
fontp->render(text, mCursorPos, cursor_rect,
LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], alpha),
- LLFontGL::LEFT, mVAlign,
+ LLFontGL::LEFT, mTextVAlign,
LLFontGL::NORMAL,
LLFontGL::NO_SHADOW,
1);
@@ -896,6 +902,28 @@ 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++)
+ {
+ llwchar code = wstr[text_kitty];
+ bool isEmoji = ed ? ed->isEmoji(code) : LLStringOps::isEmoji(code);
+ if (isEmoji)
+ {
+ if (!emoji_style)
+ {
+ emoji_style = new LLStyle(getStyleParams());
+ emoji_style->setFont(LLFontGL::getFontEmoji());
+ }
+
+ S32 new_seg_start = pos + text_kitty;
+ insertSegment(new LLEmojiTextSegment(emoji_style, new_seg_start, new_seg_start + 1, *this));
+ }
+ }
+ }
+
getViewModel()->getEditableDisplay().insert(pos, wstr);
if ( truncate() )
@@ -1079,6 +1107,7 @@ void LLTextBase::insertSegment(LLTextSegmentPtr segment_to_insert)
needsReflow(reflow_start_index);
}
+//virtual
BOOL LLTextBase::handleMouseDown(S32 x, S32 y, MASK mask)
{
// handle triple click
@@ -1133,6 +1162,7 @@ BOOL LLTextBase::handleMouseDown(S32 x, S32 y, MASK mask)
return LLUICtrl::handleMouseDown(x, y, mask);
}
+//virtual
BOOL LLTextBase::handleMouseUp(S32 x, S32 y, MASK mask)
{
LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
@@ -1152,6 +1182,7 @@ BOOL LLTextBase::handleMouseUp(S32 x, S32 y, MASK mask)
return LLUICtrl::handleMouseUp(x, y, mask);
}
+//virtual
BOOL LLTextBase::handleMiddleMouseDown(S32 x, S32 y, MASK mask)
{
LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
@@ -1163,6 +1194,7 @@ BOOL LLTextBase::handleMiddleMouseDown(S32 x, S32 y, MASK mask)
return LLUICtrl::handleMiddleMouseDown(x, y, mask);
}
+//virtual
BOOL LLTextBase::handleMiddleMouseUp(S32 x, S32 y, MASK mask)
{
LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
@@ -1174,6 +1206,7 @@ BOOL LLTextBase::handleMiddleMouseUp(S32 x, S32 y, MASK mask)
return LLUICtrl::handleMiddleMouseUp(x, y, mask);
}
+//virtual
BOOL LLTextBase::handleRightMouseDown(S32 x, S32 y, MASK mask)
{
LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
@@ -1185,6 +1218,7 @@ BOOL LLTextBase::handleRightMouseDown(S32 x, S32 y, MASK mask)
return LLUICtrl::handleRightMouseDown(x, y, mask);
}
+//virtual
BOOL LLTextBase::handleRightMouseUp(S32 x, S32 y, MASK mask)
{
LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
@@ -1196,6 +1230,7 @@ BOOL LLTextBase::handleRightMouseUp(S32 x, S32 y, MASK mask)
return LLUICtrl::handleRightMouseUp(x, y, mask);
}
+//virtual
BOOL LLTextBase::handleDoubleClick(S32 x, S32 y, MASK mask)
{
//Don't start triple click timer if user have clicked on scrollbar
@@ -1215,6 +1250,7 @@ BOOL LLTextBase::handleDoubleClick(S32 x, S32 y, MASK mask)
return LLUICtrl::handleDoubleClick(x, y, mask);
}
+//virtual
BOOL LLTextBase::handleHover(S32 x, S32 y, MASK mask)
{
LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
@@ -1226,6 +1262,7 @@ BOOL LLTextBase::handleHover(S32 x, S32 y, MASK mask)
return LLUICtrl::handleHover(x, y, mask);
}
+//virtual
BOOL LLTextBase::handleScrollWheel(S32 x, S32 y, S32 clicks)
{
LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
@@ -1237,6 +1274,7 @@ BOOL LLTextBase::handleScrollWheel(S32 x, S32 y, S32 clicks)
return LLUICtrl::handleScrollWheel(x, y, clicks);
}
+//virtual
BOOL LLTextBase::handleToolTip(S32 x, S32 y, MASK mask)
{
LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y);
@@ -1248,7 +1286,20 @@ BOOL LLTextBase::handleToolTip(S32 x, S32 y, MASK mask)
return LLUICtrl::handleToolTip(x, y, mask);
}
+//virtual
+const std::string LLTextBase::getToolTip() const
+{
+ if (sDebugUnicode)
+ {
+ std::string text = getText();
+ std::string tooltip = utf8str_showBytesUTF8(text);
+ return tooltip;
+ }
+
+ return LLUICtrl::getToolTip();
+}
+//virtual
void LLTextBase::reshape(S32 width, S32 height, BOOL called_from_parent)
{
if (width != getRect().getWidth() || height != getRect().getHeight() || LLView::sForceReshape)
@@ -1275,6 +1326,7 @@ void LLTextBase::reshape(S32 width, S32 height, BOOL called_from_parent)
}
}
+//virtual
void LLTextBase::draw()
{
// reflow if needed, on demand
@@ -1985,21 +2037,8 @@ LLTextBase::segment_set_t::const_iterator LLTextBase::getEditableSegIterContaini
LLTextBase::segment_set_t::iterator LLTextBase::getSegIterContaining(S32 index)
{
-
static LLPointer<LLIndexSegment> index_segment = new LLIndexSegment();
- S32 text_len = 0;
- if (!useLabel())
- {
- text_len = getLength();
- }
- else
- {
- text_len = mLabel.getWString().length();
- }
-
- if (index > text_len) { return mSegments.end(); }
-
// when there are no segments, we return the end iterator, which must be checked by caller
if (mSegments.size() <= 1) { return mSegments.begin(); }
@@ -2013,18 +2052,6 @@ LLTextBase::segment_set_t::const_iterator LLTextBase::getSegIterContaining(S32 i
{
static LLPointer<LLIndexSegment> index_segment = new LLIndexSegment();
- S32 text_len = 0;
- if (!useLabel())
- {
- text_len = getLength();
- }
- else
- {
- text_len = mLabel.getWString().length();
- }
-
- if (index > text_len) { return mSegments.end(); }
-
// when there are no segments, we return the end iterator, which must be checked by caller
if (mSegments.size() <= 1) { return mSegments.begin(); }
@@ -2188,8 +2215,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;
@@ -2437,18 +2464,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;
@@ -2456,7 +2483,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());
}
@@ -2468,25 +2495,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);
}
@@ -3318,12 +3348,13 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele
font->render(text, start,
rect,
color,
- LLFontGL::LEFT, mEditor.mVAlign,
+ LLFontGL::LEFT, mEditor.mTextVAlign,
LLFontGL::NORMAL,
mStyle->getShadowType(),
length,
&right_x,
- mEditor.getUseEllipses());
+ mEditor.getUseEllipses(),
+ mEditor.getUseColor());
}
rect.mLeft = right_x;
@@ -3337,12 +3368,13 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele
font->render(text, start,
rect,
mStyle->getSelectedColor().get(),
- LLFontGL::LEFT, mEditor.mVAlign,
+ LLFontGL::LEFT, mEditor.mTextVAlign,
LLFontGL::NORMAL,
LLFontGL::NO_SHADOW,
length,
&right_x,
- mEditor.getUseEllipses());
+ mEditor.getUseEllipses(),
+ mEditor.getUseColor());
}
rect.mLeft = right_x;
if( selection_end < seg_end )
@@ -3354,12 +3386,13 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele
font->render(text, start,
rect,
color,
- LLFontGL::LEFT, mEditor.mVAlign,
+ LLFontGL::LEFT, mEditor.mTextVAlign,
LLFontGL::NORMAL,
mStyle->getShadowType(),
length,
&right_x,
- mEditor.getUseEllipses());
+ mEditor.getUseEllipses(),
+ mEditor.getUseColor());
}
return right_x;
}
@@ -3591,6 +3624,33 @@ const S32 LLLabelTextSegment::getLength() const
}
//
+// LLEmojiTextSegment
+//
+LLEmojiTextSegment::LLEmojiTextSegment(LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor)
+ : LLNormalTextSegment(style, start, end, editor)
+{
+}
+
+LLEmojiTextSegment::LLEmojiTextSegment(const LLColor4& color, S32 start, S32 end, LLTextBase& editor, BOOL is_visible)
+ : LLNormalTextSegment(color, start, end, editor, is_visible)
+{
+}
+
+BOOL LLEmojiTextSegment::handleToolTip(S32 x, S32 y, MASK mask)
+{
+ if (mTooltip.empty())
+ {
+ LLWString emoji = getWText().substr(getStart(), getEnd() - getStart());
+ if (!emoji.empty())
+ {
+ mTooltip = LLEmojiHelper::instance().getToolTip(emoji[0]);
+ }
+ }
+
+ return LLNormalTextSegment::handleToolTip(x, y, mask);
+}
+
+//
// LLOnHoverChangeableTextSegment
//
diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h
index 3611ab0499..9d3c54fbee 100644
--- a/indra/llui/lltextbase.h
+++ b/indra/llui/lltextbase.h
@@ -178,6 +178,18 @@ protected:
/*virtual*/ const S32 getLength() const;
};
+// Text segment that represents a single emoji character that has a different style (=font size) than the rest of
+// the document it belongs to
+class LLEmojiTextSegment : public LLNormalTextSegment
+{
+public:
+ LLEmojiTextSegment(LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor);
+ LLEmojiTextSegment(const LLColor4& color, S32 start, S32 end, LLTextBase& editor, BOOL is_visible = TRUE);
+
+ bool canEdit() const override { return false; }
+ BOOL handleToolTip(S32 x, S32 y, MASK mask) override;
+};
+
// Text segment that changes it's style depending of mouse pointer position ( is it inside or outside segment)
class LLOnHoverChangeableTextSegment : public LLNormalTextSegment
{
@@ -273,7 +285,7 @@ typedef LLPointer<LLTextSegment> LLTextSegmentPtr;
/// as LLTextEditor and LLTextBox. It implements shared functionality
/// such as Url highlighting and opening.
///
-class LLTextBase
+class LLTextBase
: public LLUICtrl,
protected LLEditMenuHandler,
public LLSpellCheckMenuHandler,
@@ -316,6 +328,7 @@ public:
plain_text,
wrap,
use_ellipses,
+ use_color,
parse_urls,
force_urls_external,
parse_highlights,
@@ -335,55 +348,58 @@ public:
Optional<LLFontGL::ShadowType> font_shadow;
+ Optional<LLFontGL::VAlign> text_valign;
+
Params();
};
// LLMouseHandler interface
- /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask);
- /*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask);
- /*virtual*/ BOOL handleMiddleMouseDown(S32 x, S32 y, MASK mask);
- /*virtual*/ BOOL handleMiddleMouseUp(S32 x, S32 y, MASK mask);
- /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask);
- /*virtual*/ BOOL handleRightMouseUp(S32 x, S32 y, MASK mask);
- /*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask);
- /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask);
- /*virtual*/ BOOL handleScrollWheel(S32 x, S32 y, S32 clicks);
- /*virtual*/ BOOL handleToolTip(S32 x, S32 y, MASK mask);
+ /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask) override;
+ /*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask) override;
+ /*virtual*/ BOOL handleMiddleMouseDown(S32 x, S32 y, MASK mask) override;
+ /*virtual*/ BOOL handleMiddleMouseUp(S32 x, S32 y, MASK mask) override;
+ /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask) override;
+ /*virtual*/ BOOL handleRightMouseUp(S32 x, S32 y, MASK mask) override;
+ /*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask) override;
+ /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask) override;
+ /*virtual*/ BOOL handleScrollWheel(S32 x, S32 y, S32 clicks) override;
+ /*virtual*/ BOOL handleToolTip(S32 x, S32 y, MASK mask) override;
// LLView interface
- /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE);
- /*virtual*/ void draw();
+ /*virtual*/ const std::string getToolTip() const override;
+ /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE) override;
+ /*virtual*/ void draw() override;
// LLUICtrl interface
- /*virtual*/ BOOL acceptsTextInput() const { return !mReadOnly; }
- /*virtual*/ void setColor( const LLColor4& c );
+ /*virtual*/ BOOL acceptsTextInput() const override { return !mReadOnly; }
+ /*virtual*/ void setColor(const LLColor4& c) override;
virtual void setReadOnlyColor(const LLColor4 &c);
- virtual void onVisibilityChange( BOOL new_visibility );
+ /*virtual*/ void onVisibilityChange(BOOL new_visibility) override;
- /*virtual*/ void setValue(const LLSD& value );
- /*virtual*/ LLTextViewModel* getViewModel() const;
+ /*virtual*/ void setValue(const LLSD& value) override;
+ /*virtual*/ LLTextViewModel* getViewModel() const override;
// LLEditMenuHandler interface
- /*virtual*/ BOOL canDeselect() const;
- /*virtual*/ void deselect();
+ /*virtual*/ BOOL canDeselect() const override;
+ /*virtual*/ void deselect() override;
- virtual void onFocusReceived();
- virtual void onFocusLost();
+ virtual void onFocusReceived() override;
+ virtual void onFocusLost() override;
void setParseHTML(bool parse_html) { mParseHTML = parse_html; }
// LLSpellCheckMenuHandler overrides
- /*virtual*/ bool getSpellCheck() const;
+ /*virtual*/ bool getSpellCheck() const override;
- /*virtual*/ const std::string& getSuggestion(U32 index) const;
- /*virtual*/ U32 getSuggestionCount() const;
- /*virtual*/ void replaceWithSuggestion(U32 index);
+ /*virtual*/ const std::string& getSuggestion(U32 index) const override;
+ /*virtual*/ U32 getSuggestionCount() const override;
+ /*virtual*/ void replaceWithSuggestion(U32 index) override;
- /*virtual*/ void addToDictionary();
- /*virtual*/ bool canAddToDictionary() const;
+ /*virtual*/ void addToDictionary() override;
+ /*virtual*/ bool canAddToDictionary() const override;
- /*virtual*/ void addToIgnore();
- /*virtual*/ bool canAddToIgnore() const;
+ /*virtual*/ void addToIgnore() override;
+ /*virtual*/ bool canAddToIgnore() const override;
// Spell checking helper functions
std::string getMisspelledWord(U32 pos) const;
@@ -394,6 +410,7 @@ public:
// used by LLTextSegment layout code
bool getWordWrap() { return mWordWrap; }
bool getUseEllipses() { return mUseEllipses; }
+ bool getUseColor() { return mUseColor; }
bool truncate(); // returns true of truncation occurred
bool isContentTrusted() {return mTrustedContent;}
@@ -416,7 +433,7 @@ public:
void appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params = LLStyle::Params());
void setLabel(const LLStringExplicit& label);
- virtual BOOL setLabelArg(const std::string& key, const LLStringExplicit& text );
+ /*virtual*/ BOOL setLabelArg(const std::string& key, const LLStringExplicit& text) override;
const std::string& getLabel() { return mLabel.getString(); }
const LLWString& getWlabel() { return mLabel.getWString();}
@@ -633,7 +650,8 @@ protected:
S32 normalizeUri(std::string& uri);
protected:
- virtual std::string _getSearchText() const
+ // virtual
+ std::string _getSearchText() const override
{
return mLabel.getString() + getToolTip();
}
@@ -687,8 +705,9 @@ protected:
// configuration
S32 mHPad; // padding on left of text
S32 mVPad; // padding above text
- LLFontGL::HAlign mHAlign;
- LLFontGL::VAlign mVAlign;
+ LLFontGL::HAlign mHAlign; // horizontal alignment of the document in its entirety
+ LLFontGL::VAlign mVAlign; // vertical alignment of the document in its entirety
+ LLFontGL::VAlign mTextVAlign; // vertical alignment of a text segment within a single line of text
F32 mLineSpacingMult; // multiple of line height used as space for a single line of text (e.g. 1.5 to get 50% padding)
S32 mLineSpacingPixels; // padding between lines
bool mBorderVisible;
@@ -697,6 +716,7 @@ protected:
bool mParseHighlights; // highlight user-defined keywords
bool mWordWrap;
bool mUseEllipses;
+ bool mUseColor;
bool mTrackEnd; // if true, keeps scroll position at end of document during resize
bool mReadOnly;
bool mBGVisible; // render background?
diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp
index 3d2a426913..092739a538 100644
--- a/indra/llui/lltexteditor.cpp
+++ b/indra/llui/lltexteditor.cpp
@@ -43,6 +43,7 @@
#include "llmath.h"
#include "llclipboard.h"
+#include "llemojihelper.h"
#include "llscrollbar.h"
#include "llstl.h"
#include "llstring.h"
@@ -238,6 +239,7 @@ LLTextEditor::Params::Params()
default_color("default_color"),
commit_on_focus_lost("commit_on_focus_lost", false),
show_context_menu("show_context_menu"),
+ show_emoji_helper("show_emoji_helper"),
enable_tooltip_paste("enable_tooltip_paste")
{
addSynonym(prevalidate_callback, "text_type");
@@ -258,6 +260,7 @@ LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) :
mTabsToNextField(p.ignore_tab),
mPrevalidateFunc(p.prevalidate_callback()),
mShowContextMenu(p.show_context_menu),
+ mShowEmojiHelper(p.show_emoji_helper),
mEnableTooltipPaste(p.enable_tooltip_paste),
mPassDelete(FALSE),
mKeepSelectionOnReturn(false)
@@ -505,6 +508,16 @@ void LLTextEditor::getSegmentsInRange(LLTextEditor::segment_vec_t& segments_out,
}
}
+void LLTextEditor::setShowEmojiHelper(bool show)
+{
+ if (!mShowEmojiHelper)
+ {
+ LLEmojiHelper::instance().hideHelper(this);
+ }
+
+ mShowEmojiHelper = show;
+}
+
BOOL LLTextEditor::selectionContainsLineBreaks()
{
if (hasSelection())
@@ -668,6 +681,28 @@ void LLTextEditor::selectByCursorPosition(S32 prev_cursor_pos, S32 next_cursor_p
endSelection();
}
+void LLTextEditor::insertEmoji(llwchar emoji)
+{
+ LL_INFOS() << "LLTextEditor::insertEmoji(" << wchar_utf8_preview(emoji) << ")" << LL_ENDL;
+ auto styleParams = LLStyle::Params();
+ styleParams.font = LLFontGL::getFontEmoji();
+ auto segment = new LLEmojiTextSegment(new LLStyle(styleParams), mCursorPos, mCursorPos + 1, *this);
+ insert(mCursorPos, LLWString(1, emoji), false, segment);
+ setCursorPos(mCursorPos + 1);
+}
+
+void LLTextEditor::handleEmojiCommit(llwchar emoji)
+{
+ S32 shortCodePos;
+ if (LLEmojiHelper::isCursorInEmojiCode(getWText(), mCursorPos, &shortCodePos))
+ {
+ remove(shortCodePos, mCursorPos - shortCodePos, true);
+ setCursorPos(shortCodePos);
+
+ insertEmoji(emoji);
+ }
+}
+
BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask)
{
BOOL handled = FALSE;
@@ -934,6 +969,12 @@ BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask)
S32 LLTextEditor::execute( TextCmd* cmd )
{
+ if (!mReadOnly && mShowEmojiHelper)
+ {
+ // Any change to our contents should always hide the helper
+ LLEmojiHelper::instance().hideHelper(this);
+ }
+
S32 delta = 0;
if( cmd->execute(this, &delta) )
{
@@ -983,7 +1024,7 @@ S32 LLTextEditor::remove(S32 pos, S32 length, bool group_with_next_op)
// store text segments
getSegmentsInRange(segments_to_remove, pos, pos + length, false);
- if(pos <= end_pos)
+ if (pos <= end_pos)
{
removedChar = execute( new TextCmdRemove( pos, group_with_next_op, end_pos - pos, segments_to_remove ) );
}
@@ -1007,11 +1048,12 @@ S32 LLTextEditor::overwriteChar(S32 pos, llwchar wc)
// a pseudo-tab (up to for spaces in a row)
void LLTextEditor::removeCharOrTab()
{
- if( !getEnabled() )
+ if (!getEnabled())
{
return;
}
- if( mCursorPos > 0 )
+
+ if (mCursorPos > 0)
{
S32 chars_to_remove = 1;
@@ -1023,14 +1065,14 @@ void LLTextEditor::removeCharOrTab()
if (offset > 0)
{
chars_to_remove = offset % SPACES_PER_TAB;
- if( chars_to_remove == 0 )
+ if (chars_to_remove == 0)
{
chars_to_remove = SPACES_PER_TAB;
}
- for( S32 i = 0; i < chars_to_remove; i++ )
+ for (S32 i = 0; i < chars_to_remove; i++)
{
- if (text[ mCursorPos - i - 1] != ' ')
+ if (text[mCursorPos - i - 1] != ' ')
{
// Fewer than a full tab's worth of spaces, so
// just delete a single character.
@@ -1044,8 +1086,10 @@ void LLTextEditor::removeCharOrTab()
for (S32 i = 0; i < chars_to_remove; i++)
{
setCursorPos(mCursorPos - 1);
- remove( mCursorPos, 1, FALSE );
+ remove(mCursorPos, 1, false);
}
+
+ tryToShowEmojiHelper();
}
else
{
@@ -1056,7 +1100,7 @@ void LLTextEditor::removeCharOrTab()
// Remove a single character from the text
S32 LLTextEditor::removeChar(S32 pos)
{
- return remove( pos, 1, FALSE );
+ return remove(pos, 1, false);
}
void LLTextEditor::removeChar()
@@ -1065,10 +1109,12 @@ void LLTextEditor::removeChar()
{
return;
}
+
if (mCursorPos > 0)
{
setCursorPos(mCursorPos - 1);
removeChar(mCursorPos);
+ tryToShowEmojiHelper();
}
else
{
@@ -1127,6 +1173,7 @@ void LLTextEditor::addChar(llwchar wc)
}
setCursorPos(mCursorPos + addChar( mCursorPos, wc ));
+ tryToShowEmojiHelper();
if (!mReadOnly && mAutoreplaceCallback != NULL)
{
@@ -1146,6 +1193,37 @@ void LLTextEditor::addChar(llwchar wc)
}
}
+void LLTextEditor::showEmojiHelper()
+{
+ if (mReadOnly || !mShowEmojiHelper)
+ return;
+
+ const LLRect cursorRect(getLocalRectFromDocIndex(mCursorPos));
+ auto cb = [this](llwchar emoji) { insertEmoji(emoji); };
+ LLEmojiHelper::instance().showHelper(this, cursorRect.mLeft, cursorRect.mTop, LLStringUtil::null, cb);
+}
+
+void LLTextEditor::tryToShowEmojiHelper()
+{
+ if (mReadOnly || !mShowEmojiHelper)
+ return;
+
+ S32 shortCodePos;
+ LLWString wtext(getWText());
+ if (LLEmojiHelper::isCursorInEmojiCode(wtext, mCursorPos, &shortCodePos))
+ {
+ const LLRect cursorRect(getLocalRectFromDocIndex(shortCodePos));
+ const LLWString wpart(wtext.substr(shortCodePos, mCursorPos - shortCodePos));
+ const std::string part(wstring_to_utf8str(wpart));
+ auto cb = [this](llwchar emoji) { handleEmojiCommit(emoji); };
+ LLEmojiHelper::instance().showHelper(this, cursorRect.mLeft, cursorRect.mTop, part, cb);
+ }
+ else
+ {
+ LLEmojiHelper::instance().hideHelper();
+ }
+}
+
void LLTextEditor::addLineBreakChar(BOOL group_together)
{
if( !getEnabled() )
@@ -1778,6 +1856,11 @@ BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask )
}
else
{
+ if (!mReadOnly && mShowEmojiHelper && LLEmojiHelper::instance().handleKey(this, key, mask))
+ {
+ return TRUE;
+ }
+
if (mEnableTooltipPaste &&
LLToolTipMgr::instance().toolTipVisible() &&
KEY_TAB == key)
@@ -1819,6 +1902,12 @@ BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask )
{
resetCursorBlink();
needsScroll();
+
+ if (mShowEmojiHelper)
+ {
+ // Dismiss the helper whenever we handled a key that it didn't
+ LLEmojiHelper::instance().hideHelper(this);
+ }
}
return handled;
@@ -1837,7 +1926,12 @@ BOOL LLTextEditor::handleUnicodeCharHere(llwchar uni_char)
// Handle most keys only if the text editor is writeable.
if( !mReadOnly )
{
- if( mAutoIndent && '}' == uni_char )
+ if (mShowEmojiHelper && uni_char < 0x80 && LLEmojiHelper::instance().handleKey(this, (KEY)uni_char, MASK_NONE))
+ {
+ return TRUE;
+ }
+
+ if( mAutoIndent && '}' == uni_char )
{
unindentLineBeforeCloseBrace();
}
diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h
index f3939248c2..521405ec25 100644
--- a/indra/llui/lltexteditor.h
+++ b/indra/llui/lltexteditor.h
@@ -60,6 +60,7 @@ public:
ignore_tab,
commit_on_focus_lost,
show_context_menu,
+ show_emoji_helper,
enable_tooltip_paste,
auto_indent;
@@ -91,6 +92,9 @@ public:
static S32 spacesPerTab();
+ void insertEmoji(llwchar emoji);
+ void handleEmojiCommit(llwchar emoji);
+
// mousehandler overrides
virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask);
virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask);
@@ -202,6 +206,10 @@ public:
void setShowContextMenu(bool show) { mShowContextMenu = show; }
bool getShowContextMenu() const { return mShowContextMenu; }
+ void showEmojiHelper();
+ void setShowEmojiHelper(bool show);
+ bool getShowEmojiHelper() const { return mShowEmojiHelper; }
+
void setPassDelete(BOOL b) { mPassDelete = b; }
protected:
@@ -248,6 +256,7 @@ protected:
S32 insert(S32 pos, const LLWString &wstr, bool group_with_next_op, LLTextSegmentPtr segment);
S32 remove(S32 pos, S32 length, bool group_with_next_op);
+ void tryToShowEmojiHelper();
void focusLostHelper();
void updateAllowingLanguageInput();
BOOL hasPreeditString() const;
@@ -318,6 +327,7 @@ private:
BOOL mAllowEmbeddedItems;
bool mShowContextMenu;
+ bool mShowEmojiHelper;
bool mEnableTooltipPaste;
bool mPassDelete;
bool mKeepSelectionOnReturn; // disabling of removing selected text after pressing of Enter
diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp
index 21afcae7c3..7eb9ae69fb 100644
--- a/indra/llui/lluictrl.cpp
+++ b/indra/llui/lluictrl.cpp
@@ -768,25 +768,20 @@ void LLUICtrl::setIsChrome(BOOL is_chrome)
// virtual
BOOL LLUICtrl::getIsChrome() const
-{
+{
+ if (mIsChrome)
+ return TRUE;
+
LLView* parent_ctrl = getParent();
- while(parent_ctrl)
+ while (parent_ctrl)
{
- if(parent_ctrl->isCtrl())
- {
- break;
- }
+ if (parent_ctrl->isCtrl())
+ return ((LLUICtrl*)parent_ctrl)->getIsChrome();
+
parent_ctrl = parent_ctrl->getParent();
}
-
- if(parent_ctrl)
- {
- return mIsChrome || ((LLUICtrl*)parent_ctrl)->getIsChrome();
- }
- else
- {
- return mIsChrome ;
- }
+
+ return FALSE;
}
diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h
index be1c7dd0b6..cffb9933d4 100644
--- a/indra/llui/lluictrl.h
+++ b/indra/llui/lluictrl.h
@@ -264,7 +264,7 @@ public:
class LLTextInputFilter : public LLQueryFilter, public LLSingleton<LLTextInputFilter>
{
LLSINGLETON_EMPTY_CTOR(LLTextInputFilter);
- /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const
+ /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override
{
return filterResult_t(view->isCtrl() && static_cast<const LLUICtrl *>(view)->acceptsTextInput(), TRUE);
}
diff --git a/indra/llui/lluistring.h b/indra/llui/lluistring.h
index 07e02de6d8..b1089a3903 100644
--- a/indra/llui/lluistring.h
+++ b/indra/llui/lluistring.h
@@ -61,6 +61,7 @@ public:
LLUIString() : mArgs(NULL), mNeedsResult(false), mNeedsWResult(false) {}
LLUIString(const std::string& instring, const LLStringUtil::format_map_t& args);
LLUIString(const std::string& instring) : mArgs(NULL) { assign(instring); }
+ LLUIString(const LLWString& instring) : mArgs(NULL) { insert(0, instring); }
~LLUIString() { delete mArgs; }
void assign(const std::string& instring);
diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp
index 070cd4b107..21fe088550 100644
--- a/indra/llui/llview.cpp
+++ b/indra/llui/llview.cpp
@@ -59,6 +59,7 @@ static const S32 LINE_HEIGHT = 15;
S32 LLView::sDepth = 0;
bool LLView::sDebugRects = false;
+bool LLView::sDebugUnicode = false;
bool LLView::sIsRectDirty = false;
LLRect LLView::sDirtyRect;
bool LLView::sDebugRectsShowNames = true;
@@ -519,7 +520,7 @@ BOOL LLView::focusNext(LLView::child_list_t & result)
{
next = result.rbegin();
}
- if((*next)->isCtrl())
+ if ((*next)->isCtrl() && ((LLUICtrl*)*next)->hasTabStop())
{
LLUICtrl * ctrl = static_cast<LLUICtrl*>(*next);
ctrl->setFocus(TRUE);
@@ -1023,7 +1024,7 @@ BOOL LLView::handleUnicodeChar(llwchar uni_char, BOOL called_from_parent)
handled = handleUnicodeCharHere(uni_char);
if (handled && LLView::sDebugKeys)
{
- LL_INFOS() << "Unicode key handled by " << getName() << LL_ENDL;
+ LL_INFOS() << "Unicode key " << wchar_utf8_preview(uni_char) << " is handled by " << getName() << LL_ENDL;
}
}
}
@@ -1335,8 +1336,7 @@ void LLView::drawDebugRect()
std::string debug_text = llformat("%s (%d x %d)", getName().c_str(),
debug_rect.getWidth(), debug_rect.getHeight());
LLFontGL::getFontSansSerifSmall()->renderUTF8(debug_text, 0, (F32)x, (F32)y, border_color,
- LLFontGL::HCENTER, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
- S32_MAX, S32_MAX, NULL, FALSE);
+ LLFontGL::HCENTER, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW);
}
}
LLUI::popMatrix();
@@ -1752,23 +1752,26 @@ LLCoordGL getNeededTranslation(const LLRect& input, const LLRect& constraint, S3
const S32 KEEP_ONSCREEN_PIXELS_WIDTH = llmin(min_overlap_pixels, input.getWidth());
const S32 KEEP_ONSCREEN_PIXELS_HEIGHT = llmin(min_overlap_pixels, input.getHeight());
- if( input.mRight - KEEP_ONSCREEN_PIXELS_WIDTH < constraint.mLeft )
- {
- delta.mX = constraint.mLeft - (input.mRight - KEEP_ONSCREEN_PIXELS_WIDTH);
- }
- else if( input.mLeft + KEEP_ONSCREEN_PIXELS_WIDTH > constraint.mRight )
+ if (KEEP_ONSCREEN_PIXELS_WIDTH <= constraint.getWidth() &&
+ KEEP_ONSCREEN_PIXELS_HEIGHT <= constraint.getHeight())
{
- delta.mX = constraint.mRight - (input.mLeft + KEEP_ONSCREEN_PIXELS_WIDTH);
- }
+ if (input.mRight - KEEP_ONSCREEN_PIXELS_WIDTH < constraint.mLeft)
+ {
+ delta.mX = constraint.mLeft - (input.mRight - KEEP_ONSCREEN_PIXELS_WIDTH);
+ }
+ else if (input.mLeft + KEEP_ONSCREEN_PIXELS_WIDTH > constraint.mRight)
+ {
+ delta.mX = constraint.mRight - (input.mLeft + KEEP_ONSCREEN_PIXELS_WIDTH);
+ }
- if( input.mTop > constraint.mTop )
- {
- delta.mY = constraint.mTop - input.mTop;
- }
- else
- if( input.mTop - KEEP_ONSCREEN_PIXELS_HEIGHT < constraint.mBottom )
- {
- delta.mY = constraint.mBottom - (input.mTop - KEEP_ONSCREEN_PIXELS_HEIGHT);
+ if (input.mTop > constraint.mTop)
+ {
+ delta.mY = constraint.mTop - input.mTop;
+ }
+ else if (input.mTop - KEEP_ONSCREEN_PIXELS_HEIGHT < constraint.mBottom)
+ {
+ delta.mY = constraint.mBottom - (input.mTop - KEEP_ONSCREEN_PIXELS_HEIGHT);
+ }
}
return delta;
@@ -1779,13 +1782,19 @@ LLCoordGL getNeededTranslation(const LLRect& input, const LLRect& constraint, S3
// (Why top and left? That's where the drag bars are for floaters.)
BOOL LLView::translateIntoRect(const LLRect& constraint, S32 min_overlap_pixels)
{
- LLCoordGL translation = getNeededTranslation(getRect(), constraint, min_overlap_pixels);
+ return translateRectIntoRect(getRect(), constraint, min_overlap_pixels);
+}
+
+BOOL LLView::translateRectIntoRect(const LLRect& rect, const LLRect& constraint, S32 min_overlap_pixels)
+{
+ LLCoordGL translation = getNeededTranslation(rect, constraint, min_overlap_pixels);
if (translation.mX != 0 || translation.mY != 0)
{
translate(translation.mX, translation.mY);
return TRUE;
}
+
return FALSE;
}
@@ -1965,7 +1974,7 @@ private:
class SortByTabOrder : public LLQuerySorter, public LLSingleton<SortByTabOrder>
{
LLSINGLETON_EMPTY_CTOR(SortByTabOrder);
- /*virtual*/ void sort(LLView * parent, LLView::child_list_t &children) const
+ /*virtual*/ void sort(LLView * parent, LLView::child_list_t &children) const override
{
children.sort(CompareByTabOrder(parent->getTabOrder(), parent->getDefaultTabGroup()));
}
@@ -1989,7 +1998,7 @@ const LLViewQuery & LLView::getTabOrderQuery()
class LLFocusRootsFilter : public LLQueryFilter, public LLSingleton<LLFocusRootsFilter>
{
LLSINGLETON_EMPTY_CTOR(LLFocusRootsFilter);
- /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const
+ /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override
{
return filterResult_t(view->isCtrl() && view->isFocusRoot(), !view->isFocusRoot());
}
diff --git a/indra/llui/llview.h b/indra/llui/llview.h
index 7360fd0fb9..6e16d41cba 100644
--- a/indra/llui/llview.h
+++ b/indra/llui/llview.h
@@ -111,7 +111,7 @@ public:
Alternative<std::string> string;
Alternative<U32> flags;
- Follows();
+ Follows();
};
struct Params : public LLInitParam::Block<Params>
@@ -369,6 +369,7 @@ public:
virtual void translate( S32 x, S32 y );
void setOrigin( S32 x, S32 y ) { mRect.translate( x - mRect.mLeft, y - mRect.mBottom ); }
BOOL translateIntoRect( const LLRect& constraint, S32 min_overlap_pixels = S32_MAX);
+ BOOL translateRectIntoRect( const LLRect& rect, const LLRect& constraint, S32 min_overlap_pixels = S32_MAX);
BOOL translateIntoRectWithExclusion( const LLRect& inside, const LLRect& exclude, S32 min_overlap_pixels = S32_MAX);
void centerWithin(const LLRect& bounds);
@@ -658,8 +659,11 @@ public:
// Draw debug rectangles around widgets to help with alignment and spacing
static bool sDebugRects;
- static bool sIsRectDirty;
- static LLRect sDirtyRect;
+ // Show hexadecimal byte values of unicode symbols in a tooltip
+ static bool sDebugUnicode;
+
+ static bool sIsRectDirty;
+ static LLRect sDirtyRect;
// Draw widget names and sizes when drawing debug rectangles, turning this
// off is useful to make the rectangles themselves easier to see.
@@ -702,20 +706,16 @@ template <class T> T* LLView::getChild(const std::string& name, BOOL recurse) co
if (!result)
{
result = LLUICtrlFactory::getDefaultWidget<T>(name);
-
- if (result)
+ if (!result)
{
- // *NOTE: You cannot call mFoo = getChild<LLFoo>("bar")
- // in a floater or panel constructor. The widgets will not
- // be ready. Instead, put it in postBuild().
- LL_WARNS() << "Making dummy " << typeid(T).name() << " named \"" << name << "\" in " << getName() << LL_ENDL;
- }
- else
- {
- LL_WARNS() << "Failed to create dummy " << typeid(T).name() << LL_ENDL;
- return NULL;
+ LL_ERRS() << "Failed to create dummy " << typeid(T).name() << LL_ENDL;
}
+ // *NOTE: You cannot call mFoo = getChild<LLFoo>("bar")
+ // in a floater or panel constructor. The widgets will not
+ // be ready. Instead, put it in postBuild().
+ LL_WARNS() << "Making dummy " << typeid(T).name() << " named \"" << name << "\" in " << getName() << LL_ENDL;
+
getDefaultWidgetContainer().addChild(result);
}
}
diff --git a/indra/llui/llviewquery.h b/indra/llui/llviewquery.h
index 21bb1be26f..4bc9c4a08e 100644
--- a/indra/llui/llviewquery.h
+++ b/indra/llui/llviewquery.h
@@ -55,37 +55,37 @@ public:
class LLLeavesFilter : public LLQueryFilter, public LLSingleton<LLLeavesFilter>
{
LLSINGLETON_EMPTY_CTOR(LLLeavesFilter);
- /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const;
+ /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override;
};
class LLRootsFilter : public LLQueryFilter, public LLSingleton<LLRootsFilter>
{
LLSINGLETON_EMPTY_CTOR(LLRootsFilter);
- /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const;
+ /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override;
};
class LLVisibleFilter : public LLQueryFilter, public LLSingleton<LLVisibleFilter>
{
LLSINGLETON_EMPTY_CTOR(LLVisibleFilter);
- /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const;
+ /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override;
};
class LLEnabledFilter : public LLQueryFilter, public LLSingleton<LLEnabledFilter>
{
LLSINGLETON_EMPTY_CTOR(LLEnabledFilter);
- /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const;
+ /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override;
};
class LLTabStopFilter : public LLQueryFilter, public LLSingleton<LLTabStopFilter>
{
LLSINGLETON_EMPTY_CTOR(LLTabStopFilter);
- /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const;
+ /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override;
};
class LLCtrlFilter : public LLQueryFilter, public LLSingleton<LLCtrlFilter>
{
LLSINGLETON_EMPTY_CTOR(LLCtrlFilter);
- /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const;
+ /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override;
};
template <class T>