summaryrefslogtreecommitdiff
path: root/indra/llui
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llui')
-rw-r--r--indra/llui/CMakeLists.txt4
-rw-r--r--indra/llui/llbutton.cpp26
-rw-r--r--indra/llui/llbutton.h5
-rw-r--r--indra/llui/llemojidictionary.cpp405
-rw-r--r--indra/llui/llemojidictionary.h105
-rw-r--r--indra/llui/llemojihelper.cpp166
-rw-r--r--indra/llui/llemojihelper.h64
-rw-r--r--indra/llui/llfloater.cpp49
-rw-r--r--indra/llui/llfloater.h8
-rw-r--r--indra/llui/llfolderviewitem.cpp6
-rw-r--r--indra/llui/lllineeditor.cpp4
-rw-r--r--indra/llui/lllineeditor.h2
-rw-r--r--indra/llui/llmenugl.cpp3
-rw-r--r--indra/llui/llnotifications.h6
-rw-r--r--indra/llui/llscrollingpanellist.cpp184
-rw-r--r--indra/llui/llscrollingpanellist.h37
-rw-r--r--indra/llui/llscrolllistctrl.cpp6
-rw-r--r--indra/llui/llscrolllistctrl.h30
-rw-r--r--indra/llui/llspellcheck.h2
-rw-r--r--indra/llui/lltextbase.cpp128
-rw-r--r--indra/llui/lltextbase.h22
-rw-r--r--indra/llui/lltexteditor.cpp61
-rw-r--r--indra/llui/lltexteditor.h8
-rw-r--r--indra/llui/lluictrl.h2
-rw-r--r--indra/llui/llview.cpp6
-rw-r--r--indra/llui/llview.h24
-rw-r--r--indra/llui/llviewquery.h12
27 files changed, 1169 insertions, 206 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 8028f397f3..aeeff0b36f 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", false),
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),
@@ -942,11 +944,8 @@ void LLButton::draw()
break;
}
- S32 y_offset = 2 + (getRect().getHeight() - 20)/2;
-
if (pressed && mDisplayPressedState)
{
- y_offset--;
x++;
}
@@ -963,7 +962,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();
@@ -1023,6 +1022,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);
@@ -1054,14 +1063,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..cdcf5a93d6
--- /dev/null
+++ b/indra/llui/llemojidictionary.cpp
@@ -0,0 +1,405 @@
+/**
+* @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_ALL("all");
+static const std::string GROUP_NAME_OTHERS("others");
+static const std::string GROUP_NAME_SKIP("skip");
+
+// ============================================================================
+// 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;
+}
+
+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();
+ // Add group "all"
+ mGroups.emplace_back();
+ // https://www.compart.com/en/unicode/U+1F50D
+ mGroups.front().Character = 0x1F50D;
+ // https://www.compart.com/en/unicode/U+1F302
+ llwchar iconOthers = 0x1F302;
+
+ // Register all groups
+ for (LLSD::array_const_iterator it = data.beginArray(), end = data.endArray(); it != end; ++it)
+ {
+ const LLSD& sd = *it;
+ const std::string& name = sd["Name"].asStringRef();
+ if (name == GROUP_NAME_ALL)
+ {
+ mGroups.front().Character = loadIcon(sd);
+ }
+ else if (name == GROUP_NAME_OTHERS)
+ {
+ iconOthers = loadIcon(sd);
+ }
+ else if (name == GROUP_NAME_SKIP)
+ {
+ mSkipCategories = loadCategories(sd);
+ translateCategories(mSkipCategories);
+ }
+ else
+ {
+ // Add new group
+ mGroups.emplace_back();
+ LLEmojiGroup& group = mGroups.back();
+ group.Character = loadIcon(sd);
+ group.Categories = loadCategories(sd);
+ translateCategories(group.Categories);
+
+ for (const std::string& category : group.Categories)
+ {
+ mCategory2Group.insert(std::make_pair(category, &group));
+ }
+ }
+ }
+
+ // Add group "others"
+ mGroups.emplace_back();
+ mGroups.back().Character = iconOthers;
+}
+
+void LLEmojiDictionary::loadEmojis()
+{
+ std::vector<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';
+}
+
+static inline std::list<std::string> loadStrings(const LLSD& sd, const std::string key)
+{
+ auto toLower = [](std::string& str) { LLStringUtil::toLower(str); };
+ return llsd_array_to_list<std::string>(sd[key], toLower);
+}
+
+std::list<std::string> LLEmojiDictionary::loadCategories(const LLSD& sd)
+{
+ static const std::string categoriesKey("Categories");
+ return loadStrings(sd, categoriesKey);
+}
+
+std::list<std::string> LLEmojiDictionary::loadShortCodes(const LLSD& sd)
+{
+ static const std::string shortCodesKey("ShortCodes");
+ return loadStrings(sd, shortCodesKey);
+}
+
+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..f6442684a7
--- /dev/null
+++ b/indra/llui/llemojidictionary.h
@@ -0,0 +1,105 @@
+/**
+* @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;
+};
+
+// ============================================================================
+// 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;
+ 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..fb660a9e5b
--- /dev/null
+++ b/indra/llui/llemojihelper.cpp
@@ -0,0 +1,166 @@
+/**
+* @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_complete";
+constexpr S32 HELPER_FLOATER_OFFSET_X = 20;
+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 rct = pHelperFloater->getRect();
+ rct.setLeftTopAndSize(floater_x - HELPER_FLOATER_OFFSET_X, floater_y - HELPER_FLOATER_OFFSET_Y + rct.getHeight(), rct.getWidth(), rct.getHeight());
+ pHelperFloater->setRect(rct);
+ pHelperFloater->openFloater(LLSD().with("hint", short_code));
+}
+
+void LLEmojiHelper::hideHelper(const LLUICtrl* ctrl_p)
+{
+ if (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..58f68d12a4
--- /dev/null
+++ b/indra/llui/llemojihelper.h
@@ -0,0 +1,64 @@
+/**
+* @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);
+
+ // 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;
+};
diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp
index 2303cd24b7..98895d56dd 100644
--- a/indra/llui/llfloater.cpp
+++ b/indra/llui/llfloater.cpp
@@ -1509,14 +1509,24 @@ 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);
@@ -1527,12 +1537,12 @@ void LLFloater::addDependentFloater(LLFloater* floaterp, BOOL reposition)
}
}
-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);
}
}
@@ -2488,7 +2498,7 @@ 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);
}
@@ -3041,7 +3051,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;
}
}
diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h
index 668cd208a9..3699629ef8 100644
--- a/indra/llui/llfloater.h
+++ b/indra/llui/llfloater.h
@@ -255,8 +255,8 @@ 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);
BOOL isMinimized() const { return mMinimized; }
@@ -313,6 +313,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());
@@ -481,6 +484,7 @@ 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;
diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp
index e2b5279aab..674d8a6fe8 100644
--- a/indra/llui/llfolderviewitem.cpp
+++ b/indra/llui/llfolderviewitem.cpp
@@ -874,7 +874,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, /*use_color*/FALSE);
}
void LLFolderViewItem::draw()
@@ -953,7 +953,7 @@ void LLFolderViewItem::draw()
{
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, /*use_ellipses*/FALSE, /*use_color*/FALSE );
}
//--------------------------------------------------------------------------------//
@@ -966,7 +966,7 @@ void LLFolderViewItem::draw()
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 );
+ filter_string_length, S32_MAX, &right_x, /*use_ellipses*/FALSE, /*use_color*/FALSE );
}
//Gilbert Linden 9-20-2012: Although this should be legal, removing it because it causes the mLabelSuffix rendering to
diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp
index 940cf398c0..7d13d731e2 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 ),
@@ -2068,7 +2070,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,
diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h
index ae4e05c065..624371ebda 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;
@@ -395,6 +396,7 @@ protected:
BOOL mReadOnly;
BOOL mShowImageFocused;
+ BOOL mShowLabelFocused;
bool mUseBgColor;
diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp
index 33c4b6ec73..16c27da56a 100644
--- a/indra/llui/llmenugl.cpp
+++ b/indra/llui/llmenugl.cpp
@@ -487,9 +487,6 @@ void LLMenuItemGL::draw( void )
// let disabled items be highlighted, just don't draw them as such
if( getEnabled() && getHighlight() && !mBriefItem)
{
- int debug_count = 0;
- if (dynamic_cast<LLMenuItemCallGL*>(this))
- debug_count++;
gGL.color4fv( mHighlightBackground.get().mV );
gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0 );
diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h
index 921398a693..4d9a33f1d7 100644
--- a/indra/llui/llnotifications.h
+++ b/indra/llui/llnotifications.h
@@ -913,7 +913,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);
@@ -964,8 +964,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/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..9dc65dabb5 100644
--- a/indra/llui/llscrollingpanellist.h
+++ b/indra/llui/llscrollingpanellist.h
@@ -51,12 +51,18 @@ 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/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 8732a7ce45..4ca1879be6 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", false),
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() )
@@ -1979,21 +2007,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(); }
@@ -2007,18 +2022,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(); }
@@ -2182,8 +2185,8 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para
S32 start=0,end=0;
LLUrlMatch match;
std::string text = new_text;
- while ( LLUrlRegistry::instance().findUrl(text, match,
- boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3),isContentTrusted() || mAlwaysShowIcons))
+ while (LLUrlRegistry::instance().findUrl(text, match,
+ boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3), isContentTrusted() || mAlwaysShowIcons))
{
start = match.getStart();
end = match.getEnd()+1;
@@ -2431,18 +2434,18 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig
LLStyle::Params normal_style_params(style_params);
normal_style_params.font.style("NORMAL");
LLStyleConstSP normal_sp(new LLStyle(normal_style_params));
- segments.push_back(new LLOnHoverChangeableTextSegment(sp, normal_sp, segment_start, segment_end, *this ));
+ segments.push_back(new LLOnHoverChangeableTextSegment(sp, normal_sp, segment_start, segment_end, *this));
}
else
{
- segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this ));
+ segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this));
}
insertStringNoUndo(getLength(), wide_text, &segments);
}
// Set the cursor and scroll position
- if( selection_start != selection_end )
+ if (selection_start != selection_end)
{
mSelectionStart = selection_start;
mSelectionEnd = selection_end;
@@ -2450,7 +2453,7 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig
mIsSelecting = was_selecting;
setCursorPos(cursor_pos);
}
- else if( cursor_was_at_end )
+ else if (cursor_was_at_end)
{
setCursorPos(getLength());
}
@@ -2462,25 +2465,28 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig
void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only)
{
- if (new_text.empty()) return;
+ if (new_text.empty())
+ {
+ return;
+ }
std::string::size_type start = 0;
- std::string::size_type pos = new_text.find("\n",start);
+ std::string::size_type pos = new_text.find("\n", start);
- while(pos!=-1)
+ while (pos != std::string::npos)
{
- if(pos!=start)
+ if (pos != start)
{
std::string str = std::string(new_text,start,pos-start);
- appendAndHighlightTextImpl(str,highlight_part, style_params, underline_on_hover_only);
+ appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only);
}
appendLineBreakSegment(style_params);
start = pos+1;
- pos = new_text.find("\n",start);
+ pos = new_text.find("\n", start);
}
- std::string str = std::string(new_text,start,new_text.length()-start);
- appendAndHighlightTextImpl(str,highlight_part, style_params, underline_on_hover_only);
+ std::string str = std::string(new_text, start, new_text.length() - start);
+ appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only);
}
@@ -3312,12 +3318,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;
@@ -3331,12 +3338,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 )
@@ -3348,12 +3356,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;
}
@@ -3585,6 +3594,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..37ab798a1d 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
{
@@ -316,6 +328,7 @@ public:
plain_text,
wrap,
use_ellipses,
+ use_color,
parse_urls,
force_urls_external,
parse_highlights,
@@ -335,6 +348,8 @@ public:
Optional<LLFontGL::ShadowType> font_shadow;
+ Optional<LLFontGL::VAlign> text_valign;
+
Params();
};
@@ -394,6 +409,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;}
@@ -687,8 +703,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 +714,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..95d8b666ab 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,15 @@ 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 +680,27 @@ void LLTextEditor::selectByCursorPosition(S32 prev_cursor_pos, S32 next_cursor_p
endSelection();
}
+void LLTextEditor::insertEmoji(llwchar emoji)
+{
+ 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 +967,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) )
{
@@ -1128,6 +1167,17 @@ void LLTextEditor::addChar(llwchar wc)
setCursorPos(mCursorPos + addChar( mCursorPos, wc ));
+ if (!mReadOnly && mShowEmojiHelper)
+ {
+ LLWString wtext(getWText()); S32 shortCodePos;
+ if (LLEmojiHelper::isCursorInEmojiCode(wtext, mCursorPos, &shortCodePos))
+ {
+ const LLRect cursorRect = getLocalRectFromDocIndex(mCursorPos - 1);
+ const LLWString shortCode = wtext.substr(shortCodePos, mCursorPos - shortCodePos);
+ LLEmojiHelper::instance().showHelper(this, cursorRect.mLeft, cursorRect.mTop, wstring_to_utf8str(shortCode), std::bind(&LLTextEditor::handleEmojiCommit, this, std::placeholders::_1));
+ }
+ }
+
if (!mReadOnly && mAutoreplaceCallback != NULL)
{
// autoreplace the text, if necessary
@@ -1778,6 +1828,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 +1874,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;
diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h
index f3939248c2..f830732cb8 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,9 @@ public:
void setShowContextMenu(bool show) { mShowContextMenu = show; }
bool getShowContextMenu() const { return mShowContextMenu; }
+ void setShowEmojiHelper(bool show);
+ bool getShowEmojiHelper() const { return mShowEmojiHelper; }
+
void setPassDelete(BOOL b) { mPassDelete = b; }
protected:
@@ -318,6 +325,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.h b/indra/llui/lluictrl.h
index 67dd24341c..d71dc8d859 100644
--- a/indra/llui/lluictrl.h
+++ b/indra/llui/lluictrl.h
@@ -263,7 +263,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/llview.cpp b/indra/llui/llview.cpp
index 3344300635..8716e6b338 100644
--- a/indra/llui/llview.cpp
+++ b/indra/llui/llview.cpp
@@ -1326,7 +1326,7 @@ void LLView::drawDebugRect()
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);
+ S32_MAX, S32_MAX, NULL, /*use_ellipses*/FALSE, /*use_color*/FALSE);
}
}
LLUI::popMatrix();
@@ -1955,7 +1955,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()));
}
@@ -1979,7 +1979,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 8aa97aac39..29dd523648 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>
@@ -657,8 +657,8 @@ public:
// Draw debug rectangles around widgets to help with alignment and spacing
static bool sDebugRects;
- static bool sIsRectDirty;
- static LLRect sDirtyRect;
+ 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.
@@ -701,20 +701,16 @@ template <class T> T* LLView::getChild(const std::string& name, BOOL recurse) co
if (!result)
{
result = LLUICtrlFactory::getDefaultWidget<T>(name);
-
- 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
+ if (!result)
{
- 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>