diff options
author | Nat Goodspeed <nat@lindenlab.com> | 2024-03-01 10:23:56 -0500 |
---|---|---|
committer | Nat Goodspeed <nat@lindenlab.com> | 2024-03-01 10:23:56 -0500 |
commit | 1204468415dd5248bbd01d7aaede520ede3b9d36 (patch) | |
tree | b378b94ff7266ce5c7f719b3ba55c836837869e9 | |
parent | 0f2248ea12713822c546e362487aa94bb29e2899 (diff) | |
parent | 1820095dd4a64c142e14869c76ae09e1e0ca6a12 (diff) |
Merge DRTVWR-489 to main on promotion of Emoji viewer (#673).
167 files changed, 6869 insertions, 1537 deletions
diff --git a/.github/release.yaml b/.github/release.yaml index 0f4884c944..f550e52020 100644 --- a/.github/release.yaml +++ b/.github/release.yaml @@ -1,18 +1,18 @@ -changelog:
- exclude:
- labels:
- - ignore-for-release
- authors:
- - dependabot
- categories:
- - title: Breaking Changes 🛠
- labels:
- - semver-major
- - breaking-change
- - title: New Features 🎉
- labels:
- - semver-minor
- - enhancement
- - title: Other Changes
- labels:
- - '*'
+changelog: + exclude: + labels: + - ignore-for-release + authors: + - dependabot + categories: + - title: Breaking Changes 🛠 + labels: + - semver-major + - breaking-change + - title: New Features 🎉 + labels: + - semver-minor + - enhancement + - title: Other Changes + labels: + - '*' diff --git a/.gitignore b/.gitignore index 371b8383c0..3b3666af84 100755 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,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 @@ -68,6 +69,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 diff --git a/autobuild.xml b/autobuild.xml index 0e5a5a2a25..effafa31d3 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -104,37 +104,25 @@ <key>archive</key> <map> <key>hash</key> - <string>59c1827cab82516504a2eb31e0aa7e38035b5085</string> + <string>8539775e0a0783bd252bc548b20b3472a8254c31</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-boost/releases/download/v1.81-90bb2df/boost-1.81-darwin64-90bb2df.tar.zst</string> + <string>https://github.com/secondlife/3p-boost/releases/download/v1.81-09d25a7/boost-1.81-darwin64-09d25a7.tar.zst</string> </map> <key>name</key> <string>darwin64</string> </map> - <key>linux64</key> - <map> - <key>archive</key> - <map> - <key>hash</key> - <string>038853b97307a9b65de20c4c50098023</string> - <key>url</key> - <string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9675/45694/boost-1.65.1-linux64-509640.tar.bz2</string> - </map> - <key>name</key> - <string>linux64</string> - </map> <key>windows64</key> <map> <key>archive</key> <map> <key>hash</key> - <string>26214a33c568929ffeeb3463ce183f2888ce4fe4</string> + <string>d40c86fbcb6ce064d546165cbabbf035ea80e07b</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-boost/releases/download/v1.81-90bb2df/boost-1.81-windows64-90bb2df.tar.zst</string> + <string>https://github.com/secondlife/3p-boost/releases/download/v1.81-09d25a7/boost-1.81-windows64-09d25a7.tar.zst</string> </map> <key>name</key> <string>windows64</string> @@ -147,7 +135,7 @@ <key>copyright</key> <string>(see individual source files)</string> <key>version</key> - <string>1.81</string> + <string>1.81-09d25a7</string> <key>name</key> <string>boost</string> <key>description</key> @@ -208,37 +196,25 @@ <key>archive</key> <map> <key>hash</key> - <string>7f447d30d7add80270a55cf3c53000392821a1cb</string> + <string>b1bb8a9c8d458d8842d79f9633fb61df12f1b0ad</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-colladadom/releases/download/v2.3.d1ef72a/colladadom-2.3.d1ef72a-darwin64-d1ef72a.tar.zst</string> + <string>https://github.com/secondlife/3p-colladadom/releases/download/v2.3.ab0c124/colladadom-2.3.ab0c124-darwin64-ab0c124.tar.zst</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>windows64</key> <map> <key>archive</key> <map> <key>hash</key> - <string>b32294a2f31f5b4ca49928e66832aad1bb4a88ac</string> + <string>0df4c05d4efa3019afa4cbf09599df60b586fc5c</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-colladadom/releases/download/v2.3.d1ef72a/colladadom-2.3.d1ef72a-windows64-d1ef72a.tar.zst</string> + <string>https://github.com/secondlife/3p-colladadom/releases/download/v2.3.ab0c124/colladadom-2.3.ab0c124-windows64-ab0c124.tar.zst</string> </map> <key>name</key> <string>windows64</string> @@ -251,7 +227,7 @@ <key>copyright</key> <string>Copyright 2006 Sony Computer Entertainment Inc.</string> <key>version</key> - <string>2.3.d1ef72a</string> + <string>2.3.ab0c124</string> <key>name</key> <string>colladadom</string> </map> @@ -477,6 +453,50 @@ <key>description</key> <string>A headless browser SDK that uses the Chromium Embedded Framework (CEF). It is designed to make it easier to write applications that render modern web content directly to a memory buffer, inject synthesized mouse and keyboard events as well as interact with web based features like JavaScript or cookies.</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>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>platforms</key> @@ -635,6 +655,16 @@ </map> <key>freetype</key> <map> + <key>copyright</key> + <string>Copyright 2006, 2007, 2008, 2009, 2010 by David Turner, Robert Wilhelm, and Werner Lemberg.</string> + <key>description</key> + <string>Font rendering library</string> + <key>license</key> + <string>FreeType</string> + <key>license_file</key> + <string>LICENSES/freetype.txt</string> + <key>name</key> + <string>freetype</string> <key>platforms</key> <map> <key>darwin64</key> @@ -642,11 +672,11 @@ <key>archive</key> <map> <key>hash</key> - <string>912d122aae996483ba814fe8e569394ddca0d42e</string> + <string>d90a5c2fb4a729eeff3965ea6dd0a35cf146d379</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-freetype/releases/download/v2.4.4.4f739fa/freetype-2.4.4.4f739fa-darwin64-4f739fa.tar.zst</string> + <string>https://github.com/secondlife/3p-freetype/releases/download/v.2.12.1.557becd/freetype-2.12.1.557becd-darwin64-557becd.tar.zst</string> </map> <key>name</key> <string>darwin64</string> @@ -656,42 +686,46 @@ <key>archive</key> <map> <key>hash</key> - <string>14f57822f0cedef957a50a03a7b5372075cf8e1c</string> + <string>4a999279562e8f3e4ba02d3e78a844ddf6fe18f1</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-freetype/releases/download/v2.4.4.4f739fa/freetype-2.4.4.4f739fa-linux64-4f739fa.tar.zst</string> + <string>https://github.com/secondlife/3p-freetype/releases/download/v.2.12.1.557becd/freetype-2.12.1.557becd-linux64-557becd.tar.zst</string> </map> <key>name</key> <string>linux64</string> </map> + <key>windows</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>4fecff41a38d67d6b1c97c7c73980667b6a8a1bd</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-freetype/releases/download/v.2.12.1.557becd/freetype-2.12.1.557becd-windows-557becd.tar.zst</string> + </map> + <key>name</key> + <string>windows</string> + </map> <key>windows64</key> <map> <key>archive</key> <map> <key>hash</key> - <string>d175b39257b691a957724e655c6cffe0b5a7b104</string> + <string>1837fdfd44204c78e79134944f824b0211817883</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-freetype/releases/download/v2.4.4.4f739fa/freetype-2.4.4.4f739fa-windows64-4f739fa.tar.zst</string> + <string>https://github.com/secondlife/3p-freetype/releases/download/v.2.12.1.557becd/freetype-2.12.1.557becd-windows64-557becd.tar.zst</string> </map> <key>name</key> <string>windows64</string> </map> </map> - <key>license</key> - <string>FreeType</string> - <key>license_file</key> - <string>LICENSES/freetype.txt</string> - <key>copyright</key> - <string>Copyright 2006, 2007, 2008, 2009, 2010 by David Turner, Robert Wilhelm, and Werner Lemberg.</string> <key>version</key> - <string>2.4.4.4f739fa</string> - <key>name</key> - <string>freetype</string> - <key>description</key> - <string>Font rendering library</string> + <string>2.12.1.557becd</string> </map> <key>glext</key> <map> @@ -766,37 +800,25 @@ <key>archive</key> <map> <key>hash</key> - <string>c016d7333a3ded88c060119b4e3a5847015a8711</string> + <string>dce3174b12136746f5f910e311e895c1b47bf8fb</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-googlemock/releases/download/v1.7.0.77bba00/googlemock-1.7.0.77bba00-darwin64-77bba00.tar.zst</string> + <string>https://github.com/secondlife/3p-googlemock/releases/download/v1.7.0.2b109d4/googlemock-1.7.0.2b109d4-darwin64-2b109d4.tar.zst</string> </map> <key>name</key> <string>darwin64</string> </map> - <key>linux64</key> - <map> - <key>archive</key> - <map> - <key>hash</key> - <string>ff459b58695c76838782847a0b792104</string> - <key>url</key> - <string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9697/45717/googlemock-1.7.0.509686-linux64-509686.tar.bz2</string> - </map> - <key>name</key> - <string>linux64</string> - </map> <key>windows64</key> <map> <key>archive</key> <map> <key>hash</key> - <string>df51dff9a820fc96c18c2bc00b64467e541633a5</string> + <string>265813f84b04c3b03f3d7d33e149b3d5e3cf31db</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-googlemock/releases/download/v1.7.0.77bba00/googlemock-1.7.0.77bba00-windows64-77bba00.tar.zst</string> + <string>https://github.com/secondlife/3p-googlemock/releases/download/v1.7.0.2b109d4/googlemock-1.7.0.2b109d4-windows64-2b109d4.tar.zst</string> </map> <key>name</key> <string>windows64</string> @@ -809,7 +831,7 @@ <key>copyright</key> <string>Copyright 2008, Google Inc.</string> <key>version</key> - <string>1.7.0.77bba00</string> + <string>1.7.0.2b109d4</string> <key>name</key> <string>googlemock</string> <key>description</key> @@ -933,6 +955,54 @@ <key>description</key> <string>Havok source code for libs and demos</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 <http://source.icu-project.org></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>47bc32b991385f1a6530e4c6179b07f64ca6edc7</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-icu4c/releases/download/v4.8.1-7d08d82/icu4c-4.8.1-darwin64-7d08d82.tar.zst</string> + </map> + <key>name</key> + <string>darwin64</string> + </map> + <key>windows64</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>b7db881dac80302e4d9010af34c0bf6ca9897df9</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-icu4c/releases/download/v4.8.1-7d08d82/icu4c-4.8.1-windows64-7d08d82.tar.zst</string> + </map> + <key>name</key> + <string>windows64</string> + </map> + </map> + <key>version</key> + <string>4.8.1-7d08d82</string> + </map> <key>jpegencoderbasic</key> <map> <key>platforms</key> @@ -1850,6 +1920,62 @@ <key>description</key> <string>minizip-ng is a zip manipulation library. Based on work of Gilles Vollant.</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>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>platforms</key> @@ -2554,6 +2680,48 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>description</key> <string>uriparser is a strictly RFC 3986 compliant URI parsing and handling library written in C. uriparser is cross-platform, fast, supports Unicode and is licensed under the New BSD license.</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>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>platforms</key> @@ -2933,7 +3101,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>-DADDRESS_SIZE:STRING=$AUTOBUILD_ADDRSIZE</string> <string>-DROOT_PROJECT_NAME:STRING=SecondLife</string> <string>-DINSTALL_PROPRIETARY=TRUE</string> -</array> + </array> </map> <key>build</key> <map> @@ -2953,11 +3121,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>-DADDRESS_SIZE:STRING=$AUTOBUILD_ADDRSIZE</string> <string>-DROOT_PROJECT_NAME:STRING=SecondLife</string> <string>-DINSTALL_PROPRIETARY=FALSE</string> -</array> + </array> <key>arguments</key> <array> <string>../indra</string> -</array> + </array> </map> <key>name</key> <string>RelWithDebInfoOS</string> @@ -2974,7 +3142,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>-DADDRESS_SIZE:STRING=$AUTOBUILD_ADDRSIZE</string> <string>-DROOT_PROJECT_NAME:STRING=SecondLife</string> <string>-DINSTALL_PROPRIETARY=TRUE</string> -</array> + </array> </map> <key>build</key> <map> @@ -2994,11 +3162,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>-DADDRESS_SIZE:STRING=$AUTOBUILD_ADDRSIZE</string> <string>-DROOT_PROJECT_NAME:STRING=SecondLife</string> <string>-DINSTALL_PROPRIETARY=FALSE</string> -</array> + </array> <key>arguments</key> <array> <string>../indra</string> -</array> + </array> </map> <key>name</key> <string>ReleaseOS</string> @@ -3019,11 +3187,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <array> <string>-G</string> <string>Xcode</string> -</array> + </array> <key>arguments</key> <array> <string>../indra</string> -</array> + </array> </map> <key>build</key> <map> @@ -3036,7 +3204,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>-project</string> <string>SecondLife.xcodeproj</string> <string>-parallelizeTargets</string> -</array> + </array> </map> <key>default</key> <string>True</string> @@ -3051,7 +3219,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <array> <string>-G</string> <string>Xcode</string> -</array> + </array> </map> <key>build</key> <map> @@ -3064,7 +3232,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>-project</string> <string>SecondLife.xcodeproj</string> <string>-parallelizeTargets</string> -</array> + </array> </map> <key>name</key> <string>RelWithDebInfoOS</string> @@ -3077,11 +3245,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <array> <string>-G</string> <string>Xcode</string> -</array> + </array> <key>arguments</key> <array> <string>../indra</string> -</array> + </array> </map> <key>build</key> <map> @@ -3094,7 +3262,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>-project</string> <string>SecondLife.xcodeproj</string> <string>-parallelizeTargets</string> -</array> + </array> </map> <key>name</key> <string>Release</string> @@ -3107,7 +3275,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <array> <string>-G</string> <string>Xcode</string> -</array> + </array> </map> <key>build</key> <map> @@ -3120,7 +3288,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>-project</string> <string>SecondLife.xcodeproj</string> <string>-parallelizeTargets</string> -</array> + </array> </map> <key>name</key> <string>ReleaseOS</string> @@ -3144,11 +3312,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>-G</string> <string>Ninja</string> <string>-DLL_TESTS=Off</string> -</array> + </array> <key>arguments</key> <array> <string>../indra</string> -</array> + </array> </map> <key>build</key> <map> @@ -3169,7 +3337,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>-G</string> <string>Ninja</string> <string>-DLL_TESTS=Off</string> -</array> + </array> </map> <key>build</key> <map> @@ -3207,11 +3375,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>${AUTOBUILD_WIN_CMAKE_GEN|NOTWIN}</string> <string>-A</string> <string>${AUTOBUILD_WIN_VSPLATFORM|NOTWIN}</string> -</array> + </array> <key>arguments</key> <array> <string>..\indra</string> -</array> + </array> </map> <key>build</key> <map> @@ -3221,11 +3389,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <array> <string>/build</string> <string>RelWithDebInfo|${AUTOBUILD_WIN_VSPLATFORM|NOTWIN}</string> -</array> + </array> <key>arguments</key> <array> <string>SecondLife.sln</string> -</array> + </array> </map> <key>default</key> <string>True</string> @@ -3245,11 +3413,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>-DINSTALL_PROPRIETARY=FALSE</string> <string>-DUSE_KDU=FALSE</string> <string>-DUSE_OPENAL:BOOL=ON</string> -</array> + </array> <key>arguments</key> <array> <string>..\indra</string> -</array> + </array> </map> <key>build</key> <map> @@ -3263,11 +3431,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>/p:useenv=true</string> <string>/verbosity:minimal</string> <string>/p:VCBuildAdditionalOptions= /incremental</string> -</array> + </array> <key>arguments</key> <array> <string>SecondLife.sln</string> -</array> + </array> </map> <key>name</key> <string>RelWithDebInfoOS</string> @@ -3282,11 +3450,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>${AUTOBUILD_WIN_CMAKE_GEN|NOTWIN}</string> <string>-A</string> <string>${AUTOBUILD_WIN_VSPLATFORM|NOTWIN}</string> -</array> + </array> <key>arguments</key> <array> <string>..\indra</string> -</array> + </array> </map> <key>build</key> <map> @@ -3296,11 +3464,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <array> <string>/build</string> <string>Release|${AUTOBUILD_WIN_VSPLATFORM|NOTWIN}</string> -</array> + </array> <key>arguments</key> <array> <string>SecondLife.sln</string> -</array> + </array> </map> <key>name</key> <string>Release</string> @@ -3319,11 +3487,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>-DINSTALL_PROPRIETARY=FALSE</string> <string>-DUSE_KDU=FALSE</string> <string>-DUSE_OPENAL:BOOL=ON</string> -</array> + </array> <key>arguments</key> <array> <string>..\indra</string> -</array> + </array> </map> <key>build</key> <map> @@ -3337,11 +3505,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>/p:useenv=true</string> <string>/verbosity:minimal</string> <string>/p:VCBuildAdditionalOptions= /incremental</string> -</array> + </array> <key>arguments</key> <array> <string>SecondLife.sln</string> -</array> + </array> </map> <key>name</key> <string>ReleaseOS</string> diff --git a/doc/contributions.txt b/doc/contributions.txt index a097aad7f6..c902a11874 100755 --- a/doc/contributions.txt +++ b/doc/contributions.txt @@ -239,6 +239,8 @@ Ansariel Hiller SL-15398 SL-18432 SL-19140 + SL-19575 + SL-19623 SL-4126 SL-20224 Aralara Rajal @@ -898,6 +900,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 diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index 1fd83eadff..05c51c018d 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -30,6 +30,7 @@ set(cmake_SOURCE_FILES GoogleMock.cmake Havok.cmake Hunspell.cmake + ICU4C.cmake JsonCpp.cmake LLAddBuildTest.cmake LLAppearance.cmake @@ -64,7 +65,7 @@ set(cmake_SOURCE_FILES VisualLeakDetector.cmake LibVLCPlugin.cmake XmlRpcEpi.cmake - xxHash.cmake + xxHash.cmake ZLIBNG.cmake ) diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index 7938d4f54b..9f79c13a97 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -62,6 +62,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) diff --git a/indra/cmake/ICU4C.cmake b/indra/cmake/ICU4C.cmake new file mode 100644 index 0000000000..7b27665483 --- /dev/null +++ b/indra/cmake/ICU4C.cmake @@ -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) diff --git a/indra/cmake/ViewerMiscLibs.cmake b/indra/cmake/ViewerMiscLibs.cmake index 00f8b77106..cae68fbc11 100644 --- a/indra/cmake/ViewerMiscLibs.cmake +++ b/indra/cmake/ViewerMiscLibs.cmake @@ -18,3 +18,6 @@ endif() use_prebuilt_binary(slvoice) +use_prebuilt_binary(nanosvg) +use_prebuilt_binary(viewer-fonts) +use_prebuilt_binary(emoji_shortcodes) diff --git a/indra/llappearance/llwearabletype.h b/indra/llappearance/llwearabletype.h index 793a33cc87..1fbe19ddd1 100644 --- a/indra/llappearance/llwearabletype.h +++ b/indra/llappearance/llwearabletype.h @@ -37,7 +37,7 @@ class LLWearableType : public LLParamSingleton<LLWearableType> { LLSINGLETON(LLWearableType, LLTranslationBridge::ptr_t &trans); ~LLWearableType(); - void initSingleton(); + void initSingleton() override; public: enum EType { diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 5f4ed2fffa..c947184dc8 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -3,6 +3,7 @@ project(llcommon) include(00-Common) +include(ICU4C) include(LLCommon) include(bugsplat) include(Linking) @@ -282,6 +283,7 @@ target_link_libraries( ll::uriparser ll::oslibraries ll::tracy + ll::icu4c ) target_include_directories(llcommon INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h index 966ce03296..fd878f20ad 100644 --- a/indra/llcommon/llcoros.h +++ b/indra/llcommon/llcoros.h @@ -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 diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 51ef514cf7..cbe5ab6406 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -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__) diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index f6629803ee..82dc7c9f80 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -30,6 +30,7 @@ #include "llerror.h" #include "llfasttimer.h" #include "llsd.h" +#include <unicode/uchar.h> #include <vector> #if LL_WINDOWS @@ -338,8 +339,6 @@ S32 wchar_utf8_length(const llwchar wc) { if (wc < 0x80) { - // This case will also catch negative values which are - // technically invalid. return 1; } else if (wc < 0x800) @@ -364,6 +363,30 @@ S32 wchar_utf8_length(const llwchar wc) } } +std::string wchar_utf8_preview(const llwchar wc) +{ + std::ostringstream oss; + oss << std::hex << std::uppercase << (U32)wc; + + U8 out_bytes[8]; + U32 size = (U32)wchar_to_utf8chars(wc, (char*)out_bytes); + + if (size > 1) + { + oss << " ["; + for (U32 i = 0; i < size; ++i) + { + if (i) + { + oss << ", "; + } + oss << (int)out_bytes[i]; + } + oss << "]"; + } + + return oss.str(); +} S32 wstring_utf8_length(const LLWString& wstr) { @@ -600,6 +623,7 @@ std::string mbcsstring_makeASCII(const std::string& wstr) } return out_str; } + std::string utf8str_removeCRLF(const std::string& utf8str) { if (0 == utf8str.length()) @@ -621,6 +645,119 @@ std::string utf8str_removeCRLF(const std::string& utf8str) return out; } +llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t length) +{ + switch (length) + { + case 2: + return ((utf8str[offset] & 0x1F) << 6) + + (utf8str[offset + 1] & 0x3F); + case 3: + return ((utf8str[offset] & 0x0F) << 12) + + ((utf8str[offset + 1] & 0x3F) << 6) + + (utf8str[offset + 2] & 0x3F); + case 4: + return ((utf8str[offset] & 0x07) << 18) + + ((utf8str[offset + 1] & 0x3F) << 12) + + ((utf8str[offset + 2] & 0x3F) << 6) + + (utf8str[offset + 3] & 0x3F); + case 5: + return ((utf8str[offset] & 0x03) << 24) + + ((utf8str[offset + 1] & 0x3F) << 18) + + ((utf8str[offset + 2] & 0x3F) << 12) + + ((utf8str[offset + 3] & 0x3F) << 6) + + (utf8str[offset + 4] & 0x3F); + case 6: + return ((utf8str[offset] & 0x01) << 30) + + ((utf8str[offset + 1] & 0x3F) << 24) + + ((utf8str[offset + 2] & 0x3F) << 18) + + ((utf8str[offset + 3] & 0x3F) << 12) + + ((utf8str[offset + 4] & 0x3F) << 6) + + (utf8str[offset + 5] & 0x3F); + case 7: + return ((utf8str[offset + 1] & 0x03) << 30) + + ((utf8str[offset + 2] & 0x3F) << 24) + + ((utf8str[offset + 3] & 0x3F) << 18) + + ((utf8str[offset + 4] & 0x3F) << 12) + + ((utf8str[offset + 5] & 0x3F) << 6) + + (utf8str[offset + 6] & 0x3F); + } + return LL_UNKNOWN_CHAR; +} + +std::string utf8str_showBytesUTF8(const std::string& utf8str) +{ + std::string result; + + bool in_sequence = false; + size_t sequence_size = 0; + size_t byte_index = 0; + size_t source_length = utf8str.size(); + + auto open_sequence = [&]() + { + if (!result.empty() && result.back() != '\n') + result += '\n'; // Use LF as a separator before new UTF-8 sequence + result += '['; + in_sequence = true; + }; + + auto close_sequence = [&]() + { + llwchar unicode = utf8str_to_wchar(utf8str, byte_index - sequence_size, sequence_size); + if (unicode != LL_UNKNOWN_CHAR) + { + result += llformat("+%04X", unicode); + } + result += ']'; + in_sequence = false; + sequence_size = 0; + }; + + while (byte_index < source_length) + { + U8 byte = utf8str[byte_index]; + if (byte >= 0x80) // Part of an UTF-8 sequence + { + if (!in_sequence) // Start new UTF-8 sequence + { + open_sequence(); + } + else if (byte >= 0xC0) // Start another UTF-8 sequence + { + close_sequence(); + open_sequence(); + } + else // Continue the same UTF-8 sequence + { + result += '.'; + } + result += llformat("%02X", byte); // The byte is represented in hexadecimal form + ++sequence_size; + } + else // ASCII symbol is represented as a character + { + if (in_sequence) // End of UTF-8 sequence + { + close_sequence(); + if (byte != '\n') + { + result += '\n'; // Use LF as a separator between UTF-8 and ASCII + } + } + result += byte; + } + ++byte_index; + } + + if (in_sequence) // End of UTF-8 sequence + { + close_sequence(); + } + + return result; +} + #if LL_WINDOWS unsigned int ll_wstring_default_code_page() { @@ -833,6 +970,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) { diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 1fd6cac14a..bfbf25d9ab 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -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); @@ -678,6 +682,8 @@ LL_COMMON_API S32 wstring_utf8_length(const LLWString& wstr); // Length in bytes of this wide char in a UTF8 string LL_COMMON_API S32 wchar_utf8_length(const llwchar wc); +LL_COMMON_API std::string wchar_utf8_preview(const llwchar wc); + LL_COMMON_API std::string utf8str_tolower(const std::string& utf8str); // Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. @@ -737,6 +743,9 @@ LL_COMMON_API std::string mbcsstring_makeASCII(const std::string& str); LL_COMMON_API std::string utf8str_removeCRLF(const std::string& utf8str); +LL_COMMON_API llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t length); + +LL_COMMON_API std::string utf8str_showBytesUTF8(const std::string& utf8str); #if LL_WINDOWS /* @name Windows string helpers @@ -1594,6 +1603,29 @@ void LLStringUtilBase<T>::replaceTabsWithSpaces( string_type& str, size_type spa } //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) { diff --git a/indra/llcommon/tests/llsingleton_test.cpp b/indra/llcommon/tests/llsingleton_test.cpp index 15ffe68e67..6f8aaaa0cb 100644 --- a/indra/llcommon/tests/llsingleton_test.cpp +++ b/indra/llcommon/tests/llsingleton_test.cpp @@ -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(); diff --git a/indra/llinventory/llfoldertype.cpp b/indra/llinventory/llfoldertype.cpp index d2c3b419ab..4856fe4619 100644 --- a/indra/llinventory/llfoldertype.cpp +++ b/indra/llinventory/llfoldertype.cpp @@ -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; } diff --git a/indra/llinventory/llinventorysettings.cpp b/indra/llinventory/llinventorysettings.cpp index 81485b3a97..bc604097da 100644 --- a/indra/llinventory/llinventorysettings.cpp +++ b/indra/llinventory/llinventorysettings.cpp @@ -62,7 +62,7 @@ class LLSettingsDictionary : public LLSingleton<LLSettingsDictionary>, { LLSINGLETON(LLSettingsDictionary); - void initSingleton(); + void initSingleton() override; }; LLSettingsDictionary::LLSettingsDictionary() diff --git a/indra/llmessage/llexperiencecache.h b/indra/llmessage/llexperiencecache.h index 1c97133723..8be4c64dfc 100644 --- a/indra/llmessage/llexperiencecache.h +++ b/indra/llmessage/llexperiencecache.h @@ -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; diff --git a/indra/llmessage/llproxy.h b/indra/llmessage/llproxy.h index 25f6977e14..8a64cdbfaa 100644 --- a/indra/llmessage/llproxy.h +++ b/indra/llmessage/llproxy.h @@ -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. diff --git a/indra/llrender/CMakeLists.txt b/indra/llrender/CMakeLists.txt index a95daed6cd..7f881c8bb3 100644 --- a/indra/llrender/CMakeLists.txt +++ b/indra/llrender/CMakeLists.txt @@ -15,6 +15,7 @@ set(llrender_SOURCE_FILES llcubemaparray.cpp llfontbitmapcache.cpp llfontfreetype.cpp + llfontfreetypesvg.cpp llfontgl.cpp llfontregistry.cpp llgl.cpp @@ -43,6 +44,7 @@ set(llrender_HEADER_FILES llcubemaparray.h llfontgl.h llfontfreetype.h + llfontfreetypesvg.h llfontbitmapcache.h llfontregistry.h llgl.h diff --git a/indra/llrender/llfontbitmapcache.cpp b/indra/llrender/llfontbitmapcache.cpp index c71e24c83a..42b0045cf3 100644 --- a/indra/llrender/llfontbitmapcache.cpp +++ b/indra/llrender/llfontbitmapcache.cpp @@ -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; + + S32 image_width = mMaxCharWidth * 20; + S32 pow_iw = 2; + while (pow_iw < image_width) + { + 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(U32 bitmap_num) const +LLImageRaw *LLFontBitmapCache::getImageRaw(EFontGlyphType bitmap_type, U32 bitmap_num) const { - if (bitmap_num >= mImageRawVec.size()) - return NULL; + 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_num]; + return mImageRawVec[bitmap_idx][bitmap_num]; } -LLImageGL *LLFontBitmapCache::getImageGL(U32 bitmap_num) const +LLImageGL *LLFontBitmapCache::getImageGL(EFontGlyphType bitmap_type, U32 bitmap_num) const { - if (bitmap_num >= mImageGLVec.size()) - return NULL; + 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_num]; + return mImageGLVec[bitmap_idx][bitmap_num]; } -BOOL LLFontBitmapCache::nextOpenPos(S32 width, S32 &pos_x, S32 &pos_y, S32& bitmap_num) +BOOL LLFontBitmapCache::nextOpenPos(S32 width, S32& pos_x, S32& pos_y, EFontGlyphType bitmap_type, U32& bitmap_num) { - if ((mBitmapNum<0) || (mCurrentOffsetX + width + 1) > mBitmapWidth) + 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 ((mBitmapNum<0) || (mCurrentOffsetY + 2*mMaxCharHeight + 2) > mBitmapHeight) + 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); - - // 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) + + 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) { - pow_iw *= 2; + image_raw->clear(255, 0); } - 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; - } + // Make corresponding GL image. + 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; + } +} diff --git a/indra/llrender/llfontbitmapcache.h b/indra/llrender/llfontbitmapcache.h index 7de3a6b56f..c63281ab70 100644 --- a/indra/llrender/llfontbitmapcache.h +++ b/indra/llrender/llfontbitmapcache.h @@ -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 diff --git a/indra/llrender/llfontfreetype.cpp b/indra/llrender/llfontfreetype.cpp index e964d1586f..d87fb5245c 100644 --- a/indra/llrender/llfontfreetype.cpp +++ b/indra/llrender/llfontfreetype.cpp @@ -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) -{ - mFallbackFonts = font; -} - -const LLFontFreetype::font_vector_t &LLFontFreetype::getFallbackFonts() const +void LLFontFreetype::addFallbackFont(const LLPointer<LLFontFreetype>& fallback_font, const char_functor_t& functor) { - 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,95 @@ 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; + } + + FT_Error error = FT_Load_Glyph(mFTFace, glyph_index, load_flags); + if (FT_Err_Ok != error) + { + std::string message = llformat( + "Error %d (%s) loading glyph %u: bitmap_type=%u, load_flags=%d", + error, FT_Error_String(error), glyph_index, bitmap_type, load_flags); + LL_WARNS_ONCE() << message << LL_ENDL; + error = FT_Load_Glyph(mFTFace, glyph_index, load_flags ^ FT_LOAD_COLOR); + llassert_always_msg(FT_Err_Ok == error, message.c_str()); + } llassert_always(! FT_Render_Glyph(mFTFace->glyph, gFontRenderMode) ); @@ -601,7 +684,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 +694,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 +718,7 @@ void LLFontFreetype::resetBitmapCache() if(!mIsFallback) { // Add the empty glyph - addGlyphFromFont(this, 0, 0); + addGlyphFromFont(this, 0, 0, EFontGlyphType::Grayscale); } } @@ -651,6 +732,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 +775,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; } diff --git a/indra/llrender/llfontfreetype.h b/indra/llrender/llfontfreetype.h index f61f169987..b036d337ba 100644 --- a/indra/llrender/llfontfreetype.h +++ b/indra/llrender/llfontfreetype.h @@ -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; diff --git a/indra/llrender/llfontfreetypesvg.cpp b/indra/llrender/llfontfreetypesvg.cpp new file mode 100644 index 0000000000..19d327a4c9 --- /dev/null +++ b/indra/llrender/llfontfreetypesvg.cpp @@ -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; +} diff --git a/indra/llrender/llfontfreetypesvg.h b/indra/llrender/llfontfreetypesvg.h new file mode 100644 index 0000000000..b5f541991a --- /dev/null +++ b/indra/llrender/llfontfreetypesvg.h @@ -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); +}; diff --git a/indra/llrender/llfontgl.cpp b/indra/llrender/llfontgl.cpp index 15fddbc99f..53661b6a63 100644 --- a/indra/llrender/llfontgl.cpp +++ b/indra/llrender/llfontgl.cpp @@ -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(); } @@ -420,22 +420,22 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons S32 LLFontGL::render(const LLWString &text, S32 begin_offset, F32 x, F32 y, const LLColor4 &color) const { - return render(text, begin_offset, x, y, color, LEFT, BASELINE, NORMAL, NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE); + return render(text, begin_offset, x, y, color, LEFT, BASELINE, NORMAL, NO_SHADOW); } -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 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); } // 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 @@ -1010,6 +1030,20 @@ LLFontGL::VAlign LLFontGL::vAlignFromName(const std::string& name) } //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() { static LLFontGL* fontp = getFont(LLFontDescriptor("Monospace","Monospace",0)); diff --git a/indra/llrender/llfontgl.h b/indra/llrender/llfontgl.h index 9bbf9ce0c4..f6ec416c8b 100644 --- a/indra/llrender/llfontgl.h +++ b/indra/llrender/llfontgl.h @@ -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 = TRUE) 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 = TRUE) 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 = TRUE) 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, S32 max_pixels = S32_MAX, F32* right_x = NULL, BOOL use_ellipses = FALSE, BOOL use_color = TRUE) 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(); diff --git a/indra/llrender/llfontregistry.cpp b/indra/llrender/llfontregistry.cpp index 9750bd4fa1..d2c7e466e6 100644 --- a/indra/llrender/llfontregistry.cpp +++ b/indra/llrender/llfontregistry.cpp @@ -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(); } } } diff --git a/indra/llrender/llfontregistry.h b/indra/llrender/llfontregistry.h index e30c81c630..b0ef72c5de 100644 --- a/indra/llrender/llfontregistry.h +++ b/indra/llrender/llfontregistry.h @@ -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; diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index 4c7c8e6f5c..9d3abf32bb 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -2890,7 +2890,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 } } } diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 9108c6143c..a0314cb5f2 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -29,6 +29,8 @@ set(llui_SOURCE_FILES lldockcontrol.cpp lldraghandle.cpp lleditmenuhandler.cpp + llemojidictionary.cpp + llemojihelper.cpp llf32uictrl.cpp llfiltereditor.cpp llflashtimer.cpp @@ -139,6 +141,8 @@ set(llui_HEADER_FILES lldockablefloater.h lldockcontrol.h lleditmenuhandler.h + llemojidictionary.h + llemojihelper.h llf32uictrl.h llfiltereditor.h llflashtimer.h diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp index 49d275997a..9ef019840a 100644 --- a/indra/llui/llbutton.cpp +++ b/indra/llui/llbutton.cpp @@ -68,6 +68,7 @@ LLButton::Params::Params() label_shadow("label_shadow", true), auto_resize("auto_resize", false), use_ellipses("use_ellipses", false), + use_font_color("use_font_color", true), image_unselected("image_unselected"), image_selected("image_selected"), image_hover_selected("image_hover_selected"), @@ -160,6 +161,7 @@ LLButton::LLButton(const LLButton::Params& p) mDropShadowedText(p.label_shadow), mAutoResize(p.auto_resize), mUseEllipses( p.use_ellipses ), + mUseFontColor( p.use_font_color), mHAlign(p.font_halign), mLeftHPad(p.pad_left), mRightHPad(p.pad_right), @@ -961,7 +963,7 @@ void LLButton::draw() LLFontGL::NORMAL, mDropShadowedText ? LLFontGL::DROP_SHADOW_SOFT : LLFontGL::NO_SHADOW, S32_MAX, text_width, - NULL, mUseEllipses); + NULL, mUseEllipses, mUseFontColor); } LLUICtrl::draw(); @@ -1021,6 +1023,16 @@ BOOL LLButton::toggleState() return flipped; } +void LLButton::setLabel( const std::string& label ) +{ + mUnselectedLabel = mSelectedLabel = label; +} + +void LLButton::setLabel( const LLUIString& label ) +{ + mUnselectedLabel = mSelectedLabel = label; +} + void LLButton::setLabel( const LLStringExplicit& label ) { setLabelUnselected(label); @@ -1052,14 +1064,7 @@ bool LLButton::labelIsTruncated() const const LLUIString& LLButton::getCurrentLabel() const { - if( getToggleState() ) - { - return mSelectedLabel; - } - else - { - return mUnselectedLabel; - } + return getToggleState() ? mSelectedLabel : mUnselectedLabel; } void LLButton::setImageUnselected(LLPointer<LLUIImage> image) diff --git a/indra/llui/llbutton.h b/indra/llui/llbutton.h index ccd31e90c0..257159f64f 100644 --- a/indra/llui/llbutton.h +++ b/indra/llui/llbutton.h @@ -73,6 +73,7 @@ public: Optional<bool> label_shadow; Optional<bool> auto_resize; Optional<bool> use_ellipses; + Optional<bool> use_font_color; // images Optional<LLUIImage*> image_unselected, @@ -174,6 +175,7 @@ public: void setUnselectedLabelColor( const LLColor4& c ) { mUnselectedLabelColor = c; } void setSelectedLabelColor( const LLColor4& c ) { mSelectedLabelColor = c; } void setUseEllipses( BOOL use_ellipses ) { mUseEllipses = use_ellipses; } + void setUseFontColor( BOOL use_font_color) { mUseFontColor = use_font_color; } boost::signals2::connection setClickedCallback(const CommitCallbackParam& cb); @@ -238,6 +240,8 @@ public: void autoResize(); // resize with label of current btn state void resize(LLUIString label); // resize with label input + void setLabel(const std::string& label); + void setLabel(const LLUIString& label); void setLabel( const LLStringExplicit& label); virtual BOOL setLabelArg( const std::string& key, const LLStringExplicit& text ); void setLabelUnselected(const LLStringExplicit& label); @@ -353,6 +357,7 @@ protected: bool mDropShadowedText; bool mAutoResize; bool mUseEllipses; + bool mUseFontColor; bool mBorderEnabled; bool mFlashing; diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp new file mode 100644 index 0000000000..f16c38a11a --- /dev/null +++ b/indra/llui/llemojidictionary.cpp @@ -0,0 +1,469 @@ +/** +* @file llemojidictionary.cpp +* @brief Implementation of LLEmojiDictionary +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "linden_common.h" + +#include "lldir.h" +#include "llemojidictionary.h" +#include "llsdserialize.h" + +#include <boost/algorithm/string.hpp> +#include <boost/range/adaptor/filtered.hpp> +#include <boost/range/algorithm/transform.hpp> + +// ============================================================================ +// Constants +// + +static const std::string SKINNED_EMOJI_FILENAME("emoji_characters.xml"); +static const std::string SKINNED_CATEGORY_FILENAME("emoji_categories.xml"); +static const std::string COMMON_GROUP_FILENAME("emoji_groups.xml"); +static const std::string GROUP_NAME_SKIP("skip"); +// https://www.compart.com/en/unicode/U+1F302 +static const S32 GROUP_OTHERS_IMAGE_INDEX = 0x1F302; + +// ============================================================================ +// Helper functions +// + +template<class T> +std::list<T> llsd_array_to_list(const LLSD& sd, std::function<void(T&)> mutator = {}); + +template<> +std::list<std::string> llsd_array_to_list(const LLSD& sd, std::function<void(std::string&)> mutator) +{ + std::list<std::string> result; + for (LLSD::array_const_iterator it = sd.beginArray(), end = sd.endArray(); it != end; ++it) + { + const LLSD& entry = *it; + if (!entry.isString()) + continue; + + result.push_back(entry.asStringRef()); + if (mutator) + { + mutator(result.back()); + } + } + return result; +} + +struct emoji_filter_base +{ + emoji_filter_base(const std::string& needle) + { + // Search without the colon (if present) so the user can type ':food' and see all emojis in the 'Food' category + mNeedle = (boost::starts_with(needle, ":")) ? needle.substr(1) : needle; + LLStringUtil::toLower(mNeedle); + } + +protected: + std::string mNeedle; +}; + +struct emoji_filter_shortcode_or_category_contains : public emoji_filter_base +{ + emoji_filter_shortcode_or_category_contains(const std::string& needle) : emoji_filter_base(needle) {} + + bool operator()(const LLEmojiDescriptor& descr) const + { + for (const auto& short_code : descr.ShortCodes) + { + if (boost::icontains(short_code, mNeedle)) + return true; + } + + if (boost::icontains(descr.Category, mNeedle)) + return true; + + return false; + } +}; + +std::string LLEmojiDescriptor::getShortCodes() const +{ + std::string result; + for (const std::string& shortCode : ShortCodes) + { + if (!result.empty()) + { + result += ", "; + } + result += shortCode; + } + return result; +} + +// ============================================================================ +// LLEmojiDictionary class +// + +LLEmojiDictionary::LLEmojiDictionary() +{ +} + +// static +void LLEmojiDictionary::initClass() +{ + LLEmojiDictionary* pThis = &LLEmojiDictionary::initParamSingleton(); + + pThis->loadTranslations(); + pThis->loadGroups(); + pThis->loadEmojis(); +} + +LLWString LLEmojiDictionary::findMatchingEmojis(const std::string& needle) const +{ + LLWString result; + boost::transform(mEmojis | boost::adaptors::filtered(emoji_filter_shortcode_or_category_contains(needle)), + std::back_inserter(result), [](const auto& descr) { return descr.Character; }); + return result; +} + +// static +bool LLEmojiDictionary::searchInShortCode(std::size_t& begin, std::size_t& end, const std::string& shortCode, const std::string& needle) +{ + begin = 0; + end = 1; + std::size_t index = 1; + // Search for begin + char d = tolower(needle[index++]); + while (end < shortCode.size()) + { + char s = tolower(shortCode[end++]); + if (s == d) + { + begin = end - 1; + break; + } + } + if (!begin) + return false; + // Search for end + d = tolower(needle[index++]); + if (!d) + return true; + while (end < shortCode.size() && index <= needle.size()) + { + char s = tolower(shortCode[end++]); + if (s == d) + { + if (index == needle.size()) + return true; + d = tolower(needle[index++]); + continue; + } + switch (s) + { + case L'-': + case L'_': + case L'+': + continue; + } + break; + } + return false; +} + +void LLEmojiDictionary::findByShortCode( + std::vector<LLEmojiSearchResult>& result, + const std::string& needle +) const +{ + result.clear(); + + if (needle.empty() || needle.front() != ':') + return; + + std::map<llwchar, std::vector<LLEmojiSearchResult>> results; + + for (const LLEmojiDescriptor& d : mEmojis) + { + if (!d.ShortCodes.empty()) + { + const std::string& shortCode = d.ShortCodes.front(); + if (shortCode.size() >= needle.size() && shortCode.front() == needle.front()) + { + std::size_t begin, end; + if (searchInShortCode(begin, end, shortCode, needle)) + { + results[begin].emplace_back(d.Character, shortCode, begin, end); + } + } + } + } + + for (const auto& it : results) + { +#ifdef __cpp_lib_containers_ranges + result.append_range(it.second); +#else + result.insert(result.end(), it.second.cbegin(), it.second.cend()); +#endif + } +} + +const LLEmojiDescriptor* LLEmojiDictionary::getDescriptorFromEmoji(llwchar emoji) const +{ + const auto it = mEmoji2Descr.find(emoji); + return (mEmoji2Descr.end() != it) ? it->second : nullptr; +} + +const LLEmojiDescriptor* LLEmojiDictionary::getDescriptorFromShortCode(const std::string& short_code) const +{ + const auto it = mShortCode2Descr.find(short_code); + return (mShortCode2Descr.end() != it) ? it->second : nullptr; +} + +std::string LLEmojiDictionary::getNameFromEmoji(llwchar ch) const +{ + const auto it = mEmoji2Descr.find(ch); + return (mEmoji2Descr.end() != it) ? it->second->ShortCodes.front() : LLStringUtil::null; +} + +bool LLEmojiDictionary::isEmoji(llwchar ch) const +{ + // Currently used codes: A9,AE,203C,2049,2122,...,2B55,3030,303D,3297,3299,1F004,...,1FAF6 + if (ch == 0xA9 || ch == 0xAE || (ch >= 0x2000 && ch < 0x3300) || (ch >= 0x1F000 && ch < 0x20000)) + { + return mEmoji2Descr.find(ch) != mEmoji2Descr.end(); + } + + return false; +} + +void LLEmojiDictionary::loadTranslations() +{ + std::vector<std::string> filenames = gDirUtilp->findSkinnedFilenames(LLDir::XUI, SKINNED_CATEGORY_FILENAME, LLDir::CURRENT_SKIN); + if (filenames.empty()) + { + LL_WARNS() << "Emoji file categories not found" << LL_ENDL; + return; + } + + const std::string filename = filenames.back(); + llifstream file(filename.c_str()); + if (!file.is_open()) + { + LL_WARNS() << "Emoji file categories failed to open" << LL_ENDL; + return; + } + + LL_DEBUGS() << "Loading emoji categories file at " << filename << LL_ENDL; + + LLSD data; + LLSDSerialize::fromXML(data, file); + if (data.isUndefined()) + { + LL_WARNS() << "Emoji file categories missing or ill-formed" << LL_ENDL; + return; + } + + // Register translations for all categories + for (LLSD::array_const_iterator it = data.beginArray(), end = data.endArray(); it != end; ++it) + { + const LLSD& sd = *it; + const std::string& name = sd["Name"].asStringRef(); + const std::string& category = sd["Category"].asStringRef(); + if (!name.empty() && !category.empty()) + { + mTranslations[name] = category; + } + else + { + LL_WARNS() << "Skipping invalid emoji category '" << name << "' => '" << category << "'" << LL_ENDL; + } + } +} + +void LLEmojiDictionary::loadGroups() +{ + const std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, COMMON_GROUP_FILENAME); + llifstream file(filename.c_str()); + if (!file.is_open()) + { + LL_WARNS() << "Emoji file groups failed to open" << LL_ENDL; + return; + } + + LL_DEBUGS() << "Loading emoji groups file at " << filename << LL_ENDL; + + LLSD data; + LLSDSerialize::fromXML(data, file); + if (data.isUndefined()) + { + LL_WARNS() << "Emoji file groups missing or ill-formed" << LL_ENDL; + return; + } + + mGroups.clear(); + + // Register all groups + for (LLSD::array_const_iterator it = data.beginArray(), end = data.endArray(); it != end; ++it) + { + const LLSD& sd = *it; + const std::string& name = sd["Name"].asStringRef(); + if (name == GROUP_NAME_SKIP) + { + mSkipCategories = loadCategories(sd); + translateCategories(mSkipCategories); + } + else + { + // Add new group + mGroups.emplace_back(); + LLEmojiGroup& group = mGroups.back(); + group.Character = loadIcon(sd); + group.Categories = loadCategories(sd); + translateCategories(group.Categories); + + for (const std::string& category : group.Categories) + { + mCategory2Group.insert(std::make_pair(category, &group)); + } + } + } + + // Add group "others" + mGroups.emplace_back(); + mGroups.back().Character = GROUP_OTHERS_IMAGE_INDEX; +} + +void LLEmojiDictionary::loadEmojis() +{ + std::vector<std::string> filenames = gDirUtilp->findSkinnedFilenames(LLDir::XUI, SKINNED_EMOJI_FILENAME, LLDir::CURRENT_SKIN); + if (filenames.empty()) + { + LL_WARNS() << "Emoji file characters not found" << LL_ENDL; + return; + } + + const std::string filename = filenames.back(); + llifstream file(filename.c_str()); + if (!file.is_open()) + { + LL_WARNS() << "Emoji file characters failed to open" << LL_ENDL; + return; + } + + LL_DEBUGS() << "Loading emoji characters file at " << filename << LL_ENDL; + + LLSD data; + LLSDSerialize::fromXML(data, file); + if (data.isUndefined()) + { + LL_WARNS() << "Emoji file characters missing or ill-formed" << LL_ENDL; + return; + } + + for (LLSD::array_const_iterator it = data.beginArray(), end = data.endArray(); it != end; ++it) + { + const LLSD& sd = *it; + + llwchar icon = loadIcon(sd); + if (!icon) + { + LL_WARNS() << "Skipping invalid emoji descriptor (no icon)" << LL_ENDL; + continue; + } + + std::list<std::string> categories = loadCategories(sd); + if (categories.empty()) + { + LL_WARNS() << "Skipping invalid emoji descriptor (no categories)" << LL_ENDL; + continue; + } + + std::string category = categories.front(); + + if (std::find(mSkipCategories.begin(), mSkipCategories.end(), category) != mSkipCategories.end()) + { + // This category is listed for skip + continue; + } + + std::list<std::string> shortCodes = loadShortCodes(sd); + if (shortCodes.empty()) + { + LL_WARNS() << "Skipping invalid emoji descriptor (no shortCodes)" << LL_ENDL; + continue; + } + + if (mCategory2Group.find(category) == mCategory2Group.end()) + { + // Add unknown category to "others" group + mGroups.back().Categories.push_back(category); + mCategory2Group.insert(std::make_pair(category, &mGroups.back())); + } + + mEmojis.emplace_back(); + LLEmojiDescriptor& emoji = mEmojis.back(); + emoji.Character = icon; + emoji.Category = category; + emoji.ShortCodes = std::move(shortCodes); + + mEmoji2Descr.insert(std::make_pair(icon, &emoji)); + mCategory2Descrs[category].push_back(&emoji); + for (const std::string& shortCode : emoji.ShortCodes) + { + mShortCode2Descr.insert(std::make_pair(shortCode, &emoji)); + } + } +} + +llwchar LLEmojiDictionary::loadIcon(const LLSD& sd) +{ + // We don't currently support character composition + const LLWString icon = utf8str_to_wstring(sd["Character"].asString()); + return (1 == icon.size()) ? icon[0] : L'\0'; +} + +std::list<std::string> LLEmojiDictionary::loadCategories(const LLSD& sd) +{ + static const std::string key("Categories"); + return llsd_array_to_list<std::string>(sd[key]); +} + +std::list<std::string> LLEmojiDictionary::loadShortCodes(const LLSD& sd) +{ + static const std::string key("ShortCodes"); + auto toLower = [](std::string& str) { LLStringUtil::toLower(str); }; + return llsd_array_to_list<std::string>(sd[key], toLower); +} + +void LLEmojiDictionary::translateCategories(std::list<std::string>& categories) +{ + for (std::string& category : categories) + { + auto it = mTranslations.find(category); + if (it != mTranslations.end()) + { + category = it->second; + } + } +} + +// ============================================================================ diff --git a/indra/llui/llemojidictionary.h b/indra/llui/llemojidictionary.h new file mode 100644 index 0000000000..4af376df64 --- /dev/null +++ b/indra/llui/llemojidictionary.h @@ -0,0 +1,126 @@ +/** +* @file llemojidictionary.h +* @brief Header file for LLEmojiDictionary +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#pragma once + +#include "lldictionary.h" +#include "llinitdestroyclass.h" +#include "llsingleton.h" + +// ============================================================================ +// LLEmojiDescriptor class +// + +struct LLEmojiDescriptor +{ + llwchar Character; + std::string Category; + std::list<std::string> ShortCodes; + std::string getShortCodes() const; +}; + +// ============================================================================ +// LLEmojiGroup class +// + +struct LLEmojiGroup +{ + llwchar Character; + std::list<std::string> Categories; +}; + +// ============================================================================ +// LLEmojiSearchResult class +// + +struct LLEmojiSearchResult +{ + llwchar Character; + std::string String; + std::size_t Begin, End; + + LLEmojiSearchResult(llwchar character, const std::string& string, std::size_t begin, std::size_t end) + : Character(character) + , String(string) + , Begin(begin) + , End(end) + { + } +}; + +// ============================================================================ +// LLEmojiDictionary class +// + +class LLEmojiDictionary : public LLParamSingleton<LLEmojiDictionary>, public LLInitClass<LLEmojiDictionary> +{ + LLSINGLETON(LLEmojiDictionary); + ~LLEmojiDictionary() override {}; + +public: + typedef std::map<std::string, std::string> cat2cat_map_t; + typedef std::map<std::string, const LLEmojiGroup*> cat2group_map_t; + typedef std::map<llwchar, const LLEmojiDescriptor*> emoji2descr_map_t; + typedef std::map<std::string, const LLEmojiDescriptor*> code2descr_map_t; + typedef std::map<std::string, std::vector<const LLEmojiDescriptor*>> cat2descrs_map_t; + + static void initClass(); + LLWString findMatchingEmojis(const std::string& needle) const; + static bool searchInShortCode(std::size_t& begin, std::size_t& end, const std::string& shortCode, const std::string& needle); + void findByShortCode(std::vector<LLEmojiSearchResult>& result, const std::string& needle) const; + const LLEmojiDescriptor* getDescriptorFromEmoji(llwchar emoji) const; + const LLEmojiDescriptor* getDescriptorFromShortCode(const std::string& short_code) const; + std::string getNameFromEmoji(llwchar ch) const; + bool isEmoji(llwchar ch) const; + + const std::vector<LLEmojiGroup>& getGroups() const { return mGroups; } + const emoji2descr_map_t& getEmoji2Descr() const { return mEmoji2Descr; } + const cat2descrs_map_t& getCategory2Descrs() const { return mCategory2Descrs; } + const code2descr_map_t& getShortCode2Descr() const { return mShortCode2Descr; } + +private: + void loadTranslations(); + void loadGroups(); + void loadEmojis(); + + static llwchar loadIcon(const LLSD& sd); + static std::list<std::string> loadCategories(const LLSD& sd); + static std::list<std::string> loadShortCodes(const LLSD& sd); + void translateCategories(std::list<std::string>& categories); + +private: + std::vector<LLEmojiGroup> mGroups; + std::list<LLEmojiDescriptor> mEmojis; + std::list<std::string> mSkipCategories; + + cat2cat_map_t mTranslations; + cat2group_map_t mCategory2Group; + emoji2descr_map_t mEmoji2Descr; + cat2descrs_map_t mCategory2Descrs; + code2descr_map_t mShortCode2Descr; +}; + +// ============================================================================ diff --git a/indra/llui/llemojihelper.cpp b/indra/llui/llemojihelper.cpp new file mode 100644 index 0000000000..89e6ddf987 --- /dev/null +++ b/indra/llui/llemojihelper.cpp @@ -0,0 +1,169 @@ +/** +* @file llemojihelper.h +* @brief Header file for LLEmojiHelper +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "linden_common.h" + +#include "llemojidictionary.h" +#include "llemojihelper.h" +#include "llfloater.h" +#include "llfloaterreg.h" +#include "lluictrl.h" + +// ============================================================================ +// Constants +// + +constexpr char DEFAULT_EMOJI_HELPER_FLOATER[] = "emoji_picker"; +constexpr S32 HELPER_FLOATER_OFFSET_X = 0; +constexpr S32 HELPER_FLOATER_OFFSET_Y = 0; + +// ============================================================================ +// LLEmojiHelper +// + +std::string LLEmojiHelper::getToolTip(llwchar ch) const +{ + return LLEmojiDictionary::instance().getNameFromEmoji(ch); +} + +bool LLEmojiHelper::isActive(const LLUICtrl* ctrl_p) const +{ + return mHostHandle.get() == ctrl_p; +} + +// static +bool LLEmojiHelper::isCursorInEmojiCode(const LLWString& wtext, S32 cursorPos, S32* pShortCodePos) +{ + // If the cursor is currently on a colon start the check one character further back + S32 shortCodePos = (cursorPos == 0 || L':' != wtext[cursorPos - 1]) ? cursorPos : cursorPos - 1; + + auto isPartOfShortcode = [](llwchar ch) { + switch (ch) + { + case L'-': + case L'_': + case L'+': + return true; + default: + return LLStringOps::isAlnum(ch); + } + }; + while (shortCodePos > 1 && isPartOfShortcode(wtext[shortCodePos - 1])) + { + shortCodePos--; + } + + bool isShortCode = (L':' == wtext[shortCodePos - 1]) && (cursorPos - shortCodePos >= 2); + if (pShortCodePos) + *pShortCodePos = (isShortCode) ? shortCodePos - 1 : -1; + return isShortCode; +} + +void LLEmojiHelper::showHelper(LLUICtrl* hostctrl_p, S32 local_x, S32 local_y, const std::string& short_code, std::function<void(llwchar)> cb) +{ + // Commit immediately if the user already typed a full shortcode + if (const auto* emojiDescrp = LLEmojiDictionary::instance().getDescriptorFromShortCode(short_code)) + { + cb(emojiDescrp->Character); + hideHelper(); + return; + } + + if (mHelperHandle.isDead()) + { + LLFloater* pHelperFloater = LLFloaterReg::getInstance(DEFAULT_EMOJI_HELPER_FLOATER); + mHelperHandle = pHelperFloater->getHandle(); + mHelperCommitConn = pHelperFloater->setCommitCallback(std::bind([&](const LLSD& sdValue) { onCommitEmoji(utf8str_to_wstring(sdValue.asStringRef())[0]); }, std::placeholders::_2)); + } + setHostCtrl(hostctrl_p); + mEmojiCommitCb = cb; + + S32 floater_x, floater_y; + if (!hostctrl_p->localPointToOtherView(local_x, local_y, &floater_x, &floater_y, gFloaterView)) + { + LL_ERRS() << "Cannot show emoji helper for non-floater controls." << LL_ENDL; + return; + } + + LLFloater* pHelperFloater = mHelperHandle.get(); + LLRect rect = pHelperFloater->getRect(); + S32 left = floater_x - HELPER_FLOATER_OFFSET_X; + S32 top = floater_y - HELPER_FLOATER_OFFSET_Y + rect.getHeight(); + rect.setLeftTopAndSize(left, top, rect.getWidth(), rect.getHeight()); + pHelperFloater->setRect(rect); + pHelperFloater->openFloater(LLSD().with("hint", short_code)); +} + +void LLEmojiHelper::hideHelper(const LLUICtrl* ctrl_p, bool strict) +{ + mIsHideDisabled &= !strict; + if (mIsHideDisabled || (ctrl_p && !isActive(ctrl_p))) + { + return; + } + + setHostCtrl(nullptr); +} + +bool LLEmojiHelper::handleKey(const LLUICtrl* ctrl_p, KEY key, MASK mask) +{ + if (mHelperHandle.isDead() || !isActive(ctrl_p)) + { + return false; + } + + return mHelperHandle.get()->handleKey(key, mask, true); +} + +void LLEmojiHelper::onCommitEmoji(llwchar emoji) +{ + if (!mHostHandle.isDead() && mEmojiCommitCb) + { + mEmojiCommitCb(emoji); + } +} + +void LLEmojiHelper::setHostCtrl(LLUICtrl* hostctrl_p) +{ + const LLUICtrl* pCurHostCtrl = mHostHandle.get(); + if (pCurHostCtrl != hostctrl_p) + { + mHostCtrlFocusLostConn.disconnect(); + mHostHandle.markDead(); + mEmojiCommitCb = {}; + + if (!mHelperHandle.isDead()) + { + mHelperHandle.get()->closeFloater(); + } + + if (hostctrl_p) + { + mHostHandle = hostctrl_p->getHandle(); + mHostCtrlFocusLostConn = hostctrl_p->setFocusLostCallback(std::bind([&]() { hideHelper(getHostCtrl()); })); + } + } +} diff --git a/indra/llui/llemojihelper.h b/indra/llui/llemojihelper.h new file mode 100644 index 0000000000..e826ff93e6 --- /dev/null +++ b/indra/llui/llemojihelper.h @@ -0,0 +1,66 @@ +/** +* @file llemojihelper.h +* @brief Header file for LLEmojiHelper +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#pragma once + +#include "llhandle.h" +#include "llsingleton.h" + +#include <boost/signals2.hpp> + +class LLFloater; +class LLUICtrl; + +class LLEmojiHelper : public LLSingleton<LLEmojiHelper> +{ + LLSINGLETON(LLEmojiHelper) {} + ~LLEmojiHelper() override {} + +public: + // General + std::string getToolTip(llwchar ch) const; + bool isActive(const LLUICtrl* ctrl_p) const; + static bool isCursorInEmojiCode(const LLWString& wtext, S32 cursor_pos, S32* short_code_pos_p = nullptr); + void showHelper(LLUICtrl* hostctrl_p, S32 local_x, S32 local_y, const std::string& short_code, std::function<void(llwchar)> commit_cb); + void hideHelper(const LLUICtrl* ctrl_p = nullptr, bool strict = false); + void setIsHideDisabled(bool disabled) { mIsHideDisabled = disabled; }; + + // Eventing + bool handleKey(const LLUICtrl* ctrl_p, KEY key, MASK mask); + void onCommitEmoji(llwchar emoji); + +protected: + LLUICtrl* getHostCtrl() const { return mHostHandle.get(); } + void setHostCtrl(LLUICtrl* hostctrl_p); + +private: + LLHandle<LLUICtrl> mHostHandle; + LLHandle<LLFloater> mHelperHandle; + boost::signals2::connection mHostCtrlFocusLostConn; + boost::signals2::connection mHelperCommitConn; + std::function<void(llwchar)> mEmojiCommitCb; + bool mIsHideDisabled; +}; diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index 2303cd24b7..de3de53569 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -182,6 +182,7 @@ LLFloater::Params::Params() save_visibility("save_visibility", false), can_dock("can_dock", false), show_title("show_title", true), + auto_close("auto_close", false), positioning("positioning", LLFloaterEnums::POSITIONING_RELATIVE), header_height("header_height", 0), legacy_header_height("legacy_header_height", 0), @@ -254,6 +255,7 @@ LLFloater::LLFloater(const LLSD& key, const LLFloater::Params& p) mCanClose(p.can_close), mDragOnLeft(p.can_drag_on_left), mResizable(p.can_resize), + mAutoClose(p.auto_close), mPositioning(p.positioning), mMinWidth(p.min_width), mMinHeight(p.min_height), @@ -504,6 +506,7 @@ void LLFloater::enableResizeCtrls(bool enable, bool width, bool height) void LLFloater::destroy() { + gFloaterView->onDestroyFloater(this); // LLFloaterReg should be synchronized with "dead" floater to avoid returning dead instance before // it was deleted via LLMortician::updateClass(). See EXT-8458. LLFloaterReg::removeInstance(mInstanceName, mKey); @@ -681,7 +684,7 @@ void LLFloater::openFloater(const LLSD& key) if (getHost() != NULL) { getHost()->setMinimized(FALSE); - getHost()->setVisibleAndFrontmost(mAutoFocus); + getHost()->setVisibleAndFrontmost(mAutoFocus && !getIsChrome()); getHost()->showFloater(this); } else @@ -693,7 +696,7 @@ void LLFloater::openFloater(const LLSD& key) } applyControlsAndPosition(floater_to_stack); setMinimized(FALSE); - setVisibleAndFrontmost(mAutoFocus); + setVisibleAndFrontmost(mAutoFocus && !getIsChrome()); } mOpenSignal(this, key); @@ -829,6 +832,24 @@ void LLFloater::reshape(S32 width, S32 height, BOOL called_from_parent) LLPanel::reshape(width, height, called_from_parent); } +// virtual +void LLFloater::translate(S32 x, S32 y) +{ + LLView::translate(x, y); + + if (!mTranslateWithDependents || mDependents.empty()) + return; + + for (const LLHandle<LLFloater>& handle : mDependents) + { + LLFloater* floater = handle.get(); + if (floater && floater->getSnapTarget() == getHandle()) + { + floater->LLView::translate(x, y); + } + } +} + void LLFloater::releaseFocus() { LLUI::getInstance()->removePopup(this); @@ -1117,9 +1138,9 @@ BOOL LLFloater::canSnapTo(const LLView* other_view) if (other_view != getParent()) { - const LLFloater* other_floaterp = dynamic_cast<const LLFloater*>(other_view); - if (other_floaterp - && other_floaterp->getSnapTarget() == getHandle() + const LLFloater* other_floaterp = dynamic_cast<const LLFloater*>(other_view); + if (other_floaterp + && other_floaterp->getSnapTarget() == getHandle() && mDependents.find(other_floaterp->getHandle()) != mDependents.end()) { // this is a dependent that is already snapped to us, so don't snap back to it @@ -1509,30 +1530,40 @@ BOOL LLFloater::isFrontmost() && floater_view->getFrontmost() == this); } -void LLFloater::addDependentFloater(LLFloater* floaterp, BOOL reposition) +void LLFloater::addDependentFloater(LLFloater* floaterp, BOOL reposition, BOOL resize) { mDependents.insert(floaterp->getHandle()); floaterp->mDependeeHandle = getHandle(); if (reposition) { - floaterp->setRect(gFloaterView->findNeighboringPosition(this, floaterp)); + LLRect rect = gFloaterView->findNeighboringPosition(this, floaterp); + if (resize) + { + const LLRect& base = getRect(); + if (rect.mTop == base.mTop) + rect.mBottom = base.mBottom; + else if (rect.mLeft == base.mLeft) + rect.mRight = base.mRight; + floaterp->reshape(rect.getWidth(), rect.getHeight(), FALSE); + } + floaterp->setRect(rect); floaterp->setSnapTarget(getHandle()); } gFloaterView->adjustToFitScreen(floaterp, FALSE, TRUE); if (floaterp->isFrontmost()) { // make sure to bring self and sibling floaters to front - gFloaterView->bringToFront(floaterp); + gFloaterView->bringToFront(floaterp, floaterp->getAutoFocus() && !getIsChrome()); } } -void LLFloater::addDependentFloater(LLHandle<LLFloater> dependent, BOOL reposition) +void LLFloater::addDependentFloater(LLHandle<LLFloater> dependent, BOOL reposition, BOOL resize) { LLFloater* dependent_floaterp = dependent.get(); if(dependent_floaterp) { - addDependentFloater(dependent_floaterp, reposition); + addDependentFloater(dependent_floaterp, reposition, resize); } } @@ -1542,6 +1573,44 @@ void LLFloater::removeDependentFloater(LLFloater* floaterp) floaterp->mDependeeHandle = LLHandle<LLFloater>(); } +void LLFloater::fitWithDependentsOnScreen(const LLRect& left, const LLRect& bottom, const LLRect& right, const LLRect& constraint, S32 min_overlap_pixels) +{ + LLRect total_rect = getRect(); + + for (const LLHandle<LLFloater>& handle : mDependents) + { + LLFloater* floater = handle.get(); + if (floater && floater->getSnapTarget() == getHandle()) + { + total_rect.unionWith(floater->getRect()); + } + } + + S32 delta_left = left.notEmpty() ? left.mRight - total_rect.mRight : 0; + S32 delta_bottom = bottom.notEmpty() ? bottom.mTop - total_rect.mTop : 0; + S32 delta_right = right.notEmpty() ? right.mLeft - total_rect.mLeft : 0; + + // move floater with dependings fully onscreen + mTranslateWithDependents = true; + if (translateRectIntoRect(total_rect, constraint, min_overlap_pixels)) + { + clearSnapTarget(); + } + else if (delta_left > 0 && total_rect.mTop < left.mTop && total_rect.mBottom > left.mBottom) + { + translate(delta_left, 0); + } + else if (delta_bottom > 0 && total_rect.mLeft > bottom.mLeft && total_rect.mRight < bottom.mRight) + { + translate(0, delta_bottom); + } + else if (delta_right < 0 && total_rect.mTop < right.mTop && total_rect.mBottom > right.mBottom) + { + translate(delta_right, 0); + } + mTranslateWithDependents = false; +} + BOOL LLFloater::offerClickToButton(S32 x, S32 y, MASK mask, EFloaterButton index) { if( mButtonsEnabled[index] ) @@ -1630,6 +1699,7 @@ BOOL LLFloater::handleDoubleClick(S32 x, S32 y, MASK mask) return was_minimized || LLPanel::handleDoubleClick(x, y, mask); } +// virtual void LLFloater::bringToFront( S32 x, S32 y ) { if (getVisible() && pointInView(x, y)) @@ -1644,12 +1714,20 @@ void LLFloater::bringToFront( S32 x, S32 y ) LLFloaterView* parent = dynamic_cast<LLFloaterView*>( getParent() ); if (parent) { - parent->bringToFront( this ); + parent->bringToFront(this, !getIsChrome()); } } } } +// virtual +void LLFloater::goneFromFront() +{ + if (mAutoClose) + { + closeFloater(); + } +} // virtual void LLFloater::setVisibleAndFrontmost(BOOL take_focus,const LLSD& key) @@ -2488,13 +2566,18 @@ void LLFloaterView::bringToFront(LLFloater* child, BOOL give_focus, BOOL restore if (mFrontChild == child) { - if (give_focus && !gFocusMgr.childHasKeyboardFocus(child)) + if (give_focus && child->canFocusStealFrontmost() && !gFocusMgr.childHasKeyboardFocus(child)) { child->setFocus(TRUE); } return; } + if (mFrontChild) + { + mFrontChild->goneFromFront(); + } + mFrontChild = child; // *TODO: make this respect floater's mAutoFocus value, instead of @@ -2852,10 +2935,17 @@ void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_out // floater is hosted elsewhere, so ignore return; } + + if (floater->getDependee() && + floater->getDependee() == floater->getSnapTarget().get()) + { + // floater depends on other and snaps to it, so ignore + return; + } + LLRect::tCoordType screen_width = getSnapRect().getWidth(); LLRect::tCoordType screen_height = getSnapRect().getHeight(); - // only automatically resize non-minimized, resizable floaters if( floater->isResizable() && !floater->isMinimized() ) { @@ -2897,29 +2987,10 @@ void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_out } } - const LLRect& floater_rect = floater->getRect(); - - S32 delta_left = mToolbarLeftRect.notEmpty() ? mToolbarLeftRect.mRight - floater_rect.mRight : 0; - S32 delta_bottom = mToolbarBottomRect.notEmpty() ? mToolbarBottomRect.mTop - floater_rect.mTop : 0; - S32 delta_right = mToolbarRightRect.notEmpty() ? mToolbarRightRect.mLeft - floater_rect.mLeft : 0; + const LLRect& constraint = snap_in_toolbars ? getSnapRect() : gFloaterView->getRect(); + S32 min_overlap_pixels = allow_partial_outside ? FLOATER_MIN_VISIBLE_PIXELS : S32_MAX; - // move window fully onscreen - if (floater->translateIntoRect( snap_in_toolbars ? getSnapRect() : gFloaterView->getRect(), allow_partial_outside ? FLOATER_MIN_VISIBLE_PIXELS : S32_MAX )) - { - floater->clearSnapTarget(); - } - else if (delta_left > 0 && floater_rect.mTop < mToolbarLeftRect.mTop && floater_rect.mBottom > mToolbarLeftRect.mBottom) - { - floater->translate(delta_left, 0); - } - else if (delta_bottom > 0 && floater_rect.mLeft > mToolbarBottomRect.mLeft && floater_rect.mRight < mToolbarBottomRect.mRight) - { - floater->translate(0, delta_bottom); - } - else if (delta_right < 0 && floater_rect.mTop < mToolbarRightRect.mTop && floater_rect.mBottom > mToolbarRightRect.mBottom) - { - floater->translate(delta_right, 0); - } + floater->fitWithDependentsOnScreen(mToolbarLeftRect, mToolbarBottomRect, mToolbarRightRect, constraint, min_overlap_pixels); } void LLFloaterView::draw() @@ -3006,6 +3077,9 @@ LLFloater *LLFloaterView::getBackmost() const void LLFloaterView::syncFloaterTabOrder() { + if (mFrontChild && !mFrontChild->isDead() && mFrontChild->getIsChrome()) + return; + // look for a visible modal dialog, starting from first LLModalDialog* modal_dialog = NULL; for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) @@ -3041,7 +3115,34 @@ void LLFloaterView::syncFloaterTabOrder() LLFloater* floaterp = dynamic_cast<LLFloater*>(*child_it); if (gFocusMgr.childHasKeyboardFocus(floaterp)) { - bringToFront(floaterp, FALSE); + if (mFrontChild != floaterp) + { + // Grab a list of the top floaters that want to stay on top of the focused floater + std::list<LLFloater*> listTop; + if (mFrontChild && !mFrontChild->canFocusStealFrontmost()) + { + for (LLView* childp : *getChildList()) + { + LLFloater* child_floaterp = static_cast<LLFloater*>(childp); + if (child_floaterp->canFocusStealFrontmost()) + break; + listTop.push_back(child_floaterp); + } + } + + bringToFront(floaterp, FALSE); + + // Restore top floaters + if (!listTop.empty()) + { + for (LLView* childp : listTop) + { + sendChildToFront(childp); + } + mFrontChild = listTop.back(); + } + } + break; } } @@ -3134,6 +3235,14 @@ void LLFloaterView::setToolbarRect(LLToolBarEnums::EToolBarLocation tb, const LL } } +void LLFloaterView::onDestroyFloater(LLFloater* floater) +{ + if (mFrontChild == floater) + { + mFrontChild = nullptr; + } +} + void LLFloater::setInstanceName(const std::string& name) { if (name != mInstanceName) @@ -3226,6 +3335,7 @@ void LLFloater::initFromParams(const LLFloater::Params& p) mDefaultRelativeY = p.rel_y; mPositioning = p.positioning; + mAutoClose = p.auto_close; mSaveRect = p.save_rect; if (p.save_visibility) diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h index 3d15708295..88f9e77777 100644 --- a/indra/llui/llfloater.h +++ b/indra/llui/llfloater.h @@ -113,8 +113,6 @@ struct LLCoordFloater : LLCoord<LL_COORD_FLOATER> bool operator!=(const LLCoordFloater& other) const { return !(*this == other); } void setFloater(LLFloater& floater); - - }; class LLFloater : public LLPanel, public LLInstanceTracker<LLFloater> @@ -165,7 +163,8 @@ public: save_visibility, save_dock_state, can_dock, - show_title; + show_title, + auto_close; Optional<LLFloaterEnums::EOpenPositioning> positioning; @@ -238,6 +237,7 @@ public: virtual void closeHostedFloater(); /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + /*virtual*/ void translate(S32 x, S32 y); // Release keyboard and mouse focus void releaseFocus(); @@ -256,10 +256,11 @@ public: std::string getShortTitle() const; virtual void setMinimized(BOOL b); void moveResizeHandlesToFront(); - void addDependentFloater(LLFloater* dependent, BOOL reposition = TRUE); - void addDependentFloater(LLHandle<LLFloater> dependent_handle, BOOL reposition = TRUE); + void addDependentFloater(LLFloater* dependent, BOOL reposition = TRUE, BOOL resize = FALSE); + void addDependentFloater(LLHandle<LLFloater> dependent_handle, BOOL reposition = TRUE, BOOL resize = FALSE); LLFloater* getDependee() { return (LLFloater*)mDependeeHandle.get(); } - void removeDependentFloater(LLFloater* dependent); + void removeDependentFloater(LLFloater* dependent); + void fitWithDependentsOnScreen(const LLRect& left, const LLRect& bottom, const LLRect& right, const LLRect& constraint, S32 min_overlap_pixels); BOOL isMinimized() const { return mMinimized; } /// isShown() differs from getVisible() in that isShown() also considers /// isMinimized(). isShown() is true only if visible and not minimized. @@ -314,6 +315,9 @@ public: /*virtual*/ void setVisible(BOOL visible); // do not override /*virtual*/ void onVisibilityChange ( BOOL new_visibility ); // do not override + bool canFocusStealFrontmost() const { return mFocusStealsFrontmost; } + void setFocusStealsFrontmost(bool wants_frontmost) { mFocusStealsFrontmost = wants_frontmost; } + void setFrontmost(BOOL take_focus = TRUE, BOOL restore = TRUE); virtual void setVisibleAndFrontmost(BOOL take_focus=TRUE, const LLSD& key = LLSD()); @@ -387,6 +391,7 @@ protected: void setInstanceName(const std::string& name); virtual void bringToFront(S32 x, S32 y); + virtual void goneFromFront(); void setExpandedRect(const LLRect& rect) { mExpandedRect = rect; } // size when not minimized const LLRect& getExpandedRect() const { return mExpandedRect; } @@ -482,8 +487,10 @@ private: BOOL mCanTearOff; BOOL mCanMinimize; BOOL mCanClose; + bool mFocusStealsFrontmost = true; // FALSE if we don't want the currently focused floater to cover this floater without user interaction BOOL mDragOnLeft; BOOL mResizable; + BOOL mAutoClose; LLFloaterEnums::EOpenPositioning mPositioning; LLCoordFloater mPosition; @@ -503,6 +510,7 @@ private: typedef std::set<LLHandle<LLFloater> > handle_set_t; typedef std::set<LLHandle<LLFloater> >::iterator handle_set_iter_t; handle_set_t mDependents; + bool mTranslateWithDependents { false }; bool mButtonsEnabled[BUTTON_COUNT]; F32 mButtonScale; @@ -599,6 +607,7 @@ public: LLFloater* getFrontmostClosableFloater(); void setToolbarRect(LLToolBarEnums::EToolBarLocation tb, const LLRect& toolbar_rect); + void onDestroyFloater(LLFloater* floater); private: void hiddenFloaterClosed(LLFloater* floater); diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index 0dc66bf37a..bc9469cfad 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -890,7 +890,7 @@ void LLFolderViewItem::drawLabel(const LLFontGL * font, const F32 x, const F32 y // font->renderUTF8(mLabel, 0, x, y, color, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, - S32_MAX, getRect().getWidth() - (S32) x - mLabelPaddingRight, &right_x, TRUE); + S32_MAX, getRect().getWidth() - (S32) x - mLabelPaddingRight, &right_x, /*use_ellipses*/TRUE); } void LLFolderViewItem::draw() @@ -999,7 +999,7 @@ void LLFolderViewItem::draw() { suffix_font->renderUTF8( mLabelSuffix, 0, right_x, y, isFadeItem() ? color : (LLColor4)sSuffixColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, - S32_MAX, S32_MAX, &right_x, FALSE ); + S32_MAX, S32_MAX, &right_x); } //--------------------------------------------------------------------------------// @@ -1011,9 +1011,9 @@ void LLFolderViewItem::draw() { F32 match_string_left = text_left + font->getWidthF32(combined_string, 0, filter_offset + filter_string_length) - font->getWidthF32(combined_string, filter_offset, filter_string_length); F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; - font->renderUTF8( combined_string, filter_offset, match_string_left, yy, - sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, - filter_string_length, S32_MAX, &right_x, FALSE ); + font->renderUTF8(combined_string, filter_offset, match_string_left, yy, + sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, + filter_string_length, S32_MAX, &right_x); } else { @@ -1022,8 +1022,9 @@ void LLFolderViewItem::draw() { F32 match_string_left = text_left + font->getWidthF32(mLabel, 0, filter_offset + label_filter_length) - font->getWidthF32(mLabel, filter_offset, label_filter_length); F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; - font->renderUTF8( mLabel, filter_offset, match_string_left, yy, - sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, label_filter_length, S32_MAX, &right_x, FALSE ); + font->renderUTF8(mLabel, filter_offset, match_string_left, yy, + sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, + label_filter_length, S32_MAX, &right_x); } S32 suffix_filter_length = label_filter_length > 0 ? filter_string_length - label_filter_length : filter_string_length; @@ -1032,7 +1033,9 @@ void LLFolderViewItem::draw() S32 suffix_offset = llmax(0, filter_offset - (S32)mLabel.size()); F32 match_string_left = text_left + font->getWidthF32(mLabel, 0, mLabel.size()) + suffix_font->getWidthF32(mLabelSuffix, 0, suffix_offset + suffix_filter_length) - suffix_font->getWidthF32(mLabelSuffix, suffix_offset, suffix_filter_length); F32 yy = (F32)getRect().getHeight() - suffix_font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; - suffix_font->renderUTF8( mLabelSuffix, suffix_offset, match_string_left, yy, sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, suffix_filter_length, S32_MAX, &right_x, FALSE ); + suffix_font->renderUTF8(mLabelSuffix, suffix_offset, match_string_left, yy, sFilterTextColor, + LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, + suffix_filter_length, S32_MAX, &right_x); } } diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 60dbfd68c6..453fa29e7c 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -89,6 +89,7 @@ LLLineEditor::Params::Params() background_image_disabled("background_image_disabled"), background_image_focused("background_image_focused"), bg_image_always_focused("bg_image_always_focused", false), + show_label_focused("show_label_focused", false), select_on_focus("select_on_focus", false), revert_on_esc("revert_on_esc", true), spellcheck("spellcheck", false), @@ -152,6 +153,7 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) mBgImageDisabled( p.background_image_disabled ), mBgImageFocused( p.background_image_focused ), mShowImageFocused( p.bg_image_always_focused ), + mShowLabelFocused( p.show_label_focused ), mUseBgColor(p.use_bg_color), mHaveHistory(FALSE), mReplaceNewlinesWithSpaces( TRUE ), @@ -1737,6 +1739,20 @@ void LLLineEditor::drawBackground() } } +//virtual +const std::string LLLineEditor::getToolTip() const +{ + if (sDebugUnicode) + { + std::string text = getText(); + std::string tooltip = utf8str_showBytesUTF8(text); + return tooltip; + } + + return LLUICtrl::getToolTip(); +} + +//virtual void LLLineEditor::draw() { F32 alpha = getDrawContext().mAlpha; @@ -2069,7 +2085,7 @@ void LLLineEditor::draw() //draw label if no text is provided //but we should draw it in a different color //to give indication that it is not text you typed in - if (0 == mText.length() && mReadOnly) + if (0 == mText.length() && (mReadOnly || mShowLabelFocused)) { mGLFont->render(mLabel.getWString(), 0, mTextLeftEdge, (F32)text_bottom, @@ -2105,7 +2121,7 @@ void LLLineEditor::draw() LLFontGL::NO_SHADOW, S32_MAX, mTextRightEdge - ll_round(rendered_pixels_right), - &rendered_pixels_right, FALSE); + &rendered_pixels_right); } // Draw children (border) LLView::draw(); diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h index f983828d2b..5794b3c35a 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -91,6 +91,7 @@ public: commit_on_focus_lost, ignore_tab, bg_image_always_focused, + show_label_focused, is_password, use_bg_color; @@ -118,54 +119,55 @@ protected: friend class LLUICtrlFactory; friend class LLFloaterEditUI; void showContextMenu(S32 x, S32 y); + public: virtual ~LLLineEditor(); // mousehandler overrides - /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask); - /*virtual*/ BOOL handleDoubleClick(S32 x,S32 y,MASK mask); - /*virtual*/ BOOL handleMiddleMouseDown(S32 x,S32 y,MASK mask); - /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask ); - /*virtual*/ BOOL handleUnicodeCharHere(llwchar uni_char); - /*virtual*/ void onMouseCaptureLost(); + /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask) override; + /*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask) override; + /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask) override; + /*virtual*/ BOOL handleDoubleClick(S32 x,S32 y,MASK mask) override; + /*virtual*/ BOOL handleMiddleMouseDown(S32 x,S32 y,MASK mask) override; + /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask) override; + /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask) override; + /*virtual*/ BOOL handleUnicodeCharHere(llwchar uni_char) override; + /*virtual*/ void onMouseCaptureLost() override; // LLEditMenuHandler overrides - virtual void cut(); - virtual BOOL canCut() const; - virtual void copy(); - virtual BOOL canCopy() const; - virtual void paste(); - virtual BOOL canPaste() const; + /*virtual*/ void cut() override; + /*virtual*/ BOOL canCut() const override; + /*virtual*/ void copy() override; + /*virtual*/ BOOL canCopy() const override; + /*virtual*/ void paste() override; + /*virtual*/ BOOL canPaste() const override; virtual void updatePrimary(); virtual void copyPrimary(); virtual void pastePrimary(); virtual BOOL canPastePrimary() const; - virtual void doDelete(); - virtual BOOL canDoDelete() const; + /*virtual*/ void doDelete() override; + /*virtual*/ BOOL canDoDelete() const override; - virtual void selectAll(); - virtual BOOL canSelectAll() const; + /*virtual*/ void selectAll() override; + /*virtual*/ BOOL canSelectAll() const override; - virtual void deselect(); - virtual BOOL canDeselect() const; + /*virtual*/ void deselect() override; + /*virtual*/ BOOL canDeselect() const override; // LLSpellCheckMenuHandler overrides - /*virtual*/ bool getSpellCheck() const; + /*virtual*/ bool getSpellCheck() const override; - /*virtual*/ const std::string& getSuggestion(U32 index) const; - /*virtual*/ U32 getSuggestionCount() const; - /*virtual*/ void replaceWithSuggestion(U32 index); + /*virtual*/ const std::string& getSuggestion(U32 index) const override; + /*virtual*/ U32 getSuggestionCount() const override; + /*virtual*/ void replaceWithSuggestion(U32 index) override; - /*virtual*/ void addToDictionary(); - /*virtual*/ bool canAddToDictionary() const; + /*virtual*/ void addToDictionary() override; + /*virtual*/ bool canAddToDictionary() const override; - /*virtual*/ void addToIgnore(); - /*virtual*/ bool canAddToIgnore() const; + /*virtual*/ void addToIgnore() override; + /*virtual*/ bool canAddToIgnore() const override; // Spell checking helper functions std::string getMisspelledWord(U32 pos) const; @@ -173,27 +175,28 @@ public: void onSpellCheckSettingsChange(); // view overrides - virtual void draw(); - virtual void reshape(S32 width,S32 height,BOOL called_from_parent=TRUE); - virtual void onFocusReceived(); - virtual void onFocusLost(); - virtual void setEnabled(BOOL enabled); + /*virtual*/ const std::string getToolTip() const override; + /*virtual*/ void draw() override; + /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE) override; + /*virtual*/ void onFocusReceived() override; + /*virtual*/ void onFocusLost() override; + /*virtual*/ void setEnabled(BOOL enabled) override; // UI control overrides - virtual void clear(); - virtual void onTabInto(); - virtual void setFocus( BOOL b ); - virtual void setRect(const LLRect& rect); - virtual BOOL acceptsTextInput() const; - virtual void onCommit(); - virtual BOOL isDirty() const; // Returns TRUE if user changed value at all - virtual void resetDirty(); // Clear dirty state + /*virtual*/ void clear() override; + /*virtual*/ void onTabInto() override; + /*virtual*/ void setFocus(BOOL b) override; + /*virtual*/ void setRect(const LLRect& rect) override; + /*virtual*/ BOOL acceptsTextInput() const override; + /*virtual*/ void onCommit() override; + /*virtual*/ BOOL isDirty() const override; // Returns TRUE if user changed value at all + /*virtual*/ void resetDirty() override; // Clear dirty state // assumes UTF8 text - virtual void setValue(const LLSD& value ); - virtual LLSD getValue() const; - virtual BOOL setTextArg( const std::string& key, const LLStringExplicit& text ); - virtual BOOL setLabelArg( const std::string& key, const LLStringExplicit& text ); + /*virtual*/ void setValue(const LLSD& value) override; + /*virtual*/ LLSD getValue() const override; + /*virtual*/ BOOL setTextArg(const std::string& key, const LLStringExplicit& text) override; + /*virtual*/ BOOL setLabelArg(const std::string& key, const LLStringExplicit& text) override; void setLabel(const LLStringExplicit &new_label) { mLabel = new_label; } const std::string& getLabel() { return mLabel.getString(); } @@ -215,7 +218,7 @@ public: // Selects characters 'start' to 'end'. void setSelection(S32 start, S32 end); - virtual void getSelectionRange(S32 *position, S32 *length) const; + /*virtual*/ void getSelectionRange(S32 *position, S32 *length) const override; void setCommitOnFocusLost( BOOL b ) { mCommitOnFocusLost = b; } void setRevertOnEsc( BOOL b ) { mRevertOnEsc = b; } @@ -314,14 +317,14 @@ public: void updateAllowingLanguageInput(); BOOL hasPreeditString() const; // Implementation (overrides) of LLPreeditor - virtual void resetPreedit(); - virtual void updatePreedit(const LLWString &preedit_string, - const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position); - virtual void markAsPreedit(S32 position, S32 length); - virtual void getPreeditRange(S32 *position, S32 *length) const; - virtual BOOL getPreeditLocation(S32 query_position, LLCoordGL *coord, LLRect *bounds, LLRect *control) const; - virtual S32 getPreeditFontSize() const; - virtual LLWString getPreeditString() const { return getWText(); } + /*virtual*/ void resetPreedit() override; + /*virtual*/ void updatePreedit(const LLWString &preedit_string, + const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position) override; + /*virtual*/ void markAsPreedit(S32 position, S32 length) override; + /*virtual*/ void getPreeditRange(S32 *position, S32 *length) const override; + /*virtual*/ BOOL getPreeditLocation(S32 query_position, LLCoordGL *coord, LLRect *bounds, LLRect *control) const override; + /*virtual*/ S32 getPreeditFontSize() const override; + /*virtual*/ LLWString getPreeditString() const override { return getWText(); } void setText(const LLStringExplicit &new_text, bool use_size_limit); @@ -398,6 +401,7 @@ protected: BOOL mReadOnly; BOOL mShowImageFocused; + BOOL mShowLabelFocused; bool mUseBgColor; diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index cebca70b59..fececa15cf 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -573,6 +573,11 @@ void LLMenuItemGL::onVisibilityChange(BOOL new_visibility) // // This class represents a separator. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +LLMenuItemSeparatorGL::Params::Params() + : on_visible("on_visible") +{ +} + LLMenuItemSeparatorGL::LLMenuItemSeparatorGL(const LLMenuItemSeparatorGL::Params& p) : LLMenuItemGL( p ) { diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h index 87e3f18ebc..5c51cf8465 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -235,10 +235,10 @@ public: struct Params : public LLInitParam::Block<Params, LLMenuItemGL::Params> { Optional<EnableCallbackParam > on_visible; - Params() : on_visible("on_visible") - {} + Params(); }; - LLMenuItemSeparatorGL(const LLMenuItemSeparatorGL::Params& p = LLMenuItemSeparatorGL::Params()); + + LLMenuItemSeparatorGL(const LLMenuItemSeparatorGL::Params& p = LLMenuItemSeparatorGL::Params()); /*virtual*/ void draw( void ); /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index 921398a693..4d9a33f1d7 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -913,7 +913,7 @@ public: /* virtual */ LLNotificationPtr add(const std::string& name, const LLSD& substitutions, const LLSD& payload, - LLNotificationFunctorRegistry::ResponseFunctor functor); + LLNotificationFunctorRegistry::ResponseFunctor functor) override; LLNotificationPtr add(const LLNotification::Params& p); void add(const LLNotificationPtr pNotif); @@ -964,8 +964,8 @@ public: bool isVisibleByRules(LLNotificationPtr pNotification); private: - /*virtual*/ void initSingleton(); - /*virtual*/ void cleanupSingleton(); + /*virtual*/ void initSingleton() override; + /*virtual*/ void cleanupSingleton() override; void loadPersistentNotifications(); diff --git a/indra/llui/llscrollbar.cpp b/indra/llui/llscrollbar.cpp index 735e2d529e..b2be9fb1e1 100644 --- a/indra/llui/llscrollbar.cpp +++ b/indra/llui/llscrollbar.cpp @@ -475,13 +475,15 @@ void LLScrollbar::reshape(S32 width, S32 height, BOOL called_from_parent) { up_button->reshape(up_button->getRect().getWidth(), llmin(getRect().getHeight() / 2, mThickness)); down_button->reshape(down_button->getRect().getWidth(), llmin(getRect().getHeight() / 2, mThickness)); - up_button->setOrigin(up_button->getRect().mLeft, getRect().getHeight() - up_button->getRect().getHeight()); + up_button->setOrigin(0, getRect().getHeight() - up_button->getRect().getHeight()); + down_button->setOrigin(0, 0); } else { up_button->reshape(llmin(getRect().getWidth() / 2, mThickness), up_button->getRect().getHeight()); down_button->reshape(llmin(getRect().getWidth() / 2, mThickness), down_button->getRect().getHeight()); - down_button->setOrigin(getRect().getWidth() - down_button->getRect().getWidth(), down_button->getRect().mBottom); + up_button->setOrigin(0, 0); + down_button->setOrigin(getRect().getWidth() - down_button->getRect().getWidth(), 0); } updateThumbRect(); } diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp index ad32f7186c..22d27b1f2a 100644 --- a/indra/llui/llscrollcontainer.cpp +++ b/indra/llui/llscrollcontainer.cpp @@ -70,6 +70,7 @@ LLScrollContainer::Params::Params() bg_color("color"), border_visible("border_visible"), hide_scrollbar("hide_scrollbar"), + ignore_arrow_keys("ignore_arrow_keys"), min_auto_scroll_rate("min_auto_scroll_rate", 100), max_auto_scroll_rate("max_auto_scroll_rate", 1000), max_auto_scroll_zone("max_auto_scroll_zone", 16), @@ -86,6 +87,7 @@ LLScrollContainer::LLScrollContainer(const LLScrollContainer::Params& p) mBackgroundColor(p.bg_color()), mIsOpaque(p.is_opaque), mHideScrollbar(p.hide_scrollbar), + mIgnoreArrowKeys(p.ignore_arrow_keys), mReserveScrollCorner(p.reserve_scroll_corner), mMinAutoScrollRate(p.min_auto_scroll_rate), mMaxAutoScrollRate(p.max_auto_scroll_rate), @@ -204,10 +206,29 @@ void LLScrollContainer::reshape(S32 width, S32 height, } } +// virtual BOOL LLScrollContainer::handleKeyHere(KEY key, MASK mask) { + if (mIgnoreArrowKeys) + { + switch(key) + { + case KEY_LEFT: + case KEY_RIGHT: + case KEY_UP: + case KEY_DOWN: + case KEY_PAGE_UP: + case KEY_PAGE_DOWN: + case KEY_HOME: + case KEY_END: + return FALSE; + default: + break; + } + } + // allow scrolled view to handle keystrokes in case it delegated keyboard focus - // to the scroll container. + // to the scroll container. // NOTE: this should not recurse indefinitely as handleKeyHere // should not propagate to parent controls, so mScrolledView should *not* // call LLScrollContainer::handleKeyHere in turn diff --git a/indra/llui/llscrollcontainer.h b/indra/llui/llscrollcontainer.h index dacea2a987..79dc70cac9 100644 --- a/indra/llui/llscrollcontainer.h +++ b/indra/llui/llscrollcontainer.h @@ -63,7 +63,8 @@ public: Optional<bool> is_opaque, reserve_scroll_corner, border_visible, - hide_scrollbar; + hide_scrollbar, + ignore_arrow_keys; Optional<F32> min_auto_scroll_rate, max_auto_scroll_rate; Optional<U32> max_auto_scroll_zone; @@ -149,6 +150,7 @@ private: F32 mMaxAutoScrollRate; U32 mMaxAutoScrollZone; bool mHideScrollbar; + bool mIgnoreArrowKeys; }; diff --git a/indra/llui/llscrollingpanellist.cpp b/indra/llui/llscrollingpanellist.cpp index b6f2eb8ba2..3a819e7d06 100644 --- a/indra/llui/llscrollingpanellist.cpp +++ b/indra/llui/llscrollingpanellist.cpp @@ -37,53 +37,44 @@ static LLDefaultChildRegistry::Register<LLScrollingPanelList> r("scrolling_panel // This could probably be integrated with LLScrollContainer -SJB +LLScrollingPanelList::Params::Params() + : is_horizontal("is_horizontal") + , padding("padding") + , spacing("spacing") +{ +} + +LLScrollingPanelList::LLScrollingPanelList(const Params& p) + : LLUICtrl(p) + , mIsHorizontal(p.is_horizontal) + , mPadding(p.padding.isProvided() ? p.padding : DEFAULT_PADDING) + , mSpacing(p.spacing.isProvided() ? p.spacing : DEFAULT_SPACING) +{ +} + void LLScrollingPanelList::clearPanels() { deleteAllChildren(); mPanelList.clear(); - - LLRect rc = getRect(); - rc.setLeftTopAndSize(rc.mLeft, rc.mTop, 1, 1); - setRect(rc); - - notifySizeChanged(rc.getHeight()); + rearrange(); } -S32 LLScrollingPanelList::addPanel( LLScrollingPanel* panel ) +S32 LLScrollingPanelList::addPanel(LLScrollingPanel* panel, bool back) { - addChildInBack( panel ); - mPanelList.push_front( panel ); - - // Resize this view - S32 total_height = 0; - S32 max_width = 0; - S32 cur_gap = 0; - for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin(); - iter != mPanelList.end(); ++iter) + if (back) { - LLScrollingPanel *childp = *iter; - total_height += childp->getRect().getHeight() + cur_gap; - max_width = llmax( max_width, childp->getRect().getWidth() ); - cur_gap = GAP_BETWEEN_PANELS; + addChild(panel); + mPanelList.push_back(panel); } - LLRect rc = getRect(); - rc.setLeftTopAndSize(rc.mLeft, rc.mTop, max_width, total_height); - setRect(rc); - - notifySizeChanged(rc.getHeight()); - - // Reposition each of the child views - S32 cur_y = total_height; - for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin(); - iter != mPanelList.end(); ++iter) + else { - LLScrollingPanel *childp = *iter; - cur_y -= childp->getRect().getHeight(); - childp->translate( -childp->getRect().mLeft, cur_y - childp->getRect().mBottom); - cur_y -= GAP_BETWEEN_PANELS; + addChildInBack(panel); + mPanelList.push_front(panel); } - return total_height; + rearrange(); + + return mIsHorizontal ? getRect().getWidth() : getRect().getHeight(); } void LLScrollingPanelList::removePanel(LLScrollingPanel* panel) @@ -100,7 +91,7 @@ void LLScrollingPanelList::removePanel(LLScrollingPanel* panel) break; } } - if(iter != mPanelList.end()) + if (iter != mPanelList.end()) { removePanel(index); } @@ -120,62 +111,104 @@ void LLScrollingPanelList::removePanel( U32 panel_index ) mPanelList.erase( mPanelList.begin() + panel_index ); } - const S32 GAP_BETWEEN_PANELS = 6; + rearrange(); +} - // Resize this view - S32 total_height = 0; - S32 max_width = 0; - S32 cur_gap = 0; - for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin(); +void LLScrollingPanelList::updatePanels(BOOL allow_modify) +{ + for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin(); iter != mPanelList.end(); ++iter) - { + { LLScrollingPanel *childp = *iter; - total_height += childp->getRect().getHeight() + cur_gap; - max_width = llmax( max_width, childp->getRect().getWidth() ); - cur_gap = GAP_BETWEEN_PANELS; + childp->updatePanel(allow_modify); + } +} + +void LLScrollingPanelList::rearrange() +{ + // Resize this view + S32 new_width, new_height; + if (!mPanelList.empty()) + { + new_width = new_height = mPadding * 2; + for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin(); + iter != mPanelList.end(); ++iter) + { + LLScrollingPanel* childp = *iter; + const LLRect& rect = childp->getRect(); + if (mIsHorizontal) + { + new_width += rect.getWidth() + mSpacing; + new_height = llmax(new_height, rect.getHeight()); + } + else + { + new_height += rect.getHeight() + mSpacing; + new_width = llmax(new_width, rect.getWidth()); + } + } + + if (mIsHorizontal) + { + new_width -= mSpacing; + } + else + { + new_height -= mSpacing; + } } + else + { + new_width = new_height = 1; + } + LLRect rc = getRect(); - rc.setLeftTopAndSize(rc.mLeft, rc.mTop, max_width, total_height); - setRect(rc); + if (mIsHorizontal || !followsRight()) + { + rc.mRight = rc.mLeft + new_width; + } + if (!mIsHorizontal || !followsBottom()) + { + rc.mBottom = rc.mTop - new_height; + } - notifySizeChanged(rc.getHeight()); + if (rc.mRight != getRect().mRight || rc.mBottom != getRect().mBottom) + { + setRect(rc); + notifySizeChanged(); + } // Reposition each of the child views - S32 cur_y = total_height; + S32 pos = mIsHorizontal ? mPadding : rc.getHeight() - mPadding; for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin(); - iter != mPanelList.end(); ++iter) + iter != mPanelList.end(); ++iter) { - LLScrollingPanel *childp = *iter; - cur_y -= childp->getRect().getHeight(); - childp->translate( -childp->getRect().mLeft, cur_y - childp->getRect().mBottom); - cur_y -= GAP_BETWEEN_PANELS; + LLScrollingPanel* childp = *iter; + const LLRect& rect = childp->getRect(); + if (mIsHorizontal) + { + childp->translate(pos - rect.mLeft, rc.getHeight() - mPadding - rect.mTop); + pos += rect.getWidth() + mSpacing; + } + else + { + childp->translate(mPadding - rect.mLeft, pos - rect.mTop); + pos -= rect.getHeight() + mSpacing; + } } } -void LLScrollingPanelList::updatePanels(BOOL allow_modify) -{ - for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin(); - iter != mPanelList.end(); ++iter) - { - LLScrollingPanel *childp = *iter; - childp->updatePanel(allow_modify); - } -} - void LLScrollingPanelList::updatePanelVisiblilty() { // Determine visibility of children. - S32 BORDER_WIDTH = 2; // HACK - LLRect parent_local_rect = getParent()->getRect(); - parent_local_rect.stretch( -BORDER_WIDTH ); - LLRect parent_screen_rect; - getParent()->localPointToScreen( - BORDER_WIDTH, 0, + getParent()->localPointToScreen( + mPadding, mPadding, &parent_screen_rect.mLeft, &parent_screen_rect.mBottom ); - getParent()->localPointToScreen( - parent_local_rect.getWidth() - BORDER_WIDTH, parent_local_rect.getHeight() - BORDER_WIDTH, + getParent()->localPointToScreen( + getParent()->getRect().getWidth() - mPadding, + getParent()->getRect().getHeight() - mPadding, &parent_screen_rect.mRight, &parent_screen_rect.mTop ); for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin(); @@ -207,11 +240,12 @@ void LLScrollingPanelList::draw() LLUICtrl::draw(); } -void LLScrollingPanelList::notifySizeChanged(S32 height) +void LLScrollingPanelList::notifySizeChanged() { LLSD info; info["action"] = "size_changes"; - info["height"] = height; + info["height"] = getRect().getHeight(); + info["width"] = getRect().getWidth(); notifyParent(info); } diff --git a/indra/llui/llscrollingpanellist.h b/indra/llui/llscrollingpanellist.h index e8df176ec3..d625039427 100644 --- a/indra/llui/llscrollingpanellist.h +++ b/indra/llui/llscrollingpanellist.h @@ -45,18 +45,24 @@ public: /* - * A set of panels that are displayed in a vertical sequence inside a scroll container. + * A set of panels that are displayed in a sequence inside a scroll container. */ class LLScrollingPanelList : public LLUICtrl { public: struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> - {}; - LLScrollingPanelList(const Params& p) - : LLUICtrl(p) - {} + { + Optional<bool> is_horizontal; + Optional<S32> padding; + Optional<S32> spacing; + + Params(); + }; + + LLScrollingPanelList(const Params& p); - static const S32 GAP_BETWEEN_PANELS = 6; + static const S32 DEFAULT_SPACING = 6; + static const S32 DEFAULT_PADDING = 2; typedef std::deque<LLScrollingPanel*> panel_list_t; @@ -65,11 +71,18 @@ public: virtual void draw(); void clearPanels(); - S32 addPanel( LLScrollingPanel* panel ); - void removePanel( LLScrollingPanel* panel ); - void removePanel( U32 panel_index ); + S32 addPanel(LLScrollingPanel* panel, bool back = false); + void removePanel(LLScrollingPanel* panel); + void removePanel(U32 panel_index); void updatePanels(BOOL allow_modify); - const panel_list_t& getPanelList() { return mPanelList; } + void rearrange(); + + const panel_list_t& getPanelList() const { return mPanelList; } + bool getIsHorizontal() const { return mIsHorizontal; } + void setPadding(S32 padding) { mPadding = padding; rearrange(); } + void setSpacing(S32 spacing) { mSpacing = spacing; rearrange(); } + S32 getPadding() const { return mPadding; } + S32 getSpacing() const { return mSpacing; } private: void updatePanelVisiblilty(); @@ -77,7 +90,11 @@ private: /** * Notify parent about size change, makes sense when used inside accordion */ - void notifySizeChanged(S32 height); + void notifySizeChanged(); + + bool mIsHorizontal; + S32 mPadding; + S32 mSpacing; panel_list_t mPanelList; }; diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp index 219667f766..f982dc99e8 100644 --- a/indra/llui/llscrolllistctrl.cpp +++ b/indra/llui/llscrolllistctrl.cpp @@ -411,7 +411,7 @@ void LLScrollListCtrl::clearRows() LLScrollListItem* LLScrollListCtrl::getFirstSelected() const { item_list::const_iterator iter; - for(iter = mItemList.begin(); iter != mItemList.end(); iter++) + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) { LLScrollListItem* item = *iter; if (item->getSelected()) @@ -1269,7 +1269,7 @@ BOOL LLScrollListCtrl::selectItemByLabel(const std::string& label, BOOL case_sen LLScrollListItem* item = getItemByLabel(label, case_sensitive, column); bool found = NULL != item; - if(found) + if (found) { selectItem(item, -1); } @@ -2747,7 +2747,7 @@ BOOL LLScrollListCtrl::setSort(S32 column_idx, BOOL ascending) S32 LLScrollListCtrl::getLinesPerPage() { //if mPageLines is NOT provided display all item - if(mPageLines) + if (mPageLines) { return mPageLines; } diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h index 73b4fb036a..326589a329 100644 --- a/indra/llui/llscrolllistctrl.h +++ b/indra/llui/llscrolllistctrl.h @@ -253,7 +253,7 @@ public: S32 getItemIndex( LLScrollListItem* item ) const; S32 getItemIndex( const LLUUID& item_id ) const; - void setCommentText( const std::string& comment_text); + void setCommentText( const std::string& comment_text); LLScrollListItem* addSeparator(EAddPosition pos); // "Simple" interface: use this when you're creating a list that contains only unique strings, only @@ -263,7 +263,7 @@ public: BOOL selectItemByLabel( const std::string& item, BOOL case_sensitive = TRUE, S32 column = 0 ); // FALSE if item not found BOOL selectItemByPrefix(const std::string& target, BOOL case_sensitive = TRUE, S32 column = -1); BOOL selectItemByPrefix(const LLWString& target, BOOL case_sensitive = TRUE, S32 column = -1); - LLScrollListItem* getItemByLabel( const std::string& item, BOOL case_sensitive = TRUE, S32 column = 0 ); + LLScrollListItem* getItemByLabel(const std::string& item, BOOL case_sensitive = TRUE, S32 column = 0); const std::string getSelectedItemLabel(S32 column = 0) const; LLSD getSelectedValue(); @@ -322,7 +322,7 @@ public: virtual S32 getScrollPos() const; virtual void setScrollPos( S32 pos ); - S32 getSearchColumn(); + S32 getSearchColumn(); void setSearchColumn(S32 column) { mSearchColumn = column; } S32 getColumnIndexFromOffset(S32 x); S32 getColumnOffsetFromIndex(S32 index); @@ -371,13 +371,13 @@ public: // Used "internally" by the scroll bar. void onScrollChange( S32 new_pos, LLScrollbar* src ); - static void onClickColumn(void *userdata); + static void onClickColumn(void *userdata); - virtual void updateColumns(bool force_update = false); - S32 calcMaxContentWidth(); - bool updateColumnWidths(); + virtual void updateColumns(bool force_update = false); + S32 calcMaxContentWidth(); + bool updateColumnWidths(); - void setHeadingHeight(S32 heading_height); + void setHeadingHeight(S32 heading_height); /** * Sets max visible lines without scroolbar, if this value equals to 0, * then display all items. @@ -398,18 +398,20 @@ public: virtual void deselect(); virtual BOOL canDeselect() const; - void setNumDynamicColumns(S32 num) { mNumDynamicWidthColumns = num; } - void updateStaticColumnWidth(LLScrollListColumn* col, S32 new_width); - S32 getTotalStaticColumnWidth() { return mTotalStaticColumnWidth; } + void setNumDynamicColumns(S32 num) { mNumDynamicWidthColumns = num; } + void updateStaticColumnWidth(LLScrollListColumn* col, S32 new_width); + S32 getTotalStaticColumnWidth() { return mTotalStaticColumnWidth; } std::string getSortColumnName(); BOOL getSortAscending() { return mSortColumns.empty() ? TRUE : mSortColumns.back().second; } BOOL hasSortOrder() const; void clearSortOrder(); - void setAlternateSort() { mAlternateSort = true; } + void setAlternateSort() { mAlternateSort = TRUE; } - S32 selectMultiple( uuid_vec_t ids ); + void selectPrevItem(BOOL extend_selection = FALSE); + void selectNextItem(BOOL extend_selection = FALSE); + S32 selectMultiple(uuid_vec_t ids); // conceptually const, but mutates mItemList void updateSort() const; // sorts a list without affecting the permanent sort order (so further list insertions can be unsorted, for example) @@ -454,8 +456,6 @@ protected: void updateLineHeight(); private: - void selectPrevItem(BOOL extend_selection); - void selectNextItem(BOOL extend_selection); void drawItems(); void updateLineHeightInsert(LLScrollListItem* item); diff --git a/indra/llui/llsearcheditor.cpp b/indra/llui/llsearcheditor.cpp index bafeef41fb..cfaf08ec0a 100644 --- a/indra/llui/llsearcheditor.cpp +++ b/indra/llui/llsearcheditor.cpp @@ -178,6 +178,10 @@ void LLSearchEditor::setFocus( BOOL b ) void LLSearchEditor::onClearButtonClick(const LLSD& data) { setText(LLStringUtil::null); + if (mTextChangedCallback) + { + mTextChangedCallback(this, getValue()); + } mSearchEditor->onCommit(); // force keystroke callback } diff --git a/indra/llui/llspellcheck.h b/indra/llui/llspellcheck.h index 3da5e30955..14f9b44fe4 100644 --- a/indra/llui/llspellcheck.h +++ b/indra/llui/llspellcheck.h @@ -47,7 +47,7 @@ public: protected: void addToDictFile(const std::string& dict_path, const std::string& word); void initHunspell(const std::string& dict_language); - void initSingleton(); + void initSingleton() override; public: typedef std::list<std::string> dict_list_t; diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index e0697cb454..7ccf025a19 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -29,6 +29,8 @@ #include "lltextbase.h" +#include "llemojidictionary.h" +#include "llemojihelper.h" #include "lllocalcliprect.h" #include "llmenugl.h" #include "llscrollcontainer.h" @@ -161,10 +163,12 @@ LLTextBase::Params::Params() line_spacing("line_spacing"), max_text_length("max_length", 255), font_shadow("font_shadow"), + text_valign("text_valign"), wrap("wrap"), trusted_content("trusted_content", true), always_show_icons("always_show_icons", false), use_ellipses("use_ellipses", false), + use_color("use_color", true), parse_urls("parse_urls", false), force_urls_external("force_urls_external", false), parse_highlights("parse_highlights", false) @@ -208,6 +212,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) mVPad(p.v_pad), mHAlign(p.font_halign), mVAlign(p.font_valign), + mTextVAlign(p.text_valign.isProvided() ? p.text_valign.getValue() : p.font_valign.getValue()), mLineSpacingMult(p.line_spacing.multiple), mLineSpacingPixels(p.line_spacing.pixels), mClip(p.clip), @@ -222,6 +227,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) mPlainText ( p.plain_text ), mWordWrap(p.wrap), mUseEllipses( p.use_ellipses ), + mUseColor(p.use_color), mParseHTML(p.parse_urls), mForceUrlsExternal(p.force_urls_external), mParseHighlights(p.parse_highlights), @@ -582,7 +588,7 @@ void LLTextBase::drawCursor() fontp = segmentp->getStyle()->getFont(); fontp->render(text, mCursorPos, cursor_rect, LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], alpha), - LLFontGL::LEFT, mVAlign, + LLFontGL::LEFT, mTextVAlign, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, 1); @@ -896,6 +902,28 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s } } + // Insert special segments where necessary (insertSegment takes care of splitting normal text segments around them for us) + { + LLStyleSP emoji_style; + LLEmojiDictionary* ed = LLEmojiDictionary::instanceExists() ? LLEmojiDictionary::getInstance() : NULL; + for (S32 text_kitty = 0, text_len = wstr.size(); text_kitty < text_len; text_kitty++) + { + llwchar code = wstr[text_kitty]; + bool isEmoji = ed ? ed->isEmoji(code) : LLStringOps::isEmoji(code); + if (isEmoji) + { + if (!emoji_style) + { + emoji_style = new LLStyle(getStyleParams()); + emoji_style->setFont(LLFontGL::getFontEmoji()); + } + + S32 new_seg_start = pos + text_kitty; + insertSegment(new LLEmojiTextSegment(emoji_style, new_seg_start, new_seg_start + 1, *this)); + } + } + } + getViewModel()->getEditableDisplay().insert(pos, wstr); if ( truncate() ) @@ -1079,6 +1107,7 @@ void LLTextBase::insertSegment(LLTextSegmentPtr segment_to_insert) needsReflow(reflow_start_index); } +//virtual BOOL LLTextBase::handleMouseDown(S32 x, S32 y, MASK mask) { // handle triple click @@ -1133,6 +1162,7 @@ BOOL LLTextBase::handleMouseDown(S32 x, S32 y, MASK mask) return LLUICtrl::handleMouseDown(x, y, mask); } +//virtual BOOL LLTextBase::handleMouseUp(S32 x, S32 y, MASK mask) { LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); @@ -1152,6 +1182,7 @@ BOOL LLTextBase::handleMouseUp(S32 x, S32 y, MASK mask) return LLUICtrl::handleMouseUp(x, y, mask); } +//virtual BOOL LLTextBase::handleMiddleMouseDown(S32 x, S32 y, MASK mask) { LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); @@ -1163,6 +1194,7 @@ BOOL LLTextBase::handleMiddleMouseDown(S32 x, S32 y, MASK mask) return LLUICtrl::handleMiddleMouseDown(x, y, mask); } +//virtual BOOL LLTextBase::handleMiddleMouseUp(S32 x, S32 y, MASK mask) { LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); @@ -1174,6 +1206,7 @@ BOOL LLTextBase::handleMiddleMouseUp(S32 x, S32 y, MASK mask) return LLUICtrl::handleMiddleMouseUp(x, y, mask); } +//virtual BOOL LLTextBase::handleRightMouseDown(S32 x, S32 y, MASK mask) { LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); @@ -1185,6 +1218,7 @@ BOOL LLTextBase::handleRightMouseDown(S32 x, S32 y, MASK mask) return LLUICtrl::handleRightMouseDown(x, y, mask); } +//virtual BOOL LLTextBase::handleRightMouseUp(S32 x, S32 y, MASK mask) { LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); @@ -1196,6 +1230,7 @@ BOOL LLTextBase::handleRightMouseUp(S32 x, S32 y, MASK mask) return LLUICtrl::handleRightMouseUp(x, y, mask); } +//virtual BOOL LLTextBase::handleDoubleClick(S32 x, S32 y, MASK mask) { //Don't start triple click timer if user have clicked on scrollbar @@ -1215,6 +1250,7 @@ BOOL LLTextBase::handleDoubleClick(S32 x, S32 y, MASK mask) return LLUICtrl::handleDoubleClick(x, y, mask); } +//virtual BOOL LLTextBase::handleHover(S32 x, S32 y, MASK mask) { LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); @@ -1226,6 +1262,7 @@ BOOL LLTextBase::handleHover(S32 x, S32 y, MASK mask) return LLUICtrl::handleHover(x, y, mask); } +//virtual BOOL LLTextBase::handleScrollWheel(S32 x, S32 y, S32 clicks) { LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); @@ -1237,6 +1274,7 @@ BOOL LLTextBase::handleScrollWheel(S32 x, S32 y, S32 clicks) return LLUICtrl::handleScrollWheel(x, y, clicks); } +//virtual BOOL LLTextBase::handleToolTip(S32 x, S32 y, MASK mask) { LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); @@ -1248,7 +1286,20 @@ BOOL LLTextBase::handleToolTip(S32 x, S32 y, MASK mask) return LLUICtrl::handleToolTip(x, y, mask); } +//virtual +const std::string LLTextBase::getToolTip() const +{ + if (sDebugUnicode) + { + std::string text = getText(); + std::string tooltip = utf8str_showBytesUTF8(text); + return tooltip; + } + + return LLUICtrl::getToolTip(); +} +//virtual void LLTextBase::reshape(S32 width, S32 height, BOOL called_from_parent) { if (width != getRect().getWidth() || height != getRect().getHeight() || LLView::sForceReshape) @@ -1275,6 +1326,7 @@ void LLTextBase::reshape(S32 width, S32 height, BOOL called_from_parent) } } +//virtual void LLTextBase::draw() { // reflow if needed, on demand @@ -1985,21 +2037,8 @@ LLTextBase::segment_set_t::const_iterator LLTextBase::getEditableSegIterContaini LLTextBase::segment_set_t::iterator LLTextBase::getSegIterContaining(S32 index) { - static LLPointer<LLIndexSegment> index_segment = new LLIndexSegment(); - S32 text_len = 0; - if (!useLabel()) - { - text_len = getLength(); - } - else - { - text_len = mLabel.getWString().length(); - } - - if (index > text_len) { return mSegments.end(); } - // when there are no segments, we return the end iterator, which must be checked by caller if (mSegments.size() <= 1) { return mSegments.begin(); } @@ -2013,18 +2052,6 @@ LLTextBase::segment_set_t::const_iterator LLTextBase::getSegIterContaining(S32 i { static LLPointer<LLIndexSegment> index_segment = new LLIndexSegment(); - S32 text_len = 0; - if (!useLabel()) - { - text_len = getLength(); - } - else - { - text_len = mLabel.getWString().length(); - } - - if (index > text_len) { return mSegments.end(); } - // when there are no segments, we return the end iterator, which must be checked by caller if (mSegments.size() <= 1) { return mSegments.begin(); } @@ -2188,8 +2215,8 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para S32 start=0,end=0; LLUrlMatch match; std::string text = new_text; - while ( LLUrlRegistry::instance().findUrl(text, match, - boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3),isContentTrusted() || mAlwaysShowIcons)) + while (LLUrlRegistry::instance().findUrl(text, match, + boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3), isContentTrusted() || mAlwaysShowIcons)) { start = match.getStart(); end = match.getEnd()+1; @@ -2437,18 +2464,18 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig LLStyle::Params normal_style_params(style_params); normal_style_params.font.style("NORMAL"); LLStyleConstSP normal_sp(new LLStyle(normal_style_params)); - segments.push_back(new LLOnHoverChangeableTextSegment(sp, normal_sp, segment_start, segment_end, *this )); + segments.push_back(new LLOnHoverChangeableTextSegment(sp, normal_sp, segment_start, segment_end, *this)); } else { - segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this )); + segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this)); } insertStringNoUndo(getLength(), wide_text, &segments); } // Set the cursor and scroll position - if( selection_start != selection_end ) + if (selection_start != selection_end) { mSelectionStart = selection_start; mSelectionEnd = selection_end; @@ -2456,7 +2483,7 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig mIsSelecting = was_selecting; setCursorPos(cursor_pos); } - else if( cursor_was_at_end ) + else if (cursor_was_at_end) { setCursorPos(getLength()); } @@ -2468,25 +2495,28 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only) { - if (new_text.empty()) return; + if (new_text.empty()) + { + return; + } std::string::size_type start = 0; - std::string::size_type pos = new_text.find("\n",start); + std::string::size_type pos = new_text.find("\n", start); - while(pos!=-1) + while (pos != std::string::npos) { - if(pos!=start) + if (pos != start) { std::string str = std::string(new_text,start,pos-start); - appendAndHighlightTextImpl(str,highlight_part, style_params, underline_on_hover_only); + appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only); } appendLineBreakSegment(style_params); start = pos+1; - pos = new_text.find("\n",start); + pos = new_text.find("\n", start); } - std::string str = std::string(new_text,start,new_text.length()-start); - appendAndHighlightTextImpl(str,highlight_part, style_params, underline_on_hover_only); + std::string str = std::string(new_text, start, new_text.length() - start); + appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only); } @@ -3318,12 +3348,13 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele font->render(text, start, rect, color, - LLFontGL::LEFT, mEditor.mVAlign, + LLFontGL::LEFT, mEditor.mTextVAlign, LLFontGL::NORMAL, mStyle->getShadowType(), length, &right_x, - mEditor.getUseEllipses()); + mEditor.getUseEllipses(), + mEditor.getUseColor()); } rect.mLeft = right_x; @@ -3337,12 +3368,13 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele font->render(text, start, rect, mStyle->getSelectedColor().get(), - LLFontGL::LEFT, mEditor.mVAlign, + LLFontGL::LEFT, mEditor.mTextVAlign, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, length, &right_x, - mEditor.getUseEllipses()); + mEditor.getUseEllipses(), + mEditor.getUseColor()); } rect.mLeft = right_x; if( selection_end < seg_end ) @@ -3354,12 +3386,13 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele font->render(text, start, rect, color, - LLFontGL::LEFT, mEditor.mVAlign, + LLFontGL::LEFT, mEditor.mTextVAlign, LLFontGL::NORMAL, mStyle->getShadowType(), length, &right_x, - mEditor.getUseEllipses()); + mEditor.getUseEllipses(), + mEditor.getUseColor()); } return right_x; } @@ -3591,6 +3624,33 @@ const S32 LLLabelTextSegment::getLength() const } // +// LLEmojiTextSegment +// +LLEmojiTextSegment::LLEmojiTextSegment(LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor) + : LLNormalTextSegment(style, start, end, editor) +{ +} + +LLEmojiTextSegment::LLEmojiTextSegment(const LLColor4& color, S32 start, S32 end, LLTextBase& editor, BOOL is_visible) + : LLNormalTextSegment(color, start, end, editor, is_visible) +{ +} + +BOOL LLEmojiTextSegment::handleToolTip(S32 x, S32 y, MASK mask) +{ + if (mTooltip.empty()) + { + LLWString emoji = getWText().substr(getStart(), getEnd() - getStart()); + if (!emoji.empty()) + { + mTooltip = LLEmojiHelper::instance().getToolTip(emoji[0]); + } + } + + return LLNormalTextSegment::handleToolTip(x, y, mask); +} + +// // LLOnHoverChangeableTextSegment // diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 3611ab0499..9d3c54fbee 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -178,6 +178,18 @@ protected: /*virtual*/ const S32 getLength() const; }; +// Text segment that represents a single emoji character that has a different style (=font size) than the rest of +// the document it belongs to +class LLEmojiTextSegment : public LLNormalTextSegment +{ +public: + LLEmojiTextSegment(LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor); + LLEmojiTextSegment(const LLColor4& color, S32 start, S32 end, LLTextBase& editor, BOOL is_visible = TRUE); + + bool canEdit() const override { return false; } + BOOL handleToolTip(S32 x, S32 y, MASK mask) override; +}; + // Text segment that changes it's style depending of mouse pointer position ( is it inside or outside segment) class LLOnHoverChangeableTextSegment : public LLNormalTextSegment { @@ -273,7 +285,7 @@ typedef LLPointer<LLTextSegment> LLTextSegmentPtr; /// as LLTextEditor and LLTextBox. It implements shared functionality /// such as Url highlighting and opening. /// -class LLTextBase +class LLTextBase : public LLUICtrl, protected LLEditMenuHandler, public LLSpellCheckMenuHandler, @@ -316,6 +328,7 @@ public: plain_text, wrap, use_ellipses, + use_color, parse_urls, force_urls_external, parse_highlights, @@ -335,55 +348,58 @@ public: Optional<LLFontGL::ShadowType> font_shadow; + Optional<LLFontGL::VAlign> text_valign; + Params(); }; // LLMouseHandler interface - /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ BOOL handleMiddleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ BOOL handleMiddleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ BOOL handleRightMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask); - /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask); - /*virtual*/ BOOL handleScrollWheel(S32 x, S32 y, S32 clicks); - /*virtual*/ BOOL handleToolTip(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask) override; + /*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask) override; + /*virtual*/ BOOL handleMiddleMouseDown(S32 x, S32 y, MASK mask) override; + /*virtual*/ BOOL handleMiddleMouseUp(S32 x, S32 y, MASK mask) override; + /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask) override; + /*virtual*/ BOOL handleRightMouseUp(S32 x, S32 y, MASK mask) override; + /*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask) override; + /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask) override; + /*virtual*/ BOOL handleScrollWheel(S32 x, S32 y, S32 clicks) override; + /*virtual*/ BOOL handleToolTip(S32 x, S32 y, MASK mask) override; // LLView interface - /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); - /*virtual*/ void draw(); + /*virtual*/ const std::string getToolTip() const override; + /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE) override; + /*virtual*/ void draw() override; // LLUICtrl interface - /*virtual*/ BOOL acceptsTextInput() const { return !mReadOnly; } - /*virtual*/ void setColor( const LLColor4& c ); + /*virtual*/ BOOL acceptsTextInput() const override { return !mReadOnly; } + /*virtual*/ void setColor(const LLColor4& c) override; virtual void setReadOnlyColor(const LLColor4 &c); - virtual void onVisibilityChange( BOOL new_visibility ); + /*virtual*/ void onVisibilityChange(BOOL new_visibility) override; - /*virtual*/ void setValue(const LLSD& value ); - /*virtual*/ LLTextViewModel* getViewModel() const; + /*virtual*/ void setValue(const LLSD& value) override; + /*virtual*/ LLTextViewModel* getViewModel() const override; // LLEditMenuHandler interface - /*virtual*/ BOOL canDeselect() const; - /*virtual*/ void deselect(); + /*virtual*/ BOOL canDeselect() const override; + /*virtual*/ void deselect() override; - virtual void onFocusReceived(); - virtual void onFocusLost(); + virtual void onFocusReceived() override; + virtual void onFocusLost() override; void setParseHTML(bool parse_html) { mParseHTML = parse_html; } // LLSpellCheckMenuHandler overrides - /*virtual*/ bool getSpellCheck() const; + /*virtual*/ bool getSpellCheck() const override; - /*virtual*/ const std::string& getSuggestion(U32 index) const; - /*virtual*/ U32 getSuggestionCount() const; - /*virtual*/ void replaceWithSuggestion(U32 index); + /*virtual*/ const std::string& getSuggestion(U32 index) const override; + /*virtual*/ U32 getSuggestionCount() const override; + /*virtual*/ void replaceWithSuggestion(U32 index) override; - /*virtual*/ void addToDictionary(); - /*virtual*/ bool canAddToDictionary() const; + /*virtual*/ void addToDictionary() override; + /*virtual*/ bool canAddToDictionary() const override; - /*virtual*/ void addToIgnore(); - /*virtual*/ bool canAddToIgnore() const; + /*virtual*/ void addToIgnore() override; + /*virtual*/ bool canAddToIgnore() const override; // Spell checking helper functions std::string getMisspelledWord(U32 pos) const; @@ -394,6 +410,7 @@ public: // used by LLTextSegment layout code bool getWordWrap() { return mWordWrap; } bool getUseEllipses() { return mUseEllipses; } + bool getUseColor() { return mUseColor; } bool truncate(); // returns true of truncation occurred bool isContentTrusted() {return mTrustedContent;} @@ -416,7 +433,7 @@ public: void appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params = LLStyle::Params()); void setLabel(const LLStringExplicit& label); - virtual BOOL setLabelArg(const std::string& key, const LLStringExplicit& text ); + /*virtual*/ BOOL setLabelArg(const std::string& key, const LLStringExplicit& text) override; const std::string& getLabel() { return mLabel.getString(); } const LLWString& getWlabel() { return mLabel.getWString();} @@ -633,7 +650,8 @@ protected: S32 normalizeUri(std::string& uri); protected: - virtual std::string _getSearchText() const + // virtual + std::string _getSearchText() const override { return mLabel.getString() + getToolTip(); } @@ -687,8 +705,9 @@ protected: // configuration S32 mHPad; // padding on left of text S32 mVPad; // padding above text - LLFontGL::HAlign mHAlign; - LLFontGL::VAlign mVAlign; + LLFontGL::HAlign mHAlign; // horizontal alignment of the document in its entirety + LLFontGL::VAlign mVAlign; // vertical alignment of the document in its entirety + LLFontGL::VAlign mTextVAlign; // vertical alignment of a text segment within a single line of text F32 mLineSpacingMult; // multiple of line height used as space for a single line of text (e.g. 1.5 to get 50% padding) S32 mLineSpacingPixels; // padding between lines bool mBorderVisible; @@ -697,6 +716,7 @@ protected: bool mParseHighlights; // highlight user-defined keywords bool mWordWrap; bool mUseEllipses; + bool mUseColor; bool mTrackEnd; // if true, keeps scroll position at end of document during resize bool mReadOnly; bool mBGVisible; // render background? diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index 3d2a426913..092739a538 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -43,6 +43,7 @@ #include "llmath.h" #include "llclipboard.h" +#include "llemojihelper.h" #include "llscrollbar.h" #include "llstl.h" #include "llstring.h" @@ -238,6 +239,7 @@ LLTextEditor::Params::Params() default_color("default_color"), commit_on_focus_lost("commit_on_focus_lost", false), show_context_menu("show_context_menu"), + show_emoji_helper("show_emoji_helper"), enable_tooltip_paste("enable_tooltip_paste") { addSynonym(prevalidate_callback, "text_type"); @@ -258,6 +260,7 @@ LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) : mTabsToNextField(p.ignore_tab), mPrevalidateFunc(p.prevalidate_callback()), mShowContextMenu(p.show_context_menu), + mShowEmojiHelper(p.show_emoji_helper), mEnableTooltipPaste(p.enable_tooltip_paste), mPassDelete(FALSE), mKeepSelectionOnReturn(false) @@ -505,6 +508,16 @@ void LLTextEditor::getSegmentsInRange(LLTextEditor::segment_vec_t& segments_out, } } +void LLTextEditor::setShowEmojiHelper(bool show) +{ + if (!mShowEmojiHelper) + { + LLEmojiHelper::instance().hideHelper(this); + } + + mShowEmojiHelper = show; +} + BOOL LLTextEditor::selectionContainsLineBreaks() { if (hasSelection()) @@ -668,6 +681,28 @@ void LLTextEditor::selectByCursorPosition(S32 prev_cursor_pos, S32 next_cursor_p endSelection(); } +void LLTextEditor::insertEmoji(llwchar emoji) +{ + LL_INFOS() << "LLTextEditor::insertEmoji(" << wchar_utf8_preview(emoji) << ")" << LL_ENDL; + auto styleParams = LLStyle::Params(); + styleParams.font = LLFontGL::getFontEmoji(); + auto segment = new LLEmojiTextSegment(new LLStyle(styleParams), mCursorPos, mCursorPos + 1, *this); + insert(mCursorPos, LLWString(1, emoji), false, segment); + setCursorPos(mCursorPos + 1); +} + +void LLTextEditor::handleEmojiCommit(llwchar emoji) +{ + S32 shortCodePos; + if (LLEmojiHelper::isCursorInEmojiCode(getWText(), mCursorPos, &shortCodePos)) + { + remove(shortCodePos, mCursorPos - shortCodePos, true); + setCursorPos(shortCodePos); + + insertEmoji(emoji); + } +} + BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) { BOOL handled = FALSE; @@ -934,6 +969,12 @@ BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) S32 LLTextEditor::execute( TextCmd* cmd ) { + if (!mReadOnly && mShowEmojiHelper) + { + // Any change to our contents should always hide the helper + LLEmojiHelper::instance().hideHelper(this); + } + S32 delta = 0; if( cmd->execute(this, &delta) ) { @@ -983,7 +1024,7 @@ S32 LLTextEditor::remove(S32 pos, S32 length, bool group_with_next_op) // store text segments getSegmentsInRange(segments_to_remove, pos, pos + length, false); - if(pos <= end_pos) + if (pos <= end_pos) { removedChar = execute( new TextCmdRemove( pos, group_with_next_op, end_pos - pos, segments_to_remove ) ); } @@ -1007,11 +1048,12 @@ S32 LLTextEditor::overwriteChar(S32 pos, llwchar wc) // a pseudo-tab (up to for spaces in a row) void LLTextEditor::removeCharOrTab() { - if( !getEnabled() ) + if (!getEnabled()) { return; } - if( mCursorPos > 0 ) + + if (mCursorPos > 0) { S32 chars_to_remove = 1; @@ -1023,14 +1065,14 @@ void LLTextEditor::removeCharOrTab() if (offset > 0) { chars_to_remove = offset % SPACES_PER_TAB; - if( chars_to_remove == 0 ) + if (chars_to_remove == 0) { chars_to_remove = SPACES_PER_TAB; } - for( S32 i = 0; i < chars_to_remove; i++ ) + for (S32 i = 0; i < chars_to_remove; i++) { - if (text[ mCursorPos - i - 1] != ' ') + if (text[mCursorPos - i - 1] != ' ') { // Fewer than a full tab's worth of spaces, so // just delete a single character. @@ -1044,8 +1086,10 @@ void LLTextEditor::removeCharOrTab() for (S32 i = 0; i < chars_to_remove; i++) { setCursorPos(mCursorPos - 1); - remove( mCursorPos, 1, FALSE ); + remove(mCursorPos, 1, false); } + + tryToShowEmojiHelper(); } else { @@ -1056,7 +1100,7 @@ void LLTextEditor::removeCharOrTab() // Remove a single character from the text S32 LLTextEditor::removeChar(S32 pos) { - return remove( pos, 1, FALSE ); + return remove(pos, 1, false); } void LLTextEditor::removeChar() @@ -1065,10 +1109,12 @@ void LLTextEditor::removeChar() { return; } + if (mCursorPos > 0) { setCursorPos(mCursorPos - 1); removeChar(mCursorPos); + tryToShowEmojiHelper(); } else { @@ -1127,6 +1173,7 @@ void LLTextEditor::addChar(llwchar wc) } setCursorPos(mCursorPos + addChar( mCursorPos, wc )); + tryToShowEmojiHelper(); if (!mReadOnly && mAutoreplaceCallback != NULL) { @@ -1146,6 +1193,37 @@ void LLTextEditor::addChar(llwchar wc) } } +void LLTextEditor::showEmojiHelper() +{ + if (mReadOnly || !mShowEmojiHelper) + return; + + const LLRect cursorRect(getLocalRectFromDocIndex(mCursorPos)); + auto cb = [this](llwchar emoji) { insertEmoji(emoji); }; + LLEmojiHelper::instance().showHelper(this, cursorRect.mLeft, cursorRect.mTop, LLStringUtil::null, cb); +} + +void LLTextEditor::tryToShowEmojiHelper() +{ + if (mReadOnly || !mShowEmojiHelper) + return; + + S32 shortCodePos; + LLWString wtext(getWText()); + if (LLEmojiHelper::isCursorInEmojiCode(wtext, mCursorPos, &shortCodePos)) + { + const LLRect cursorRect(getLocalRectFromDocIndex(shortCodePos)); + const LLWString wpart(wtext.substr(shortCodePos, mCursorPos - shortCodePos)); + const std::string part(wstring_to_utf8str(wpart)); + auto cb = [this](llwchar emoji) { handleEmojiCommit(emoji); }; + LLEmojiHelper::instance().showHelper(this, cursorRect.mLeft, cursorRect.mTop, part, cb); + } + else + { + LLEmojiHelper::instance().hideHelper(); + } +} + void LLTextEditor::addLineBreakChar(BOOL group_together) { if( !getEnabled() ) @@ -1778,6 +1856,11 @@ BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask ) } else { + if (!mReadOnly && mShowEmojiHelper && LLEmojiHelper::instance().handleKey(this, key, mask)) + { + return TRUE; + } + if (mEnableTooltipPaste && LLToolTipMgr::instance().toolTipVisible() && KEY_TAB == key) @@ -1819,6 +1902,12 @@ BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask ) { resetCursorBlink(); needsScroll(); + + if (mShowEmojiHelper) + { + // Dismiss the helper whenever we handled a key that it didn't + LLEmojiHelper::instance().hideHelper(this); + } } return handled; @@ -1837,7 +1926,12 @@ BOOL LLTextEditor::handleUnicodeCharHere(llwchar uni_char) // Handle most keys only if the text editor is writeable. if( !mReadOnly ) { - if( mAutoIndent && '}' == uni_char ) + if (mShowEmojiHelper && uni_char < 0x80 && LLEmojiHelper::instance().handleKey(this, (KEY)uni_char, MASK_NONE)) + { + return TRUE; + } + + if( mAutoIndent && '}' == uni_char ) { unindentLineBeforeCloseBrace(); } diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index f3939248c2..521405ec25 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -60,6 +60,7 @@ public: ignore_tab, commit_on_focus_lost, show_context_menu, + show_emoji_helper, enable_tooltip_paste, auto_indent; @@ -91,6 +92,9 @@ public: static S32 spacesPerTab(); + void insertEmoji(llwchar emoji); + void handleEmojiCommit(llwchar emoji); + // mousehandler overrides virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); @@ -202,6 +206,10 @@ public: void setShowContextMenu(bool show) { mShowContextMenu = show; } bool getShowContextMenu() const { return mShowContextMenu; } + void showEmojiHelper(); + void setShowEmojiHelper(bool show); + bool getShowEmojiHelper() const { return mShowEmojiHelper; } + void setPassDelete(BOOL b) { mPassDelete = b; } protected: @@ -248,6 +256,7 @@ protected: S32 insert(S32 pos, const LLWString &wstr, bool group_with_next_op, LLTextSegmentPtr segment); S32 remove(S32 pos, S32 length, bool group_with_next_op); + void tryToShowEmojiHelper(); void focusLostHelper(); void updateAllowingLanguageInput(); BOOL hasPreeditString() const; @@ -318,6 +327,7 @@ private: BOOL mAllowEmbeddedItems; bool mShowContextMenu; + bool mShowEmojiHelper; bool mEnableTooltipPaste; bool mPassDelete; bool mKeepSelectionOnReturn; // disabling of removing selected text after pressing of Enter diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index 21afcae7c3..7eb9ae69fb 100644 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -768,25 +768,20 @@ void LLUICtrl::setIsChrome(BOOL is_chrome) // virtual BOOL LLUICtrl::getIsChrome() const -{ +{ + if (mIsChrome) + return TRUE; + LLView* parent_ctrl = getParent(); - while(parent_ctrl) + while (parent_ctrl) { - if(parent_ctrl->isCtrl()) - { - break; - } + if (parent_ctrl->isCtrl()) + return ((LLUICtrl*)parent_ctrl)->getIsChrome(); + parent_ctrl = parent_ctrl->getParent(); } - - if(parent_ctrl) - { - return mIsChrome || ((LLUICtrl*)parent_ctrl)->getIsChrome(); - } - else - { - return mIsChrome ; - } + + return FALSE; } diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h index be1c7dd0b6..cffb9933d4 100644 --- a/indra/llui/lluictrl.h +++ b/indra/llui/lluictrl.h @@ -264,7 +264,7 @@ public: class LLTextInputFilter : public LLQueryFilter, public LLSingleton<LLTextInputFilter> { LLSINGLETON_EMPTY_CTOR(LLTextInputFilter); - /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override { return filterResult_t(view->isCtrl() && static_cast<const LLUICtrl *>(view)->acceptsTextInput(), TRUE); } diff --git a/indra/llui/lluistring.h b/indra/llui/lluistring.h index 07e02de6d8..b1089a3903 100644 --- a/indra/llui/lluistring.h +++ b/indra/llui/lluistring.h @@ -61,6 +61,7 @@ public: LLUIString() : mArgs(NULL), mNeedsResult(false), mNeedsWResult(false) {} LLUIString(const std::string& instring, const LLStringUtil::format_map_t& args); LLUIString(const std::string& instring) : mArgs(NULL) { assign(instring); } + LLUIString(const LLWString& instring) : mArgs(NULL) { insert(0, instring); } ~LLUIString() { delete mArgs; } void assign(const std::string& instring); diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index da7868d804..139eb17efa 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -60,6 +60,7 @@ static const S32 LINE_HEIGHT = 15; S32 LLView::sDepth = 0; bool LLView::sDebugRects = false; +bool LLView::sDebugUnicode = false; bool LLView::sIsRectDirty = false; LLRect LLView::sDirtyRect; bool LLView::sDebugRectsShowNames = true; @@ -520,7 +521,7 @@ BOOL LLView::focusNext(LLView::child_list_t & result) { next = result.rbegin(); } - if((*next)->isCtrl()) + if ((*next)->isCtrl() && ((LLUICtrl*)*next)->hasTabStop()) { LLUICtrl * ctrl = static_cast<LLUICtrl*>(*next); ctrl->setFocus(TRUE); @@ -1024,7 +1025,7 @@ BOOL LLView::handleUnicodeChar(llwchar uni_char, BOOL called_from_parent) handled = handleUnicodeCharHere(uni_char); if (handled && LLView::sDebugKeys) { - LL_INFOS() << "Unicode key handled by " << getName() << LL_ENDL; + LL_INFOS() << "Unicode key " << wchar_utf8_preview(uni_char) << " is handled by " << getName() << LL_ENDL; } } } @@ -1336,8 +1337,7 @@ void LLView::drawDebugRect() std::string debug_text = llformat("%s (%d x %d)", getName().c_str(), debug_rect.getWidth(), debug_rect.getHeight()); LLFontGL::getFontSansSerifSmall()->renderUTF8(debug_text, 0, (F32)x, (F32)y, border_color, - LLFontGL::HCENTER, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, - S32_MAX, S32_MAX, NULL, FALSE); + LLFontGL::HCENTER, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); } } LLUI::popMatrix(); @@ -1753,23 +1753,26 @@ LLCoordGL getNeededTranslation(const LLRect& input, const LLRect& constraint, S3 const S32 KEEP_ONSCREEN_PIXELS_WIDTH = llmin(min_overlap_pixels, input.getWidth()); const S32 KEEP_ONSCREEN_PIXELS_HEIGHT = llmin(min_overlap_pixels, input.getHeight()); - if( input.mRight - KEEP_ONSCREEN_PIXELS_WIDTH < constraint.mLeft ) - { - delta.mX = constraint.mLeft - (input.mRight - KEEP_ONSCREEN_PIXELS_WIDTH); - } - else if( input.mLeft + KEEP_ONSCREEN_PIXELS_WIDTH > constraint.mRight ) + if (KEEP_ONSCREEN_PIXELS_WIDTH <= constraint.getWidth() && + KEEP_ONSCREEN_PIXELS_HEIGHT <= constraint.getHeight()) { - delta.mX = constraint.mRight - (input.mLeft + KEEP_ONSCREEN_PIXELS_WIDTH); - } + if (input.mRight - KEEP_ONSCREEN_PIXELS_WIDTH < constraint.mLeft) + { + delta.mX = constraint.mLeft - (input.mRight - KEEP_ONSCREEN_PIXELS_WIDTH); + } + else if (input.mLeft + KEEP_ONSCREEN_PIXELS_WIDTH > constraint.mRight) + { + delta.mX = constraint.mRight - (input.mLeft + KEEP_ONSCREEN_PIXELS_WIDTH); + } - if( input.mTop > constraint.mTop ) - { - delta.mY = constraint.mTop - input.mTop; - } - else - if( input.mTop - KEEP_ONSCREEN_PIXELS_HEIGHT < constraint.mBottom ) - { - delta.mY = constraint.mBottom - (input.mTop - KEEP_ONSCREEN_PIXELS_HEIGHT); + if (input.mTop > constraint.mTop) + { + delta.mY = constraint.mTop - input.mTop; + } + else if (input.mTop - KEEP_ONSCREEN_PIXELS_HEIGHT < constraint.mBottom) + { + delta.mY = constraint.mBottom - (input.mTop - KEEP_ONSCREEN_PIXELS_HEIGHT); + } } return delta; @@ -1780,13 +1783,19 @@ LLCoordGL getNeededTranslation(const LLRect& input, const LLRect& constraint, S3 // (Why top and left? That's where the drag bars are for floaters.) BOOL LLView::translateIntoRect(const LLRect& constraint, S32 min_overlap_pixels) { - LLCoordGL translation = getNeededTranslation(getRect(), constraint, min_overlap_pixels); + return translateRectIntoRect(getRect(), constraint, min_overlap_pixels); +} + +BOOL LLView::translateRectIntoRect(const LLRect& rect, const LLRect& constraint, S32 min_overlap_pixels) +{ + LLCoordGL translation = getNeededTranslation(rect, constraint, min_overlap_pixels); if (translation.mX != 0 || translation.mY != 0) { translate(translation.mX, translation.mY); return TRUE; } + return FALSE; } @@ -1966,7 +1975,7 @@ private: class SortByTabOrder : public LLQuerySorter, public LLSingleton<SortByTabOrder> { LLSINGLETON_EMPTY_CTOR(SortByTabOrder); - /*virtual*/ void sort(LLView * parent, LLView::child_list_t &children) const + /*virtual*/ void sort(LLView * parent, LLView::child_list_t &children) const override { children.sort(CompareByTabOrder(parent->getTabOrder(), parent->getDefaultTabGroup())); } @@ -1990,7 +1999,7 @@ const LLViewQuery & LLView::getTabOrderQuery() class LLFocusRootsFilter : public LLQueryFilter, public LLSingleton<LLFocusRootsFilter> { LLSINGLETON_EMPTY_CTOR(LLFocusRootsFilter); - /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override { return filterResult_t(view->isCtrl() && view->isFocusRoot(), !view->isFocusRoot()); } diff --git a/indra/llui/llview.h b/indra/llui/llview.h index 7360fd0fb9..6e16d41cba 100644 --- a/indra/llui/llview.h +++ b/indra/llui/llview.h @@ -111,7 +111,7 @@ public: Alternative<std::string> string; Alternative<U32> flags; - Follows(); + Follows(); }; struct Params : public LLInitParam::Block<Params> @@ -369,6 +369,7 @@ public: virtual void translate( S32 x, S32 y ); void setOrigin( S32 x, S32 y ) { mRect.translate( x - mRect.mLeft, y - mRect.mBottom ); } BOOL translateIntoRect( const LLRect& constraint, S32 min_overlap_pixels = S32_MAX); + BOOL translateRectIntoRect( const LLRect& rect, const LLRect& constraint, S32 min_overlap_pixels = S32_MAX); BOOL translateIntoRectWithExclusion( const LLRect& inside, const LLRect& exclude, S32 min_overlap_pixels = S32_MAX); void centerWithin(const LLRect& bounds); @@ -658,8 +659,11 @@ public: // Draw debug rectangles around widgets to help with alignment and spacing static bool sDebugRects; - static bool sIsRectDirty; - static LLRect sDirtyRect; + // Show hexadecimal byte values of unicode symbols in a tooltip + static bool sDebugUnicode; + + static bool sIsRectDirty; + static LLRect sDirtyRect; // Draw widget names and sizes when drawing debug rectangles, turning this // off is useful to make the rectangles themselves easier to see. @@ -702,20 +706,16 @@ template <class T> T* LLView::getChild(const std::string& name, BOOL recurse) co if (!result) { result = LLUICtrlFactory::getDefaultWidget<T>(name); - - if (result) + if (!result) { - // *NOTE: You cannot call mFoo = getChild<LLFoo>("bar") - // in a floater or panel constructor. The widgets will not - // be ready. Instead, put it in postBuild(). - LL_WARNS() << "Making dummy " << typeid(T).name() << " named \"" << name << "\" in " << getName() << LL_ENDL; - } - else - { - LL_WARNS() << "Failed to create dummy " << typeid(T).name() << LL_ENDL; - return NULL; + LL_ERRS() << "Failed to create dummy " << typeid(T).name() << LL_ENDL; } + // *NOTE: You cannot call mFoo = getChild<LLFoo>("bar") + // in a floater or panel constructor. The widgets will not + // be ready. Instead, put it in postBuild(). + LL_WARNS() << "Making dummy " << typeid(T).name() << " named \"" << name << "\" in " << getName() << LL_ENDL; + getDefaultWidgetContainer().addChild(result); } } diff --git a/indra/llui/llviewquery.h b/indra/llui/llviewquery.h index 21bb1be26f..4bc9c4a08e 100644 --- a/indra/llui/llviewquery.h +++ b/indra/llui/llviewquery.h @@ -55,37 +55,37 @@ public: class LLLeavesFilter : public LLQueryFilter, public LLSingleton<LLLeavesFilter> { LLSINGLETON_EMPTY_CTOR(LLLeavesFilter); - /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const; + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override; }; class LLRootsFilter : public LLQueryFilter, public LLSingleton<LLRootsFilter> { LLSINGLETON_EMPTY_CTOR(LLRootsFilter); - /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const; + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override; }; class LLVisibleFilter : public LLQueryFilter, public LLSingleton<LLVisibleFilter> { LLSINGLETON_EMPTY_CTOR(LLVisibleFilter); - /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const; + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override; }; class LLEnabledFilter : public LLQueryFilter, public LLSingleton<LLEnabledFilter> { LLSINGLETON_EMPTY_CTOR(LLEnabledFilter); - /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const; + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override; }; class LLTabStopFilter : public LLQueryFilter, public LLSingleton<LLTabStopFilter> { LLSINGLETON_EMPTY_CTOR(LLTabStopFilter); - /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const; + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override; }; class LLCtrlFilter : public LLQueryFilter, public LLSingleton<LLCtrlFilter> { LLSINGLETON_EMPTY_CTOR(LLCtrlFilter); - /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const; + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override; }; template <class T> diff --git a/indra/llwindow/llopenglview-objc.mm b/indra/llwindow/llopenglview-objc.mm index 586e00b5e4..0bd4e506a2 100644 --- a/indra/llwindow/llopenglview-objc.mm +++ b/indra/llwindow/llopenglview-objc.mm @@ -733,23 +733,52 @@ attributedStringInfo getSegments(NSAttributedString *str) - (void)insertText:(id)aString replacementRange:(NSRange)replacementRange { - if (!mHasMarkedText) + // SL-19801 Special workaround for system emoji picker + if ([aString length] == 2) { - for (NSInteger i = 0; i < [aString length]; i++) - { - callUnicodeCallback([aString characterAtIndex:i], mModifiers); - } - } else { - resetPreedit(); - // We may never get this point since unmarkText may be called before insertText ever gets called once we submit our text. - // But just in case... - - for (NSInteger i = 0; i < [aString length]; i++) - { - handleUnicodeCharacter([aString characterAtIndex:i]); - } - mHasMarkedText = FALSE; + @try + { + uint32_t b0 = [aString characterAtIndex:0]; + uint32_t b1 = [aString characterAtIndex:1]; + if (((b0 & 0xF000) == 0xD000) && ((b1 & 0xF000) == 0xD000)) + { + uint32_t b = 0x10000 | ((b0 & 0x3F) << 10) | (b1 & 0x3FF); + callUnicodeCallback(b, 0); + return; + } + } + @catch(NSException * e) + { + // One of the characters is an attribute string? + NSLog(@"Encountered an unsupported attributed character. Exception: %@ String: %@", e.name, aString); + return; + } } + + @try + { + if (!mHasMarkedText) + { + for (NSInteger i = 0; i < [aString length]; i++) + { + callUnicodeCallback([aString characterAtIndex:i], mModifiers); + } + } else { + resetPreedit(); + // We may never get this point since unmarkText may be called before insertText ever gets called once we submit our text. + // But just in case... + + for (NSInteger i = 0; i < [aString length]; i++) + { + handleUnicodeCharacter([aString characterAtIndex:i]); + } + mHasMarkedText = FALSE; + } + } + @catch(NSException * e) + { + NSLog(@"Failed to process an attributed string. Exception: %@ String: %@", e.name, aString); + } } - (void) insertNewline:(id)sender diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 7a70d0b6e6..355f35c558 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -18,6 +18,7 @@ include(DragDrop) include(EXPAT) include(FMODSTUDIO) include(Hunspell) +include(ICU4C) include(JPEGEncoderBasic) include(JsonCpp) include(LLAppearance) @@ -207,6 +208,7 @@ set(viewer_SOURCE_FILES llfloaterdisplayname.cpp llfloatereditenvironmentbase.cpp llfloatereditextdaycycle.cpp + llfloateremojipicker.cpp llfloaterenvironmentadjust.cpp llfloaterevent.cpp llfloaterexperiencepicker.cpp @@ -418,6 +420,7 @@ set(viewer_SOURCE_FILES llpaneleditsky.cpp llpaneleditwater.cpp llpaneleditwearable.cpp + llpanelemojicomplete.cpp llpanelenvironment.cpp llpanelexperiencelisteditor.cpp llpanelexperiencelog.cpp @@ -858,6 +861,7 @@ set(viewer_HEADER_FILES llfloaterdisplayname.h llfloatereditenvironmentbase.h llfloatereditextdaycycle.h + llfloateremojipicker.h llfloaterenvironmentadjust.h llfloaterevent.h llfloaterexperiencepicker.h @@ -1061,6 +1065,7 @@ set(viewer_HEADER_FILES llpaneleditsky.h llpaneleditwater.h llpaneleditwearable.h + llpanelemojicomplete.h llpanelenvironment.h llpanelexperiencelisteditor.h llpanelexperiencelog.h @@ -1927,6 +1932,7 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${LLPHYSICSEXTENSIONS_LIBRARIES} ll::bugsplat ll::tracy + ll::icu4c ) if( TARGET ll::intel_memops ) @@ -1940,6 +1946,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}) diff --git a/indra/newview/app_settings/emoji_groups.xml b/indra/newview/app_settings/emoji_groups.xml new file mode 100644 index 0000000000..b433927f91 --- /dev/null +++ b/indra/newview/app_settings/emoji_groups.xml @@ -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> diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 00b59f9a4d..bd38527462 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -11255,7 +11255,7 @@ <key>Type</key> <string>String</string> <key>Value</key> - <string>https://jira.secondlife.com/secure/CreateIssueDetails!init.jspa?pid=10610&issuetype=1&environment=[ENVIRONMENT]&customfield_10253=[LOCATION]</string> + <string>https://feedback.secondlife.com/</string> </map> <key>RevokePermsOnStopAnimation</key> <map> diff --git a/indra/newview/fonts/DejaVu-license.txt b/indra/newview/fonts/DejaVu-license.txt deleted file mode 100644 index df52c1709b..0000000000 --- a/indra/newview/fonts/DejaVu-license.txt +++ /dev/null @@ -1,187 +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. - -TeX Gyre DJV Math ------------------ -Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. - -Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski -(on behalf of TeX users groups) are in public domain. - -Letters imported from Euler Fraktur from AMSfonts are (c) American -Mathematical Society (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. - -AMSFonts (v. 2.2) copyright - -The PostScript Type 1 implementation of the AMSFonts produced by and -previously distributed by Blue Sky Research and Y&Y, Inc. are now freely -available for general use. This has been accomplished through the -cooperation -of a consortium of scientific publishers with Blue Sky Research and Y&Y. -Members of this consortium include: - -Elsevier Science IBM Corporation Society for Industrial and Applied -Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS) - -In order to assure the authenticity of these fonts, copyright will be -held by -the American Mathematical Society. This is not meant to restrict in any way -the legitimate use of the fonts, such as (but not limited to) electronic -distribution of documents containing these fonts, inclusion of these fonts -into other public domain or commercial font collections or computer -applications, use of the outline data to create derivative fonts and/or -faces, etc. However, the AMS does require that the AMS copyright notice be -removed from any derivative versions of the fonts which have been altered in -any way. In addition, to ensure the fidelity of TeX documents using Computer -Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces, -has requested that any alterations which yield different font metrics be -given a different name. - -$Id$ diff --git a/indra/newview/fonts/DejaVuSans-Bold.ttf b/indra/newview/fonts/DejaVuSans-Bold.ttf Binary files differdeleted file mode 100644 index 6d65fa7dc4..0000000000 --- a/indra/newview/fonts/DejaVuSans-Bold.ttf +++ /dev/null diff --git a/indra/newview/fonts/DejaVuSans-BoldOblique.ttf b/indra/newview/fonts/DejaVuSans-BoldOblique.ttf Binary files differdeleted file mode 100644 index 753f2d80b1..0000000000 --- a/indra/newview/fonts/DejaVuSans-BoldOblique.ttf +++ /dev/null diff --git a/indra/newview/fonts/DejaVuSans-Oblique.ttf b/indra/newview/fonts/DejaVuSans-Oblique.ttf Binary files differdeleted file mode 100644 index 999bac7714..0000000000 --- a/indra/newview/fonts/DejaVuSans-Oblique.ttf +++ /dev/null diff --git a/indra/newview/fonts/DejaVuSans.ttf b/indra/newview/fonts/DejaVuSans.ttf Binary files differdeleted file mode 100644 index e5f7eecce4..0000000000 --- a/indra/newview/fonts/DejaVuSans.ttf +++ /dev/null diff --git a/indra/newview/fonts/DejaVuSansMono.ttf b/indra/newview/fonts/DejaVuSansMono.ttf Binary files differdeleted file mode 100644 index f5786022f1..0000000000 --- a/indra/newview/fonts/DejaVuSansMono.ttf +++ /dev/null diff --git a/indra/newview/llautoreplace.h b/indra/newview/llautoreplace.h index 23cc313646..a1eebf9dcb 100644 --- a/indra/newview/llautoreplace.h +++ b/indra/newview/llautoreplace.h @@ -203,7 +203,7 @@ public: void setSettings(const LLAutoReplaceSettings& settings); private: - /*virtual*/ void initSingleton(); + /*virtual*/ void initSingleton() override; LLAutoReplaceSettings mSettings; ///< configuration information diff --git a/indra/newview/llchannelmanager.h b/indra/newview/llchannelmanager.h index 8abe350196..22ae595d66 100644 --- a/indra/newview/llchannelmanager.h +++ b/indra/newview/llchannelmanager.h @@ -46,7 +46,7 @@ class LLChannelManager : public LLSingleton<LLChannelManager> LLSINGLETON(LLChannelManager); virtual ~LLChannelManager(); - void cleanupSingleton(); + void cleanupSingleton() override; public: diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp index 43dc10ef5f..b9bf432581 100644 --- a/indra/newview/llchathistory.cpp +++ b/indra/newview/llchathistory.cpp @@ -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()) diff --git a/indra/newview/llchicletbar.h b/indra/newview/llchicletbar.h index 6c521dc1d5..c295b99962 100644 --- a/indra/newview/llchicletbar.h +++ b/indra/newview/llchicletbar.h @@ -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; /** diff --git a/indra/newview/llconversationlog.h b/indra/newview/llconversationlog.h index 820a5db491..54aeedcf9a 100644 --- a/indra/newview/llconversationlog.h +++ b/indra/newview/llconversationlog.h @@ -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(); diff --git a/indra/newview/llexpandabletextbox.cpp b/indra/newview/llexpandabletextbox.cpp index 3395777aab..2d332f75f5 100644 --- a/indra/newview/llexpandabletextbox.cpp +++ b/indra/newview/llexpandabletextbox.cpp @@ -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; } diff --git a/indra/newview/llfeaturemanager.h b/indra/newview/llfeaturemanager.h index 651404d890..70c6c09e0d 100644 --- a/indra/newview/llfeaturemanager.h +++ b/indra/newview/llfeaturemanager.h @@ -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: diff --git a/indra/newview/llfloateravatarpicker.cpp b/indra/newview/llfloateravatarpicker.cpp index 2422596f60..42ef41017a 100644 --- a/indra/newview/llfloateravatarpicker.cpp +++ b/indra/newview/llfloateravatarpicker.cpp @@ -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); diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp new file mode 100644 index 0000000000..1578caa39c --- /dev/null +++ b/indra/newview/llfloateremojipicker.cpp @@ -0,0 +1,1346 @@ +/** + * @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 "llemojihelper.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 constants +static const S32 ALL_EMOJIS_GROUP_INDEX = -2; +// https://www.compart.com/en/unicode/U+1F50D +static const S32 ALL_EMOJIS_IMAGE_INDEX = 0x1F50D; +static const S32 USED_EMOJIS_GROUP_INDEX = -1; +// https://www.compart.com/en/unicode/U+23F2 +static const S32 USED_EMOJIS_IMAGE_INDEX = 0x23F2; +// https://www.compart.com/en/unicode/U+1F6D1 +static const S32 EMPTY_LIST_IMAGE_INDEX = 0x1F6D1; +// The following categories should follow the required alphabetic order +static const std::string RECENTLY_USED_CATEGORY = "1 recently used"; +static const std::string FREQUENTLY_USED_CATEGORY = "2 frequently used"; + +// Floater state related variables +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 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 + } + + virtual void updatePanel(BOOL allow_modify) override {} + +private: + const LLWString mText; +}; + +class LLEmojiGridIcon : public LLScrollingPanel +{ +public: + LLEmojiGridIcon( + const LLPanel::Params& panel_params + , const LLEmojiSearchResult& emoji) + : LLScrollingPanel(panel_params) + , mData(emoji) + , mText(LLWString(1, emoji.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 + } + + virtual void updatePanel(BOOL allow_modify) override {} + + const LLEmojiSearchResult& getData() const { return mData; } + LLWString getText() const { return mText; } + +private: + const LLEmojiSearchResult mData; + const LLWString mText; +}; + +class LLEmojiPreviewPanel : public LLPanel +{ +public: + LLEmojiPreviewPanel() + : LLPanel() + { + } + + void setIcon(const LLEmojiGridIcon* icon) + { + if (icon) + { + setData(icon->getData().Character, icon->getData().String, icon->getData().Begin, icon->getData().End); + } + else + { + setData(0, LLStringUtil::null, 0, 0); + } + } + + void setData(llwchar emoji, std::string title, size_t begin, size_t end) + { + mWStr = LLWString(1, emoji); + mEmoji = emoji; + mTitle = title; + mBegin = begin; + mEnd = end; + } + + virtual void draw() override + { + LLPanel::draw(); + + 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; + drawName(iconWidth, centerY, max_pixels, textColor); + } + +protected: + void drawIcon(F32 x, F32 y, S32 max_pixels) + { + LLFontGL::getFontEmojiHuge()->render( + mWStr, // 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 + } + + void drawName(F32 x, F32 y, S32 max_pixels, LLColor4& color) + { + F32 x0 = x; + F32 x1 = max_pixels; + LLFontGL* font = LLFontGL::getFontEmoji(); + if (mBegin) + { + std::string text = mTitle.substr(0, mBegin); + font->renderUTF8( + text, // text + 0, // begin_offset + x0, // x + y, // y + color, // color + LLFontGL::LEFT, // halign + LLFontGL::VCENTER, // valign + LLFontGL::NORMAL, // style + LLFontGL::DROP_SHADOW_SOFT, // shadow + text.size(), // max_chars + x1); // max_pixels + F32 dx = font->getWidthF32(text); + x0 += dx; + x1 -= dx; + } + if (x1 > 0 && mEnd > mBegin) + { + std::string text = mTitle.substr(mBegin, mEnd - mBegin); + font->renderUTF8( + text, // text + 0, // begin_offset + x0, // x + y, // y + LLColor4::yellow6, // color + LLFontGL::LEFT, // halign + LLFontGL::VCENTER, // valign + LLFontGL::NORMAL, // style + LLFontGL::DROP_SHADOW_SOFT, // shadow + text.size(), // max_chars + x1); // max_pixels + F32 dx = font->getWidthF32(text); + x0 += dx; + x1 -= dx; + } + if (x1 > 0 && mEnd < mTitle.size()) + { + std::string text = mEnd ? mTitle.substr(mEnd) : mTitle; + font->renderUTF8( + text, // text + 0, // begin_offset + x0, // x + y, // y + color, // color + LLFontGL::LEFT, // halign + LLFontGL::VCENTER, // valign + LLFontGL::NORMAL, // style + LLFontGL::DROP_SHADOW_SOFT, // shadow + text.size(), // max_chars + x1); // max_pixels + } + } + +private: + llwchar mEmoji; + LLWString mWStr; + std::string mTitle; + size_t mBegin; + size_t mEnd; +}; + +LLFloaterEmojiPicker::LLFloaterEmojiPicker(const LLSD& key) +: super(key) +{ + // This floater should hover on top of our dependent (with the dependent having the focus) + setFocusStealsFrontmost(FALSE); + setBackgroundVisible(FALSE); + setAutoFocus(FALSE); + + loadState(); +} + +BOOL LLFloaterEmojiPicker::postBuild() +{ + mGroups = getChild<LLPanel>("Groups"); + mBadge = getChild<LLPanel>("Badge"); + mEmojiScroll = getChild<LLScrollContainer>("EmojiGridContainer"); + mEmojiGrid = getChild<LLScrollingPanelList>("EmojiGrid"); + mDummy = getChild<LLTextBox>("Dummy"); + + mPreview = new LLEmojiPreviewPanel(); + mPreview->setVisible(FALSE); + addChild(mPreview); + + return LLFloater::postBuild(); +} + +void LLFloaterEmojiPicker::onOpen(const LLSD& key) +{ + mHint = key["hint"].asString(); + + LLEmojiHelper::instance().setIsHideDisabled(mHint.empty()); + mFilterPattern = mHint; + + initialize(); + + gFloaterView->adjustToFitScreen(this, FALSE); +} + +void LLFloaterEmojiPicker::dirtyRect() +{ + super::dirtyRect(); + + if (!mPreview) + 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 && mEmojiGrid) + { + S32 outer_width = mEmojiScroll->getRect().getWidth(); + S32 inner_width = mEmojiGrid->getRect().getWidth(); + if (outer_width != inner_width) + { + resizeGroupButtons(); + fillEmojis(true); + } + } +} + +void LLFloaterEmojiPicker::initialize() +{ + S32 groupIndex = mSelectedGroupIndex && mSelectedGroupIndex <= mFilteredEmojiGroups.size() ? + mFilteredEmojiGroups[mSelectedGroupIndex - 1] : ALL_EMOJIS_GROUP_INDEX; + + fillGroups(); + + if (mFilteredEmojis.empty()) + { + if (!mHint.empty()) + { + hideFloater(); + return; + } + + mGroups->setVisible(FALSE); + mFocusedIconRow = -1; + mFocusedIconCol = -1; + mFocusedIcon = nullptr; + mHoveredIcon = nullptr; + mEmojiScroll->goToTop(); + mEmojiGrid->clearPanels(); + + if (mFilterPattern.empty()) + { + showPreview(false); + } + else + { + const std::string prompt("No emoji found for "); + std::string title(prompt + '"' + mFilterPattern.substr(1) + '"'); + mPreview->setData(EMPTY_LIST_IMAGE_INDEX, title, prompt.size() + 1, title.size() - 1); + showPreview(true); + } + return; + } + + mGroups->setVisible(TRUE); + mPreview->setIcon(nullptr); + showPreview(true); + + mSelectedGroupIndex = groupIndex == ALL_EMOJIS_GROUP_INDEX ? 0 : + (1 + std::distance(mFilteredEmojiGroups.begin(), + std::find(mFilteredEmojiGroups.begin(), mFilteredEmojiGroups.end(), groupIndex))) % + (1 + mFilteredEmojiGroups.size()); + + mGroupButtons[mSelectedGroupIndex]->setToggleState(TRUE); + mGroupButtons[mSelectedGroupIndex]->setUseFontColor(TRUE); + + fillEmojis(); +} + +void LLFloaterEmojiPicker::fillGroups() +{ + // Do not use deleteAllChildren() because mBadge shouldn't be removed + for (LLButton* button : mGroupButtons) + { + mGroups->removeChild(button); + } + mFilteredEmojiGroups.clear(); + mFilteredEmojis.clear(); + mGroupButtons.clear(); + + LLButton::Params params; + params.font = LLFontGL::getFontEmoji(); + + LLRect rect; + rect.mTop = mGroups->getRect().getHeight(); + rect.mBottom = mBadge->getRect().getHeight(); + + // Create button for "All categories" + createGroupButton(params, rect, ALL_EMOJIS_IMAGE_INDEX); + + // Create group and button for "Recently used" and/or "Frequently used" + if (!sRecentlyUsed.empty() || !sFrequentlyUsed.empty()) + { + std::map<std::string, std::vector<LLEmojiSearchResult>> cats; + fillCategoryRecentlyUsed(cats); + fillCategoryFrequentlyUsed(cats); + + if (!cats.empty()) + { + mFilteredEmojiGroups.push_back(USED_EMOJIS_GROUP_INDEX); + mFilteredEmojis.emplace_back(cats); + createGroupButton(params, rect, USED_EMOJIS_IMAGE_INDEX); + } + } + + const std::vector<LLEmojiGroup>& groups = LLEmojiDictionary::instance().getGroups(); + + // List all categories in the dictionary + for (U32 i = 0; i < groups.size(); ++i) + { + std::map<std::string, std::vector<LLEmojiSearchResult>> cats; + + fillGroupEmojis(cats, i); + + if (!cats.empty()) + { + mFilteredEmojiGroups.push_back(i); + mFilteredEmojis.emplace_back(cats); + createGroupButton(params, rect, groups[i].Character); + } + } + + resizeGroupButtons(); +} + +void LLFloaterEmojiPicker::fillCategoryRecentlyUsed(std::map<std::string, std::vector<LLEmojiSearchResult>>& cats) +{ + if (sRecentlyUsed.empty()) + return; + + std::vector<LLEmojiSearchResult> emojis; + + // In case of empty mFilterPattern we'd use sRecentlyUsed directly + if (!mFilterPattern.empty()) + { + // List all emojis in "Recently used" + const LLEmojiDictionary::emoji2descr_map_t& emoji2descr = LLEmojiDictionary::instance().getEmoji2Descr(); + std::size_t begin, end; + for (llwchar emoji : sRecentlyUsed) + { + auto e2d = emoji2descr.find(emoji); + if (e2d != emoji2descr.end() && !e2d->second->ShortCodes.empty()) + { + const std::string shortcode(e2d->second->ShortCodes.front()); + if (LLEmojiDictionary::searchInShortCode(begin, end, shortcode, mFilterPattern)) + { + emojis.emplace_back(emoji, shortcode, begin, end); + } + } + } + if (emojis.empty()) + return; + } + + cats.emplace(std::make_pair(RECENTLY_USED_CATEGORY, emojis)); +} + +void LLFloaterEmojiPicker::fillCategoryFrequentlyUsed(std::map<std::string, std::vector<LLEmojiSearchResult>>& cats) +{ + if (sFrequentlyUsed.empty()) + return; + + std::vector<LLEmojiSearchResult> emojis; + + // In case of empty mFilterPattern we'd use sFrequentlyUsed directly + if (!mFilterPattern.empty()) + { + // List all emojis in "Frequently used" + const LLEmojiDictionary::emoji2descr_map_t& emoji2descr = LLEmojiDictionary::instance().getEmoji2Descr(); + std::size_t begin, end; + for (const auto& emoji : sFrequentlyUsed) + { + auto e2d = emoji2descr.find(emoji.first); + if (e2d != emoji2descr.end() && !e2d->second->ShortCodes.empty()) + { + const std::string shortcode(e2d->second->ShortCodes.front()); + if (LLEmojiDictionary::searchInShortCode(begin, end, shortcode, mFilterPattern)) + { + emojis.emplace_back(emoji.first, shortcode, begin, end); + } + } + } + if (emojis.empty()) + return; + } + + cats.emplace(std::make_pair(FREQUENTLY_USED_CATEGORY, emojis)); +} + +void LLFloaterEmojiPicker::fillGroupEmojis(std::map<std::string, std::vector<LLEmojiSearchResult>>& cats, U32 index) +{ + const std::vector<LLEmojiGroup>& groups = LLEmojiDictionary::instance().getGroups(); + const LLEmojiDictionary::cat2descrs_map_t& category2Descr = LLEmojiDictionary::instance().getCategory2Descrs(); + + for (const std::string& category : groups[index].Categories) + { + const LLEmojiDictionary::cat2descrs_map_t::const_iterator& c2d = category2Descr.find(category); + if (c2d == category2Descr.end()) + continue; + + std::vector<LLEmojiSearchResult> emojis; + + // In case of empty mFilterPattern we'd use category2Descr directly + if (!mFilterPattern.empty()) + { + // List all emojis in category + std::size_t begin, end; + for (const LLEmojiDescriptor* descr : c2d->second) + { + if (!descr->ShortCodes.empty()) + { + const std::string shortcode(descr->ShortCodes.front()); + if (LLEmojiDictionary::searchInShortCode(begin, end, shortcode, mFilterPattern)) + { + emojis.emplace_back(descr->Character, shortcode, begin, end); + } + } + } + if (emojis.empty()) + continue; + } + + cats.emplace(std::make_pair(category, emojis)); + } +} + +void LLFloaterEmojiPicker::createGroupButton(LLButton::Params& params, const LLRect& rect, llwchar emoji) +{ + 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->setTabStop(FALSE); + button->setLabel(LLUIString(LLWString(1, emoji))); + button->setUseFontColor(FALSE); + + mGroupButtons.push_back(button); + mGroups->addChild(button); +} + +void LLFloaterEmojiPicker::resizeGroupButtons() +{ + U32 groupCount = (U32)mGroupButtons.size(); + if (!groupCount) + return; + + S32 totalWidth = mGroups->getRect().getWidth(); + S32 badgeWidth = totalWidth / groupCount; + S32 leftOffset = (totalWidth - badgeWidth * groupCount) / 2; + + for (U32 i = 0; i < groupCount; ++i) + { + LLRect rect = mGroupButtons[i]->getRect(); + rect.mLeft = leftOffset + badgeWidth * i; + rect.mRight = rect.mLeft + badgeWidth; + mGroupButtons[i]->setRect(rect); + } + + LLRect rect = mBadge->getRect(); + rect.mLeft = leftOffset + badgeWidth * mSelectedGroupIndex; + rect.mRight = rect.mLeft + badgeWidth; + mBadge->setRect(rect); +} + +void LLFloaterEmojiPicker::selectEmojiGroup(U32 index) +{ + if (index == mSelectedGroupIndex || index >= mGroupButtons.size()) + return; + + if (mSelectedGroupIndex < mGroupButtons.size()) + { + mGroupButtons[mSelectedGroupIndex]->setUseFontColor(FALSE); + mGroupButtons[mSelectedGroupIndex]->setToggleState(FALSE); + } + + mSelectedGroupIndex = index; + mGroupButtons[mSelectedGroupIndex]->setToggleState(TRUE); + mGroupButtons[mSelectedGroupIndex]->setUseFontColor(TRUE); + + LLButton* button = mGroupButtons[mSelectedGroupIndex]; + LLRect rect = mBadge->getRect(); + rect.mLeft = button->getRect().mLeft; + rect.mRight = button->getRect().mRight; + mBadge->setRect(rect); + + fillEmojis(); +} + +void LLFloaterEmojiPicker::fillEmojis(bool fromResize) +{ + S32 scrollbar_size = mEmojiScroll->getSize(); + if (scrollbar_size < 0) + { + static LLUICachedControl<S32> scrollbar_size_control("UIScrollbarSize", 0); + scrollbar_size = scrollbar_size_control; + } + + const S32 scroll_width = mEmojiScroll->getRect().getWidth(); + const S32 client_width = scroll_width - scrollbar_size - mEmojiScroll->getBorderWidth() * 2; + const S32 grid_padding = mEmojiGrid->getPadding(); + const S32 icon_spacing = mEmojiGrid->getSpacing(); + const S32 row_width = client_width - grid_padding * 2; + const S32 icon_size = 28; // icon width and height + const S32 max_icons = llmax(1, (row_width + icon_spacing) / (icon_size + icon_spacing)); + + // Optimization: don't rearrange for different widths with the same maxIcons + if (fromResize && (max_icons == mRecentMaxIcons)) + return; + + mRecentMaxIcons = max_icons; + + mFocusedIconRow = 0; + mFocusedIconCol = 0; + mFocusedIcon = nullptr; + mHoveredIcon = nullptr; + mEmojiScroll->goToTop(); + mEmojiGrid->clearPanels(); + mPreview->setIcon(nullptr); + + if (mEmojiGrid->getRect().getWidth() != client_width) + { + LLRect rect = mEmojiGrid->getRect(); + rect.mRight = rect.mLeft + client_width; + mEmojiGrid->setRect(rect); + } + + LLPanel::Params row_panel_params; + row_panel_params.rect = LLRect(0, icon_size, row_width, 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 = icon_spacing; + + LLPanel::Params icon_params; + LLRect icon_rect(0, icon_size, icon_size, 0); + + static LLColor4 default_color(0.75f, 0.75f, 0.75f, 1.0f); + LLColor4 bg_color = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", default_color); + + if (!mSelectedGroupIndex) + { + // List all groups + for (const auto& group : mFilteredEmojis) + { + // List all categories in the group + for (const auto& category : group) + { + // List all emojis in the category + fillEmojisCategory(category.second, category.first, row_panel_params, + row_list_params, icon_params, icon_rect, max_icons, bg_color); + } + } + } + else + { + // List all categories in the selected group + const auto& group = mFilteredEmojis[mSelectedGroupIndex - 1]; + for (const auto& category : group) + { + // List all emojis in the category + fillEmojisCategory(category.second, category.first, row_panel_params, + row_list_params, icon_params, icon_rect, max_icons, bg_color); + } + } + + if (mEmojiGrid->getPanelList().empty()) + { + showPreview(false); + mFocusedIconRow = -1; + mFocusedIconCol = -1; + if (!mHint.empty()) + { + hideFloater(); + } + } + else + { + showPreview(true); + mFocusedIconRow = 0; + mFocusedIconCol = 0; + moveFocusedIconNext(); + } +} + +void LLFloaterEmojiPicker::fillEmojisCategory(const std::vector<LLEmojiSearchResult>& emojis, + const std::string& category, const LLPanel::Params& row_panel_params, const LLUICtrl::Params& row_list_params, + const LLPanel::Params& icon_params, const LLRect& icon_rect, S32 max_icons, const LLColor4& bg) +{ + // Place the category title + std::string title = + category == RECENTLY_USED_CATEGORY ? getString("title_for_recently_used") : + category == FREQUENTLY_USED_CATEGORY ? getString("title_for_frequently_used") : + isupper(category.front()) ? category : LLStringUtil::capitalize(category); + LLEmojiGridDivider* div = new LLEmojiGridDivider(row_panel_params, title); + mEmojiGrid->addPanel(div, true); + + int icon_index = 0; + LLEmojiGridRow* row = nullptr; + + if (mFilterPattern.empty()) + { + const LLEmojiDictionary::emoji2descr_map_t& emoji2descr = LLEmojiDictionary::instance().getEmoji2Descr(); + LLEmojiSearchResult emoji { 0, "", 0, 0 }; + if (category == RECENTLY_USED_CATEGORY) + { + for (llwchar code : sRecentlyUsed) + { + const LLEmojiDictionary::emoji2descr_map_t::const_iterator& e2d = emoji2descr.find(code); + if (e2d != emoji2descr.end() && !e2d->second->ShortCodes.empty()) + { + emoji.Character = code; + emoji.String = e2d->second->ShortCodes.front(); + createEmojiIcon(emoji, category, row_panel_params, row_list_params, icon_params, + icon_rect, max_icons, bg, row, icon_index); + } + } + } + else if (category == FREQUENTLY_USED_CATEGORY) + { + for (const auto& code : sFrequentlyUsed) + { + const LLEmojiDictionary::emoji2descr_map_t::const_iterator& e2d = emoji2descr.find(code.first); + if (e2d != emoji2descr.end() && !e2d->second->ShortCodes.empty()) + { + emoji.Character = code.first; + emoji.String = e2d->second->ShortCodes.front(); + createEmojiIcon(emoji, category, row_panel_params, row_list_params, icon_params, + icon_rect, max_icons, bg, row, icon_index); + } + } + } + else + { + const LLEmojiDictionary::cat2descrs_map_t& category2Descr = LLEmojiDictionary::instance().getCategory2Descrs(); + const LLEmojiDictionary::cat2descrs_map_t::const_iterator& c2d = category2Descr.find(category); + if (c2d != category2Descr.end()) + { + for (const LLEmojiDescriptor* descr : c2d->second) + { + emoji.Character = descr->Character; + emoji.String = descr->ShortCodes.front(); + createEmojiIcon(emoji, category, row_panel_params, row_list_params, icon_params, + icon_rect, max_icons, bg, row, icon_index); + } + } + } + } + else + { + for (const LLEmojiSearchResult& emoji : emojis) + { + createEmojiIcon(emoji, category, row_panel_params, row_list_params, icon_params, + icon_rect, max_icons, bg, row, icon_index); + } + } +} + +void LLFloaterEmojiPicker::createEmojiIcon(const LLEmojiSearchResult& emoji, + const std::string& category, const LLPanel::Params& row_panel_params, const LLUICtrl::Params& row_list_params, + const LLPanel::Params& icon_params, const LLRect& icon_rect, S32 max_icons, const LLColor4& bg, + LLEmojiGridRow*& row, int& icon_index) +{ + // Place a new row each (max_icons) icons + if (!(icon_index % max_icons)) + { + row = new LLEmojiGridRow(row_panel_params, *(const LLScrollingPanelList::Params*)&row_list_params); + mEmojiGrid->addPanel(row, true); + } + + // Place a new icon to the current row + LLEmojiGridIcon* icon = new LLEmojiGridIcon(icon_params, emoji); + 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(bg); + icon->setBackgroundOpaque(1); + icon->setRect(icon_rect); + row->mList->addPanel(icon, true); + + icon_index++; +} + +void LLFloaterEmojiPicker::showPreview(bool show) +{ + //mPreview->setIcon(nullptr); + mDummy->setVisible(show ? FALSE : TRUE); + mPreview->setVisible(show ? TRUE : FALSE); +} + +void LLFloaterEmojiPicker::onGroupButtonClick(LLUICtrl* ctrl) +{ + if (LLButton* button = dynamic_cast<LLButton*>(ctrl)) + { + if (button == mGroupButtons[mSelectedGroupIndex] || button->getToggleState()) + return; + + auto it = std::find(mGroupButtons.begin(), mGroupButtons.end(), button); + if (it == mGroupButtons.end()) + return; + + selectEmojiGroup(it - mGroupButtons.begin()); + } +} + +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 (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(ctrl)) + { + if (mFocusedIcon && mFocusedIcon != icon && mFocusedIcon->isBackgroundVisible()) + { + unselectGridIcon(mFocusedIcon); + } + + if (mHoveredIcon && mHoveredIcon != icon) + { + unselectGridIcon(mHoveredIcon); + } + + selectGridIcon(icon); + + mHoveredIcon = icon; + } +} + +void LLFloaterEmojiPicker::onEmojiMouseLeave(LLUICtrl* ctrl) +{ + if (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(ctrl)) + { + if (icon == mHoveredIcon) + { + if (icon != mFocusedIcon) + { + unselectGridIcon(icon); + } + mHoveredIcon = nullptr; + } + + if (!mHoveredIcon && mFocusedIcon && !mFocusedIcon->isBackgroundVisible()) + { + selectGridIcon(mFocusedIcon); + } + } +} + +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 (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(ctrl)) + { + LLSD value(wstring_to_utf8str(icon->getText())); + setValue(value); + + onCommit(); + + if (!mHint.empty() || !(gKeyboard->currentMask(TRUE) & MASK_SHIFT)) + { + hideFloater(); + } + } +} + +void LLFloaterEmojiPicker::selectFocusedIcon() +{ + if (mFocusedIcon && mFocusedIcon != mHoveredIcon) + { + unselectGridIcon(mFocusedIcon); + } + + // Both mFocusedIconRow and mFocusedIconCol should be already verified + LLEmojiGridRow* row = dynamic_cast<LLEmojiGridRow*>(mEmojiGrid->getPanelList()[mFocusedIconRow]); + mFocusedIcon = row ? dynamic_cast<LLEmojiGridIcon*>(row->mList->getPanelList()[mFocusedIconCol]) : nullptr; + + if (mFocusedIcon && !mHoveredIcon) + { + selectGridIcon(mFocusedIcon); + } +} + +bool LLFloaterEmojiPicker::moveFocusedIconUp() +{ + for (S32 i = mFocusedIconRow - 1; i >= 0; --i) + { + LLScrollingPanel* panel = mEmojiGrid->getPanelList()[i]; + LLEmojiGridRow* row = dynamic_cast<LLEmojiGridRow*>(panel); + if (row && row->mList->getPanelList().size() > mFocusedIconCol) + { + mEmojiScroll->scrollToShowRect(row->getBoundingRect()); + mFocusedIconRow = i; + selectFocusedIcon(); + return true; + } + } + + return false; +} + +bool LLFloaterEmojiPicker::moveFocusedIconDown() +{ + S32 rowCount = mEmojiGrid->getPanelList().size(); + for (S32 i = mFocusedIconRow + 1; i < rowCount; ++i) + { + LLScrollingPanel* panel = mEmojiGrid->getPanelList()[i]; + LLEmojiGridRow* row = dynamic_cast<LLEmojiGridRow*>(panel); + if (row && row->mList->getPanelList().size() > mFocusedIconCol) + { + mEmojiScroll->scrollToShowRect(row->getBoundingRect()); + mFocusedIconRow = i; + selectFocusedIcon(); + return true; + } + } + + return false; +} + +bool LLFloaterEmojiPicker::moveFocusedIconPrev() +{ + if (mHoveredIcon) + return false; + + if (mFocusedIconCol > 0) + { + mFocusedIconCol--; + selectFocusedIcon(); + return true; + } + + for (S32 i = mFocusedIconRow - 1; i >= 0; --i) + { + LLScrollingPanel* panel = mEmojiGrid->getPanelList()[i]; + LLEmojiGridRow* row = dynamic_cast<LLEmojiGridRow*>(panel); + if (row && row->mList->getPanelList().size()) + { + mEmojiScroll->scrollToShowRect(row->getBoundingRect()); + mFocusedIconCol = row->mList->getPanelList().size() - 1; + mFocusedIconRow = i; + selectFocusedIcon(); + return true; + } + } + + return false; +} + +bool LLFloaterEmojiPicker::moveFocusedIconNext() +{ + if (mHoveredIcon) + return false; + + LLScrollingPanel* panel = mEmojiGrid->getPanelList()[mFocusedIconRow]; + LLEmojiGridRow* row = dynamic_cast<LLEmojiGridRow*>(panel); + S32 colCount = row ? row->mList->getPanelList().size() : 0; + if (mFocusedIconCol < colCount - 1) + { + mFocusedIconCol++; + selectFocusedIcon(); + return true; + } + + S32 rowCount = mEmojiGrid->getPanelList().size(); + for (S32 i = mFocusedIconRow + 1; i < rowCount; ++i) + { + LLScrollingPanel* panel = mEmojiGrid->getPanelList()[i]; + LLEmojiGridRow* row = dynamic_cast<LLEmojiGridRow*>(panel); + if (row && row->mList->getPanelList().size()) + { + mEmojiScroll->scrollToShowRect(row->getBoundingRect()); + mFocusedIconCol = 0; + mFocusedIconRow = i; + selectFocusedIcon(); + return true; + } + } + + return false; +} + +void LLFloaterEmojiPicker::selectGridIcon(LLEmojiGridIcon* icon) +{ + icon->setBackgroundVisible(TRUE); + mPreview->setIcon(icon); +} + +void LLFloaterEmojiPicker::unselectGridIcon(LLEmojiGridIcon* icon) +{ + icon->setBackgroundVisible(FALSE); + mPreview->setIcon(nullptr); +} + +// virtual +BOOL LLFloaterEmojiPicker::handleKey(KEY key, MASK mask, BOOL called_from_parent) +{ + if (mask == MASK_NONE) + { + switch (key) + { + case KEY_UP: + moveFocusedIconUp(); + return TRUE; + case KEY_DOWN: + moveFocusedIconDown(); + return TRUE; + case KEY_LEFT: + moveFocusedIconPrev(); + return TRUE; + case KEY_RIGHT: + moveFocusedIconNext(); + return TRUE; + case KEY_ESCAPE: + hideFloater(); + return TRUE; + } + } + + if (mask == MASK_ALT) + { + switch (key) + { + case KEY_LEFT: + selectEmojiGroup((mSelectedGroupIndex + mFilteredEmojis.size()) % mGroupButtons.size()); + return TRUE; + case KEY_RIGHT: + selectEmojiGroup((mSelectedGroupIndex + 1) % mGroupButtons.size()); + return TRUE; + } + } + + if (key == KEY_RETURN) + { + U64 time = totalTime(); + // <Shift+Return> comes twice for unknown reason + if (mFocusedIcon && (time - mRecentReturnPressedMs > 100000)) // Min interval 0.1 sec. + { + onEmojiMouseDown(mFocusedIcon); + onEmojiMouseUp(mFocusedIcon); + } + mRecentReturnPressedMs = time; + return TRUE; + } + + if (mHint.empty()) + { + if (key >= 0x20 && key < 0x80) + { + if (!mEmojiGrid->getPanelList().empty()) + { + if (mFilterPattern.empty()) + { + mFilterPattern = ":"; + } + mFilterPattern += (char)key; + initialize(); + } + return TRUE; + } + else if (key == KEY_BACKSPACE) + { + if (!mFilterPattern.empty()) + { + mFilterPattern.pop_back(); + if (mFilterPattern == ":") + { + mFilterPattern.clear(); + } + initialize(); + } + return TRUE; + } + } + + return super::handleKey(key, mask, called_from_parent); +} + +// virtual +void LLFloaterEmojiPicker::goneFromFront() +{ + hideFloater(); +} + +void LLFloaterEmojiPicker::hideFloater() const +{ + LLEmojiHelper::instance().hideHelper(nullptr, true); +} + +// 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()) + { + // Insert before others with count == 1 + while (itf != sFrequentlyUsed.begin()) + { + auto prior = itf; + prior--; + if (prior->second > 1) + break; + itf = prior; + } + sFrequentlyUsed.insert(itf, std::make_pair(emoji, 1)); + } +} + +// static +void LLFloaterEmojiPicker::loadState() +{ + if (!sStateFileName.empty()) + return; // Already loaded + + sStateFileName = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "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; + } + + // 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 - 1; + 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 (!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); +} diff --git a/indra/newview/llfloateremojipicker.h b/indra/newview/llfloateremojipicker.h new file mode 100644 index 0000000000..5d0402ca83 --- /dev/null +++ b/indra/newview/llfloateremojipicker.h @@ -0,0 +1,122 @@ +/** + * @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" + +class LLEmojiGridRow; +class LLEmojiGridIcon; +struct LLEmojiDescriptor; +struct LLEmojiSearchResult; + +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; + + LLFloaterEmojiPicker(const LLSD& key); + + virtual BOOL postBuild() override; + virtual void dirtyRect() override; + virtual void goneFromFront() override; + + void hideFloater() const; + + static std::list<llwchar>& getRecentlyUsed(); + static void onEmojiUsed(llwchar emoji); + + static void loadState(); + static void saveState(); + +private: + void initialize(); + void fillGroups(); + void fillCategoryRecentlyUsed(std::map<std::string, std::vector<LLEmojiSearchResult>>& cats); + void fillCategoryFrequentlyUsed(std::map<std::string, std::vector<LLEmojiSearchResult>>& cats); + void fillGroupEmojis(std::map<std::string, std::vector<LLEmojiSearchResult>>& cats, U32 index); + void createGroupButton(LLButton::Params& params, const LLRect& rect, llwchar emoji); + void resizeGroupButtons(); + void selectEmojiGroup(U32 index); + void fillEmojis(bool fromResize = false); + void fillEmojisCategory(const std::vector<LLEmojiSearchResult>& emojis, + const std::string& category, const LLPanel::Params& row_panel_params, const LLUICtrl::Params& row_list_params, + const LLPanel::Params& icon_params, const LLRect& icon_rect, S32 max_icons, const LLColor4& bg); + void createEmojiIcon(const LLEmojiSearchResult& emoji, + const std::string& category, const LLPanel::Params& row_panel_params, const LLUICtrl::Params& row_list_params, + const LLPanel::Params& icon_params, const LLRect& icon_rect, S32 max_icons, const LLColor4& bg, + LLEmojiGridRow*& row, int& icon_index); + void showPreview(bool show); + + void onGroupButtonClick(LLUICtrl* ctrl); + 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 selectFocusedIcon(); + bool moveFocusedIconUp(); + bool moveFocusedIconDown(); + bool moveFocusedIconPrev(); + bool moveFocusedIconNext(); + + void selectGridIcon(LLEmojiGridIcon* icon); + void unselectGridIcon(LLEmojiGridIcon* icon); + + void onOpen(const LLSD& key) override; + virtual BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent) override; + + class LLPanel* mGroups { nullptr }; + class LLPanel* mBadge { nullptr }; + class LLScrollContainer* mEmojiScroll { nullptr }; + class LLScrollingPanelList* mEmojiGrid { nullptr }; + class LLEmojiPreviewPanel* mPreview { nullptr }; + class LLTextBox* mDummy { nullptr }; + + std::vector<S32> mFilteredEmojiGroups; + std::vector<std::map<std::string, std::vector<LLEmojiSearchResult>>> mFilteredEmojis; + std::vector<class LLButton*> mGroupButtons; + + std::string mHint; + std::string mFilterPattern; + U32 mSelectedGroupIndex { 0 }; + S32 mRecentMaxIcons { 0 }; + S32 mFocusedIconRow { 0 }; + S32 mFocusedIconCol { 0 }; + LLEmojiGridIcon* mFocusedIcon { nullptr }; + LLEmojiGridIcon* mHoveredIcon { nullptr }; + + U64 mRecentReturnPressedMs { 0 }; +}; + +#endif diff --git a/indra/newview/llfloaterimnearbychat.cpp b/indra/newview/llfloaterimnearbychat.cpp index bb4cc9bca3..40bdf14deb 100644 --- a/indra/newview/llfloaterimnearbychat.cpp +++ b/indra/newview/llfloaterimnearbychat.cpp @@ -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; diff --git a/indra/newview/llfloaterimsession.cpp b/indra/newview/llfloaterimsession.cpp index ee9dc35283..ed2a2807b5 100644 --- a/indra/newview/llfloaterimsession.cpp +++ b/indra/newview/llfloaterimsession.cpp @@ -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); diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index 0b0dce29fb..24cc398f3b 100644 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -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 "llfloaterimnearbychat.h" +#include "llgroupiconctrl.h" #include "lllayoutstack.h" +#include "llpanelemojicomplete.h" #include "lltoolbarview.h" -#include "llfloaterimnearbychat.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->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiRecentPanelToggleBtnClicked(); }); + + mEmojiRecentPanel = getChild<LLLayoutPanel>("emoji_recent_layout_panel"); + mEmojiRecentPanel->setVisible(false); + + mEmojiRecentEmptyText = getChild<LLTextBox>("emoji_recent_empty_text"); + mEmojiRecentEmptyText->setToolTip(mEmojiRecentEmptyText->getText()); + mEmojiRecentEmptyText->setVisible(false); + + mEmojiRecentIconsCtrl = getChild<LLPanelEmojiComplete>("emoji_recent_icons_ctrl"); + mEmojiRecentIconsCtrl->setCommitCallback([this](LLUICtrl*, const LLSD& value) { onRecentEmojiPicked(value); }); + mEmojiRecentIconsCtrl->setVisible(false); + + mEmojiPickerShowBtn = getChild<LLButton>("emoji_picker_show_btn"); + mEmojiPickerShowBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiPickerShowBtnClicked(); }); + 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,130 @@ void LLFloaterIMSessionTab::onInputEditorClicked() gToolBarView->flashCommand(LLCommandId("chat"), false); } +void LLFloaterIMSessionTab::onEmojiRecentPanelToggleBtnClicked() +{ + BOOL show = mEmojiRecentPanel->getVisible() ? FALSE : TRUE; + if (show) + { + initEmojiRecentPanel(); + } + + mEmojiRecentPanel->setVisible(show); + mInputEditor->setFocus(TRUE); +} + +void LLFloaterIMSessionTab::onEmojiPickerShowBtnClicked() +{ + mInputEditor->setFocus(TRUE); + mInputEditor->showEmojiHelper(); +} + +void LLFloaterIMSessionTab::initEmojiRecentPanel() +{ + std::list<llwchar>& recentlyUsed = LLFloaterEmojiPicker::getRecentlyUsed(); + if (recentlyUsed.empty()) + { + mEmojiRecentEmptyText->setVisible(TRUE); + mEmojiRecentIconsCtrl->setVisible(FALSE); + } + else + { + LLWString emojis; + for (llwchar emoji : recentlyUsed) + { + emojis += emoji; + } + mEmojiRecentIconsCtrl->setEmojis(emojis); + mEmojiRecentEmptyText->setVisible(FALSE); + mEmojiRecentIconsCtrl->setVisible(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::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"); - - if (mChatHistory) - { - mChatHistory->appendMessage(chat, chat_args); - } - } + tmp_chat.mFromName = chat.mFromName; + + 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::saveState(); + + if (mEmojiRecentPanel->getVisible()) + { + initEmojiRecentPanel(); + } } static LLTrace::BlockTimerStatHandle FTM_BUILD_CONVERSATION_VIEW_PARTICIPANT("Build Conversation View"); @@ -502,10 +604,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 +627,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 +680,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 +740,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 +868,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 +885,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 +919,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 +948,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 +994,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 +1039,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 +1069,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); @@ -995,8 +1097,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"); @@ -1086,8 +1188,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 @@ -1099,8 +1201,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 @@ -1111,8 +1213,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 @@ -1122,19 +1224,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() @@ -1142,8 +1244,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()) ; } diff --git a/indra/newview/llfloaterimsessiontab.h b/indra/newview/llfloaterimsessiontab.h index d478922617..cc985b2753 100644 --- a/indra/newview/llfloaterimsessiontab.h +++ b/indra/newview/llfloaterimsessiontab.h @@ -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; + LLTextBox* 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* mEmojiPickerShowBtn; 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,14 @@ private: void onInputEditorClicked(); + void onEmojiRecentPanelToggleBtnClicked(); + void onEmojiPickerShowBtnClicked(); + void initEmojiRecentPanel(); + void onRecentEmojiPicked(const LLSD& value); + bool checkIfTornOff(); - bool mIsHostAttached; - bool mHasVisibleBeenInitialized; + bool mIsHostAttached; + bool mHasVisibleBeenInitialized; LLTimer* mRefreshTimer; ///< Defines the rate at which refresh() is called. diff --git a/indra/newview/llfloateruipreview.cpp b/indra/newview/llfloateruipreview.cpp index 67a205417e..db69c3e2c3 100644 --- a/indra/newview/llfloateruipreview.cpp +++ b/indra/newview/llfloateruipreview.cpp @@ -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); } 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); // 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); 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); // draw element LLUI::translate(0,-mSpacing-viewp->getRect().getHeight()); // skip spacing distance + height diff --git a/indra/newview/llfriendcard.h b/indra/newview/llfriendcard.h index f5679d7d85..ef0dda7949 100644 --- a/indra/newview/llfriendcard.h +++ b/indra/newview/llfriendcard.h @@ -55,7 +55,7 @@ public: }; // LLFriendObserver implementation - void changed(U32 mask) + void changed(U32 mask) override { onFriendListUpdate(mask); } diff --git a/indra/newview/llgesturemgr.h b/indra/newview/llgesturemgr.h index 7c8e8279c2..7bb60f00e2 100644 --- a/indra/newview/llgesturemgr.h +++ b/indra/newview/llgesturemgr.h @@ -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, diff --git a/indra/newview/llhudrender.cpp b/indra/newview/llhudrender.cpp index dff310ecf9..c1f17c9d33 100644 --- a/indra/newview/llhudrender.cpp +++ b/indra/newview/llhudrender.cpp @@ -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(); diff --git a/indra/newview/llimagefiltersmanager.h b/indra/newview/llimagefiltersmanager.h index d06212d85a..05d1806da4 100644 --- a/indra/newview/llimagefiltersmanager.h +++ b/indra/newview/llimagefiltersmanager.h @@ -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; diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h index 946eb02f26..bace97d37a 100644 --- a/indra/newview/llimview.h +++ b/indra/newview/llimview.h @@ -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); diff --git a/indra/newview/llinventorygallery.cpp b/indra/newview/llinventorygallery.cpp index 68581d04eb..d4ca58f778 100644 --- a/indra/newview/llinventorygallery.cpp +++ b/indra/newview/llinventorygallery.cpp @@ -2402,11 +2402,17 @@ void LLInventoryGallery::startDrag() { std::vector<EDragAndDropType> types; uuid_vec_t ids; + LLToolDragAndDrop::ESource src = LLToolDragAndDrop::SOURCE_AGENT; for (LLUUID& selected_id : mSelectedItemIDs) { const LLInventoryItem* item = gInventory.getItem(selected_id); if (item) { + if (item->getPermissions().getOwner() == ALEXANDRIA_LINDEN_ID) + { + src = LLToolDragAndDrop::SOURCE_LIBRARY; + } + EDragAndDropType type = LLViewerAssetType::lookupDragAndDropType(item->getType()); types.push_back(type); ids.push_back(selected_id); @@ -2416,12 +2422,17 @@ void LLInventoryGallery::startDrag() if (cat && gInventory.isObjectDescendentOf(selected_id, gInventory.getRootFolderID()) && !LLFolderType::lookupIsProtectedType((cat)->getPreferredType())) { + if (cat->getOwnerID() == ALEXANDRIA_LINDEN_ID) + { + src = LLToolDragAndDrop::SOURCE_LIBRARY; + } + EDragAndDropType type = LLViewerAssetType::lookupDragAndDropType(cat->getType()); types.push_back(type); ids.push_back(selected_id); } } - LLToolDragAndDrop::getInstance()->beginMultiDrag(types, ids, LLToolDragAndDrop::SOURCE_AGENT); + LLToolDragAndDrop::getInstance()->beginMultiDrag(types, ids, src); } bool LLInventoryGallery::areViewsInitialized() diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp index 292ddb765f..1f5974c74d 100644 --- a/indra/newview/llmaterialeditor.cpp +++ b/indra/newview/llmaterialeditor.cpp @@ -2228,7 +2228,7 @@ bool LLMaterialEditor::canModifyObjectsMaterial() LLSelectedTEGetMatData func(true); LLPermissions permissions; LLViewerInventoryItem* item_out; - return can_use_objects_material(func, std::vector({PERM_MODIFY}), ItemSource::OBJECT, permissions, item_out); + return can_use_objects_material(func, std::vector<PermissionBit>({PERM_MODIFY}), ItemSource::OBJECT, permissions, item_out); } bool LLMaterialEditor::canSaveObjectsMaterial() @@ -2236,7 +2236,7 @@ bool LLMaterialEditor::canSaveObjectsMaterial() LLSelectedTEGetMatData func(true); LLPermissions permissions; LLViewerInventoryItem* item_out; - return can_use_objects_material(func, std::vector({PERM_COPY, PERM_MODIFY}), ItemSource::AGENT, permissions, item_out); + return can_use_objects_material(func, std::vector<PermissionBit>({PERM_COPY, PERM_MODIFY}), ItemSource::AGENT, permissions, item_out); } bool LLMaterialEditor::canClipboardObjectsMaterial() @@ -2262,7 +2262,7 @@ bool LLMaterialEditor::canClipboardObjectsMaterial() LLSelectedTEGetMatData func(true); LLPermissions permissions; LLViewerInventoryItem* item_out; - return can_use_objects_material(func, std::vector({PERM_COPY, PERM_MODIFY, PERM_TRANSFER}), ItemSource::OBJECT, permissions, item_out); + return can_use_objects_material(func, std::vector<PermissionBit>({PERM_COPY, PERM_MODIFY, PERM_TRANSFER}), ItemSource::OBJECT, permissions, item_out); } void LLMaterialEditor::saveObjectsMaterialAs() @@ -2270,7 +2270,7 @@ void LLMaterialEditor::saveObjectsMaterialAs() LLSelectedTEGetMatData func(true); LLPermissions permissions; LLViewerInventoryItem* item = nullptr; - bool allowed = can_use_objects_material(func, std::vector({PERM_COPY, PERM_MODIFY}), ItemSource::AGENT, permissions, item); + bool allowed = can_use_objects_material(func, std::vector<PermissionBit>({PERM_COPY, PERM_MODIFY}), ItemSource::AGENT, permissions, item); if (!allowed) { LL_WARNS("MaterialEditor") << "Failed to save GLTF material from object" << LL_ENDL; diff --git a/indra/newview/llmutelist.h b/indra/newview/llmutelist.h index 14840f1b2e..dda407e708 100644 --- a/indra/newview/llmutelist.h +++ b/indra/newview/llmutelist.h @@ -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 diff --git a/indra/newview/llnavigationbar.h b/indra/newview/llnavigationbar.h index 11c671294a..4649f5bcb0 100755 --- a/indra/newview/llnavigationbar.h +++ b/indra/newview/llnavigationbar.h @@ -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(); diff --git a/indra/newview/lloutfitobserver.h b/indra/newview/lloutfitobserver.h index 2f136d48e8..56f2ceb8b1 100644 --- a/indra/newview/lloutfitobserver.h +++ b/indra/newview/lloutfitobserver.h @@ -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(); } diff --git a/indra/newview/llpanelemojicomplete.cpp b/indra/newview/llpanelemojicomplete.cpp new file mode 100644 index 0000000000..e6e3a10e13 --- /dev/null +++ b/indra/newview/llpanelemojicomplete.cpp @@ -0,0 +1,579 @@ +/** +* @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); + 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); + 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); + 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); + } + 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) + { + // In case of wheel up (clicks < 0) we shouldn't subtract more than value of mScrollPos + // Example: if mScrollPos = 0, clicks = -1 then (mScrollPos + clicks) becomes SIZE_MAX + // As a result of llclamp<size_t>() mScrollPos becomes (mTotalEmojis - mVisibleEmojis) + S32 newScrollPos = llmax(0, (S32)mScrollPos + clicks); + mScrollPos = llclamp<size_t>((size_t)newScrollPos, 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); + if (mAutoSize) + { + updateConstraints(); + } + else + { + onEmojisChanged(); + } +} + +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 ? + mEmojiHeight ? getRect().getHeight() / mEmojiHeight : 0 : + mEmojiWidth ? getRect().getWidth() / mEmojiWidth : 0; + } + + 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); + } +} + +// ============================================================================ diff --git a/indra/newview/llpanelemojicomplete.h b/indra/newview/llpanelemojicomplete.h new file mode 100644 index 0000000000..36a965202e --- /dev/null +++ b/indra/newview/llpanelemojicomplete.h @@ -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; +}; + +// ============================================================================ diff --git a/indra/newview/llpaneltopinfobar.h b/indra/newview/llpaneltopinfobar.h index 78dd997029..b6c263e331 100644 --- a/indra/newview/llpaneltopinfobar.h +++ b/indra/newview/llpaneltopinfobar.h @@ -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. diff --git a/indra/newview/llpathfindingpathtool.h b/indra/newview/llpathfindingpathtool.h index 88cb3a15f8..f98624e30d 100644 --- a/indra/newview/llpathfindingpathtool.h +++ b/indra/newview/llpathfindingpathtool.h @@ -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; diff --git a/indra/newview/llproductinforequest.h b/indra/newview/llproductinforequest.h index d1036374e8..0b94c39d11 100644 --- a/indra/newview/llproductinforequest.h +++ b/indra/newview/llproductinforequest.h @@ -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; diff --git a/indra/newview/llrecentpeople.h b/indra/newview/llrecentpeople.h index 1b322f2c0a..0c04222a9f 100644 --- a/indra/newview/llrecentpeople.h +++ b/indra/newview/llrecentpeople.h @@ -106,7 +106,7 @@ public: /** * LLSimpleListener interface. */ - /*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata) override; void updateAvatarsArrivalTime(uuid_vec_t& uuids); F32 getArrivalTimeByID(const LLUUID& id); diff --git a/indra/newview/llsceneview.cpp b/indra/newview/llsceneview.cpp index 9b1d2d48c6..2643ee95f8 100644 --- a/indra/newview/llsceneview.cpp +++ b/indra/newview/llsceneview.cpp @@ -99,7 +99,6 @@ void LLSceneView::draw() std::vector<F32> physics_cost[2]; F32 total_physics[] = { 0.f, 0.f }; - LLViewerRegion* region = gAgent.getRegion(); if (region) { diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp index d172a87b1d..4745bda641 100644 --- a/indra/newview/llselectmgr.cpp +++ b/indra/newview/llselectmgr.cpp @@ -5519,9 +5519,6 @@ void LLSelectMgr::sendListToRegions(LLObjectSelectionHandle selected_handle, LLSelectNode* linkset_root = NULL; LLViewerRegion* last_region; LLViewerRegion* current_region; - -// S32 objects_sent = 0; -// S32 packets_sent = 0; S32 objects_in_this_packet = 0; bool link_operation = message_name == "ObjectLink"; @@ -5653,7 +5650,6 @@ void LLSelectMgr::sendListToRegions(LLObjectSelectionHandle selected_handle, (*pack_body)(node, user_data); // do any related logging (*log_func)(node, user_data); -// ++objects_sent; ++objects_in_this_packet; // and on to the next object @@ -5671,7 +5667,6 @@ void LLSelectMgr::sendListToRegions(LLObjectSelectionHandle selected_handle, { // otherwise send current message and start new one gMessageSystem->sendReliable( last_region->getHost()); -// packets_sent++; objects_in_this_packet = 0; gMessageSystem->newMessage(message_name.c_str()); @@ -5688,7 +5683,6 @@ void LLSelectMgr::sendListToRegions(LLObjectSelectionHandle selected_handle, { // add root instance into new message (*pack_body)(linkset_root, user_data); -// ++objects_sent; ++objects_in_this_packet; } } @@ -5702,7 +5696,6 @@ void LLSelectMgr::sendListToRegions(LLObjectSelectionHandle selected_handle, if (gMessageSystem->getCurrentSendTotal() > 0) { gMessageSystem->sendReliable( current_region->getHost()); -// packets_sent++; } else { diff --git a/indra/newview/llspeakers.h b/indra/newview/llspeakers.h index ed795b5155..22c9481687 100644 --- a/indra/newview/llspeakers.h +++ b/indra/newview/llspeakers.h @@ -338,7 +338,7 @@ class LLActiveSpeakerMgr : public LLSpeakerMgr, public LLSingleton<LLActiveSpeak LOG_CLASS(LLActiveSpeakerMgr); protected: - virtual void updateSpeakerList(); + virtual void updateSpeakerList() override; }; class LLLocalSpeakerMgr : public LLSpeakerMgr, public LLSingleton<LLLocalSpeakerMgr> @@ -347,7 +347,7 @@ class LLLocalSpeakerMgr : public LLSpeakerMgr, public LLSingleton<LLLocalSpeaker ~LLLocalSpeakerMgr (); LOG_CLASS(LLLocalSpeakerMgr); protected: - virtual void updateSpeakerList(); + virtual void updateSpeakerList() override; }; #endif // LL_LLSPEAKERS_H diff --git a/indra/newview/llspeakingindicatormanager.cpp b/indra/newview/llspeakingindicatormanager.cpp index 0111d8869c..c4c9673be3 100644 --- a/indra/newview/llspeakingindicatormanager.cpp +++ b/indra/newview/llspeakingindicatormanager.cpp @@ -53,7 +53,7 @@ class SpeakingIndicatorManager : public LLSingleton<SpeakingIndicatorManager>, L LOG_CLASS(SpeakingIndicatorManager); protected: - void cleanupSingleton(); + void cleanupSingleton() override; public: @@ -88,7 +88,7 @@ public: * So, method does not calculate difference between these list it only switches off already * switched on indicators and switches on indicators of voice channel participants */ - void onParticipantsChanged(); + void onParticipantsChanged() override; private: typedef std::set<LLUUID> speaker_ids_t; diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp index 62703e3499..84cd6e2da7 100644 --- a/indra/newview/lltextureview.cpp +++ b/indra/newview/lltextureview.cpp @@ -588,8 +588,7 @@ void LLGLTexMemBar::draw() x_right = 550.0; LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*3, text_color, LLFontGL::LEFT, LLFontGL::TOP, - LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, - &x_right, FALSE); + LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, &x_right); F32Kilobits bandwidth(LLAppViewer::getTextureFetch()->getTextureBandwidth()); F32Kilobits max_bandwidth(gSavedSettings.getF32("ThrottleBandwidthKBPS")); diff --git a/indra/newview/lltoolbrush.h b/indra/newview/lltoolbrush.h index c108d83256..6545ee3611 100644 --- a/indra/newview/lltoolbrush.h +++ b/indra/newview/lltoolbrush.h @@ -49,27 +49,27 @@ class LLToolBrushLand : public LLTool, public LLEditMenuHandler, public LLSingle public: // x,y in window coords, 0,0 = left,bot - virtual BOOL handleMouseDown( S32 x, S32 y, MASK mask ); - virtual BOOL handleMouseUp( S32 x, S32 y, MASK mask ); - virtual BOOL handleHover( S32 x, S32 y, MASK mask ); - virtual void handleSelect(); - virtual void handleDeselect(); + virtual BOOL handleMouseDown( S32 x, S32 y, MASK mask ) override; + virtual BOOL handleMouseUp( S32 x, S32 y, MASK mask ) override; + virtual BOOL handleHover( S32 x, S32 y, MASK mask ) override; + virtual void handleSelect() override; + virtual void handleDeselect() override; // isAlwaysRendered() - return true if this is a tool that should // always be rendered regardless of selection. - virtual BOOL isAlwaysRendered() { return TRUE; } + virtual BOOL isAlwaysRendered() override { return TRUE; } // Draw the area that will be affected. - virtual void render(); + virtual void render() override; // on Idle is where the land modification actually occurs static void onIdle(void* brush_tool); - void onMouseCaptureLost(); + void onMouseCaptureLost() override; void modifyLandInSelectionGlobal(); - virtual void undo(); - virtual BOOL canUndo() const { return TRUE; } + virtual void undo() override; + virtual BOOL canUndo() const override { return TRUE; } protected: void brush( void ); diff --git a/indra/newview/lltoolcomp.h b/indra/newview/lltoolcomp.h index 86506f725e..f539a045b7 100644 --- a/indra/newview/lltoolcomp.h +++ b/indra/newview/lltoolcomp.h @@ -108,11 +108,11 @@ class LLToolCompInspect : public LLToolComposite, public LLSingleton<LLToolCompI public: // Overridden from LLToolComposite - virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); - virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); - virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); - virtual BOOL handleKey(KEY key, MASK mask); - virtual void onMouseCaptureLost(); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask) override; + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask) override; + virtual BOOL handleKey(KEY key, MASK mask) override; + virtual void onMouseCaptureLost() override; void keyUp(KEY key, MASK mask); static void pickCallback(const LLPickInfo& pick_info); @@ -133,13 +133,13 @@ class LLToolCompTranslate : public LLToolComposite, public LLSingleton<LLToolCom public: // Overridden from LLToolComposite - virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); - virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); - virtual BOOL handleHover(S32 x, S32 y, MASK mask); - virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); // Returns to the default tool - virtual void render(); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask) override; + virtual BOOL handleHover(S32 x, S32 y, MASK mask) override; + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask) override; // Returns to the default tool + virtual void render() override; - virtual LLTool* getOverrideTool(MASK mask); + virtual LLTool* getOverrideTool(MASK mask) override; static void pickCallback(const LLPickInfo& pick_info); }; @@ -154,13 +154,13 @@ class LLToolCompScale : public LLToolComposite, public LLSingleton<LLToolCompSca public: // Overridden from LLToolComposite - virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); - virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); - virtual BOOL handleHover(S32 x, S32 y, MASK mask); - virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); // Returns to the default tool - virtual void render(); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask) override; + virtual BOOL handleHover(S32 x, S32 y, MASK mask) override; + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask) override; // Returns to the default tool + virtual void render() override; - virtual LLTool* getOverrideTool(MASK mask); + virtual LLTool* getOverrideTool(MASK mask) override; static void pickCallback(const LLPickInfo& pick_info); }; @@ -176,13 +176,13 @@ class LLToolCompRotate : public LLToolComposite, public LLSingleton<LLToolCompRo public: // Overridden from LLToolComposite - virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); - virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); - virtual BOOL handleHover(S32 x, S32 y, MASK mask); - virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); - virtual void render(); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask) override; + virtual BOOL handleHover(S32 x, S32 y, MASK mask) override; + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask) override; + virtual void render() override; - virtual LLTool* getOverrideTool(MASK mask); + virtual LLTool* getOverrideTool(MASK mask) override; static void pickCallback(const LLPickInfo& pick_info); @@ -199,9 +199,9 @@ class LLToolCompCreate : public LLToolComposite, public LLSingleton<LLToolCompCr public: // Overridden from LLToolComposite - virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); - virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); - virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask) override; + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask) override; static void pickCallback(const LLPickInfo& pick_info); protected: @@ -224,16 +224,16 @@ class LLToolCompGun : public LLToolComposite, public LLSingleton<LLToolCompGun> public: // Overridden from LLToolComposite - virtual BOOL handleHover(S32 x, S32 y, MASK mask); - virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); - virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); - virtual BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); - virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); - virtual BOOL handleScrollWheel(S32 x, S32 y, S32 clicks); - virtual void onMouseCaptureLost(); - virtual void handleSelect(); - virtual void handleDeselect(); - virtual LLTool* getOverrideTool(MASK mask) { return NULL; } + virtual BOOL handleHover(S32 x, S32 y, MASK mask) override; + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask) override; + virtual BOOL handleRightMouseDown(S32 x, S32 y, MASK mask) override; + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask) override; + virtual BOOL handleScrollWheel(S32 x, S32 y, S32 clicks) override; + virtual void onMouseCaptureLost() override; + virtual void handleSelect() override; + virtual void handleDeselect() override; + virtual LLTool* getOverrideTool(MASK mask) override { return NULL; } protected: LLToolGun* mGun; diff --git a/indra/newview/lltooldraganddrop.h b/indra/newview/lltooldraganddrop.h index 7bdd2d1a49..f69774952c 100644 --- a/indra/newview/lltooldraganddrop.h +++ b/indra/newview/lltooldraganddrop.h @@ -48,12 +48,12 @@ public: typedef boost::signals2::signal<void ()> enddrag_signal_t; // overridden from LLTool - virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); - virtual BOOL handleHover(S32 x, S32 y, MASK mask); - virtual BOOL handleKey(KEY key, MASK mask); - virtual BOOL handleToolTip(S32 x, S32 y, MASK mask); - virtual void onMouseCaptureLost(); - virtual void handleDeselect(); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask) override; + virtual BOOL handleHover(S32 x, S32 y, MASK mask) override; + virtual BOOL handleKey(KEY key, MASK mask) override; + virtual BOOL handleToolTip(S32 x, S32 y, MASK mask) override; + virtual void onMouseCaptureLost() override; + virtual void handleDeselect() override; void setDragStart( S32 x, S32 y ); // In screen space BOOL isOverThreshold( S32 x, S32 y ); // In screen space diff --git a/indra/newview/lltoolface.h b/indra/newview/lltoolface.h index e4b8ae12b8..7c8ff20480 100644 --- a/indra/newview/lltoolface.h +++ b/indra/newview/lltoolface.h @@ -39,11 +39,11 @@ class LLToolFace virtual ~LLToolFace(); public: - virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); - virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); - virtual void handleSelect(); - virtual void handleDeselect(); - virtual void render(); // draw face highlights + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask) override; + virtual void handleSelect() override; + virtual void handleDeselect() override; + virtual void render() override; // draw face highlights static void pickCallback(const LLPickInfo& pick_info); }; diff --git a/indra/newview/lltoolfocus.h b/indra/newview/lltoolfocus.h index ef71f9230a..54d9827ae6 100644 --- a/indra/newview/lltoolfocus.h +++ b/indra/newview/lltoolfocus.h @@ -38,16 +38,16 @@ class LLToolCamera virtual ~LLToolCamera(); public: - virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); - virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); - virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask) override; + virtual BOOL handleHover(S32 x, S32 y, MASK mask) override; - virtual void onMouseCaptureLost(); + virtual void onMouseCaptureLost() override; - virtual void handleSelect(); - virtual void handleDeselect(); + virtual void handleSelect() override; + virtual void handleDeselect() override; - virtual LLTool* getOverrideTool(MASK mask) { return NULL; } + virtual LLTool* getOverrideTool(MASK mask) override { return NULL; } void setClickPickPending() { mClickPickPending = true; } static void pickCallback(const LLPickInfo& pick_info); diff --git a/indra/newview/lltoolindividual.h b/indra/newview/lltoolindividual.h index e7c2060fba..89dd9d9796 100644 --- a/indra/newview/lltoolindividual.h +++ b/indra/newview/lltoolindividual.h @@ -43,11 +43,9 @@ class LLToolIndividual : public LLTool, public LLSingleton<LLToolIndividual> virtual ~LLToolIndividual(); public: - virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); - virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); - virtual void handleSelect(); - //virtual void handleDeselect(); - //virtual void render(); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask) override; + virtual void handleSelect() override; static void pickCallback(const LLPickInfo& pick_info); diff --git a/indra/newview/lltoolobjpicker.h b/indra/newview/lltoolobjpicker.h index 5ad9b67e21..a55cd223de 100644 --- a/indra/newview/lltoolobjpicker.h +++ b/indra/newview/lltoolobjpicker.h @@ -38,16 +38,16 @@ class LLToolObjPicker : public LLTool, public LLSingleton<LLToolObjPicker> LLSINGLETON(LLToolObjPicker); public: - virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); - virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); - virtual BOOL handleHover(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask) override; + virtual BOOL handleHover(S32 x, S32 y, MASK mask) override; - virtual void handleSelect(); - virtual void handleDeselect(); + virtual void handleSelect() override; + virtual void handleDeselect() override; - virtual void onMouseCaptureLost(); + virtual void onMouseCaptureLost() override; - virtual void setExitCallback(void (*callback)(void *), void *callback_data); + void setExitCallback(void (*callback)(void *), void *callback_data); LLUUID getObjectID() const { return mHitObjectID; } diff --git a/indra/newview/lltoolpie.h b/indra/newview/lltoolpie.h index 8f6100e4b4..dca0d12cf6 100644 --- a/indra/newview/lltoolpie.h +++ b/indra/newview/lltoolpie.h @@ -42,26 +42,26 @@ class LLToolPie : public LLTool, public LLSingleton<LLToolPie> public: // Virtual functions inherited from LLMouseHandler - virtual BOOL handleAnyMouseClick(S32 x, S32 y, MASK mask, EMouseClickType clicktype, BOOL down); - virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); - virtual BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); - virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); - virtual BOOL handleRightMouseUp(S32 x, S32 y, MASK mask); - virtual BOOL handleHover(S32 x, S32 y, MASK mask); - virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); + virtual BOOL handleAnyMouseClick(S32 x, S32 y, MASK mask, EMouseClickType clicktype, BOOL down) override; + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual BOOL handleRightMouseDown(S32 x, S32 y, MASK mask) override; + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask) override; + virtual BOOL handleRightMouseUp(S32 x, S32 y, MASK mask) override; + virtual BOOL handleHover(S32 x, S32 y, MASK mask) override; + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask) override; BOOL handleScrollWheelAny(S32 x, S32 y, S32 clicks_x, S32 clicks_y); - virtual BOOL handleScrollWheel(S32 x, S32 y, S32 clicks); - virtual BOOL handleScrollHWheel(S32 x, S32 y, S32 clicks); - virtual BOOL handleToolTip(S32 x, S32 y, MASK mask); + virtual BOOL handleScrollWheel(S32 x, S32 y, S32 clicks) override; + virtual BOOL handleScrollHWheel(S32 x, S32 y, S32 clicks) override; + virtual BOOL handleToolTip(S32 x, S32 y, MASK mask) override; - virtual void render(); + virtual void render() override; - virtual void stopEditing(); + virtual void stopEditing() override; - virtual void onMouseCaptureLost(); - virtual void handleSelect(); - virtual void handleDeselect(); - virtual LLTool* getOverrideTool(MASK mask); + virtual void onMouseCaptureLost() override; + virtual void handleSelect() override; + virtual void handleDeselect() override; + virtual LLTool* getOverrideTool(MASK mask) override; LLPickInfo& getPick() { return mPick; } U8 getClickAction() { return mClickAction; } diff --git a/indra/newview/lltoolpipette.h b/indra/newview/lltoolpipette.h index 7575d8ad18..2636811c66 100644 --- a/indra/newview/lltoolpipette.h +++ b/indra/newview/lltoolpipette.h @@ -47,10 +47,10 @@ class LLToolPipette virtual ~LLToolPipette(); public: - virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); - virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); - virtual BOOL handleHover(S32 x, S32 y, MASK mask); - virtual BOOL handleToolTip(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask) override; + virtual BOOL handleHover(S32 x, S32 y, MASK mask) override; + virtual BOOL handleToolTip(S32 x, S32 y, MASK mask) override; // Note: Don't return connection; use boost::bind + boost::signals2::trackable to disconnect slots typedef boost::signals2::signal<void (const LLTextureEntry& te)> signal_t; diff --git a/indra/newview/lltoolselectland.h b/indra/newview/lltoolselectland.h index b5ba72f16d..88bc4e2e3d 100644 --- a/indra/newview/lltoolselectland.h +++ b/indra/newview/lltoolselectland.h @@ -39,15 +39,15 @@ class LLToolSelectLand virtual ~LLToolSelectLand(); public: - /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask); - /*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask); - /*virtual*/ void render(); // draw the select rectangle - /*virtual*/ BOOL isAlwaysRendered() { return TRUE; } + /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask) override; + /*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask) override; + /*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask) override; + /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask) override; + /*virtual*/ void render() override; // draw the select rectangle + /*virtual*/ BOOL isAlwaysRendered() override { return TRUE; } - /*virtual*/ void handleSelect(); - /*virtual*/ void handleDeselect(); + /*virtual*/ void handleSelect() override; + /*virtual*/ void handleDeselect() override; protected: BOOL outsideSlop(S32 x, S32 y, S32 start_x, S32 start_y); diff --git a/indra/newview/llversioninfo.h b/indra/newview/llversioninfo.h index a40042380a..f82c5ffa98 100644 --- a/indra/newview/llversioninfo.h +++ b/indra/newview/llversioninfo.h @@ -47,7 +47,7 @@ class LLStoreListener; class LLVersionInfo: public LLSingleton<LLVersionInfo> { LLSINGLETON(LLVersionInfo); - void initSingleton(); + void initSingleton() override; public: ~LLVersionInfo(); diff --git a/indra/newview/llviewerchat.cpp b/indra/newview/llviewerchat.cpp index 1c3c547bc1..0d2d62fd77 100644 --- a/indra/newview/llviewerchat.cpp +++ b/indra/newview/llviewerchat.cpp @@ -25,7 +25,7 @@ */ #include "llviewerprecompiledheaders.h" -#include "llviewerchat.h" +#include "llviewerchat.h" // newview includes #include "llagent.h" // gAgent diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 3a08f748d6..8353d4d1d7 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -66,6 +66,7 @@ #include "llfloaterdestinations.h" #include "llfloaterdisplayname.h" #include "llfloatereditextdaycycle.h" +#include "llfloateremojipicker.h" #include "llfloaterenvironmentadjust.h" #include "llfloaterexperienceprofile.h" #include "llfloaterexperiences.h" @@ -161,6 +162,7 @@ #include "llfloaterimnearbychat.h" #include "llpanelblockedlist.h" #include "llpanelprofileclassifieds.h" +#include "llpanelemojicomplete.h" #include "llpreviewanim.h" #include "llpreviewgesture.h" #include "llpreviewnotecard.h" @@ -339,7 +341,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("chat_voice", "floater_voice_chat_volume.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterChatVoiceVolume>); LLFloaterReg::add("change_item_thumbnail", "floater_change_item_thumbnail.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterChangeItemThumbnail>); LLFloaterReg::add("nearby_chat", "floater_im_session.xml", (LLFloaterBuildFunc)&LLFloaterIMNearbyChat::buildFloater); - LLFloaterReg::add("classified", "floater_classified.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterClassified>); + LLFloaterReg::add("classified", "floater_classified.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterClassified>); LLFloaterReg::add("compile_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterCompileQueue>); LLFloaterReg::add("conversation", "floater_conversation_log.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterConversationLog>); LLFloaterReg::add("add_landmark", "floater_create_landmark.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterCreateLandmark>); @@ -347,18 +349,20 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("delete_pref_preset", "floater_delete_pref_preset.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterDeletePrefPreset>); LLFloaterReg::add("destinations", "floater_destinations.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterDestinations>); + LLFloaterReg::add("emoji_picker", "floater_emoji_picker.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterEmojiPicker>); + LLFloaterReg::add("emoji_complete", "floater_emoji_complete.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterEmojiComplete>); LLFloaterReg::add("env_post_process", "floater_post_process.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterPostProcess>); - LLFloaterReg::add("env_fixed_environmentent_water", "floater_fixedenvironment.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterFixedEnvironmentWater>); - LLFloaterReg::add("env_fixed_environmentent_sky", "floater_fixedenvironment.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterFixedEnvironmentSky>); + LLFloaterReg::add("env_fixed_environmentent_water", "floater_fixedenvironment.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterFixedEnvironmentWater>); + LLFloaterReg::add("env_fixed_environmentent_sky", "floater_fixedenvironment.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterFixedEnvironmentSky>); - LLFloaterReg::add("env_adjust_snapshot", "floater_adjust_environment.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterEnvironmentAdjust>); + LLFloaterReg::add("env_adjust_snapshot", "floater_adjust_environment.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterEnvironmentAdjust>); - LLFloaterReg::add("env_edit_extdaycycle", "floater_edit_ext_day_cycle.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterEditExtDayCycle>); - LLFloaterReg::add("my_environments", "floater_my_environments.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMyEnvironment>); + LLFloaterReg::add("env_edit_extdaycycle", "floater_edit_ext_day_cycle.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterEditExtDayCycle>); + LLFloaterReg::add("my_environments", "floater_my_environments.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMyEnvironment>); - LLFloaterReg::add("event", "floater_event.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterEvent>); - LLFloaterReg::add("experiences", "floater_experiences.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterExperiences>); + LLFloaterReg::add("event", "floater_event.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterEvent>); + LLFloaterReg::add("experiences", "floater_experiences.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterExperiences>); LLFloaterReg::add("experience_profile", "floater_experienceprofile.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterExperienceProfile>); LLFloaterReg::add("experience_search", "floater_experience_search.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterExperiencePicker>); diff --git a/indra/newview/llviewerhelp.h b/indra/newview/llviewerhelp.h index da50e07a43..bbd20bc07e 100644 --- a/indra/newview/llviewerhelp.h +++ b/indra/newview/llviewerhelp.h @@ -43,21 +43,21 @@ class LLViewerHelp : public LLHelp, public LLSingleton<LLViewerHelp> public: /// display the specified help topic in the help viewer - /*virtual*/ void showTopic(const std::string &topic); + /*virtual*/ void showTopic(const std::string &topic) override; - std::string getURL(const std::string& topic); + std::string getURL(const std::string& topic) override; // return topic derived from viewer UI focus, else default topic std::string getTopicFromFocus(); /// return default (fallback) topic name suitable for showTopic() - /*virtual*/ std::string defaultTopic(); + /*virtual*/ std::string defaultTopic() override; // return topic to use before the user logs in - /*virtual*/ std::string preLoginTopic(); + /*virtual*/ std::string preLoginTopic() override; // return topic to use for the top-level help, invoked by F1 - /*virtual*/ std::string f1HelpTopic(); + /*virtual*/ std::string f1HelpTopic() override; }; #endif // header guard diff --git a/indra/newview/llviewermedia.h b/indra/newview/llviewermedia.h index f1f42afd81..ad7c4bcefa 100644 --- a/indra/newview/llviewermedia.h +++ b/indra/newview/llviewermedia.h @@ -74,7 +74,7 @@ class LLViewerMedia: public LLSingleton<LLViewerMedia> { LLSINGLETON(LLViewerMedia); ~LLViewerMedia(); - void initSingleton(); + void initSingleton() override; LOG_CLASS(LLViewerMedia); public: diff --git a/indra/newview/llviewermediafocus.h b/indra/newview/llviewermediafocus.h index effd08a559..2310e4dbfc 100644 --- a/indra/newview/llviewermediafocus.h +++ b/indra/newview/llviewermediafocus.h @@ -54,10 +54,10 @@ public: void setHoverFace(LLPointer<LLViewerObject> objectp, S32 face, viewer_media_t media_impl, LLVector3 pick_normal = LLVector3::zero); void clearHover(); - /*virtual*/ bool getFocus(); - /*virtual*/ BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent); - /*virtual*/ BOOL handleKeyUp(KEY key, MASK mask, BOOL called_from_parent); - /*virtual*/ BOOL handleUnicodeChar(llwchar uni_char, BOOL called_from_parent); + bool getFocus(); + /*virtual*/ BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent) override; + /*virtual*/ BOOL handleKeyUp(KEY key, MASK mask, BOOL called_from_parent) override; + /*virtual*/ BOOL handleUnicodeChar(llwchar uni_char, BOOL called_from_parent) override; BOOL handleScrollWheel(const LLVector2& texture_coords, S32 clicks_x, S32 clicks_y); BOOL handleScrollWheel(S32 x, S32 y, S32 clicks_x, S32 clicks_y); @@ -92,12 +92,12 @@ public: LLUUID getControlsMediaID(); // The MoaP object wants keyup and keydown events. Overridden to return true. - virtual bool wantsKeyUpKeyDown() const; - virtual bool wantsReturnKey() const; + virtual bool wantsKeyUpKeyDown() const override; + virtual bool wantsReturnKey() const override; protected: - /*virtual*/ void onFocusReceived(); - /*virtual*/ void onFocusLost(); + /*virtual*/ void onFocusReceived() override; + /*virtual*/ void onFocusLost() override; private: diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 9db9d97ddc..2cf341f87f 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -1437,6 +1437,30 @@ class LLAdvancedCheckDebugViews : public view_listener_t +/////////////////// +// DEBUG UNICODE // +/////////////////// + + +class LLAdvancedToggleDebugUnicode : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLView::sDebugUnicode = !(LLView::sDebugUnicode); + return true; + } +}; + +class LLAdvancedCheckDebugUnicode : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return LLView::sDebugUnicode; + } +}; + + + /////////////////////// // XUI NAME TOOLTIPS // /////////////////////// @@ -8509,23 +8533,8 @@ void handle_show_url(const LLSD& param) void handle_report_bug(const LLSD& param) { - LLUIString url(param.asString()); - - LLStringUtil::format_map_t replace; - std::string environment = LLAppViewer::instance()->getViewerInfoString(true); - boost::regex regex; - regex.assign("</?nolink>"); - std::string stripped_env = boost::regex_replace(environment, regex, ""); - - replace["[ENVIRONMENT]"] = LLURI::escape(stripped_env); - LLSLURL location_url; - LLAgentUI::buildSLURL(location_url); - replace["[LOCATION]"] = LLURI::escape(location_url.getSLURLString()); - - LLUIString file_bug_url = gSavedSettings.getString("ReportBugURL"); - file_bug_url.setArgs(replace); - - LLWeb::loadURLExternal(file_bug_url.getString()); + std::string url = gSavedSettings.getString("ReportBugURL"); + LLWeb::loadURLExternal(url); } void handle_buy_currency_test(void*) @@ -9524,6 +9533,8 @@ void initialize_menus() view_listener_t::addMenu(new LLAdvancedCheckDebugClicks(), "Advanced.CheckDebugClicks"); view_listener_t::addMenu(new LLAdvancedCheckDebugViews(), "Advanced.CheckDebugViews"); view_listener_t::addMenu(new LLAdvancedToggleDebugViews(), "Advanced.ToggleDebugViews"); + view_listener_t::addMenu(new LLAdvancedCheckDebugUnicode(), "Advanced.CheckDebugUnicode"); + view_listener_t::addMenu(new LLAdvancedToggleDebugUnicode(), "Advanced.ToggleDebugUnicode"); view_listener_t::addMenu(new LLAdvancedToggleXUINameTooltips(), "Advanced.ToggleXUINameTooltips"); view_listener_t::addMenu(new LLAdvancedCheckXUINameTooltips(), "Advanced.CheckXUINameTooltips"); view_listener_t::addMenu(new LLAdvancedToggleDebugMouseEvents(), "Advanced.ToggleDebugMouseEvents"); @@ -9623,7 +9634,11 @@ void initialize_menus() //Develop (clear cache immediately) commit.add("Develop.ClearCache", boost::bind(&handle_cache_clear_immediately) ); - + + // Develop (Fonts debugging) + commit.add("Develop.Fonts.Dump", boost::bind(&LLFontGL::dumpFonts)); + commit.add("Develop.Fonts.DumpTextures", boost::bind(&LLFontGL::dumpFontTextures)); + // Admin >Object view_listener_t::addMenu(new LLAdminForceTakeCopy(), "Admin.ForceTakeCopy"); view_listener_t::addMenu(new LLAdminHandleObjectOwnerSelf(), "Admin.HandleObjectOwnerSelf"); diff --git a/indra/newview/llviewerparcelaskplay.h b/indra/newview/llviewerparcelaskplay.h index dc711917d2..56faddae66 100644 --- a/indra/newview/llviewerparcelaskplay.h +++ b/indra/newview/llviewerparcelaskplay.h @@ -34,8 +34,8 @@ class LLViewerParcelAskPlay : public LLSingleton<LLViewerParcelAskPlay> { LLSINGLETON(LLViewerParcelAskPlay); ~LLViewerParcelAskPlay(); - void initSingleton(); - void cleanupSingleton(); + void initSingleton() override; + void cleanupSingleton() override; public: // functor expects functor(region_id, parcel_id, url, play/stop) typedef boost::function<void(const LLUUID&, const S32&, const std::string&, const bool&)> ask_callback; diff --git a/indra/newview/llviewerparcelmedia.h b/indra/newview/llviewerparcelmedia.h index 779a65bdf8..790b2b71fc 100644 --- a/indra/newview/llviewerparcelmedia.h +++ b/indra/newview/llviewerparcelmedia.h @@ -74,7 +74,7 @@ public: void sendMediaNavigateMessage(const std::string& url); // inherited from LLViewerMediaObserver - virtual void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event); + virtual void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) override; private: void processParcelMediaCommandMessage(LLMessageSystem *msg); diff --git a/indra/newview/llviewerparcelmediaautoplay.h b/indra/newview/llviewerparcelmediaautoplay.h index d71fd4c075..e83085dee0 100644 --- a/indra/newview/llviewerparcelmediaautoplay.h +++ b/indra/newview/llviewerparcelmediaautoplay.h @@ -35,7 +35,7 @@ class LLViewerParcelMediaAutoPlay : LLEventTimer, public LLSingleton<LLViewerPar { LLSINGLETON(LLViewerParcelMediaAutoPlay); public: - virtual BOOL tick(); + virtual BOOL tick() override; static void playStarted(); private: diff --git a/indra/newview/llviewertexturelist.h b/indra/newview/llviewertexturelist.h index e8dd96daee..10a2cfa32a 100644 --- a/indra/newview/llviewertexturelist.h +++ b/indra/newview/llviewertexturelist.h @@ -244,9 +244,9 @@ class LLUIImageList : public LLImageProviderInterface, public LLSingleton<LLUIIm LLSINGLETON_EMPTY_CTOR(LLUIImageList); public: // LLImageProviderInterface - /*virtual*/ LLPointer<LLUIImage> getUIImageByID(const LLUUID& id, S32 priority); - /*virtual*/ LLPointer<LLUIImage> getUIImage(const std::string& name, S32 priority); - void cleanUp(); + /*virtual*/ LLPointer<LLUIImage> getUIImageByID(const LLUUID& id, S32 priority) override; + /*virtual*/ LLPointer<LLUIImage> getUIImage(const std::string& name, S32 priority) override; + void cleanUp() override; bool initFromFile(); diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index ed671fe849..47c7eed872 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -955,8 +955,7 @@ public: { const Line& line = *iter; LLFontGL::getFontMonospace()->renderUTF8(line.text, 0, (F32)line.x, (F32)line.y, mTextColor, - LLFontGL::LEFT, LLFontGL::TOP, - LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE); + LLFontGL::LEFT, LLFontGL::TOP, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); } } @@ -3000,6 +2999,7 @@ BOOL LLViewerWindow::handleKey(KEY key, MASK mask) case KEY_PAGE_UP: case KEY_PAGE_DOWN: case KEY_HOME: + case KEY_END: // when chatbar is empty or ArrowKeysAlwaysMove set, // pass arrow keys on to avatar... return FALSE; diff --git a/indra/newview/llvoicechannel.h b/indra/newview/llvoicechannel.h index 309c3eebdd..e68bfbe1ff 100644 --- a/indra/newview/llvoicechannel.h +++ b/indra/newview/llvoicechannel.h @@ -170,12 +170,12 @@ class LLVoiceChannelProximal : public LLVoiceChannel, public LLSingleton<LLVoice LLSINGLETON(LLVoiceChannelProximal); public: - /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); - /*virtual*/ void handleStatusChange(EStatusType status); - /*virtual*/ void handleError(EStatusType status); - /*virtual*/ BOOL isActive(); - /*virtual*/ void activate(); - /*virtual*/ void deactivate(); + /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal) override; + /*virtual*/ void handleStatusChange(EStatusType status) override; + /*virtual*/ void handleError(EStatusType status) override; + /*virtual*/ BOOL isActive() override; + /*virtual*/ void activate() override; + /*virtual*/ void deactivate() override; }; @@ -184,15 +184,15 @@ class LLVoiceChannelP2P : public LLVoiceChannelGroup public: LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id); - /*virtual*/ void handleStatusChange(EStatusType status); - /*virtual*/ void handleError(EStatusType status); - /*virtual*/ void activate(); - /*virtual*/ void getChannelInfo(); + /*virtual*/ void handleStatusChange(EStatusType status) override; + /*virtual*/ void handleError(EStatusType status) override; + /*virtual*/ void activate() override; + /*virtual*/ void getChannelInfo() override; void setSessionHandle(const std::string& handle, const std::string &inURI); protected: - virtual void setState(EState state); + virtual void setState(EState state) override; private: diff --git a/indra/newview/llvoicevivox.h b/indra/newview/llvoicevivox.h index e3ab99c675..ae2aec0e9c 100644 --- a/indra/newview/llvoicevivox.h +++ b/indra/newview/llvoicevivox.h @@ -64,26 +64,26 @@ public: /// @name LLVoiceModuleInterface virtual implementations /// @see LLVoiceModuleInterface //@{ - virtual void init(LLPumpIO *pump); // Call this once at application startup (creates connector) - virtual void terminate(); // Call this to clean up during shutdown + virtual void init(LLPumpIO *pump) override; // Call this once at application startup (creates connector) + virtual void terminate() override; // Call this to clean up during shutdown - virtual const LLVoiceVersionInfo& getVersion(); + virtual const LLVoiceVersionInfo& getVersion() override; - virtual void updateSettings(); // call after loading settings and whenever they change + virtual void updateSettings() override; // call after loading settings and whenever they change // Returns true if vivox has successfully logged in and is not in error state - virtual bool isVoiceWorking() const; + virtual bool isVoiceWorking() const override; ///////////////////// /// @name Tuning //@{ - virtual void tuningStart(); - virtual void tuningStop(); - virtual bool inTuningMode(); + virtual void tuningStart() override; + virtual void tuningStop() override; + virtual bool inTuningMode() override; - virtual void tuningSetMicVolume(float volume); - virtual void tuningSetSpeakerVolume(float volume); - virtual float tuningGetEnergy(void); + virtual void tuningSetMicVolume(float volume) override; + virtual void tuningSetSpeakerVolume(float volume) override; + virtual float tuningGetEnergy(void) override; //@} ///////////////////// @@ -91,40 +91,40 @@ public: //@{ // This returns true when it's safe to bring up the "device settings" dialog in the prefs. // i.e. when the daemon is running and connected, and the device lists are populated. - virtual bool deviceSettingsAvailable(); - virtual bool deviceSettingsUpdated(); //return if the list has been updated and never fetched, only to be called from the voicepanel. + virtual bool deviceSettingsAvailable() override; + virtual bool deviceSettingsUpdated() override; //return if the list has been updated and never fetched, only to be called from the voicepanel. // Requery the vivox daemon for the current list of input/output devices. // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed // (use this if you want to know when it's done). // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. - virtual void refreshDeviceLists(bool clearCurrentList = true); + virtual void refreshDeviceLists(bool clearCurrentList = true) override; - virtual void setCaptureDevice(const std::string& name); - virtual void setRenderDevice(const std::string& name); + virtual void setCaptureDevice(const std::string& name) override; + virtual void setRenderDevice(const std::string& name) override; - virtual LLVoiceDeviceList& getCaptureDevices(); - virtual LLVoiceDeviceList& getRenderDevices(); + virtual LLVoiceDeviceList& getCaptureDevices() override; + virtual LLVoiceDeviceList& getRenderDevices() override; //@} - virtual void getParticipantList(std::set<LLUUID> &participants); - virtual bool isParticipant(const LLUUID& speaker_id); + virtual void getParticipantList(std::set<LLUUID> &participants) override; + virtual bool isParticipant(const LLUUID& speaker_id) override; // Send a text message to the specified user, initiating the session if necessary. // virtual BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message) const {return false;}; // close any existing text IM session with the specified user - virtual void endUserIMSession(const LLUUID &uuid); + virtual void endUserIMSession(const LLUUID &uuid) override; // Returns true if calling back the session URI after the session has closed is possible. // Currently this will be false only for PSTN P2P calls. // NOTE: this will return true if the session can't be found. - virtual BOOL isSessionCallBackPossible(const LLUUID &session_id); + virtual BOOL isSessionCallBackPossible(const LLUUID &session_id) override; // Returns true if the session can accepte text IM's. // Currently this will be false only for PSTN P2P calls. // NOTE: this will return true if the session can't be found. - virtual BOOL isSessionTextIMPossible(const LLUUID &session_id); + virtual BOOL isSessionTextIMPossible(const LLUUID &session_id) override; //////////////////////////// @@ -132,21 +132,21 @@ public: //@{ // returns true iff the user is currently in a proximal (local spatial) channel. // Note that gestures should only fire if this returns true. - virtual bool inProximalChannel(); + virtual bool inProximalChannel() override; virtual void setNonSpatialChannel(const std::string &uri, - const std::string &credentials); + const std::string &credentials) override; virtual bool setSpatialChannel(const std::string &uri, - const std::string &credentials); + const std::string &credentials) override; - virtual void leaveNonSpatialChannel(); + virtual void leaveNonSpatialChannel() override; - virtual void leaveChannel(void); + virtual void leaveChannel(void) override; // Returns the URI of the current channel, or an empty string if not currently in a channel. // NOTE that it will return an empty string if it's in the process of joining a channel. - virtual std::string getCurrentChannel(); + virtual std::string getCurrentChannel() override; //@} @@ -154,59 +154,59 @@ public: /// @name invitations //@{ // start a voice channel with the specified user - virtual void callUser(const LLUUID &uuid); - virtual bool isValidChannel(std::string &channelHandle); - virtual bool answerInvite(std::string &channelHandle); - virtual void declineInvite(std::string &channelHandle); + virtual void callUser(const LLUUID &uuid) override; + virtual bool isValidChannel(std::string &channelHandle) override; + virtual bool answerInvite(std::string &channelHandle) override; + virtual void declineInvite(std::string &channelHandle) override; //@} ///////////////////////// /// @name Volume/gain //@{ - virtual void setVoiceVolume(F32 volume); - virtual void setMicGain(F32 volume); + virtual void setVoiceVolume(F32 volume) override; + virtual void setMicGain(F32 volume) override; //@} ///////////////////////// /// @name enable disable voice and features //@{ - virtual bool voiceEnabled(); - virtual void setVoiceEnabled(bool enabled); - virtual BOOL lipSyncEnabled(); - virtual void setLipSyncEnabled(BOOL enabled); - virtual void setMuteMic(bool muted); // Set the mute state of the local mic. + virtual bool voiceEnabled() override; + virtual void setVoiceEnabled(bool enabled) override; + virtual BOOL lipSyncEnabled() override; + virtual void setLipSyncEnabled(BOOL enabled) override; + virtual void setMuteMic(bool muted) override; // Set the mute state of the local mic. //@} ////////////////////////// /// @name nearby speaker accessors //@{ - virtual BOOL getVoiceEnabled(const LLUUID& id); // true if we've received data for this avatar - virtual std::string getDisplayName(const LLUUID& id); - virtual BOOL isParticipantAvatar(const LLUUID &id); - virtual BOOL getIsSpeaking(const LLUUID& id); - virtual BOOL getIsModeratorMuted(const LLUUID& id); - virtual F32 getCurrentPower(const LLUUID& id); // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... - virtual BOOL getOnMuteList(const LLUUID& id); - virtual F32 getUserVolume(const LLUUID& id); - virtual void setUserVolume(const LLUUID& id, F32 volume); // set's volume for specified agent, from 0-1 (where .5 is nominal) + virtual BOOL getVoiceEnabled(const LLUUID& id) override; // true if we've received data for this avatar + virtual std::string getDisplayName(const LLUUID& id) override; + virtual BOOL isParticipantAvatar(const LLUUID &id) override; + virtual BOOL getIsSpeaking(const LLUUID& id) override; + virtual BOOL getIsModeratorMuted(const LLUUID& id) override; + virtual F32 getCurrentPower(const LLUUID& id) override; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... + virtual BOOL getOnMuteList(const LLUUID& id) override; + virtual F32 getUserVolume(const LLUUID& id) override; + virtual void setUserVolume(const LLUUID& id, F32 volume) override; // set's volume for specified agent, from 0-1 (where .5 is nominal) //@} // authorize the user virtual void userAuthorized(const std::string& user_id, - const LLUUID &agentID); + const LLUUID &agentID) override; ////////////////////////////// /// @name Status notification //@{ - virtual void addObserver(LLVoiceClientStatusObserver* observer); - virtual void removeObserver(LLVoiceClientStatusObserver* observer); - virtual void addObserver(LLFriendObserver* observer); - virtual void removeObserver(LLFriendObserver* observer); - virtual void addObserver(LLVoiceClientParticipantObserver* observer); - virtual void removeObserver(LLVoiceClientParticipantObserver* observer); + virtual void addObserver(LLVoiceClientStatusObserver* observer) override; + virtual void removeObserver(LLVoiceClientStatusObserver* observer) override; + virtual void addObserver(LLFriendObserver* observer) override; + virtual void removeObserver(LLFriendObserver* observer) override; + virtual void addObserver(LLVoiceClientParticipantObserver* observer) override; + virtual void removeObserver(LLVoiceClientParticipantObserver* observer) override; //@} - virtual std::string sipURIFromID(const LLUUID &id); + virtual std::string sipURIFromID(const LLUUID &id) override; //@} /// @name LLVoiceEffectInterface virtual implementations @@ -216,32 +216,32 @@ public: ////////////////////////// /// @name Accessors //@{ - virtual bool setVoiceEffect(const LLUUID& id); - virtual const LLUUID getVoiceEffect(); - virtual LLSD getVoiceEffectProperties(const LLUUID& id); + virtual bool setVoiceEffect(const LLUUID& id) override; + virtual const LLUUID getVoiceEffect() override; + virtual LLSD getVoiceEffectProperties(const LLUUID& id) override; - virtual void refreshVoiceEffectLists(bool clear_lists); - virtual const voice_effect_list_t& getVoiceEffectList() const; - virtual const voice_effect_list_t& getVoiceEffectTemplateList() const; + virtual void refreshVoiceEffectLists(bool clear_lists) override; + virtual const voice_effect_list_t& getVoiceEffectList() const override; + virtual const voice_effect_list_t& getVoiceEffectTemplateList() const override; //@} ////////////////////////////// /// @name Status notification //@{ - virtual void addObserver(LLVoiceEffectObserver* observer); - virtual void removeObserver(LLVoiceEffectObserver* observer); + virtual void addObserver(LLVoiceEffectObserver* observer) override; + virtual void removeObserver(LLVoiceEffectObserver* observer) override; //@} ////////////////////////////// /// @name Effect preview buffer //@{ - virtual void enablePreviewBuffer(bool enable); - virtual void recordPreviewBuffer(); - virtual void playPreviewBuffer(const LLUUID& effect_id = LLUUID::null); - virtual void stopPreviewBuffer(); + virtual void enablePreviewBuffer(bool enable) override; + virtual void recordPreviewBuffer() override; + virtual void playPreviewBuffer(const LLUUID& effect_id = LLUUID::null) override; + virtual void stopPreviewBuffer() override; - virtual bool isPreviewRecording(); - virtual bool isPreviewPlaying(); + virtual bool isPreviewRecording() override; + virtual bool isPreviewPlaying() override; //@} //@} @@ -750,7 +750,7 @@ private: std::string getAudioSessionURI(); std::string getAudioSessionHandle(); - void setHidden(bool hidden); //virtual + void setHidden(bool hidden) override; //virtual void sendPositionAndVolumeUpdate(void); void sendCaptureAndRenderDevices(); diff --git a/indra/newview/llvotree.cpp b/indra/newview/llvotree.cpp index 36e6da802b..575b1dbe7e 100644 --- a/indra/newview/llvotree.cpp +++ b/indra/newview/llvotree.cpp @@ -865,6 +865,10 @@ BOOL LLVOTree::updateGeometry(LLDrawable *drawable) mReferenceBuffer->unmapBuffer(); llassert(vertex_count == max_vertices); llassert(index_count == max_indices); +#ifndef SHOW_ASSERT + (void)vertex_count; + (void)index_count; +#endif } //generate tree mesh diff --git a/indra/newview/llwearableitemslist.h b/indra/newview/llwearableitemslist.h index f7774a7086..0dbe50d5e4 100644 --- a/indra/newview/llwearableitemslist.h +++ b/indra/newview/llwearableitemslist.h @@ -428,7 +428,7 @@ public: { LLSINGLETON(ContextMenu); public: - /*virtual*/ void show(LLView* spawning_view, const uuid_vec_t& uuids, S32 x, S32 y); + /*virtual*/ void show(LLView* spawning_view, const uuid_vec_t& uuids, S32 x, S32 y) override; void show(LLView* spawning_view, LLWearableType::EType w_type, S32 x, S32 y); @@ -441,7 +441,7 @@ public: MASK_UNKNOWN = 0x10, }; - /* virtual */ LLContextMenu* createMenu(); + /* virtual */ LLContextMenu* createMenu() override; void updateItemsVisibility(LLContextMenu* menu); void updateItemsLabels(LLContextMenu* menu); static void setMenuItemVisible(LLContextMenu* menu, const std::string& name, bool val); @@ -472,7 +472,7 @@ public: virtual ~LLWearableItemsList(); - /*virtual*/ LLPanel* createNewItem(LLViewerInventoryItem* item); + /*virtual*/ LLPanel* createNewItem(LLViewerInventoryItem* item) override; void updateList(const LLUUID& category_id); diff --git a/indra/newview/llwindebug.h b/indra/newview/llwindebug.h index 524adba652..31dbfb8ffd 100644 --- a/indra/newview/llwindebug.h +++ b/indra/newview/llwindebug.h @@ -40,9 +40,9 @@ class LLWinDebug: { LLSINGLETON_EMPTY_CTOR(LLWinDebug); public: - void initSingleton(); + void initSingleton() override; static void generateMinidump(struct _EXCEPTION_POINTERS *pExceptionInfo = NULL); - void cleanupSingleton(); + void cleanupSingleton() override; private: static void writeDumpToFile(MINIDUMP_TYPE type, MINIDUMP_EXCEPTION_INFORMATION *ExInfop, const std::string& filename); }; diff --git a/indra/newview/llworldmapview.cpp b/indra/newview/llworldmapview.cpp index cc947c87d5..a8676d2ad6 100755 --- a/indra/newview/llworldmapview.cpp +++ b/indra/newview/llworldmapview.cpp @@ -520,7 +520,7 @@ void LLWorldMapView::draw() S32_MAX, //max_chars mMapScale, //max_pixels NULL, - TRUE); //use ellipses + /*use_ellipses*/TRUE); } } } diff --git a/indra/newview/skins/default/textures/icons/emoji_picker_icon.png b/indra/newview/skins/default/textures/icons/emoji_picker_icon.png Binary files differnew file mode 100644 index 0000000000..ad4f3fa63c --- /dev/null +++ b/indra/newview/skins/default/textures/icons/emoji_picker_icon.png diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index 2126db32df..1a49ec7e24 100644 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -206,6 +206,7 @@ with the same filename but different name <texture name="DropTarget" file_name="widgets/DropTarget.png" preload="false" /> + <texture name="Emoji_Picker_Icon" file_name="icons/emoji_picker_icon.png" preload="true" /> <texture name="ExternalBrowser_Off" file_name="icons/ExternalBrowser_Off.png" preload="false" /> <texture name="Edit_Wrench" file_name="icons/Edit_Wrench.png" preload="false" /> @@ -213,7 +214,7 @@ with the same filename but different name <texture name="Presets_Icon" file_name="icons/Presets_Icon.png" preload="true" /> <texture name="Presets_Icon_Graphic" file_name="icons/Presets_Icon_Graphic.png" preload="true" /> - <texture name="Favorite_Star_Active" file_name="navbar/Favorite_Star_Active.png" preload="false" /> + <texture name="Favorite_Star_Active" file_name="navbar/Favorite_Star_Active.png" preload="false" /> <texture name="Favorite_Star_Off" file_name="navbar/Favorite_Star_Off.png" preload="false" /> <texture name="Favorite_Star_Press" file_name="navbar/Favorite_Star_Press.png" preload="false" /> <texture name="Favorite_Star_Over" file_name="navbar/Favorite_Star_Over.png" preload="false" /> @@ -342,7 +343,7 @@ with the same filename but different name <texture name="Inv_Underpants" file_name="icons/Inv_Underpants.png" preload="false" /> <texture name="Inv_Undershirt" file_name="icons/Inv_Undershirt.png" preload="false" /> <texture name="Inv_Link" file_name="icons/Inv_Link.png" preload="false" /> - <texture name="Inv_Settings" file_name="icons/Inv_Settings.png" preload="false" /> + <texture name="Inv_Settings" file_name="icons/Inv_Settings.png" preload="false" /> <texture name="Inv_SettingsSky" file_name="icons/Inv_SettingsSky.png" preload="false" /> <texture name="Inv_SettingsWater" file_name="icons/Inv_SettingsWater.png" preload="false" /> <texture name="Inv_SettingsDay" file_name="icons/Inv_SettingsDay.png" preload="false" /> diff --git a/indra/newview/skins/default/xui/da/emoji_categories.xml b/indra/newview/skins/default/xui/da/emoji_categories.xml new file mode 100644 index 0000000000..456b18e4e2 --- /dev/null +++ b/indra/newview/skins/default/xui/da/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>smileys and følelser</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>mennesker and krop</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>komponenter</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>dyr and natur</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>mad and drikke</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>rejser and steder</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>oplevelser</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>objekter</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>symboler</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/de/emoji_categories.xml b/indra/newview/skins/default/xui/de/emoji_categories.xml new file mode 100644 index 0000000000..ed63d0bac9 --- /dev/null +++ b/indra/newview/skins/default/xui/de/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>Smileys and Emotionen</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>Menschen and Körper</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>Komponenten</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>Tiere and Natur</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>Essen and Trinken</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>Reisen and Orte</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>Aktivitäten</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>Gegenstände</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>Symbole</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/en/emoji_categories.xml b/indra/newview/skins/default/xui/en/emoji_categories.xml new file mode 100644 index 0000000000..0315d0c43a --- /dev/null +++ b/indra/newview/skins/default/xui/en/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>smileys and emotion</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>people and body</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>components</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>animals and nature</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>food and drink</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>travel and places</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>activities</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>objects</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>symbols</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/en/floater_activeim.xml b/indra/newview/skins/default/xui/en/floater_activeim.xml index b79c5d9a19..42c3e7e935 100644 --- a/indra/newview/skins/default/xui/en/floater_activeim.xml +++ b/indra/newview/skins/default/xui/en/floater_activeim.xml @@ -23,7 +23,7 @@ <scrolling_panel_list follows="left|right" layout="topleft" - left="1" + left="1" name="chiclet_row_panel_list" width="318"/> </scroll_container> diff --git a/indra/newview/skins/default/xui/en/floater_emoji_complete.xml b/indra/newview/skins/default/xui/en/floater_emoji_complete.xml new file mode 100644 index 0000000000..d290d647e8 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_emoji_complete.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + name="emoji_complete" + single_instance="true" + layout="topleft" + bg_opaque_image="Window_NoTitle_Foreground" + bg_alpha_image="Window_NoTitle_Background" + can_close="false" + can_dock="false" + can_drag_on_left="false" + can_minimize="false" + can_resize="false" + can_tear_off="false" + header_height="0" + legacy_header_height="0" + show_title="false" + width="240" + height="40" + > + <emoji_complete + name="emoji_complete_ctrl" + follows="top|left" + layout="topleft" + autosize="true" + vertical="true" + max_visible="7" + padding="4" + width="230" + height="30" + left="5" + top="5" + > + </emoji_complete> +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_emoji_picker.xml b/indra/newview/skins/default/xui/en/floater_emoji_picker.xml new file mode 100644 index 0000000000..d21f8c82bc --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_emoji_picker.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + name="emojipicker" + title="CHOOSE EMOJI" + help_topic="emojipicker" + single_instance="true" + can_minimize="false" + can_tear_off="false" + can_resize="true" + auto_close="true" + layout="topleft" + min_width="250" + chrome="true" + height="350" + width="304"> + <floater.string name="title_for_recently_used" value="Recently used"/> + <floater.string name="title_for_frequently_used" value="Frequently used"/> + <scroll_container + name="EmojiGridContainer" + layout="topleft" + follows="all" + ignore_arrow_keys="true" + top="25" + left="0" + height="275"> + <scrolling_panel_list + name="EmojiGrid" + layout="topleft" + follows="top|left|right" + padding="4" + spacing="0" + top="0" + left="0"/> + </scroll_container> + <panel + name="Groups" + layout="topleft" + follows="top|left|right" + top="0" + left="0" + height="25"> + <panel + name="Badge" + layout="bottomleft" + follows="bottom|left" + background_visible="true" + background_opaque="true" + bg_opaque_color="FrogGreen" + tab_stop="false" + bottom="0" + height="2" + width="20" + /> + </panel> + <text + name="Dummy" + type="string" + layout="bottomleft" + follows="bottom|left|right" + halign="center" + valign="center" + bottom="14" + left="10" + height="25">No emoji selected</text> +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_im_session.xml b/indra/newview/skins/default/xui/en/floater_im_session.xml index da84fbeea6..a6493c5e24 100644 --- a/indra/newview/skins/default/xui/en/floater_im_session.xml +++ b/indra/newview/skins/default/xui/en/floater_im_session.xml @@ -238,7 +238,7 @@ </layout_stack> </layout_panel> <layout_panel - height="35" + height="30" auto_resize="false" name="chat_layout_panel"> <layout_stack @@ -249,7 +249,7 @@ name="input_panels" top="0" bottom="-1" - left="0" + left="1" right="-1"> <layout_panel name="input_editor_layout_panel"> @@ -260,7 +260,7 @@ default_icon_name="Generic_Person" layout="topleft" left="3" - bottom="-9" + bottom="-4" visible="false" width="20" /> <group_icon @@ -270,7 +270,7 @@ default_icon_name="Generic_Group" layout="topleft" left="3" - bottom="-9" + bottom="-4" visible="false" width="20" /> <icon @@ -279,7 +279,7 @@ image_name="Nearby_chat_icon" layout="topleft" left="3" - bottom="-9" + bottom="-4" name="nearby_chat_icon" visible="false" width="20"/> @@ -288,17 +288,31 @@ expand_lines_count="5" follows="left|right|bottom" font="SansSerifSmall" - height="20" + height="20" is_expandable="true" text_tentative_color="TextFgTentativeColor" + bg_writeable_color="ScriptBackground" name="chat_editor" max_length="1023" spellcheck="true" tab_group="3" - bottom="-8" + bottom="-3" left_pad="5" - right="-5" + right="-30" wrap="true" /> + <button + name="emoji_recent_panel_toggle_btn" + tool_tip="Shows/hides recent emojis" + follows="right|bottom" + font="EmojiLarge" + image_hover_unselected="Toolbar_Middle_Over" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + image_overlay="Emoji_Picker_Icon" + bottom="-2" + right="-1" + height="25" + width="25"/> </layout_panel> <layout_panel auto_resize="false" @@ -319,6 +333,42 @@ </layout_panel> </layout_stack> </layout_panel> + <layout_panel + name="emoji_recent_layout_panel" + height="30" + auto_resize="false"> + <text + name="emoji_recent_empty_text" + follows="top|left|right" + layout="topleft" + auto_resize="false" + h_pad="20" + v_pad="10" + top="0" + left="1" + right="-65" + height="30" + >Recently used emojis will appear here</text> + <emoji_complete + name="emoji_recent_icons_ctrl" + follows="top|left|right" + layout="topleft" + max_visible="20" + top="0" + left="1" + right="-65" + height="30"/> + <button + name="emoji_picker_show_btn" + label="More" + tool_tip="Shows/hides emoji picker" + follows="right|bottom" + layout="topleft" + bottom="-5" + right="-3" + height="20" + width="60"/> + </layout_panel> </layout_stack> </view> </floater> diff --git a/indra/newview/skins/default/xui/en/floater_preview_notecard.xml b/indra/newview/skins/default/xui/en/floater_preview_notecard.xml index dcbdfa8794..ac5467c036 100644 --- a/indra/newview/skins/default/xui/en/floater_preview_notecard.xml +++ b/indra/newview/skins/default/xui/en/floater_preview_notecard.xml @@ -73,6 +73,8 @@ spellcheck="true" tab_group="1" top="46" + use_color="true" + show_emoji_helper="true" width="392" word_wrap="true"> Loading... diff --git a/indra/newview/skins/default/xui/en/fonts.xml b/indra/newview/skins/default/xui/en/fonts.xml index d88c267a95..40045625fd 100644 --- a/indra/newview/skins/default/xui/en/fonts.xml +++ b/indra/newview/skins/default/xui/en/fonts.xml @@ -3,6 +3,7 @@ <font name="default" comment="default font files (global fallbacks)"> <file>DejaVuSans.ttf</file> + <file functor="is_emoji">TwemojiSVG.ttf</file> <os name="Windows"> <file>meiryo.TTC</file> <file>MSGOTHIC.TTC</file> @@ -69,6 +70,11 @@ <file>DejaVuSans-BoldOblique.ttf</file> </font> + <font name="Emoji" + comment="Name of emoji font"> + <file>TwemojiSVG.ttf</file> + </font> + <font name="Monospace" comment="Name of monospace font"> <file>DejaVuSansMono.ttf</file> diff --git a/indra/newview/skins/default/xui/en/menu_login.xml b/indra/newview/skins/default/xui/en/menu_login.xml index 96fac1c6e8..40399b33ef 100644 --- a/indra/newview/skins/default/xui/en/menu_login.xml +++ b/indra/newview/skins/default/xui/en/menu_login.xml @@ -161,6 +161,32 @@ <menu_item_separator /> <menu create_jump_keys="true" + label="Fonts" + name="Fonts" + tear_off="true"> + <menu_item_call + label="Show Font Test" + name="Show Font Test"> + <menu_item_call.on_click + function="Floater.Show" + parameter="font_test" /> + </menu_item_call> + <menu_item_separator /> + <menu_item_call + label="Dump Fonts" + name="Dump Fonts"> + <menu_item_call.on_click + function="Develop.Fonts.Dump" /> + </menu_item_call> + <menu_item_call + label="Dump Font Textures" + name="Dump Font Textures"> + <menu_item_call.on_click + function="Develop.Fonts.DumpTextures" /> + </menu_item_call> + </menu> + <menu + create_jump_keys="true" label="UI Tests" name="UI Tests" tear_off="true"> diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 660f4b62c7..38763cd9a8 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -3531,6 +3531,18 @@ function="World.EnvPreset" parameter="http://duckduckgo.com"/> </menu_item_call> <menu_item_call + label="Dump Fonts" + name="Dump Fonts"> + <menu_item_call.on_click + function="Develop.Fonts.Dump" /> + </menu_item_call> + <menu_item_call + label="Dump Font Textures" + name="Dump Font Textures"> + <menu_item_call.on_click + function="Develop.Fonts.DumpTextures" /> + </menu_item_call> + <menu_item_call label="Dump SelectMgr" name="Dump SelectMgr"> <menu_item_call.on_click @@ -3599,6 +3611,14 @@ function="World.EnvPreset" function="Advanced.ToggleDebugViews" /> </menu_item_check> <menu_item_check + label="Debug Unicode" + name="Debug Unicode"> + <menu_item_check.on_check + function="Advanced.CheckDebugUnicode" /> + <menu_item_check.on_click + function="Advanced.ToggleDebugUnicode" /> + </menu_item_check> + <menu_item_check label="Debug Name Tooltips" name="Debug Name Tooltips"> <menu_item_check.on_check @@ -4405,7 +4425,7 @@ function="World.EnvPreset" <menu_item_call.on_click function="PromptShowURL" name="PublicIssueTracker_url" - parameter="WebLaunchPublicIssue,http://jira.secondlife.com" /> + parameter="WebLaunchPublicIssue,https://feedback.secondlife.com/" /> </menu_item_call> <menu_item_call label="Public Issue Tracker Help" diff --git a/indra/newview/skins/default/xui/en/panel_preferences_graphics1.xml b/indra/newview/skins/default/xui/en/panel_preferences_graphics1.xml index fe74cea2f1..8a9d3b755e 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_graphics1.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_graphics1.xml @@ -27,7 +27,7 @@ left_delta="110" name="preset_text" top="5" - width="120"> + width="320"> (None) </text> diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 5d33853adc..3fbb5bc7e8 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -491,9 +491,9 @@ http://secondlife.com/support for help fixing this problem. <!-- build floater --> <string name="multiple_textures">Multiple</string> -<string name="use_texture">Use texture</string> - <string name="manip_hint1">Move mouse cursor over ruler</string> - <string name="manip_hint2">to snap to grid</string> + <string name="use_texture">Use texture</string> + <string name="manip_hint1">Move mouse cursor over ruler</string> + <string name="manip_hint2">to snap to grid</string> <!-- world map --> <string name="texture_loading">Loading...</string> @@ -508,14 +508,14 @@ http://secondlife.com/support for help fixing this problem. <!-- Chat --> <string name="NearbyChatTitle">Nearby chat</string> - <string name="NearbyChatLabel">(Nearby chat)</string> + <string name="NearbyChatLabel">(Nearby chat)</string> <string name="whisper">whispers:</string> <string name="shout">shouts:</string> <string name="ringing">Connecting to in-world Voice Chat...</string> <string name="connected">Connected</string> <string name="unavailable">Voice not available at your current location</string> <string name="hang_up">Disconnected from in-world Voice Chat</string> - <string name="reconnect_nearby">You will now be reconnected to Nearby Voice Chat</string> + <string name="reconnect_nearby">You will now be reconnected to Nearby Voice Chat</string> <string name="ScriptQuestionCautionChatGranted">'[OBJECTNAME]', an object owned by '[OWNERNAME]', located in [REGIONNAME] at [REGIONPOS], has been granted permission to: [PERMISSIONS].</string> <string name="ScriptQuestionCautionChatDenied">'[OBJECTNAME]', an object owned by '[OWNERNAME]', located in [REGIONNAME] at [REGIONPOS], has been denied permission to: [PERMISSIONS].</string> <string name="AdditionalPermissionsRequestHeader">If you allow access to your account, you will also be allowing the object to:</string> diff --git a/indra/newview/skins/default/xui/en/widgets/chat_editor.xml b/indra/newview/skins/default/xui/en/widgets/chat_editor.xml index f9facb593a..c550f634e5 100644 --- a/indra/newview/skins/default/xui/en/widgets/chat_editor.xml +++ b/indra/newview/skins/default/xui/en/widgets/chat_editor.xml @@ -1,4 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes" ?> <chat_editor name="chat_editor" - show_context_menu="true"/> + show_context_menu="true" + show_emoji_helper="true" + use_color="true" + /> diff --git a/indra/newview/skins/default/xui/en/widgets/chat_history.xml b/indra/newview/skins/default/xui/en/widgets/chat_history.xml index c0a948931c..c4300c9350 100644 --- a/indra/newview/skins/default/xui/en/widgets/chat_history.xml +++ b/indra/newview/skins/default/xui/en/widgets/chat_history.xml @@ -10,11 +10,11 @@ bottom_separator_pad="1" top_header_pad="12" bottom_header_pad="5" - max_length="2147483647" - track_bottom="true" - name="chat_history" - type="string" - word_wrap="true" + max_length="2147483647" + track_bottom="true" + name="chat_history" + type="string" + word_wrap="true" line_spacing.multiple="1.0" font="SansSerif"> <more_chat_text diff --git a/indra/newview/skins/default/xui/en/widgets/emoji_complete.xml b/indra/newview/skins/default/xui/en/widgets/emoji_complete.xml new file mode 100644 index 0000000000..6cc8d7118f --- /dev/null +++ b/indra/newview/skins/default/xui/en/widgets/emoji_complete.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<emoji_complete + autosize="false" + hover_image="ListItem_Over" + selected_image="ListItem_Select" + max_visible="7" + padding="8" + > +</emoji_complete> diff --git a/indra/newview/skins/default/xui/es/emoji_categories.xml b/indra/newview/skins/default/xui/es/emoji_categories.xml new file mode 100644 index 0000000000..b1b73eba5e --- /dev/null +++ b/indra/newview/skins/default/xui/es/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>emoticonos y emoción</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>personas y cuerpo</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>componentes</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>animales y la naturaleza</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>comida y bebida</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>viajes y lugares</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>actividades</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>objetos</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>símbolos</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/fr/emoji_categories.xml b/indra/newview/skins/default/xui/fr/emoji_categories.xml new file mode 100644 index 0000000000..38dc9cb8f8 --- /dev/null +++ b/indra/newview/skins/default/xui/fr/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>smileys et émotion</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>les gens et le corps</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>composants</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>animaux et la nature</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>nourriture et boissons</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>voyages et lieux</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>activités</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>objets</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>symboles</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/it/emoji_categories.xml b/indra/newview/skins/default/xui/it/emoji_categories.xml new file mode 100644 index 0000000000..a4782e60a6 --- /dev/null +++ b/indra/newview/skins/default/xui/it/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>smileys and emozione</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>persone e corpo</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>componenti</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>animali and natura</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>cibo e bevande</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>viaggi and luoghi</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>attività</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>oggetti</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>simboli</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/ja/emoji_categories.xml b/indra/newview/skins/default/xui/ja/emoji_categories.xml new file mode 100644 index 0000000000..7750f4ad2e --- /dev/null +++ b/indra/newview/skins/default/xui/ja/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>スマイリーと感情</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>人体</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>コンポーネント</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>動物自然</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>飲み物・食べ物</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>旅行・場所</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>有効化</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>オブジェクト</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>シンボル</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/pl/emoji_categories.xml b/indra/newview/skins/default/xui/pl/emoji_categories.xml new file mode 100644 index 0000000000..9aad7af794 --- /dev/null +++ b/indra/newview/skins/default/xui/pl/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>buźki and emocje</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>ludzie and ciało</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>składniki</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>zwierzęta and przyroda</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>jedzenie i picie</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>podróże and miejsca</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>aktywność</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>objekt</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>symbole</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/pt/emoji_categories.xml b/indra/newview/skins/default/xui/pt/emoji_categories.xml new file mode 100644 index 0000000000..887444b957 --- /dev/null +++ b/indra/newview/skins/default/xui/pt/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>sorrisos e emoção</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>pessoas e corpo</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>componentes</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>animais e natureza</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>comida e bebida</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>viagens e lugares</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>atividades</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>objetos</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>símbolos</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/ru/emoji_categories.xml b/indra/newview/skins/default/xui/ru/emoji_categories.xml new file mode 100644 index 0000000000..b08f0d8117 --- /dev/null +++ b/indra/newview/skins/default/xui/ru/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>смайлики и люди</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>тело людей</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>компонент</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>животные и природа</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>еда и напитки</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>путешествия и местности</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>варианты досуга</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>предметы</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>символы</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/zh/emoji_categories.xml b/indra/newview/skins/default/xui/zh/emoji_categories.xml new file mode 100644 index 0000000000..fbe6165eeb --- /dev/null +++ b/indra/newview/skins/default/xui/zh/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>笑脸</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>人体</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>组件</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>野生动物</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>食物飲料</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>旅遊地點</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>个人活动</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>物件</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>人的符号</string> + </map> + </array> +</llsd> diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 1fa4df1682..c7f32d0da9 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -141,7 +141,7 @@ class ViewerManifest(LLManifest): self.path("*.tga") # Include our fonts - with self.prefix(src_dst="fonts"): + with self.prefix(src="../packages/fonts",src_dst="fonts"): self.path("*.ttf") self.path("*.txt") @@ -559,6 +559,10 @@ class Windows_x86_64_Manifest(ViewerManifest): self.path("OpenAL32.dll") self.path("alut.dll") + # For ICU4C + self.path("icudt48.dll") + self.path("icuuc48.dll") + # For textures self.path("openjp2.dll") diff --git a/indra/test/test.cpp b/indra/test/test.cpp index a265e1273b..94478a5263 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -54,6 +54,12 @@ #endif #ifndef LL_WINDOWS + +typedef struct { + void *re_pcre; + size_t re_nsub; + size_t re_erroffset; +} regex_t; #include <gmock/gmock.h> #include <gtest/gtest.h> #endif |