Merge branch main into DRTVWR-489

master
Alexander Gavriliuk 2023-10-07 15:16:07 +02:00
commit e7f1466a7b
160 changed files with 5871 additions and 1178 deletions

2
.gitignore vendored
View File

@ -44,6 +44,7 @@ indra/newview/dbghelp.dll
indra/newview/filters.xml
indra/newview/fmod.dll
indra/newview/fmod.log
indra/newview/fonts
indra/newview/mozilla-theme
indra/newview/mozilla-universal-darwin.tgz
indra/newview/pilot.txt
@ -55,6 +56,7 @@ indra/newview/teleport_history.txt
indra/newview/typed_locations.txt
indra/newview/vivox-runtime
indra/newview/skins/default/html/common/equirectangular/js
emoji_characters.xml
indra/server-linux-*
indra/temp
indra/test/linden_file.dat

View File

@ -118,9 +118,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>fedc8d63856f534b6098102e059dc548</string>
<string>cae490999f05151e3a048b00d64237bb</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/87776/805857/boost-1.72-darwin64-563847.tar.bz2</string>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/109513/952781/boost-1.72-darwin64-577704.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
@ -142,9 +142,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>6cc9fb4ca21365c4470a3e516544ba71</string>
<string>aa0f955996442b8d7097447de1f5a9fe</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/87784/805850/boost-1.72-windows-563847.tar.bz2</string>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/109516/952793/boost-1.72-windows-577704.tar.bz2</string>
</map>
<key>name</key>
<string>windows</string>
@ -154,9 +154,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>0c526efc3f8825cd25cdf635e238fab3</string>
<string>d3738a7ea84e2569017ae0237526da36</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/87783/805851/boost-1.72-windows64-563847.tar.bz2</string>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/109515/952797/boost-1.72-windows64-577704.tar.bz2</string>
</map>
<key>name</key>
<string>windows64</string>
@ -236,33 +236,21 @@
<key>archive</key>
<map>
<key>hash</key>
<string>a880dfc15fcb330baf548a85324cd88a</string>
<string>cb76a5dcef6577fefff3f070029a4012</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/104133/913090/colladadom-2.3.574693-darwin64-574693.tar.bz2</string>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/115496/994482/colladadom-2.3.580380-darwin64-580380.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
</map>
<key>linux64</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>c90613240ba3e3a171d3379275ae4ee3</string>
<key>url</key>
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9695/45732/colladadom-2.3.509683-linux64-509683.tar.bz2</string>
</map>
<key>name</key>
<string>linux64</string>
</map>
<key>windows</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>7e84441d9c7cf019a7bdc7b818b16c27</string>
<string>31996b4f4bbf478298edcd28a6581f92</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/104112/912957/colladadom-2.3.574693-windows-574693.tar.bz2</string>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/115498/994495/colladadom-2.3.580380-windows-580380.tar.bz2</string>
</map>
<key>name</key>
<string>windows</string>
@ -272,16 +260,16 @@
<key>archive</key>
<map>
<key>hash</key>
<string>2eaffbb8a93b03a732d3c47055a8efcb</string>
<string>9b0de47cd59e1aa88617d751c40dcff7</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/104135/913103/colladadom-2.3.574693-windows64-574693.tar.bz2</string>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/115497/994489/colladadom-2.3.580380-windows64-580380.tar.bz2</string>
</map>
<key>name</key>
<string>windows64</string>
</map>
</map>
<key>version</key>
<string>2.3.574693</string>
<string>2.3.580380</string>
</map>
<key>cubemaptoequirectangular</key>
<map>
@ -527,6 +515,62 @@
<key>version</key>
<string>1.12.4.202209142021_91.1.21_g9dd45fe_chromium-91.0.4472.114</string>
</map>
<key>emoji_shortcodes</key>
<map>
<key>canonical_repo</key>
<string>https://github.com/secondlife/3p-emoji-shortcodes</string>
<key>copyright</key>
<string>Copyright 2017-2019 Miles Johnson.</string>
<key>description</key>
<string>Emoji shortcodes</string>
<key>license</key>
<string>MIT</string>
<key>license_file</key>
<string>LICENSES/emojibase-license.txt</string>
<key>name</key>
<string>emoji_shortcodes</string>
<key>platforms</key>
<map>
<key>darwin64</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>7ac35da9b1b5c9a05954edeef3fe8e54</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/113242/980233/emoji_shortcodes-6.1.0.579438-darwin64-579438.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
</map>
<key>windows</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>3c5c4c7e3bb2eeef61e605b6690a63f3</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/113244/980245/emoji_shortcodes-6.1.0.579438-windows-579438.tar.bz2</string>
</map>
<key>name</key>
<string>windows</string>
</map>
<key>windows64</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>087ce7e6d93dcd88b477b10d8e1ab259</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/113243/980244/emoji_shortcodes-6.1.0.579438-windows64-579438.tar.bz2</string>
</map>
<key>name</key>
<string>windows64</string>
</map>
</map>
<key>version</key>
<string>6.1.0.579438</string>
</map>
<key>expat</key>
<map>
<key>copyright</key>
@ -708,9 +752,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>8865739d8e530199dacb3c3042c1bc01</string>
<string>9cbcd452efa877fb8550c8c9b092b465</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/87777/805782/freetype-2.4.4.563848-darwin64-563848.tar.bz2</string>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/111566/967515/freetype-2.12.1.578664-darwin64-578664.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
@ -732,9 +776,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>c0b3601e997553931cadc7d7ee94168b</string>
<string>f3d7b72d10c162c9a8aa2ac4443ac52f</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/87779/805814/freetype-2.4.4.563848-windows-563848.tar.bz2</string>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/111571/967545/freetype-2.12.1.578664-windows-578664.tar.bz2</string>
</map>
<key>name</key>
<string>windows</string>
@ -744,16 +788,16 @@
<key>archive</key>
<map>
<key>hash</key>
<string>e98e1e088cdcd20442e05e9abecdadf9</string>
<string>96d3689043e939ae424060a8b498be1f</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/87778/805815/freetype-2.4.4.563848-windows64-563848.tar.bz2</string>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/111570/967535/freetype-2.12.1.578664-windows64-578664.tar.bz2</string>
</map>
<key>name</key>
<string>windows64</string>
</map>
</map>
<key>version</key>
<string>2.4.4.563848</string>
<string>2.12.1.578664</string>
</map>
<key>glext</key>
<map>
@ -870,9 +914,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>11d0794582e91a57f6524ad345f2399d</string>
<string>772d3ae9d4a1d3e36eadcfb4b81ca47f</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/87791/805924/googlemock-1.7.0.563853-darwin64-563853.tar.bz2</string>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/109950/956085/googlemock-1.7.0.577943-darwin64-577943.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
@ -894,9 +938,9 @@
<key>archive</key>
<map>
<key>hash</key>
<string>7d267050970ec6e28749178597bc8af0</string>
<string>61df276991917f710d27cb578d9df25b</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/87793/805930/googlemock-1.7.0.563853-windows-563853.tar.bz2</string>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/109953/956105/googlemock-1.7.0.577943-windows-577943.tar.bz2</string>
</map>
<key>name</key>
<string>windows</string>
@ -906,16 +950,16 @@
<key>archive</key>
<map>
<key>hash</key>
<string>27638c692f0ec6121e54bf75f2d45e49</string>
<string>cc1307e11133755eac29ab04ef240092</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/87792/805936/googlemock-1.7.0.563853-windows64-563853.tar.bz2</string>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/109952/956106/googlemock-1.7.0.577943-windows64-577943.tar.bz2</string>
</map>
<key>name</key>
<string>windows64</string>
</map>
</map>
<key>version</key>
<string>1.7.0.563853</string>
<string>1.7.0.577943</string>
</map>
<key>gstreamer</key>
<map>
@ -1039,6 +1083,62 @@
<key>version</key>
<string>2012.1-2</string>
</map>
<key>icu4c</key>
<map>
<key>canonical_repo</key>
<string>https://bitbucket.org/lindenlab/3p-icu4c</string>
<key>copyright</key>
<string>Copyright (c) 1995-2011 International Business Machines Corporation and others &lt;http://source.icu-project.org&gt;</string>
<key>description</key>
<string>ICU is a mature, widely used set of C/C++ and Java libraries providing Unicode and Globalization support for software applications. ICU is widely portable and gives applications the same results on all platforms and between C/C++ and Java software.</string>
<key>license</key>
<string>ICU, permissive non-copyleft free software license</string>
<key>license_file</key>
<string>LICENSES/icu.txt</string>
<key>name</key>
<string>icu4c</string>
<key>platforms</key>
<map>
<key>darwin64</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>acc2484ff03dfaf7652cac9f42ce198a</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/112891/977803/icu4c-4.8.1-darwin64-579276.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
</map>
<key>windows</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>d88003ff2c3c9145bc2c7d2d2f80f9b1</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/112892/977809/icu4c-4.8.1-windows-579276.tar.bz2</string>
</map>
<key>name</key>
<string>windows</string>
</map>
<key>windows64</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>2cdc400727294edcc144558d4f94fd00</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/112893/977815/icu4c-4.8.1-windows64-579276.tar.bz2</string>
</map>
<key>name</key>
<string>windows64</string>
</map>
</map>
<key>version</key>
<string>4.8.1</string>
</map>
<key>jpegencoderbasic</key>
<map>
<key>copyright</key>
@ -1948,6 +2048,74 @@
<key>version</key>
<string>3.0.2.569217</string>
</map>
<key>nanosvg</key>
<map>
<key>canonical_repo</key>
<string>https://bitbucket.org/lindenlab/3p-nanosvg</string>
<key>copyright</key>
<string>Copyright (c) 2013-14 Mikko Mononen</string>
<key>description</key>
<string>NanoSVG is a simple single-header-file SVG parser and rasterizer</string>
<key>license</key>
<string>Zlib</string>
<key>license_file</key>
<string>LICENSES/nanosvg.txt</string>
<key>name</key>
<string>nanosvg</string>
<key>platforms</key>
<map>
<key>darwin64</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>32ead724319c2ea6f65fc5be0e3157cc</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/115452/994130/nanosvg-2022.09.27-darwin64-580364.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
</map>
<key>linux</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>84698f044598ff79e255965f3d1c3e80</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/115397/993664/nanosvg-2022.09.27-linux-580337.tar.bz2</string>
</map>
<key>name</key>
<string>linux</string>
</map>
<key>windows</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>4e56202b09c026e08fc51116b22feabc</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/115456/994153/nanosvg-2022.09.27-windows-580364.tar.bz2</string>
</map>
<key>name</key>
<string>windows</string>
</map>
<key>windows64</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>ee61ff8b866be04c325f1fe2db516d71</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/115454/994144/nanosvg-2022.09.27-windows64-580364.tar.bz2</string>
</map>
<key>name</key>
<string>windows64</string>
</map>
</map>
<key>version</key>
<string>2022.09.27</string>
</map>
<key>nghttp2</key>
<map>
<key>copyright</key>
@ -2682,6 +2850,60 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>version</key>
<string>0.9.4</string>
</map>
<key>viewer-fonts</key>
<map>
<key>copyright</key>
<string>Copyright 2016-2022 Brad Erickson CC-BY-4.0/MIT, Copyright 2016-2022 Twitter, Inc. CC-BY-4.0, Copyright 2013 Joe Loughry and Terence Eden MIT</string>
<key>description</key>
<string>Viewer fonts</string>
<key>license</key>
<string>Various open source</string>
<key>license_file</key>
<string>LICENSES/fonts.txt</string>
<key>name</key>
<string>viewer-fonts</string>
<key>platforms</key>
<map>
<key>darwin64</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>6041bbd4001e3951f96ac3456c7906da</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/113314/980656/viewer_fonts-1.579464-darwin64-579464.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
</map>
<key>windows</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>19c48abc168a1c4e0f0ec6d471b2e1bd</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/113308/980632/viewer_fonts-1.579464-windows-579464.tar.bz2</string>
</map>
<key>name</key>
<string>windows</string>
</map>
<key>windows64</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>1745ba6eec0108250446fe01d4aa065c</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/113307/980631/viewer_fonts-1.579464-windows64-579464.tar.bz2</string>
</map>
<key>name</key>
<string>windows64</string>
</map>
</map>
<key>version</key>
<string>1.579464</string>
</map>
<key>viewer-manager</key>
<map>
<key>copyright</key>

View File

