diff options
Diffstat (limited to 'indra/llui')
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 ceceb90f0a..b93b92a2e7 100644 --- a/indra/llui/llbutton.cpp +++ b/indra/llui/llbutton.cpp @@ -69,6 +69,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"), @@ -161,6 +162,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), @@ -962,7 +964,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(); @@ -1022,6 +1024,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); @@ -1053,14 +1065,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 ab64440c19..5ab7f07ab8 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 d08084cdf5..5787bc5929 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -182,6 +182,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), @@ -254,6 +255,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), @@ -504,6 +506,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); @@ -681,7 +684,7 @@ void LLFloater::openFloater(const LLSD& key)  	if (getHost() != NULL)  	{  		getHost()->setMinimized(FALSE); -		getHost()->setVisibleAndFrontmost(mAutoFocus); +		getHost()->setVisibleAndFrontmost(mAutoFocus && !getIsChrome());  		getHost()->showFloater(this);  	}  	else @@ -693,7 +696,7 @@ void LLFloater::openFloater(const LLSD& key)  		}  		applyControlsAndPosition(floater_to_stack);  		setMinimized(FALSE); -		setVisibleAndFrontmost(mAutoFocus); +		setVisibleAndFrontmost(mAutoFocus && !getIsChrome());  	}  	mOpenSignal(this, key); @@ -829,6 +832,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); @@ -1117,9 +1138,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 @@ -1509,30 +1530,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);  	}  } @@ -1542,6 +1573,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] ) @@ -1630,6 +1699,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)) @@ -1644,12 +1714,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) @@ -2487,13 +2565,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 @@ -2851,10 +2934,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() )  	{ @@ -2896,29 +2986,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() @@ -3005,6 +3076,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) @@ -3040,7 +3114,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;  			}  		} @@ -3133,6 +3234,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) @@ -3225,6 +3334,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 9f9d950189..17dde69de7 100644 --- a/indra/llui/llfloater.h +++ b/indra/llui/llfloater.h @@ -117,8 +117,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> @@ -169,7 +167,8 @@ public:  								save_visibility,  								save_dock_state,  								can_dock, -								show_title; +								show_title, +								auto_close;  		Optional<LLFloaterEnums::EOpenPositioning>	positioning; @@ -242,6 +241,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(); @@ -260,10 +260,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. @@ -318,6 +319,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()); @@ -391,6 +395,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; } @@ -486,8 +491,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; @@ -507,6 +514,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; @@ -603,6 +611,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 6ae150dca5..de4fce86de 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -888,7 +888,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() @@ -997,7 +997,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);  	}  	//--------------------------------------------------------------------------------// @@ -1009,9 +1009,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          { @@ -1020,8 +1020,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; @@ -1030,7 +1031,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 06ece90e30..1928c0157e 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), @@ -154,6 +155,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 ), @@ -1759,6 +1761,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; @@ -2091,7 +2107,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, @@ -2127,7 +2143,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 5b19e044cc..40de9e155e 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,  										allow_emoji,  										use_bg_color; @@ -119,54 +120,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; @@ -174,27 +176,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(); } @@ -216,7 +219,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; } @@ -316,14 +319,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); @@ -400,6 +403,7 @@ protected:  	BOOL		mReadOnly;  	BOOL 		mShowImageFocused; +	BOOL 		mShowLabelFocused;  	bool		mAllowEmoji;  	bool		mUseBgColor; diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 10da3fb7e0..0803f0bd12 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -573,6 +573,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 0729c00946..d44be65841 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -914,7 +914,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); @@ -965,8 +965,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 bafeef41fb..cfaf08ec0a 100644 --- a/indra/llui/llsearcheditor.cpp +++ b/indra/llui/llsearcheditor.cpp @@ -178,6 +178,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 513cc56a02..a247e8700a 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 ef295976a2..4c47256519 100644 --- a/indra/llui/lluictrl.h +++ b/indra/llui/lluictrl.h @@ -265,7 +265,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 da7868d804..139eb17efa 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -60,6 +60,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; @@ -520,7 +521,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); @@ -1024,7 +1025,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;  			}  		}  	} @@ -1336,8 +1337,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(); @@ -1753,23 +1753,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; @@ -1780,13 +1783,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;  } @@ -1966,7 +1975,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()));  	} @@ -1990,7 +1999,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> | 