@ -238,6 +238,8 @@ Ansariel Hiller
SL-15398
SL-18432
SL-19140
SL-19575
SL-19623
SL-4126
Aralara Rajal
Arare Chantilly
@ -895,6 +897,7 @@ Kitty Barnett
STORM-2149
MAINT-7581
MAINT-7081
DRTVWR-489 (Internal JIRA that tracks Kitty's sizeable, epic contribution: support for Emoji characters in the Viewer [April 2023])
SL-18988
Kolor Fall
Komiko Okamoto

View File

@ -30,6 +30,7 @@ set(cmake_SOURCE_FILES
GoogleMock.cmake
Havok.cmake
Hunspell.cmake
ICU4C.cmake
JsonCpp.cmake
LLAddBuildTest.cmake
LLAppearance.cmake

View File

@ -63,6 +63,15 @@ if(WINDOWS)
uriparser.dll
)
# ICU4C (same filenames for 32 and 64 bit builds)
set(release_files ${release_files} icudt48.dll)
set(release_files ${release_files} icuin48.dll)
set(release_files ${release_files} icuio48.dll)
set(release_files ${release_files} icule48.dll)
set(release_files ${release_files} iculx48.dll)
set(release_files ${release_files} icutu48.dll)
set(release_files ${release_files} icuuc48.dll)
# OpenSSL
if(ADDRESS_SIZE EQUAL 64)
set(release_files ${release_files} libcrypto-1_1-x64.dll)

23
indra/cmake/ICU4C.cmake Normal file
View File

@ -0,0 +1,23 @@
# -*- cmake -*-
include(Prebuilt)
include_guard()
add_library( ll::icu4c INTERFACE IMPORTED )
use_system_binary(icu4c)
use_prebuilt_binary(icu4c)
if (WINDOWS)
target_link_libraries( ll::icu4c INTERFACE icuuc)
elseif(DARWIN)
target_link_libraries( ll::icu4c INTERFACE icuuc)
#elseif(LINUX)
## target_link_libraries( ll::icu4c INTERFACE )
else()
message(FATAL_ERROR "Invalid platform")
endif()
target_include_directories( ll::icu4c SYSTEM INTERFACE ${LIBS_PREBUILT_DIR}/include/unicode )
use_prebuilt_binary(dictionaries)

View File

@ -18,3 +18,6 @@ endif()
use_prebuilt_binary(slvoice)
use_prebuilt_binary(nanosvg)
use_prebuilt_binary(viewer-fonts)
use_prebuilt_binary(emoji_shortcodes)

View File

@ -37,7 +37,7 @@ class LLWearableType : public LLParamSingleton<LLWearableType>
{
LLSINGLETON(LLWearableType, LLTranslationBridge::ptr_t &trans);
~LLWearableType();
void initSingleton();
void initSingleton() override;
public:
enum EType
{

View File

@ -3,6 +3,7 @@
project(llcommon)
include(00-Common)
include(ICU4C)
include(LLCommon)
include(bugsplat)
include(Linking)
@ -272,6 +273,7 @@ target_link_libraries(
ll::uriparser
ll::oslibraries
ll::tracy
ll::icu4c
)
target_include_directories(llcommon INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -92,7 +92,7 @@ class LL_COMMON_API LLCoros: public LLSingleton<LLCoros>
LLSINGLETON(LLCoros);
~LLCoros();
void cleanupSingleton();
void cleanupSingleton() override;
public:
/// The viewer's use of the term "coroutine" became deeply embedded before
/// the industry term "fiber" emerged to distinguish userland threads from

View File

@ -802,7 +802,7 @@ public:
private: \
/* implement LLSingleton pure virtual method whose sole purpose */ \
/* is to remind people to use this macro */ \
virtual void you_must_use_LLSINGLETON_macro() {} \
virtual void you_must_use_LLSINGLETON_macro() override {} \
friend class LLSingleton<DERIVED_CLASS>; \
DERIVED_CLASS(__VA_ARGS__)

View File

@ -30,6 +30,7 @@
#include "llerror.h"
#include "llfasttimer.h"
#include "llsd.h"
#include <unicode/uchar.h>
#include <vector>
#if LL_WINDOWS
@ -833,6 +834,40 @@ std::string LLStringOps::sDayFormat;
std::string LLStringOps::sAM;
std::string LLStringOps::sPM;
// static
bool LLStringOps::isEmoji(llwchar wch)
{
int ublock = ublock_getCode(wch);
switch (ublock)
{
case UBLOCK_GENERAL_PUNCTUATION:
case UBLOCK_LETTERLIKE_SYMBOLS:
case UBLOCK_ARROWS:
case UBLOCK_MISCELLANEOUS_TECHNICAL:
case UBLOCK_ENCLOSED_ALPHANUMERICS:
case UBLOCK_GEOMETRIC_SHAPES:
case UBLOCK_MISCELLANEOUS_SYMBOLS:
case UBLOCK_DINGBATS:
case UBLOCK_CJK_SYMBOLS_AND_PUNCTUATION:
case UBLOCK_ENCLOSED_CJK_LETTERS_AND_MONTHS:
case UBLOCK_MISCELLANEOUS_SYMBOLS_AND_PICTOGRAPHS:
case UBLOCK_EMOTICONS:
case UBLOCK_TRANSPORT_AND_MAP_SYMBOLS:
#if U_ICU_VERSION_MAJOR_NUM > 56
// Boost uses ICU so we can't update it independently
case UBLOCK_SUPPLEMENTAL_SYMBOLS_AND_PICTOGRAPHS:
#endif // U_ICU_VERSION_MAJOR_NUM > 56
return true;
default:
#if U_ICU_VERSION_MAJOR_NUM > 56
return false;
#else
// See https://en.wikipedia.org/wiki/Supplemental_Symbols_and_Pictographs
return wch >= 0x1F900 && wch <= 0x1F9FF;
#endif // U_ICU_VERSION_MAJOR_NUM > 56
}
}
S32 LLStringOps::collate(const llwchar* a, const llwchar* b)
{

View File

@ -189,6 +189,8 @@ public:
static bool isAlnum(char a) { return isalnum((unsigned char)a) != 0; }
static bool isAlnum(llwchar a) { return iswalnum(a) != 0; }
static bool isEmoji(llwchar wch);
static S32 collate(const char* a, const char* b) { return strcoll(a, b); }
static S32 collate(const llwchar* a, const llwchar* b);
@ -355,6 +357,8 @@ public:
static void replaceNonstandardASCII( string_type& string, T replacement );
static void replaceChar( string_type& string, T target, T replacement );
static void replaceString( string_type& string, string_type target, string_type replacement );
static string_type capitalize(const string_type& str);
static void capitalize(string_type& str);
static BOOL containsNonprintable(const string_type& string);
static void stripNonprintable(string_type& string);
@ -1593,6 +1597,29 @@ void LLStringUtilBase<T>::replaceTabsWithSpaces( string_type& str, size_type spa
str = out_str;
}
//static
template<class T>
std::basic_string<T> LLStringUtilBase<T>::capitalize(const string_type& str)
{
string_type result(str);
capitalize(result);
return result;
}
//static
template<class T>
void LLStringUtilBase<T>::capitalize(string_type& str)
{
if (str.size())
{
auto last = str[0] = toupper(str[0]);
for (U32 i = 1; i < str.size(); ++i)
{
last = (last == ' ' || last == '-' || last == '_') ? str[i] = toupper(str[i]) : str[i];
}
}
}
//static
template<class T>
BOOL LLStringUtilBase<T>::containsNonprintable(const string_type& string)

View File

@ -47,8 +47,8 @@ public: \
DEP_INIT /* dependency in initSingleton */ \
} sDepFlag; \
\
void initSingleton(); \
void cleanupSingleton(); \
void initSingleton() override; \
void cleanupSingleton() override; \
}; \
\
CLS::dep_flag CLS::sDepFlag = DEP_NONE
@ -300,7 +300,7 @@ namespace tut
{
LLSINGLETON_EMPTY_CTOR(CircularPInit);
public:
virtual void initSingleton()
virtual void initSingleton() override
{
// never mind indirection, just go straight for the circularity
CircularPInit *pt = getInstance();

View File

@ -60,7 +60,7 @@ class LLFolderDictionary : public LLSingleton<LLFolderDictionary>,
{
LLSINGLETON(LLFolderDictionary);
protected:
virtual LLFolderType::EType notFound() const
virtual LLFolderType::EType notFound() const override
{
return LLFolderType::FT_NONE;
}

View File

@ -62,7 +62,7 @@ class LLSettingsDictionary : public LLSingleton<LLSettingsDictionary>,
{
LLSINGLETON(LLSettingsDictionary);
void initSingleton();
void initSingleton() override;
};
LLSettingsDictionary::LLSettingsDictionary()

View File

@ -5482,14 +5482,12 @@ bool LLVolumeFace::cacheOptimize()
new_indices.push_back(tri->mVertex[2]->mIdx);
tri->complete();
//U32 breaks = 0;
for (U32 i = 1; i < mNumIndices/3; ++i)
{
cache.updateScores();
tri = cache.mBestTriangle;
if (!tri)
{
//breaks++;
for (U32 j = 0; j < triangle_data.size(); ++j)
{
if (triangle_data[j].mActive)

View File

@ -106,7 +106,7 @@ public:
private:
virtual ~LLExperienceCache();
virtual void initSingleton();
virtual void initSingleton() override;
typedef boost::function<LLSD(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &, LLCore::HttpRequest::ptr_t, std::string)> permissionInvoker_fn;

View File

@ -226,7 +226,7 @@ class LLProxy: public LLSingleton<LLProxy>
LLSINGLETON(LLProxy);
LOG_CLASS(LLProxy);
/*virtual*/ void initSingleton();
/*virtual*/ void initSingleton() override;
public:
// Static check for enabled status for UDP packets. Call from main thread only.

View File

@ -14,6 +14,7 @@ set(llrender_SOURCE_FILES
llcubemap.cpp
llfontbitmapcache.cpp
llfontfreetype.cpp
llfontfreetypesvg.cpp
llfontgl.cpp
llfontregistry.cpp
llgl.cpp
@ -42,6 +43,7 @@ set(llrender_HEADER_FILES
llcubemap.h
llfontgl.h
llfontfreetype.h
llfontfreetypesvg.h
llfontbitmapcache.h
llfontregistry.h
llgl.h

View File

@ -30,14 +30,7 @@
#include "llfontbitmapcache.h"
LLFontBitmapCache::LLFontBitmapCache()
: mNumComponents(0),
mBitmapWidth(0),
mBitmapHeight(0),
mBitmapNum(-1),
mMaxCharWidth(0),
mMaxCharHeight(0),
mCurrentOffsetX(1),
mCurrentOffsetY(1)
{
}
@ -45,121 +38,149 @@ LLFontBitmapCache::~LLFontBitmapCache()
{
}
void LLFontBitmapCache::init(S32 num_components,
S32 max_char_width,
void LLFontBitmapCache::init(S32 max_char_width,
S32 max_char_height)
{
reset();
mNumComponents = num_components;
mMaxCharWidth = max_char_width;
mMaxCharHeight = max_char_height;
}
LLImageRaw *LLFontBitmapCache::getImageRaw(U32 bitmap_num) const
{
if (bitmap_num >= mImageRawVec.size())
return NULL;
return mImageRawVec[bitmap_num];
}
LLImageGL *LLFontBitmapCache::getImageGL(U32 bitmap_num) const
{
if (bitmap_num >= mImageGLVec.size())
return NULL;
return mImageGLVec[bitmap_num];
}
BOOL LLFontBitmapCache::nextOpenPos(S32 width, S32 &pos_x, S32 &pos_y, S32& bitmap_num)
{
if ((mBitmapNum<0) || (mCurrentOffsetX + width + 1) > mBitmapWidth)
S32 image_width = mMaxCharWidth * 20;
S32 pow_iw = 2;
while (pow_iw < image_width)
{
if ((mBitmapNum<0) || (mCurrentOffsetY + 2*mMaxCharHeight + 2) > mBitmapHeight)
pow_iw <<= 1;
}
image_width = pow_iw;
image_width = llmin(512, image_width); // Don't make bigger than 512x512, ever.
mBitmapWidth = image_width;
mBitmapHeight = image_width;
}
LLImageRaw *LLFontBitmapCache::getImageRaw(EFontGlyphType bitmap_type, U32 bitmap_num) const
{
const U32 bitmap_idx = static_cast<U32>(bitmap_type);
if (bitmap_type >= EFontGlyphType::Count || bitmap_num >= mImageRawVec[bitmap_idx].size())
return nullptr;
return mImageRawVec[bitmap_idx][bitmap_num];
}
LLImageGL *LLFontBitmapCache::getImageGL(EFontGlyphType bitmap_type, U32 bitmap_num) const
{
const U32 bitmap_idx = static_cast<U32>(bitmap_type);
if (bitmap_type >= EFontGlyphType::Count || bitmap_num >= mImageGLVec[bitmap_idx].size())
return nullptr;
return mImageGLVec[bitmap_idx][bitmap_num];
}
BOOL LLFontBitmapCache::nextOpenPos(S32 width, S32& pos_x, S32& pos_y, EFontGlyphType bitmap_type, U32& bitmap_num)
{
if (bitmap_type >= EFontGlyphType::Count)
{
return FALSE;
}
const U32 bitmap_idx = static_cast<U32>(bitmap_type);
if (mImageRawVec[bitmap_idx].empty() || (mCurrentOffsetX[bitmap_idx] + width + 1) > mBitmapWidth)
{
if ((mImageRawVec[bitmap_idx].empty()) || (mCurrentOffsetY[bitmap_idx] + 2*mMaxCharHeight + 2) > mBitmapHeight)
{
// We're out of space in the current image, or no image
// has been allocated yet. Make a new one.
mImageRawVec.push_back(new LLImageRaw);
mBitmapNum = mImageRawVec.size()-1;
LLImageRaw *image_raw = getImageRaw(mBitmapNum);
S32 image_width = mMaxCharWidth * 20;
S32 pow_iw = 2;
while (pow_iw < image_width)
{
pow_iw *= 2;
}
image_width = pow_iw;
image_width = llmin(512, image_width); // Don't make bigger than 512x512, ever.
S32 image_height = image_width;
mBitmapWidth = image_width;
mBitmapHeight = image_height;
S32 num_components = getNumComponents(bitmap_type);
mImageRawVec[bitmap_idx].push_back(new LLImageRaw(mBitmapWidth, mBitmapHeight, num_components));
bitmap_num = mImageRawVec[bitmap_idx].size() - 1;
LLImageRaw* image_raw = getImageRaw(bitmap_type, bitmap_num);
if (EFontGlyphType::Grayscale == bitmap_type)
{
image_raw->clear(255, 0);
}
// Make corresponding GL image.
mImageGLVec.push_back(new LLImageGL(FALSE));
LLImageGL *image_gl = getImageGL(mBitmapNum);
S32 image_width = mMaxCharWidth * 20;
S32 pow_iw = 2;
while (pow_iw < image_width)
{
pow_iw *= 2;
}
image_width = pow_iw;
image_width = llmin(512, image_width); // Don't make bigger than 512x512, ever.
S32 image_height = image_width;
image_raw->resize(image_width, image_height, mNumComponents);
mBitmapWidth = image_width;
mBitmapHeight = image_height;
switch (mNumComponents)
{
case 1:
image_raw->clear();
break;
case 2:
image_raw->clear(255, 0);
break;
}
mImageGLVec[bitmap_idx].push_back(new LLImageGL(image_raw, false));
LLImageGL* image_gl = getImageGL(bitmap_type, bitmap_num);
// Start at beginning of the new image.
mCurrentOffsetX = 1;
mCurrentOffsetY = 1;
mCurrentOffsetX[bitmap_idx] = 1;
mCurrentOffsetY[bitmap_idx] = 1;
// Attach corresponding GL texture.
image_gl->createGLTexture(0, image_raw);
// Attach corresponding GL texture. (*TODO: is this needed?)
gGL.getTexUnit(0)->bind(image_gl);
image_gl->setFilteringOption(LLTexUnit::TFO_POINT); // was setMipFilterNearest(TRUE, TRUE);
}
else
{
// Move to next row in current image.
mCurrentOffsetX = 1;
mCurrentOffsetY += mMaxCharHeight + 1;
mCurrentOffsetX[bitmap_idx] = 1;
mCurrentOffsetY[bitmap_idx] += mMaxCharHeight + 1;
}
}
pos_x = mCurrentOffsetX;
pos_y = mCurrentOffsetY;
bitmap_num = mBitmapNum;
pos_x = mCurrentOffsetX[bitmap_idx];
pos_y = mCurrentOffsetY[bitmap_idx];
bitmap_num = getNumBitmaps(bitmap_type) - 1;
mCurrentOffsetX += width + 1;
mCurrentOffsetX[bitmap_idx] += width + 1;
return TRUE;
}
void LLFontBitmapCache::destroyGL()
{
for (std::vector<LLPointer<LLImageGL> >::iterator it = mImageGLVec.begin();
it != mImageGLVec.end(); ++it)
for (U32 idx = 0, cnt = static_cast<U32>(EFontGlyphType::Count); idx < cnt; idx++)
{
(*it)->destroyGLTexture();
for (LLImageGL* image_gl : mImageGLVec[idx])
{
image_gl->destroyGLTexture();
}
}
}
void LLFontBitmapCache::reset()
{
mImageRawVec.clear();
mImageGLVec.clear();
for (U32 idx = 0, cnt = static_cast<U32>(EFontGlyphType::Count); idx < cnt; idx++)
{
mImageRawVec[idx].clear();
mImageGLVec[idx].clear();
mCurrentOffsetX[idx] = 1;
mCurrentOffsetY[idx] = 1;
}
mBitmapWidth = 0;
mBitmapHeight = 0;
mBitmapNum = -1;
mCurrentOffsetX = 1;
mCurrentOffsetY = 1;
}
//static
U32 LLFontBitmapCache::getNumComponents(EFontGlyphType bitmap_type)
{
switch (bitmap_type)
{
case EFontGlyphType::Grayscale:
return 2;
case EFontGlyphType::Color:
return 4;
default:
llassert(false);
return 2;
}
}

View File

@ -30,6 +30,14 @@
#include <vector>
#include "lltrace.h"
enum class EFontGlyphType : U32
{
Grayscale = 0,
Color,
Count,
Unspecified,
};
// Maintain a collection of bitmaps containing rendered glyphs.
// Generalizes the single-bitmap logic from LLFontFreetype and LLFontGL.
class LLFontBitmapCache
@ -39,35 +47,35 @@ public:
~LLFontBitmapCache();
// Need to call this once, before caching any glyphs.
void init(S32 num_components,
S32 max_char_width,
void init(S32 max_char_width,
S32 max_char_height);
void reset();
BOOL nextOpenPos(S32 width, S32 &posX, S32 &posY, S32 &bitmapNum);
BOOL nextOpenPos(S32 width, S32& posX, S32& posY, EFontGlyphType bitmapType, U32& bitmapNum);
void destroyGL();
LLImageRaw *getImageRaw(U32 bitmapNum = 0) const;
LLImageGL *getImageGL(U32 bitmapNum = 0) const;
LLImageRaw* getImageRaw(EFontGlyphType bitmapType, U32 bitmapNum) const;
LLImageGL* getImageGL(EFontGlyphType bitmapType, U32 bitmapNum) const;
S32 getMaxCharWidth() const { return mMaxCharWidth; }
S32 getNumComponents() const { return mNumComponents; }
U32 getNumBitmaps(EFontGlyphType bitmapType) const { return (bitmapType < EFontGlyphType::Count) ? mImageRawVec[static_cast<U32>(bitmapType)].size() : 0; }
S32 getBitmapWidth() const { return mBitmapWidth; }
S32 getBitmapHeight() const { return mBitmapHeight; }
protected:
static U32 getNumComponents(EFontGlyphType bitmap_type);
private:
S32 mNumComponents;
S32 mBitmapWidth;
S32 mBitmapHeight;
S32 mBitmapNum;
S32 mMaxCharWidth;
S32 mMaxCharHeight;
S32 mCurrentOffsetX;
S32 mCurrentOffsetY;
std::vector<LLPointer<LLImageRaw> > mImageRawVec;
std::vector<LLPointer<LLImageGL> > mImageGLVec;
S32 mBitmapWidth = 0;
S32 mBitmapHeight = 0;
S32 mCurrentOffsetX[static_cast<U32>(EFontGlyphType::Count)] = { 1 };
S32 mCurrentOffsetY[static_cast<U32>(EFontGlyphType::Count)] = { 1 };
S32 mMaxCharWidth = 0;
S32 mMaxCharHeight = 0;
std::vector<LLPointer<LLImageRaw>> mImageRawVec[static_cast<U32>(EFontGlyphType::Count)];
std::vector<LLPointer<LLImageGL>> mImageGLVec[static_cast<U32>(EFontGlyphType::Count)];
};
#endif //LL_LLFONTBITMAPCACHE_H

View File

@ -34,14 +34,17 @@
#ifdef LL_WINDOWS
#include <freetype2\freetype\ftsystem.h>
#endif
#include "llfontfreetypesvg.h"
// For some reason, this won't work if it's not wrapped in the ifdef
#ifdef FT_FREETYPE_H
#include FT_FREETYPE_H
#endif
#include "lldir.h"
#include "llerror.h"
#include "llimage.h"
#include "llimagepng.h"
//#include "llimagej2c.h"
#include "llmath.h" // Linden math
#include "llstring.h"
@ -49,6 +52,8 @@
#include "llfontbitmapcache.h"
#include "llgl.h"
#define ENABLE_OT_SVG_SUPPORT
FT_Render_Mode gFontRenderMode = FT_RENDER_MODE_NORMAL;
LLFontManager *gFontManagerp = NULL;
@ -81,6 +86,16 @@ LLFontManager::LLFontManager()
LL_ERRS() << "Freetype initialization failure!" << LL_ENDL;
FT_Done_FreeType(gFTLibrary);
}
#ifdef ENABLE_OT_SVG_SUPPORT
SVG_RendererHooks hooks = {
LLFontFreeTypeSvgRenderer::OnInit,
LLFontFreeTypeSvgRenderer::OnFree,
LLFontFreeTypeSvgRenderer::OnRender,
LLFontFreeTypeSvgRenderer::OnPresetGlypthSlot,
};
FT_Property_Set(gFTLibrary, "ot-svg", "svg-hooks", &hooks);
#endif
}
LLFontManager::~LLFontManager()
@ -89,8 +104,9 @@ LLFontManager::~LLFontManager()
}
LLFontGlyphInfo::LLFontGlyphInfo(U32 index)
LLFontGlyphInfo::LLFontGlyphInfo(U32 index, EFontGlyphType glyph_type)
: mGlyphIndex(index),
mGlyphType(glyph_type),
mWidth(0), // In pixels
mHeight(0), // In pixels
mXAdvance(0.f), // In pixels
@ -99,10 +115,25 @@ LLFontGlyphInfo::LLFontGlyphInfo(U32 index)
mYBitmapOffset(0), // Offset to the origin in the bitmap
mXBearing(0), // Distance from baseline to left in pixels
mYBearing(0), // Distance from baseline to top in pixels
mBitmapNum(0) // Which bitmap in the bitmap cache contains this glyph
mBitmapEntry(std::make_pair(EFontGlyphType::Unspecified, -1)) // Which bitmap in the bitmap cache contains this glyph
{
}
LLFontGlyphInfo::LLFontGlyphInfo(const LLFontGlyphInfo& fgi)
: mGlyphIndex(fgi.mGlyphIndex)
, mGlyphType(fgi.mGlyphType)
, mWidth(fgi.mWidth)
, mHeight(fgi.mHeight)
, mXAdvance(fgi.mXAdvance)
, mYAdvance(fgi.mYAdvance)
, mXBitmapOffset(fgi.mXBitmapOffset)
, mYBitmapOffset(fgi.mYBitmapOffset)
, mXBearing(fgi.mXBearing)
, mYBearing(fgi.mYBearing)
{
mBitmapEntry = fgi.mBitmapEntry;
}
LLFontFreetype::LLFontFreetype()
: mFontBitmapCachep(new LLFontBitmapCache),
mAscender(0.f),
@ -156,7 +187,7 @@ void ft_close_cb(FT_Stream stream) {
}
#endif
BOOL LLFontFreetype::loadFace(const std::string& filename, F32 point_size, F32 vert_dpi, F32 horz_dpi, S32 components, BOOL is_fallback, S32 face_n)
BOOL LLFontFreetype::loadFace(const std::string& filename, F32 point_size, F32 vert_dpi, F32 horz_dpi, bool is_fallback, S32 face_n)
{
// Don't leak face objects. This is also needed to deal with
// changed font file names.
@ -220,7 +251,7 @@ BOOL LLFontFreetype::loadFace(const std::string& filename, F32 point_size, F32 v
S32 max_char_width = ll_round(0.5f + (x_max - x_min));
S32 max_char_height = ll_round(0.5f + (y_max - y_min));
mFontBitmapCachep->init(components, max_char_width, max_char_height);
mFontBitmapCachep->init(max_char_width, max_char_height);
if (!mFTFace->charmap)
{
@ -231,7 +262,7 @@ BOOL LLFontFreetype::loadFace(const std::string& filename, F32 point_size, F32 v
if (!mIsFallback)
{
// Add the default glyph
addGlyphFromFont(this, 0, 0);
addGlyphFromFont(this, 0, 0, EFontGlyphType::Grayscale);
}
mName = filename;
@ -323,14 +354,11 @@ void LLFontFreetype::clearFontStreams()
}
#endif
void LLFontFreetype::setFallbackFonts(const font_vector_t &font)
void LLFontFreetype::addFallbackFont(const LLPointer<LLFontFreetype>& fallback_font, const char_functor_t& functor)
{
mFallbackFonts = font;
}
const LLFontFreetype::font_vector_t &LLFontFreetype::getFallbackFonts() const
{
return mFallbackFonts;
// Insert functor fallbacks before generic fallbacks
mFallbackFonts.insert((functor) ? std::find_if(mFallbackFonts.begin(), mFallbackFonts.end(), [](const fallback_font_t& fe) { return !fe.second; }) : mFallbackFonts.end(),
std::make_pair(fallback_font, functor));
}
F32 LLFontFreetype::getLineHeight() const
@ -354,7 +382,7 @@ F32 LLFontFreetype::getXAdvance(llwchar wch) const
return 0.0;
// Return existing info only if it is current
LLFontGlyphInfo* gi = getGlyphInfo(wch);
LLFontGlyphInfo* gi = getGlyphInfo(wch, EFontGlyphType::Unspecified);
if (gi)
{
return gi->mXAdvance;
@ -386,10 +414,10 @@ F32 LLFontFreetype::getXKerning(llwchar char_left, llwchar char_right) const
return 0.0;
//llassert(!mIsFallback);
LLFontGlyphInfo* left_glyph_info = getGlyphInfo(char_left);;
LLFontGlyphInfo* left_glyph_info = getGlyphInfo(char_left, EFontGlyphType::Unspecified);;
U32 left_glyph = left_glyph_info ? left_glyph_info->mGlyphIndex : 0;
// Kern this puppy.
LLFontGlyphInfo* right_glyph_info = getGlyphInfo(char_right);
LLFontGlyphInfo* right_glyph_info = getGlyphInfo(char_right, EFontGlyphType::Unspecified);
U32 right_glyph = right_glyph_info ? right_glyph_info->mGlyphIndex : 0;
FT_Vector delta;
@ -420,60 +448,94 @@ BOOL LLFontFreetype::hasGlyph(llwchar wch) const
return(mCharGlyphInfoMap.find(wch) != mCharGlyphInfoMap.end());
}
LLFontGlyphInfo* LLFontFreetype::addGlyph(llwchar wch) const
LLFontGlyphInfo* LLFontFreetype::addGlyph(llwchar wch, EFontGlyphType glyph_type) const
{
if (mFTFace == NULL)
return FALSE;
llassert(!mIsFallback);
llassert(glyph_type < EFontGlyphType::Count);
//LL_DEBUGS() << "Adding new glyph for " << wch << " to font" << LL_ENDL;
FT_UInt glyph_index;
// Fallback fonts with a functor have precedence over everything else
fallback_font_vector_t::const_iterator it_fallback = mFallbackFonts.cbegin();
/* This leads to a bug SL-19831 "Check marks in the menu are less visible."
** Also, LLFontRegistry::createFont() says: "Fallback fonts don't render"
for (; it_fallback != mFallbackFonts.cend() && it_fallback->second; ++it_fallback)
{
if (it_fallback->second(wch))
{
glyph_index = FT_Get_Char_Index(it_fallback->first->mFTFace, wch);
if (glyph_index)
{
return addGlyphFromFont(it_fallback->first, wch, glyph_index, glyph_type);
}
}
}
*/
// Initialize char to glyph map
glyph_index = FT_Get_Char_Index(mFTFace, wch);
if (glyph_index == 0)
{
//LL_INFOS() << "Trying to add glyph from fallback font!" << LL_ENDL;
font_vector_t::const_iterator iter;
for(iter = mFallbackFonts.begin(); iter != mFallbackFonts.end(); iter++)
for (; it_fallback != mFallbackFonts.cend(); ++it_fallback)
{
glyph_index = FT_Get_Char_Index((*iter)->mFTFace, wch);
glyph_index = FT_Get_Char_Index(it_fallback->first->mFTFace, wch);
if (glyph_index)
{
return addGlyphFromFont(*iter, wch, glyph_index);
return addGlyphFromFont(it_fallback->first, wch, glyph_index, glyph_type);
}
}
}
char_glyph_info_map_t::iterator iter = mCharGlyphInfoMap.find(wch);
if (iter == mCharGlyphInfoMap.end())
std::pair<char_glyph_info_map_t::iterator, char_glyph_info_map_t::iterator> range_it = mCharGlyphInfoMap.equal_range(wch);
char_glyph_info_map_t::iterator iter =
std::find_if(range_it.first, range_it.second, [&glyph_type](const char_glyph_info_map_t::value_type& entry) { return entry.second->mGlyphType == glyph_type; });
if (iter == range_it.second)
{
return addGlyphFromFont(this, wch, glyph_index);
return addGlyphFromFont(this, wch, glyph_index, glyph_type);
}
return NULL;
}
LLFontGlyphInfo* LLFontFreetype::addGlyphFromFont(const LLFontFreetype *fontp, llwchar wch, U32 glyph_index) const
LLFontGlyphInfo* LLFontFreetype::addGlyphFromFont(const LLFontFreetype *fontp, llwchar wch, U32 glyph_index, EFontGlyphType requested_glyph_type) const
{
LL_PROFILE_ZONE_SCOPED;
if (mFTFace == NULL)
return NULL;
llassert(!mIsFallback);
fontp->renderGlyph(glyph_index);
fontp->renderGlyph(requested_glyph_type, glyph_index);
EFontGlyphType bitmap_glyph_type = EFontGlyphType::Unspecified;
switch (fontp->mFTFace->glyph->bitmap.pixel_mode)
{
case FT_PIXEL_MODE_MONO:
case FT_PIXEL_MODE_GRAY:
bitmap_glyph_type = EFontGlyphType::Grayscale;
break;
case FT_PIXEL_MODE_BGRA:
bitmap_glyph_type = EFontGlyphType::Color;
break;
default:
llassert_always(true);
break;
}
S32 width = fontp->mFTFace->glyph->bitmap.width;
S32 height = fontp->mFTFace->glyph->bitmap.rows;
S32 pos_x, pos_y;
S32 bitmap_num;
mFontBitmapCachep->nextOpenPos(width, pos_x, pos_y, bitmap_num);
U32 bitmap_num;
mFontBitmapCachep->nextOpenPos(width, pos_x, pos_y, bitmap_glyph_type, bitmap_num);
mAddGlyphCount++;
LLFontGlyphInfo* gi = new LLFontGlyphInfo(glyph_index);
LLFontGlyphInfo* gi = new LLFontGlyphInfo(glyph_index, requested_glyph_type);
gi->mXBitmapOffset = pos_x;
gi->mYBitmapOffset = pos_y;
gi->mBitmapNum = bitmap_num;
gi->mBitmapEntry = std::make_pair(bitmap_glyph_type, bitmap_num);
gi->mWidth = width;
gi->mHeight = height;
gi->mXBearing = fontp->mFTFace->glyph->bitmap_left;
@ -484,8 +546,12 @@ LLFontGlyphInfo* LLFontFreetype::addGlyphFromFont(const LLFontFreetype *fontp, l
insertGlyphInfo(wch, gi);
llassert(fontp->mFTFace->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO
|| fontp->mFTFace->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY);
if (requested_glyph_type != bitmap_glyph_type)
{
LLFontGlyphInfo* gi_temp = new LLFontGlyphInfo(*gi);
gi_temp->mGlyphType = bitmap_glyph_type;
insertGlyphInfo(wch, gi_temp);
}
if (fontp->mFTFace->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO
|| fontp->mFTFace->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY)
@ -520,78 +586,86 @@ LLFontGlyphInfo* LLFontFreetype::addGlyphFromFont(const LLFontFreetype *fontp, l
buffer_row_stride = width;
}
switch (mFontBitmapCachep->getNumComponents())
{
case 1:
mFontBitmapCachep->getImageRaw(bitmap_num)->setSubImage(pos_x,
pos_y,
width,
height,
buffer_data,
buffer_row_stride,
TRUE);
break;
case 2:
setSubImageLuminanceAlpha(pos_x,
pos_y,
bitmap_num,
width,
height,
buffer_data,
buffer_row_stride);
break;
default:
break;
}
setSubImageLuminanceAlpha(pos_x,
pos_y,
bitmap_num,
width,
height,
buffer_data,
buffer_row_stride);
if (tmp_graydata)
delete[] tmp_graydata;
}
else if (fontp->mFTFace->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA)
{
setSubImageBGRA(pos_x,
pos_y,
bitmap_num,
fontp->mFTFace->glyph->bitmap.width,
fontp->mFTFace->glyph->bitmap.rows,
fontp->mFTFace->glyph->bitmap.buffer,
llabs(fontp->mFTFace->glyph->bitmap.pitch));
} else {
// we don't know how to handle this pixel format from FreeType;
// omit it from the font-image.
llassert(false);
}
LLImageGL *image_gl = mFontBitmapCachep->getImageGL(bitmap_num);
LLImageRaw *image_raw = mFontBitmapCachep->getImageRaw(bitmap_num);
LLImageGL *image_gl = mFontBitmapCachep->getImageGL(bitmap_glyph_type, bitmap_num);
LLImageRaw *image_raw = mFontBitmapCachep->getImageRaw(bitmap_glyph_type, bitmap_num);
image_gl->setSubImage(image_raw, 0, 0, image_gl->getWidth(), image_gl->getHeight());
return gi;
}
LLFontGlyphInfo* LLFontFreetype::getGlyphInfo(llwchar wch) const
LLFontGlyphInfo* LLFontFreetype::getGlyphInfo(llwchar wch, EFontGlyphType glyph_type) const
{
char_glyph_info_map_t::iterator iter = mCharGlyphInfoMap.find(wch);
if (iter != mCharGlyphInfoMap.end())
std::pair<char_glyph_info_map_t::iterator, char_glyph_info_map_t::iterator> range_it = mCharGlyphInfoMap.equal_range(wch);
char_glyph_info_map_t::iterator iter = (EFontGlyphType::Unspecified != glyph_type)
? std::find_if(range_it.first, range_it.second, [&glyph_type](const char_glyph_info_map_t::value_type& entry) { return entry.second->mGlyphType == glyph_type; })
: range_it.first;
if (iter != range_it.second)
{
return iter->second;
}
else
{
// this glyph doesn't yet exist, so render it and return the result
return addGlyph(wch);
return addGlyph(wch, (EFontGlyphType::Unspecified != glyph_type) ? glyph_type : EFontGlyphType::Grayscale);
}
}
void LLFontFreetype::insertGlyphInfo(llwchar wch, LLFontGlyphInfo* gi) const
{
char_glyph_info_map_t::iterator iter = mCharGlyphInfoMap.find(wch);
if (iter != mCharGlyphInfoMap.end())
llassert(gi->mGlyphType < EFontGlyphType::Count);
std::pair<char_glyph_info_map_t::iterator, char_glyph_info_map_t::iterator> range_it = mCharGlyphInfoMap.equal_range(wch);
char_glyph_info_map_t::iterator iter =
std::find_if(range_it.first, range_it.second, [&gi](const char_glyph_info_map_t::value_type& entry) { return entry.second->mGlyphType == gi->mGlyphType; });
if (iter != range_it.second)
{
delete iter->second;
iter->second = gi;
}
else
{
mCharGlyphInfoMap[wch] = gi;
mCharGlyphInfoMap.insert(std::make_pair(wch, gi));
}
}
void LLFontFreetype::renderGlyph(U32 glyph_index) const
void LLFontFreetype::renderGlyph(EFontGlyphType bitmap_type, U32 glyph_index) const
{
if (mFTFace == NULL)
return;
llassert_always(! FT_Load_Glyph(mFTFace, glyph_index, FT_LOAD_FORCE_AUTOHINT) );
FT_Int32 load_flags = FT_LOAD_FORCE_AUTOHINT;
if (EFontGlyphType::Color == bitmap_type)
{
// We may not actually get a color render so our caller should always examine mFTFace->glyph->bitmap.pixel_mode
load_flags |= FT_LOAD_COLOR;
}
llassert_always(! FT_Load_Glyph(mFTFace, glyph_index, load_flags) );
llassert_always(! FT_Render_Glyph(mFTFace->glyph, gFontRenderMode) );
@ -601,7 +675,7 @@ void LLFontFreetype::renderGlyph(U32 glyph_index) const
void LLFontFreetype::reset(F32 vert_dpi, F32 horz_dpi)
{
resetBitmapCache();
loadFace(mName, mPointSize, vert_dpi ,horz_dpi, mFontBitmapCachep->getNumComponents(), mIsFallback);
loadFace(mName, mPointSize, vert_dpi ,horz_dpi, mIsFallback, 0);
if (!mIsFallback)
{
// This is the head of the list - need to rebuild ourself and all fallbacks.
@ -611,11 +685,9 @@ void LLFontFreetype::reset(F32 vert_dpi, F32 horz_dpi)
}
else
{
for(font_vector_t::iterator it = mFallbackFonts.begin();
it != mFallbackFonts.end();
++it)
for (fallback_font_vector_t::iterator it = mFallbackFonts.begin(); it != mFallbackFonts.end(); ++it)
{
(*it)->reset(vert_dpi, horz_dpi);
it->first->reset(vert_dpi, horz_dpi);
}
}
}
@ -637,7 +709,7 @@ void LLFontFreetype::resetBitmapCache()
if(!mIsFallback)
{
// Add the empty glyph
addGlyphFromFont(this, 0, 0);
addGlyphFromFont(this, 0, 0, EFontGlyphType::Grayscale);
}
}
@ -651,6 +723,34 @@ const std::string &LLFontFreetype::getName() const
return mName;
}
static void dumpFontBitmap(const LLImageRaw* image_raw, const std::string& file_name)
{
LLPointer<LLImagePNG> tmpImage = new LLImagePNG();
if ( (tmpImage->encode(image_raw, 0.0f)) && (tmpImage->save(gDirUtilp->getExpandedFilename(LL_PATH_LOGS, file_name))) )
{
LL_INFOS("Font") << "Successfully saved " << file_name << LL_ENDL;
}
else
{
LL_WARNS("Font") << "Failed to save " << file_name << LL_ENDL;
}
}
void LLFontFreetype::dumpFontBitmaps() const
{
// Dump all the regular bitmaps (if any)
for (int idx = 0, cnt = mFontBitmapCachep->getNumBitmaps(EFontGlyphType::Grayscale); idx < cnt; idx++)
{
dumpFontBitmap(mFontBitmapCachep->getImageRaw(EFontGlyphType::Grayscale, idx), llformat("%s_%d_%d_%d.png", mFTFace->family_name, (int)(mPointSize * 10), mStyle, idx));
}
// Dump all the color bitmaps (if any)
for (int idx = 0, cnt = mFontBitmapCachep->getNumBitmaps(EFontGlyphType::Color); idx < cnt; idx++)
{
dumpFontBitmap(mFontBitmapCachep->getImageRaw(EFontGlyphType::Color, idx), llformat("%s_%d_%d_%d_clr.png", mFTFace->family_name, (int)(mPointSize * 10), mStyle, idx));
}
}
const LLFontBitmapCache* LLFontFreetype::getFontBitmapCache() const
{
return mFontBitmapCachep;
@ -666,17 +766,46 @@ U8 LLFontFreetype::getStyle() const
return mStyle;
}
bool LLFontFreetype::setSubImageBGRA(U32 x, U32 y, U32 bitmap_num, U16 width, U16 height, const U8* data, U32 stride) const
{
LLImageRaw* image_raw = mFontBitmapCachep->getImageRaw(EFontGlyphType::Color, bitmap_num);
llassert(!mIsFallback);
llassert(image_raw && (image_raw->getComponents() == 4));
// NOTE: inspired by LLImageRaw::setSubImage()
U32* image_data = (U32*)image_raw->getData();
if (!image_data)
{
return false;
}
for (U32 idxRow = 0; idxRow < height; idxRow++)
{
const U32 nSrcRow = height - 1 - idxRow;
const U32 nSrcOffset = nSrcRow * width * image_raw->getComponents();
const U32 nDstOffset = (y + idxRow) * image_raw->getWidth() + x;
for (U32 idxCol = 0; idxCol < width; idxCol++)
{
U32 nTemp = nSrcOffset + idxCol * 4;
image_data[nDstOffset + idxCol] = data[nTemp + 3] << 24 | data[nTemp] << 16 | data[nTemp + 1] << 8 | data[nTemp + 2];
}
}
return true;
}
void LLFontFreetype::setSubImageLuminanceAlpha(U32 x, U32 y, U32 bitmap_num, U32 width, U32 height, U8 *data, S32 stride) const
{
LLImageRaw *image_raw = mFontBitmapCachep->getImageRaw(bitmap_num);
LLImageRaw *image_raw = mFontBitmapCachep->getImageRaw(EFontGlyphType::Grayscale, bitmap_num);
llassert(!mIsFallback);
llassert(image_raw && (image_raw->getComponents() == 2));
U8 *target = image_raw->getData();
llassert(target);
if (!data)
if (!data || !target)
{
return;
}

View File

@ -56,9 +56,11 @@ private:
struct LLFontGlyphInfo
{
LLFontGlyphInfo(U32 index);
LLFontGlyphInfo(U32 index, EFontGlyphType glyph_type);
LLFontGlyphInfo(const LLFontGlyphInfo& fgi);
U32 mGlyphIndex;
EFontGlyphType mGlyphType;
// Metrics
S32 mWidth; // In pixels
@ -71,7 +73,7 @@ struct LLFontGlyphInfo
S32 mYBitmapOffset; // Offset to the origin in the bitmap
S32 mXBearing; // Distance from baseline to left in pixels
S32 mYBearing; // Distance from baseline to top in pixels
S32 mBitmapNum; // Which bitmap in the bitmap cache contains this glyph
std::pair<EFontGlyphType, S32> mBitmapEntry; // Which bitmap in the bitmap cache contains this glyph
};
extern LLFontManager *gFontManagerp;
@ -84,7 +86,7 @@ public:
// is_fallback should be true for fallback fonts that aren't used
// to render directly (Unicode backup, primarily)
BOOL loadFace(const std::string& filename, F32 point_size, F32 vert_dpi, F32 horz_dpi, S32 components, BOOL is_fallback, S32 face_n = 0);
BOOL loadFace(const std::string& filename, F32 point_size, F32 vert_dpi, F32 horz_dpi, bool is_fallback, S32 face_n);
S32 getNumFaces(const std::string& filename);
@ -93,10 +95,8 @@ public:
void clearFontStreams();
#endif
typedef std::vector<LLPointer<LLFontFreetype> > font_vector_t;
void setFallbackFonts(const font_vector_t &font);
const font_vector_t &getFallbackFonts() const;
typedef std::function<bool(llwchar)> char_functor_t;
void addFallbackFont(const LLPointer<LLFontFreetype>& fallback_font, const char_functor_t& functor = nullptr);
// Global font metrics - in units of pixels
F32 getLineHeight() const;
@ -135,7 +135,7 @@ public:
F32 getXKerning(llwchar char_left, llwchar char_right) const; // Get the kerning between the two characters
F32 getXKerning(const LLFontGlyphInfo* left_glyph_info, const LLFontGlyphInfo* right_glyph_info) const; // Get the kerning between the two characters
LLFontGlyphInfo* getGlyphInfo(llwchar wch) const;
LLFontGlyphInfo* getGlyphInfo(llwchar wch, EFontGlyphType glyph_type) const;
void reset(F32 vert_dpi, F32 horz_dpi);
@ -143,6 +143,7 @@ public:
const std::string& getName() const;
void dumpFontBitmaps() const;
const LLFontBitmapCache* getFontBitmapCache() const;
void setStyle(U8 style);
@ -151,10 +152,11 @@ public:
private:
void resetBitmapCache();
void setSubImageLuminanceAlpha(U32 x, U32 y, U32 bitmap_num, U32 width, U32 height, U8 *data, S32 stride = 0) const;
bool setSubImageBGRA(U32 x, U32 y, U32 bitmap_num, U16 width, U16 height, const U8* data, U32 stride) const;
BOOL hasGlyph(llwchar wch) const; // Has a glyph for this character
LLFontGlyphInfo* addGlyph(llwchar wch) const; // Add a new character to the font if necessary
LLFontGlyphInfo* addGlyphFromFont(const LLFontFreetype *fontp, llwchar wch, U32 glyph_index) const; // Add a glyph from this font to the other (returns the glyph_index, 0 if not found)
void renderGlyph(U32 glyph_index) const;
LLFontGlyphInfo* addGlyph(llwchar wch, EFontGlyphType glyph_type) const; // Add a new character to the font if necessary
LLFontGlyphInfo* addGlyphFromFont(const LLFontFreetype *fontp, llwchar wch, U32 glyph_index, EFontGlyphType bitmap_type) const; // Add a glyph from this font to the other (returns the glyph_index, 0 if not found)
void renderGlyph(EFontGlyphType bitmap_type, U32 glyph_index) const;
void insertGlyphInfo(llwchar wch, LLFontGlyphInfo* gi) const;
std::string mName;
@ -174,9 +176,12 @@ private:
#endif
BOOL mIsFallback;
font_vector_t mFallbackFonts; // A list of fallback fonts to look for glyphs in (for Unicode chars)
typedef std::pair<LLPointer<LLFontFreetype>, char_functor_t> fallback_font_t;
typedef std::vector<fallback_font_t> fallback_font_vector_t;
fallback_font_vector_t mFallbackFonts; // A list of fallback fonts to look for glyphs in (for Unicode chars)
typedef boost::unordered_map<llwchar, LLFontGlyphInfo*> char_glyph_info_map_t;
// *NOTE: the same glyph can be present with multiple representations (but the pointer is always unique)
typedef boost::unordered_multimap<llwchar, LLFontGlyphInfo*> char_glyph_info_map_t;
mutable char_glyph_info_map_t mCharGlyphInfoMap; // Information about glyph location in bitmap
mutable LLFontBitmapCache* mFontBitmapCachep;

View File

@ -0,0 +1,205 @@
/**
* @file llfontfreetypesvg.cpp
* @brief Freetype font library SVG glyph rendering
*
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, 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 "llfontfreetypesvg.h"
#if LL_WINDOWS
#pragma warning (push)
#pragma warning (disable : 4702)
#endif
#define NANOSVG_IMPLEMENTATION
#include <nanosvg/nanosvg.h>
#define NANOSVGRAST_IMPLEMENTATION
#include <nanosvg/nanosvgrast.h>
#if LL_WINDOWS
#pragma warning (pop)
#endif
struct LLSvgRenderData
{
FT_UInt GlyphIndex = 0;
FT_Error Error = FT_Err_Ok; // FreeType currently (@2.12.1) ignores the error value returned by the preset glyph slot callback so we return it at render time
// (See https://github.com/freetype/freetype/blob/5faa1df8b93ebecf0f8fd5fe8fda7b9082eddced/src/base/ftobjs.c#L1170)
NSVGimage* pNSvgImage = nullptr;
float Scale = 0.f;
};
// static
FT_Error LLFontFreeTypeSvgRenderer::OnInit(FT_Pointer* state)
{
// The SVG driver hook state is shared across all callback invocations; since our state is lightweight
// we store it in the glyph instead.
*state = nullptr;
return FT_Err_Ok;
}
// static
void LLFontFreeTypeSvgRenderer::OnFree(FT_Pointer* state)
{
}
// static
void LLFontFreeTypeSvgRenderer::OnDataFinalizer(void* objectp)
{
FT_GlyphSlot glyph_slot = static_cast<FT_GlyphSlot>(objectp);
LLSvgRenderData* pData = static_cast<LLSvgRenderData*>(glyph_slot->generic.data);
glyph_slot->generic.data = nullptr;
glyph_slot->generic.finalizer = nullptr;
delete(pData);
}
//static
FT_Error LLFontFreeTypeSvgRenderer::OnPresetGlypthSlot(FT_GlyphSlot glyph_slot, FT_Bool cache, FT_Pointer*)
{
FT_SVG_Document document = static_cast<FT_SVG_Document>(glyph_slot->other);
llassert(!glyph_slot->generic.data || !cache || glyph_slot->glyph_index == ((LLSvgRenderData*)glyph_slot->generic.data)->GlyphIndex);
if (!glyph_slot->generic.data)
{
glyph_slot->generic.data = new LLSvgRenderData();
glyph_slot->generic.finalizer = LLFontFreeTypeSvgRenderer::OnDataFinalizer;
}
LLSvgRenderData* datap = static_cast<LLSvgRenderData*>(glyph_slot->generic.data);
if (!cache)
{
datap->GlyphIndex = glyph_slot->glyph_index;
datap->Error = FT_Err_Ok;
}
// NOTE: nsvgParse modifies the input string so we need a temporary copy
llassert(!datap->pNSvgImage || cache);
if (!datap->pNSvgImage)
{
char* document_buffer = new char[document->svg_document_length + 1];
memcpy(document_buffer, document->svg_document, document->svg_document_length);
document_buffer[document->svg_document_length] = '\0';
datap->pNSvgImage = nsvgParse(document_buffer, "px", 0.);
delete[] document_buffer;
}
if (!datap->pNSvgImage)
{
datap->Error = FT_Err_Invalid_SVG_Document;
return FT_Err_Invalid_SVG_Document;
}
// We don't (currently) support transformations so test for an identity rotation matrix + zero translation
if (document->transform.xx != 1 << 16 || document->transform.yx != 0 ||
document->transform.xy != 0 || document->transform.yy != 1 << 16 ||
document->delta.x > 0 || document->delta.y > 0)
{
datap->Error = FT_Err_Unimplemented_Feature;
return FT_Err_Unimplemented_Feature;
}
float svg_width = datap->pNSvgImage->width;
float svg_height = datap->pNSvgImage->height;
if (svg_width == 0.f || svg_height == 0.f)
{
svg_width = document->units_per_EM;
svg_height = document->units_per_EM;
}
float svg_x_scale = (float)document->metrics.x_ppem / floorf(svg_width);
float svg_y_scale = (float)document->metrics.y_ppem / floorf(svg_height);
float svg_scale = llmin(svg_x_scale, svg_y_scale);
datap->Scale = svg_scale;
glyph_slot->bitmap.width = floorf(svg_width) * svg_scale;
glyph_slot->bitmap.rows = floorf(svg_height) * svg_scale;
glyph_slot->bitmap_left = (document->metrics.x_ppem - glyph_slot->bitmap.width) / 2;
glyph_slot->bitmap_top = glyph_slot->face->size->metrics.ascender / 64.f;
glyph_slot->bitmap.pitch = glyph_slot->bitmap.width * 4;
glyph_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
/* Copied as-is from fcft (MIT license) */
// Compute all the bearings and set them correctly. The outline is scaled already, we just need to use the bounding box.
float horiBearingX = 0.;
float horiBearingY = -glyph_slot->bitmap_top;
// XXX parentheses correct?
float vertBearingX = glyph_slot->metrics.horiBearingX / 64.0f - glyph_slot->metrics.horiAdvance / 64.0f / 2;
float vertBearingY = (glyph_slot->metrics.vertAdvance / 64.0f - glyph_slot->metrics.height / 64.0f) / 2;
// Do conversion in two steps to avoid 'bad function cast' warning
glyph_slot->metrics.width = glyph_slot->bitmap.width * 64;
glyph_slot->metrics.height = glyph_slot->bitmap.rows * 64;
glyph_slot->metrics.horiBearingX = horiBearingX * 64;
glyph_slot->metrics.horiBearingY = horiBearingY * 64;
glyph_slot->metrics.vertBearingX = vertBearingX * 64;
glyph_slot->metrics.vertBearingY = vertBearingY * 64;
if (glyph_slot->metrics.vertAdvance == 0)
{
glyph_slot->metrics.vertAdvance = glyph_slot->bitmap.rows * 1.2f * 64;
}
return FT_Err_Ok;
}
// static
FT_Error LLFontFreeTypeSvgRenderer::OnRender(FT_GlyphSlot glyph_slot, FT_Pointer*)
{
LLSvgRenderData* datap = static_cast<LLSvgRenderData*>(glyph_slot->generic.data);
llassert(FT_Err_Ok == datap->Error);
if (FT_Err_Ok != datap->Error)
{
return datap->Error;
}
// Render to glyph bitmap
NSVGrasterizer* nsvgRasterizer = nsvgCreateRasterizer();
nsvgRasterize(nsvgRasterizer, datap->pNSvgImage, 0, 0, datap->Scale, glyph_slot->bitmap.buffer, glyph_slot->bitmap.width, glyph_slot->bitmap.rows, glyph_slot->bitmap.pitch);
nsvgDeleteRasterizer(nsvgRasterizer);
nsvgDelete(datap->pNSvgImage);
datap->pNSvgImage = nullptr;
// Convert from RGBA to BGRA
U32* pixel_buffer = (U32*)glyph_slot->bitmap.buffer; U8* byte_buffer = glyph_slot->bitmap.buffer;
for (size_t y = 0, h = glyph_slot->bitmap.rows; y < h; y++)
{
for (size_t x = 0, w = glyph_slot->bitmap.pitch / 4; x < w; x++)
{
size_t pixel_idx = y * w + x;
size_t byte_idx = pixel_idx * 4;
U8 alpha = byte_buffer[byte_idx + 3];
// Store as ARGB (*TODO - do we still have to care about endianness?)
pixel_buffer[y * w + x] = alpha << 24 | (byte_buffer[byte_idx] * alpha / 0xFF) << 16 | (byte_buffer[byte_idx + 1] * alpha / 0xFF) << 8 | (byte_buffer[byte_idx + 2] * alpha / 0xFF);
}
}
glyph_slot->format = FT_GLYPH_FORMAT_BITMAP;
glyph_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
return FT_Err_Ok;
}

View File

@ -0,0 +1,54 @@
/**
* @file llfontfreetypesvg.h
* @brief Freetype font library SVG glyph rendering
*
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, 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 <ft2build.h>
#include FT_TYPES_H
#include FT_MODULE_H
#include FT_OTSVG_H
// See https://freetype.org/freetype2/docs/reference/ft2-svg_fonts.html
class LLFontFreeTypeSvgRenderer
{
public:
// Called when the very first OT-SVG glyph is rendered (across the entire lifetime of our FT_Library object)
static FT_Error OnInit(FT_Pointer* state);
// Called when the ot-svg module is being freed (but only called if the init hook was called previously)
static void OnFree(FT_Pointer* state);
// Called to preset the glyph slot, twice per glyph:
// - when FT_Load_Glyph needs to preset the glyph slot (with cache == false)
// - right before the svg module calls the render callback hook. (with cache == true)
static FT_Error OnPresetGlypthSlot(FT_GlyphSlot glyph_slot, FT_Bool cache, FT_Pointer* state);
// Called to render an OT-SVG glyph (right after the preset hook OnPresetGlypthSlot was called with cache set to TRUE)
static FT_Error OnRender(FT_GlyphSlot glyph_slot, FT_Pointer* state);
// Called to deallocate our per glyph slot data
static void OnDataFinalizer(void* objectp);
};

View File

@ -89,14 +89,14 @@ void LLFontGL::destroyGL()
mFontFreetype->destroyGL();
}
BOOL LLFontGL::loadFace(const std::string& filename, F32 point_size, F32 vert_dpi, F32 horz_dpi, S32 components, BOOL is_fallback, S32 face_n)
BOOL LLFontGL::loadFace(const std::string& filename, F32 point_size, const F32 vert_dpi, const F32 horz_dpi, bool is_fallback, S32 face_n)
{
if(mFontFreetype == reinterpret_cast<LLFontFreetype*>(NULL))
{
mFontFreetype = new LLFontFreetype;
}
return mFontFreetype->loadFace(filename, point_size, vert_dpi, horz_dpi, components, is_fallback, face_n);
return mFontFreetype->loadFace(filename, point_size, vert_dpi, horz_dpi, is_fallback, face_n);
}
S32 LLFontGL::getNumFaces(const std::string& filename)
@ -110,14 +110,14 @@ S32 LLFontGL::getNumFaces(const std::string& filename)
}
S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, const LLRect& rect, const LLColor4 &color, HAlign halign, VAlign valign, U8 style,
ShadowType shadow, S32 max_chars, F32* right_x, BOOL use_ellipses) const
ShadowType shadow, S32 max_chars, F32* right_x, BOOL use_ellipses, BOOL use_color) const
{
LLRectf rect_float(rect.mLeft, rect.mTop, rect.mRight, rect.mBottom);
return render(wstr, begin_offset, rect_float, color, halign, valign, style, shadow, max_chars, right_x, use_ellipses);
return render(wstr, begin_offset, rect_float, color, halign, valign, style, shadow, max_chars, right_x, use_ellipses, use_color);
}
S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, const LLRectf& rect, const LLColor4 &color, HAlign halign, VAlign valign, U8 style,
ShadowType shadow, S32 max_chars, F32* right_x, BOOL use_ellipses) const
ShadowType shadow, S32 max_chars, F32* right_x, BOOL use_ellipses, BOOL use_color) const
{
F32 x = rect.mLeft;
F32 y = 0.f;
@ -138,12 +138,12 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, const LLRectf& rec
y = rect.mBottom;
break;
}
return render(wstr, begin_offset, x, y, color, halign, valign, style, shadow, max_chars, rect.getWidth(), right_x, use_ellipses);
return render(wstr, begin_offset, x, y, color, halign, valign, style, shadow, max_chars, rect.getWidth(), right_x, use_ellipses, use_color);
}
S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, const LLColor4 &color, HAlign halign, VAlign valign, U8 style,
ShadowType shadow, S32 max_chars, S32 max_pixels, F32* right_x, BOOL use_ellipses) const
ShadowType shadow, S32 max_chars, S32 max_pixels, F32* right_x, BOOL use_ellipses, BOOL use_color) const
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
@ -193,7 +193,7 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons
if (-1 == max_chars)
{
length = (S32)wstr.length() - begin_offset;
max_chars = length = (S32)wstr.length() - begin_offset;
}
else
{
@ -254,7 +254,6 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons
const S32 LAST_CHARACTER = LLFontFreetype::LAST_CHAR_FULL;
BOOL draw_ellipses = FALSE;
if (use_ellipses)
{
@ -278,7 +277,7 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons
LLColor4U text_color(color);
S32 bitmap_num = -1;
std::pair<EFontGlyphType, S32> bitmap_entry = std::make_pair(EFontGlyphType::Grayscale, -1);
S32 glyph_count = 0;
for (i = begin_offset; i < begin_offset + length; i++)
{
@ -288,7 +287,7 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons
next_glyph = NULL;
if(!fgi)
{
fgi = mFontFreetype->getGlyphInfo(wch);
fgi = mFontFreetype->getGlyphInfo(wch, (!use_color) ? EFontGlyphType::Grayscale : EFontGlyphType::Color);
}
if (!fgi)
{
@ -296,8 +295,8 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons
break;
}
// Per-glyph bitmap texture.
S32 next_bitmap_num = fgi->mBitmapNum;
if (next_bitmap_num != bitmap_num)
std::pair<EFontGlyphType, S32> next_bitmap_entry = fgi->mBitmapEntry;
if (next_bitmap_entry != bitmap_entry)
{
// Actually draw the queued glyphs before switching their texture;
// otherwise the queued glyphs will be taken from wrong textures.
@ -311,8 +310,8 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons
glyph_count = 0;
}
bitmap_num = next_bitmap_num;
LLImageGL *font_image = font_bitmap_cache->getImageGL(bitmap_num);
bitmap_entry = next_bitmap_entry;
LLImageGL* font_image = font_bitmap_cache->getImageGL(bitmap_entry.first, bitmap_entry.second);
gGL.getTexUnit(0)->bind(font_image);
}
@ -345,7 +344,7 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons
glyph_count = 0;
}
drawGlyph(glyph_count, vertices, uvs, colors, screen_rect, uv_rect, text_color, style_to_add, shadow, drop_shadow_strength);
drawGlyph(glyph_count, vertices, uvs, colors, screen_rect, uv_rect, (bitmap_entry.first == EFontGlyphType::Grayscale) ? text_color : LLColor4U::white, style_to_add, shadow, drop_shadow_strength);
chars_drawn++;
cur_x += fgi->mXAdvance;
@ -355,7 +354,7 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons
if (next_char && (next_char < LAST_CHARACTER))
{
// Kern this puppy.
next_glyph = mFontFreetype->getGlyphInfo(next_char);
next_glyph = mFontFreetype->getGlyphInfo(next_char, (!use_color) ? EFontGlyphType::Grayscale : EFontGlyphType::Color);
cur_x += mFontFreetype->getXKerning(fgi, next_glyph);
}
@ -409,7 +408,8 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons
shadow,
S32_MAX, max_pixels,
right_x,
FALSE);
FALSE,
use_color);
gGL.popUIMatrix();
}
@ -423,19 +423,19 @@ S32 LLFontGL::render(const LLWString &text, S32 begin_offset, F32 x, F32 y, cons
return render(text, begin_offset, x, y, color, LEFT, BASELINE, NORMAL, NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE);
}
S32 LLFontGL::renderUTF8(const std::string &text, S32 begin_offset, F32 x, F32 y, const LLColor4 &color, HAlign halign, VAlign valign, U8 style, ShadowType shadow, S32 max_chars, S32 max_pixels, F32* right_x, BOOL use_ellipses) const
S32 LLFontGL::renderUTF8(const std::string &text, S32 begin_offset, F32 x, F32 y, const LLColor4 &color, HAlign halign, VAlign valign, U8 style, ShadowType shadow, S32 max_chars, S32 max_pixels, F32* right_x, BOOL use_ellipses, BOOL use_color) const
{
return render(utf8str_to_wstring(text), begin_offset, x, y, color, halign, valign, style, shadow, max_chars, max_pixels, right_x, use_ellipses);
return render(utf8str_to_wstring(text), begin_offset, x, y, color, halign, valign, style, shadow, max_chars, max_pixels, right_x, use_ellipses, use_color);
}
S32 LLFontGL::renderUTF8(const std::string &text, S32 begin_offset, S32 x, S32 y, const LLColor4 &color) const
{
return renderUTF8(text, begin_offset, (F32)x, (F32)y, color, LEFT, BASELINE, NORMAL, NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE);
return renderUTF8(text, begin_offset, (F32)x, (F32)y, color, LEFT, BASELINE, NORMAL, NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE, FALSE);
}
S32 LLFontGL::renderUTF8(const std::string &text, S32 begin_offset, S32 x, S32 y, const LLColor4 &color, HAlign halign, VAlign valign, U8 style, ShadowType shadow) const
{
return renderUTF8(text, begin_offset, (F32)x, (F32)y, color, halign, valign, style, shadow, S32_MAX, S32_MAX, NULL, FALSE);
return renderUTF8(text, begin_offset, (F32)x, (F32)y, color, halign, valign, style, shadow, S32_MAX, S32_MAX, NULL, FALSE, FALSE);
}
// font metrics - override for LLFontFreetype that returns units of virtual pixels
@ -488,7 +488,7 @@ F32 LLFontGL::getWidthF32(const llwchar* wchars) const
return getWidthF32(wchars, 0, S32_MAX);
}
F32 LLFontGL::getWidthF32(const std::string& utf8text, S32 begin_offset, S32 max_chars ) const
F32 LLFontGL::getWidthF32(const std::string& utf8text, S32 begin_offset, S32 max_chars) const
{
LLWString wtext = utf8str_to_wstring(utf8text);
return getWidthF32(wtext.c_str(), begin_offset, max_chars);
@ -512,7 +512,7 @@ F32 LLFontGL::getWidthF32(const llwchar* wchars, S32 begin_offset, S32 max_chars
next_glyph = NULL;
if(!fgi)
{
fgi = mFontFreetype->getGlyphInfo(wch);
fgi = mFontFreetype->getGlyphInfo(wch, EFontGlyphType::Unspecified);
}
F32 advance = mFontFreetype->getXAdvance(fgi);
@ -535,7 +535,7 @@ F32 LLFontGL::getWidthF32(const llwchar* wchars, S32 begin_offset, S32 max_chars
&& (next_char < LAST_CHARACTER))
{
// Kern this puppy.
next_glyph = mFontFreetype->getGlyphInfo(next_char);
next_glyph = mFontFreetype->getGlyphInfo(next_char, EFontGlyphType::Unspecified);
cur_x += mFontFreetype->getXKerning(fgi, next_glyph);
}
// Round after kerning.
@ -556,7 +556,7 @@ void LLFontGL::generateASCIIglyphs()
LL_PROFILE_ZONE_SCOPED_CATEGORY_UI
for (U32 i = 32; (i < 127); i++)
{
mFontFreetype->getGlyphInfo(i);
mFontFreetype->getGlyphInfo(i, EFontGlyphType::Grayscale);
}
}
@ -630,7 +630,7 @@ S32 LLFontGL::maxDrawableChars(const llwchar* wchars, F32 max_pixels, S32 max_ch
next_glyph = NULL;
if(!fgi)
{
fgi = mFontFreetype->getGlyphInfo(wch);
fgi = mFontFreetype->getGlyphInfo(wch, EFontGlyphType::Unspecified);
if (NULL == fgi)
{
@ -655,7 +655,7 @@ S32 LLFontGL::maxDrawableChars(const llwchar* wchars, F32 max_pixels, S32 max_ch
if (((i+1) < max_chars) && wchars[i+1])
{
// Kern this puppy.
next_glyph = mFontFreetype->getGlyphInfo(wchars[i+1]);
next_glyph = mFontFreetype->getGlyphInfo(wchars[i+1], EFontGlyphType::Unspecified);
cur_x += mFontFreetype->getXKerning(fgi, next_glyph);
}
@ -702,7 +702,7 @@ S32 LLFontGL::firstDrawableChar(const llwchar* wchars, F32 max_pixels, S32 text_
{
llwchar wch = wchars[i];
const LLFontGlyphInfo* fgi= mFontFreetype->getGlyphInfo(wch);
const LLFontGlyphInfo* fgi= mFontFreetype->getGlyphInfo(wch, EFontGlyphType::Unspecified);
// last character uses character width, since the whole character needs to be visible
// other characters just use advance
@ -777,7 +777,7 @@ S32 LLFontGL::charFromPixelOffset(const llwchar* wchars, S32 begin_offset, F32 t
next_glyph = NULL;
if(!glyph)
{
glyph = mFontFreetype->getGlyphInfo(wch);
glyph = mFontFreetype->getGlyphInfo(wch, EFontGlyphType::Unspecified);
}
F32 char_width = mFontFreetype->getXAdvance(glyph);
@ -807,7 +807,7 @@ S32 LLFontGL::charFromPixelOffset(const llwchar* wchars, S32 begin_offset, F32 t
&& (wchars[(pos + 1)]))
{
// Kern this puppy.
next_glyph = mFontFreetype->getGlyphInfo(wchars[pos + 1]);
next_glyph = mFontFreetype->getGlyphInfo(wchars[pos + 1], EFontGlyphType::Unspecified);
cur_x += mFontFreetype->getXKerning(glyph, next_glyph);
}
@ -847,6 +847,26 @@ void LLFontGL::initClass(F32 screen_dpi, F32 x_scale, F32 y_scale, const std::st
LLFontGL::loadDefaultFonts();
}
void LLFontGL::dumpTextures()
{
if (mFontFreetype.notNull())
{
mFontFreetype->dumpFontBitmaps();
}
}
// static
void LLFontGL::dumpFonts()
{
sFontRegistry->dump();
}
// static
void LLFontGL::dumpFontTextures()
{
sFontRegistry->dumpTextures();
}
// Force standard fonts to get generated up front.
// This is primarily for error detection purposes.
// Don't do this during initClass because it can be slow and we want to get
@ -1009,6 +1029,20 @@ LLFontGL::VAlign LLFontGL::vAlignFromName(const std::string& name)
return gl_vfont_align;
}
//static
LLFontGL* LLFontGL::getFontEmoji()
{
static LLFontGL* fontp = getFont(LLFontDescriptor("Emoji", "Large", 0));
return fontp;;
}
//static
LLFontGL* LLFontGL::getFontEmojiHuge()
{
static LLFontGL* fontp = getFont(LLFontDescriptor("Emoji", "Huge", 0));
return fontp;;
}
//static
LLFontGL* LLFontGL::getFontMonospace()
{

View File

@ -87,7 +87,7 @@ public:
void destroyGL();
BOOL loadFace(const std::string& filename, F32 point_size, const F32 vert_dpi, const F32 horz_dpi, const S32 components, BOOL is_fallback, S32 face_n = 0);
BOOL loadFace(const std::string& filename, F32 point_size, const F32 vert_dpi, const F32 horz_dpi, bool is_fallback, S32 face_n);
S32 getNumFaces(const std::string& filename);
@ -98,7 +98,8 @@ public:
U8 style = NORMAL, ShadowType shadow = NO_SHADOW,
S32 max_chars = S32_MAX,
F32* right_x=NULL,
BOOL use_ellipses = FALSE) const;
BOOL use_ellipses = FALSE,
BOOL use_color = FALSE) const;
S32 render(const LLWString &text, S32 begin_offset,
const LLRectf& rect,
@ -107,7 +108,8 @@ public:
U8 style = NORMAL, ShadowType shadow = NO_SHADOW,
S32 max_chars = S32_MAX,
F32* right_x=NULL,
BOOL use_ellipses = FALSE) const;
BOOL use_ellipses = FALSE,
BOOL use_color = FALSE) const;
S32 render(const LLWString &text, S32 begin_offset,
F32 x, F32 y,
@ -116,12 +118,13 @@ public:
U8 style = NORMAL, ShadowType shadow = NO_SHADOW,
S32 max_chars = S32_MAX, S32 max_pixels = S32_MAX,
F32* right_x=NULL,
BOOL use_ellipses = FALSE) const;
BOOL use_ellipses = FALSE,
BOOL use_color = FALSE) const;
S32 render(const LLWString &text, S32 begin_offset, F32 x, F32 y, const LLColor4 &color) const;
// renderUTF8 does a conversion, so is slower!
S32 renderUTF8(const std::string &text, S32 begin_offset, F32 x, F32 y, const LLColor4 &color, HAlign halign, VAlign valign, U8 style, ShadowType shadow, S32 max_chars, S32 max_pixels, F32* right_x, BOOL use_ellipses) const;
S32 renderUTF8(const std::string &text, S32 begin_offset, F32 x, F32 y, const LLColor4 &color, HAlign halign, VAlign valign, U8 style, ShadowType shadow, S32 max_chars, S32 max_pixels, F32* right_x, BOOL use_ellipses, BOOL use_color) const;
S32 renderUTF8(const std::string &text, S32 begin_offset, S32 x, S32 y, const LLColor4 &color) const;
S32 renderUTF8(const std::string &text, S32 begin_offset, S32 x, S32 y, const LLColor4 &color, HAlign halign, VAlign valign, U8 style = NORMAL, ShadowType shadow = NO_SHADOW) const;
@ -132,12 +135,12 @@ public:
S32 getWidth(const std::string& utf8text) const;
S32 getWidth(const llwchar* wchars) const;
S32 getWidth(const std::string& utf8text, S32 offset, S32 max_chars ) const;
S32 getWidth(const std::string& utf8text, S32 offset, S32 max_chars) const;
S32 getWidth(const llwchar* wchars, S32 offset, S32 max_chars) const;
F32 getWidthF32(const std::string& utf8text) const;
F32 getWidthF32(const llwchar* wchars) const;
F32 getWidthF32(const std::string& text, S32 offset, S32 max_chars ) const;
F32 getWidthF32(const std::string& text, S32 offset, S32 max_chars) const;
F32 getWidthF32(const llwchar* wchars, S32 offset, S32 max_chars, bool no_padding = false) const;
// The following are called often, frequently with large buffers, so do not use a string interface
@ -165,6 +168,10 @@ public:
static void initClass(F32 screen_dpi, F32 x_scale, F32 y_scale, const std::string& app_dir, bool create_gl_textures = true);
void dumpTextures();
static void dumpFonts();
static void dumpFontTextures();
// Load sans-serif, sans-serif-small, etc.
// Slow, requires multiple seconds to load fonts.
static bool loadDefaultFonts();
@ -187,6 +194,8 @@ public:
static void setFontDisplay(BOOL flag) { sDisplayFont = flag; }
static LLFontGL* getFontEmoji();
static LLFontGL* getFontEmojiHuge();
static LLFontGL* getFontMonospace();
static LLFontGL* getFontSansSerifSmall();
static LLFontGL* getFontSansSerifSmallBold();

View File

@ -47,6 +47,10 @@ bool init_from_xml(LLFontRegistry* registry, LLXMLNodePtr node);
const std::string MACOSX_FONT_PATH_LIBRARY = "/Library/Fonts/";
const std::string MACOSX_FONT_SUPPLEMENTAL = "Supplemental/";
LLFontDescriptor::char_functor_map_t LLFontDescriptor::mCharFunctors({
{ "is_emoji", LLStringOps::isEmoji }
});
LLFontDescriptor::LLFontDescriptor():
mStyle(0)
{
@ -55,22 +59,22 @@ LLFontDescriptor::LLFontDescriptor():
LLFontDescriptor::LLFontDescriptor(const std::string& name,
const std::string& size,
const U8 style,
const string_vec_t& file_names):
const font_file_info_vec_t& font_files):
mName(name),
mSize(size),
mStyle(style),
mFileNames(file_names)
mFontFiles(font_files)
{
}
LLFontDescriptor::LLFontDescriptor(const std::string& name,
const std::string& size,
const U8 style,
const string_vec_t& file_names,
const string_vec_t& ft_collection_listections) :
LLFontDescriptor(name, size, style, file_names)
const font_file_info_vec_t& font_list,
const font_file_info_vec_t& font_collection_files) :
LLFontDescriptor(name, size, style, font_list)
{
mFontCollectionsList = ft_collection_listections;
mFontCollectionFiles = font_collection_files;
}
LLFontDescriptor::LLFontDescriptor(const std::string& name,
@ -82,7 +86,6 @@ LLFontDescriptor::LLFontDescriptor(const std::string& name,
{
}
bool LLFontDescriptor::operator<(const LLFontDescriptor& b) const
{
if (mName < b.mName)
@ -175,7 +178,19 @@ LLFontDescriptor LLFontDescriptor::normalize() const
if (removeSubString(new_name,"Italic"))
new_style |= LLFontGL::ITALIC;
return LLFontDescriptor(new_name,new_size,new_style,getFileNames(),getFontCollectionsList());
return LLFontDescriptor(new_name,new_size,new_style, getFontFiles(), getFontCollectionFiles());
}
void LLFontDescriptor::addFontFile(const std::string& file_name, const std::string& char_functor)
{
char_functor_map_t::const_iterator it = mCharFunctors.find(char_functor);
mFontFiles.push_back(LLFontFileInfo(file_name, (mCharFunctors.end() != it) ? it->second : nullptr));
}
void LLFontDescriptor::addFontCollectionFile(const std::string& file_name, const std::string& char_functor)
{
char_functor_map_t::const_iterator it = mCharFunctors.find(char_functor);
mFontCollectionFiles.push_back(LLFontFileInfo(file_name, (mCharFunctors.end() != it) ? it->second : nullptr));
}
LLFontRegistry::LLFontRegistry(bool create_gl_textures)
@ -273,17 +288,24 @@ bool font_desc_init_from_xml(LLXMLNodePtr node, LLFontDescriptor& desc)
if (child->hasName("file"))
{
std::string font_file_name = child->getTextContents();
desc.getFileNames().push_back(font_file_name);
std::string char_functor;
if (child->hasAttribute("functor"))
{
child->getAttributeString("functor", char_functor);
}
if (child->hasAttribute("load_collection"))
{
BOOL col = FALSE;
child->getAttributeBOOL("load_collection", col);
if (col)
{
desc.getFontCollectionsList().push_back(font_file_name);
desc.addFontCollectionFile(font_file_name, char_functor);
}
}
desc.addFontFile(font_file_name, char_functor);
}
else if (child->hasName("os"))
{
@ -326,19 +348,19 @@ bool init_from_xml(LLFontRegistry* registry, LLXMLNodePtr node)
// A little roundabout because the map key is const,
// so we have to fetch it, make a new map key, and
// replace the old entry.
string_vec_t match_file_names = match_desc->getFileNames();
match_file_names.insert(match_file_names.begin(),
desc.getFileNames().begin(),
desc.getFileNames().end());
font_file_info_vec_t font_files = match_desc->getFontFiles();
font_files.insert(font_files.begin(),
desc.getFontFiles().begin(),
desc.getFontFiles().end());
string_vec_t collections_list = match_desc->getFontCollectionsList();
collections_list.insert(collections_list.begin(),
desc.getFontCollectionsList().begin(),
desc.getFontCollectionsList().end());
font_file_info_vec_t font_collection_files = match_desc->getFontCollectionFiles();
font_collection_files.insert(font_collection_files.begin(),
desc.getFontCollectionFiles().begin(),
desc.getFontCollectionFiles().end());
LLFontDescriptor new_desc = *match_desc;
new_desc.getFileNames() = match_file_names;
new_desc.getFontCollectionsList() = collections_list;
new_desc.setFontFiles(font_files);
new_desc.setFontCollectionFiles(font_collection_files);
registry->mFontMap.erase(*match_desc);
registry->mFontMap[new_desc] = NULL;
}
@ -423,82 +445,80 @@ LLFontGL *LLFontRegistry::createFont(const LLFontDescriptor& desc)
// Build list of font names to look for.
// Files specified for this font come first, followed by those from the default descriptor.
string_vec_t file_names = match_desc->getFileNames();
string_vec_t ft_collection_list = match_desc->getFontCollectionsList();
string_vec_t default_file_names;
font_file_info_vec_t font_files = match_desc->getFontFiles();
font_file_info_vec_t font_collection_files = match_desc->getFontCollectionFiles();
LLFontDescriptor default_desc("default",s_template_string,0);
const LLFontDescriptor *match_default_desc = getMatchingFontDesc(default_desc);
if (match_default_desc)
{
file_names.insert(file_names.end(),
match_default_desc->getFileNames().begin(),
match_default_desc->getFileNames().end());
ft_collection_list.insert(ft_collection_list.end(),
match_default_desc->getFontCollectionsList().begin(),
match_default_desc->getFontCollectionsList().end());
font_files.insert(font_files.end(),
match_default_desc->getFontFiles().begin(),
match_default_desc->getFontFiles().end());
font_collection_files.insert(font_collection_files.end(),
match_default_desc->getFontCollectionFiles().begin(),
match_default_desc->getFontCollectionFiles().end());
}
// Add ultimate fallback list - generated dynamically on linux,
// null elsewhere.
file_names.insert(file_names.end(),
getUltimateFallbackList().begin(),
getUltimateFallbackList().end());
std::transform(getUltimateFallbackList().begin(), getUltimateFallbackList().end(), std::back_inserter(font_files),
[](const std::string& file_name) { return LLFontFileInfo(file_name); });
// Load fonts based on names.
if (file_names.empty())
if (font_files.empty())
{
LL_WARNS() << "createFont failed, no file names specified" << LL_ENDL;
return NULL;
}
LLFontFreetype::font_vector_t fontlist;
LLFontGL *result = NULL;
// Snarf all fonts we can into fontlist. First will get pulled
// off the list and become the "head" font, set to non-fallback.
// The first font will get pulled will be the "head" font, set to non-fallback.
// Rest will consitute the fallback list.
BOOL is_first_found = TRUE;
std::string local_path = LLFontGL::getFontPathLocal();
std::string sys_path = LLFontGL::getFontPathSystem();
string_vec_t font_search_paths;
font_search_paths.push_back(LLFontGL::getFontPathLocal());
font_search_paths.push_back(LLFontGL::getFontPathSystem());
#if LL_DARWIN
font_search_paths.push_back(MACOSX_FONT_PATH_LIBRARY);
font_search_paths.push_back(MACOSX_FONT_PATH_LIBRARY + MACOSX_FONT_SUPPLEMENTAL);
font_search_paths.push_back(LLFontGL::getFontPathSystem() + MACOSX_FONT_SUPPLEMENTAL);
#endif
// The fontname string may contain multiple font file names separated by semicolons.
// Break it apart and try loading each one, in order.
for(string_vec_t::iterator file_name_it = file_names.begin();
file_name_it != file_names.end();
++file_name_it)
for(font_file_info_vec_t::iterator font_file_it = font_files.begin();
font_file_it != font_files.end();
++font_file_it)
{
LLFontGL *fontp = NULL;
string_vec_t font_paths;
font_paths.push_back(local_path + *file_name_it);
font_paths.push_back(sys_path + *file_name_it);
#if LL_DARWIN
font_paths.push_back(MACOSX_FONT_PATH_LIBRARY + *file_name_it);
font_paths.push_back(MACOSX_FONT_PATH_LIBRARY + MACOSX_FONT_SUPPLEMENTAL + *file_name_it);
font_paths.push_back(sys_path + MACOSX_FONT_SUPPLEMENTAL + *file_name_it);
#endif
bool is_ft_collection = (std::find(ft_collection_list.begin(), ft_collection_list.end(), *file_name_it) != ft_collection_list.end());
bool is_ft_collection = (std::find_if(font_collection_files.begin(), font_collection_files.end(),
[&font_file_it](const LLFontFileInfo& ffi) { return font_file_it->FileName == ffi.FileName; }) != font_collection_files.end());
// *HACK: Fallback fonts don't render, so we can use that to suppress
// creation of OpenGL textures for test apps. JC
BOOL is_fallback = !is_first_found || !mCreateGLTextures;
F32 extra_scale = (is_fallback)?fallback_scale:1.0;
F32 point_size_scale = extra_scale * point_size;
bool is_font_loaded = false;
for(string_vec_t::iterator font_paths_it = font_paths.begin();
font_paths_it != font_paths.end();
++font_paths_it)
for(string_vec_t::iterator font_search_path_it = font_search_paths.begin();
font_search_path_it != font_search_paths.end();
++font_search_path_it)
{
const std::string font_path = *font_search_path_it + font_file_it->FileName;
fontp = new LLFontGL;
S32 num_faces = is_ft_collection ? fontp->getNumFaces(*font_paths_it) : 1;
S32 num_faces = is_ft_collection ? fontp->getNumFaces(font_path) : 1;
for (S32 i = 0; i < num_faces; i++)
{
if (fontp == NULL)
{
fontp = new LLFontGL;
}
if (fontp->loadFace(*font_paths_it, point_size_scale,
LLFontGL::sVertDPI, LLFontGL::sHorizDPI, 2, is_fallback, i))
if (fontp->loadFace(font_path, point_size_scale,
LLFontGL::sVertDPI, LLFontGL::sHorizDPI, is_fallback, i))
{
is_font_loaded = true;
if (is_first_found)
@ -508,7 +528,8 @@ LLFontGL *LLFontRegistry::createFont(const LLFontDescriptor& desc)
}
else
{
fontlist.push_back(fontp->mFontFreetype);
result->mFontFreetype->addFallbackFont(fontp->mFontFreetype, font_file_it->CharFunctor);
delete fontp;
fontp = NULL;
}
@ -523,17 +544,12 @@ LLFontGL *LLFontRegistry::createFont(const LLFontDescriptor& desc)
}
if(!is_font_loaded)
{
LL_INFOS_ONCE("LLFontRegistry") << "Couldn't load font " << *file_name_it << LL_ENDL;
LL_INFOS_ONCE("LLFontRegistry") << "Couldn't load font " << font_file_it->FileName << LL_ENDL;
delete fontp;
fontp = NULL;
}
}
if (result && !fontlist.empty())
{
result->mFontFreetype->setFallbackFonts(fontlist);
}
if (result)
{
result->mFontDescriptor = desc;
@ -720,11 +736,22 @@ void LLFontRegistry::dump()
<< " size=[" << desc.getSize() << "]"
<< " fileNames="
<< LL_ENDL;
for (string_vec_t::const_iterator file_it=desc.getFileNames().begin();
file_it != desc.getFileNames().end();
for (font_file_info_vec_t::const_iterator file_it=desc.getFontFiles().begin();
file_it != desc.getFontFiles().end();
++file_it)
{
LL_INFOS() << " file: " << *file_it <<LL_ENDL;
LL_INFOS() << " file: " << file_it->FileName << LL_ENDL;
}
}
}
void LLFontRegistry::dumpTextures()
{
for (const auto& fontEntry : mFontMap)
{
if (fontEntry.second)
{
fontEntry.second->dumpTextures();
}
}
}

View File

@ -34,13 +34,32 @@ class LLFontGL;
typedef std::vector<std::string> string_vec_t;
struct LLFontFileInfo
{
LLFontFileInfo(const std::string& file_name, const std::function<bool(llwchar)>& char_functor = nullptr)
: FileName(file_name)
, CharFunctor(char_functor)
{
}
LLFontFileInfo(const LLFontFileInfo& ffi)
: FileName(ffi.FileName)
, CharFunctor(ffi.CharFunctor)
{
}
std::string FileName;
std::function<bool(llwchar)> CharFunctor;
};
typedef std::vector<LLFontFileInfo> font_file_info_vec_t;
class LLFontDescriptor
{
public:
LLFontDescriptor();
LLFontDescriptor(const std::string& name, const std::string& size, const U8 style);
LLFontDescriptor(const std::string& name, const std::string& size, const U8 style, const string_vec_t& file_names);
LLFontDescriptor(const std::string& name, const std::string& size, const U8 style, const string_vec_t& file_names, const string_vec_t& font_collections);
LLFontDescriptor(const std::string& name, const std::string& size, const U8 style, const font_file_info_vec_t& font_list);
LLFontDescriptor(const std::string& name, const std::string& size, const U8 style, const font_file_info_vec_t& font_list, const font_file_info_vec_t& font_collection_list);
LLFontDescriptor normalize() const;
bool operator<(const LLFontDescriptor& b) const;
@ -51,19 +70,26 @@ public:
void setName(const std::string& name) { mName = name; }
const std::string& getSize() const { return mSize; }
void setSize(const std::string& size) { mSize = size; }
const std::vector<std::string>& getFileNames() const { return mFileNames; }
std::vector<std::string>& getFileNames() { return mFileNames; }
const std::vector<std::string>& getFontCollectionsList() const { return mFontCollectionsList; }
std::vector<std::string>& getFontCollectionsList() { return mFontCollectionsList; }
void addFontFile(const std::string& file_name, const std::string& char_functor = LLStringUtil::null);
const font_file_info_vec_t & getFontFiles() const { return mFontFiles; }
void setFontFiles(const font_file_info_vec_t& font_files) { mFontFiles = font_files; }
void addFontCollectionFile(const std::string& file_name, const std::string& char_functor = LLStringUtil::null);
const font_file_info_vec_t& getFontCollectionFiles() const { return mFontCollectionFiles; }
void setFontCollectionFiles(const font_file_info_vec_t& font_collection_files) { mFontCollectionFiles = font_collection_files; }
const U8 getStyle() const { return mStyle; }
void setStyle(U8 style) { mStyle = style; }
private:
std::string mName;
std::string mSize;
string_vec_t mFileNames;
string_vec_t mFontCollectionsList;
font_file_info_vec_t mFontFiles;
font_file_info_vec_t mFontCollectionFiles;
U8 mStyle;
typedef std::map<std::string, std::function<bool(llwchar)>> char_functor_map_t;
static char_functor_map_t mCharFunctors;
};
class LLFontRegistry
@ -94,6 +120,7 @@ public:
bool nameToSize(const std::string& size_name, F32& size);
void dump();
void dumpTextures();
const string_vec_t& getUltimateFallbackList() const;

View File

@ -2329,7 +2329,7 @@ void LLGLSyncFence::wait()
if (mSync)
{
while (glClientWaitSync(mSync, 0, FENCE_WAIT_TIME_NANOSECONDS) == GL_TIMEOUT_EXPIRED)
{
{ //track the number of times we've waited here
}
}
#endif

View File

@ -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

View File

@ -68,6 +68,7 @@ LLButton::Params::Params()
label_shadow("label_shadow", true),
auto_resize("auto_resize", false),
use_ellipses("use_ellipses", false),
use_font_color("use_font_color", false),
image_unselected("image_unselected"),
image_selected("image_selected"),
image_hover_selected("image_hover_selected"),
@ -160,6 +161,7 @@ LLButton::LLButton(const LLButton::Params& p)
mDropShadowedText(p.label_shadow),
mAutoResize(p.auto_resize),
mUseEllipses( p.use_ellipses ),
mUseFontColor( p.use_font_color),
mHAlign(p.font_halign),
mLeftHPad(p.pad_left),
mRightHPad(p.pad_right),
@ -961,7 +963,7 @@ void LLButton::draw()
LLFontGL::NORMAL,
mDropShadowedText ? LLFontGL::DROP_SHADOW_SOFT : LLFontGL::NO_SHADOW,
S32_MAX, text_width,
NULL, mUseEllipses);
NULL, mUseEllipses, mUseFontColor);
}
LLUICtrl::draw();
@ -1021,6 +1023,16 @@ BOOL LLButton::toggleState()
return flipped;
}
void LLButton::setLabel( const std::string& label )
{
mUnselectedLabel = mSelectedLabel = label;
}
void LLButton::setLabel( const LLUIString& label )
{
mUnselectedLabel = mSelectedLabel = label;
}
void LLButton::setLabel( const LLStringExplicit& label )
{
setLabelUnselected(label);
@ -1052,14 +1064,7 @@ bool LLButton::labelIsTruncated() const
const LLUIString& LLButton::getCurrentLabel() const
{
if( getToggleState() )
{
return mSelectedLabel;
}
else
{
return mUnselectedLabel;
}
return getToggleState() ? mSelectedLabel : mUnselectedLabel;
}
void LLButton::setImageUnselected(LLPointer<LLUIImage> image)

View File

@ -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;

View File

@ -0,0 +1,475 @@
/**
* @file llemojidictionary.cpp
* @brief Implementation of LLEmojiDictionary
*
* $LicenseInfo:firstyear=2014&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2014, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "lldir.h"
#include "llemojidictionary.h"
#include "llsdserialize.h"
#include <boost/algorithm/string.hpp>
#include <boost/range/adaptor/filtered.hpp>
#include <boost/range/algorithm/transform.hpp>
// ============================================================================
// Constants
//
static const std::string SKINNED_EMOJI_FILENAME("emoji_characters.xml");
static const std::string SKINNED_CATEGORY_FILENAME("emoji_categories.xml");
static const std::string COMMON_GROUP_FILENAME("emoji_groups.xml");
static const std::string GROUP_NAME_ALL("all");
static const std::string GROUP_NAME_OTHERS("others");
static const std::string GROUP_NAME_SKIP("skip");
// ============================================================================
// Helper functions
//
template<class T>
std::list<T> llsd_array_to_list(const LLSD& sd, std::function<void(T&)> mutator = {});
template<>
std::list<std::string> llsd_array_to_list(const LLSD& sd, std::function<void(std::string&)> mutator)
{
std::list<std::string> result;
for (LLSD::array_const_iterator it = sd.beginArray(), end = sd.endArray(); it != end; ++it)
{
const LLSD& entry = *it;
if (!entry.isString())
continue;
result.push_back(entry.asStringRef());
if (mutator)
{
mutator(result.back());
}
}
return result;
}
struct emoji_filter_base
{
emoji_filter_base(const std::string& needle)
{
// Search without the colon (if present) so the user can type ':food' and see all emojis in the 'Food' category
mNeedle = (boost::starts_with(needle, ":")) ? needle.substr(1) : needle;
LLStringUtil::toLower(mNeedle);
}
protected:
std::string mNeedle;
};
struct emoji_filter_shortcode_or_category_contains : public emoji_filter_base
{
emoji_filter_shortcode_or_category_contains(const std::string& needle) : emoji_filter_base(needle) {}
bool operator()(const LLEmojiDescriptor& descr) const
{
for (const auto& short_code : descr.ShortCodes)
{
if (boost::icontains(short_code, mNeedle))
return true;
}
if (boost::icontains(descr.Category, mNeedle))
return true;
return false;
}
};
std::string LLEmojiDescriptor::getShortCodes() const
{
std::string result;
for (const std::string& shortCode : ShortCodes)
{
if (!result.empty())
{
result += ", ";
}
result += shortCode;
}
return result;
}
// ============================================================================
// LLEmojiDictionary class
//
LLEmojiDictionary::LLEmojiDictionary()
{
}
// static
void LLEmojiDictionary::initClass()
{
LLEmojiDictionary* pThis = &LLEmojiDictionary::initParamSingleton();
pThis->loadTranslations();
pThis->loadGroups();
pThis->loadEmojis();
}
LLWString LLEmojiDictionary::findMatchingEmojis(const std::string& needle) const
{
LLWString result;
boost::transform(mEmojis | boost::adaptors::filtered(emoji_filter_shortcode_or_category_contains(needle)),
std::back_inserter(result), [](const auto& descr) { return descr.Character; });
return result;
}
void LLEmojiDictionary::findByShortCode(std::vector<LLEmojiSearchResult>& result, const std::string& needle) const
{
result.clear();
if (needle.empty() || needle.front() != ':')
return;
auto search = [needle](std::size_t& begin, std::size_t& end, const std::string& shortCode) -> bool
{
begin = 0;
end = 1;
std::size_t index = 1;
// Search for begin
char d = tolower(needle[index++]);
while (end < shortCode.size())
{
char s = tolower(shortCode[end++]);
if (s == d)
{
begin = end - 1;
break;
}
}
if (!begin)
return false;
// Search for end
d = tolower(needle[index++]);
while (end < shortCode.size() && index <= needle.size())
{
char s = tolower(shortCode[end++]);
if (s == d)
{
if (index == needle.size())
return true;
d = tolower(needle[index++]);
continue;
}
switch (s)
{
case L'-':
case L'_':
case L'+':
continue;
}
break;
}
return false;
};
std::map<std::size_t, std::vector<LLEmojiSearchResult>> results;
for (const LLEmojiDescriptor& d : mEmojis)
{
if (d.ShortCodes.empty())
continue;
const std::string& shortCode = d.ShortCodes.front();
if (shortCode.size() < needle.size() || shortCode.front() != needle.front())
continue;
std::size_t begin, end;
if (search(begin, end, shortCode))
{
results[begin].emplace_back(d.Character, shortCode, begin, end);
}
}
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();
// Add group "all"
mGroups.emplace_back();
// https://www.compart.com/en/unicode/U+1F50D
mGroups.front().Character = 0x1F50D;
// https://www.compart.com/en/unicode/U+1F302
llwchar iconOthers = 0x1F302;
// Register all groups
for (LLSD::array_const_iterator it = data.beginArray(), end = data.endArray(); it != end; ++it)
{
const LLSD& sd = *it;
const std::string& name = sd["Name"].asStringRef();
if (name == GROUP_NAME_ALL)
{
mGroups.front().Character = loadIcon(sd);
}
else if (name == GROUP_NAME_OTHERS)
{
iconOthers = loadIcon(sd);
}
else if (name == GROUP_NAME_SKIP)
{
mSkipCategories = loadCategories(sd);
translateCategories(mSkipCategories);
}
else
{
// Add new group
mGroups.emplace_back();
LLEmojiGroup& group = mGroups.back();
group.Character = loadIcon(sd);
group.Categories = loadCategories(sd);
translateCategories(group.Categories);
for (const std::string& category : group.Categories)
{
mCategory2Group.insert(std::make_pair(category, &group));
}
}
}
// Add group "others"
mGroups.emplace_back();
mGroups.back().Character = iconOthers;
}
void LLEmojiDictionary::loadEmojis()
{
std::vector<std::string> filenames = gDirUtilp->findSkinnedFilenames(LLDir::XUI, SKINNED_EMOJI_FILENAME, LLDir::CURRENT_SKIN);
if (filenames.empty())
{
LL_WARNS() << "Emoji file characters not found" << LL_ENDL;
return;
}
const std::string filename = filenames.back();
llifstream file(filename.c_str());
if (!file.is_open())
{
LL_WARNS() << "Emoji file characters failed to open" << LL_ENDL;
return;
}
LL_DEBUGS() << "Loading emoji characters file at " << filename << LL_ENDL;
LLSD data;
LLSDSerialize::fromXML(data, file);
if (data.isUndefined())
{
LL_WARNS() << "Emoji file characters missing or ill-formed" << LL_ENDL;
return;
}
for (LLSD::array_const_iterator it = data.beginArray(), end = data.endArray(); it != end; ++it)
{
const LLSD& sd = *it;
llwchar icon = loadIcon(sd);
if (!icon)
{
LL_WARNS() << "Skipping invalid emoji descriptor (no icon)" << LL_ENDL;
continue;
}
std::list<std::string> categories = loadCategories(sd);
if (categories.empty())
{
LL_WARNS() << "Skipping invalid emoji descriptor (no categories)" << LL_ENDL;
continue;
}
std::string category = categories.front();
if (std::find(mSkipCategories.begin(), mSkipCategories.end(), category) != mSkipCategories.end())
{
// This category is listed for skip
continue;
}
std::list<std::string> shortCodes = loadShortCodes(sd);
if (shortCodes.empty())
{
LL_WARNS() << "Skipping invalid emoji descriptor (no shortCodes)" << LL_ENDL;
continue;
}
if (mCategory2Group.find(category) == mCategory2Group.end())
{
// Add unknown category to "others" group
mGroups.back().Categories.push_back(category);
mCategory2Group.insert(std::make_pair(category, &mGroups.back()));
}
mEmojis.emplace_back();
LLEmojiDescriptor& emoji = mEmojis.back();
emoji.Character = icon;
emoji.Category = category;
emoji.ShortCodes = std::move(shortCodes);
mEmoji2Descr.insert(std::make_pair(icon, &emoji));
mCategory2Descrs[category].push_back(&emoji);
for (const std::string& shortCode : emoji.ShortCodes)
{
mShortCode2Descr.insert(std::make_pair(shortCode, &emoji));
}
}
}
llwchar LLEmojiDictionary::loadIcon(const LLSD& sd)
{
// We don't currently support character composition
const LLWString icon = utf8str_to_wstring(sd["Character"].asString());
return (1 == icon.size()) ? icon[0] : L'\0';
}
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;
}
}
}
// ============================================================================

View File

@ -0,0 +1,125 @@
/**
* @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;
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;
};
// ============================================================================

View File

@ -0,0 +1,168 @@
/**
* @file llemojihelper.h
* @brief Header file for LLEmojiHelper
*
* $LicenseInfo:firstyear=2014&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2014, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "llemojidictionary.h"
#include "llemojihelper.h"
#include "llfloater.h"
#include "llfloaterreg.h"
#include "lluictrl.h"
// ============================================================================
// Constants
//
constexpr char DEFAULT_EMOJI_HELPER_FLOATER[] = "emoji_complete";
constexpr S32 HELPER_FLOATER_OFFSET_X = 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)
{
if (ctrl_p && !isActive(ctrl_p))
{
return;
}
setHostCtrl(nullptr);
}
bool LLEmojiHelper::handleKey(const LLUICtrl* ctrl_p, KEY key, MASK mask)
{
if (mHelperHandle.isDead() || !isActive(ctrl_p))
{
return false;
}
return mHelperHandle.get()->handleKey(key, mask, true);
}
void LLEmojiHelper::onCommitEmoji(llwchar emoji)
{
if (!mHostHandle.isDead() && mEmojiCommitCb)
{
mEmojiCommitCb(emoji);
}
}
void LLEmojiHelper::setHostCtrl(LLUICtrl* hostctrl_p)
{
const LLUICtrl* pCurHostCtrl = mHostHandle.get();
if (pCurHostCtrl != hostctrl_p)
{
mHostCtrlFocusLostConn.disconnect();
mHostHandle.markDead();
mEmojiCommitCb = {};
if (!mHelperHandle.isDead())
{
mHelperHandle.get()->closeFloater();
}
if (hostctrl_p)
{
mHostHandle = hostctrl_p->getHandle();
mHostCtrlFocusLostConn = hostctrl_p->setFocusLostCallback(std::bind([&]() { hideHelper(getHostCtrl()); }));
}
}
}

View File

@ -0,0 +1,64 @@
/**
* @file llemojihelper.h
* @brief Header file for LLEmojiHelper
*
* $LicenseInfo:firstyear=2014&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2014, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#pragma once
#include "llhandle.h"
#include "llsingleton.h"
#include <boost/signals2.hpp>
class LLFloater;
class LLUICtrl;
class LLEmojiHelper : public LLSingleton<LLEmojiHelper>
{
LLSINGLETON(LLEmojiHelper) {}
~LLEmojiHelper() override {}
public:
// General
std::string getToolTip(llwchar ch) const;
bool isActive(const LLUICtrl* ctrl_p) const;
static bool isCursorInEmojiCode(const LLWString& wtext, S32 cursor_pos, S32* short_code_pos_p = nullptr);
void showHelper(LLUICtrl* hostctrl_p, S32 local_x, S32 local_y, const std::string& short_code, std::function<void(llwchar)> commit_cb);
void hideHelper(const LLUICtrl* ctrl_p = nullptr);
// Eventing
bool handleKey(const LLUICtrl* ctrl_p, KEY key, MASK mask);
void onCommitEmoji(llwchar emoji);
protected:
LLUICtrl* getHostCtrl() const { return mHostHandle.get(); }
void setHostCtrl(LLUICtrl* hostctrl_p);
private:
LLHandle<LLUICtrl> mHostHandle;
LLHandle<LLFloater> mHelperHandle;
boost::signals2::connection mHostCtrlFocusLostConn;
boost::signals2::connection mHelperCommitConn;
std::function<void(llwchar)> mEmojiCommitCb;
};

View File

@ -1509,14 +1509,24 @@ BOOL LLFloater::isFrontmost()
&& floater_view->getFrontmost() == this);
}
void LLFloater::addDependentFloater(LLFloater* floaterp, BOOL reposition)
void LLFloater::addDependentFloater(LLFloater* floaterp, BOOL reposition, BOOL resize)
{
mDependents.insert(floaterp->getHandle());
floaterp->mDependeeHandle = getHandle();
if (reposition)
{
floaterp->setRect(gFloaterView->findNeighboringPosition(this, floaterp));
LLRect rect = gFloaterView->findNeighboringPosition(this, floaterp);
if (resize)
{
const LLRect& base = getRect();
if (rect.mTop == base.mTop)
rect.mBottom = base.mBottom;
else if (rect.mLeft == base.mLeft)
rect.mRight = base.mRight;
floaterp->reshape(rect.getWidth(), rect.getHeight(), FALSE);
}
floaterp->setRect(rect);
floaterp->setSnapTarget(getHandle());
}
gFloaterView->adjustToFitScreen(floaterp, FALSE, TRUE);
@ -1527,12 +1537,12 @@ void LLFloater::addDependentFloater(LLFloater* floaterp, BOOL reposition)
}
}
void LLFloater::addDependentFloater(LLHandle<LLFloater> dependent, BOOL reposition)
void LLFloater::addDependentFloater(LLHandle<LLFloater> dependent, BOOL reposition, BOOL resize)
{
LLFloater* dependent_floaterp = dependent.get();
if(dependent_floaterp)
{
addDependentFloater(dependent_floaterp, reposition);
addDependentFloater(dependent_floaterp, reposition, resize);
}
}
@ -2488,7 +2498,7 @@ void LLFloaterView::bringToFront(LLFloater* child, BOOL give_focus, BOOL restore
if (mFrontChild == child)
{
if (give_focus && !gFocusMgr.childHasKeyboardFocus(child))
if (give_focus && child->canFocusStealFrontmost() && !gFocusMgr.childHasKeyboardFocus(child))
{
child->setFocus(TRUE);
}
@ -3041,7 +3051,34 @@ void LLFloaterView::syncFloaterTabOrder()
LLFloater* floaterp = dynamic_cast<LLFloater*>(*child_it);
if (gFocusMgr.childHasKeyboardFocus(floaterp))
{
bringToFront(floaterp, FALSE);
if (mFrontChild != floaterp)
{
// Grab a list of the top floaters that want to stay on top of the focused floater
std::list<LLFloater*> listTop;
if (mFrontChild && !mFrontChild->canFocusStealFrontmost())
{
for (LLView* childp : *getChildList())
{
LLFloater* child_floaterp = static_cast<LLFloater*>(childp);
if (child_floaterp->canFocusStealFrontmost())
break;
listTop.push_back(child_floaterp);
}
}
bringToFront(floaterp, FALSE);
// Restore top floaters
if (!listTop.empty())
{
for (LLView* childp : listTop)
{
sendChildToFront(childp);
}
mFrontChild = listTop.back();
}
}
break;
}
}

View File

@ -255,8 +255,8 @@ public:
std::string getShortTitle() const;
virtual void setMinimized(BOOL b);
void moveResizeHandlesToFront();
void addDependentFloater(LLFloater* dependent, BOOL reposition = TRUE);
void addDependentFloater(LLHandle<LLFloater> dependent_handle, BOOL reposition = TRUE);
void addDependentFloater(LLFloater* dependent, BOOL reposition = TRUE, BOOL resize = FALSE);
void addDependentFloater(LLHandle<LLFloater> dependent_handle, BOOL reposition = TRUE, BOOL resize = FALSE);
LLFloater* getDependee() { return (LLFloater*)mDependeeHandle.get(); }
void removeDependentFloater(LLFloater* dependent);
BOOL isMinimized() const { return mMinimized; }
@ -313,6 +313,9 @@ public:
/*virtual*/ void setVisible(BOOL visible); // do not override
/*virtual*/ void onVisibilityChange ( BOOL new_visibility ); // do not override
bool canFocusStealFrontmost() const { return mFocusStealsFrontmost; }
void setFocusStealsFrontmost(bool wants_frontmost) { mFocusStealsFrontmost = wants_frontmost; }
void setFrontmost(BOOL take_focus = TRUE, BOOL restore = TRUE);
virtual void setVisibleAndFrontmost(BOOL take_focus=TRUE, const LLSD& key = LLSD());
@ -481,6 +484,7 @@ private:
BOOL mCanTearOff;
BOOL mCanMinimize;
BOOL mCanClose;
bool mFocusStealsFrontmost = true; // FALSE if we don't want the currently focused floater to cover this floater without user interaction
BOOL mDragOnLeft;
BOOL mResizable;

View File

@ -890,7 +890,7 @@ void LLFolderViewItem::drawLabel(const LLFontGL * font, const F32 x, const F32 y
//
font->renderUTF8(mLabel, 0, x, y, color,
LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
S32_MAX, getRect().getWidth() - (S32) x - mLabelPaddingRight, &right_x, TRUE);
S32_MAX, getRect().getWidth() - (S32) x - mLabelPaddingRight, &right_x, /*use_ellipses*/TRUE, /*use_color*/FALSE);
}
void LLFolderViewItem::draw()
@ -999,7 +999,7 @@ void LLFolderViewItem::draw()
{
suffix_font->renderUTF8( mLabelSuffix, 0, right_x, y, isFadeItem() ? color : (LLColor4)sSuffixColor,
LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
S32_MAX, S32_MAX, &right_x, FALSE );
S32_MAX, S32_MAX, &right_x, /*use_ellipses*/FALSE, /*use_color*/FALSE );
}
//--------------------------------------------------------------------------------//
@ -1011,9 +1011,9 @@ void LLFolderViewItem::draw()
{
F32 match_string_left = text_left + font->getWidthF32(combined_string, 0, filter_offset + filter_string_length) - font->getWidthF32(combined_string, filter_offset, filter_string_length);
F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD;
font->renderUTF8( combined_string, filter_offset, match_string_left, yy,
sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
filter_string_length, S32_MAX, &right_x, FALSE );
font->renderUTF8(combined_string, filter_offset, match_string_left, yy,
sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
filter_string_length, S32_MAX, &right_x, FALSE, FALSE);
}
else
{
@ -1022,8 +1022,9 @@ void LLFolderViewItem::draw()
{
F32 match_string_left = text_left + font->getWidthF32(mLabel, 0, filter_offset + label_filter_length) - font->getWidthF32(mLabel, filter_offset, label_filter_length);
F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD;
font->renderUTF8( mLabel, filter_offset, match_string_left, yy,
sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, label_filter_length, S32_MAX, &right_x, FALSE );
font->renderUTF8(mLabel, filter_offset, match_string_left, yy,
sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
label_filter_length, S32_MAX, &right_x, FALSE, FALSE);
}
S32 suffix_filter_length = label_filter_length > 0 ? filter_string_length - label_filter_length : filter_string_length;
@ -1032,7 +1033,9 @@ void LLFolderViewItem::draw()
S32 suffix_offset = llmax(0, filter_offset - (S32)mLabel.size());
F32 match_string_left = text_left + font->getWidthF32(mLabel, 0, mLabel.size()) + suffix_font->getWidthF32(mLabelSuffix, 0, suffix_offset + suffix_filter_length) - suffix_font->getWidthF32(mLabelSuffix, suffix_offset, suffix_filter_length);
F32 yy = (F32)getRect().getHeight() - suffix_font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD;
suffix_font->renderUTF8( mLabelSuffix, suffix_offset, match_string_left, yy, sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, suffix_filter_length, S32_MAX, &right_x, FALSE );
suffix_font->renderUTF8(mLabelSuffix, suffix_offset, match_string_left, yy, sFilterTextColor,
LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
suffix_filter_length, S32_MAX, &right_x, FALSE, FALSE);
}
}

View File

@ -89,6 +89,7 @@ LLLineEditor::Params::Params()
background_image_disabled("background_image_disabled"),
background_image_focused("background_image_focused"),
bg_image_always_focused("bg_image_always_focused", false),
show_label_focused("show_label_focused", false),
select_on_focus("select_on_focus", false),
revert_on_esc("revert_on_esc", true),
spellcheck("spellcheck", false),
@ -152,6 +153,7 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)
mBgImageDisabled( p.background_image_disabled ),
mBgImageFocused( p.background_image_focused ),
mShowImageFocused( p.bg_image_always_focused ),
mShowLabelFocused( p.show_label_focused ),
mUseBgColor(p.use_bg_color),
mHaveHistory(FALSE),
mReplaceNewlinesWithSpaces( TRUE ),
@ -2068,7 +2070,7 @@ void LLLineEditor::draw()
//draw label if no text is provided
//but we should draw it in a different color
//to give indication that it is not text you typed in
if (0 == mText.length() && mReadOnly)
if (0 == mText.length() && (mReadOnly || mShowLabelFocused))
{
mGLFont->render(mLabel.getWString(), 0,
mTextLeftEdge, (F32)text_bottom,

View File

@ -91,6 +91,7 @@ public:
commit_on_focus_lost,
ignore_tab,
bg_image_always_focused,
show_label_focused,
is_password,
use_bg_color;
@ -395,6 +396,7 @@ protected:
BOOL mReadOnly;
BOOL mShowImageFocused;
BOOL mShowLabelFocused;
bool mUseBgColor;

View File

@ -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 )
{

View File

@ -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);

View File

@ -913,7 +913,7 @@ public:
/* virtual */ LLNotificationPtr add(const std::string& name,
const LLSD& substitutions,
const LLSD& payload,
LLNotificationFunctorRegistry::ResponseFunctor functor);
LLNotificationFunctorRegistry::ResponseFunctor functor) override;
LLNotificationPtr add(const LLNotification::Params& p);
void add(const LLNotificationPtr pNotif);
@ -964,8 +964,8 @@ public:
bool isVisibleByRules(LLNotificationPtr pNotification);
private:
/*virtual*/ void initSingleton();
/*virtual*/ void cleanupSingleton();
/*virtual*/ void initSingleton() override;
/*virtual*/ void cleanupSingleton() override;
void loadPersistentNotifications();

View File

@ -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();
}

View File

@ -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,36 +111,7 @@ void LLScrollingPanelList::removePanel( U32 panel_index )
mPanelList.erase( mPanelList.begin() + panel_index );
}
const S32 GAP_BETWEEN_PANELS = 6;
// 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)
{
LLScrollingPanel *childp = *iter;
total_height += childp->getRect().getHeight() + cur_gap;
max_width = llmax( max_width, childp->getRect().getWidth() );
cur_gap = GAP_BETWEEN_PANELS;
}
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)
{
LLScrollingPanel *childp = *iter;
cur_y -= childp->getRect().getHeight();
childp->translate( -childp->getRect().mLeft, cur_y - childp->getRect().mBottom);
cur_y -= GAP_BETWEEN_PANELS;
}
rearrange();
}
void LLScrollingPanelList::updatePanels(BOOL allow_modify)
@ -162,20 +124,91 @@ void LLScrollingPanelList::updatePanels(BOOL 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();
if (mIsHorizontal || !followsRight())
{
rc.mRight = rc.mLeft + new_width;
}
if (!mIsHorizontal || !followsBottom())
{
rc.mBottom = rc.mTop - new_height;
}
if (rc.mRight != getRect().mRight || rc.mBottom != getRect().mBottom)
{
setRect(rc);
notifySizeChanged();
}
// Reposition each of the child views
S32 pos = mIsHorizontal ? mPadding : rc.getHeight() - mPadding;
for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin();
iter != mPanelList.end(); ++iter)
{
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::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);
}

View File

@ -51,12 +51,18 @@ class LLScrollingPanelList : public LLUICtrl
{
public:
struct Params : public LLInitParam::Block<Params, LLUICtrl::Params>
{};
LLScrollingPanelList(const Params& p)
: LLUICtrl(p)
{}
{
Optional<bool> is_horizontal;
Optional<S32> padding;
Optional<S32> spacing;
Params();
};
LLScrollingPanelList(const Params& p);
static const S32 GAP_BETWEEN_PANELS = 6;
static const S32 DEFAULT_SPACING = 6;
static const S32 DEFAULT_PADDING = 2;
typedef std::deque<LLScrollingPanel*> panel_list_t;
@ -65,11 +71,18 @@ public:
virtual void draw();
void clearPanels();
S32 addPanel( LLScrollingPanel* panel );
void removePanel( LLScrollingPanel* panel );
void removePanel( U32 panel_index );
S32 addPanel(LLScrollingPanel* panel, bool back = false);
void removePanel(LLScrollingPanel* panel);
void removePanel(U32 panel_index);
void updatePanels(BOOL allow_modify);
const panel_list_t& getPanelList() { return mPanelList; }
void rearrange();
const panel_list_t& getPanelList() const { return mPanelList; }
bool getIsHorizontal() const { return mIsHorizontal; }
void setPadding(S32 padding) { mPadding = padding; rearrange(); }
void setSpacing(S32 spacing) { mSpacing = spacing; rearrange(); }
S32 getPadding() const { return mPadding; }
S32 getSpacing() const { return mSpacing; }
private:
void updatePanelVisiblilty();
@ -77,7 +90,11 @@ private:
/**
* Notify parent about size change, makes sense when used inside accordion
*/
void notifySizeChanged(S32 height);
void notifySizeChanged();
bool mIsHorizontal;
S32 mPadding;
S32 mSpacing;
panel_list_t mPanelList;
};

View File

@ -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;
}

View File

@ -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);

View File

@ -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
}

View File

@ -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;

View File

@ -29,6 +29,8 @@
#include "lltextbase.h"
#include "llemojidictionary.h"
#include "llemojihelper.h"
#include "lllocalcliprect.h"
#include "llmenugl.h"
#include "llscrollcontainer.h"
@ -161,10 +163,12 @@ LLTextBase::Params::Params()
line_spacing("line_spacing"),
max_text_length("max_length", 255),
font_shadow("font_shadow"),
text_valign("text_valign"),
wrap("wrap"),
trusted_content("trusted_content", true),
always_show_icons("always_show_icons", false),
use_ellipses("use_ellipses", false),
use_color("use_color", false),
parse_urls("parse_urls", false),
force_urls_external("force_urls_external", false),
parse_highlights("parse_highlights", false)
@ -208,6 +212,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p)
mVPad(p.v_pad),
mHAlign(p.font_halign),
mVAlign(p.font_valign),
mTextVAlign(p.text_valign.isProvided() ? p.text_valign.getValue() : p.font_valign.getValue()),
mLineSpacingMult(p.line_spacing.multiple),
mLineSpacingPixels(p.line_spacing.pixels),
mClip(p.clip),
@ -222,6 +227,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p)
mPlainText ( p.plain_text ),
mWordWrap(p.wrap),
mUseEllipses( p.use_ellipses ),
mUseColor(p.use_color),
mParseHTML(p.parse_urls),
mForceUrlsExternal(p.force_urls_external),
mParseHighlights(p.parse_highlights),
@ -582,7 +588,7 @@ void LLTextBase::drawCursor()
fontp = segmentp->getStyle()->getFont();
fontp->render(text, mCursorPos, cursor_rect,
LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], alpha),
LLFontGL::LEFT, mVAlign,
LLFontGL::LEFT, mTextVAlign,
LLFontGL::NORMAL,
LLFontGL::NO_SHADOW,
1);
@ -896,6 +902,28 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s
}
}
// Insert special segments where necessary (insertSegment takes care of splitting normal text segments around them for us)
{
LLStyleSP emoji_style;
LLEmojiDictionary* ed = LLEmojiDictionary::instanceExists() ? LLEmojiDictionary::getInstance() : NULL;
for (S32 text_kitty = 0, text_len = wstr.size(); text_kitty < text_len; text_kitty++)
{
llwchar code = wstr[text_kitty];
bool isEmoji = ed ? ed->isEmoji(code) : LLStringOps::isEmoji(code);
if (isEmoji)
{
if (!emoji_style)
{
emoji_style = new LLStyle(getStyleParams());
emoji_style->setFont(LLFontGL::getFontEmoji());
}
S32 new_seg_start = pos + text_kitty;
insertSegment(new LLEmojiTextSegment(emoji_style, new_seg_start, new_seg_start + 1, *this));
}
}
}
getViewModel()->getEditableDisplay().insert(pos, wstr);
if ( truncate() )
@ -1979,21 +2007,8 @@ LLTextBase::segment_set_t::const_iterator LLTextBase::getEditableSegIterContaini
LLTextBase::segment_set_t::iterator LLTextBase::getSegIterContaining(S32 index)
{
static LLPointer<LLIndexSegment> index_segment = new LLIndexSegment();
S32 text_len = 0;
if (!useLabel())
{
text_len = getLength();
}
else
{
text_len = mLabel.getWString().length();
}
if (index > text_len) { return mSegments.end(); }
// when there are no segments, we return the end iterator, which must be checked by caller
if (mSegments.size() <= 1) { return mSegments.begin(); }
@ -2007,18 +2022,6 @@ LLTextBase::segment_set_t::const_iterator LLTextBase::getSegIterContaining(S32 i
{
static LLPointer<LLIndexSegment> index_segment = new LLIndexSegment();
S32 text_len = 0;
if (!useLabel())
{
text_len = getLength();
}
else
{
text_len = mLabel.getWString().length();
}
if (index > text_len) { return mSegments.end(); }
// when there are no segments, we return the end iterator, which must be checked by caller
if (mSegments.size() <= 1) { return mSegments.begin(); }
@ -2182,8 +2185,8 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para
S32 start=0,end=0;
LLUrlMatch match;
std::string text = new_text;
while ( LLUrlRegistry::instance().findUrl(text, match,
boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3),isContentTrusted() || mAlwaysShowIcons))
while (LLUrlRegistry::instance().findUrl(text, match,
boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3), isContentTrusted() || mAlwaysShowIcons))
{
start = match.getStart();
end = match.getEnd()+1;
@ -2431,18 +2434,18 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig
LLStyle::Params normal_style_params(style_params);
normal_style_params.font.style("NORMAL");
LLStyleConstSP normal_sp(new LLStyle(normal_style_params));
segments.push_back(new LLOnHoverChangeableTextSegment(sp, normal_sp, segment_start, segment_end, *this ));
segments.push_back(new LLOnHoverChangeableTextSegment(sp, normal_sp, segment_start, segment_end, *this));
}
else
{
segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this ));
segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this));
}
insertStringNoUndo(getLength(), wide_text, &segments);
}
// Set the cursor and scroll position
if( selection_start != selection_end )
if (selection_start != selection_end)
{
mSelectionStart = selection_start;
mSelectionEnd = selection_end;
@ -2450,7 +2453,7 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig
mIsSelecting = was_selecting;
setCursorPos(cursor_pos);
}
else if( cursor_was_at_end )
else if (cursor_was_at_end)
{
setCursorPos(getLength());
}
@ -2462,25 +2465,28 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig
void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only)
{
if (new_text.empty()) return;
if (new_text.empty())
{
return;
}
std::string::size_type start = 0;
std::string::size_type pos = new_text.find("\n",start);
std::string::size_type pos = new_text.find("\n", start);
while(pos!=-1)
while (pos != std::string::npos)
{
if(pos!=start)
if (pos != start)
{
std::string str = std::string(new_text,start,pos-start);
appendAndHighlightTextImpl(str,highlight_part, style_params, underline_on_hover_only);
appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only);
}
appendLineBreakSegment(style_params);
start = pos+1;
pos = new_text.find("\n",start);
pos = new_text.find("\n", start);
}
std::string str = std::string(new_text,start,new_text.length()-start);
appendAndHighlightTextImpl(str,highlight_part, style_params, underline_on_hover_only);
std::string str = std::string(new_text, start, new_text.length() - start);
appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only);
}
@ -3312,12 +3318,13 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele
font->render(text, start,
rect,
color,
LLFontGL::LEFT, mEditor.mVAlign,
LLFontGL::LEFT, mEditor.mTextVAlign,
LLFontGL::NORMAL,
mStyle->getShadowType(),
length,
&right_x,
mEditor.getUseEllipses());
mEditor.getUseEllipses(),
mEditor.getUseColor());
}
rect.mLeft = right_x;
@ -3331,12 +3338,13 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele
font->render(text, start,
rect,
mStyle->getSelectedColor().get(),
LLFontGL::LEFT, mEditor.mVAlign,
LLFontGL::LEFT, mEditor.mTextVAlign,
LLFontGL::NORMAL,
LLFontGL::NO_SHADOW,
length,
&right_x,
mEditor.getUseEllipses());
mEditor.getUseEllipses(),
mEditor.getUseColor());
}
rect.mLeft = right_x;
if( selection_end < seg_end )
@ -3348,12 +3356,13 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele
font->render(text, start,
rect,
color,
LLFontGL::LEFT, mEditor.mVAlign,
LLFontGL::LEFT, mEditor.mTextVAlign,
LLFontGL::NORMAL,
mStyle->getShadowType(),
length,
&right_x,
mEditor.getUseEllipses());
mEditor.getUseEllipses(),
mEditor.getUseColor());
}
return right_x;
}
@ -3584,6 +3593,33 @@ const S32 LLLabelTextSegment::getLength() const
return mEditor.getWlabel().length();
}
//
// 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
//

View File

@ -178,6 +178,18 @@ protected:
/*virtual*/ const S32 getLength() const;
};
// Text segment that represents a single emoji character that has a different style (=font size) than the rest of
// the document it belongs to
class LLEmojiTextSegment : public LLNormalTextSegment
{
public:
LLEmojiTextSegment(LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor);
LLEmojiTextSegment(const LLColor4& color, S32 start, S32 end, LLTextBase& editor, BOOL is_visible = TRUE);
bool canEdit() const override { return false; }
BOOL handleToolTip(S32 x, S32 y, MASK mask) override;
};
// Text segment that changes it's style depending of mouse pointer position ( is it inside or outside segment)
class LLOnHoverChangeableTextSegment : public LLNormalTextSegment
{
@ -316,6 +328,7 @@ public:
plain_text,
wrap,
use_ellipses,
use_color,
parse_urls,
force_urls_external,
parse_highlights,
@ -335,6 +348,8 @@ public:
Optional<LLFontGL::ShadowType> font_shadow;
Optional<LLFontGL::VAlign> text_valign;
Params();
};
@ -394,6 +409,7 @@ public:
// used by LLTextSegment layout code
bool getWordWrap() { return mWordWrap; }
bool getUseEllipses() { return mUseEllipses; }
bool getUseColor() { return mUseColor; }
bool truncate(); // returns true of truncation occurred
bool isContentTrusted() {return mTrustedContent;}
@ -687,8 +703,9 @@ protected:
// configuration
S32 mHPad; // padding on left of text
S32 mVPad; // padding above text
LLFontGL::HAlign mHAlign;
LLFontGL::VAlign mVAlign;
LLFontGL::HAlign mHAlign; // horizontal alignment of the document in its entirety
LLFontGL::VAlign mVAlign; // vertical alignment of the document in its entirety
LLFontGL::VAlign mTextVAlign; // vertical alignment of a text segment within a single line of text
F32 mLineSpacingMult; // multiple of line height used as space for a single line of text (e.g. 1.5 to get 50% padding)
S32 mLineSpacingPixels; // padding between lines
bool mBorderVisible;
@ -697,6 +714,7 @@ protected:
bool mParseHighlights; // highlight user-defined keywords
bool mWordWrap;
bool mUseEllipses;
bool mUseColor;
bool mTrackEnd; // if true, keeps scroll position at end of document during resize
bool mReadOnly;
bool mBGVisible; // render background?

View File

@ -43,6 +43,7 @@
#include "llmath.h"
#include "llclipboard.h"
#include "llemojihelper.h"
#include "llscrollbar.h"
#include "llstl.h"
#include "llstring.h"
@ -238,6 +239,7 @@ LLTextEditor::Params::Params()
default_color("default_color"),
commit_on_focus_lost("commit_on_focus_lost", false),
show_context_menu("show_context_menu"),
show_emoji_helper("show_emoji_helper"),
enable_tooltip_paste("enable_tooltip_paste")
{
addSynonym(prevalidate_callback, "text_type");
@ -258,6 +260,7 @@ LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) :
mTabsToNextField(p.ignore_tab),
mPrevalidateFunc(p.prevalidate_callback()),
mShowContextMenu(p.show_context_menu),
mShowEmojiHelper(p.show_emoji_helper),
mEnableTooltipPaste(p.enable_tooltip_paste),
mPassDelete(FALSE),
mKeepSelectionOnReturn(false)
@ -505,6 +508,15 @@ void LLTextEditor::getSegmentsInRange(LLTextEditor::segment_vec_t& segments_out,
}
}
void LLTextEditor::setShowEmojiHelper(bool show) {
if (!mShowEmojiHelper)
{
LLEmojiHelper::instance().hideHelper(this);
}
mShowEmojiHelper = show;
}
BOOL LLTextEditor::selectionContainsLineBreaks()
{
if (hasSelection())
@ -668,6 +680,27 @@ void LLTextEditor::selectByCursorPosition(S32 prev_cursor_pos, S32 next_cursor_p
endSelection();
}
void LLTextEditor::insertEmoji(llwchar emoji)
{
auto styleParams = LLStyle::Params();
styleParams.font = LLFontGL::getFontEmoji();
auto segment = new LLEmojiTextSegment(new LLStyle(styleParams), mCursorPos, mCursorPos + 1, *this);
insert(mCursorPos, LLWString(1, emoji), false, segment);
setCursorPos(mCursorPos + 1);
}
void LLTextEditor::handleEmojiCommit(llwchar emoji)
{
S32 shortCodePos;
if (LLEmojiHelper::isCursorInEmojiCode(getWText(), mCursorPos, &shortCodePos))
{
remove(shortCodePos, mCursorPos - shortCodePos, true);
setCursorPos(shortCodePos);
insertEmoji(emoji);
}
}
BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask)
{
BOOL handled = FALSE;
@ -934,6 +967,12 @@ BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask)
S32 LLTextEditor::execute( TextCmd* cmd )
{
if (!mReadOnly && mShowEmojiHelper)
{
// Any change to our contents should always hide the helper
LLEmojiHelper::instance().hideHelper(this);
}
S32 delta = 0;
if( cmd->execute(this, &delta) )
{
@ -983,7 +1022,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 +1046,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 +1063,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 +1084,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 +1098,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 +1107,12 @@ void LLTextEditor::removeChar()
{
return;
}
if (mCursorPos > 0)
{
setCursorPos(mCursorPos - 1);
removeChar(mCursorPos);
tryToShowEmojiHelper();
}
else
{
@ -1127,6 +1171,7 @@ void LLTextEditor::addChar(llwchar wc)
}
setCursorPos(mCursorPos + addChar( mCursorPos, wc ));
tryToShowEmojiHelper();
if (!mReadOnly && mAutoreplaceCallback != NULL)
{
@ -1146,6 +1191,23 @@ void LLTextEditor::addChar(llwchar wc)
}
}
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);
}
}
void LLTextEditor::addLineBreakChar(BOOL group_together)
{
if( !getEnabled() )
@ -1778,6 +1840,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 +1886,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;

View File

@ -60,6 +60,7 @@ public:
ignore_tab,
commit_on_focus_lost,
show_context_menu,
show_emoji_helper,
enable_tooltip_paste,
auto_indent;
@ -91,6 +92,9 @@ public:
static S32 spacesPerTab();
void insertEmoji(llwchar emoji);
void handleEmojiCommit(llwchar emoji);
// mousehandler overrides
virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask);
virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask);
@ -202,6 +206,9 @@ public:
void setShowContextMenu(bool show) { mShowContextMenu = show; }
bool getShowContextMenu() const { return mShowContextMenu; }
void setShowEmojiHelper(bool show);
bool getShowEmojiHelper() const { return mShowEmojiHelper; }
void setPassDelete(BOOL b) { mPassDelete = b; }
protected:
@ -248,6 +255,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 +326,7 @@ private:
BOOL mAllowEmbeddedItems;
bool mShowContextMenu;
bool mShowEmojiHelper;
bool mEnableTooltipPaste;
bool mPassDelete;
bool mKeepSelectionOnReturn; // disabling of removing selected text after pressing of Enter

View File

@ -264,7 +264,7 @@ public:
class LLTextInputFilter : public LLQueryFilter, public LLSingleton<LLTextInputFilter>
{
LLSINGLETON_EMPTY_CTOR(LLTextInputFilter);
/*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const
/*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override
{
return filterResult_t(view->isCtrl() && static_cast<const LLUICtrl *>(view)->acceptsTextInput(), TRUE);
}

View File

@ -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);

View File

@ -520,7 +520,7 @@ BOOL LLView::focusNext(LLView::child_list_t & result)
{
next = result.rbegin();
}
if((*next)->isCtrl())
if ((*next)->isCtrl() && ((LLUICtrl*)*next)->hasTabStop())
{
LLUICtrl * ctrl = static_cast<LLUICtrl*>(*next);
ctrl->setFocus(TRUE);
@ -1337,7 +1337,7 @@ void LLView::drawDebugRect()
debug_rect.getWidth(), debug_rect.getHeight());
LLFontGL::getFontSansSerifSmall()->renderUTF8(debug_text, 0, (F32)x, (F32)y, border_color,
LLFontGL::HCENTER, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
S32_MAX, S32_MAX, NULL, FALSE);
S32_MAX, S32_MAX, NULL, /*use_ellipses*/FALSE, /*use_color*/FALSE);
}
}
LLUI::popMatrix();
@ -1966,7 +1966,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 +1990,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());
}

View File

@ -111,7 +111,7 @@ public:
Alternative<std::string> string;
Alternative<U32> flags;
Follows();
Follows();
};
struct Params : public LLInitParam::Block<Params>
@ -658,8 +658,8 @@ public:
// Draw debug rectangles around widgets to help with alignment and spacing
static bool sDebugRects;
static bool sIsRectDirty;
static LLRect sDirtyRect;
static bool sIsRectDirty;
static LLRect sDirtyRect;
// Draw widget names and sizes when drawing debug rectangles, turning this
// off is useful to make the rectangles themselves easier to see.
@ -702,19 +702,15 @@ template <class T> T* LLView::getChild(const std::string& name, BOOL recurse) co
if (!result)
{
result = LLUICtrlFactory::getDefaultWidget<T>(name);
if (!result)
{
LL_ERRS() << "Failed to create dummy " << typeid(T).name() << LL_ENDL;
}
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;
}
// *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);
}

View File

@ -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>

View File

@ -18,6 +18,7 @@ include(DragDrop)
include(EXPAT)
include(FMODSTUDIO)
include(Hunspell)
include(ICU4C)
include(JPEGEncoderBasic)
include(JsonCpp)
include(LLAppearance)
@ -205,6 +206,7 @@ set(viewer_SOURCE_FILES
llfloaterdisplayname.cpp
llfloatereditenvironmentbase.cpp
llfloatereditextdaycycle.cpp
llfloateremojipicker.cpp
llfloaterenvironmentadjust.cpp
llfloaterevent.cpp
llfloaterexperiencepicker.cpp
@ -414,6 +416,7 @@ set(viewer_SOURCE_FILES
llpaneleditsky.cpp
llpaneleditwater.cpp
llpaneleditwearable.cpp
llpanelemojicomplete.cpp
llpanelenvironment.cpp
llpanelexperiencelisteditor.cpp
llpanelexperiencelog.cpp
@ -851,6 +854,7 @@ set(viewer_HEADER_FILES
llfloaterdisplayname.h
llfloatereditenvironmentbase.h
llfloatereditextdaycycle.h
llfloateremojipicker.h
llfloaterenvironmentadjust.h
llfloaterevent.h
llfloaterexperiencepicker.h
@ -1052,6 +1056,7 @@ set(viewer_HEADER_FILES
llpaneleditsky.h
llpaneleditwater.h
llpaneleditwearable.h
llpanelemojicomplete.h
llpanelenvironment.h
llpanelexperiencelisteditor.h
llpanelexperiencelog.h
@ -1911,6 +1916,7 @@ target_link_libraries(${VIEWER_BINARY_NAME}
${LLPHYSICSEXTENSIONS_LIBRARIES}
ll::bugsplat
ll::tracy
ll::icu4c
)
if( TARGET ll::intel_memops )
@ -1924,6 +1930,28 @@ endif()
set(ARTWORK_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE PATH
"Path to artwork files.")
message("Copying fonts")
file(GLOB FONT_FILE_GLOB_LIST
"${AUTOBUILD_INSTALL_DIR}/fonts/*"
)
file(COPY ${FONT_FILE_GLOB_LIST} DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/fonts")
# Copy over the Emoji/shortcodes mapping XML files (and create dependency
# if they are changed, CMake will run again and copy over new versions)
message("Copying Emoji/shortcode mappings")
set(emoji_mapping_src_folder ${AUTOBUILD_INSTALL_DIR}/xui)
set(emoji_mapping_dst_folder ${CMAKE_CURRENT_SOURCE_DIR}/skins/default/xui)
# Note Turkey is missing from this set (not available in Emoji package yet)
set(country_codes "da;de;en;es;fr;it;ja;pl;pt;ru;zh")
foreach(elem ${country_codes})
set(emoji_mapping_src_file
"${emoji_mapping_src_folder}/${elem}/emoji_characters.xml")
set(emoji_mapping_dst_file
"${emoji_mapping_dst_folder}/${elem}/emoji_characters.xml")
configure_file(${emoji_mapping_src_file} ${emoji_mapping_dst_file} COPYONLY)
endforeach()
if (LINUX)
set(product SecondLife-${ARCH}-${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION})

View File

@ -0,0 +1,82 @@
<?xml version="1.0" ?>
<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd">
<array>
<map>
<key>Name</key>
<string>all</string>
<key>Character</key>
<string>🔍</string>
</map>
<map>
<key>Character</key>
<string>😀</string>
<key>Categories</key>
<array>
<string>smileys and emotion</string>
<string>people and body</string>
</array>
</map>
<map>
<key>Character</key>
<string>🥬</string>
<key>Categories</key>
<array>
<string>animals and nature</string>
</array>
</map>
<map>
<key>Character</key>
<string>🍔</string>
<key>Categories</key>
<array>
<string>food and drink</string>
</array>
</map>
<map>
<key>Character</key>
<string>🛩</string>
<key>Categories</key>
<array>
<string>travel and places</string>
</array>
</map>
<map>
<key>Character</key>
<string>🏈</string>
<key>Categories</key>
<array>
<string>activities</string>
</array>
</map>
<map>
<key>Character</key>
<string>💡</string>
<key>Categories</key>
<array>
<string>objects</string>
</array>
</map>
<map>
<key>Character</key>
<string></string>
<key>Categories</key>
<array>
<string>symbols</string>
</array>
</map>
<map>
<key>Name</key>
<string>others</string>
<key>Character</key>
<string>🌂</string>
</map>
<map>
<key>Name</key>
<string>skip</string>
<key>Categories</key>
<array>
<string>components</string>
</array>
</map>
</array>
</llsd>

View File

@ -1,99 +0,0 @@
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
Bitstream Vera Fonts Copyright
------------------------------
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of the fonts accompanying this license ("Fonts") and associated
documentation files (the "Font Software"), to reproduce and distribute the
Font Software, including without limitation the rights to use, copy, merge,
publish, distribute, and/or sell copies of the Font Software, and to permit
persons to whom the Font Software is furnished to do so, subject to the
following conditions:
The above copyright and trademark notices and this permission notice shall
be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular
the designs of glyphs or characters in the Fonts may be modified and
additional glyphs or characters may be added to the Fonts, only if the fonts
are renamed to names not containing either the words "Bitstream" or the word
"Vera".
This License becomes null and void to the extent applicable to Fonts or Font
Software that has been modified and is distributed under the "Bitstream
Vera" names.
The Font Software may be sold as part of a larger software package but no
copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
FONT SOFTWARE.
Except as contained in this notice, the names of Gnome, the Gnome
Foundation, and Bitstream Inc., shall not be used in advertising or
otherwise to promote the sale, use or other dealings in this Font Software
without prior written authorization from the Gnome Foundation or Bitstream
Inc., respectively. For further information, contact: fonts at gnome dot
org.
Arev Fonts Copyright
------------------------------
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining
a copy of the fonts accompanying this license ("Fonts") and
associated documentation files (the "Font Software"), to reproduce
and distribute the modifications to the Bitstream Vera Font Software,
including without limitation the rights to use, copy, merge, publish,
distribute, and/or sell copies of the Font Software, and to permit
persons to whom the Font Software is furnished to do so, subject to
the following conditions:
The above copyright and trademark notices and this permission notice
shall be included in all copies of one or more of the Font Software
typefaces.
The Font Software may be modified, altered, or added to, and in
particular the designs of glyphs or characters in the Fonts may be
modified and additional glyphs or characters may be added to the
Fonts, only if the fonts are renamed to names not containing either
the words "Tavmjong Bah" or the word "Arev".
This License becomes null and void to the extent applicable to Fonts
or Font Software that has been modified and is distributed under the
"Tavmjong Bah Arev" names.
The Font Software may be sold as part of a larger software package but
no copy of one or more of the Font Software typefaces may be sold by
itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the name of Tavmjong Bah shall not
be used in advertising or otherwise to promote the sale, use or other
dealings in this Font Software without prior written authorization
from Tavmjong Bah. For further information, contact: tavmjong @ free
. fr.
$Id: LICENSE 2133 2007-11-28 02:46:28Z lechimp $

Binary file not shown.

View File

@ -203,7 +203,7 @@ public:
void setSettings(const LLAutoReplaceSettings& settings);
private:
/*virtual*/ void initSingleton();
/*virtual*/ void initSingleton() override;
LLAutoReplaceSettings mSettings; ///< configuration information

View File

@ -46,7 +46,7 @@ class LLChannelManager : public LLSingleton<LLChannelManager>
LLSINGLETON(LLChannelManager);
virtual ~LLChannelManager();
void cleanupSingleton();
void cleanupSingleton() override;
public:

View File

@ -1096,6 +1096,8 @@ LLChatHistory::LLChatHistory(const LLChatHistory::Params& p)
editor_params.enabled = false; // read only
editor_params.show_context_menu = "true";
editor_params.trusted_content = false;
editor_params.text_valign = LLFontGL::VAlign::VCENTER;
editor_params.use_color = true;
mEditor = LLUICtrlFactory::create<LLTextEditor>(editor_params, this);
mEditor->setIsFriendCallback(LLAvatarActions::isFriend);
mEditor->setIsObjectBlockedCallback(boost::bind(&LLMuteList::isMuted, LLMuteList::getInstance(), _1, _2, 0));
@ -1213,9 +1215,7 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
llassert(mEditor);
if (!mEditor)
{
return;
}
bool from_me = chat.mFromID == gAgent.getID();
mEditor->setPlainText(use_plain_text_chat_history);
@ -1225,26 +1225,16 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
mUnreadChatSources.insert(chat.mFromName);
mMoreChatPanel->setVisible(TRUE);
std::string chatters;
for (unread_chat_source_t::iterator it = mUnreadChatSources.begin();
it != mUnreadChatSources.end();)
for (const std::string& source : mUnreadChatSources)
{
chatters += *it;
if (++it != mUnreadChatSources.end())
{
chatters += ", ";
}
chatters += chatters.size() ? ", " + source : source;
}
LLStringUtil::format_map_t args;
args["SOURCES"] = chatters;
if (mUnreadChatSources.size() == 1)
{
mMoreChatText->setValue(LLTrans::getString("unread_chat_single", args));
}
else
{
mMoreChatText->setValue(LLTrans::getString("unread_chat_multiple", args));
}
std::string xml_desc = mUnreadChatSources.size() == 1 ?
"unread_chat_single" : "unread_chat_multiple";
mMoreChatText->setValue(LLTrans::getString(xml_desc, args));
S32 height = mMoreChatText->getTextPixelHeight() + 5;
mMoreChatPanel->reshape(mMoreChatPanel->getRect().getWidth(), height);
}
@ -1292,11 +1282,11 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
body_message_params.font.style = "ITALIC";
}
if(chat.mChatType == CHAT_TYPE_WHISPER)
if (chat.mChatType == CHAT_TYPE_WHISPER)
{
body_message_params.font.style = "ITALIC";
}
else if(chat.mChatType == CHAT_TYPE_SHOUT)
else if (chat.mChatType == CHAT_TYPE_SHOUT)
{
body_message_params.font.style = "BOLD";
}
@ -1343,10 +1333,10 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
}
// names showing
if (args["show_names_for_p2p_conv"].asBoolean() && utf8str_trim(chat.mFromName).size() != 0)
if (args["show_names_for_p2p_conv"].asBoolean() && utf8str_trim(chat.mFromName).size())
{
// Don't hotlink any messages from the system (e.g. "Second Life:"), so just add those in plain text.
if ( chat.mSourceType == CHAT_SOURCE_OBJECT && chat.mFromID.notNull())
if (chat.mSourceType == CHAT_SOURCE_OBJECT && chat.mFromID.notNull())
{
// for object IMs, create a secondlife:///app/objectim SLapp
std::string url = LLViewerChat::getSenderSLURL(chat, args);
@ -1406,36 +1396,27 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
&& mIsLastMessageFromLog == message_from_log) //distinguish between current and previous chat session's histories
{
view = getSeparator();
p.top_pad = mTopSeparatorPad;
p.bottom_pad = mBottomSeparatorPad;
if (!view)
{
// Might be wiser to make this LL_ERRS, getSeparator() should work in case of correct instalation.
LL_WARNS() << "Failed to create separator from " << mMessageSeparatorFilename << ": can't append to history" << LL_ENDL;
return;
}
p.top_pad = mTopSeparatorPad;
p.bottom_pad = mBottomSeparatorPad;
}
else
{
view = getHeader(chat, name_params, args);
if (mEditor->getLength() == 0)
p.top_pad = 0;
else
p.top_pad = mTopHeaderPad;
if (teleport_separator)
{
p.bottom_pad = mBottomSeparatorPad;
}
else
{
p.bottom_pad = mBottomHeaderPad;
}
if (!view)
{
LL_WARNS() << "Failed to create header from " << mMessageHeaderFilename << ": can't append to history" << LL_ENDL;
return;
}
if (!view)
{
LL_WARNS() << "Failed to create header from " << mMessageHeaderFilename << ": can't append to history" << LL_ENDL;
return;
}
p.top_pad = mEditor->getLength() ? mTopHeaderPad : 0;
p.bottom_pad = teleport_separator ? mBottomSeparatorPad : mBottomHeaderPad;
}
p.view = view;
@ -1508,11 +1489,10 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
}
}
// usual messages showing
else if(!teleport_separator)
else if (!teleport_separator)
{
std::string message = irc_me ? chat.mText.substr(3) : chat.mText;
//MESSAGE TEXT PROCESSING
//*HACK getting rid of redundant sender names in system notifications sent using sender name (see EXT-5010)
if (use_plain_text_chat_history && !from_me && chat.mFromID.notNull())

View File

@ -43,11 +43,11 @@ class LLChicletBar
public:
BOOL postBuild();
BOOL postBuild() override;
LLChicletPanel* getChicletPanel() { return mChicletPanel; }
/*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent);
/*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent) override;
/**

View File

@ -125,11 +125,11 @@ public:
void removeObserver(LLConversationLogObserver* observer);
// LLIMSessionObserver triggers
virtual void sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, BOOL has_offline_msg);
virtual void sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) {}; // Stub
virtual void sessionRemoved(const LLUUID& session_id){} // Stub
virtual void sessionVoiceOrIMStarted(const LLUUID& session_id){}; // Stub
virtual void sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id){}; // Stub
virtual void sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, BOOL has_offline_msg) override;
virtual void sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) override {}; // Stub
virtual void sessionRemoved(const LLUUID& session_id) override{} // Stub
virtual void sessionVoiceOrIMStarted(const LLUUID& session_id) override{}; // Stub
virtual void sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id) override{}; // Stub
void notifyObservers();

View File

@ -88,7 +88,7 @@ public:
mStyle->getShadowType(),
end - start, draw_rect.getWidth(),
&right_x,
mEditor.getUseEllipses());
mEditor.getUseEllipses(), mEditor.getUseColor());
return right_x;
}
/*virtual*/ bool canEdit() const { return false; }

View File

@ -101,7 +101,7 @@ class LLFeatureManager : public LLFeatureList, public LLSingleton<LLFeatureManag
~LLFeatureManager() {cleanupFeatureTables();}
// initialize this by loading feature table and gpu table
void initSingleton();
void initSingleton() override;
public:

View File

@ -741,7 +741,6 @@ void LLFloaterAvatarPicker::processResponse(const LLUUID& query_id, const LLSD&
}
}
//static
void LLFloaterAvatarPicker::editKeystroke(LLLineEditor* caller, void* user_data)
{
getChildView("Find")->setEnabled(caller->getText().size() > 0);

View File

@ -0,0 +1,926 @@
/**
* @file llfloateremojipicker.cpp
*
* $LicenseInfo:firstyear=2003&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, 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 "llviewerprecompiledheaders.h"
#include "llfloateremojipicker.h"
#include "llappviewer.h"
#include "llbutton.h"
#include "llcombobox.h"
#include "llemojidictionary.h"
#include "llfiltereditor.h"
#include "llfloaterreg.h"
#include "llkeyboard.h"
#include "llscrollcontainer.h"
#include "llscrollingpanellist.h"
#include "llscrolllistctrl.h"
#include "llscrolllistitem.h"
#include "llsdserialize.h"
#include "lltextbox.h"
#include "llviewerchat.h"
namespace {
// The following variables and constants are used for storing the floater state
// between different lifecycles of the floater and different sissions of the viewer
// Floater state related variables
static U32 sSelectedGroupIndex = 0;
static std::string sFilterPattern;
static std::list<llwchar> sRecentlyUsed;
static std::list<std::pair<llwchar, U32>> sFrequentlyUsed;
// State file related values
static std::string sStateFileName;
static const std::string sKeySelectedGroupIndex("SelectedGroupIndex");
static const std::string sKeyFilterPattern("FilterPattern");
static const std::string sKeyRecentlyUsed("RecentlyUsed");
static const std::string sKeyFrequentlyUsed("FrequentlyUsed");
}
class LLEmojiGridRow : public LLScrollingPanel
{
public:
LLEmojiGridRow(const LLPanel::Params& panel_params,
const LLScrollingPanelList::Params& list_params)
: LLScrollingPanel(panel_params)
, mList(new LLScrollingPanelList(list_params))
{
addChild(mList);
}
virtual void updatePanel(BOOL allow_modify) override {}
public:
LLScrollingPanelList* mList;
};
class LLEmojiGridDivider : public LLScrollingPanel
{
public:
LLEmojiGridDivider(const LLPanel::Params& panel_params, std::string text)
: LLScrollingPanel(panel_params)
, mText(utf8string_to_wstring(text))
{
}
virtual void draw() override
{
LLScrollingPanel::draw();
F32 x = 4; // padding-left
F32 y = getRect().getHeight() / 2;
LLFontGL::getFontSansSerif()->render(
mText, // wstr
0, // begin_offset
x, // x
y, // y
LLColor4::white, // color
LLFontGL::LEFT, // halign
LLFontGL::VCENTER, // valign
LLFontGL::NORMAL, // style
LLFontGL::DROP_SHADOW_SOFT, // shadow
mText.size(), // max_chars
S32_MAX, // max_pixels
nullptr, // right_x
false, // use_ellipses
true); // use_color
}
virtual void updatePanel(BOOL allow_modify) override {}
private:
const LLWString mText;
};
class LLEmojiGridIcon : public LLScrollingPanel
{
public:
LLEmojiGridIcon(
const LLPanel::Params& panel_params
, const LLEmojiDescriptor* descr
, std::string category)
: LLScrollingPanel(panel_params)
, mDescr(descr)
, mText(LLWString(1, descr->Character))
{
}
virtual void draw() override
{
LLScrollingPanel::draw();
F32 x = getRect().getWidth() / 2;
F32 y = getRect().getHeight() / 2;
LLFontGL::getFontEmoji()->render(
mText, // wstr
0, // begin_offset
x, // x
y, // y
LLColor4::white, // color
LLFontGL::HCENTER, // halign
LLFontGL::VCENTER, // valign
LLFontGL::NORMAL, // style
LLFontGL::DROP_SHADOW_SOFT, // shadow
1, // max_chars
S32_MAX, // max_pixels
nullptr, // right_x
false, // use_ellipses
true); // use_color
}
virtual void updatePanel(BOOL allow_modify) override {}
const LLEmojiDescriptor* getDescr() const { return mDescr; }
llwchar getEmoji() const { return mDescr->Character; }
LLWString getText() const { return mText; }
private:
const LLEmojiDescriptor* mDescr;
const LLWString mText;
};
class LLEmojiPreviewPanel : public LLPanel
{
public:
LLEmojiPreviewPanel()
: LLPanel()
{
}
void setEmoji(const LLEmojiDescriptor* descr)
{
mDescr = descr;
if (!mDescr)
return;
mEmojiText = LLWString(1, descr->Character);
}
virtual void draw() override
{
LLPanel::draw();
if (!mDescr)
return;
S32 clientHeight = getRect().getHeight();
S32 clientWidth = getRect().getWidth();
S32 iconWidth = clientHeight;
F32 centerX = 0.5f * iconWidth;
F32 centerY = 0.5f * clientHeight;
drawIcon(centerX, centerY - 1, iconWidth);
static LLColor4 defaultColor(0.75f, 0.75f, 0.75f, 1.0f);
LLColor4 textColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", defaultColor);
S32 max_pixels = clientWidth - iconWidth;
size_t count = mDescr->ShortCodes.size();
if (count == 1)
{
drawName(mDescr->ShortCodes.front(), iconWidth, centerY, max_pixels, textColor);
}
else if (count > 1)
{
F32 quarterY = 0.5f * centerY;
drawName(mDescr->ShortCodes.front(), iconWidth, centerY + quarterY, max_pixels, textColor);
drawName(*++mDescr->ShortCodes.begin(), iconWidth, quarterY, max_pixels, textColor);
}
}
protected:
void drawIcon(F32 x, F32 y, S32 max_pixels)
{
LLFontGL::getFontEmojiHuge()->render(
mEmojiText, // wstr
0, // begin_offset
x, // x
y, // y
LLColor4::white, // color
LLFontGL::HCENTER, // halign
LLFontGL::VCENTER, // valign
LLFontGL::NORMAL, // style
LLFontGL::DROP_SHADOW_SOFT, // shadow
1, // max_chars
max_pixels, // max_pixels
nullptr, // right_x
false, // use_ellipses
true); // use_color
}
void drawName(std::string name, F32 x, F32 y, S32 max_pixels, LLColor4& color)
{
LLFontGL::getFontEmoji()->renderUTF8(
name, // wstr
0, // begin_offset
x, // x
y, // y
color, // color
LLFontGL::LEFT, // halign
LLFontGL::VCENTER, // valign
LLFontGL::NORMAL, // style
LLFontGL::DROP_SHADOW_SOFT, // shadow
-1, // max_chars
max_pixels, // max_pixels
nullptr, // right_x
true, // use_ellipses
false); // use_color
}
private:
const LLEmojiDescriptor* mDescr { nullptr };
LLWString mEmojiText;
};
LLFloaterEmojiPicker* LLFloaterEmojiPicker::getInstance()
{
LLFloaterEmojiPicker* floater = LLFloaterReg::getTypedInstance<LLFloaterEmojiPicker>("emoji_picker");
if (!floater)
LL_ERRS() << "Cannot instantiate emoji picker" << LL_ENDL;
return floater;
}
LLFloaterEmojiPicker* LLFloaterEmojiPicker::showInstance(pick_callback_t pick_callback, close_callback_t close_callback)
{
LLFloaterEmojiPicker* floater = getInstance();
floater->show(pick_callback, close_callback);
return floater;
}
void LLFloaterEmojiPicker::show(pick_callback_t pick_callback, close_callback_t close_callback)
{
mEmojiPickCallback = pick_callback;
mFloaterCloseCallback = close_callback;
openFloater(mKey);
setFocus(TRUE);
}
LLFloaterEmojiPicker::LLFloaterEmojiPicker(const LLSD& key)
: LLFloater(key)
{
loadState();
}
BOOL LLFloaterEmojiPicker::postBuild()
{
// Should be initialized first
mPreview = new LLEmojiPreviewPanel();
mPreview->setVisible(FALSE);
addChild(mPreview);
mDummy = getChild<LLTextBox>("Dummy");
mGroups = getChild<LLPanel>("Groups");
mBadge = getChild<LLPanel>("Badge");
mFilter = getChild<LLFilterEditor>("Filter");
mFilter->setKeystrokeCallback([this](LLUICtrl*, const LLSD&) { onFilterChanged(); });
mFilter->setTextChangedCallback([this](LLUICtrl*, const LLSD&) { onFilterChanged(); });
mFilter->setText(sFilterPattern);
mEmojiScroll = getChild<LLScrollContainer>("EmojiGridContainer");
mEmojiScroll->setMouseEnterCallback([this](LLUICtrl*, const LLSD&) { onGridMouseEnter(); });
mEmojiScroll->setMouseLeaveCallback([this](LLUICtrl*, const LLSD&) { onGridMouseLeave(); });
mEmojiGrid = getChild<LLScrollingPanelList>("EmojiGrid");
fillGroups();
fillEmojis();
return TRUE;
}
void LLFloaterEmojiPicker::dirtyRect()
{
super::dirtyRect();
if (!mFilter)
return;
const S32 HPADDING = 4;
const S32 VOFFSET = 12;
LLRect rect(HPADDING, mDummy->getRect().mTop + 6, getRect().getWidth() - HPADDING, VOFFSET);
if (mPreview->getRect() != rect)
{
mPreview->setRect(rect);
}
if (mEmojiScroll && mEmojiScroll->getRect().getWidth() != mRecentGridWidth)
{
moveGroups();
fillEmojis(true);
}
}
LLFloaterEmojiPicker::~LLFloaterEmojiPicker()
{
gFocusMgr.releaseFocusIfNeeded( this );
}
void LLFloaterEmojiPicker::fillGroups()
{
LLButton::Params params;
params.font = LLFontGL::getFontEmoji();
LLRect rect;
rect.mTop = mGroups->getRect().getHeight();
rect.mBottom = mBadge->getRect().getHeight();
const std::vector<LLEmojiGroup>& groups = LLEmojiDictionary::instance().getGroups();
for (const LLEmojiGroup& group : groups)
{
LLButton* button = LLUICtrlFactory::create<LLButton>(params);
button->setClickedCallback([this](LLUICtrl* ctrl, const LLSD&) { onGroupButtonClick(ctrl); });
button->setMouseEnterCallback([this](LLUICtrl* ctrl, const LLSD&) { onGroupButtonMouseEnter(ctrl); });
button->setMouseLeaveCallback([this](LLUICtrl* ctrl, const LLSD&) { onGroupButtonMouseLeave(ctrl); });
button->setRect(rect);
button->setLabel(LLUIString(LLWString(1, group.Character)));
if (mGroupButtons.size() == sSelectedGroupIndex)
{
button->setToggleState(TRUE);
button->setUseFontColor(TRUE);
}
mGroupButtons.push_back(button);
mGroups->addChild(button);
}
moveGroups();
}
void LLFloaterEmojiPicker::moveGroups()
{
const std::vector<LLEmojiGroup>& groups = LLEmojiDictionary::instance().getGroups();
if (groups.empty())
return;
int badgeWidth = mGroups->getRect().getWidth() / groups.size();
if (badgeWidth == mRecentBadgeWidth)
return;
mRecentBadgeWidth = badgeWidth;
for (int i = 0; i < mGroupButtons.size(); ++i)
{
LLRect rect = mGroupButtons[i]->getRect();
rect.mLeft = badgeWidth * i;
rect.mRight = rect.mLeft + badgeWidth;
mGroupButtons[i]->setRect(rect);
}
LLRect rect = mBadge->getRect();
rect.mLeft = badgeWidth * sSelectedGroupIndex;
rect.mRight = rect.mLeft + badgeWidth;
mBadge->setRect(rect);
}
void LLFloaterEmojiPicker::fillEmojis(bool fromResize)
{
mRecentGridWidth = mEmojiScroll->getRect().getWidth();
S32 scrollbarSize = mEmojiScroll->getSize();
if (scrollbarSize < 0)
{
static LLUICachedControl<S32> scrollbar_size_control("UIScrollbarSize", 0);
scrollbarSize = scrollbar_size_control;
}
const S32 clientWidth = mRecentGridWidth - scrollbarSize - mEmojiScroll->getBorderWidth() * 2;
const S32 gridPadding = mEmojiGrid->getPadding();
const S32 iconSpacing = mEmojiGrid->getSpacing();
const S32 rowWidth = clientWidth - gridPadding * 2;
const S32 iconSize = 28; // icon width and height
const S32 maxIcons = llmax(1, (rowWidth + iconSpacing) / (iconSize + iconSpacing));
// Optimization: don't rearrange for different widths with the same maxIcons
if (fromResize && (maxIcons == mRecentMaxIcons))
return;
mRecentMaxIcons = maxIcons;
mHoveredIcon = nullptr;
mEmojiGrid->clearPanels();
mPreview->setEmoji(nullptr);
if (mEmojiGrid->getRect().getWidth() != clientWidth)
{
LLRect rect = mEmojiGrid->getRect();
rect.mRight = rect.mLeft + clientWidth;
mEmojiGrid->setRect(rect);
}
LLPanel::Params row_panel_params;
row_panel_params.rect = LLRect(0, iconSize, rowWidth, 0);
LLScrollingPanelList::Params row_list_params;
row_list_params.rect = row_panel_params.rect;
row_list_params.is_horizontal = TRUE;
row_list_params.padding = 0;
row_list_params.spacing = iconSpacing;
LLPanel::Params icon_params;
LLRect icon_rect(0, iconSize, iconSize, 0);
static LLColor4 defaultColor(0.75f, 0.75f, 0.75f, 1.0f);
LLColor4 bgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", defaultColor);
auto matchesPattern = [](const LLEmojiDescriptor* descr) -> bool
{
for (const std::string& shortCode : descr->ShortCodes)
if (shortCode.find(sFilterPattern) != std::string::npos)
return true;
return false;
};
auto listCategory = [&](std::string category, const std::vector<const LLEmojiDescriptor*>& emojis, int maxRows = 0)
{
int rowCount = 0;
int iconIndex = 0;
bool showDivider = true;
bool mixedFolder = maxRows;
LLEmojiGridRow* row = nullptr;
if (!mixedFolder && !isupper(category.front()))
{
LLStringUtil::capitalize(category);
}
for (const LLEmojiDescriptor* descr : emojis)
{
if (sFilterPattern.empty() || matchesPattern(descr))
{
// Place a category title if needed
if (showDivider)
{
LLEmojiGridDivider* div = new LLEmojiGridDivider(row_panel_params, category);
mEmojiGrid->addPanel(div, true);
showDivider = false;
}
// Place a new row each (maxIcons) icons
if (!(iconIndex % maxIcons))
{
if (maxRows && ++rowCount > maxRows)
break;
row = new LLEmojiGridRow(row_panel_params, row_list_params);
mEmojiGrid->addPanel(row, true);
}
// Place a new icon to the current row
LLEmojiGridIcon* icon = new LLEmojiGridIcon(icon_params, descr, mixedFolder ? LLStringUtil::capitalize(descr->Category) : category);
icon->setMouseEnterCallback([this](LLUICtrl* ctrl, const LLSD&) { onEmojiMouseEnter(ctrl); });
icon->setMouseLeaveCallback([this](LLUICtrl* ctrl, const LLSD&) { onEmojiMouseLeave(ctrl); });
icon->setMouseDownCallback([this](LLUICtrl* ctrl, S32, S32, MASK) { onEmojiMouseDown(ctrl); });
icon->setMouseUpCallback([this](LLUICtrl* ctrl, S32, S32, MASK) { onEmojiMouseUp(ctrl); });
icon->setBackgroundColor(bgColor);
icon->setBackgroundOpaque(1);
icon->setRect(icon_rect);
row->mList->addPanel(icon, true);
iconIndex++;
}
}
};
const std::vector<LLEmojiGroup>& groups = LLEmojiDictionary::instance().getGroups();
const LLEmojiDictionary::emoji2descr_map_t& emoji2descr = LLEmojiDictionary::instance().getEmoji2Descr();
const LLEmojiDictionary::cat2descrs_map_t& category2Descr = LLEmojiDictionary::instance().getCategory2Descrs();
if (!sSelectedGroupIndex)
{
std::vector<const LLEmojiDescriptor*> recentlyUsed;
for (llwchar emoji : sRecentlyUsed)
{
auto it = emoji2descr.find(emoji);
if (it != emoji2descr.end())
{
recentlyUsed.push_back(it->second);
}
}
listCategory(getString("title_for_recently_used"), recentlyUsed, 1);
std::vector<const LLEmojiDescriptor*> frequentlyUsed;
for (auto& emoji : sFrequentlyUsed)
{
auto it = emoji2descr.find(emoji.first);
if (it != emoji2descr.end())
{
frequentlyUsed.push_back(it->second);
}
}
listCategory(getString("title_for_frequently_used"), frequentlyUsed, 1);
// List all groups
for (const LLEmojiGroup& group : groups)
{
// List all categories in group
for (const std::string& category : group.Categories)
{
// List all emojis in category
const LLEmojiDictionary::cat2descrs_map_t::const_iterator& item = category2Descr.find(category);
if (item != category2Descr.end())
{
listCategory(category, item->second);
}
}
}
}
else
{
// List all categories in the selected group
for (const std::string& category : groups[sSelectedGroupIndex].Categories)
{
// List all emojis in category
const LLEmojiDictionary::cat2descrs_map_t::const_iterator& item = category2Descr.find(category);
if (item != category2Descr.end())
{
listCategory(category, item->second);
}
}
}
}
void LLFloaterEmojiPicker::onGroupButtonClick(LLUICtrl* ctrl)
{
if (LLButton* button = dynamic_cast<LLButton*>(ctrl))
{
if (button == mGroupButtons[sSelectedGroupIndex] || button->getToggleState())
return;
auto it = std::find(mGroupButtons.begin(), mGroupButtons.end(), button);
if (it == mGroupButtons.end())
return;
mGroupButtons[sSelectedGroupIndex]->setUseFontColor(FALSE);
mGroupButtons[sSelectedGroupIndex]->setToggleState(FALSE);
sSelectedGroupIndex = it - mGroupButtons.begin();
mGroupButtons[sSelectedGroupIndex]->setToggleState(TRUE);
mGroupButtons[sSelectedGroupIndex]->setUseFontColor(TRUE);
LLRect rect = mBadge->getRect();
rect.mLeft = button->getRect().mLeft;
rect.mRight = button->getRect().mRight;
mBadge->setRect(rect);
mFilter->setFocus(TRUE);
fillEmojis();
}
}
void LLFloaterEmojiPicker::onFilterChanged()
{
sFilterPattern = mFilter->getText();
fillEmojis();
}
void LLFloaterEmojiPicker::onGridMouseEnter()
{
mDummy->setVisible(FALSE);
mPreview->setEmoji(nullptr);
mPreview->setVisible(TRUE);
}
void LLFloaterEmojiPicker::onGridMouseLeave()
{
mPreview->setVisible(FALSE);
mDummy->setVisible(TRUE);
}
void LLFloaterEmojiPicker::onGroupButtonMouseEnter(LLUICtrl* ctrl)
{
if (LLButton* button = dynamic_cast<LLButton*>(ctrl))
{
button->setUseFontColor(TRUE);
}
}
void LLFloaterEmojiPicker::onGroupButtonMouseLeave(LLUICtrl* ctrl)
{
if (LLButton* button = dynamic_cast<LLButton*>(ctrl))
{
button->setUseFontColor(button->getToggleState());
}
}
void LLFloaterEmojiPicker::onEmojiMouseEnter(LLUICtrl* ctrl)
{
if (ctrl)
{
if (mHoveredIcon && mHoveredIcon != ctrl)
{
unselectGridIcon(mHoveredIcon);
}
selectGridIcon(ctrl);
mHoveredIcon = ctrl;
}
}
void LLFloaterEmojiPicker::onEmojiMouseLeave(LLUICtrl* ctrl)
{
if (ctrl)
{
if (ctrl == mHoveredIcon)
{
unselectGridIcon(ctrl);
}
}
}
void LLFloaterEmojiPicker::onEmojiMouseDown(LLUICtrl* ctrl)
{
if (getSoundFlags() & MOUSE_DOWN)
{
make_ui_sound("UISndClick");
}
}
void LLFloaterEmojiPicker::onEmojiMouseUp(LLUICtrl* ctrl)
{
if (getSoundFlags() & MOUSE_UP)
{
make_ui_sound("UISndClickRelease");
}
if (mEmojiPickCallback)
{
if (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(ctrl))
{
if (mEmojiPickCallback)
{
mEmojiPickCallback(icon->getEmoji());
}
}
}
}
void LLFloaterEmojiPicker::selectGridIcon(LLUICtrl* ctrl)
{
if (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(ctrl))
{
icon->setBackgroundVisible(TRUE);
mPreview->setEmoji(icon->getDescr());
}
}
void LLFloaterEmojiPicker::unselectGridIcon(LLUICtrl* ctrl)
{
if (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(ctrl))
{
icon->setBackgroundVisible(FALSE);
mPreview->setEmoji(nullptr);
}
}
// virtual
BOOL LLFloaterEmojiPicker::handleKeyHere(KEY key, MASK mask)
{
if (mask == MASK_NONE)
{
switch (key)
{
case KEY_ESCAPE:
closeFloater();
return TRUE;
}
}
return LLFloater::handleKeyHere(key, mask);
}
// virtual
void LLFloaterEmojiPicker::closeFloater(bool app_quitting)
{
saveState();
LLFloater::closeFloater(app_quitting);
if (mFloaterCloseCallback)
{
mFloaterCloseCallback();
}
}
// static
std::list<llwchar>& LLFloaterEmojiPicker::getRecentlyUsed()
{
loadState();
return sRecentlyUsed;
}
// static
void LLFloaterEmojiPicker::onEmojiUsed(llwchar emoji)
{
// Update sRecentlyUsed
auto itr = std::find(sRecentlyUsed.begin(), sRecentlyUsed.end(), emoji);
if (itr == sRecentlyUsed.end())
{
sRecentlyUsed.push_front(emoji);
}
else if (itr != sRecentlyUsed.begin())
{
sRecentlyUsed.erase(itr);
sRecentlyUsed.push_front(emoji);
}
// Increment and reorder sFrequentlyUsed
auto itf = sFrequentlyUsed.begin();
while (itf != sFrequentlyUsed.end())
{
if (itf->first == emoji)
{
itf->second++;
while (itf != sFrequentlyUsed.begin())
{
auto prior = itf;
prior--;
if (prior->second > itf->second)
break;
prior->swap(*itf);
itf = prior;
}
break;
}
itf++;
}
// Append new if not found
if (itf == sFrequentlyUsed.end())
sFrequentlyUsed.push_back(std::make_pair(emoji, 1));
}
// static
void LLFloaterEmojiPicker::onRecentlyUsedChanged()
{
if (sSelectedGroupIndex)
return;
if (LLFloaterEmojiPicker* picker = getInstance())
{
picker->fillEmojis();
}
}
// static
void LLFloaterEmojiPicker::loadState()
{
if (!sStateFileName.empty())
return; // Already loaded
sStateFileName = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "emoji_floater_state.xml");
llifstream file;
file.open(sStateFileName.c_str());
if (!file.is_open())
{
LL_WARNS() << "Emoji floater state file is missing or inaccessible: " << sStateFileName << LL_ENDL;
return;
}
LLSD state;
LLSDSerialize::fromXML(state, file);
if (state.isUndefined())
{
LL_WARNS() << "Emoji floater state file is missing or ill-formed: " << sStateFileName << LL_ENDL;
return;
}
sSelectedGroupIndex = state[sKeySelectedGroupIndex].asInteger();
sFilterPattern = state[sKeyFilterPattern].asString();
// Load and parse sRecentlyUsed
std::string recentlyUsed = state[sKeyRecentlyUsed];
std::vector<std::string> rtokens = LLStringUtil::getTokens(recentlyUsed, ",");
int maxCountR = 20;
for (const std::string& token : rtokens)
{
llwchar emoji = (llwchar)atoi(token.c_str());
if (std::find(sRecentlyUsed.begin(), sRecentlyUsed.end(), emoji) == sRecentlyUsed.end())
{
sRecentlyUsed.push_back(emoji);
if (!--maxCountR)
break;
}
}
// Load and parse sFrequentlyUsed
std::string frequentlyUsed = state[sKeyFrequentlyUsed];
std::vector<std::string> ftokens = LLStringUtil::getTokens(frequentlyUsed, ",");
int maxCountF = 20;
for (const std::string& token : ftokens)
{
std::vector<std::string> pair = LLStringUtil::getTokens(token, ":");
if (pair.size() == 2)
{
llwchar emoji = (llwchar)atoi(pair[0].c_str());
if (emoji)
{
U32 count = atoi(pair[1].c_str());
auto it = std::find_if(sFrequentlyUsed.begin(), sFrequentlyUsed.end(),
[emoji](std::pair<llwchar, U32>& it) { return it.first == emoji; });
if (it != sFrequentlyUsed.end())
{
it->second += count;
}
else
{
sFrequentlyUsed.push_back(std::make_pair(emoji, count));
if (!--maxCountF)
break;
}
}
}
}
// Normalize by minimum
if (!sFrequentlyUsed.empty())
{
U32 delta = sFrequentlyUsed.back().second;
for (auto& it : sFrequentlyUsed)
{
it.second = std::max((U32)0, it.second - delta);
}
}
}
// static
void LLFloaterEmojiPicker::saveState()
{
if (sStateFileName.empty())
return; // Not loaded
if (LLAppViewer::instance()->isSecondInstance())
return; // Not allowed
LLSD state = LLSD::emptyMap();
if (sSelectedGroupIndex)
{
state[sKeySelectedGroupIndex] = (int)sSelectedGroupIndex;
}
if (!sFilterPattern.empty())
{
state[sKeyFilterPattern] = sFilterPattern;
}
if (!sRecentlyUsed.empty())
{
U32 maxCount = 20;
std::string recentlyUsed;
for (llwchar emoji : sRecentlyUsed)
{
if (!recentlyUsed.empty())
recentlyUsed += ",";
char buffer[32];
sprintf(buffer, "%u", (U32)emoji);
recentlyUsed += buffer;
if (!--maxCount)
break;
}
state[sKeyRecentlyUsed] = recentlyUsed;
}
if (!sFrequentlyUsed.empty())
{
U32 maxCount = 20;
std::string frequentlyUsed;
for (auto& it : sFrequentlyUsed)
{
if (!frequentlyUsed.empty())
frequentlyUsed += ",";
char buffer[32];
sprintf(buffer, "%u:%u", (U32)it.first, (U32)it.second);
frequentlyUsed += buffer;
if (!--maxCount)
break;
}
state[sKeyFrequentlyUsed] = frequentlyUsed;
}
llofstream stream(sStateFileName.c_str());
LLSDSerialize::toPrettyXML(state, stream);
}

View File

@ -0,0 +1,103 @@
/**
* @file llfloateremojipicker.h
* @brief Header file for llfloateremojipicker
*
* $LicenseInfo:firstyear=2003&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, 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$
*/
#ifndef LLFLOATEREMOJIPICKER_H
#define LLFLOATEREMOJIPICKER_H
#include "llfloater.h"
struct LLEmojiDescriptor;
class LLFloaterEmojiPicker : public LLFloater
{
using super = LLFloater;
public:
// The callback function will be called with an emoji char.
typedef boost::function<void (llwchar)> pick_callback_t;
typedef boost::function<void ()> close_callback_t;
// Call this to select an emoji.
static LLFloaterEmojiPicker* getInstance();
static LLFloaterEmojiPicker* showInstance(pick_callback_t pick_callback = nullptr, close_callback_t close_callback = nullptr);
LLFloaterEmojiPicker(const LLSD& key);
virtual ~LLFloaterEmojiPicker();
virtual BOOL postBuild() override;
virtual void dirtyRect() override;
void show(pick_callback_t pick_callback = nullptr, close_callback_t close_callback = nullptr);
virtual void closeFloater(bool app_quitting = false) override;
static std::list<llwchar>& getRecentlyUsed();
static void onEmojiUsed(llwchar emoji);
static void onRecentlyUsedChanged();
static void loadState();
static void saveState();
private:
void fillGroups();
void moveGroups();
void fillEmojis(bool fromResize = false);
void onGroupButtonClick(LLUICtrl* ctrl);
void onFilterChanged();
void onGridMouseEnter();
void onGridMouseLeave();
void onGroupButtonMouseEnter(LLUICtrl* ctrl);
void onGroupButtonMouseLeave(LLUICtrl* ctrl);
void onEmojiMouseEnter(LLUICtrl* ctrl);
void onEmojiMouseLeave(LLUICtrl* ctrl);
void onEmojiMouseDown(LLUICtrl* ctrl);
void onEmojiMouseUp(LLUICtrl* ctrl);
void selectGridIcon(LLUICtrl* ctrl);
void unselectGridIcon(LLUICtrl* ctrl);
virtual BOOL handleKeyHere(KEY key, MASK mask) override;
class LLPanel* mGroups { nullptr };
class LLPanel* mBadge { nullptr };
class LLFilterEditor* mFilter { nullptr };
class LLScrollContainer* mEmojiScroll { nullptr };
class LLScrollingPanelList* mEmojiGrid { nullptr };
class LLEmojiPreviewPanel* mPreview { nullptr };
class LLTextBox* mDummy { nullptr };
pick_callback_t mEmojiPickCallback;
close_callback_t mFloaterCloseCallback;
std::vector<class LLButton*> mGroupButtons;
S32 mRecentBadgeWidth { 0 };
S32 mRecentGridWidth { 0 };
S32 mRecentMaxIcons { 0 };
LLUICtrl* mHoveredIcon { nullptr };
};
#endif

View File

@ -289,6 +289,9 @@ void LLFloaterIMContainer::onOpen(const LLSD& key)
LLMultiFloater::onOpen(key);
reSelectConversation();
assignResizeLimits();
LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(mSelectedSession);
session_floater->onOpen(key);
}
// virtual

View File

@ -130,11 +130,12 @@ BOOL LLFloaterIMNearbyChat::postBuild()
mInputEditor->setKeystrokeCallback(boost::bind(&LLFloaterIMNearbyChat::onChatBoxKeystroke, this));
mInputEditor->setFocusLostCallback(boost::bind(&LLFloaterIMNearbyChat::onChatBoxFocusLost, this));
mInputEditor->setFocusReceivedCallback(boost::bind(&LLFloaterIMNearbyChat::onChatBoxFocusReceived, this));
mInputEditor->setLabel(LLTrans::getString("NearbyChatTitle"));
std::string nearbyChatTitle(LLTrans::getString("NearbyChatTitle"));
mInputEditor->setLabel(nearbyChatTitle);
// Title must be defined BEFORE call to addConversationListItem() because
// it is used to show the item's name in the conversations list
setTitle(LLTrans::getString("NearbyChatTitle"));
setTitle(nearbyChatTitle);
// obsolete, but may be needed for backward compatibility?
gSavedSettings.declareS32("nearbychat_showicons_and_names", 2, "NearByChat header settings", LLControlVariable::PERSIST_NONDFT);
@ -590,6 +591,8 @@ void LLFloaterIMNearbyChat::sendChat( EChatType type )
S32 channel = 0;
stripChannelNumber(text, &channel);
updateUsedEmojis(text);
std::string utf8text = wstring_to_utf8str(text);
// Try to trigger a gesture, if not chat to a script.
std::string utf8_revised_text;

View File

@ -249,6 +249,8 @@ void LLFloaterIMSession::sendMsgFromInputEditor()
LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines.
if(!text.empty())
{
updateUsedEmojis(text);
// Truncate and convert to UTF8 for transport
std::string utf8_text = wstring_to_utf8str(text);

View File

@ -33,18 +33,21 @@
#include "llagentcamera.h"
#include "llavataractions.h"
#include "llavatariconctrl.h"
#include "llgroupiconctrl.h"
#include "llchatentry.h"
#include "llchathistory.h"
#include "llchiclet.h"
#include "llchicletbar.h"
#include "lldraghandle.h"
#include "llemojidictionary.h"
#include "llfloaterreg.h"
#include "llfloateremojipicker.h"
#include "llfloaterimsession.h"
#include "llfloaterimcontainer.h" // to replace separate IM Floaters with multifloater container
#include "lllayoutstack.h"
#include "lltoolbarview.h"
#include "llfloaterimnearbychat.h"
#include "llgroupiconctrl.h"
#include "lllayoutstack.h"
#include "llpanelemojicomplete.h"
#include "lltoolbarview.h"
const F32 REFRESH_INTERVAL = 1.0f;
const std::string ICN_GROUP("group_chat_icon");
@ -56,7 +59,7 @@ void cb_group_do_nothing()
}
LLFloaterIMSessionTab::LLFloaterIMSessionTab(const LLSD& session_id)
: LLTransientDockableFloater(NULL, false, session_id),
: super(NULL, false, session_id),
mIsP2PChat(false),
mExpandCollapseBtn(NULL),
mTearOffBtn(NULL),
@ -75,7 +78,7 @@ LLFloaterIMSessionTab::LLFloaterIMSessionTab(const LLSD& session_id)
mInputPanels(NULL),
mChatLayoutPanelHeight(0)
{
setAutoFocus(FALSE);
setAutoFocus(FALSE);
mSession = LLIMModel::getInstance()->findIMSession(mSessionID);
mCommitCallbackRegistrar.add("IMSession.Menu.Action",
@ -88,12 +91,12 @@ LLFloaterIMSessionTab::LLFloaterIMSessionTab(const LLSD& session_id)
boost::bind(&LLFloaterIMSessionTab::onIMShowModesMenuItemEnable, this, _2));
// Right click menu handling
mEnableCallbackRegistrar.add("Avatar.CheckItem", boost::bind(&LLFloaterIMSessionTab::checkContextMenuItem, this, _2));
mEnableCallbackRegistrar.add("Avatar.EnableItem", boost::bind(&LLFloaterIMSessionTab::enableContextMenuItem, this, _2));
mCommitCallbackRegistrar.add("Avatar.DoToSelected", boost::bind(&LLFloaterIMSessionTab::doToSelected, this, _2));
mCommitCallbackRegistrar.add("Group.DoToSelected", boost::bind(&cb_group_do_nothing));
mEnableCallbackRegistrar.add("Avatar.CheckItem", boost::bind(&LLFloaterIMSessionTab::checkContextMenuItem, this, _2));
mEnableCallbackRegistrar.add("Avatar.EnableItem", boost::bind(&LLFloaterIMSessionTab::enableContextMenuItem, this, _2));
mCommitCallbackRegistrar.add("Avatar.DoToSelected", boost::bind(&LLFloaterIMSessionTab::doToSelected, this, _2));
mCommitCallbackRegistrar.add("Group.DoToSelected", boost::bind(&cb_group_do_nothing));
mMinFloaterHeight = getMinHeight();
mMinFloaterHeight = getMinHeight();
}
LLFloaterIMSessionTab::~LLFloaterIMSessionTab()
@ -101,7 +104,7 @@ LLFloaterIMSessionTab::~LLFloaterIMSessionTab()
delete mRefreshTimer;
}
//static
// static
LLFloaterIMSessionTab* LLFloaterIMSessionTab::findConversation(const LLUUID& uuid)
{
LLFloaterIMSessionTab* conv;
@ -118,7 +121,7 @@ LLFloaterIMSessionTab* LLFloaterIMSessionTab::findConversation(const LLUUID& uui
return conv;
};
//static
// static
LLFloaterIMSessionTab* LLFloaterIMSessionTab::getConversation(const LLUUID& uuid)
{
LLFloaterIMSessionTab* conv;
@ -134,14 +137,16 @@ LLFloaterIMSessionTab* LLFloaterIMSessionTab::getConversation(const LLUUID& uuid
}
return conv;
};
// virtual
void LLFloaterIMSessionTab::setVisible(BOOL visible)
{
if(visible && !mHasVisibleBeenInitialized)
if (visible && !mHasVisibleBeenInitialized)
{
mHasVisibleBeenInitialized = true;
if(!gAgentCamera.cameraMouselook())
if (!gAgentCamera.cameraMouselook())
{
LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container")->setVisible(true);
}
@ -155,27 +160,26 @@ void LLFloaterIMSessionTab::setVisible(BOOL visible)
mInputButtonPanel->setVisible(isTornOff());
}
LLTransientDockableFloater::setVisible(visible);
super::setVisible(visible);
}
/*virtual*/
// virtual
void LLFloaterIMSessionTab::setFocus(BOOL focus)
{
LLTransientDockableFloater::setFocus(focus);
super::setFocus(focus);
//Redirect focus to input editor
if (focus)
// Redirect focus to input editor
if (focus)
{
updateMessages();
updateMessages();
if (mInputEditor)
{
mInputEditor->setFocus(TRUE);
}
if (mInputEditor)
{
mInputEditor->setFocus(TRUE);
}
}
}
void LLFloaterIMSessionTab::addToHost(const LLUUID& session_id)
{
if ((session_id.notNull() && !gIMMgr->hasSession(session_id))
@ -220,42 +224,60 @@ void LLFloaterIMSessionTab::assignResizeLimits()
{
bool is_participants_pane_collapsed = mParticipantListPanel->isCollapsed();
// disable a layoutstack's functionality when participant list panel is collapsed
// disable a layoutstack's functionality when participant list panel is collapsed
mRightPartPanel->setIgnoreReshape(is_participants_pane_collapsed);
S32 participants_pane_target_width = is_participants_pane_collapsed?
0 : (mParticipantListPanel->getRect().getWidth() + mParticipantListAndHistoryStack->getPanelSpacing());
S32 participants_pane_target_width = is_participants_pane_collapsed?
0 : (mParticipantListPanel->getRect().getWidth() + mParticipantListAndHistoryStack->getPanelSpacing());
S32 new_min_width = participants_pane_target_width + mRightPartPanel->getExpandedMinDim() + mFloaterExtraWidth;
S32 new_min_width = participants_pane_target_width + mRightPartPanel->getExpandedMinDim() + mFloaterExtraWidth;
setResizeLimits(new_min_width, getMinHeight());
this->mParticipantListAndHistoryStack->updateLayout();
}
// virtual
BOOL LLFloaterIMSessionTab::postBuild()
{
BOOL result;
mBodyStack = getChild<LLLayoutStack>("main_stack");
mParticipantListAndHistoryStack = getChild<LLLayoutStack>("im_panels");
mParticipantListAndHistoryStack = getChild<LLLayoutStack>("im_panels");
mCloseBtn = getChild<LLButton>("close_btn");
mCloseBtn->setCommitCallback(boost::bind(&LLFloater::onClickClose, this));
mCloseBtn->setCommitCallback([this](LLUICtrl*, const LLSD&) { onClickClose(this); });
mExpandCollapseBtn = getChild<LLButton>("expand_collapse_btn");
mExpandCollapseBtn->setClickedCallback(boost::bind(&LLFloaterIMSessionTab::onSlide, this));
mExpandCollapseBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onSlide(this); });
mExpandCollapseLineBtn = getChild<LLButton>("minz_btn");
mExpandCollapseLineBtn->setClickedCallback(boost::bind(&LLFloaterIMSessionTab::onCollapseToLine, this));
mExpandCollapseLineBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onCollapseToLine(this); });
mTearOffBtn = getChild<LLButton>("tear_off_btn");
mTearOffBtn->setCommitCallback(boost::bind(&LLFloaterIMSessionTab::onTearOffClicked, this));
mEmojiRecentPanelToggleBtn = getChild<LLButton>("emoji_recent_panel_toggle_btn");
mEmojiRecentPanelToggleBtn->setLabel(LLUIString(LLWString(1, 128512)));
mEmojiRecentPanelToggleBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiRecentPanelToggleBtnClicked(this); });
mEmojiRecentPanel = getChild<LLLayoutPanel>("emoji_recent_layout_panel");
mEmojiRecentPanel->setVisible(false);
mEmojiRecentEmptyText = getChildView("emoji_recent_empty_text");
mEmojiRecentEmptyText->setVisible(false);
mEmojiRecentIconsCtrl = getChild<LLPanelEmojiComplete>("emoji_recent_icons_ctrl");
mEmojiRecentIconsCtrl->setCommitCallback([this](LLUICtrl*, const LLSD& value) { onRecentEmojiPicked(value); });
mEmojiRecentIconsCtrl->setVisible(false);
mEmojiPickerToggleBtn = getChild<LLButton>("emoji_picker_toggle_btn");
mEmojiPickerToggleBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiPickerToggleBtnClicked(this); });
mGearBtn = getChild<LLButton>("gear_btn");
mAddBtn = getChild<LLButton>("add_btn");
mAddBtn = getChild<LLButton>("add_btn");
mVoiceButton = getChild<LLButton>("voice_call_btn");
mParticipantListPanel = getChild<LLLayoutPanel>("speakers_list_panel");
mRightPartPanel = getChild<LLLayoutPanel>("right_part_holder");
@ -307,17 +329,17 @@ BOOL LLFloaterIMSessionTab::postBuild()
// Create the root using an ad-hoc base item
LLConversationItem* base_item = new LLConversationItem(mSessionID, mConversationViewModel);
LLFolderView::Params p(LLUICtrlFactory::getDefaultParams<LLFolderView>());
p.rect = LLRect(0, 0, getRect().getWidth(), 0);
p.parent_panel = mParticipantListPanel;
p.listener = base_item;
p.view_model = &mConversationViewModel;
p.root = NULL;
p.use_ellipses = true;
p.options_menu = "menu_conversation.xml";
p.name = "root";
LLFolderView::Params p(LLUICtrlFactory::getDefaultParams<LLFolderView>());
p.rect = LLRect(0, 0, getRect().getWidth(), 0);
p.parent_panel = mParticipantListPanel;
p.listener = base_item;
p.view_model = &mConversationViewModel;
p.root = NULL;
p.use_ellipses = true;
p.options_menu = "menu_conversation.xml";
p.name = "root";
mConversationsRoot = LLUICtrlFactory::create<LLFolderView>(p);
mConversationsRoot->setCallbackRegistrar(&mCommitCallbackRegistrar);
mConversationsRoot->setCallbackRegistrar(&mCommitCallbackRegistrar);
mConversationsRoot->setEnableRegistrar(&mEnableCallbackRegistrar);
// Attach that root to the scroller
mScroller->addChild(mConversationsRoot);
@ -357,6 +379,7 @@ LLParticipantList* LLFloaterIMSessionTab::getParticipantList()
return dynamic_cast<LLParticipantList*>(LLFloaterIMContainer::getInstance()->getSessionModel(mSessionID));
}
// virtual
void LLFloaterIMSessionTab::draw()
{
if (mRefreshTimer->hasExpired())
@ -381,23 +404,24 @@ void LLFloaterIMSessionTab::draw()
mRefreshTimer->setTimerExpirySec(REFRESH_INTERVAL);
}
LLTransientDockableFloater::draw();
super::draw();
}
void LLFloaterIMSessionTab::enableDisableCallBtn()
{
if (LLVoiceClient::instanceExists() && mVoiceButton)
{
mVoiceButton->setEnabled(
mSessionID.notNull()
&& mSession
&& mSession->mSessionInitialized
&& LLVoiceClient::getInstance()->voiceEnabled()
&& LLVoiceClient::getInstance()->isVoiceWorking()
&& mSession->mCallBackEnabled);
}
if (LLVoiceClient::instanceExists() && mVoiceButton)
{
mVoiceButton->setEnabled(
mSessionID.notNull()
&& mSession
&& mSession->mSessionInitialized
&& LLVoiceClient::getInstance()->voiceEnabled()
&& LLVoiceClient::getInstance()->isVoiceWorking()
&& mSession->mCallBackEnabled);
}
}
// virtual
void LLFloaterIMSessionTab::onFocusReceived()
{
setBackgroundOpaque(true);
@ -407,13 +431,14 @@ void LLFloaterIMSessionTab::onFocusReceived()
LLIMModel::instance().sendNoUnreadMessages(mSessionID);
}
LLTransientDockableFloater::onFocusReceived();
super::onFocusReceived();
}
// virtual
void LLFloaterIMSessionTab::onFocusLost()
{
setBackgroundOpaque(false);
LLTransientDockableFloater::onFocusLost();
super::onFocusLost();
}
void LLFloaterIMSessionTab::onInputEditorClicked()
@ -426,53 +451,169 @@ void LLFloaterIMSessionTab::onInputEditorClicked()
gToolBarView->flashCommand(LLCommandId("chat"), false);
}
void LLFloaterIMSessionTab::onEmojiRecentPanelToggleBtnClicked(LLFloaterIMSessionTab* self)
{
bool restore_focus = (gFocusMgr.getLastKeyboardFocus() == self->mInputEditor);
BOOL show = !self->mEmojiRecentPanel->getVisible();
if (show)
{
self->initEmojiRecentPanel(!restore_focus);
}
self->mEmojiRecentPanel->setVisible(show);
if (restore_focus)
{
self->mInputEditor->setFocus(true);
}
}
void LLFloaterIMSessionTab::onEmojiPickerToggleBtnClicked(LLFloaterIMSessionTab* self)
{
if (LLFloaterEmojiPicker* picker = LLFloaterEmojiPicker::getInstance())
{
if (!picker->isShown())
{
picker->show(
[self](llwchar emoji) { self->onEmojiPicked(emoji); },
[self]() { self->onEmojiPickerClosed(); });
if (LLFloater* root_floater = gFloaterView->getParentFloater(self))
{
root_floater->addDependentFloater(picker, TRUE, TRUE);
}
}
else
{
picker->closeFloater();
}
}
}
void LLFloaterIMSessionTab::initEmojiRecentPanel(bool moveFocus)
{
std::list<llwchar>& recentlyUsed = LLFloaterEmojiPicker::getRecentlyUsed();
if (recentlyUsed.empty())
{
mEmojiRecentEmptyText->setVisible(true);
mEmojiRecentIconsCtrl->setVisible(false);
if (moveFocus)
{
mEmojiPickerToggleBtn->setFocus(true);
}
}
else
{
LLWString emojis;
for (llwchar emoji : recentlyUsed)
{
emojis += emoji;
}
mEmojiRecentIconsCtrl->setEmojis(emojis);
mEmojiRecentEmptyText->setVisible(false);
mEmojiRecentIconsCtrl->setVisible(true);
if (moveFocus)
{
mEmojiRecentIconsCtrl->setFocus(true);
}
}
}
void LLFloaterIMSessionTab::onRecentEmojiPicked(const LLSD& value)
{
LLSD::String str = value.asString();
if (str.size())
{
LLWString wstr = utf8string_to_wstring(str);
if (wstr.size())
{
llwchar emoji = wstr[0];
mInputEditor->insertEmoji(emoji);
}
}
}
void LLFloaterIMSessionTab::onEmojiPicked(llwchar emoji)
{
mInputEditor->insertEmoji(emoji);
mInputEditor->setFocus(TRUE);
}
void LLFloaterIMSessionTab::onEmojiPickerClosed()
{
mInputEditor->setFocus(TRUE);
}
void LLFloaterIMSessionTab::closeFloater(bool app_quitting)
{
LLFloaterEmojiPicker::saveState();
super::closeFloater(app_quitting);
}
std::string LLFloaterIMSessionTab::appendTime()
{
time_t utc_time;
utc_time = time_corrected();
std::string timeStr ="["+ LLTrans::getString("TimeHour")+"]:["
+LLTrans::getString("TimeMin")+"]";
std::string timeStr = "[" + LLTrans::getString("TimeHour") + "]:"
"[" + LLTrans::getString("TimeMin") + "]";
LLSD substitution;
substitution["datetime"] = (S32) utc_time;
LLStringUtil::format (timeStr, substitution);
substitution["datetime"] = (S32)time_corrected();
LLStringUtil::format(timeStr, substitution);
return timeStr;
}
void LLFloaterIMSessionTab::appendMessage(const LLChat& chat, const LLSD &args)
void LLFloaterIMSessionTab::appendMessage(const LLChat& chat, const LLSD& args)
{
if (chat.mMuted || !mChatHistory)
return;
// Update the participant activity time
LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance();
if (im_box)
{
im_box->setTimeNow(mSessionID,chat.mFromID);
im_box->setTimeNow(mSessionID, chat.mFromID);
}
LLChat& tmp_chat = const_cast<LLChat&>(chat);
if(tmp_chat.mTimeStr.empty())
if (tmp_chat.mTimeStr.empty())
tmp_chat.mTimeStr = appendTime();
if (!chat.mMuted)
{
tmp_chat.mFromName = chat.mFromName;
LLSD chat_args;
if (args) chat_args = args;
chat_args["use_plain_text_chat_history"] =
gSavedSettings.getBOOL("PlainTextChatHistory");
chat_args["show_time"] = gSavedSettings.getBOOL("IMShowTime");
chat_args["show_names_for_p2p_conv"] =
!mIsP2PChat || gSavedSettings.getBOOL("IMShowNamesForP2PConv");
tmp_chat.mFromName = chat.mFromName;
if (mChatHistory)
{
mChatHistory->appendMessage(chat, chat_args);
}
}
LLSD chat_args = args;
chat_args["use_plain_text_chat_history"] =
gSavedSettings.getBOOL("PlainTextChatHistory");
chat_args["show_time"] = gSavedSettings.getBOOL("IMShowTime");
chat_args["show_names_for_p2p_conv"] = !mIsP2PChat ||
gSavedSettings.getBOOL("IMShowNamesForP2PConv");
mChatHistory->appendMessage(chat, chat_args);
}
void LLFloaterIMSessionTab::updateUsedEmojis(LLWString text)
{
LLEmojiDictionary* dictionary = LLEmojiDictionary::getInstance();
llassert_always(dictionary);
bool emojiSent = false;
for (llwchar& c : text)
{
if (dictionary->isEmoji(c))
{
LLFloaterEmojiPicker::onEmojiUsed(c);
emojiSent = true;
}
}
if (!emojiSent)
return;
LLFloaterEmojiPicker::onRecentlyUsedChanged();
if (mEmojiRecentPanel->getVisible())
{
initEmojiRecentPanel(false);
}
}
static LLTrace::BlockTimerStatHandle FTM_BUILD_CONVERSATION_VIEW_PARTICIPANT("Build Conversation View");
@ -502,10 +643,10 @@ void LLFloaterIMSessionTab::buildConversationViewParticipant()
while (current_participant_model != end_participant_model)
{
LLConversationItem* participant_model = dynamic_cast<LLConversationItem*>(*current_participant_model);
if (participant_model)
{
addConversationViewParticipant(participant_model);
}
if (participant_model)
{
addConversationViewParticipant(participant_model);
}
current_participant_model++;
}
}
@ -525,10 +666,10 @@ void LLFloaterIMSessionTab::addConversationViewParticipant(LLConversationItem* p
// If not already present, create the participant view and attach it to the root, otherwise, just refresh it
if (widget)
{
if (update_view)
{
updateConversationViewParticipant(uuid); // overkill?
}
if (update_view)
{
updateConversationViewParticipant(uuid); // overkill?
}
}
else
{
@ -578,11 +719,11 @@ void LLFloaterIMSessionTab::refreshConversation()
{
participants_uuids.push_back(widget_it->first);
}
if (widget_it->second->getViewModelItem())
{
widget_it->second->refresh();
widget_it->second->setVisible(TRUE);
}
if (widget_it->second->getViewModelItem())
{
widget_it->second->refresh();
widget_it->second->setVisible(TRUE);
}
++widget_it;
}
if (is_ad_hoc || mIsP2PChat)
@ -638,7 +779,7 @@ void LLFloaterIMSessionTab::refreshConversation()
// Copied from LLFloaterIMContainer::createConversationViewParticipant(). Refactor opportunity!
LLConversationViewParticipant* LLFloaterIMSessionTab::createConversationViewParticipant(LLConversationItem* item)
{
LLRect panel_rect = mParticipantListPanel->getRect();
LLRect panel_rect = mParticipantListPanel->getRect();
LLConversationViewParticipant::Params params;
params.name = item->getDisplayName();
@ -766,7 +907,7 @@ void LLFloaterIMSessionTab::hideAllStandardButtons()
void LLFloaterIMSessionTab::updateHeaderAndToolbar()
{
// prevent start conversation before its container
LLFloaterIMContainer::getInstance();
LLFloaterIMContainer::getInstance();
bool is_not_torn_off = !checkIfTornOff();
if (is_not_torn_off)
@ -783,12 +924,12 @@ void LLFloaterIMSessionTab::updateHeaderAndToolbar()
&& !mIsP2PChat;
mParticipantListAndHistoryStack->collapsePanel(mParticipantListPanel, !is_participant_list_visible);
mParticipantListPanel->setVisible(is_participant_list_visible);
mParticipantListPanel->setVisible(is_participant_list_visible);
// Display collapse image (<<) if the floater is hosted
// or if it is torn off but has an open control panel.
bool is_expanded = is_not_torn_off || is_participant_list_visible;
mExpandCollapseBtn->setImageOverlay(getString(is_expanded ? "collapse_icon" : "expand_icon"));
mExpandCollapseBtn->setToolTip(
is_not_torn_off?
@ -817,10 +958,10 @@ void LLFloaterIMSessionTab::updateHeaderAndToolbar()
void LLFloaterIMSessionTab::forceReshape()
{
LLRect floater_rect = getRect();
reshape(llmax(floater_rect.getWidth(), this->getMinWidth()),
llmax(floater_rect.getHeight(), this->getMinHeight()),
true);
LLRect floater_rect = getRect();
reshape(llmax(floater_rect.getWidth(), this->getMinWidth()),
llmax(floater_rect.getHeight(), this->getMinHeight()),
true);
}
@ -846,7 +987,7 @@ void LLFloaterIMSessionTab::processChatHistoryStyleUpdate(bool clean_messages/*
LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat");
if (nearby_chat)
{
nearby_chat->reloadMessages(clean_messages);
nearby_chat->reloadMessages(clean_messages);
}
}
@ -892,15 +1033,15 @@ void LLFloaterIMSessionTab::onSlide(LLFloaterIMSessionTab* self)
{
if (!self->mIsP2PChat)
{
// The state must toggle the collapsed state of the panel
should_be_expanded = self->mParticipantListPanel->isCollapsed();
// The state must toggle the collapsed state of the panel
should_be_expanded = self->mParticipantListPanel->isCollapsed();
// Update the expand/collapse flag of the participant list panel and save it
gSavedSettings.setBOOL("IMShowControlPanel", should_be_expanded);
self->mIsParticipantListExpanded = should_be_expanded;
// Refresh for immediate feedback
self->refreshConversation();
gSavedSettings.setBOOL("IMShowControlPanel", should_be_expanded);
self->mIsParticipantListExpanded = should_be_expanded;
// Refresh for immediate feedback
self->refreshConversation();
}
}
@ -937,12 +1078,12 @@ void LLFloaterIMSessionTab::reshapeFloater(bool collapse)
+ mChatLayoutPanel->getRect().getHeight() - mChatLayoutPanelHeight + 2;
floater_rect.mTop -= height;
setResizeLimits(getMinWidth(), floater_rect.getHeight());
setResizeLimits(getMinWidth(), floater_rect.getHeight());
}
else
{
floater_rect.mTop = floater_rect.mBottom + mFloaterHeight;
setResizeLimits(getMinWidth(), mMinFloaterHeight);
setResizeLimits(getMinWidth(), mMinFloaterHeight);
}
enableResizeCtrls(true, true, !collapse);
@ -967,7 +1108,7 @@ void LLFloaterIMSessionTab::restoreFloater()
setShape(floater_rect, true);
mBodyStack->updateLayout();
mExpandCollapseLineBtn->setImageOverlay(getString("expandline_icon"));
setResizeLimits(getMinWidth(), mMinFloaterHeight);
setResizeLimits(getMinWidth(), mMinFloaterHeight);
setMessagePaneExpanded(true);
saveCollapsedState();
mInputEditor->enableSingleLineMode(false);
@ -986,6 +1127,8 @@ void LLFloaterIMSessionTab::onOpen(const LLSD& key)
}
mInputButtonPanel->setVisible(isTornOff());
setFocus(TRUE);
}
@ -993,8 +1136,8 @@ void LLFloaterIMSessionTab::onTearOffClicked()
{
restoreFloater();
setFollows(isTornOff()? FOLLOWS_ALL : FOLLOWS_NONE);
mSaveRect = isTornOff();
initRectControl();
mSaveRect = isTornOff();
initRectControl();
LLFloater::onClickTearOff(this);
LLFloaterIMContainer* container = LLFloaterReg::findTypedInstance<LLFloaterIMContainer>("im_container");
@ -1084,8 +1227,8 @@ bool LLFloaterIMSessionTab::checkIfTornOff()
void LLFloaterIMSessionTab::doToSelected(const LLSD& userdata)
{
// Get the list of selected items in the tab
std::string command = userdata.asString();
uuid_vec_t selected_uuids;
std::string command = userdata.asString();
uuid_vec_t selected_uuids;
getSelectedUUIDs(selected_uuids);
// Perform the command (IM, profile, etc...) on the list using the general conversation container method
@ -1097,8 +1240,8 @@ void LLFloaterIMSessionTab::doToSelected(const LLSD& userdata)
bool LLFloaterIMSessionTab::enableContextMenuItem(const LLSD& userdata)
{
// Get the list of selected items in the tab
std::string command = userdata.asString();
uuid_vec_t selected_uuids;
std::string command = userdata.asString();
uuid_vec_t selected_uuids;
getSelectedUUIDs(selected_uuids);
// Perform the item enable test on the list using the general conversation container method
@ -1109,8 +1252,8 @@ bool LLFloaterIMSessionTab::enableContextMenuItem(const LLSD& userdata)
bool LLFloaterIMSessionTab::checkContextMenuItem(const LLSD& userdata)
{
// Get the list of selected items in the tab
std::string command = userdata.asString();
uuid_vec_t selected_uuids;
std::string command = userdata.asString();
uuid_vec_t selected_uuids;
getSelectedUUIDs(selected_uuids);
// Perform the item check on the list using the general conversation container method
@ -1120,19 +1263,19 @@ bool LLFloaterIMSessionTab::checkContextMenuItem(const LLSD& userdata)
void LLFloaterIMSessionTab::getSelectedUUIDs(uuid_vec_t& selected_uuids)
{
const std::set<LLFolderViewItem*> selected_items = mConversationsRoot->getSelectionList();
const std::set<LLFolderViewItem*> selected_items = mConversationsRoot->getSelectionList();
std::set<LLFolderViewItem*>::const_iterator it = selected_items.begin();
const std::set<LLFolderViewItem*>::const_iterator it_end = selected_items.end();
std::set<LLFolderViewItem*>::const_iterator it = selected_items.begin();
const std::set<LLFolderViewItem*>::const_iterator it_end = selected_items.end();
for (; it != it_end; ++it)
{
LLConversationItem* conversation_item = static_cast<LLConversationItem *>((*it)->getViewModelItem());
if (conversation_item)
{
selected_uuids.push_back(conversation_item->getUUID());
}
}
for (; it != it_end; ++it)
{
LLConversationItem* conversation_item = static_cast<LLConversationItem *>((*it)->getViewModelItem());
if (conversation_item)
{
selected_uuids.push_back(conversation_item->getUUID());
}
}
}
LLConversationItem* LLFloaterIMSessionTab::getCurSelectedViewModelItem()
@ -1140,8 +1283,8 @@ LLConversationItem* LLFloaterIMSessionTab::getCurSelectedViewModelItem()
LLConversationItem *conversationItem = NULL;
if(mConversationsRoot &&
mConversationsRoot->getCurSelectedItem() &&
mConversationsRoot->getCurSelectedItem()->getViewModelItem())
mConversationsRoot->getCurSelectedItem() &&
mConversationsRoot->getCurSelectedItem()->getViewModelItem())
{
conversationItem = static_cast<LLConversationItem *>(mConversationsRoot->getCurSelectedItem()->getViewModelItem()) ;
}

View File

@ -41,10 +41,12 @@
class LLPanelChatControlPanel;
class LLChatEntry;
class LLChatHistory;
class LLPanelEmojiComplete;
class LLFloaterIMSessionTab
: public LLTransientDockableFloater
{
using super = LLTransientDockableFloater;
public:
LOG_CLASS(LLFloaterIMSessionTab);
@ -68,9 +70,8 @@ public:
bool isHostAttached() {return mIsHostAttached;}
void setHostAttached(bool is_attached) {mIsHostAttached = is_attached;}
static LLFloaterIMSessionTab* findConversation(const LLUUID& uuid);
static LLFloaterIMSessionTab* getConversation(const LLUUID& uuid);
static LLFloaterIMSessionTab* findConversation(const LLUUID& uuid);
static LLFloaterIMSessionTab* getConversation(const LLUUID& uuid);
bool isNearbyChat() {return mIsNearbyChat;}
@ -80,6 +81,7 @@ public:
/*virtual*/ void draw();
/*virtual*/ void setVisible(BOOL visible);
/*virtual*/ void setFocus(BOOL focus);
/*virtual*/ void closeFloater(bool app_quitting = false);
// Handle the left hand participant list widgets
void addConversationViewParticipant(LLConversationItem* item, bool update_view = true);
@ -136,15 +138,17 @@ protected:
virtual void enableDisableCallBtn();
// process focus events to set a currently active session
/* virtual */ void onFocusLost();
/* virtual */ void onFocusReceived();
/* virtual */ void onFocusLost();
// prepare chat's params and out one message to chatHistory
void appendMessage(const LLChat& chat, const LLSD &args = 0);
void appendMessage(const LLChat& chat, const LLSD& args = LLSD());
std::string appendTime();
void assignResizeLimits();
void updateUsedEmojis(LLWString text);
S32 mFloaterExtraWidth;
bool mIsNearbyChat;
@ -152,8 +156,7 @@ protected:
bool mMessagePaneExpanded;
bool mIsParticipantListExpanded;
S32 mMinFloaterHeight;
S32 mMinFloaterHeight;
LLIMModel::LLIMSession* mSession;
@ -168,32 +171,37 @@ protected:
LLLayoutPanel* mContentPanel;
LLLayoutPanel* mToolbarPanel;
LLLayoutPanel* mInputButtonPanel;
LLLayoutPanel* mEmojiRecentPanel;
LLView* mEmojiRecentEmptyText;
LLPanelEmojiComplete* mEmojiRecentIconsCtrl;
LLParticipantList* getParticipantList();
conversations_widgets_map mConversationsWidgets;
LLConversationViewModel mConversationViewModel;
LLFolderView* mConversationsRoot;
LLScrollContainer* mScroller;
LLChatHistory* mChatHistory;
LLChatHistory* mChatHistory;
LLChatEntry* mInputEditor;
LLLayoutPanel * mChatLayoutPanel;
LLLayoutStack * mInputPanels;
LLLayoutPanel* mChatLayoutPanel;
LLLayoutStack* mInputPanels;
LLButton* mExpandCollapseLineBtn;
LLButton* mExpandCollapseBtn;
LLButton* mTearOffBtn;
LLButton* mEmojiRecentPanelToggleBtn;
LLButton* mEmojiPickerToggleBtn;
LLButton* mCloseBtn;
LLButton* mGearBtn;
LLButton* mAddBtn;
LLButton* mVoiceButton;
LLButton* mVoiceButton;
private:
// Handling selection and contextual menu
void doToSelected(const LLSD& userdata);
bool enableContextMenuItem(const LLSD& userdata);
bool checkContextMenuItem(const LLSD& userdata);
void doToSelected(const LLSD& userdata);
bool enableContextMenuItem(const LLSD& userdata);
bool checkContextMenuItem(const LLSD& userdata);
void getSelectedUUIDs(uuid_vec_t& selected_uuids);
void getSelectedUUIDs(uuid_vec_t& selected_uuids);
/// Refreshes the floater at a constant rate.
virtual void refresh() = 0;
@ -207,9 +215,16 @@ private:
void onInputEditorClicked();
static void onEmojiRecentPanelToggleBtnClicked(LLFloaterIMSessionTab* self);
static void onEmojiPickerToggleBtnClicked(LLFloaterIMSessionTab* self);
void initEmojiRecentPanel(bool moveFocus);
void onRecentEmojiPicked(const LLSD& value);
void onEmojiPicked(llwchar emoji);
void onEmojiPickerClosed();
bool checkIfTornOff();
bool mIsHostAttached;
bool mHasVisibleBeenInitialized;
bool mIsHostAttached;
bool mHasVisibleBeenInitialized;
LLTimer* mRefreshTimer; ///< Defines the rate at which refresh() is called.

View File

@ -1601,7 +1601,7 @@ void LLOverlapPanel::draw()
LLUI::translate(5,getRect().getHeight()-20); // translate to top-5,left-5
LLView::sDrawPreviewHighlights = FALSE;
LLFontGL::getFontSansSerifSmall()->renderUTF8(current_selection_text, 0, 0, 0, text_color,
LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE);
LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, /*use_ellipses*/FALSE, /*use_color*/FALSE);
}
else
{
@ -1619,7 +1619,7 @@ void LLOverlapPanel::draw()
std::string current_selection = std::string(current_selection_text + LLView::sPreviewClickedElement->getName() + " (no elements overlap)");
S32 text_width = LLFontGL::getFontSansSerifSmall()->getWidth(current_selection) + 10;
LLFontGL::getFontSansSerifSmall()->renderUTF8(current_selection, 0, 0, 0, text_color,
LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE);
LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, /*use_ellipses*/FALSE, /*use_color*/FALSE);
// widen panel enough to fit this text
LLRect rect = getRect();
setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() < text_width ? rect.mLeft + text_width : rect.mRight,rect.mTop));
@ -1685,7 +1685,7 @@ void LLOverlapPanel::draw()
// draw currently-selected element at top of overlappers
LLUI::translate(0,-mSpacing);
LLFontGL::getFontSansSerifSmall()->renderUTF8(current_selection_text + LLView::sPreviewClickedElement->getName(), 0, 0, 0, text_color,
LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE);
LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, /*use_ellipses*/FALSE, /*use_color*/FALSE);
LLUI::translate(0,-mSpacing-LLView::sPreviewClickedElement->getRect().getHeight()); // skip spacing distance + height
LLView::sPreviewClickedElement->draw();
@ -1700,7 +1700,7 @@ void LLOverlapPanel::draw()
// draw name
LLUI::translate(0,-mSpacing);
LLFontGL::getFontSansSerifSmall()->renderUTF8(overlapper_text + viewp->getName(), 0, 0, 0, text_color,
LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE);
LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, /*use_ellipses*/FALSE, /*use_color*/FALSE);
// draw element
LLUI::translate(0,-mSpacing-viewp->getRect().getHeight()); // skip spacing distance + height

View File

@ -55,7 +55,7 @@ public:
};
// LLFriendObserver implementation
void changed(U32 mask)
void changed(U32 mask) override
{
onFriendListUpdate(mask);
}

View File

@ -135,7 +135,7 @@ public:
void notifyObservers();
// Overriding so we can update active gesture names and notify observers
void changed(U32 mask);
void changed(U32 mask) override;
BOOL matchPrefix(const std::string& in_str, std::string* out_str);
@ -150,7 +150,7 @@ protected:
void runStep(LLMultiGesture* gesture, LLGestureStep* step);
// LLInventoryCompletionObserver trigger
void done();
void done() override;
// Used by loadGesture
static void onLoadComplete(const LLUUID& asset_uuid,

View File

@ -138,7 +138,7 @@ void hud_render_text(const LLWString &wstr, const LLVector3 &pos_agent,
LLUI::translate((F32) winX*1.0f/LLFontGL::sScaleX, (F32) winY*1.0f/(LLFontGL::sScaleY), -(((F32) winZ*2.f)-1.f));
F32 right_x;
font.render(wstr, 0, 0, 1, color, LLFontGL::LEFT, LLFontGL::BASELINE, style, shadow, wstr.length(), 1000, &right_x);
font.render(wstr, 0, 0, 1, color, LLFontGL::LEFT, LLFontGL::BASELINE, style, shadow, wstr.length(), 1000, &right_x, /*use_ellipses*/false, /*use_color*/true);
LLUI::popMatrix();
gGL.popMatrix();

View File

@ -45,7 +45,7 @@ private:
void loadAllFilters();
void loadFiltersFromDir(const std::string& dir);
/*virtual*/ void initSingleton();
/*virtual*/ void initSingleton() override;
// List of filters : first is the user friendly localized name, second is the xml file name
std::map<std::string,std::string> mFiltersList;

View File

@ -537,7 +537,7 @@ public:
static void onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction, bool ended_by_agent);
private:
void initSingleton();
void initSingleton() override;
void onVoiceChannelChangedInt(const LLUUID &session_id);
void onVoiceChannelStateChangedInt(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction, bool ended_by_agent);

View File

@ -59,7 +59,7 @@
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/local_time_adjustor.hpp>
const S32 LOG_RECALL_SIZE = 2048;
const S32 LOG_RECALL_SIZE = 20480;
const std::string LL_IM_TIME("time");
const std::string LL_IM_DATE_TIME("datetime");

View File

@ -73,7 +73,7 @@ class LLMuteList : public LLSingleton<LLMuteList>
{
LLSINGLETON(LLMuteList);
~LLMuteList();
/*virtual*/ void cleanupSingleton();
/*virtual*/ void cleanupSingleton() override;
public:
// reasons for auto-unmuting a resident
enum EAutoReason

View File

@ -92,10 +92,10 @@ class LLNavigationBar
public:
/*virtual*/ void draw();
/*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask);
/*virtual*/ BOOL postBuild();
/*virtual*/ void setVisible(BOOL visible);
/*virtual*/ void draw() override;
/*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask) override;
/*virtual*/ BOOL postBuild() override;
/*virtual*/ void setVisible(BOOL visible) override;
void handleLoginComplete();
void clearHistoryCache();

View File

@ -40,7 +40,7 @@ class LLOutfitObserver: public LLInventoryObserver, public LLSingleton<LLOutfitO
public:
virtual void changed(U32 mask);
virtual void changed(U32 mask) override;
void notifyOutfitLockChanged() { mOutfitLockChanged(); }

View File

@ -0,0 +1,566 @@
/**
* @file llpanelemojicomplete.h
* @brief Header file for LLPanelEmojiComplete
*
* $LicenseInfo:firstyear=2012&license=lgpl$
* Second Life Viewer Source Code
* Copyright (C) 2011, 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 "llviewerprecompiledheaders.h"
#include "llemojidictionary.h"
#include "llemojihelper.h"
#include "llpanelemojicomplete.h"
#include "llscrollbar.h"
#include "lluictrlfactory.h"
constexpr U32 MIN_MOUSE_MOVE_DELTA = 4;
constexpr U32 MIN_SHORT_CODE_WIDTH = 100;
constexpr U32 DEF_PADDING = 8;
// ============================================================================
// LLPanelEmojiComplete
//
static LLDefaultChildRegistry::Register<LLPanelEmojiComplete> r("emoji_complete");
LLPanelEmojiComplete::Params::Params()
: autosize("autosize")
, noscroll("noscroll")
, vertical("vertical")
, max_visible("max_visible")
, padding("padding", DEF_PADDING)
, selected_image("selected_image")
{
}
LLPanelEmojiComplete::LLPanelEmojiComplete(const LLPanelEmojiComplete::Params& p)
: LLUICtrl(p)
, mAutoSize(p.autosize)
, mNoScroll(p.noscroll)
, mVertical(p.vertical)
, mMaxVisible(p.max_visible)
, mPadding(p.padding)
, mSelectedImage(p.selected_image)
, mIconFont(LLFontGL::getFontEmojiHuge())
, mTextFont(LLFontGL::getFontSansSerifBig())
, mScrollbar(nullptr)
{
if (mVertical)
{
LLScrollbar::Params sbparams;
sbparams.orientation(LLScrollbar::VERTICAL);
sbparams.change_callback([this](S32 index, LLScrollbar*) { onScrollbarChange(index); });
mScrollbar = LLUICtrlFactory::create<LLScrollbar>(sbparams);
addChild(mScrollbar);
}
}
LLPanelEmojiComplete::~LLPanelEmojiComplete()
{
}
void LLPanelEmojiComplete::draw()
{
LLUICtrl::draw();
if (!mTotalEmojis)
return;
const size_t firstVisibleIdx = mScrollPos;
const size_t lastVisibleIdx = llmin(mScrollPos + mVisibleEmojis, mTotalEmojis);
if (mCurSelected >= firstVisibleIdx && mCurSelected < lastVisibleIdx)
{
S32 x, y, width, height;
if (mVertical)
{
x = mRenderRect.mLeft;
y = mRenderRect.mTop - (mCurSelected - firstVisibleIdx + 1) * mEmojiHeight;
width = mRenderRect.getWidth();
height = mEmojiHeight;
}
else
{
x = mRenderRect.mLeft + (mCurSelected - firstVisibleIdx) * mEmojiWidth;
y = mRenderRect.mBottom;
width = mEmojiWidth;
height = mRenderRect.getHeight();
}
mSelectedImage->draw(x, y, width, height);
}
F32 iconCenterX = mRenderRect.mLeft + (F32)mEmojiWidth / 2;
F32 iconCenterY = mRenderRect.mTop - (F32)mEmojiHeight / 2;
F32 textLeft = mVertical ? mRenderRect.mLeft + mEmojiWidth + mPadding : 0;
F32 textWidth = mVertical ? getRect().getWidth() - textLeft - mPadding : 0;
for (U32 curIdx = firstVisibleIdx; curIdx < lastVisibleIdx; curIdx++)
{
LLWString text(1, mEmojis[curIdx].Character);
mIconFont->render(text, 0, iconCenterX, iconCenterY,
LLColor4::white, LLFontGL::HCENTER, LLFontGL::VCENTER, LLFontGL::NORMAL,
LLFontGL::DROP_SHADOW_SOFT, 1, S32_MAX, nullptr, false, true);
if (mVertical)
{
const std::string& shortCode = mEmojis[curIdx].String;
F32 x0 = textLeft;
F32 x1 = textWidth;
if (mEmojis[curIdx].Begin)
{
std::string text = shortCode.substr(0, mEmojis[curIdx].Begin);
mTextFont->renderUTF8(text, 0, x0, iconCenterY, LLColor4::white,
LLFontGL::LEFT, LLFontGL::VCENTER, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
text.size(), x1, NULL, FALSE, FALSE);
x0 += mTextFont->getWidthF32(text);
x1 = textLeft + textWidth - x0;
}
if (x1 > 0 && mEmojis[curIdx].End > mEmojis[curIdx].Begin)
{
std::string text = shortCode.substr(mEmojis[curIdx].Begin, mEmojis[curIdx].End - mEmojis[curIdx].Begin);
mTextFont->renderUTF8(text, 0, x0, iconCenterY, LLColor4::yellow6,
LLFontGL::LEFT, LLFontGL::VCENTER, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
text.size(), x1, NULL, FALSE, FALSE);
x0 += mTextFont->getWidthF32(text);
x1 = textLeft + textWidth - x0;
}
if (x1 > 0 && mEmojis[curIdx].End < shortCode.size())
{
std::string text = shortCode.substr(mEmojis[curIdx].End);
mTextFont->renderUTF8(text, 0, x0, iconCenterY, LLColor4::white,
LLFontGL::LEFT, LLFontGL::VCENTER, LLFontGL::NORMAL, LLFontGL::NO_SHADOW,
text.size(), x1, NULL, FALSE, FALSE);
}
iconCenterY -= mEmojiHeight;
}
else
{
iconCenterX += mEmojiWidth;
}
}
}
BOOL LLPanelEmojiComplete::handleHover(S32 x, S32 y, MASK mask)
{
if (mScrollbar && mScrollbar->getVisible() && childrenHandleHover(x, y, mask))
return TRUE;
LLVector2 curHover(x, y);
if ((mLastHover - curHover).lengthSquared() > MIN_MOUSE_MOVE_DELTA)
{
size_t index = posToIndex(x, y);
if (index < mTotalEmojis)
mCurSelected = index;
mLastHover = curHover;
}
return TRUE;
}
BOOL LLPanelEmojiComplete::handleKey(KEY key, MASK mask, BOOL called_from_parent)
{
bool handled = false;
if (mTotalEmojis && MASK_NONE == mask)
{
switch (key)
{
case KEY_HOME:
select(0);
handled = true;
break;
case KEY_END:
select(mTotalEmojis - 1);
handled = true;
break;
case KEY_PAGE_DOWN:
select(mCurSelected + mVisibleEmojis - 1);
handled = true;
break;
case KEY_PAGE_UP:
select(mCurSelected - llmin(mCurSelected, mVisibleEmojis + 1));
handled = true;
break;
case KEY_LEFT:
case KEY_UP:
selectPrevious();
handled = true;
break;
case KEY_RIGHT:
case KEY_DOWN:
selectNext();
handled = true;
break;
case KEY_RETURN:
onCommit();
handled = true;
break;
}
}
if (handled)
{
return TRUE;
}
return LLUICtrl::handleKey(key, mask, called_from_parent);
}
BOOL LLPanelEmojiComplete::handleMouseDown(S32 x, S32 y, MASK mask)
{
if (mScrollbar && mScrollbar->getVisible() && childrenHandleMouseDown(x, y, mask))
return TRUE;
mCurSelected = posToIndex(x, y);
mLastHover = LLVector2(x, y);
return TRUE;
}
BOOL LLPanelEmojiComplete::handleMouseUp(S32 x, S32 y, MASK mask)
{
if (mScrollbar && mScrollbar->getVisible() && childrenHandleMouseUp(x, y, mask))
return TRUE;
mCurSelected = posToIndex(x, y);
onCommit();
return TRUE;
}
BOOL LLPanelEmojiComplete::handleScrollWheel(S32 x, S32 y, S32 clicks)
{
if (mNoScroll)
return FALSE;
if (mScrollbar && mScrollbar->getVisible() && mScrollbar->handleScrollWheel(x, y, clicks))
{
mCurSelected = posToIndex(x, y);
return TRUE;
}
if (mTotalEmojis > mVisibleEmojis)
{
mScrollPos = llclamp<size_t>(mScrollPos + clicks, 0, mTotalEmojis - mVisibleEmojis);
mCurSelected = posToIndex(x, y);
return TRUE;
}
return FALSE;
}
void LLPanelEmojiComplete::onCommit()
{
if (mCurSelected < mTotalEmojis)
{
LLSD value(wstring_to_utf8str(LLWString(1, mEmojis[mCurSelected].Character)));
setValue(value);
LLUICtrl::onCommit();
}
}
void LLPanelEmojiComplete::reshape(S32 width, S32 height, BOOL called_from_parent)
{
LLUICtrl::reshape(width, height, called_from_parent);
updateConstraints();
}
void LLPanelEmojiComplete::setEmojis(const LLWString& emojis)
{
mEmojis.clear();
auto& emoji2descr = LLEmojiDictionary::instance().getEmoji2Descr();
for (const llwchar& emoji : emojis)
{
std::string shortCode;
if (mVertical)
{
auto it = emoji2descr.find(emoji);
if (it != emoji2descr.end() && !it->second->ShortCodes.empty())
{
shortCode = it->second->ShortCodes.front();
}
}
mEmojis.emplace_back(emoji, shortCode, 0, 0);
}
mTotalEmojis = mEmojis.size();
mCurSelected = 0;
onEmojisChanged();
}
void LLPanelEmojiComplete::setEmojiHint(const std::string& hint)
{
llwchar curEmoji = mCurSelected < mTotalEmojis ? mEmojis[mCurSelected].Character : 0;
LLEmojiDictionary::instance().findByShortCode(mEmojis, hint);
mTotalEmojis = mEmojis.size();
mCurSelected = 0;
for (size_t i = 1; i < mTotalEmojis; ++i)
{
if (mEmojis[i].Character == curEmoji)
{
mCurSelected = i;
break;
}
}
onEmojisChanged();
}
U32 LLPanelEmojiComplete::getMaxShortCodeWidth() const
{
U32 max_width = 0;
for (const LLEmojiSearchResult& result : mEmojis)
{
S32 width = mTextFont->getWidth(result.String);
if (width > max_width)
{
max_width = width;
}
}
return max_width;
}
void LLPanelEmojiComplete::onEmojisChanged()
{
if (mAutoSize)
{
S32 width, height;
mVisibleEmojis = llmin(mTotalEmojis, mMaxVisible);
if (mVertical)
{
U32 maxShortCodeWidth = getMaxShortCodeWidth();
U32 shortCodeWidth = llmax(maxShortCodeWidth, MIN_SHORT_CODE_WIDTH);
width = mEmojiWidth + shortCodeWidth + mPadding * 2;
if (!mNoScroll && mVisibleEmojis < mTotalEmojis)
{
width += mScrollbar->getThickness();
}
height = mVisibleEmojis * mEmojiHeight;
}
else
{
width = mVisibleEmojis * mEmojiWidth;
height = getRect().getHeight();
}
LLUICtrl::reshape(width, height, false);
}
else
{
mVisibleEmojis = mVertical ? getRect().getHeight() / mEmojiHeight : getRect().getWidth() / mEmojiWidth;
}
updateConstraints();
}
void LLPanelEmojiComplete::onScrollbarChange(S32 index)
{
mScrollPos = llclamp<size_t>(index, 0, mTotalEmojis - mVisibleEmojis);
}
size_t LLPanelEmojiComplete::posToIndex(S32 x, S32 y) const
{
if (mRenderRect.pointInRect(x, y))
{
U32 pos = mVertical ? (U32)(mRenderRect.mTop - y) / mEmojiHeight : x / mEmojiWidth;
return llmin(mScrollPos + pos, mTotalEmojis - 1);
}
return std::string::npos;
}
void LLPanelEmojiComplete::select(size_t emoji_idx)
{
mCurSelected = llclamp<size_t>(emoji_idx, 0, mTotalEmojis - 1);
updateScrollPos();
}
void LLPanelEmojiComplete::selectNext()
{
if (!mTotalEmojis)
return;
mCurSelected = (mCurSelected < mTotalEmojis - 1) ? mCurSelected + 1 : 0;
updateScrollPos();
}
void LLPanelEmojiComplete::selectPrevious()
{
if (!mTotalEmojis)
return;
mCurSelected = (mCurSelected && mCurSelected < mTotalEmojis) ? mCurSelected - 1 : mTotalEmojis - 1;
updateScrollPos();
}
void LLPanelEmojiComplete::updateConstraints()
{
mRenderRect = getLocalRect();
mEmojiWidth = mIconFont->getWidthF32(u8"\U0001F431") + mPadding * 2;
if (mVertical)
{
mEmojiHeight = mIconFont->getLineHeight() + mPadding * 2;
if (!mNoScroll && mVisibleEmojis < mTotalEmojis)
{
mRenderRect.mRight -= mScrollbar->getThickness();
mScrollbar->setDocSize(mTotalEmojis);
mScrollbar->setPageSize(mVisibleEmojis);
mScrollbar->setOrigin(mRenderRect.mRight, 0);
mScrollbar->reshape(mScrollbar->getThickness(), mRenderRect.mTop, TRUE);
mScrollbar->setVisible(TRUE);
}
else
{
mScrollbar->setVisible(FALSE);
}
}
else
{
mEmojiHeight = mRenderRect.getHeight();
mRenderRect.stretch((mRenderRect.getWidth() - mVisibleEmojis * mEmojiWidth) / -2, 0);
}
updateScrollPos();
}
void LLPanelEmojiComplete::updateScrollPos()
{
if (mNoScroll || 0 == mTotalEmojis || mTotalEmojis < mVisibleEmojis || 0 == mCurSelected)
{
mScrollPos = 0;
if (mCurSelected >= mVisibleEmojis)
{
mCurSelected = mVisibleEmojis ? mVisibleEmojis - 1 : 0;
}
}
else if (mTotalEmojis - 1 == mCurSelected)
{
mScrollPos = mTotalEmojis - mVisibleEmojis;
}
else
{
mScrollPos = mCurSelected - ((float)mCurSelected / (mTotalEmojis - 2) * (mVisibleEmojis - 2));
}
if (mScrollbar && mScrollbar->getVisible())
{
mScrollbar->setDocPos(mScrollPos);
}
}
// ============================================================================
// LLFloaterEmojiComplete
//
LLFloaterEmojiComplete::LLFloaterEmojiComplete(const LLSD& sdKey)
: LLFloater(sdKey)
{
// This floater should hover on top of our dependent (with the dependent having the focus)
setFocusStealsFrontmost(false);
setAutoFocus(false);
setBackgroundVisible(false);
setIsChrome(true);
}
BOOL LLFloaterEmojiComplete::handleKey(KEY key, MASK mask, BOOL called_from_parent)
{
bool handled = false;
if (MASK_NONE == mask)
{
switch (key)
{
case KEY_ESCAPE:
LLEmojiHelper::instance().hideHelper();
handled = true;
break;
}
}
if (handled)
return TRUE;
return LLFloater::handleKey(key, mask, called_from_parent);
}
void LLFloaterEmojiComplete::onOpen(const LLSD& key)
{
mEmojiCtrl->setEmojiHint(key["hint"].asString());
if (0 == mEmojiCtrl->getEmojiCount())
{
LLEmojiHelper::instance().hideHelper();
return;
}
if (mEmojiCtrl->isAutoSize())
{
LLRect outer_rect = getRect();
const LLRect& inner_rect = mEmojiCtrl->getRect();
outer_rect.mTop = outer_rect.mBottom + inner_rect.mBottom * 2 + inner_rect.getHeight();
outer_rect.mRight = outer_rect.mLeft + inner_rect.mLeft * 2 + inner_rect.getWidth();
setRect(outer_rect);
}
gFloaterView->adjustToFitScreen(this, FALSE);
}
BOOL LLFloaterEmojiComplete::postBuild()
{
mEmojiCtrl = findChild<LLPanelEmojiComplete>("emoji_complete_ctrl");
mEmojiCtrl->setCommitCallback(
[this](LLUICtrl* ctrl, const LLSD& param)
{
setValue(param);
onCommit();
});
mEmojiCtrlHorz = getRect().getWidth() - mEmojiCtrl->getRect().getWidth();
mEmojiCtrlVert = getRect().getHeight() - mEmojiCtrl->getRect().getHeight();
return LLFloater::postBuild();
}
void LLFloaterEmojiComplete::reshape(S32 width, S32 height, BOOL called_from_parent)
{
if (called_from_parent)
{
LLFloater::reshape(width, height, called_from_parent);
}
else
{
LLRect outer(getRect()), inner(mEmojiCtrl->getRect());
outer.mRight = outer.mLeft + inner.getWidth() + mEmojiCtrlHorz;
outer.mTop = outer.mBottom + inner.getHeight() + mEmojiCtrlVert;
setRect(outer);
}
}
// ============================================================================

View File

@ -0,0 +1,132 @@
/**
* @file llpanelemojicomplete.h
* @brief Header file for LLPanelEmojiComplete
*
* $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 "llemojidictionary.h"
#include "llfloater.h"
#include "lluictrl.h"
class LLScrollbar;
// ============================================================================
// LLPanelEmojiComplete
//
class LLPanelEmojiComplete : public LLUICtrl
{
friend class LLUICtrlFactory;
public:
struct Params : public LLInitParam::Block<Params, LLUICtrl::Params>
{
Optional<bool> autosize;
Optional<bool> noscroll;
Optional<bool> vertical;
Optional<S32> max_visible,
padding;
Optional<LLUIImage*> selected_image;
Params();
};
protected:
LLPanelEmojiComplete(const LLPanelEmojiComplete::Params&);
public:
virtual ~LLPanelEmojiComplete();
void draw() override;
BOOL handleHover(S32 x, S32 y, MASK mask) override;
BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent) override;
BOOL handleMouseDown(S32 x, S32 y, MASK mask) override;
BOOL handleMouseUp(S32 x, S32 y, MASK mask) override;
BOOL handleScrollWheel(S32 x, S32 y, S32 clicks) override;
void onCommit() override;
void reshape(S32 width, S32 height, BOOL called_from_parent) override;
public:
size_t getEmojiCount() const { return mEmojis.size(); }
void setEmojis(const LLWString& emojis);
void setEmojiHint(const std::string& hint);
bool isAutoSize() const { return mAutoSize; }
U32 getMaxShortCodeWidth() const;
protected:
void onEmojisChanged();
void onScrollbarChange(S32 index);
size_t posToIndex(S32 x, S32 y) const;
void select(size_t emoji_idx);
void selectNext();
void selectPrevious();
void updateConstraints();
void updateScrollPos();
protected:
const bool mAutoSize;
const bool mNoScroll;
const bool mVertical;
const size_t mMaxVisible;
const S32 mPadding;
const LLUIImagePtr mSelectedImage;
const LLFontGL* mIconFont;
const LLFontGL* mTextFont;
std::vector<LLEmojiSearchResult> mEmojis;
LLScrollbar* mScrollbar;
LLRect mRenderRect;
U16 mEmojiWidth = 0;
U16 mEmojiHeight = 0;
size_t mTotalEmojis = 0;
size_t mVisibleEmojis = 0;
size_t mFirstVisible = 0;
size_t mScrollPos = 0;
size_t mCurSelected = 0;
LLVector2 mLastHover;
};
// ============================================================================
// LLFloaterEmojiComplete
//
class LLFloaterEmojiComplete : public LLFloater
{
public:
LLFloaterEmojiComplete(const LLSD& sdKey);
public:
BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent) override;
void onOpen(const LLSD& key) override;
BOOL postBuild() override;
void reshape(S32 width, S32 height, BOOL called_from_parent) override;
protected:
LLPanelEmojiComplete* mEmojiCtrl = nullptr;
S32 mEmojiCtrlHorz = 0;
S32 mEmojiCtrlVert = 0;
};
// ============================================================================

View File

@ -46,8 +46,8 @@ class LLPanelTopInfoBar : public LLPanel, public LLSingleton<LLPanelTopInfoBar>,
public:
typedef boost::signals2::signal<void ()> resize_signal_t;
/*virtual*/ BOOL postBuild();
/*virtual*/ void draw();
/*virtual*/ BOOL postBuild() override;
/*virtual*/ void draw() override;
/**
* Updates location and parcel icons on login complete
@ -83,7 +83,7 @@ private:
*/
void initParcelIcons();
BOOL handleRightMouseDown(S32 x, S32 y, MASK mask);
BOOL handleRightMouseDown(S32 x, S32 y, MASK mask) override;
/**
* Handles clicks on the parcel icons.

View File

@ -66,17 +66,17 @@ public:
typedef boost::signals2::signal<void (void)> path_event_signal_t;
typedef boost::signals2::connection path_event_slot_t;
virtual BOOL handleMouseDown(S32 pX, S32 pY, MASK pMask);
virtual BOOL handleMouseUp(S32 pX, S32 pY, MASK pMask);
virtual BOOL handleMiddleMouseDown(S32 pX, S32 pY, MASK pMask);
virtual BOOL handleMiddleMouseUp(S32 pX, S32 pY, MASK pMask);
virtual BOOL handleRightMouseDown(S32 pX, S32 pY, MASK pMask);
virtual BOOL handleRightMouseUp(S32 pX, S32 pY, MASK pMask);
virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask);
virtual BOOL handleMouseDown(S32 pX, S32 pY, MASK pMask) override;
virtual BOOL handleMouseUp(S32 pX, S32 pY, MASK pMask) override;
virtual BOOL handleMiddleMouseDown(S32 pX, S32 pY, MASK pMask) override;
virtual BOOL handleMiddleMouseUp(S32 pX, S32 pY, MASK pMask) override;
virtual BOOL handleRightMouseDown(S32 pX, S32 pY, MASK pMask) override;
virtual BOOL handleRightMouseUp(S32 pX, S32 pY, MASK pMask) override;
virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask) override;
virtual BOOL handleHover(S32 pX, S32 pY, MASK pMask);
virtual BOOL handleHover(S32 pX, S32 pY, MASK pMask) override;
virtual BOOL handleKey(KEY pKey, MASK pMask);
virtual BOOL handleKey(KEY pKey, MASK pMask) override;
EPathStatus getPathStatus() const;

View File

@ -46,7 +46,7 @@ public:
std::string getDescriptionForSku(const std::string& sku);
private:
/* virtual */ void initSingleton();
/* virtual */ void initSingleton() override;
void getLandDescriptionsCoro(std::string url);
LLSD mSkuDescriptions;

Some files were not shown because too many files have changed in this diff Show More