diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 19a6a0ef6f..d23678a01d 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -4,8 +4,8 @@ on: workflow_dispatch: pull_request: push: - branches: ["*"] - tags: ["*"] + branches: ["main", "release/*", "project/*"] + tags: ["Second_Life_*"] jobs: build: diff --git a/.github/workflows/build_viewer.yml b/.github/workflows/build_viewer.yml index e4d0f3aa52..f50c4b8e8a 100644 --- a/.github/workflows/build_viewer.yml +++ b/.github/workflows/build_viewer.yml @@ -44,8 +44,8 @@ jobs: echo "/usr/local/bin" >> $GITHUB_PATH echo "$(brew --prefix)/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 id: py311 with: python-version: '3.11' @@ -130,12 +130,12 @@ jobs: shell: bash - name: Get the code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Checkout build vars (after the main code) - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: FirestormViewer/fs-build-variables path: build-variables @@ -242,11 +242,17 @@ jobs: if: env.FS_RELEASE_TYPE == 'Nightly' run: | echo "EXTRA_ARGS=${{ env.EXTRA_ARGS}} --testbuild=14" >> $GITHUB_ENV + shell: bash - name: make Alpha builds expire after 28 days if: env.FS_RELEASE_TYPE == 'Alpha' run: | echo "EXTRA_ARGS=${{ env.EXTRA_ARGS}} --testbuild=28" >> $GITHUB_ENV - + shell: bash + - name: make Beta builds expire after 28 days + if: env.FS_RELEASE_TYPE == 'Beta' + run: | + echo "EXTRA_ARGS=${{ env.EXTRA_ARGS}} --testbuild=60" >> $GITHUB_ENV + shell: bash - name: Clean up packages to give more space run: rm *${{ env.fallback_platform }}*bz2 shell: bash @@ -269,7 +275,7 @@ jobs: # fi - name: Publish artifacts if: runner.os == 'Windows' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.FS_RELEASE_TYPE }}-${{ matrix.os }}-${{ matrix.addrsize }}-${{ matrix.grid }}-artifacts.zip path: | @@ -278,7 +284,7 @@ jobs: - name: publish Linux artifacts if: runner.os == 'Linux' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.FS_RELEASE_TYPE }}-${{ matrix.os }}-${{matrix.addrsize}}-${{matrix.grid}}-artifacts.zip path: | @@ -287,7 +293,7 @@ jobs: - name: publish MacOS artifacts if: runner.os == 'macOS' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.FS_RELEASE_TYPE }}-${{ matrix.os }}-${{matrix.addrsize}}-${{matrix.grid}}-artifacts.zip path: | @@ -355,7 +361,7 @@ jobs: echo "FS_BUILD_WEBHOOK_URL=${FS_BUILD_WEBHOOK_URL}" >> $GITHUB_ENV - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 id: download with: path: to_deploy diff --git a/.github/workflows/cla.yaml b/.github/workflows/cla.yaml index fa180c66c9..b4b2565889 100644 --- a/.github/workflows/cla.yaml +++ b/.github/workflows/cla.yaml @@ -23,3 +23,4 @@ jobs: path-to-signatures: signatures.json remote-organization-name: secondlife remote-repository-name: cla-signatures + allowlist: callum@mbp.localdomain diff --git a/.gitignore b/.gitignore index f4b85b77a2..5e3377d62a 100755 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ indra/newview/dbghelp.dll indra/newview/filters.xml indra/newview/fmod.dll indra/newview/fmod.log +#indra/newview/fonts # Don't copy fonts to the source folder 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/README.md b/README.md index cb09b89340..64993294bf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ - - Firestorm Viewer Logo - +![Firestorm Viewer Logo](doc/firestorm_256.png) **[Firestorm](https://www.firestormviewer.org/) is a free client for 3D virtual worlds such as Second Life and various OpenSim worlds where users can create, connect and chat with others from around the world.** This repository contains the official source code for the Firestorm viewer. @@ -14,15 +12,14 @@ Pre-built versions of the viewer releases for Windows, Mac and Linux can be down ## Build Instructions -Build instructions for each operating system can be found in the official [wiki](https://wiki.firestormviewer.org/). +Build instructions for each operating system can be found using the links below and in the official [wiki](https://wiki.firestormviewer.org/). -- [Windows](https://wiki.firestormviewer.org/fs_compiling_firestorm_windows) +- [Windows](doc/building_windows.md) +- [Mac](doc/building_macos.md) +- [Linux](doc/building_linux.md) -- [Mac](https://wiki.firestormviewer.org/fs_compiling_firestorm_macos) - -- [Linux](https://wiki.firestormviewer.org/fs_compiling_firestorm_linux) - -Please note that we do not provide support for compiling the viewer or issues resulting from using a self-compiled viewer. However, there is a self-compilers group within Second Life that can be joined to ask questions related to compiling the viewer: [Firestorm Self Compilers](secondlife:///app/group/2014ce4c-5393-fb67-83ac-910db84c273c/about) +> [!NOTE] +> We do not provide support for compiling the viewer or issues resulting from using a self-compiled viewer. However, there is a self-compilers group within Second Life that can be joined to ask questions related to compiling the viewer: [Firestorm Self Compilers](https://tinyurl.com/firestorm-self-compilers) ## Contribute diff --git a/README_BUILD_FIRESTORM_LINUX.txt b/README_BUILD_FIRESTORM_LINUX.txt deleted file mode 100755 index 3aa979da25..0000000000 --- a/README_BUILD_FIRESTORM_LINUX.txt +++ /dev/null @@ -1,48 +0,0 @@ -First, make sure gcc-7.5.0 and g++-7.5.0 are installed. - -If you want to use licensed FMOD or KDU build libraries (they are optional) you have to provision these yourself. -If you're licensing these with Phoenix/Firestorm, ask for the libraries for fmod and kdu. Put them into: - /opt/firestorm - -If you're a community builder, you'll need to build these libraries yourself, then change your autobuild.xml file to -point to your own versions, or create a different autobuild.xml with your customizations, and use this with autobuild -instead of our default autobuild.xml There are some examples of how to build FMOD on the LL Wiki and opensource-dev -mailing list. We've created a non-KDU build target to make this easier. Everywhere you see "ReleaseFS" below, use -"ReleaseFS_open" instead. This will perform the same build, using openjpeg instead of KDU. - -Available premade firestorm-specific build targets: - ReleaseFS (includes KDU, FMODSTUDIO) - ReleaseFS_open (no KDU, no FMODSTUDIO) - RelWithDebInfoFS_open (no KDU, no FMODSTUDIO) - -To build firestorm: - autobuild build -A64 -c ReleaseFS - -Other examples: - autobuild configure -A64 -c ReleaseFS # basic configuration step, don't build, just configure - autobuild configure -A64 -c ReleaseFS -- --clean # clean the output area first, then configure - autobuild configure -A64 -c ReleaseFS -- --chan Private-Yourname # configure with a custom channel - - autobuild build -A64 -c ReleaseFS --no-configure # default quick rebuild - autobuild build -A64 -c ReleaseFS --no-configure -- --clean # Clean rebuild - autobuild build -A64 -c ReleaseFS -- --package # Complete a build and package it into a tarball - -When using the --package switch you can set the XZ_DEFAULTS variable to -T0 to use all available CPU cores -to create the .tar.xz file. This can significantly reduce the time needed to create the archive, but it will -use a lot more memory. For example: - export XZ_DEFAULTS="-T0" - autobuild build -A64 -c ReleaseFS_open -- --package - -If you want to build with clang you can call autobuild like this: - CC=clang CXX=clang++ autobuild configure -A64 -c ReleaseFS - -Any of the configure options can also be used (and do the same thing) with the build options. -Typical LL autobuild configure options should also work, as long as they don't duplicate configuration we are -already doing. - - -Logs: - Look for logs in build-linux-x86_64/logs - -Output: - Look for output in build-linux-x86_64/newview/Release diff --git a/README_BUILD_FIRESTORM_MACOSX.txt b/README_BUILD_FIRESTORM_MACOSX.txt deleted file mode 100755 index c44631128e..0000000000 --- a/README_BUILD_FIRESTORM_MACOSX.txt +++ /dev/null @@ -1,64 +0,0 @@ -Make sure Xcode is installed, it's a (sometimes) free download from Apple. -Make sure cmake is installed, use at least a 2.8.x version. - -If you are using OSX 10.5 or 10.6, Xcode 3 will be used. Output will work on -all 10.5 systems and higher. - -If you are using OSX 10.7 (ie, Lion), the build will use Xcode 4 and your -output will only work on OSX 10.6 and higher unless you install the OS X -10.5 SDK. You can copy that from an Xcode 3 installation; you need the -directory /Developer/SDKs/MacOSX10.5.sdk and its contents. If you do this, -then the output will work on 10.5. - -Ensure you can build a stock viewer-development tree as described in the SL -wiki. Before asking for any help compiling Firestorm, make sure you can -build viewer-development first. If you try and skip this step, you may -receive much less help. -http://wiki.secondlife.com/wiki/Compiling_the_viewer_(Mac_OS_X) - -If you want to use licensed FMOD or KDU build libraries (they are optional) -you have to provision these yourself. If you're licensing these with -Phoenix/Firestorm, ask for the libraries for fmod and kdu. Put them into: - /opt/firestorm - -If you're a community builder, you'll need to build these libraries -yourself, then change your autobuild.xml file to point to your own versions, -or create a different autobuild.xml with your customizations, and use this -with autobuild instead of our default autobuild.xml There are some examples -of how to build FMOD on the LL Wiki and opensource-dev mailing list. We've -created a non-KDU build target to make this easier. Everywhere you see -"ReleaseFS" below, use "ReleaseFS_open" instead. This will perform the same -build, using openjpeg instead of KDU. - -Available premade firestorm-specific build targets: - - ReleaseFS (includes KDU, FMOD) - ReleaseFS_open (no KDU, no FMOD) - RelWithDebInfo_open (no KDU, no FMOD) - -To build firestorm: - - autobuild build -c ReleaseFS - -Other examples: - autobuild configure -c ReleaseFS # basic configuration step, don't build, just configure - autobuild configure -c ReleaseFS -- --clean # clean the output area first, then configure - autobuild configure -c ReleaseFS -- --chan Private-Yourname # configure with a custom channel - - autobuild build -c ReleaseFS --no-configure # default quick rebuild - autobuild build -c ReleaseFS --no-configure -- --clean # Clean rebuild - -Any of the configure options can also be used (and do the same thing) with -the build options. Typical LL autobuild configure options should also work, -as long as they don't duplicate configuration we are already doing. - - -Logs: - - Look for logs in build-darwin-i386/logs - -Output: - Look for output in build-darwin-i386/newview/Release - - - diff --git a/README_BUILD_FIRESTORM_WINDOWS.txt b/README_BUILD_FIRESTORM_WINDOWS.txt deleted file mode 100755 index bc7909e9f0..0000000000 --- a/README_BUILD_FIRESTORM_WINDOWS.txt +++ /dev/null @@ -1,50 +0,0 @@ -Before you start configuring your Windows build system, be aware of our tested configurations: - Memory: You will need at least 2GB RAM, 4GB strongly recommended. - CPU: Multiple CPUs are strongly recommended. - A build can take over an hour. - Visual Studio 2017/2022 Community Edition. - -Ensure you can build a stock viewer-development try as described in the SL wiki. Before asking for any help -compiling Firestorm, make sure you can build the Second Life viewer first. If you try and skip this step, you may -receive much less help. https://wiki.secondlife.com/wiki/Build_the_Viewer_on_Windows - -If you want to use licensed FMOD or KDU build libraries (they are optional) you have to provision these yourself. -If you're licensing these with Phoenix/Firestorm, ask for the libraries for fmod and kdu. Put them into: - - c:\cygwin\opt\firestorm - -If you're a community builder, you'll need to build these libraries yourself, then change your autobuild.xml file to -point to your own versions, or create a different autobuild.xml with your customizations, and use this with autobuild -instead of our default autobuild.xml There are some examples of how to build FMOD on the LL Wiki and opensource-dev -mailing list. We've created a non-KDU build target to make this easier. Everywhere you see "ReleaseFS" below, use -"ReleaseFS_open" instead. This will perform the same build, using OpenJpeg instead of KDU. - - -To build Firestorm - a more detailed instruction can be found at https://wiki.firestormviewer.org/fs_compiling_firestorm_windows: - -Open a CMD shell and navigating to your firestorm code repo: - - autobuild build -A 64 -c ReleaseFS - -Other build targets you may use are: - - ReleaseFS (includes KDU, FMOD) - ReleaseFS_open (no KDU, no FMOD) - RelWithDebInfoFS_open (no KDU, no FMOD) - -Other examples: - autobuild configure -A 64 -c ReleaseFS # basic configuration step, don't build, just configure - autobuild configure -A 64 -c ReleaseFS -- --clean # clean the output area first, then configure - autobuild configure -A 64 -c ReleaseFS -- --chan Private-Yourname # configure with a custom channel - - autobuild build -A 64 -c ReleaseFS --no-configure # default quick rebuild - -If you want to set custom configuration, do this in the configure step separately from build, then run "autobuild -build -A 64 -c ReleaseFS --no-configure" as a secondary step. - - -Logs: - Look for logs in build-vc1x0-64/logs - -Output: - Look for output in build-vc1x0-32/newview/Release diff --git a/autobuild.xml b/autobuild.xml index d0a22c65b1..7319b07230 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -530,11 +530,11 @@ archive hash - 59c1827cab82516504a2eb31e0aa7e38035b5085 + 8539775e0a0783bd252bc548b20b3472a8254c31 hash_algorithm sha1 url - https://github.com/secondlife/3p-boost/releases/download/v1.81-90bb2df/boost-1.81-darwin64-90bb2df.tar.zst + https://github.com/secondlife/3p-boost/releases/download/v1.81-09d25a7/boost-1.81-darwin64-09d25a7.tar.zst name darwin64 @@ -556,11 +556,11 @@ archive hash - 26214a33c568929ffeeb3463ce183f2888ce4fe4 + d40c86fbcb6ce064d546165cbabbf035ea80e07b hash_algorithm sha1 url - https://github.com/secondlife/3p-boost/releases/download/v1.81-90bb2df/boost-1.81-windows64-90bb2df.tar.zst + https://github.com/secondlife/3p-boost/releases/download/v1.81-09d25a7/boost-1.81-windows64-09d25a7.tar.zst name windows64 @@ -573,7 +573,7 @@ copyright (see individual source files) version - 1.81 + 1.81-09d25a7 name boost description @@ -634,11 +634,11 @@ archive hash - 7f447d30d7add80270a55cf3c53000392821a1cb + b1bb8a9c8d458d8842d79f9633fb61df12f1b0ad hash_algorithm sha1 url - https://github.com/secondlife/3p-colladadom/releases/download/v2.3.d1ef72a/colladadom-2.3.d1ef72a-darwin64-d1ef72a.tar.zst + https://github.com/secondlife/3p-colladadom/releases/download/v2.3.ab0c124/colladadom-2.3.ab0c124-darwin64-ab0c124.tar.zst name darwin64 @@ -660,11 +660,11 @@ archive hash - b32294a2f31f5b4ca49928e66832aad1bb4a88ac + 0df4c05d4efa3019afa4cbf09599df60b586fc5c hash_algorithm sha1 url - https://github.com/secondlife/3p-colladadom/releases/download/v2.3.d1ef72a/colladadom-2.3.d1ef72a-windows64-d1ef72a.tar.zst + https://github.com/secondlife/3p-colladadom/releases/download/v2.3.ab0c124/colladadom-2.3.ab0c124-windows64-ab0c124.tar.zst name windows64 @@ -677,7 +677,7 @@ copyright Copyright 2006 Sony Computer Entertainment Inc. version - 2.3.d1ef72a + 2.3.ab0c124 name colladadom @@ -885,6 +885,50 @@ description 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. + emoji_shortcodes + + canonical_repo + https://github.com/secondlife/3p-emoji-shortcodes + copyright + Copyright 2017-2019 Miles Johnson. + description + Emoji shortcodes + license + MIT + license_file + LICENSES/emojibase-license.txt + name + emoji_shortcodes + platforms + + darwin64 + + archive + + hash + 7ac35da9b1b5c9a05954edeef3fe8e54 + url + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/113242/980233/emoji_shortcodes-6.1.0.579438-darwin64-579438.tar.bz2 + + name + darwin64 + + windows64 + + archive + + hash + 087ce7e6d93dcd88b477b10d8e1ab259 + url + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/113243/980244/emoji_shortcodes-6.1.0.579438-windows64-579438.tar.bz2 + + name + windows64 + + + version + 6.1.0.579438 + expat platforms @@ -952,11 +996,11 @@ archive hash - bfcc4acbe6b8dddaf10f0f7a47e4aad8 + 3f66914b7931a7e45b50c9f947eed3d1 hash_algorithm md5 url - file:///opt/firestorm/fmodstudio-2.02.18-darwin64-232960129.tar.bz2 + file:///opt/firestorm/fmodstudio-2.02.20-darwin64-240390127.tar.bz2 name darwin64 @@ -966,11 +1010,11 @@ archive hash - 14846f1a55274399362db4b674fcded6 + bbb978f21e690599aedcd44658dceeaf hash_algorithm md5 url - file:///opt/firestorm/fmodstudio-2.02.18-linux64-232801304.tar.bz2 + file:///opt/firestorm/fmodstudio-2.02.20-linux64-240390132.tar.bz2 name linux64 @@ -980,11 +1024,11 @@ archive hash - 7b38bd83eb63f25df7c5a20070a05732 + 8672d21ae8382a5526f0e17358de8575 hash_algorithm md5 url - file:///c:/cygwin/opt/firestorm/fmodstudio-2.02.18-windows64-232801156.tar.bz2 + file:///c:/cygwin/opt/firestorm/fmodstudio-2.02.20-windows64-240381643.tar.bz2 name windows64 @@ -997,7 +1041,7 @@ copyright FMOD Studio by Firelight Technologies Pty Ltd. version - 2.02.18 + 2.02.20 name fmodstudio description @@ -1035,6 +1079,16 @@ freetype + copyright + Copyright 2006, 2007, 2008, 2009, 2010 by David Turner, Robert Wilhelm, and Werner Lemberg. + description + Font rendering library + license + FreeType + license_file + LICENSES/freetype.txt + name + freetype platforms darwin64 @@ -1042,11 +1096,11 @@ archive hash - 912d122aae996483ba814fe8e569394ddca0d42e + d90a5c2fb4a729eeff3965ea6dd0a35cf146d379 hash_algorithm sha1 url - https://github.com/secondlife/3p-freetype/releases/download/v2.4.4.4f739fa/freetype-2.4.4.4f739fa-darwin64-4f739fa.tar.zst + https://github.com/secondlife/3p-freetype/releases/download/v.2.12.1.557becd/freetype-2.12.1.557becd-darwin64-557becd.tar.zst name darwin64 @@ -1056,9 +1110,11 @@ archive hash - 41db760508e6b48ae3a291ad2d1aef3d + 4a999279562e8f3e4ba02d3e78a844ddf6fe18f1 + hash_algorithm + sha1 url - https://3p.firestormviewer.org/freetype-2.8.1.223020901-linux64-223020901.tar.bz2 + https://github.com/secondlife/3p-freetype/releases/download/v.2.12.1.557becd/freetype-2.12.1.557becd-linux64-557becd.tar.zst name linux64 @@ -1068,26 +1124,18 @@ archive hash - 2c2151b439cf92e03d9573dddbbdd3b6 + 1837fdfd44204c78e79134944f824b0211817883 + hash_algorithm + sha1 url - https://3p.firestormviewer.org/freetype-2.3.9.232991153-windows64-232991153.tar.bz2 + https://github.com/secondlife/3p-freetype/releases/download/v.2.12.1.557becd/freetype-2.12.1.557becd-windows64-557becd.tar.zst name windows64 - license - FreeType - license_file - LICENSES/freetype.txt - copyright - Copyright 2006, 2007, 2008, 2009, 2010 by David Turner, Robert Wilhelm, and Werner Lemberg. version - 2.4.4.4f739fa - name - freetype - description - Font rendering library + 2.12.1.557becd glext @@ -1162,11 +1210,11 @@ archive hash - c016d7333a3ded88c060119b4e3a5847015a8711 + dce3174b12136746f5f910e311e895c1b47bf8fb hash_algorithm sha1 url - https://github.com/secondlife/3p-googlemock/releases/download/v1.7.0.77bba00/googlemock-1.7.0.77bba00-darwin64-77bba00.tar.zst + https://github.com/secondlife/3p-googlemock/releases/download/v1.7.0.2b109d4/googlemock-1.7.0.2b109d4-darwin64-2b109d4.tar.zst name darwin64 @@ -1188,11 +1236,11 @@ archive hash - df51dff9a820fc96c18c2bc00b64467e541633a5 + 265813f84b04c3b03f3d7d33e149b3d5e3cf31db hash_algorithm sha1 url - https://github.com/secondlife/3p-googlemock/releases/download/v1.7.0.77bba00/googlemock-1.7.0.77bba00-windows64-77bba00.tar.zst + https://github.com/secondlife/3p-googlemock/releases/download/v1.7.0.2b109d4/googlemock-1.7.0.2b109d4-windows64-2b109d4.tar.zst name windows64 @@ -1205,7 +1253,7 @@ copyright Copyright 2008, Google Inc. version - 1.7.0.77bba00 + 1.7.0.2b109d4 name googlemock description @@ -1301,6 +1349,54 @@ description Havok source code for libs and demos + icu4c + + canonical_repo + https://bitbucket.org/lindenlab/3p-icu4c + copyright + Copyright (c) 1995-2011 International Business Machines Corporation and others <http://source.icu-project.org> + description + 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. + license + ICU, permissive non-copyleft free software license + license_file + LICENSES/icu.txt + name + icu4c + platforms + + darwin64 + + archive + + hash + 47bc32b991385f1a6530e4c6179b07f64ca6edc7 + hash_algorithm + sha1 + url + https://github.com/secondlife/3p-icu4c/releases/download/v4.8.1-7d08d82/icu4c-4.8.1-darwin64-7d08d82.tar.zst + + name + darwin64 + + windows64 + + archive + + hash + b7db881dac80302e4d9010af34c0bf6ca9897df9 + hash_algorithm + sha1 + url + https://github.com/secondlife/3p-icu4c/releases/download/v4.8.1-7d08d82/icu4c-4.8.1-windows64-7d08d82.tar.zst + + name + windows64 + + + version + 4.8.1-7d08d82 + jpegencoderbasic platforms @@ -2220,6 +2316,62 @@ description minizip-ng is a zip manipulation library. Based on work of Gilles Vollant. + nanosvg + + canonical_repo + https://bitbucket.org/lindenlab/3p-nanosvg + copyright + Copyright (c) 2013-14 Mikko Mononen + description + NanoSVG is a simple single-header-file SVG parser and rasterizer + license + Zlib + license_file + LICENSES/nanosvg.txt + name + nanosvg + platforms + + darwin64 + + archive + + hash + 32ead724319c2ea6f65fc5be0e3157cc + url + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/115452/994130/nanosvg-2022.09.27-darwin64-580364.tar.bz2 + + name + darwin64 + + linux + + archive + + hash + 84698f044598ff79e255965f3d1c3e80 + url + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/115397/993664/nanosvg-2022.09.27-linux-580337.tar.bz2 + + name + linux + + windows64 + + archive + + hash + ee61ff8b866be04c325f1fe2db516d71 + url + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/115454/994144/nanosvg-2022.09.27-windows64-580364.tar.bz2 + + name + windows64 + + + version + 2022.09.27 + nghttp2 platforms @@ -2925,6 +3077,48 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors description 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. + viewer-fonts + + copyright + 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 + description + Viewer fonts + license + Various open source + license_file + LICENSES/fonts.txt + name + viewer-fonts + platforms + + darwin64 + + archive + + hash + 6041bbd4001e3951f96ac3456c7906da + url + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/113314/980656/viewer_fonts-1.579464-darwin64-579464.tar.bz2 + + name + darwin64 + + windows64 + + archive + + hash + 1745ba6eec0108250446fe01d4aa065c + url + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/113307/980631/viewer_fonts-1.579464-windows64-579464.tar.bz2 + + name + windows64 + + + version + 1.579464 + viewer-manager platforms @@ -4268,4 +4462,4 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors Firestorm Viewer - \ No newline at end of file + diff --git a/doc/building_linux.md b/doc/building_linux.md new file mode 100644 index 0000000000..7455f36feb --- /dev/null +++ b/doc/building_linux.md @@ -0,0 +1,285 @@ +# Build instructions for Linux + +> [!WARNING] +> Please note that we do not give support for compiling the viewer on your own. However, there is a self-compilers group in Second Life that can be joined to ask questions related to compiling the viewer: [Firestorm Self Compilers](https://tinyurl.com/firestorm-self-compilers) + +> [!NOTE] +> These instructions only apply to Firestorm versions with AlexIvy code. + +This procedure is based on discussions with the Firestorm Linux development team and is the only one recommended for Firestorm for Linux. System requirements are: +- Ubuntu 20.04 x64 fully upgraded +- 16GB or more RAM ([Low Memory Caution](#common-issuesbugsglitches-and-solutions)) +- 64GB hard drive space +- 4 or more core CPU (you could get by with 2 cores, but the process will take much longer) + +Due to the age of the build OS, it is recommended that you use a virtual machine, ensuring the guest can meet the hardware requirements. + +This procedure may or may not work on other Linux distributions. + +## Establish your programming environment + +This only needs to be done once. + +### Create your source tree + +Typically, Linux source code is stored in a src directory in your home directory. So: `mkdir ~/src` + +### Install required packages + +A few packages must be installed on the build system. Some may already be installed: + +| | | | | | | +| --------------- | ---------------- | ------------- | ------------------ | ---------------- | ------ | +| libgl1-mesa-dev | libglu1-mesa-dev | libpulse-dev | build-essential | python3-pip | git | +| libssl-dev | libxinerama-dev | libxrandr-dev | libfontconfig1-dev | libfreetype6-dev | gcc-10 | +| | | | | | | + +``` +sudo apt install libgl1-mesa-dev libglu1-mesa-dev libpulse-dev build-essential python3-pip git libssl-dev libxinerama-dev libxrandr-dev libfontconfig1-dev libfreetype6-dev gcc-10 +``` + +### CMake + +CMake version 3.18 is required but not available in Ubuntu's repositories, we have to build it from source + +``` +wget https://github.com/Kitware/CMake/releases/download/v3.18.0/cmake-3.18.0.tar.gz +tar xvf cmake-3.18.0.tar.gz +cd cmake-3.18.0 +./bootstrap --parallel=$(nproc) --prefix=/usr && make -j $(nproc) && sudo make install +``` + +### Install Autobuild + +Autobuild is a Linden Lab resource that does all the hard work. + +``` +sudo pip3 install --upgrade pip +sudo pip3 install git+https://github.com/secondlife/autobuild.git#egg=autobuild +``` + +Check Autobuild version to be "autobuild 3.8" or higher: `autobuild --version` + +## Download the source code + +There are two required repositories, the viewer itself and the build variables. An optional third repository is used to configure and package FMOD Studio. + +### Clone the viewer + +``` +cd ~/src +git clone https://github.com/FirestormViewer/phoenix-firestorm.git +``` + +This will create a folder called phoenix-firestorm and add all the source files. If you desire, you can choose a different folder name by adding the name to the end of the command: + +``` +git clone https://github.com/FirestormViewer/phoenix-firestorm.git NewDestinationDirectory +``` + +The rest of this document will assume the default directory, `phoenix-firestorm` + +### Clone the Autobuild build variables + +Autobuild 3.0 uses a separate file to control compiler options, switches, and the like for different configurations. + +``` +cd ~/src +git clone https://github.com/FirestormViewer/fs-build-variables.git +``` + +### Create FMOD Studio package (optional) + +Although not required, including FMOD Studio in your build will improve your audio-based experience. + +``` +cd ~/src +git clone https://github.com/FirestormViewer/3p-fmodstudio.git +cd ~/src/3p-fmodstudio +``` + +Open the file called `build-cmd.sh` and look at the fifth line down, it begins with `FMOD_VERSION`. This is the version of the API you need to download. + +The FMOD Studio API can be downloaded [here](https://www.fmod.com) (requires creating a free account to access the download section). + +Click the button representing the version you're after, then click the Download link for the Linux file. + +> [!IMPORTANT] +> Make sure to download the FMOD Studio API and not the FMOD Studio Tool! + +Copy that file to the `~/src/3p-fmodstudio` directory. + +``` +export AUTOBUILD_VARIABLES_FILE=$HOME/src/fs-build-variables/variables +autobuild build -A 64 --all +autobuild package -A 64 --results-file result.txt +``` + +Near the end of the output you will see the package name written, similar to: + +``` +wrote !/home/username/src/3p-fmodstudio/fmodstudio-2.01.02-linux64-202161533.tar.bz2 +``` + +Additionally, a file `result.txt` has been created containing the md5 hash value of the package file, which you will need in the next steps. + +``` +cd ~/src/phoenix-firestorm +``` + +Copy the FMOD Studio path and md5 value from the package process into this command: + +``` +autobuild installables edit fmodstudio platform=linux64 hash= url=file:/// +``` + +For example: + +``` +autobuild installables edit fmodstudio platform=linux64 hash=c3f696412ef74f1559c6d023efe3a087 url=file:///!/src/3p-fmodstudio/fmodstudio-2.00.07-linux64-200912220.tar.bz2 +``` + +> [!NOTE] +> Having modified autobuild.xml would require it be restored before trying to fetch any new commits or more especially if you push a commit. This can be done with `git reset --hard && git remote update` + +## Configure and build + +### Configuring the viewer + +Start by initializing the variables + +``` +export AUTOBUILD_VARIABLES_FILE=$HOME/src/fs-build-variables/variables +``` + +You can add that to `~/.bashrc` or `~/.profile` so they execute automatically, or execute them before you run autobuild. + +``` +cd ~/src/phoenix-firestorm +autobuild configure -A 64 -c ReleaseFS_open +``` + +This will set up to compile with all defaults and without non-default libraries. It will fetch any additional necessary libraries. + +Available premade firestorm-specific build targets: + +``` +ReleaseFS (includes KDU, FMOD) +ReleaseFS_open (no KDU, no FMOD) +RelWithDebInfo_open (no KDU, no FMOD) +``` + +### Configuration Switches + +There are a number of switches you can use to modify the configuration process. The name of each switch is followed by its type and then by the value you want to set. + +- **LL_TESTS** (bool) controls if the tests are compiled and run. There are quite a lot of them so excluding them is recommended unless you have some reason to need one or more of them. +- **clean** will cause autobuild to remove any previously compiled objects and fetched packages. It can be useful if you need to force a reload of all packages +- **package** will result in a bzip2 archive of the completed viewer. Enabled by default, you would have to use **-DPACKAGE:BOOL=Off** to disable it +- **chan** will set a unique channel (and the name) for the viewer, appending whatever is defined to "Firestorm-". By default, the channel is "private" followed by your computer's name. +- **fmodstudio** will tell autobuiild to use the FmodStudio package when compiling. + +Most switches start with a double-dash (\--). And if you use any switches you must enclose them with a double-dash at the start and an optional double-dash at the end. + +> [!TIP] +> **OFF** and **NO** are the same as **FALSE**; anything else is considered to be **TRUE** + +Examples: + +``` +autobuild configure -A 64 -c ReleaseFS_open -- -DLL_TESTS:BOOL=FALSE +autobuild configure -A 64 -c ReleaseFS_open -- --clean +autobuild configure -A 64 -c ReleaseFS_open -- --fmodstudio +autobuild configure -A 64 -c ReleaseFS_open -- --chan="MyBuild" +``` + +In the last example, the channel and resulting viewer name would be "Firestorm-MyBuild". + +The first time you configure, several additional files will be downloaded from Firestorm and Second Life sources. These are mostly binary packages maintained outside the viewer development itself. And if you use the `--clean` switch, you will re-download them all. + +## Compiling the viewer + +``` +autobuild build -A 64 -c ReleaseFS_open +``` + +Be sure to use the fmodstudio and chan switches again. + +Compiling can take quite a bit of time depending on your computer's processing power. + +> [!NOTE] +> It is possible to use autobuild to do both the configure step (only needed once) and the build step with one command (`autobuild build -A 64 -c ReleaseFS_open -- --clean [more switches]`). For clarity, they are mentioned separately. + +> [!TIP] +> When using the --package switch you can set the XZ_DEFAULTS variable to -T0 to use all available CPU cores to create the .tar.xz file. This can significantly reduce the time needed to create the archive, but it will use a lot more memory. For example: +> ``` +> export XZ_DEFAULTS="-T0" +> autobuild build -A64 -c ReleaseFS_open -- --package +> ``` + +### Copy out of the guest + +If you build the viewer using a virtual machine (guest), you will need to copy the viewer files over to your host or to a different machine in order to use the viewer. Your guest may not have sufficient resources to run the viewer. The rsync program can make copying easy and accurate. And if you build the viewer again, the options included in the command example will cause rsync to only copy the new files, cutting down on the time needed to copy. + +The build process created a ready-to-use viewer as well as a compressed archive. The archive can be copied or moved to any shared filesystem, such as a flash or cloud drive, and it could be installed or extracted in the same manner as is the official release. + +``` +cp ~/src/phoenix-firestorm/build-linux-x86_64/newview/Phoenix*.tar.* /path/to/shared/drive +``` + +or +``` +mv ~/src/phoenix-firestorm/build-linux-x86_64/newview/Phoenix*.tar.* /path/to/shared/drive +``` + +When copying the ready-to-run folders and files, use + +``` +rsync -rptgoDLK --update --progress ~/src/phoenix-firestorm/build-linux-x86_64/newview/packaged/* /path/to/destination +``` + +Using rsync has the advantage of updating the destination, replacing only those files that changed or are missing, which takes much less time than copying and replacing every file. + +## Running your newly built viewer + +### Running from a menu item + +Create the desktop launcher after copying to the destination machine + +``` +cd /path/to/firestorm +etc/refresh_desktop_app_entry.sh +``` + +Then open your applications menu and look in the Internet or Network branch for the Firestorm launcher. + +### Running from command line or file browser + +``` +cd /path/to/firestorm +./firestorm +``` + +## Troubleshooting + +### Handling problems + +If you encounter errors or run into problems, please first double check that you followed the steps correctly. One typo can break it. Then, check whether someone else already had the same issue. A solution might be known already. + +- **Firestorm Self-Compilers group:** [Firestorm Self Compilers](https://tinyurl.com/firestorm-self-compilers) is free to join, fellow self-compilers may be able to offer assistance. +- **Jira:** [JIRA](https://jira.firestormviewer.org) may contain resolved issues related to the error you're seeing. Search using the error you encountered. Or create a new issue to report an error in this document, or if a code change causes a build process to fail. + +### Common issues/bugs/glitches and solutions + +- **Virtual memory exhausted, c++ fatal error, or similar out-of-memory issues** may occur if you are building the viewer for the first time, or re-building after a very large set of changes were added. Sometimes restarting the build command will let you recover, sometimes you have to restart the build system. To avoid that from happening, add ram to your virtual environment, or add swap space, something on the order of 10GB or more. As well, reducing the number of CPU cores assigned, down to 4 or less, will lower ram usage. +- **Missing libraries/applications/packages:** This may occur if you did not or could not install the packages shown above, or are attempting to use this procedure with a different Linux OS, or are attempting to build a 32-bit viewer. Start over, making sure you are using the right OS, reinstall all packages as listed, and do not attempt to make a 32-bit viewer. +- **SDL2:** Currently the SDL2 install/update process has a problem if the SDL2 files already exist. The workaround is to delete those files: +``` +rm -rf ../build-linux-x86_64/packages/include/SDL2/ +rm -rf ../build-linux-x86_64/packages/docs/SDL/ +rm ../build-linux-x86_64/packages/LICENSES/SDL.txt +rm ../build-linux-x86_64/packages/lib/release/*SDL* +``` +- **Delayed sounds:** Some users have noted that OpenAL plays sounds from the viewer up to 20 seconds after they are triggered. There is no solution to this via the viewer, but there may be some solutions on the Internet. Compiling with FModStudio may resolve this issue. +- **No sounds:** The viewer will try to use whatever sound service you have running, but might need a little coaxing. Read through the firestorm script inside the program directlry, you will find various commented options. Uncommenting one or more may help restore sound, as can compiling with FModStudio. Refer also to the README.Linux.txt and README-linux-voice.txt files in the program directory. +- **Voice won't connect:** Refer to **[this link](https://wiki.firestormviewer.org/fs_voice#linux)** or the relevant link on **[this page](https://wiki.firestormviewer.org/linux)** to make needed adjustments to your computer and/or the SLVoice files. diff --git a/doc/building_macos.md b/doc/building_macos.md new file mode 100644 index 0000000000..f5ad4ea481 --- /dev/null +++ b/doc/building_macos.md @@ -0,0 +1,249 @@ +# Build instructions for macOS + +The viewer and its libraries are built with the current version of Xcode and Cmake, as well as the standard version of Autobuild from Linden Lab. + +> [!WARNING] +> Please note that we do not give support for compiling the viewer on your own. However, there is a self-compilers group in Second Life that can be joined to ask questions related to compiling the viewer: [Firestorm Self Compilers](https://tinyurl.com/firestorm-self-compilers) + +## Obtaining a shell to work with + +The steps listed below are expected to be run from a shell prompt. Simply copy and paste the commands, or type them out if you wish, into a shell prompt when told to. If you do not know how to acquire a shell prompt, open up the Applications folder, go to Utilities, then open Terminal. This will open a shell prompt. If you are a power user and know what a shell prompt is, open one now using your preferred method and program, then continue on to the next section. Leave this window open throughout the guide. You don't need to close it and re-open it after every command. + +## Getting development tools + +You will need to install the following tools: + +### Xcode +- Xcode, It's a free download from Apple, but the latest version (12.x) is too new, so you will need to get an older version. +- To download a version that will work (11.7 in this case), go [here](https://developer.apple.com/download) and log in with an apple ID. +- Either find the 11.7 download in the list or use this [direct link](https://download.developer.apple.com/Developer_Tools/Xcode_11.7/Xcode_11.7.xip) (will prompt you to log in if you skipped the above step.) +- Open the downloaded file and copy the Xcode application to the Applications folder. +- Open Xcode at least once to set up everything to compile the viewer later on. +- Test by running `clang --version` from the terminal window. +- It should report version 11 something (Apple clang version 11.0.3 (clang-1103.0.32.62) for example.) +- If it reports version 12, something is messed up and you installed Xcode 12. + +### CMake +- Download [CMake](http://www.cmake.org/download) version 3.16.0 or higher +- Open the downloaded file and copy CMake to the Applications folder. +- You will need to install the command line links manually. To do this, run from the terminal: `sudo /Applications/CMake.app/Contents/MacOS/CMake --install` +- Again, test by running: `cmake --version` from the terminal window. + +### Pip +The pip Python package installation tool is required for the next step. To install it, run from terminal: +``` +curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py +sudo python3 get-pip.py +``` + +### Autobuild +The Linden Lab [Autobuild](https://github.com/secondlife/autobuild) tool. +- Use the following command to install it on your machine: +``` +pip3 install --user git+https://bitbucket.org/lindenlab/autobuild.git#egg=autobuild +``` + +- Add it to your PATH environment variable so it can be found by the shell. The macOS-approved way to do this is to issue the following command (This change will not take effect until the next time you open a Terminal window.): +``` +echo '~/Library/Python/3.7/bin/' | sudo tee /etc/paths.d/99-autobuild +``` + +- If you do not want to close and re-open your terminal, type the following to add it your PATH (otherwise if you did close it down, re-open it as shown above [here](#obtaining-a-shell-to-work-with)): +``` +export PATH=$PATH:~/Library/Python/3.7/bin/ +``` + +- Check Autobuild version to be "autobuild 3.8" or higher: `autobuild --version` + +### Additional third party libraries +If you want to use licensed FMOD Studio API or KDU build libraries (they are optional) you have to provide these yourself. If you're building Firestorm as part of the project team, ask for the libraries for fmodstudio and kdu. Put them into `/opt/firestorm`. + +If you're a community builder, you'll need to build these libraries yourself, then change your autobuild.xml file to point to your own versions, or create a different autobuild.xml with your customizations, and use this with autobuild instead of our default autobuild.xml. There are some examples of how to build FMOD Studio on the LL Wiki and opensource-dev mailing list. + +We've created a non-KDU build target to make this easier. Everywhere you see "ReleaseFS" below, use "ReleaseFS_open" instead. This will perform the same build, using openjpeg instead of KDU and omitting FMOD Studio. + +Available premade firestorm-specific build targets: + +``` +ReleaseFS (includes KDU, FMOD) +ReleaseFS_open (no KDU, no FMOD) +RelWithDebInfo_open (no KDU, no FMOD) +``` + +To build firestorm: + +``` +autobuild build -c ReleaseFS +``` + +Other examples: + +``` +autobuild configure -c ReleaseFS # basic configuration step, don't build, just configure +autobuild configure -c ReleaseFS -- --clean # clean the output area first, then configure +autobuild configure -c ReleaseFS -- --chan Private-Yourname # configure with a custom channel + +autobuild build -c ReleaseFS --no-configure # default quick rebuild +autobuild build -c ReleaseFS --no-configure -- --clean # Clean rebuild + +autobuild configure -c ReleaseFS_open -- # configure with no third-party libraries +autobuild configure -c ReleaseFS_open -- --fmodstudio # configure with FMOD Studio but no KDU +``` + +Any of the configure options can also be used (and do the same thing) with the build options. Typical LL autobuild configure options should also work, as long as they don't duplicate configuration we are already doing. + +Logs: Look for logs in `build-darwin-x86_64/logs`. + +Output: Look for output in `build-darwin-x86_64/newview/Release`. + +## Set up your source code tree + +Plan your directory structure ahead of time. If you are going to be producing changes or patches you will be cloning a copy of an unaltered source code tree for every change or patch you make, so you might want to have all this work stored in its own directory. If you are a casual compiler and won't be producing any changes, you can use one directory. For this document, we will assume $HOME/firestorm. + +``` +mkdir ~/firestorm +cd ~/firestorm +git clone https://github.com/FirestormViewer/phoenix-firestorm.git +``` + +This can take a while. It's a rather large download. + +You will also need to download the build variables used for building the viewer. Like the viewer source, these are downloaded from the Firestorm git repository. Assuming you are still in your ~/firestorm directory (or wherever else you chose), issue the following commands: + +``` +git clone https://github.com/FirestormViewer/fs-build-variables.git +``` + +You will then need to add this line to your ~/.zshrc file (assuming you are on Catalina or later): +``` +echo 'AUTOBUILD_VARIABLES_FILE=~/firestorm/fs-build-variables/variables' | sudo tee ~/.zshrc +``` + +Again, if you do not wish to restart your terminal: +``` +export AUTOBUILD_VARIABLES_FILE=~/firestorm/fs-build-variables/variables +``` + +## Prepare third party libraries + +Most third party libraries needed to build the viewer will be automatically downloaded for you and installed into the build directory within your source tree during compilation. Some need to be manually prepared and are not normally required when using an open source configuration (ReleaseFS_open). + +### FMOD Studio using autobuild + +- Get the FMOD Studio API disk image installer for the Mac from the [FMOD site](https://www.fmod.com) (requires creating an account to access the download section). + +> [!IMPORTANT] +> Make sure to download the FMOD Studio API and not the FMOD Studio Tool! + +- Enter these commands into the terminal window: + +``` +git clone https://github.com/FirestormViewer/3p-fmodstudio.git +cd 3p-fmodstudio +``` + +- Place the installer disk image (.dmg) file you downloaded into the current directory. +- Issue the following commands: +``` +autobuild build +autobuild package --results-file result.txt +``` + +Near the top of the output you will see the package name written: + +``` +wrote /Users/yourname/3p-fmodstudio/fmodstudio-2.01.05-darwin-202981448.tar.bz2 +``` + +Additionally, a file `result.txt` has been created containing the md5 hash value of the package file, which you will need in the next step. + +- Next, update Firestorms autobuild.xml file to use your FMOD Studio. + +``` +cd ~/firestorm/phoenix-firestorm +cp autobuild.xml my_autobuild.xml +export AUTOBUILD_CONFIG_FILE=my_autobuild.xml +``` + +- Copy the fmodstudio path and md5 value from the package process into this command: + +``` +autobuild installables edit fmodstudio platform=darwin64 hash= url=file:/// +``` + +For example: +``` +autobuild installables edit fmodstudio platform=darwin64 hash=3b0d38f2a17ff1b73c8ab6dffbd661eb url=file:///Users/yourname/3p-fmodstudio/https://fmodstudio-2.01.05-darwin-202981448.tar.bz2 +``` +> [!NOTE] +> Having to copy autobuild.xml and modify the copy from within a cloned repository is a lot of work for every repository you make, but this is the only way to guarantee you pick up upstream changes to autobuild.xml and do not send up a modified autobuild.xml when you do a git push. + +## Configuring the Viewer + +The following is all still done from within the terminal window: +``` +cd ~/firestorm/phoenix-firestorm +autobuild configure -A 64 -c ReleaseFS_open +``` + +This will configure the viewer for compiling with all defaults and without third party libraries. + +### Configuration Switches + +There are a number of switches you can use to modify the configuration process. The name of each switch is followed by its type and then by the value you want to set. + +- FMODSTUDIO (bool) controls if the FMOD Studio package is incorporated into the viewer. You must have performed the FMOD Studio installation steps in [FMOD Studio using autobuild](#fmod-studio-using-autobuild) for this to work. This is the switch the --fmodstudio build argument sets. +- LL_TESTS (bool) controls if the tests are compiled and run. There are quite a lot of them so excluding them is recommended unless you have some reason to need one or more of them. + +> [!TIP] +> OFF and NO are the same as FALSE; anything else is considered to be TRUE + +Example: + +``` +autobuild configure -A 64 -c ReleaseFS_open -- -DLL_TESTS:BOOL=FALSE -DFMODSTUDIO:BOOL=TRUE +``` +``` +autobuild configure -A 64 -c ReleaseFS_open +``` + +> [!IMPORTANT] +> You must specify the -A 64 switch to autobuild whenever you configure or build. This tells autobuild to build a 64-bit viewer; this is the only architecture supported on macOS. + +## Compiling the Viewer + +To compile the code into an app that can be used on your computer, run the following in the terminal window: + +``` +cd ~/firestorm/phoenix-firestorm +autobuild build -A 64 -c ReleaseFS_open --no-configure +``` + +Compiling may take quite a bit of time, depending on how fast your machine is and how much else you're doing. + +> [!NOTE] +> It is possible to use autobuild to do both the configure step (only needed once) and the build step with one command (`autobuild build -A 64 -c ReleaseFS[_open] [-- config options]`). Some find it is clearer if these steps are done separately, but can save a bit of time if done together. + +## Launching the viewer + +Now that the viewer has been compiled, it can be found in `build-darwin-x86_64/newview/Release`. Enter the following into the terminal to open Finder: + +``` +open build-darwin-x86_64/newview/Release +``` + +From here you can run it directly or copy it to the Applications folder for ease of finding and running later. + +## Updating the viewer + +If you want to update your self-compiled viewer, you don't have to go through this entire page again. Follow these steps to pull down any new code and re-compile. + +- First you need to change to the directory where you built the viewer before. Assuming you followed the steps above and used the same names, run the following in the terminal window: `cd ~/firestorm/phoenix-firestorm` +- Next, decide if you are going to remove the already compiled files and perform a "Clean build", or if you want to keep the existing build files, "Dirty build". Clean builds are generally recommended by developers but have the downside of building everything again, which comes at a cost of time; dirty builds on the other hand, keep all the previously compiled files and only compile the parts that have changed. The downside to dirty builds comes when a change to a core file is pulled down, which will then cause everything to be rebuilt, negating any speed benefit or possibly taking longer than a clean build. +- If you have picked to do a clean build, run the following in the terminal window, otherwise skip to the next step: `rm -r build-darwin-x86_64` +- If you are using a custom autobuild.xml file, then run the following in the terminal window, otherwise skip this step: `export AUTOBUILD_CONFIG_FILE=my_autobuild.xml` +- Now, to pull down any new code, run the following in the terminal window: `git pull` +- After any new code is downloaded, it needs to be reconfigured. Run the following in the terminal window: `autobuild configure -A 64 -c ReleaseFS_open` +- Finally, re-compile the viewer with the new changes. Again, in the terminal window, run: `autobuild build -A 64 -c ReleaseFS_open --no-configure` + +Finally, follow the instructions in [Launching the viewer](#launching-the-viewer) to run your freshly updated viewer. diff --git a/doc/building_windows.md b/doc/building_windows.md new file mode 100644 index 0000000000..46117d7fbc --- /dev/null +++ b/doc/building_windows.md @@ -0,0 +1,292 @@ +# Build instructions for Windows + +This page describes all necessary steps to build the Firestorm viewer for Windows. For building instructions up to (and including) release 6.5.3, see the archived version for [building with Python 2.7](https://wiki.firestormviewer.org/archive:fs_compiling_firestorm_windows_py_27). + +> [!WARNING] +> Please note that we do not give support for compiling the viewer on your own. However, there is a self-compilers group in Second Life that can be joined to ask questions related to compiling the viewer: [Firestorm Self Compilers](https://tinyurl.com/firestorm-self-compilers) + +> [!IMPORTANT] +> With the [merge of Linden Lab release 6.6.16](https://github.com/FirestormViewer/phoenix-firestorm/commit/b64793e2b0d14e44274335c874660af9f679f7f8) it is **NOT** possible to create 32bit builds anymore! Only 64bit builds are possible going forward! + +## Install required development tools + +This is needed for compiling any viewer based on the Linden Lab open source code and only needs to be done once. + +All installations are done with default settings (unless told explicitly) - if you change that, you're on your own! + +### Windows + +- Install Windows 10/11 64bit using your own product key + +### Microsoft Visual Studio 2022 + +- Install Visual Studio 2022 + - Run the installer as Administrator (right click, "Run as administrator") + - Check "Desktop development with C++" on the "Workloads" tab. + - All other workload options can be unchecked + +> [!TIP] +> If you don't own a copy of a commercial edition of Visual Studio 2022 (e.g. Professional), you might consider installing the [Community version](https://visualstudio.microsoft.com/free-developer-offers) + +### Tortoise Git + +- Download and install [TortoiseGit 2.9.0 or newer](https://tortoisegit.org) (64bit) + - Note: No option available to install as Administrator + - Use default options (path, components etc.) for Tortoise Git itself + - At some point, it will ask you to download and install Git for Windows + - You can install with default options **EXCEPT** when it asks for "Configuring the line endings conversion": You **MUST** select "Checkout as-is, commit as-is" here! + +### CMake + +- Download and install at least [CMake 3.16.0](http://www.cmake.org/download) + - Note: No option available to install as Administrator + - At the "Install options" screen, select "Add CMake to the system PATH for all users" + - For everything else, use the default options (path, etc.) + - Make sure that the following directory was added to your path: + For the 32bit version: + `C:\Program Files (x86)\CMake\bin` + For the 64bit version: + `C:\Program Files\CMake\bin` + +### Cygwin + +- Download and install [Cygwin 64](http://cygwin.com/install.html) (64bit) + - Run the installer as Administrator (right click, "Run as administrator") + - Use default options (path, components etc.) *until* you get to the "Select Packages" screen + - Add additional packages: + - Devel/patch + - Use default options for everything else + - Make sure that the following directory was added to your path and that it is placed before "%SystemRoot%\system32": + `C:\Cygwin64\bin` + +### Python + +- Download and install the most recent version of [Python 3](https://www.python.org/downloads/windows) + - Run the installer as Administrator (right click, “Run as administrator”) + - Tick "Add Python 3.10 to PATH" + - Choose the "Customize Installation" option. + - Make sure that "pip" is ticked. + - "Documentation", "tcl/tk and IDLE", "Python test suite" and "py launcher" are not needed to compile the viewer but can be selected if you wish. + - On the next screen, the correct options should already be ticked. + - Set custom install location to: `C:\Python3` + - Make sure that the following directory was added to your path: `C:\Python3` + +> [!TIP] +> On Windows 10/11, you also might want to disable the app alias for Python. Open the Windows settings app, search for "Manage app execution aliases" and disable the alias for "python3.exe" + +### Intermediate check + +Confirm things are installed properly so far by opening a Cygwin terminal and enter: + +``` +cmake --version +git --version +python --version +pip --version +``` + +If they all report sensible values and not "Command not found" errors, then you are in good shape. + +> [!NOTE] +> The Cygwin terminal is only needed for testing. All commands for actually building the viewer will be run from the Windows command shell. + +### Set up Autobuild + +- Install Autobuild + - Open Windows Command Prompt and enter: + `pip install git+https://github.com/secondlife/autobuild.git#egg=autobuild` + - Autobuild will be installed. **Earlier versions of Autobuild could be made to work by just putting the source files into your path correctly; this is no longer true - Autobuild _must_ be installed as described here.** +- Set environment variable AUTOBUILD_VSVER to 170 (170 = Visual Studio 2022). +- Check Autobuild version to be "autobuild 3.8" or higher: + `autobuild --version` + +### NSIS + +- If you plan to package the viewer and create an installer file, you must install the NSIS from the [official website](https://nsis.sourceforge.io). +- Not required unless you need to build an actual viewer installer for distribution, or change the NSIS installer package logic itself + +> [!IMPORTANT] +> If you want to package the viewer built on a revision prior to the [Bugsplat merge](https://github.com/FirestormViewer/phoenix-firestorm/commit/a399c6778579ac7c8965737088c275dde1371c9e), you must install the Unicode version of NSIS [from here](http://www.scratchpaper.com) - the installer from the NSIS website **WILL NOT** work! + +## Setup viewer build variables + +In order to make it easier to build collections of related packages (such as the viewer and all the library packages that it imports) with the same compilation options, Autobuild expects a file of variable definitions. This can be set using the environmenat variable AUTOBUILD_VARIABLES_FILE. + +- Clone the build variables repository: + `git clone https://github.com/FirestormViewer/fs-build-variables.git ` +- Set the environment variable AUTOBUILD_VARIABLES_FILE to + `\variables` + +## Configure Visual Studio 2022 (optional) + +- Start the IDE +- Navigate to **Tools** > **Options** > **Projects and Solutions** > **Build and Run** and set **maximum number of parallel projects builds** to **1**. + +## Set up your source code tree + +Plan your directory structure ahead of time. If you are going to be producing changes or patches you will be cloning a copy of an unaltered source code tree for every change or patch you make, so you might want to have all this work stored in its own directory. If you are a casual compiler and won't be producing any changes, you can use one directory. For this document, it is assumed that you created a folder c:\firestorm. + +``` +c: +cd \firestorm +git clone https://github.com/FirestormViewer/phoenix-firestorm.git +``` + +This can take a bit, it's a rather large download. + +## Prepare third party libraries + +Most third party libraries needed to build the viewer will be automatically downloaded for you and installed into the build directory within your source tree during compilation. Some need to be manually prepared and are not normally required when using an open source configuration (ReleaseFS_open). + +> [!IMPORTANT] +> If you are manually building the third party libraries, you will have to build the correct version (32bit libraries for a 32bit viewer, 64bit versions for a 64bit viewer)! + +## FMOD Studio using Autobuild + +If you want to use FMOD Studio to play sounds within the viewer, you will have to download your own copy. FMOD Studio can be downloaded [here](https://www.fmod.com) (requires creating an account to access the download section). + +> [!IMPORTANT] +> Make sure to download the FMOD Studio API and not the FMOD Studio Tool! + +``` +c: +cd \firestorm +git clone https://github.com/FirestormViewer/3p-fmodstudio.git +``` + +- After you have cloned the repository, copy the downloaded FMOD Studio installer file into the root of the repository +- Make sure to modify the file build-cmd.sh in the root of the repository and set the correct version number based on the version you downloaded. Right at the top, you find the version number of FMOD Studio you want to package (one short version without separator and one long version): + +``` +FMOD_VERSION="20102" +FMOD_VERSION_PRETTY="2.01.02" +``` + +Continue on the Windows command line: + +``` +c: +cd \firestorm\3p-fmodstudio +autobuild build -A 64 --all +autobuild package -A 64 --results-file result.txt +``` + +While running the Autobuild build command, Windows might ask if you want to allow making changes to the computer. This is because of the FMOD Studio installer being executed. Allow these changes to be made. + +Near the end of the output you will see the package name written: + +``` +wrote C:\firestorm\3p-fmodstudio\fmodstudio-{version#}-windows64-{build_id}.tar.bz2'' +``` + +where {version#} is the version of FMOD Studio (like 2.01.02) and {build_id} is an internal build id of the package. Additionally, a file `result.txt` has been created containing the md5 hash value of the package file, which you will need in the next step. + +``` +cd \firestorm\phoenix-firestorm +cp autobuild.xml my_autobuild.xml +set AUTOBUILD_CONFIG_FILE=my_autobuild.xml +``` + +Copy the FMOD Studio path and md5 value from the package process into this command: + +`autobuild installables edit fmodstudio platform=windows64 hash= url=file:///` + +For example: + +`autobuild installables edit fmodstudio platform=windows64 hash=a0d1821154e7ce5c418e3cdc2f26f3fc url=file:///C:/firestorm/3p-fmodstudio/fmodstudio-2.01.02-windows-192171947.tar.bz2` + +> [!NOTE] +> Having to copy autobuild.xml and modify the copy from within a cloned repository is a lot of work for every repository you make, but this is the only way to guarantee you pick up upstream changes to autobuild.xml and do not send up a modified autobuild.xml when you do a git push. + +## Configuring the viewer + +Open the Windows command prompt. + +If you are building with FMOD Studio and have followed the previous FMOD Studio setup instructions AND you are now using a new terminal you will need to reset the environment variable first by entering + +`set AUTOBUILD_CONFIG_FILE=my_autobuild.xml` + +Then enter: + +``` +c: +cd \firestorm\phoenix-firestorm +autobuild configure -A 64 -c ReleaseFS_open +``` + +This will configure Firestorm to be built with all defaults and without third party libraries. + +Available premade firestorm-specific build targets: + +``` +ReleaseFS (includes KDU, FMOD) +ReleaseFS_open (no KDU, no FMOD) +RelWithDebInfo_open (no KDU, no FMOD) +``` + +> [!TIP] +> Configuring the viewer for the first time will take some time to download all the required third-party libraries. The download progress is hidden by default. If you want to watch the download progress, you can use the verbose option to display a more detailed output: +> `autobuild configure -A 64 -v -c ReleaseFS_open` + +### Configuration switches + +There are a number of switches you can use to modify the configuration process. The name of each switch is followed by its type and then by the value you want to set. + +- -A \ sets the target architecture, that is if you want to build a 32bit or 64bit viewer (32bit is default if omitted). +- --fmodstudio controls if the FMOD Studio package is incorporated into the viewer. You must have performed the FMOD Studio installation steps in [FMOD Studio using Autobuild](#fmod-studio-using-autobuild) for this to work. +- --package makes sure all files are copied into viewers output directory. You won't be able to start your compiled viewer if you don't enable package or do 'compile' it in VS. +- --chan \ lets you define a custom channel name for the viewer +- -LL_TESTS:BOOL=\ controls if the tests are compiled and run. There are quite a lot of them so excluding them is recommended unless you have some reason to need one or more of them. + +> [!TIP] +> **OFF** and **NO** are the same as **FALSE**; anything else is considered to be **TRUE** + +Examples: + +- To build a 64bit viewer with FMOD Studio and to create an installer package, run this command in the Windows command window: +`autobuild configure -A 64 -c ReleaseFS_open -- --fmodstudio --package --chan MyViewer -DLL_TESTS:BOOL=FALSE` + +- To build a 64bit viewer without FMOD Studio and without installer package, run this command: +`autobuild configure -A 64 -c ReleaseFS_open -- --chan MyViewer -DLL_TESTS:BOOL=FALSE` + +## Building the viewer + +There are two ways to build the viewer: Via Windows command line or from within Visual Studio. + +### Building from the Windows command line + +If you are building with FMOD Studio and have followed the previous FMOD Studio setup instructions AND you are now using a new terminal you will need to reset the environment variable with + +`set AUTOBUILD_CONFIG_FILE=my_autobuild.xml` + +Then run the Autobuild build command. Make sure you include the same architecture parameter you used while [configuring the viewer](#configuring-the-viewer): + +`autobuild build -A 64 -c ReleaseFS_open --no-configure` + +Compiling will take quite a bit of time. + +### Building from within Visual Studio + +Inside the Firestorm source folder, you will find a folder named build-vc170-\, with \ either being 32 or 64, depending on what you chose during the configuration step. Inside the folder is the Visual Studio solution file for Firestorm, called Firestorm.sln. + +- Double-click Firestorm.sln to open the Firestorm solution in Visual Studio. +- From the menu, choose Build -> Build Solution +- Wait until the build is finished + +## Troubleshooting + +### SystemRootsystem32: unbound variable + +When trying to execute the Autobuild build command, you might encounter an error similar to + +`../build.cmd.sh line 200: SystemRootsystem32: unbound variable` + +This error is caused by the order of the items in the Windows "path" environment variable. Autobuild exports all paths set in the "path" environment variable into Cygpath names and variables. Since these Windows "paths" can also contain variables like %SystemRoot% and they can also depend on each other, it is important to keep the dependency order intact. Example: + +``` +%SystemRoot% +%SystemRoot%\system32 +%SystemRoot%\System32\Wbem +``` + +Make sure the ones mentioned are the first items set in the "path" environment variable. diff --git a/doc/contributions.txt b/doc/contributions.txt index 0e728f993a..8dbd423c27 100755 --- a/doc/contributions.txt +++ b/doc/contributions.txt @@ -241,6 +241,8 @@ Ansariel Hiller SL-15398 SL-18432 SL-19140 + SL-19575 + SL-19623 SL-4126 SL-20224 Aralara Rajal @@ -905,6 +907,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 636b0f0d0d..f8ba23316b 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -31,6 +31,7 @@ set(cmake_SOURCE_FILES GoogleMock.cmake Havok.cmake Hunspell.cmake + ICU4C.cmake JsonCpp.cmake LLAddBuildTest.cmake LLAppearance.cmake diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index f2730c0494..2828c3db0a 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -63,6 +63,15 @@ if(WINDOWS) uriparser.dll ) + # ICU4C (same filenames for 32 and 64 bit builds) + set(release_files ${release_files} icudt48.dll) + set(release_files ${release_files} icuin48.dll) + set(release_files ${release_files} icuio48.dll) + set(release_files ${release_files} icule48.dll) + set(release_files ${release_files} iculx48.dll) + set(release_files ${release_files} icutu48.dll) + set(release_files ${release_files} icuuc48.dll) + # Only copy OpenJPEG dll if needed if (NOT USE_KDU) set(release_files ${release_files} openjp2.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 879d9c3b1e..c563ea130e 100644 --- a/indra/cmake/ViewerMiscLibs.cmake +++ b/indra/cmake/ViewerMiscLibs.cmake @@ -18,4 +18,6 @@ if( NOT USE_CONAN ) 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 11beda624a..48747be056 100644 --- a/indra/llappearance/llwearabletype.h +++ b/indra/llappearance/llwearabletype.h @@ -37,7 +37,7 @@ class LLWearableType : public LLParamSingleton { 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 43946ba62c..053cf7246c 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) @@ -320,6 +321,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 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 bda153095d..ec3c562599 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -843,7 +843,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(__VA_ARGS__) diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 26f84c1f8d..ec792ad240 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 #include #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) { @@ -672,6 +695,7 @@ std::string mbcsstring_makeASCII(const std::string& wstr) } return out_str; } + std::string utf8str_removeCRLF(const std::string& utf8str) { if (0 == utf8str.length()) @@ -693,6 +717,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() { @@ -905,6 +1042,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 d1e44efc22..7b37600a81 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -198,6 +198,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); @@ -364,6 +366,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); @@ -687,6 +691,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. @@ -751,6 +757,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 @@ -1607,6 +1616,29 @@ void LLStringUtilBase::replaceTabsWithSpaces( string_type& str, size_type spa str = out_str; } +//static +template +std::basic_string LLStringUtilBase::capitalize(const string_type& str) +{ + string_type result(str); + capitalize(result); + return result; +} + +//static +template +void LLStringUtilBase::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 BOOL LLStringUtilBase::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/llfilesystem/lldir.cpp b/indra/llfilesystem/lldir.cpp index 75ce6aa478..c95db90340 100644 --- a/indra/llfilesystem/lldir.cpp +++ b/indra/llfilesystem/lldir.cpp @@ -1108,6 +1108,14 @@ void LLDir::setSkinFolder(const std::string &skin_folder, const std::string& the addSearchSkinDir(mUserDefaultSkinDir); // then user-defined skins. addSearchSkinDir(mUserSkinDir); + + // If working directory is different from executable directory, add executable subdirs as searchable folders + if (mExecutableDir != mWorkingDir) + { + addSearchSkinDir(add(mExecutableDir, "skins")); + addSearchSkinDir(add(mExecutableDir, "skins", "default")); + } + // } void LLDir::addSearchSkinDir(const std::string& skindir) diff --git a/indra/llinventory/llfoldertype.cpp b/indra/llinventory/llfoldertype.cpp index 231d6ce6a8..0b210d4622 100644 --- a/indra/llinventory/llfoldertype.cpp +++ b/indra/llinventory/llfoldertype.cpp @@ -60,7 +60,7 @@ class LLFolderDictionary : public LLSingleton, { 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 4d4c002a03..2562614480 100644 --- a/indra/llinventory/llinventorysettings.cpp +++ b/indra/llinventory/llinventorysettings.cpp @@ -70,7 +70,7 @@ class LLSettingsDictionary : public LLSingleton, { 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 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 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 112052a070..5a30d3bcb2 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; -} -LLImageRaw *LLFontBitmapCache::getImageRaw(U32 bitmap_num) const -{ - if (bitmap_num >= mImageRawVec.size()) - return NULL; - - return mImageRawVec[bitmap_num]; -} - -LLImageGL *LLFontBitmapCache::getImageGL(U32 bitmap_num) const -{ - if (bitmap_num >= mImageGLVec.size()) - return NULL; - - return mImageGLVec[bitmap_num]; -} - - -BOOL LLFontBitmapCache::nextOpenPos(S32 width, S32 &pos_x, S32 &pos_y, S32& bitmap_num) -{ - if ((mBitmapNum<0) || (mCurrentOffsetX + width + 1) > mBitmapWidth) + S32 image_width = mMaxCharWidth * 20; + S32 pow_iw = 2; + while (pow_iw < image_width) { - if ((mBitmapNum<0) || (mCurrentOffsetY + 2*mMaxCharHeight + 2) > mBitmapHeight) + pow_iw <<= 1; + } + image_width = pow_iw; + image_width = llmin(512, image_width); // Don't make bigger than 512x512, ever. + + mBitmapWidth = image_width; + mBitmapHeight = image_width; +} + +LLImageRaw *LLFontBitmapCache::getImageRaw(EFontGlyphType bitmap_type, U32 bitmap_num) const +{ + const U32 bitmap_idx = static_cast(bitmap_type); + if (bitmap_type >= EFontGlyphType::Count || bitmap_num >= mImageRawVec[bitmap_idx].size()) + return nullptr; + + return mImageRawVec[bitmap_idx][bitmap_num]; +} + +LLImageGL *LLFontBitmapCache::getImageGL(EFontGlyphType bitmap_type, U32 bitmap_num) const +{ + const U32 bitmap_idx = static_cast(bitmap_type); + if (bitmap_type >= EFontGlyphType::Count || bitmap_num >= mImageGLVec[bitmap_idx].size()) + return nullptr; + + return mImageGLVec[bitmap_idx][bitmap_num]; +} + + +BOOL LLFontBitmapCache::nextOpenPos(S32 width, S32& pos_x, S32& pos_y, EFontGlyphType bitmap_type, U32& bitmap_num) +{ + if (bitmap_type >= EFontGlyphType::Count) + { + return FALSE; + } + + const U32 bitmap_idx = static_cast(bitmap_type); + if (mImageRawVec[bitmap_idx].empty() || (mCurrentOffsetX[bitmap_idx] + width + 1) > mBitmapWidth) + { + if ((mImageRawVec[bitmap_idx].empty()) || (mCurrentOffsetY[bitmap_idx] + 2*mMaxCharHeight + 2) > mBitmapHeight) { // We're out of space in the current image, or no image // has been allocated yet. Make a new one. - - mImageRawVec.push_back(new LLImageRaw); - mBitmapNum = mImageRawVec.size()-1; - LLImageRaw *image_raw = getImageRaw(mBitmapNum); + + S32 image_width = mMaxCharWidth * 20; + S32 pow_iw = 2; + while (pow_iw < image_width) + { + pow_iw *= 2; + } + image_width = pow_iw; + image_width = llmin(512, image_width); // Don't make bigger than 512x512, ever. + S32 image_height = image_width; + + mBitmapWidth = image_width; + mBitmapHeight = image_height; + + S32 num_components = getNumComponents(bitmap_type); + mImageRawVec[bitmap_idx].push_back(new LLImageRaw(mBitmapWidth, mBitmapHeight, num_components)); + bitmap_num = mImageRawVec[bitmap_idx].size() - 1; + + LLImageRaw* image_raw = getImageRaw(bitmap_type, bitmap_num); + if (EFontGlyphType::Grayscale == bitmap_type) + { + image_raw->clear(255, 0); + } // Make corresponding GL image. - mImageGLVec.push_back(new LLImageGL(FALSE)); - LLImageGL *image_gl = getImageGL(mBitmapNum); - - S32 image_width = mMaxCharWidth * 20; - S32 pow_iw = 2; - while (pow_iw < image_width) - { - pow_iw *= 2; - } - image_width = pow_iw; - image_width = llmin(512, image_width); // Don't make bigger than 512x512, ever. - S32 image_height = image_width; - - image_raw->resize(image_width, image_height, mNumComponents); - - mBitmapWidth = image_width; - mBitmapHeight = image_height; - - switch (mNumComponents) - { - case 1: - image_raw->clear(); - break; - case 2: - image_raw->clear(255, 0); - break; - } + mImageGLVec[bitmap_idx].push_back(new LLImageGL(image_raw, false)); + LLImageGL* image_gl = getImageGL(bitmap_type, bitmap_num); // Start at beginning of the new image. - mCurrentOffsetX = 1; - mCurrentOffsetY = 1; + mCurrentOffsetX[bitmap_idx] = 1; + mCurrentOffsetY[bitmap_idx] = 1; - // Attach corresponding GL texture. - image_gl->createGLTexture(0, image_raw); + // Attach corresponding GL texture. (*TODO: is this needed?) gGL.getTexUnit(0)->bind(image_gl); image_gl->setFilteringOption(LLTexUnit::TFO_POINT); // was setMipFilterNearest(TRUE, TRUE); } else { // Move to next row in current image. - mCurrentOffsetX = 1; - mCurrentOffsetY += mMaxCharHeight + 1; + mCurrentOffsetX[bitmap_idx] = 1; + mCurrentOffsetY[bitmap_idx] += mMaxCharHeight + 1; } } - pos_x = mCurrentOffsetX; - pos_y = mCurrentOffsetY; - bitmap_num = mBitmapNum; + pos_x = mCurrentOffsetX[bitmap_idx]; + pos_y = mCurrentOffsetY[bitmap_idx]; + bitmap_num = getNumBitmaps(bitmap_type) - 1; - mCurrentOffsetX += width + 1; + mCurrentOffsetX[bitmap_idx] += width + 1; return TRUE; } void LLFontBitmapCache::destroyGL() { - for (std::vector >::iterator it = mImageGLVec.begin(); - it != mImageGLVec.end(); ++it) + for (U32 idx = 0, cnt = static_cast(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(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 #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(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 > mImageRawVec; - std::vector > mImageGLVec; + S32 mBitmapWidth = 0; + S32 mBitmapHeight = 0; + S32 mCurrentOffsetX[static_cast(EFontGlyphType::Count)] = { 1 }; + S32 mCurrentOffsetY[static_cast(EFontGlyphType::Count)] = { 1 }; + S32 mMaxCharWidth = 0; + S32 mMaxCharHeight = 0; + std::vector> mImageRawVec[static_cast(EFontGlyphType::Count)]; + std::vector> mImageGLVec[static_cast(EFontGlyphType::Count)]; }; #endif //LL_LLFONTBITMAPCACHE_H diff --git a/indra/llrender/llfontfreetype.cpp b/indra/llrender/llfontfreetype.cpp index d7f7029c8a..1cd9dfe432 100644 --- a/indra/llrender/llfontfreetype.cpp +++ b/indra/llrender/llfontfreetype.cpp @@ -34,14 +34,17 @@ #ifdef LL_WINDOWS #include #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" @@ -51,6 +54,8 @@ #include "llapr.h" +#define ENABLE_OT_SVG_SUPPORT + FT_Render_Mode gFontRenderMode = FT_RENDER_MODE_NORMAL; LLFontManager *gFontManagerp = NULL; @@ -83,6 +88,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() @@ -92,8 +107,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 @@ -102,10 +118,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), @@ -172,7 +203,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. @@ -255,7 +286,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) { @@ -266,7 +297,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; @@ -377,14 +408,11 @@ void LLFontFreetype::clearFontStreams() } #endif -void LLFontFreetype::setFallbackFonts(const font_vector_t &font) +void LLFontFreetype::addFallbackFont(const LLPointer& fallback_font, const char_functor_t& functor) { - mFallbackFonts = font; -} - -const LLFontFreetype::font_vector_t &LLFontFreetype::getFallbackFonts() const -{ - return mFallbackFonts; + // Insert functor fallbacks before generic fallbacks + mFallbackFonts.insert((functor) ? std::find_if(mFallbackFonts.begin(), mFallbackFonts.end(), [](const fallback_font_t& fe) { return !fe.second; }) : mFallbackFonts.end(), + std::make_pair(fallback_font, functor)); } F32 LLFontFreetype::getLineHeight() const @@ -408,7 +436,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; @@ -440,10 +468,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; // Use cached kerning if possible, only do so for glyphs < 256 for now @@ -517,60 +545,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 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; @@ -581,8 +643,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) @@ -617,84 +683,104 @@ 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 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 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; + 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; + } - // try to load given glyph, if that fails, fallback to ?. This can happen with invalid unicode codepoints. - if( 0 != FT_Load_Glyph(mFTFace, glyph_index, FT_LOAD_FORCE_AUTOHINT) ) - glyph_index = FT_Get_Char_Index( mFTFace, L'?'); - // + 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(! FT_Load_Glyph(mFTFace, glyph_index, FT_LOAD_FORCE_AUTOHINT) ); + // try to load given glyph, if that fails, fallback to ?. This can happen with invalid unicode codepoints. + if (FT_Err_Ok != error) + { + glyph_index = FT_Get_Char_Index( mFTFace, L'?'); + 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) ); @@ -704,7 +790,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. @@ -714,11 +800,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); } } } @@ -740,7 +824,7 @@ void LLFontFreetype::resetBitmapCache() if(!mIsFallback) { // Add the empty glyph - addGlyphFromFont(this, 0, 0); + addGlyphFromFont(this, 0, 0, EFontGlyphType::Grayscale); } } @@ -754,6 +838,34 @@ const std::string &LLFontFreetype::getName() const return mName; } +static void dumpFontBitmap(const LLImageRaw* image_raw, const std::string& file_name) +{ + LLPointer 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; @@ -769,17 +881,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 77c9bf3236..27547ae402 100644 --- a/indra/llrender/llfontfreetype.h +++ b/indra/llrender/llfontfreetype.h @@ -74,9 +74,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 @@ -89,7 +91,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 mBitmapEntry; // Which bitmap in the bitmap cache contains this glyph }; extern LLFontManager *gFontManagerp; @@ -102,7 +104,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); @@ -111,10 +113,8 @@ public: void clearFontStreams(); #endif - typedef std::vector > font_vector_t; - - void setFallbackFonts(const font_vector_t &font); - const font_vector_t &getFallbackFonts() const; + typedef std::function char_functor_t; + void addFallbackFont(const LLPointer& fallback_font, const char_functor_t& functor = nullptr); // Global font metrics - in units of pixels F32 getLineHeight() const; @@ -153,7 +153,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); @@ -161,6 +161,7 @@ public: const std::string& getName() const; + void dumpFontBitmaps() const; const LLFontBitmapCache* getFontBitmapCache() const; void setStyle(U8 style); @@ -169,10 +170,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; @@ -192,9 +194,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, char_functor_t> fallback_font_t; + typedef std::vector 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 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 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 +#define NANOSVGRAST_IMPLEMENTATION +#include + +#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(objectp); + + LLSvgRenderData* pData = static_cast(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(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(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(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 +#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 6854f9a8e6..d39c4eb834 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(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) { @@ -283,7 +282,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 bitmap_entry = std::make_pair(EFontGlyphType::Grayscale, -1); S32 glyph_count = 0; for (i = begin_offset; i < begin_offset + length; i++) { @@ -293,7 +292,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) { @@ -301,8 +300,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 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. @@ -323,8 +322,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); } @@ -364,7 +363,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; @@ -374,7 +373,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); } @@ -435,7 +434,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(); } @@ -446,22 +446,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 @@ -514,7 +514,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); @@ -538,7 +538,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); @@ -561,7 +561,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. @@ -582,7 +582,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); } } @@ -656,7 +656,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) { @@ -681,7 +681,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); } @@ -728,7 +728,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 @@ -803,7 +803,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); @@ -833,7 +833,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); } @@ -903,6 +903,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 @@ -1069,6 +1089,20 @@ LLFontGL::VAlign LLFontGL::vAlignFromName(const std::string& name) return gl_vfont_align; } +//static +LLFontGL* LLFontGL::getFontEmoji() +{ + static LLFontGL* fontp = getFont(LLFontDescriptor("Emoji", "Large", 0)); + return fontp;; +} + +//static +LLFontGL* LLFontGL::getFontEmojiHuge() +{ + static LLFontGL* fontp = getFont(LLFontDescriptor("Emoji", "Huge", 0)); + return fontp;; +} + //static LLFontGL* LLFontGL::getFontMonospace() { diff --git a/indra/llrender/llfontgl.h b/indra/llrender/llfontgl.h index ce52850c05..13c9e8bfce 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, const std::string& fonts_file, F32 size_mod = 0, 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 6e928bf0a0..0e12f5225d 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) @@ -181,7 +184,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, F32 size_mod) @@ -308,17 +323,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")) { @@ -361,19 +383,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; } @@ -460,86 +482,84 @@ 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()); // User fonts: Also load from user_settings/fonts - std::string usr_path = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS , "fonts", ""); - + font_search_paths.push_back(gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS , "fonts", "")); + // Search executable path as well - in case we run from within VS (seems to work without as well, but just to be safe) + font_search_paths.push_back(gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, "fonts", "")); +#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); - // User fonts: Also load from user_settings/fonts - font_paths.push_back(usr_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) @@ -549,7 +569,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; } @@ -564,17 +585,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; @@ -761,11 +777,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 <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 215505ffa4..4d8c9ed544 100644 --- a/indra/llrender/llfontregistry.h +++ b/indra/llrender/llfontregistry.h @@ -34,13 +34,32 @@ class LLFontGL; typedef std::vector string_vec_t; +struct LLFontFileInfo +{ + LLFontFileInfo(const std::string& file_name, const std::function& char_functor = nullptr) + : FileName(file_name) + , CharFunctor(char_functor) + { + } + + LLFontFileInfo(const LLFontFileInfo& ffi) + : FileName(ffi.FileName) + , CharFunctor(ffi.CharFunctor) + { + } + + std::string FileName; + std::function CharFunctor; +}; +typedef std::vector 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& getFileNames() const { return mFileNames; } - std::vector& getFileNames() { return mFileNames; } - const std::vector& getFontCollectionsList() const { return mFontCollectionsList; } - std::vector& 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> 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 c88f9f052e..b2ca583611 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -2946,7 +2946,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 c88e3f886e..09a0d38056 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -31,6 +31,8 @@ set(llui_SOURCE_FILES lldockcontrol.cpp lldraghandle.cpp lleditmenuhandler.cpp + llemojidictionary.cpp + llemojihelper.cpp llf32uictrl.cpp llfiltereditor.cpp llflashtimer.cpp @@ -143,6 +145,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 312f621d2e..6c3b6c057c 100644 --- a/indra/llui/llbutton.cpp +++ b/indra/llui/llbutton.cpp @@ -70,6 +70,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"), @@ -167,6 +168,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), @@ -1027,7 +1029,7 @@ void LLButton::draw() LLFontGL::NORMAL, mDropShadowedText ? LLFontGL::DROP_SHADOW_SOFT : LLFontGL::NO_SHADOW, S32_MAX, text_width, - NULL, mUseEllipses); + NULL, mUseEllipses, mUseFontColor); } // Add checkbox control toggle @@ -1096,6 +1098,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); @@ -1127,14 +1139,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 image) diff --git a/indra/llui/llbutton.h b/indra/llui/llbutton.h index 6225ba7165..e4bd6adc3d 100644 --- a/indra/llui/llbutton.h +++ b/indra/llui/llbutton.h @@ -73,6 +73,7 @@ public: Optional label_shadow; Optional auto_resize; Optional use_ellipses; + Optional use_font_color; // images Optional image_unselected, @@ -178,6 +179,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); @@ -248,6 +250,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); @@ -370,6 +374,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 +#include +#include + +// ============================================================================ +// 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 +std::list llsd_array_to_list(const LLSD& sd, std::function mutator = {}); + +template<> +std::list llsd_array_to_list(const LLSD& sd, std::function mutator) +{ + std::list 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& result, + const std::string& needle +) const +{ + result.clear(); + + if (needle.empty() || needle.front() != ':') + return; + + std::map> 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 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 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 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 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 LLEmojiDictionary::loadCategories(const LLSD& sd) +{ + static const std::string key("Categories"); + return llsd_array_to_list(sd[key]); +} + +std::list LLEmojiDictionary::loadShortCodes(const LLSD& sd) +{ + static const std::string key("ShortCodes"); + auto toLower = [](std::string& str) { LLStringUtil::toLower(str); }; + return llsd_array_to_list(sd[key], toLower); +} + +void LLEmojiDictionary::translateCategories(std::list& 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 ShortCodes; + std::string getShortCodes() const; +}; + +// ============================================================================ +// LLEmojiGroup class +// + +struct LLEmojiGroup +{ + llwchar Character; + std::list 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, public LLInitClass +{ + LLSINGLETON(LLEmojiDictionary); + ~LLEmojiDictionary() override {}; + +public: + typedef std::map cat2cat_map_t; + typedef std::map cat2group_map_t; + typedef std::map emoji2descr_map_t; + typedef std::map code2descr_map_t; + typedef std::map> 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& 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& 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 loadCategories(const LLSD& sd); + static std::list loadShortCodes(const LLSD& sd); + void translateCategories(std::list& categories); + +private: + std::vector mGroups; + std::list mEmojis; + std::list 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 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 + +class LLFloater; +class LLUICtrl; + +class LLEmojiHelper : public LLSingleton +{ + 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 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 mHostHandle; + LLHandle mHelperHandle; + boost::signals2::connection mHostCtrlFocusLostConn; + boost::signals2::connection mHelperCommitConn; + std::function mEmojiCommitCb; + bool mIsHideDisabled; +}; diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index f44910fdca..dbd59e3393 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -194,6 +194,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), @@ -271,6 +272,7 @@ LLFloater::LLFloater(const LLSD& key, const LLFloater::Params& p) mCanSnooze(p.can_snooze), // FIRE-11724: Snooze group chat 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), @@ -539,6 +541,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); @@ -737,7 +740,7 @@ void LLFloater::openFloater(const LLSD& key) if (getHost() != NULL) { getHost()->setMinimized(FALSE); - getHost()->setVisibleAndFrontmost(mAutoFocus); + getHost()->setVisibleAndFrontmost(mAutoFocus && !getIsChrome()); getHost()->showFloater(this); // Make sure the floater knows it's not torn off mTornOff = false; @@ -754,7 +757,7 @@ void LLFloater::openFloater(const LLSD& key) } applyControlsAndPosition(floater_to_stack); setMinimized(FALSE); - setVisibleAndFrontmost(mAutoFocus); + setVisibleAndFrontmost(mAutoFocus && !getIsChrome()); } mOpenSignal(this, key); @@ -896,6 +899,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& handle : mDependents) + { + LLFloater* floater = handle.get(); + if (floater && floater->getSnapTarget() == getHandle()) + { + floater->LLView::translate(x, y); + } + } +} + void LLFloater::releaseFocus() { LLUI::getInstance()->removePopup(this); @@ -1204,9 +1225,9 @@ BOOL LLFloater::canSnapTo(const LLView* other_view) if (other_view != getParent()) { - const LLFloater* other_floaterp = dynamic_cast(other_view); - if (other_floaterp - && other_floaterp->getSnapTarget() == getHandle() + const LLFloater* other_floaterp = dynamic_cast(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 @@ -1596,30 +1617,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 dependent, BOOL reposition) +void LLFloater::addDependentFloater(LLHandle dependent, BOOL reposition, BOOL resize) { LLFloater* dependent_floaterp = dependent.get(); if(dependent_floaterp) { - addDependentFloater(dependent_floaterp, reposition); + addDependentFloater(dependent_floaterp, reposition, resize); } } @@ -1629,6 +1660,79 @@ void LLFloater::removeDependentFloater(LLFloater* floaterp) floaterp->mDependeeHandle = LLHandle(); } +// Fix floater relocation +//void LLFloater::fitWithDependentsOnScreen(const LLRect& left, const LLRect& bottom, const LLRect& right, const LLRect& constraint, S32 min_overlap_pixels) +void LLFloater::fitWithDependentsOnScreen(const LLRect& left, const LLRect& bottom, const LLRect& right, const LLRect& chatbar, const LLRect& utilitybar, const LLRect& constraint, S32 min_overlap_pixels) +// +{ + LLRect total_rect = getRect(); + + for (const LLHandle& 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; + // Prevent floaters being dragged under main chat bar + S32 delta_bottom_chatbar = chatbar.notEmpty() ? chatbar.mTop - total_rect.mTop : 0; + S32 delta_utility_bar = utilitybar.notEmpty() ? utilitybar.mTop - total_rect.mTop : 0; + + // Fix floater relocation for vertical toolbars; Only header guarantees that floater can be dragged! + S32 header_height = getHeaderHeight(); + + // move floater with dependings fully onscreen + mTranslateWithDependents = true; + if (translateRectIntoRect(total_rect, constraint, min_overlap_pixels)) + { + clearSnapTarget(); + } + // Fix floater relocation for vertical toolbars; Only header guarantees that floater can be dragged! + //else if (delta_left > 0 && total_rect.mTop < left.mTop && total_rect.mBottom > left.mBottom) + else if (delta_left > 0 && total_rect.mTop < left.mTop && (total_rect.mTop - header_height) > left.mBottom) + // + { + translate(delta_left, 0); + } + // Prevent floaters being dragged under main chat bar + //else if (delta_bottom > 0 && total_rect.mLeft > bottom.mLeft && total_rect.mRight < bottom.mRight) + else if (delta_bottom > 0 && ((total_rect.mLeft > bottom.mLeft && total_rect.mRight < bottom.mRight) // floater completely within toolbar rect + || (total_rect.mLeft > bottom.mLeft && total_rect.mLeft < bottom.mRight && bottom.mRight > constraint.mRight) // floater partially within toolbar rect, toolbar bound to right side + || (delta_bottom_chatbar > 0 && total_rect.mLeft < chatbar.mRight && total_rect.mRight > bottom.mLeft && bottom.mLeft <= chatbar.mRight)) // floater within chatbar and toolbar rect + ) + // + { + translate(0, delta_bottom); + } + // Fix floater relocation for vertical toolbars; Only header guarantees that floater can be dragged! + //else if (delta_right < 0 && total_rect.mTop < right.mTop && total_rect.mBottom > right.mBottom) + else if (delta_right < 0 && total_rect.mTop < right.mTop && (total_rect.mTop - header_height) > right.mBottom) + // + { + translate(delta_right, 0); + } + // Prevent floaters being dragged under main chat bar + else if (delta_bottom_chatbar > 0 && ((total_rect.mLeft > chatbar.mLeft && total_rect.mRight < chatbar.mRight) // floater completely within chatbar rect + || (total_rect.mRight > chatbar.mLeft && total_rect.mRight < chatbar.mRight && chatbar.mLeft < constraint.mLeft) // floater partially within chatbar rect, chatbar bound to left side + || (delta_bottom > 0 && total_rect.mRight > bottom.mLeft && total_rect.mLeft < chatbar.mRight && bottom.mLeft <= chatbar.mRight)) // floater within chatbar and toolbar rect + ) + { + translate(0, delta_bottom_chatbar); + } + else if (delta_utility_bar > 0 && (total_rect.mLeft > utilitybar.mLeft && total_rect.mRight < utilitybar.mRight)) + { + // Utility bar on legacy skins + translate(0, delta_utility_bar); + } + // + mTranslateWithDependents = false; +} + BOOL LLFloater::offerClickToButton(S32 x, S32 y, MASK mask, EFloaterButton index) { if( mButtonsEnabled[index] ) @@ -1719,6 +1823,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)) @@ -1733,12 +1838,20 @@ void LLFloater::bringToFront( S32 x, S32 y ) LLFloaterView* parent = dynamic_cast( 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) @@ -2518,7 +2631,8 @@ LLFloaterView::LLFloaterView (const Params& p) mSnapOffsetBottom(0), mSnapOffsetChatBar(0), mSnapOffsetLeft(0), - mSnapOffsetRight(0) + mSnapOffsetRight(0), + mFrontChild(NULL) { mSnapView = getHandle(); } @@ -2674,16 +2788,21 @@ void LLFloaterView::bringToFront(LLFloater* child, BOOL give_focus, BOOL restore if (!child) return; - if (mFrontChildHandle.get() == child) + if (mFrontChild == child) { - if (give_focus && !gFocusMgr.childHasKeyboardFocus(child)) + if (give_focus && child->canFocusStealFrontmost() && !gFocusMgr.childHasKeyboardFocus(child)) { child->setFocus(TRUE); } return; } - mFrontChildHandle = child->getHandle(); + if (mFrontChild && !mFrontChild->isDead()) + { + mFrontChild->goneFromFront(); + } + + mFrontChild = child; // *TODO: make this respect floater's mAutoFocus value, instead of // using parameter @@ -3114,10 +3233,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() ) { @@ -3159,64 +3285,12 @@ void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_out } } - const LLRect& left_toolbar_rect = mToolbarLeftRect; - const LLRect& bottom_toolbar_rect = mToolbarBottomRect; - const LLRect& right_toolbar_rect = mToolbarRightRect; - const LLRect& floater_rect = floater->getRect(); + const LLRect& constraint = snap_in_toolbars ? getSnapRect() : gFloaterView->getRect(); + S32 min_overlap_pixels = allow_partial_outside ? FLOATER_MIN_VISIBLE_PIXELS : S32_MAX; - S32 delta_left = left_toolbar_rect.notEmpty() ? left_toolbar_rect.mRight - floater_rect.mRight : 0; - S32 delta_bottom = bottom_toolbar_rect.notEmpty() ? bottom_toolbar_rect.mTop - floater_rect.mTop : 0; - S32 delta_right = right_toolbar_rect.notEmpty() ? right_toolbar_rect.mLeft - floater_rect.mLeft : 0; - // Prevent floaters being dragged under main chat bar - S32 delta_bottom_chatbar = mMainChatbarRect.notEmpty() ? mMainChatbarRect.mTop - floater_rect.mTop : 0; - S32 delta_utility_bar = mUtilityBarRect.notEmpty() ? mUtilityBarRect.mTop - floater_rect.mTop : 0; - - // Fix floater relocation for vertical toolbars; Only header guarantees that floater can be dragged! - S32 header_height = floater->getHeaderHeight(); - - // move window fully onscreen - if (floater->translateIntoRect( snap_in_toolbars ? getSnapRect() : gFloaterView->getRect(), allow_partial_outside ? FLOATER_MIN_VISIBLE_PIXELS : S32_MAX )) - { - floater->clearSnapTarget(); - } - // Fix floater relocation for vertical toolbars; Only header guarantees that floater can be dragged! - //else if (delta_left > 0 && floater_rect.mTop < left_toolbar_rect.mTop && floater_rect.mBottom > left_toolbar_rect.mBottom) - else if (delta_left > 0 && floater_rect.mTop < left_toolbar_rect.mTop && (floater_rect.mTop - header_height) > left_toolbar_rect.mBottom) - // - { - floater->translate(delta_left, 0); - } - // Prevent floaters being dragged under main chat bar - //else if (delta_bottom > 0 && floater_rect.mLeft > bottom_toolbar_rect.mLeft && floater_rect.mRight < bottom_toolbar_rect.mRight) - else if (delta_bottom > 0 && ((floater_rect.mLeft > bottom_toolbar_rect.mLeft && floater_rect.mRight < bottom_toolbar_rect.mRight) // floater completely within toolbar rect - || (floater_rect.mLeft > bottom_toolbar_rect.mLeft && floater_rect.mLeft < bottom_toolbar_rect.mRight && bottom_toolbar_rect.mRight > gFloaterView->getRect().mRight) // floater partially within toolbar rect, toolbar bound to right side - || (delta_bottom_chatbar > 0 && floater_rect.mLeft < mMainChatbarRect.mRight && floater_rect.mRight > bottom_toolbar_rect.mLeft && bottom_toolbar_rect.mLeft <= mMainChatbarRect.mRight)) // floater within chatbar and toolbar rect - ) - // - { - floater->translate(0, delta_bottom); - } - // Fix floater relocation for vertical toolbars; Only header guarantees that floater can be dragged! - //else if (delta_right < 0 && floater_rect.mTop < right_toolbar_rect.mTop && floater_rect.mBottom > right_toolbar_rect.mBottom) - else if (delta_right < 0 && floater_rect.mTop < right_toolbar_rect.mTop && (floater_rect.mTop - header_height) > right_toolbar_rect.mBottom) - // - { - floater->translate(delta_right, 0); - } - // Prevent floaters being dragged under main chat bar - else if (delta_bottom_chatbar > 0 && ((floater_rect.mLeft > mMainChatbarRect.mLeft && floater_rect.mRight < mMainChatbarRect.mRight) // floater completely within chatbar rect - || (floater_rect.mRight > mMainChatbarRect.mLeft && floater_rect.mRight < mMainChatbarRect.mRight && mMainChatbarRect.mLeft < gFloaterView->getRect().mLeft) // floater partially within chatbar rect, chatbar bound to left side - || (delta_bottom > 0 && floater_rect.mRight > bottom_toolbar_rect.mLeft && floater_rect.mLeft < mMainChatbarRect.mRight && bottom_toolbar_rect.mLeft <= mMainChatbarRect.mRight)) // floater within chatbar and toolbar rect - ) - { - floater->translate(0, delta_bottom_chatbar); - } - else if (delta_utility_bar > 0 && (floater_rect.mLeft > mUtilityBarRect.mLeft && floater_rect.mRight < mUtilityBarRect.mRight)) - { - // Utility bar on legacy skins - floater->translate(0, delta_utility_bar); - } - // + // Fix floater relocation + //floater->fitWithDependentsOnScreen(mToolbarLeftRect, mToolbarBottomRect, mToolbarRightRect, constraint, min_overlap_pixels); + floater->fitWithDependentsOnScreen(mToolbarLeftRect, mToolbarBottomRect, mToolbarRightRect, mMainChatbarRect, mUtilityBarRect, constraint, min_overlap_pixels); } void LLFloaterView::draw() @@ -3315,6 +3389,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) @@ -3350,7 +3427,34 @@ void LLFloaterView::syncFloaterTabOrder() LLFloater* floaterp = dynamic_cast(*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 listTop; + if (mFrontChild && !mFrontChild->canFocusStealFrontmost()) + { + for (LLView* childp : *getChildList()) + { + LLFloater* child_floaterp = static_cast(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; } } @@ -3443,6 +3547,14 @@ void LLFloaterView::setToolbarRect(LLToolBarEnums::EToolBarLocation tb, const LL } } +void LLFloaterView::onDestroyFloater(LLFloater* floater) +{ + if (mFrontChild == floater) + { + mFrontChild = nullptr; + } +} + // Prevent floaters being dragged under main chat bar void LLFloaterView::setMainChatbarRect(LLLayoutPanel* panel, const LLRect& chatbar_rect) { @@ -3567,6 +3679,7 @@ void LLFloater::initFromParams(const LLFloater::Params& p) mDefaultRelativeY = p.rel_y; mPositioning = p.positioning; + mAutoClose = p.auto_close; mHostedFloaterShowtitlebar = p.hosted_floater_show_titlebar; // MultiFloater without titlebar for hosted floater diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h index 62002a0c97..21b132f239 100644 --- a/indra/llui/llfloater.h +++ b/indra/llui/llfloater.h @@ -113,8 +113,6 @@ struct LLCoordFloater : LLCoord bool operator!=(const LLCoordFloater& other) const { return !(*this == other); } void setFloater(LLFloater& floater); - - }; class LLFloater : public LLPanel, public LLInstanceTracker @@ -170,7 +168,8 @@ public: save_visibility, save_dock_state, can_dock, - show_title; + show_title, + auto_close; Optional positioning; @@ -248,6 +247,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(); @@ -266,10 +266,14 @@ public: std::string getShortTitle() const; virtual void setMinimized(BOOL b); void moveResizeHandlesToFront(); - void addDependentFloater(LLFloater* dependent, BOOL reposition = TRUE); - void addDependentFloater(LLHandle dependent_handle, BOOL reposition = TRUE); + void addDependentFloater(LLFloater* dependent, BOOL reposition = TRUE, BOOL resize = FALSE); + void addDependentFloater(LLHandle dependent_handle, BOOL reposition = TRUE, BOOL resize = FALSE); LLFloater* getDependee() { return (LLFloater*)mDependeeHandle.get(); } - void removeDependentFloater(LLFloater* dependent); + void removeDependentFloater(LLFloater* dependent); + // Fix floater relocation + //void fitWithDependentsOnScreen(const LLRect& left, const LLRect& bottom, const LLRect& right, const LLRect& constraint, S32 min_overlap_pixels); + void fitWithDependentsOnScreen(const LLRect& left, const LLRect& bottom, const LLRect& right, const LLRect& chatbar, const LLRect& utilitybar, 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. @@ -326,6 +330,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()); @@ -405,6 +412,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; } @@ -506,8 +514,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; BOOL mCanSnooze; // FIRE-11724: Snooze group chat LLFloaterEnums::EOpenPositioning mPositioning; @@ -528,6 +538,7 @@ private: typedef std::set > handle_set_t; typedef std::set >::iterator handle_set_iter_t; handle_set_t mDependents; + bool mTranslateWithDependents { false }; bool mButtonsEnabled[BUTTON_COUNT]; F32 mButtonScale; @@ -634,6 +645,7 @@ public: // Fix for bad edge snapping void setToolbarRect(LLToolBarEnums::EToolBarLocation tb, const LLRect& toolbar_rect); + void onDestroyFloater(LLFloater* floater); // Prevent floaters being dragged under main chat bar void setMainChatbarRect(LLLayoutPanel* panel, const LLRect& chatbar_rect); @@ -658,7 +670,7 @@ private: S32 mMinimizePositionVOffset; typedef std::vector, boost::signals2::connection> > hidden_floaters_t; hidden_floaters_t mHiddenFloaters; - LLHandle mFrontChildHandle; + LLFloater * mFrontChild; // Prevent floaters being dragged under main chat bar LLRect mMainChatbarRect; diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index 42f0ad68cb..b31c4e8c1c 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -963,7 +963,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() @@ -1073,7 +1073,7 @@ void LLFolderViewItem::draw() static const std::string locked_string = " (" + LLTrans::getString("LockedFolder") + ") "; font->renderUTF8(locked_string, 0, right_x, y, sProtectedColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, - S32_MAX, S32_MAX, &right_x, FALSE); + S32_MAX, S32_MAX, &right_x, FALSE, FALSE); } // @@ -1083,7 +1083,7 @@ void LLFolderViewItem::draw() static const std::string protected_string = " (" + LLTrans::getString("ProtectedFolder") + ") "; font->renderUTF8(protected_string, 0, right_x, y, sProtectedColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, - S32_MAX, S32_MAX, &right_x, FALSE); + S32_MAX, S32_MAX, &right_x, FALSE, FALSE); } // @@ -1094,7 +1094,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); } //--------------------------------------------------------------------------------// @@ -1106,9 +1106,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 { @@ -1117,8 +1117,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; @@ -1127,7 +1128,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 3e181c6bea..a66f8e20da 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 ), @@ -1814,6 +1816,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; @@ -2152,7 +2168,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, @@ -2188,7 +2204,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 4833c76179..dc23d8a17d 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; @@ -121,54 +122,55 @@ protected: //void showContextMenu(S32 x, S32 y); void showContextMenu(S32 x, S32 y, bool set_cursor_pos = true); // + 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; @@ -176,27 +178,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; // FIRE-11373: Autoreplace doesn't work in nearby chat bar typedef boost::function autoreplace_callback_t; @@ -224,7 +227,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; } @@ -331,14 +334,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); @@ -415,6 +418,7 @@ protected: BOOL mReadOnly; BOOL mShowImageFocused; + BOOL mShowLabelFocused; bool mUseBgColor; diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 6593f89940..7aa9c11be4 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -594,6 +594,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 eb24017c49..a35c0fcf7d 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -236,10 +236,10 @@ public: struct Params : public LLInitParam::Block { Optional 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 300a124d68..ac1d0f8d4b 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -919,7 +919,7 @@ public: /* virtual */ LLNotificationPtr add(const std::string& name, const LLSD& substitutions, const LLSD& payload, - LLNotificationFunctorRegistry::ResponseFunctor functor); + LLNotificationFunctorRegistry::ResponseFunctor functor) override; LLNotificationPtr add(const LLNotification::Params& p); void add(const LLNotificationPtr pNotif); @@ -970,8 +970,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 613bb2ca2f..8b389defc0 100644 --- a/indra/llui/llscrollcontainer.h +++ b/indra/llui/llscrollcontainer.h @@ -64,7 +64,8 @@ public: Optional is_opaque, reserve_scroll_corner, border_visible, - hide_scrollbar; + hide_scrollbar, + ignore_arrow_keys; Optional min_auto_scroll_rate, max_auto_scroll_rate; Optional max_auto_scroll_zone; @@ -152,6 +153,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 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::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::iterator iter = mPanelList.begin(); - iter != mPanelList.end(); ++iter) + else { - LLScrollingPanel *childp = *iter; - cur_y -= childp->getRect().getHeight(); - childp->translate( -childp->getRect().mLeft, cur_y - childp->getRect().mBottom); - cur_y -= GAP_BETWEEN_PANELS; + addChildInBack(panel); + mPanelList.push_front(panel); } - return total_height; + rearrange(); + + return mIsHorizontal ? getRect().getWidth() : getRect().getHeight(); } void LLScrollingPanelList::removePanel(LLScrollingPanel* panel) @@ -100,7 +91,7 @@ void LLScrollingPanelList::removePanel(LLScrollingPanel* panel) break; } } - if(iter != mPanelList.end()) + if (iter != mPanelList.end()) { removePanel(index); } @@ -120,36 +111,7 @@ void LLScrollingPanelList::removePanel( U32 panel_index ) mPanelList.erase( mPanelList.begin() + panel_index ); } - const S32 GAP_BETWEEN_PANELS = 6; - - // Resize this view - S32 total_height = 0; - S32 max_width = 0; - S32 cur_gap = 0; - for (std::deque::iterator iter = mPanelList.begin(); - iter != mPanelList.end(); ++iter) - { - LLScrollingPanel *childp = *iter; - total_height += childp->getRect().getHeight() + cur_gap; - max_width = llmax( max_width, childp->getRect().getWidth() ); - cur_gap = GAP_BETWEEN_PANELS; - } - LLRect rc = getRect(); - rc.setLeftTopAndSize(rc.mLeft, rc.mTop, max_width, total_height); - setRect(rc); - - notifySizeChanged(rc.getHeight()); - - // Reposition each of the child views - S32 cur_y = total_height; - for (std::deque::iterator iter = mPanelList.begin(); - iter != mPanelList.end(); ++iter) - { - LLScrollingPanel *childp = *iter; - cur_y -= childp->getRect().getHeight(); - childp->translate( -childp->getRect().mLeft, cur_y - childp->getRect().mBottom); - cur_y -= GAP_BETWEEN_PANELS; - } + rearrange(); } void LLScrollingPanelList::updatePanels(BOOL allow_modify) @@ -162,20 +124,91 @@ void LLScrollingPanelList::updatePanels(BOOL allow_modify) } } +void LLScrollingPanelList::rearrange() +{ + // Resize this view + S32 new_width, new_height; + if (!mPanelList.empty()) + { + new_width = new_height = mPadding * 2; + for (std::deque::iterator iter = mPanelList.begin(); + iter != mPanelList.end(); ++iter) + { + LLScrollingPanel* childp = *iter; + const LLRect& rect = childp->getRect(); + if (mIsHorizontal) + { + new_width += rect.getWidth() + mSpacing; + new_height = llmax(new_height, rect.getHeight()); + } + else + { + new_height += rect.getHeight() + mSpacing; + new_width = llmax(new_width, rect.getWidth()); + } + } + + if (mIsHorizontal) + { + new_width -= mSpacing; + } + else + { + new_height -= mSpacing; + } + } + else + { + new_width = new_height = 1; + } + + LLRect rc = getRect(); + if (mIsHorizontal || !followsRight()) + { + rc.mRight = rc.mLeft + new_width; + } + if (!mIsHorizontal || !followsBottom()) + { + rc.mBottom = rc.mTop - new_height; + } + + if (rc.mRight != getRect().mRight || rc.mBottom != getRect().mBottom) + { + setRect(rc); + notifySizeChanged(); + } + + // Reposition each of the child views + S32 pos = mIsHorizontal ? mPadding : rc.getHeight() - mPadding; + for (std::deque::iterator iter = mPanelList.begin(); + iter != mPanelList.end(); ++iter) + { + LLScrollingPanel* childp = *iter; + const LLRect& rect = childp->getRect(); + if (mIsHorizontal) + { + childp->translate(pos - rect.mLeft, rc.getHeight() - mPadding - rect.mTop); + pos += rect.getWidth() + mSpacing; + } + else + { + childp->translate(mPadding - rect.mLeft, pos - rect.mTop); + pos -= rect.getHeight() + mSpacing; + } + } +} + void LLScrollingPanelList::updatePanelVisiblilty() { // Determine visibility of children. - S32 BORDER_WIDTH = 2; // HACK - LLRect parent_local_rect = getParent()->getRect(); - parent_local_rect.stretch( -BORDER_WIDTH ); - LLRect parent_screen_rect; - getParent()->localPointToScreen( - BORDER_WIDTH, 0, + getParent()->localPointToScreen( + mPadding, mPadding, &parent_screen_rect.mLeft, &parent_screen_rect.mBottom ); - getParent()->localPointToScreen( - parent_local_rect.getWidth() - BORDER_WIDTH, parent_local_rect.getHeight() - BORDER_WIDTH, + getParent()->localPointToScreen( + getParent()->getRect().getWidth() - mPadding, + getParent()->getRect().getHeight() - mPadding, &parent_screen_rect.mRight, &parent_screen_rect.mTop ); for (std::deque::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 - {}; - LLScrollingPanelList(const Params& p) - : LLUICtrl(p) - {} + { + Optional is_horizontal; + Optional padding; + Optional 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 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 e909446e8f..93a161c118 100644 --- a/indra/llui/llscrolllistctrl.cpp +++ b/indra/llui/llscrolllistctrl.cpp @@ -495,7 +495,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()) @@ -1385,7 +1385,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); } @@ -3155,7 +3155,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 30d7e54f82..a7106c5d53 100644 --- a/indra/llui/llscrolllistctrl.h +++ b/indra/llui/llscrolllistctrl.h @@ -259,7 +259,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); // Allow appending of comment text void addCommentText( const std::string& comment_text); // Allow appending of comment text @@ -277,7 +277,7 @@ public: BOOL selectItemBySubstring(const LLWString& target, BOOL case_sensitive = TRUE); BOOL selectItemByStringMatch(const LLWString& target, bool prefix_match, 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(); @@ -339,7 +339,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); @@ -394,13 +394,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. @@ -421,18 +421,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) @@ -487,8 +489,6 @@ public: mutable U32 mLastUpdateFrame; 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 dict_list_t; diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 64b2f7e37e..3e0b4ae11f 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" @@ -180,10 +182,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), // Optional icon position icon_positioning("icon_positioning", LLTextBaseEnums::RIGHT), // Optional icon position @@ -233,6 +237,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), @@ -247,6 +252,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), @@ -725,7 +731,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); @@ -1045,6 +1051,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() ) @@ -1228,6 +1256,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 @@ -1282,6 +1311,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); @@ -1301,6 +1331,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); @@ -1312,6 +1343,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); @@ -1323,6 +1355,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); @@ -1334,6 +1367,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); @@ -1345,6 +1379,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 @@ -1364,6 +1399,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); @@ -1375,6 +1411,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); @@ -1386,6 +1423,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); @@ -1397,7 +1435,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) @@ -1424,6 +1475,7 @@ void LLTextBase::reshape(S32 width, S32 height, BOOL called_from_parent) } } +//virtual void LLTextBase::draw() { // reflow if needed, on demand @@ -2158,21 +2210,8 @@ LLTextBase::segment_set_t::const_iterator LLTextBase::getEditableSegIterContaini LLTextBase::segment_set_t::iterator LLTextBase::getSegIterContaining(S32 index) { - static LLPointer 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(); } @@ -2187,18 +2226,6 @@ LLTextBase::segment_set_t::const_iterator LLTextBase::getSegIterContaining(S32 i { static LLPointer 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(); } @@ -2431,8 +2458,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; @@ -2738,18 +2765,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; @@ -2757,7 +2784,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()); } @@ -2769,25 +2796,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); } @@ -3676,12 +3706,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; @@ -3695,12 +3726,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 ) @@ -3712,12 +3744,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; } @@ -3961,6 +3994,33 @@ const S32 LLLabelTextSegment::getLength() const return mEditor.getWlabel().length(); } +// +// LLEmojiTextSegment +// +LLEmojiTextSegment::LLEmojiTextSegment(LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor) + : LLNormalTextSegment(style, start, end, editor) +{ +} + +LLEmojiTextSegment::LLEmojiTextSegment(const LLColor4& color, S32 start, S32 end, LLTextBase& editor, BOOL is_visible) + : LLNormalTextSegment(color, start, end, editor, is_visible) +{ +} + +BOOL LLEmojiTextSegment::handleToolTip(S32 x, S32 y, MASK mask) +{ + if (mTooltip.empty()) + { + LLWString emoji = getWText().substr(getStart(), getEnd() - getStart()); + if (!emoji.empty()) + { + mTooltip = LLEmojiHelper::instance().getToolTip(emoji[0]); + } + } + + return LLNormalTextSegment::handleToolTip(x, y, mask); +} + // // LLOnHoverChangeableTextSegment // diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 6837637d2f..074d9f19d6 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 { @@ -294,7 +306,7 @@ namespace LLInitParam /// as LLTextEditor and LLTextBox. It implements shared functionality /// such as Url highlighting and opening. /// -class LLTextBase +class LLTextBase : public LLUICtrl, protected LLEditMenuHandler, public LLSpellCheckMenuHandler, @@ -340,6 +352,7 @@ public: plain_text, wrap, use_ellipses, + use_color, parse_urls, force_urls_external, parse_highlights, @@ -359,6 +372,8 @@ public: Optional font_shadow; + Optional text_valign; + // Optional icon position Optional icon_positioning; @@ -366,51 +381,52 @@ public: }; // 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; @@ -421,6 +437,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;} @@ -443,7 +460,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();} @@ -680,7 +697,8 @@ protected: S32 normalizeUri(std::string& uri); protected: - virtual std::string _getSearchText() const + // virtual + std::string _getSearchText() const override { return mLabel.getString() + getToolTip(); } @@ -745,8 +763,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; @@ -755,6 +774,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 e6759a6cb1..e91e6894f4 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"), enable_tab_remove("enable_tab_remove", true) // FIRE-15591: Optional tab remove { @@ -259,6 +261,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), @@ -555,6 +558,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()) @@ -719,6 +732,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; @@ -992,6 +1027,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) ) { @@ -1041,7 +1082,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 ) ); } @@ -1065,11 +1106,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; @@ -1084,14 +1126,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. @@ -1105,8 +1147,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 { @@ -1117,7 +1161,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() @@ -1126,10 +1170,12 @@ void LLTextEditor::removeChar() { return; } + if (mCursorPos > 0) { setCursorPos(mCursorPos - 1); removeChar(mCursorPos); + tryToShowEmojiHelper(); } else { @@ -1217,6 +1263,7 @@ void LLTextEditor::addChar(llwchar wc) } setCursorPos(mCursorPos + addChar( mCursorPos, wc )); + tryToShowEmojiHelper(); if (!mReadOnly && mAutoreplaceCallback != NULL) { @@ -1236,6 +1283,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() ) @@ -1963,6 +2041,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() && LLToolTipMgr::instance().isTooltipPastable() && @@ -2005,6 +2088,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; @@ -2023,7 +2112,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 4fc837c439..aad0d2a864 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, enable_tab_remove, // FIRE-15591: Optional tab remove auto_indent; @@ -92,6 +93,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); @@ -212,6 +216,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: @@ -263,6 +271,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; @@ -333,6 +342,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 5d012dad83..77ce32d15d 100644 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -798,25 +798,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 cc8659834b..c4015b239f 100644 --- a/indra/llui/lluictrl.h +++ b/indra/llui/lluictrl.h @@ -280,7 +280,7 @@ public: class LLTextInputFilter : public LLQueryFilter, public LLSingleton { 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(view)->acceptsTextInput(), TRUE); } diff --git a/indra/llui/lluistring.h b/indra/llui/lluistring.h index 55a202bef6..dcd9882e91 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 fe6533ec88..fb9afd66d3 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -65,6 +65,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; @@ -570,7 +571,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(*next); ctrl->setFocus(TRUE); @@ -1080,7 +1081,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; } } } @@ -1392,8 +1393,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(); @@ -1809,23 +1809,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 ) + if (KEEP_ONSCREEN_PIXELS_WIDTH <= constraint.getWidth() && + KEEP_ONSCREEN_PIXELS_HEIGHT <= constraint.getHeight()) { - 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.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; @@ -1836,13 +1839,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; } @@ -2022,7 +2031,7 @@ private: class SortByTabOrder : public LLQuerySorter, public LLSingleton { 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())); } @@ -2046,7 +2055,7 @@ const LLViewQuery & LLView::getTabOrderQuery() class LLFocusRootsFilter : public LLQueryFilter, public LLSingleton { 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 fea241c92d..e1b9d82c4d 100644 --- a/indra/llui/llview.h +++ b/indra/llui/llview.h @@ -111,7 +111,7 @@ public: Alternative string; Alternative flags; - Follows(); + Follows(); }; struct Params : public LLInitParam::Block @@ -373,6 +373,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); @@ -670,8 +671,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. @@ -714,19 +718,15 @@ template T* LLView::getChild(const std::string& name, BOOL recurse) co if (!result) { result = LLUICtrlFactory::getDefaultWidget(name); + if (!result) + { + LL_ERRS() << "Failed to create dummy " << typeid(T).name() << LL_ENDL; + } - if (result) - { - // *NOTE: You cannot call mFoo = getChild("bar") - // in a floater or panel constructor. The widgets will not - // be ready. Instead, put it in postBuild(). - LL_WARNS() << "Making dummy " << typeid(T).name() << " named \"" << name << "\" in " << getName() << LL_ENDL; - } - else - { - LL_WARNS() << "Failed to create dummy " << typeid(T).name() << LL_ENDL; - return NULL; - } + // *NOTE: You cannot call mFoo = getChild("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 { 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 { 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 { 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 { 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 { 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 { 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 diff --git a/indra/llwindow/llopenglview-objc.mm b/indra/llwindow/llopenglview-objc.mm index 08a38a8ad7..b62df21803 100644 --- a/indra/llwindow/llopenglview-objc.mm +++ b/indra/llwindow/llopenglview-objc.mm @@ -743,23 +743,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 be2375d0b8..f71601900d 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -19,6 +19,7 @@ include(EXPAT) include(FMODSTUDIO) include(GLOD) # restore GLOD include(Hunspell) +include(ICU4C) include(JPEGEncoderBasic) include(JsonCpp) include(LLAppearance) @@ -316,6 +317,7 @@ set(viewer_SOURCE_FILES llfloaterdisplayname.cpp llfloatereditenvironmentbase.cpp llfloatereditextdaycycle.cpp + llfloateremojipicker.cpp llfloaterenvironmentadjust.cpp llfloaterevent.cpp llfloaterexperiencepicker.cpp @@ -531,6 +533,7 @@ set(viewer_SOURCE_FILES llpaneleditsky.cpp llpaneleditwater.cpp llpaneleditwearable.cpp + llpanelemojicomplete.cpp llpanelenvironment.cpp llpanelexperiencelisteditor.cpp llpanelexperiencelog.cpp @@ -1107,6 +1110,7 @@ set(viewer_HEADER_FILES llfloaterdisplayname.h llfloatereditenvironmentbase.h llfloatereditextdaycycle.h + llfloateremojipicker.h llfloaterenvironmentadjust.h llfloaterevent.h llfloaterexperiencepicker.h @@ -1314,6 +1318,7 @@ set(viewer_HEADER_FILES llpaneleditsky.h llpaneleditwater.h llpaneleditwearable.h + llpanelemojicomplete.h llpanelenvironment.h llpanelexperiencelisteditor.h llpanelexperiencelog.h @@ -2406,6 +2411,7 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${LLPHYSICSEXTENSIONS_LIBRARIES} ll::bugsplat ll::tracy + ll::icu4c fs::glod # restore GLOD dependencies fs::discord # Discord support ) @@ -2444,6 +2450,32 @@ endif (LINUX) set(ARTWORK_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE PATH "Path to artwork files.") +# Don't copy fonts into the source folder! +#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") +# + +# Don't copy emoji characters into source folder +# 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 Firestorm-${ARCH}-${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}) diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index 1996c50447..b7f8ee41e6 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -7.1.3 +7.1.4 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 @@ + + + + + Name + all + Character + 🔍 + + + Character + 😀 + Categories + + smileys and emotion + people and body + + + + Character + 🥬 + Categories + + animals and nature + + + + Character + 🍔 + Categories + + food and drink + + + + Character + 🛩 + Categories + + travel and places + + + + Character + 🏈 + Categories + + activities + + + + Character + 💡 + Categories + + objects + + + + Character + + Categories + + symbols + + + + Name + others + Character + 🌂 + + + Name + skip + Categories + + components + + + + diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 11909adb73..e7dd7ea589 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -13459,7 +13459,7 @@ Change of this parameter will affect the layout of buttons in notification toast RenderReflectionsEnabled Comment - Enable/disable reflection probes + Enable/disable reflection probes - Deprecated, disabling this removes all sky ambiance, use RenderReflectionProbeLevel=0 instead Persist 1 Type @@ -26506,6 +26506,17 @@ Change of this parameter will affect the layout of buttons in notification toast Value 0 + FSShowEmojiButton + + Comment + Show or hide the emoji selection button in chat/IM windows + Persist + 1 + Type + Boolean + Value + 1 + FSAnimationPreviewExpanded Comment diff --git a/indra/newview/app_settings/shaders/class1/deferred/rlvF.glsl b/indra/newview/app_settings/shaders/class1/deferred/rlvF.glsl index d4aec3be7a..20ac01735b 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/rlvF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/rlvF.glsl @@ -130,7 +130,7 @@ void main() break; case 2: // Blur (variable) fragColor = texture(diffuseRect, fragTC).rgb; - fragColor = mix(fragColor, blurVariable(diffuseRect, fragTC, SPHERE_PARAMS.x, BLUR_DIRECTION, effectStrength), int(effectStrength > 0)); + fragColor = mix(fragColor, blurVariable(diffuseRect, fragTC, SPHERE_PARAMS.x, BLUR_DIRECTION, effectStrength), bvec3(effectStrength > 0)); break; case 3: // ChromaticAberration fragColor = chromaticAberration(diffuseRect, fragTC, SPHERE_PARAMS.xy, SPHERE_PARAMS.zw, effectStrength); 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 deleted file mode 100644 index 6d65fa7dc4..0000000000 Binary files a/indra/newview/fonts/DejaVuSans-Bold.ttf and /dev/null differ diff --git a/indra/newview/fonts/DejaVuSans-BoldOblique.ttf b/indra/newview/fonts/DejaVuSans-BoldOblique.ttf deleted file mode 100644 index 753f2d80b1..0000000000 Binary files a/indra/newview/fonts/DejaVuSans-BoldOblique.ttf and /dev/null differ diff --git a/indra/newview/fonts/DejaVuSans-Oblique.ttf b/indra/newview/fonts/DejaVuSans-Oblique.ttf deleted file mode 100644 index 999bac7714..0000000000 Binary files a/indra/newview/fonts/DejaVuSans-Oblique.ttf and /dev/null differ diff --git a/indra/newview/fonts/DejaVuSans.ttf b/indra/newview/fonts/DejaVuSans.ttf deleted file mode 100644 index e5f7eecce4..0000000000 Binary files a/indra/newview/fonts/DejaVuSans.ttf and /dev/null differ diff --git a/indra/newview/fonts/DejaVuSansMono.ttf b/indra/newview/fonts/DejaVuSansMono.ttf deleted file mode 100644 index f5786022f1..0000000000 Binary files a/indra/newview/fonts/DejaVuSansMono.ttf and /dev/null differ diff --git a/indra/newview/fonts/fonts.xml b/indra/newview/fonts/fonts.xml index 4d921b7a5d..58f45e6d80 100644 --- a/indra/newview/fonts/fonts.xml +++ b/indra/newview/fonts/fonts.xml @@ -3,6 +3,7 @@ DejaVuSans.ttf + TwemojiSVG.ttf meiryo.TTC YuGothR.ttc @@ -76,6 +77,11 @@ DejaVuSans-BoldOblique.ttf + + TwemojiSVG.ttf + + DejaVuSansMono.ttf diff --git a/indra/newview/fonts/fonts_cascadia_code.xml b/indra/newview/fonts/fonts_cascadia_code.xml index 7467601597..c854b7b2b9 100644 --- a/indra/newview/fonts/fonts_cascadia_code.xml +++ b/indra/newview/fonts/fonts_cascadia_code.xml @@ -3,6 +3,7 @@ CascadiaCode-Light.ttf + TwemojiSVG.ttf meiryo.TTC YuGothR.ttc @@ -76,6 +77,11 @@ CascadiaCode-Light.ttf + + TwemojiSVG.ttf + + CascadiaCode-Light.ttf diff --git a/indra/newview/fonts/fonts_celestia_medium_redux.xml b/indra/newview/fonts/fonts_celestia_medium_redux.xml index eb4c726e36..7a37d265ae 100644 --- a/indra/newview/fonts/fonts_celestia_medium_redux.xml +++ b/indra/newview/fonts/fonts_celestia_medium_redux.xml @@ -3,6 +3,7 @@ CelestiaMediumRedux1.55.ttf + TwemojiSVG.ttf meiryo.TTC YuGothR.ttc @@ -76,6 +77,11 @@ CelestiaMediumRedux1.55.ttf + + TwemojiSVG.ttf + + DejaVuSansMono.ttf diff --git a/indra/newview/fonts/fonts_deja_vu_all_caps.xml b/indra/newview/fonts/fonts_deja_vu_all_caps.xml index 8eb6df4f6a..a40030d90b 100644 --- a/indra/newview/fonts/fonts_deja_vu_all_caps.xml +++ b/indra/newview/fonts/fonts_deja_vu_all_caps.xml @@ -3,6 +3,7 @@ DejaVuSansAllCaps.ttf + TwemojiSVG.ttf meiryo.TTC YuGothR.ttc @@ -74,6 +75,11 @@ DejaVuSansAllCaps-BoldOblique.ttf + + TwemojiSVG.ttf + + DejaVuSansAllCapsMono.ttf diff --git a/indra/newview/fonts/fonts_droid.xml b/indra/newview/fonts/fonts_droid.xml index 2e37a9e1a9..ec61de5fa6 100644 --- a/indra/newview/fonts/fonts_droid.xml +++ b/indra/newview/fonts/fonts_droid.xml @@ -3,6 +3,7 @@ DroidSans.ttf + TwemojiSVG.ttf meiryo.TTC YuGothR.ttc @@ -76,6 +77,11 @@ DroidSerif-BoldItalic.ttf + + TwemojiSVG.ttf + + DroidSansMono.ttf diff --git a/indra/newview/fonts/fonts_dyslexia.xml b/indra/newview/fonts/fonts_dyslexia.xml index ffdb023159..98e36c4c24 100644 --- a/indra/newview/fonts/fonts_dyslexia.xml +++ b/indra/newview/fonts/fonts_dyslexia.xml @@ -3,6 +3,7 @@ MobiSans.ttf + TwemojiSVG.ttf meiryo.TTC YuGothR.ttc @@ -76,6 +77,11 @@ opendyslexic-bolditalic.ttf + + TwemojiSVG.ttf + + SourceCodePro-Regular.ttf diff --git a/indra/newview/fonts/fonts_liberation.xml b/indra/newview/fonts/fonts_liberation.xml index 598367730c..343c319dd8 100644 --- a/indra/newview/fonts/fonts_liberation.xml +++ b/indra/newview/fonts/fonts_liberation.xml @@ -3,6 +3,7 @@ LiberationSans-Regular.ttf + TwemojiSVG.ttf meiryo.TTC YuGothR.ttc @@ -76,6 +77,11 @@ LiberationSans-BoldItalic.ttf + + TwemojiSVG.ttf + + LiberationMono-Regular.ttf diff --git a/indra/newview/fonts/fonts_mobi.xml b/indra/newview/fonts/fonts_mobi.xml index cc3c8f78ce..500cde4657 100644 --- a/indra/newview/fonts/fonts_mobi.xml +++ b/indra/newview/fonts/fonts_mobi.xml @@ -3,6 +3,7 @@ MobiSans.ttf + TwemojiSVG.ttf meiryo.TTC YuGothR.ttc @@ -76,6 +77,11 @@ MobiSans-BoldItalic.ttf + + TwemojiSVG.ttf + + MobiSansMono.ttf diff --git a/indra/newview/fonts/fonts_noto.xml b/indra/newview/fonts/fonts_noto.xml index 00ac2f67ca..c9a35877b4 100644 --- a/indra/newview/fonts/fonts_noto.xml +++ b/indra/newview/fonts/fonts_noto.xml @@ -3,6 +3,7 @@ NotoSans.ttf + TwemojiSVG.ttf meiryo.TTC YuGothR.ttc @@ -76,6 +77,11 @@ NotoSans-BoldItalic.ttf + + TwemojiSVG.ttf + + NotoMono-Regular.ttf diff --git a/indra/newview/fonts/fonts_roboto.xml b/indra/newview/fonts/fonts_roboto.xml index 71332707c2..83872ff9c4 100644 --- a/indra/newview/fonts/fonts_roboto.xml +++ b/indra/newview/fonts/fonts_roboto.xml @@ -3,6 +3,7 @@ DroidSans.ttf + TwemojiSVG.ttf meiryo.TTC YuGothR.ttc @@ -76,6 +77,11 @@ Roboto-BoldItalic.ttf + + TwemojiSVG.ttf + + MobiSansMono.ttf diff --git a/indra/newview/fonts/fonts_ubuntu.xml b/indra/newview/fonts/fonts_ubuntu.xml index 2278e0f301..d8a99a4baf 100644 --- a/indra/newview/fonts/fonts_ubuntu.xml +++ b/indra/newview/fonts/fonts_ubuntu.xml @@ -3,6 +3,7 @@ Ubuntu-R.ttf + TwemojiSVG.ttf meiryo.TTC YuGothR.ttc @@ -76,6 +77,11 @@ Ubuntu-MI.ttf + + TwemojiSVG.ttf + + UbuntuMono-R.ttf diff --git a/indra/newview/fsassetblacklist.cpp b/indra/newview/fsassetblacklist.cpp index 098543a998..bd59437e4a 100644 --- a/indra/newview/fsassetblacklist.cpp +++ b/indra/newview/fsassetblacklist.cpp @@ -66,7 +66,7 @@ LLAssetType::EType S32toAssetType(S32 assetindex) return type; } -void FSAssetBlacklist::init() +void FSAssetBlacklist::initSingleton() { mBlacklistFileName = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "asset_blacklist.xml"); loadBlacklist(); diff --git a/indra/newview/fsassetblacklist.h b/indra/newview/fsassetblacklist.h index d7fbdcb612..d7a90b10ff 100644 --- a/indra/newview/fsassetblacklist.h +++ b/indra/newview/fsassetblacklist.h @@ -44,7 +44,7 @@ class FSAssetBlacklist : public LLSingleton LLSINGLETON_EMPTY_CTOR(FSAssetBlacklist); public: - void init(); + void initSingleton() override; bool isBlacklisted(const LLUUID& id, LLAssetType::EType type); void addNewItemToBlacklist(const LLUUID& id, const std::string& name, const std::string& region, LLAssetType::EType type, bool permanent = true, bool save = true); void addNewItemToBlacklistData(const LLUUID& id, const LLSD& data, bool save = true); diff --git a/indra/newview/fschathistory.cpp b/indra/newview/fschathistory.cpp index df13d853f6..b25c4eb6ff 100644 --- a/indra/newview/fschathistory.cpp +++ b/indra/newview/fschathistory.cpp @@ -1259,6 +1259,8 @@ FSChatHistory::FSChatHistory(const FSChatHistory::Params& p) mUnreadChatSources(0) { mLineSpacingPixels = llclamp(gSavedSettings.getS32("FSFontChatLineSpacingPixels"), 0, 36); + mTextVAlign = LLFontGL::VAlign::VCENTER; + mUseColor = true; setIsObjectBlockedCallback(boost::bind(&LLMuteList::isMuted, LLMuteList::getInstance(), _1, _2, 0)); } diff --git a/indra/newview/fscommon.cpp b/indra/newview/fscommon.cpp index 4a35c0def6..b4421c4ae9 100644 --- a/indra/newview/fscommon.cpp +++ b/indra/newview/fscommon.cpp @@ -33,6 +33,8 @@ #include "llagentbenefits.h" #include "llavataractions.h" #include "llavatarnamecache.h" +#include "llemojidictionary.h" +#include "llfloateremojipicker.h" #include "llfloaterperms.h" #include "llgroupactions.h" #include "llgroupmgr.h" @@ -519,3 +521,23 @@ LLUUID FSCommon::getGroupForRezzing() return group_id; } + +void FSCommon::updateUsedEmojis(LLWString text) +{ + LLEmojiDictionary* dictionary = LLEmojiDictionary::getInstance(); + + bool emojiSent = false; + for (llwchar& c : text) + { + if (dictionary->isEmoji(c)) + { + LLFloaterEmojiPicker::onEmojiUsed(c); + emojiSent = true; + } + } + + if (!emojiSent) + return; + + LLFloaterEmojiPicker::saveState(); +} \ No newline at end of file diff --git a/indra/newview/fscommon.h b/indra/newview/fscommon.h index 4653b1a402..0cf2196313 100644 --- a/indra/newview/fscommon.h +++ b/indra/newview/fscommon.h @@ -93,6 +93,8 @@ namespace FSCommon bool isFilterEditorKeyCombo(KEY key, MASK mask); LLUUID getGroupForRezzing(); + + void updateUsedEmojis(LLWString text); }; #endif // FS_COMMON_H diff --git a/indra/newview/fsfloaterim.cpp b/indra/newview/fsfloaterim.cpp index b96d8461d2..897289cc91 100644 --- a/indra/newview/fsfloaterim.cpp +++ b/indra/newview/fsfloaterim.cpp @@ -53,6 +53,7 @@ #include "llconsole.h" #include "llfloaterabout.h" // for sysinfo button -Zi #include "llfloateravatarpicker.h" +#include "llfloateremojipicker.h" #include "llfloaterreg.h" #include "llfloatersearchreplace.h" #include "llgroupactions.h" @@ -63,6 +64,7 @@ #include "llnotifications.h" #include "llnotificationsutil.h" #include "llnotificationtemplate.h" // Viewer version popup +#include "llpanelemojicomplete.h" #include "llrootview.h" #include "llscreenchannel.h" #include "llspeakers.h" @@ -375,6 +377,8 @@ void FSFloaterIM::sendMsgFromInputEditor(EChatType type) LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines. if (!text.empty()) { + FSCommon::updateUsedEmojis(text); + if (type == CHAT_TYPE_OOC) { std::string tempText = wstring_to_utf8str( text ); @@ -599,6 +603,11 @@ FSFloaterIM::~FSFloaterIM() { mAvatarNameCacheConnection.disconnect(); } + + if (mRecentEmojisUpdatedCallbackConnection.connected()) + { + mRecentEmojisUpdatedCallbackConnection.disconnect(); + } } void FSFloaterIM::onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state) @@ -949,8 +958,27 @@ BOOL FSFloaterIM::postBuild() mInputEditor->enableSingleLineMode(gSavedSettings.getBOOL("FSUseSingleLineChatEntry")); mInputEditor->setCommitCallback(boost::bind(&FSFloaterIM::sendMsgFromInputEditor, this, CHAT_TYPE_NORMAL)); - getChild("send_chat")->setCommitCallback(boost::bind(&FSFloaterIM::sendMsgFromInputEditor, this, CHAT_TYPE_NORMAL)); + mEmojiRecentPanelToggleBtn = getChild("emoji_recent_panel_toggle_btn"); + mEmojiRecentPanelToggleBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiRecentPanelToggleBtnClicked(); }); + mEmojiRecentPanel = getChild("emoji_recent_layout_panel"); + mEmojiRecentPanel->setVisible(false); + + mEmojiRecentEmptyText = getChild("emoji_recent_empty_text"); + mEmojiRecentEmptyText->setToolTip(mEmojiRecentEmptyText->getText()); + mEmojiRecentEmptyText->setVisible(false); + + mEmojiRecentIconsCtrl = getChild("emoji_recent_icons_ctrl"); + mEmojiRecentIconsCtrl->setCommitCallback([this](LLUICtrl*, const LLSD& value) { onRecentEmojiPicked(value); }); + mEmojiRecentIconsCtrl->setVisible(false); + + mEmojiPickerToggleBtn = getChild("emoji_picker_toggle_btn"); + mEmojiPickerToggleBtn->setLabel(LLUIString(LLWString(1, 128512))); + mEmojiPickerToggleBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiPickerToggleBtnClicked(); }); + + mRecentEmojisUpdatedCallbackConnection = LLFloaterEmojiPicker::setRecentEmojisUpdatedCallback([this](const std::list& recent_emojis_list) { initEmojiRecentPanel(); }); + + getChild("send_chat")->setCommitCallback(boost::bind(&FSFloaterIM::sendMsgFromInputEditor, this, CHAT_TYPE_NORMAL)); getChild("chat_search_btn")->setCommitCallback(boost::bind(&FSFloaterIM::onChatSearchButtonClicked, this)); bool isFSSupportGroup = FSData::getInstance()->isFirestormGroup(mSessionID); @@ -2443,3 +2471,56 @@ bool FSFloaterIM::applyRectControl() return res; } + +void FSFloaterIM::onEmojiRecentPanelToggleBtnClicked() +{ + BOOL show = mEmojiRecentPanel->getVisible() ? FALSE : TRUE; + if (show) + { + initEmojiRecentPanel(); + } + + mEmojiRecentPanel->setVisible(show); + mInputEditor->setFocus(TRUE); +} + +void FSFloaterIM::initEmojiRecentPanel() +{ + std::list& 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 FSFloaterIM::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 FSFloaterIM::onEmojiPickerToggleBtnClicked() +{ + mInputEditor->setFocus(TRUE); + mInputEditor->showEmojiHelper(); +} diff --git a/indra/newview/fsfloaterim.h b/indra/newview/fsfloaterim.h index 8c5f88ee2e..ac42d03cd7 100644 --- a/indra/newview/fsfloaterim.h +++ b/indra/newview/fsfloaterim.h @@ -47,10 +47,10 @@ class LLInventoryCategory; class LLInventoryItem; class LLLayoutPanel; class LLLayoutStack; +class LLPanelEmojiComplete; class LLTextBox; class LLTextEditor; - typedef boost::signals2::signal floater_showed_signal_t; /** @@ -161,6 +161,8 @@ public: void timedUpdate(); + void onEmojiPickerToggleBtnClicked(); + protected: /* virtual */ void onClickCloseBtn(bool app_quitting = false); @@ -234,10 +236,14 @@ private: void addSessionParticipants(const uuid_vec_t& uuids); void addP2PSessionParticipants(const LLSD& notification, const LLSD& response, const uuid_vec_t& uuids); - void onChatOptionsContextMenuItemClicked(const LLSD& userdata); - bool onChatOptionsCheckContextMenuItem(const LLSD& userdata); - bool onChatOptionsVisibleContextMenuItem(const LLSD& userdata); - bool onChatOptionsEnableContextMenuItem(const LLSD& userdata); + void onChatOptionsContextMenuItemClicked(const LLSD& userdata); + bool onChatOptionsCheckContextMenuItem(const LLSD& userdata); + bool onChatOptionsVisibleContextMenuItem(const LLSD& userdata); + bool onChatOptionsEnableContextMenuItem(const LLSD& userdata); + + void onEmojiRecentPanelToggleBtnClicked(); + void initEmojiRecentPanel(); + void onRecentEmojiPicked(const LLSD& value); FSPanelChatControlPanel* mControlPanel; LLUUID mSessionID; @@ -252,6 +258,11 @@ private: LLLayoutStack* mInputPanels; LLLayoutPanel* mUnreadMessagesNotificationPanel; LLTextBox* mUnreadMessagesNotificationTextBox; + LLButton* mEmojiRecentPanelToggleBtn; + LLButton* mEmojiPickerToggleBtn; + LLLayoutPanel* mEmojiRecentPanel; + LLTextBox* mEmojiRecentEmptyText; + LLPanelEmojiComplete* mEmojiRecentIconsCtrl; std::string mSavedTitle; LLUIString mTypingStart; @@ -283,6 +294,8 @@ private: bool mApplyRect; FSFloaterIMTimer* mIMFloaterTimer; + + boost::signals2::connection mRecentEmojisUpdatedCallbackConnection{}; }; class FSFloaterIMTimer : public LLEventTimer diff --git a/indra/newview/fsfloaterimcontainer.cpp b/indra/newview/fsfloaterimcontainer.cpp index 3c1f7d88ff..2192b8dfe9 100644 --- a/indra/newview/fsfloaterimcontainer.cpp +++ b/indra/newview/fsfloaterimcontainer.cpp @@ -37,6 +37,7 @@ #include "llfloaterreg.h" #include "llchiclet.h" #include "llchicletbar.h" +#include "llemojihelper.h" #include "lltoolbarview.h" #include "llvoiceclient.h" @@ -580,4 +581,8 @@ void FSFloaterIMContainer::sessionIDUpdated(const LLUUID& old_session_id, const } } +void FSFloaterIMContainer::tabOpen(LLFloater* opened_floater, bool from_click) +{ + LLEmojiHelper::instance().hideHelper(nullptr, true); +} // EOF diff --git a/indra/newview/fsfloaterimcontainer.h b/indra/newview/fsfloaterimcontainer.h index 4d11da1993..21938f9a96 100644 --- a/indra/newview/fsfloaterimcontainer.h +++ b/indra/newview/fsfloaterimcontainer.h @@ -76,6 +76,8 @@ public: void addFlashingSession(const LLUUID& session_id); + void tabOpen(LLFloater* opened_floater, bool from_click); + private: enum eVoiceState { diff --git a/indra/newview/fsfloaternearbychat.cpp b/indra/newview/fsfloaternearbychat.cpp index aa49fc0e64..db9c677fdf 100644 --- a/indra/newview/fsfloaternearbychat.cpp +++ b/indra/newview/fsfloaternearbychat.cpp @@ -50,6 +50,7 @@ #include "llcommandhandler.h" #include "llconsole.h" #include "lldraghandle.h" +#include "llfloateremojipicker.h" #include "llfloaterreg.h" #include "llfloatersearchreplace.h" #include "llfocusmgr.h" @@ -60,6 +61,7 @@ #include "lllogchat.h" #include "llmenugl.h" #include "llmultigesture.h" +#include "llpanelemojicomplete.h" #include "llresizebar.h" #include "llresizehandle.h" #include "llrootview.h" @@ -102,6 +104,10 @@ FSFloaterNearbyChat::FSFloaterNearbyChat(const LLSD& key) FSFloaterNearbyChat::~FSFloaterNearbyChat() { + if (mRecentEmojisUpdatedCallbackConnection.connected()) + { + mRecentEmojisUpdatedCallbackConnection.disconnect(); + } } void FSFloaterNearbyChat::updateFSUseNearbyChatConsole(const LLSD &data) @@ -141,8 +147,27 @@ BOOL FSFloaterNearbyChat::postBuild() mChatLayoutPanelHeight = mChatLayoutPanel->getRect().getHeight(); mInputEditorPad = mChatLayoutPanelHeight - mInputEditor->getRect().getHeight(); - getChild("chat_history_btn")->setCommitCallback(boost::bind(&FSFloaterNearbyChat::onHistoryButtonClicked, this)); + mEmojiRecentPanelToggleBtn = getChild("emoji_recent_panel_toggle_btn"); + mEmojiRecentPanelToggleBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiRecentPanelToggleBtnClicked(); }); + mEmojiRecentPanel = getChild("emoji_recent_layout_panel"); + mEmojiRecentPanel->setVisible(false); + + mEmojiRecentEmptyText = getChild("emoji_recent_empty_text"); + mEmojiRecentEmptyText->setToolTip(mEmojiRecentEmptyText->getText()); + mEmojiRecentEmptyText->setVisible(false); + + mEmojiRecentIconsCtrl = getChild("emoji_recent_icons_ctrl"); + mEmojiRecentIconsCtrl->setCommitCallback([this](LLUICtrl*, const LLSD& value) { onRecentEmojiPicked(value); }); + mEmojiRecentIconsCtrl->setVisible(false); + + mEmojiPickerToggleBtn = getChild("emoji_picker_toggle_btn"); + mEmojiPickerToggleBtn->setLabel(LLUIString(LLWString(1, 128512))); + mEmojiPickerToggleBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiPickerToggleBtnClicked(); }); + + mRecentEmojisUpdatedCallbackConnection = LLFloaterEmojiPicker::setRecentEmojisUpdatedCallback([this](const std::list& recent_emojis_list) { initEmojiRecentPanel(); }); + + getChild("chat_history_btn")->setCommitCallback(boost::bind(&FSFloaterNearbyChat::onHistoryButtonClicked, this)); getChild("chat_search_btn")->setCommitCallback(boost::bind(&FSFloaterNearbyChat::onSearchButtonClicked, this)); // chat type selector and send chat button @@ -762,6 +787,8 @@ void FSFloaterNearbyChat::sendChat( EChatType type ) LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines. if (!text.empty()) { + FSCommon::updateUsedEmojis(text); + if(type == CHAT_TYPE_OOC) { std::string tempText = wstring_to_utf8str( text ); @@ -942,3 +969,56 @@ void FSFloaterNearbyChat::handleMinimized(bool minimized) gConsole->addSession(LLUUID::null); } } + +void FSFloaterNearbyChat::onEmojiRecentPanelToggleBtnClicked() +{ + BOOL show = mEmojiRecentPanel->getVisible() ? FALSE : TRUE; + if (show) + { + initEmojiRecentPanel(); + } + + mEmojiRecentPanel->setVisible(show); + mInputEditor->setFocus(TRUE); +} + +void FSFloaterNearbyChat::initEmojiRecentPanel() +{ + std::list& 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 FSFloaterNearbyChat::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 FSFloaterNearbyChat::onEmojiPickerToggleBtnClicked() +{ + mInputEditor->setFocus(TRUE); + mInputEditor->showEmojiHelper(); +} diff --git a/indra/newview/fsfloaternearbychat.h b/indra/newview/fsfloaternearbychat.h index 05fd42e210..02acc7dd3f 100644 --- a/indra/newview/fsfloaternearbychat.h +++ b/indra/newview/fsfloaternearbychat.h @@ -39,6 +39,7 @@ class LLChatEntry; class LLComboBox; class LLLayoutStack; class LLLayoutPanel; +class LLPanelEmojiComplete; class LLResizeBar; class LLTextBox; @@ -93,7 +94,6 @@ public: virtual BOOL handleKeyHere( KEY key, MASK mask ); - static void startChat(const char* line); static void stopChat(); void updateUnreadMessageNotification(S32 unread_messages, bool muted_history); @@ -101,6 +101,8 @@ public: void handleMinimized(bool minimized); + void onEmojiPickerToggleBtnClicked(); + protected: void onChatBoxKeystroke(); void onChatBoxFocusLost(); @@ -111,22 +113,30 @@ protected: void onChatBoxCommit(); void onChatTypeChanged(); - void reshapeFloater(bool collapse); void reshapeChatLayoutPanel(); static S32 sLastSpecialChatChannel; private: - void onChatOptionsContextMenuItemClicked(const LLSD& userdata); - bool onChatOptionsCheckContextMenuItem(const LLSD& userdata); - bool onChatOptionsVisibleContextMenuItem(const LLSD& userdata); - bool onChatOptionsEnableContextMenuItem(const LLSD& userdata); + void onChatOptionsContextMenuItemClicked(const LLSD& userdata); + bool onChatOptionsCheckContextMenuItem(const LLSD& userdata); + bool onChatOptionsVisibleContextMenuItem(const LLSD& userdata); + bool onChatOptionsEnableContextMenuItem(const LLSD& userdata); + + void onEmojiRecentPanelToggleBtnClicked(); + void initEmojiRecentPanel(); + void onRecentEmojiPicked(const LLSD& value); FSChatHistory* mChatHistory; FSChatHistory* mChatHistoryMuted; LLChatEntry* mInputEditor; // chat type selector and send chat buttons + LLButton* mEmojiRecentPanelToggleBtn; + LLButton* mEmojiPickerToggleBtn; + LLLayoutPanel* mEmojiRecentPanel; + LLTextBox* mEmojiRecentEmptyText; + LLPanelEmojiComplete* mEmojiRecentIconsCtrl; LLButton* mSendChatButton; LLComboBox* mChatTypeCombo; @@ -145,6 +155,8 @@ private: std::vector mMessageArchive; BOOL FSUseNearbyChatConsole; + + boost::signals2::connection mRecentEmojisUpdatedCallbackConnection{}; }; #endif // FS_FLOATERNEARBYCHAT_H diff --git a/indra/newview/fsnearbychathub.cpp b/indra/newview/fsnearbychathub.cpp index e607f503d5..1dd862d50c 100644 --- a/indra/newview/fsnearbychathub.cpp +++ b/indra/newview/fsnearbychathub.cpp @@ -253,6 +253,8 @@ void FSNearbyChat::sendChat(LLWString text, EChatType type) if (!text.empty()) { + FSCommon::updateUsedEmojis(text); + if (type == CHAT_TYPE_OOC) { text = utf8string_to_wstring(gSavedSettings.getString("FSOOCPrefix") + " ") + text + utf8string_to_wstring(" " + gSavedSettings.getString("FSOOCPostfix")); 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 LLSINGLETON(LLChannelManager); virtual ~LLChannelManager(); - void cleanupSingleton(); + void cleanupSingleton() override; public: diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp index 430307e516..c3873f1b2e 100644 --- a/indra/newview/llchathistory.cpp +++ b/indra/newview/llchathistory.cpp @@ -1156,6 +1156,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(editor_params, this); mEditor->setIsFriendCallback(LLAvatarActions::isFriend); mEditor->setIsObjectBlockedCallback(boost::bind(&LLMuteList::isMuted, LLMuteList::getInstance(), _1, _2, 0)); @@ -1273,9 +1275,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); @@ -1285,26 +1285,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); } @@ -1352,11 +1342,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"; } @@ -1403,10 +1393,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()) { // [RLVa:KB] - Checked: 2010-04-22 (RLVa-1.2.0f) | Added: RLVa-1.2.0f // NOTE-RLVa: we don't need to do any @shownames or @showloc filtering here because we'll already have an existing URL @@ -1479,36 +1469,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; @@ -1581,11 +1562,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 d54af7d06a..710491f36a 100644 --- a/indra/newview/llchicletbar.h +++ b/indra/newview/llchicletbar.h @@ -63,11 +63,11 @@ public: LLIMChiclet* createIMChiclet(const LLUUID& session_id); // [FS communication UI] - 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 LLSingletonsetEnabled(caller->getText().size() > 0); diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp new file mode 100644 index 0000000000..6b49756df3 --- /dev/null +++ b/indra/newview/llfloateremojipicker.cpp @@ -0,0 +1,1354 @@ +/** + * @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" + +LLFloaterEmojiPicker::recent_emojis_updated_callback_t LLFloaterEmojiPicker::sRecentEmojisUpdatedCallback{}; // Live-update recently used emojis + +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 sRecentlyUsed; +static std::list> 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("Groups"); + mBadge = getChild("Badge"); + mEmojiScroll = getChild("EmojiGridContainer"); + mEmojiGrid = getChild("EmojiGrid"); + mDummy = getChild("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" + params.name = "emojigroup_all_cagetories"; // Fix mandatory name missing (XUI parser warning) + 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> cats; + fillCategoryRecentlyUsed(cats); + fillCategoryFrequentlyUsed(cats); + + if (!cats.empty()) + { + mFilteredEmojiGroups.push_back(USED_EMOJIS_GROUP_INDEX); + mFilteredEmojis.emplace_back(cats); + params.name = "emojigroup_recently_frequently"; // Fix mandatory name missing (XUI parser warning) + createGroupButton(params, rect, USED_EMOJIS_IMAGE_INDEX); + } + } + + const std::vector& groups = LLEmojiDictionary::instance().getGroups(); + + // List all categories in the dictionary + for (U32 i = 0; i < groups.size(); ++i) + { + std::map> cats; + + fillGroupEmojis(cats, i); + + if (!cats.empty()) + { + mFilteredEmojiGroups.push_back(i); + mFilteredEmojis.emplace_back(cats); + params.name = "emojigroup_" + std::to_string(i); // Fix mandatory name missing (XUI parser warning) + createGroupButton(params, rect, groups[i].Character); + } + } + + resizeGroupButtons(); +} + +void LLFloaterEmojiPicker::fillCategoryRecentlyUsed(std::map>& cats) +{ + if (sRecentlyUsed.empty()) + return; + + std::vector 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>& cats) +{ + if (sFrequentlyUsed.empty()) + return; + + std::vector 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>& cats, U32 index) +{ + const std::vector& 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 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(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 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& 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(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(ctrl)) + { + button->setUseFontColor(TRUE); + } +} + +void LLFloaterEmojiPicker::onGroupButtonMouseLeave(LLUICtrl* ctrl) +{ + if (LLButton* button = dynamic_cast(ctrl)) + { + button->setUseFontColor(button->getToggleState()); + } +} + +void LLFloaterEmojiPicker::onEmojiMouseEnter(LLUICtrl* ctrl) +{ + if (LLEmojiGridIcon* icon = dynamic_cast(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(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(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(mEmojiGrid->getPanelList()[mFocusedIconRow]); + mFocusedIcon = row ? dynamic_cast(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(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(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(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(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(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(); + // 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& 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)); + } + + // Live-update recently used emoji + sRecentEmojisUpdatedCallback(sRecentlyUsed); +} + +// 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 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 ftokens = LLStringUtil::getTokens(frequentlyUsed, ","); + int maxCountF = 20; + for (const std::string& token : ftokens) + { + std::vector 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& 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..c04c0cfb07 --- /dev/null +++ b/indra/newview/llfloateremojipicker.h @@ -0,0 +1,133 @@ +/** + * @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 pick_callback_t; + typedef boost::function 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& getRecentlyUsed(); + static void onEmojiUsed(llwchar emoji); + + static void loadState(); + static void saveState(); + + // Live-update recently used emojis + using recent_emojis_updated_callback_t = boost::signals2::signal& recent_emojis_list)>; + static boost::signals2::connection setRecentEmojisUpdatedCallback(const recent_emojis_updated_callback_t::slot_type& cb) + { + return sRecentEmojisUpdatedCallback.connect(cb); + } + // + +private: + void initialize(); + void fillGroups(); + void fillCategoryRecentlyUsed(std::map>& cats); + void fillCategoryFrequentlyUsed(std::map>& cats); + void fillGroupEmojis(std::map>& 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& 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 mFilteredEmojiGroups; + std::vector>> mFilteredEmojis; + std::vector 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 }; + + // Live-update recently used emojis + static recent_emojis_updated_callback_t sRecentEmojisUpdatedCallback; +}; + +#endif diff --git a/indra/newview/llfloaterimnearbychat.cpp b/indra/newview/llfloaterimnearbychat.cpp index 8fec82d2eb..8e5715f128 100644 --- a/indra/newview/llfloaterimnearbychat.cpp +++ b/indra/newview/llfloaterimnearbychat.cpp @@ -140,11 +140,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); @@ -600,6 +601,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 a7698134f6..f0954aa955 100644 --- a/indra/newview/llfloaterimsession.cpp +++ b/indra/newview/llfloaterimsession.cpp @@ -254,6 +254,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 8788170e27..14e7cac4bd 100644 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -35,18 +35,21 @@ #include "llagentcamera.h" #include "llavataractions.h" #include "llavatariconctrl.h" -#include "llgroupiconctrl.h" #include "llchatentry.h" #include "llchathistory.h" #include "llchiclet.h" #include "llchicletbar.h" #include "lldraghandle.h" +#include "llemojidictionary.h" #include "llfloaterreg.h" +#include "llfloateremojipicker.h" #include "llfloaterimsession.h" #include "llfloaterimcontainer.h" // to replace separate IM Floaters with multifloater container -#include "lllayoutstack.h" -#include "lltoolbarview.h" #include "llfloaterimnearbychat.h" +#include "llgroupiconctrl.h" +#include "lllayoutstack.h" +#include "llpanelemojicomplete.h" +#include "lltoolbarview.h" const F32 REFRESH_INTERVAL = 1.0f; const std::string ICN_GROUP("group_chat_icon"); @@ -58,7 +61,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), @@ -77,7 +80,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", @@ -90,12 +93,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() @@ -103,7 +106,7 @@ LLFloaterIMSessionTab::~LLFloaterIMSessionTab() delete mRefreshTimer; } -//static +// static LLFloaterIMSessionTab* LLFloaterIMSessionTab::findConversation(const LLUUID& uuid) { LLFloaterIMSessionTab* conv; @@ -120,7 +123,7 @@ LLFloaterIMSessionTab* LLFloaterIMSessionTab::findConversation(const LLUUID& uui return conv; }; -//static +// static LLFloaterIMSessionTab* LLFloaterIMSessionTab::getConversation(const LLUUID& uuid) { LLFloaterIMSessionTab* conv; @@ -136,14 +139,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("im_container")->setVisible(true); } @@ -157,27 +162,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)) @@ -222,42 +226,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("main_stack"); - mParticipantListAndHistoryStack = getChild("im_panels"); + mParticipantListAndHistoryStack = getChild("im_panels"); mCloseBtn = getChild("close_btn"); - mCloseBtn->setCommitCallback(boost::bind(&LLFloater::onClickClose, this)); + mCloseBtn->setCommitCallback([this](LLUICtrl*, const LLSD&) { onClickClose(this); }); mExpandCollapseBtn = getChild("expand_collapse_btn"); - mExpandCollapseBtn->setClickedCallback(boost::bind(&LLFloaterIMSessionTab::onSlide, this)); + mExpandCollapseBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onSlide(this); }); mExpandCollapseLineBtn = getChild("minz_btn"); - mExpandCollapseLineBtn->setClickedCallback(boost::bind(&LLFloaterIMSessionTab::onCollapseToLine, this)); + mExpandCollapseLineBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onCollapseToLine(this); }); mTearOffBtn = getChild("tear_off_btn"); mTearOffBtn->setCommitCallback(boost::bind(&LLFloaterIMSessionTab::onTearOffClicked, this)); + mEmojiRecentPanelToggleBtn = getChild("emoji_recent_panel_toggle_btn"); + mEmojiRecentPanelToggleBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiRecentPanelToggleBtnClicked(); }); + + mEmojiRecentPanel = getChild("emoji_recent_layout_panel"); + mEmojiRecentPanel->setVisible(false); + + mEmojiRecentEmptyText = getChild("emoji_recent_empty_text"); + mEmojiRecentEmptyText->setToolTip(mEmojiRecentEmptyText->getText()); + mEmojiRecentEmptyText->setVisible(false); + + mEmojiRecentIconsCtrl = getChild("emoji_recent_icons_ctrl"); + mEmojiRecentIconsCtrl->setCommitCallback([this](LLUICtrl*, const LLSD& value) { onRecentEmojiPicked(value); }); + mEmojiRecentIconsCtrl->setVisible(false); + + mEmojiPickerShowBtn = getChild("emoji_picker_show_btn"); + mEmojiPickerShowBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiPickerShowBtnClicked(); }); + mGearBtn = getChild("gear_btn"); - mAddBtn = getChild("add_btn"); + mAddBtn = getChild("add_btn"); mVoiceButton = getChild("voice_call_btn"); - + mParticipantListPanel = getChild("speakers_list_panel"); mRightPartPanel = getChild("right_part_holder"); @@ -309,17 +331,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()); - 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()); + 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(p); - mConversationsRoot->setCallbackRegistrar(&mCommitCallbackRegistrar); + mConversationsRoot->setCallbackRegistrar(&mCommitCallbackRegistrar); mConversationsRoot->setEnableRegistrar(&mEnableCallbackRegistrar); // Attach that root to the scroller mScroller->addChild(mConversationsRoot); @@ -359,6 +381,7 @@ LLParticipantList* LLFloaterIMSessionTab::getParticipantList() return dynamic_cast(LLFloaterIMContainer::getInstance()->getSessionModel(mSessionID)); } +// virtual void LLFloaterIMSessionTab::draw() { if (mRefreshTimer->hasExpired()) @@ -383,23 +406,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); @@ -409,13 +433,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() @@ -428,53 +453,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& 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(chat); - if(tmp_chat.mTimeStr.empty()) + if (tmp_chat.mTimeStr.empty()) tmp_chat.mTimeStr = appendTime(); - if (!chat.mMuted) - { - tmp_chat.mFromName = chat.mFromName; - LLSD chat_args; - if (args) chat_args = args; - chat_args["use_plain_text_chat_history"] = - gSavedSettings.getBOOL("PlainTextChatHistory"); - chat_args["show_time"] = gSavedSettings.getBOOL("IMShowTime"); - chat_args["show_names_for_p2p_conv"] = - !mIsP2PChat || gSavedSettings.getBOOL("IMShowNamesForP2PConv"); + tmp_chat.mFromName = chat.mFromName; - if (mChatHistory) - { - mChatHistory->appendMessage(chat, chat_args); - } - } + LLSD chat_args = args; + chat_args["use_plain_text_chat_history"] = + gSavedSettings.getBOOL("PlainTextChatHistory"); + chat_args["show_time"] = gSavedSettings.getBOOL("IMShowTime"); + chat_args["show_names_for_p2p_conv"] = !mIsP2PChat || + gSavedSettings.getBOOL("IMShowNamesForP2PConv"); + + mChatHistory->appendMessage(chat, chat_args); +} + +void LLFloaterIMSessionTab::updateUsedEmojis(LLWString text) +{ + LLEmojiDictionary* dictionary = LLEmojiDictionary::getInstance(); + llassert_always(dictionary); + + bool emojiSent = false; + for (llwchar& c : text) + { + if (dictionary->isEmoji(c)) + { + LLFloaterEmojiPicker::onEmojiUsed(c); + emojiSent = true; + } + } + + if (!emojiSent) + return; + + LLFloaterEmojiPicker::saveState(); + + if (mEmojiRecentPanel->getVisible()) + { + initEmojiRecentPanel(); + } } static LLTrace::BlockTimerStatHandle FTM_BUILD_CONVERSATION_VIEW_PARTICIPANT("Build Conversation View"); @@ -504,10 +606,10 @@ void LLFloaterIMSessionTab::buildConversationViewParticipant() while (current_participant_model != end_participant_model) { LLConversationItem* participant_model = dynamic_cast(*current_participant_model); - if (participant_model) - { - addConversationViewParticipant(participant_model); - } + if (participant_model) + { + addConversationViewParticipant(participant_model); + } current_participant_model++; } } @@ -527,10 +629,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 { @@ -580,11 +682,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) @@ -640,7 +742,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(); @@ -768,7 +870,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) @@ -785,12 +887,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? @@ -819,10 +921,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); } @@ -848,7 +950,7 @@ void LLFloaterIMSessionTab::processChatHistoryStyleUpdate(bool clean_messages/* LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); if (nearby_chat) { - nearby_chat->reloadMessages(clean_messages); + nearby_chat->reloadMessages(clean_messages); } } @@ -894,15 +996,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(); } } @@ -939,12 +1041,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); @@ -969,7 +1071,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); @@ -997,8 +1099,8 @@ void LLFloaterIMSessionTab::onTearOffClicked() { restoreFloater(); setFollows(isTornOff()? FOLLOWS_ALL : FOLLOWS_NONE); - mSaveRect = isTornOff(); - initRectControl(); + mSaveRect = isTornOff(); + initRectControl(); LLFloater::onClickTearOff(this); LLFloaterIMContainer* container = LLFloaterReg::findTypedInstance("im_container"); @@ -1088,8 +1190,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 @@ -1101,8 +1203,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 @@ -1113,8 +1215,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 @@ -1124,19 +1226,19 @@ bool LLFloaterIMSessionTab::checkContextMenuItem(const LLSD& userdata) void LLFloaterIMSessionTab::getSelectedUUIDs(uuid_vec_t& selected_uuids) { - const std::set selected_items = mConversationsRoot->getSelectionList(); + const std::set selected_items = mConversationsRoot->getSelectionList(); - std::set::const_iterator it = selected_items.begin(); - const std::set::const_iterator it_end = selected_items.end(); + std::set::const_iterator it = selected_items.begin(); + const std::set::const_iterator it_end = selected_items.end(); - for (; it != it_end; ++it) - { - LLConversationItem* conversation_item = static_cast((*it)->getViewModelItem()); - if (conversation_item) - { - selected_uuids.push_back(conversation_item->getUUID()); - } - } + for (; it != it_end; ++it) + { + LLConversationItem* conversation_item = static_cast((*it)->getViewModelItem()); + if (conversation_item) + { + selected_uuids.push_back(conversation_item->getUUID()); + } + } } LLConversationItem* LLFloaterIMSessionTab::getCurSelectedViewModelItem() @@ -1144,8 +1246,8 @@ LLConversationItem* LLFloaterIMSessionTab::getCurSelectedViewModelItem() LLConversationItem *conversationItem = NULL; if(mConversationsRoot && - mConversationsRoot->getCurSelectedItem() && - mConversationsRoot->getCurSelectedItem()->getViewModelItem()) + mConversationsRoot->getCurSelectedItem() && + mConversationsRoot->getCurSelectedItem()->getViewModelItem()) { conversationItem = static_cast(mConversationsRoot->getCurSelectedItem()->getViewModelItem()) ; } diff --git a/indra/newview/llfloaterimsessiontab.h b/indra/newview/llfloaterimsessiontab.h index 84bb0ac6cd..7464d2e7a0 100644 --- a/indra/newview/llfloaterimsessiontab.h +++ b/indra/newview/llfloaterimsessiontab.h @@ -43,10 +43,12 @@ class LLPanelChatControlPanel; class LLChatEntry; class LLChatHistory; +class LLPanelEmojiComplete; class LLFloaterIMSessionTab : public LLTransientDockableFloater { + using super = LLTransientDockableFloater; public: LOG_CLASS(LLFloaterIMSessionTab); @@ -70,9 +72,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;} @@ -82,6 +83,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); @@ -138,15 +140,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; @@ -154,8 +158,7 @@ protected: bool mMessagePaneExpanded; bool mIsParticipantListExpanded; - S32 mMinFloaterHeight; - + S32 mMinFloaterHeight; LLIMModel::LLIMSession* mSession; @@ -170,32 +173,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; @@ -209,9 +217,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/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index d3b6c7abf8..bddec16b09 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -2214,13 +2214,14 @@ void LLFloaterPreference::refreshEnabledState() enabled = FALSE; if (!LLFeatureManager::instance().isFeatureAvailable("RenderReflectionsEnabled")) { - getChildView("ReflectionsEnabled")->setEnabled(FALSE); + // getChildView("ReflectionsEnabled")->setEnabled(FALSE); } else { enabled = gSavedSettings.getBOOL("RenderReflectionsEnabled"); } getChildView("ReflectionDetail")->setEnabled(enabled); + getChildView("ReflectionLevel")->setEnabled(enabled); //getChildView("ReflectionDetailText")->setEnabled(enabled); getChildView("ScreenSpaceReflections")->setEnabled(enabled); diff --git a/indra/newview/llfloateruipreview.cpp b/indra/newview/llfloateruipreview.cpp index 6fe4bd4d3c..dd4176cba2 100644 --- a/indra/newview/llfloateruipreview.cpp +++ b/indra/newview/llfloateruipreview.cpp @@ -1647,7 +1647,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 { @@ -1665,7 +1665,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)); @@ -1731,7 +1731,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(); @@ -1746,7 +1746,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 7cc1c3a044..d867d77983 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 mFiltersList; diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h index 3c9ec41f16..1833e8c44e 100644 --- a/indra/newview/llimview.h +++ b/indra/newview/llimview.h @@ -596,7 +596,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 495629274d..a67bebdd01 100644 --- a/indra/newview/llinventorygallery.cpp +++ b/indra/newview/llinventorygallery.cpp @@ -2418,7 +2418,7 @@ void LLInventoryGallery::startDrag() ids.push_back(selected_id); } - const LLViewerInventoryCategory* cat = gInventory.getCategory(selected_id); + const LLViewerInventoryCategory* cat = gInventory.getCategory(selected_id); if (cat && gInventory.isObjectDescendentOf(selected_id, gInventory.getRootFolderID()) && !LLFolderType::lookupIsProtectedType((cat)->getPreferredType())) { diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp index f4d9275d48..9f696f2787 100644 --- a/indra/newview/llmaterialeditor.cpp +++ b/indra/newview/llmaterialeditor.cpp @@ -2231,7 +2231,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({PERM_MODIFY}), ItemSource::OBJECT, permissions, item_out); } bool LLMaterialEditor::canSaveObjectsMaterial() @@ -2239,7 +2239,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({PERM_COPY, PERM_MODIFY}), ItemSource::AGENT, permissions, item_out); } bool LLMaterialEditor::canClipboardObjectsMaterial() @@ -2265,7 +2265,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({PERM_COPY, PERM_MODIFY, PERM_TRANSFER}), ItemSource::OBJECT, permissions, item_out); } void LLMaterialEditor::saveObjectsMaterialAs() @@ -2273,7 +2273,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({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 5f14f963c9..00364861ec 100644 --- a/indra/newview/llmutelist.h +++ b/indra/newview/llmutelist.h @@ -73,7 +73,7 @@ class LLMuteList : public LLSingleton { 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 0c53762e72..8f39b0016c 100755 --- a/indra/newview/llnavigationbar.h +++ b/indra/newview/llnavigationbar.h @@ -99,11 +99,11 @@ class LLNavigationBar public: // Make navigation bar part of the UI - // /*virtual*/ void draw(); - // /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); - // /*virtual*/ BOOL postBuild(); + // /*virtual*/ void draw() override; + // /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask) override; + // /*virtual*/ BOOL postBuild() override; // -// /*virtual*/ void setVisible(BOOL visible); // Is done inside XUI now, using visibility_control +// /*virtual*/ void setVisible(BOOL visible) override; // Is done inside XUI now, using visibility_control 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 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(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() mScrollPos becomes (mTotalEmojis - mVisibleEmojis) + S32 newScrollPos = llmax(0, (S32)mScrollPos + clicks); + mScrollPos = llclamp((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(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(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("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 + { + Optional autosize; + Optional noscroll; + Optional vertical; + Optional max_visible, + padding; + + Optional 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 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 5405548ea8..b19e82aaa0 100644 --- a/indra/newview/llpaneltopinfobar.h +++ b/indra/newview/llpaneltopinfobar.h @@ -46,8 +46,8 @@ class LLPanelTopInfoBar : public LLPanel, public LLSingleton, public: typedef boost::signals2::signal 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 @@ -89,7 +89,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 68cda42ec2..f887514fac 100644 --- a/indra/newview/llpathfindingpathtool.h +++ b/indra/newview/llpathfindingpathtool.h @@ -66,17 +66,17 @@ public: typedef boost::signals2::signal 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 event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata) override; void updateAvatarsArrivalTime(uuid_vec_t& uuids); F32 getArrivalTimeByID(const LLUUID& id); diff --git a/indra/newview/llreflectionmap.cpp b/indra/newview/llreflectionmap.cpp index a26445b4bc..90c2967861 100644 --- a/indra/newview/llreflectionmap.cpp +++ b/indra/newview/llreflectionmap.cpp @@ -74,8 +74,11 @@ void LLReflectionMap::autoAdjustOrigin() { const LLVector4a* bounds = mGroup->getBounds(); auto* node = mGroup->getOctreeNode(); - - if (mGroup->getSpatialPartition()->mPartitionType == LLViewerRegion::PARTITION_VOLUME) + // Bugsplat-Fix + // if (mGroup->getSpatialPartition()->mPartitionType == LLViewerRegion::PARTITION_VOLUME) + const auto* partition = mGroup->getSpatialPartition(); + if (partition && partition->mPartitionType == LLViewerRegion::PARTITION_VOLUME) + // { mPriority = 0; // cast a ray towards 8 corners of bounding box diff --git a/indra/newview/llsceneview.cpp b/indra/newview/llsceneview.cpp index d9df846a5c..7b498043dc 100644 --- a/indra/newview/llsceneview.cpp +++ b/indra/newview/llsceneview.cpp @@ -104,7 +104,6 @@ void LLSceneView::draw() std::vector 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 2c63c059e6..6e2ff170ca 100644 --- a/indra/newview/llselectmgr.cpp +++ b/indra/newview/llselectmgr.cpp @@ -5700,9 +5700,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"; @@ -5834,7 +5831,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 @@ -5852,7 +5848,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()); @@ -5869,7 +5864,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; } } @@ -5883,7 +5877,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 5731898344..b18ed1029f 100644 --- a/indra/newview/llspeakers.h +++ b/indra/newview/llspeakers.h @@ -340,7 +340,7 @@ class LLActiveSpeakerMgr : public LLSpeakerMgr, public LLSingleton @@ -349,7 +349,7 @@ class LLLocalSpeakerMgr : public LLSpeakerMgr, public LLSingleton, 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 speaker_ids_t; diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index dc78898a86..e9c8479459 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -1532,7 +1532,7 @@ bool idle_startup() LLFile::mkdir(user_fonts_path_name.c_str()); // Initalize Account based asset_blacklist - FSAssetBlacklist::getInstance()->init(); + FSAssetBlacklist::instance(); // load per grid data FSData::instance().downloadAgents(); diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp index 6434944109..704ff74ae0 100644 --- a/indra/newview/lltextureview.cpp +++ b/indra/newview/lltextureview.cpp @@ -687,8 +687,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); // Move BW figures further to the right to prevent overlapping left = 575; 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 d27f7b7b57..ec50d32a7a 100644 --- a/indra/newview/lltoolcomp.h +++ b/indra/newview/lltoolcomp.h @@ -111,11 +111,11 @@ class LLToolCompInspect : public LLToolComposite, public LLSingleton 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 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; // NaCl - Rightclick-mousewheel zoom - virtual BOOL handleRightMouseUp(S32 x, S32 y, MASK mask); + virtual BOOL handleRightMouseUp(S32 x, S32 y, MASK mask) override; // NaCl End - 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 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 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 430ba9c8d0..654c9baec8 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 c9d432eedd..9afa87ddb5 100644 --- a/indra/newview/lltoolfocus.h +++ b/indra/newview/lltoolfocus.h @@ -38,18 +38,18 @@ 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 handleRightMouseDown(S32 x, S32 y, MASK mask);// Mouse movement by Singularity - virtual BOOL handleRightMouseUp(S32 x, S32 y, MASK mask);// Mouse movement by Singularity + 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 handleRightMouseDown(S32 x, S32 y, MASK mask) override;// Mouse movement by Singularity + virtual BOOL handleRightMouseUp(S32 x, S32 y, MASK mask) override;// Mouse movement by Singularity - 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 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 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 1f54e51589..6adaaceed3 100644 --- a/indra/newview/lltoolpie.h +++ b/indra/newview/lltoolpie.h @@ -42,26 +42,26 @@ class LLToolPie : public LLTool, public LLSingleton 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; } // [RLVa:KB] - Checked: 2010-03-06 (RLVa-1.2.0c) | Added: RLVa-1.2.0a 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 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 6a42649851..8ce872c0bf 100644 --- a/indra/newview/llversioninfo.h +++ b/indra/newview/llversioninfo.h @@ -47,7 +47,7 @@ class LLStoreListener; class LLVersionInfo: public LLSingleton { LLSINGLETON(LLVersionInfo); - void initSingleton(); + void initSingleton() override; public: ~LLVersionInfo(); diff --git a/indra/newview/llviewerchat.cpp b/indra/newview/llviewerchat.cpp index 585eecf38d..3e9b9ab54d 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/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index 6c04622b71..97dfdfb9ab 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -505,6 +505,21 @@ static bool handleRenderDynamicLODChanged(const LLSD& newvalue) return true; } +// static bool handleReflectionsEnabled(const LLSD& newvalue) +// { +// // FIRE-33659 - everything is too dark when reflections are disabled. +// if(newvalue.asBoolean()) +// { +// // TODO(Beq): This setting level should probably be governed by render quality settings. +// gSavedSettings.setS32("RenderReflectionProbeLevel", 3); +// } +// else +// { +// gSavedSettings.setS32("RenderReflectionProbeLevel", 0); +// } +// return true; +// } + static bool handleReflectionProbeDetailChanged(const LLSD& newvalue) { if (gPipeline.isInit()) @@ -1173,7 +1188,7 @@ void settings_setup_listeners() // [/SL:KB] setting_setup_signal_listener(gSavedSettings, "RenderReflectionProbeLevel", handleReflectionProbeDetailChanged); setting_setup_signal_listener(gSavedSettings, "RenderReflectionProbeDetail", handleReflectionProbeDetailChanged); - setting_setup_signal_listener(gSavedSettings, "RenderReflectionsEnabled", handleReflectionProbeDetailChanged); + // setting_setup_signal_listener(gSavedSettings, "RenderReflectionsEnabled", handleReflectionsEnabled); // FIRE-33659 better way to enable/disable reflections setting_setup_signal_listener(gSavedSettings, "RenderScreenSpaceReflections", handleReflectionProbeDetailChanged); setting_setup_signal_listener(gSavedSettings, "RenderShadowDetail", handleSetShaderChanged); setting_setup_signal_listener(gSavedSettings, "RenderDeferredSSAO", handleSetShaderChanged); diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 8b430a42f5..d0d4660971 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" @@ -166,6 +167,7 @@ // [FS communication UI] #include "llpanelblockedlist.h" #include "llpanelprofileclassifieds.h" +#include "llpanelemojicomplete.h" #include "llpreviewanim.h" #include "llpreviewgesture.h" #include "llpreviewnotecard.h" @@ -410,7 +412,7 @@ void LLViewerFloaterReg::registerFloaters() //LLFloaterReg::add("nearby_chat", "floater_im_session.xml", (LLFloaterBuildFunc)&LLFloaterIMNearbyChat::buildFloater); LLFloaterReg::add("fs_nearby_chat", "floater_fs_nearby_chat.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); // [FS communication UI] - LLFloaterReg::add("classified", "floater_classified.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("classified", "floater_classified.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("compile_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("conversation", "floater_conversation_log.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("add_landmark", "floater_create_landmark.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); @@ -418,18 +420,20 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("delete_pref_preset", "floater_delete_pref_preset.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("destinations", "floater_destinations.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("emoji_picker", "floater_emoji_picker.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("emoji_complete", "floater_emoji_complete.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("env_post_process", "floater_post_process.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); - LLFloaterReg::add("env_fixed_environmentent_water", "floater_fixedenvironment.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); - LLFloaterReg::add("env_fixed_environmentent_sky", "floater_fixedenvironment.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("env_fixed_environmentent_water", "floater_fixedenvironment.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("env_fixed_environmentent_sky", "floater_fixedenvironment.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); - LLFloaterReg::add("env_adjust_snapshot", "floater_adjust_environment.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("env_adjust_snapshot", "floater_adjust_environment.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); - LLFloaterReg::add("env_edit_extdaycycle", "floater_edit_ext_day_cycle.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); - LLFloaterReg::add("my_environments", "floater_my_environments.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("env_edit_extdaycycle", "floater_edit_ext_day_cycle.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("my_environments", "floater_my_environments.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); - LLFloaterReg::add("event", "floater_event.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); - LLFloaterReg::add("experiences", "floater_experiences.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("event", "floater_event.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("experiences", "floater_experiences.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("experience_profile", "floater_experienceprofile.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("experience_search", "floater_experience_search.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); diff --git a/indra/newview/llviewerhelp.h b/indra/newview/llviewerhelp.h index 72b3743681..284d9b94f0 100644 --- a/indra/newview/llviewerhelp.h +++ b/indra/newview/llviewerhelp.h @@ -43,24 +43,24 @@ class LLViewerHelp : public LLHelp, public LLSingleton 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; // return topic to use for grid manager help at the login screen - /*virtual*/ std::string gridMgrHelpTopic(); + std::string gridMgrHelpTopic(); }; #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 { 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 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 71e54ead75..8d0731dda9 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -1634,6 +1634,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 // /////////////////////// @@ -10933,19 +10957,13 @@ void handle_show_url(const LLSD& param) void handle_report_bug(const LLSD& param) { + // Keep linking to out JIRA + //std::string url = gSavedSettings.getString("ReportBugURL"); + //LLWeb::loadURLExternal(url); LLUIString url(param.asString()); - - LLStringUtil::format_map_t replace; - // FIRE-14001: JIRA report is being cut off when using Help -> Report Bug - //std::string environment = LLAppViewer::instance()->getViewerInfoString(true); - //boost::regex regex; - //regex.assign(""); - //std::string stripped_env = boost::regex_replace(environment, regex, ""); - - //replace["[ENVIRONMENT]"] = LLURI::escape(stripped_env); LLSD sysinfo = FSData::getSystemInfo(); + LLStringUtil::format_map_t replace; replace["[ENVIRONMENT]"] = LLURI::escape(sysinfo["Part1"].asString().substr(1) + sysinfo["Part2"].asString().substr(1)); - // LLSLURL location_url; LLAgentUI::buildSLURL(location_url); replace["[LOCATION]"] = LLURI::escape(location_url.getSLURLString()); @@ -10954,6 +10972,7 @@ void handle_report_bug(const LLSD& param) file_bug_url.setArgs(replace); LLWeb::loadURLExternal(file_bug_url.getString()); + // } void handle_buy_currency_test(void*) @@ -12342,6 +12361,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"); @@ -12455,6 +12476,10 @@ 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)); + // Add telemetry controls to the viewer Develop menu (Toggle profiling) view_listener_t::addMenu(new FSProfilerToggle(), "Develop.ToggleProfiling"); view_listener_t::addMenu(new FSProfilerCheckEnabled(), "Develop.EnableProfiling"); 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 { 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 ask_callback; diff --git a/indra/newview/llviewerparcelmedia.h b/indra/newview/llviewerparcelmedia.h index cf85e054f3..0189df18fa 100644 --- a/indra/newview/llviewerparcelmedia.h +++ b/indra/newview/llviewerparcelmedia.h @@ -83,7 +83,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 LLSingletonisState(LLVOCacheEntry::ACTIVE)) { - ((LLDrawable*)entry->getEntry()->getDrawable())->getVObj()->loadFlags(flags); + // Bugsplat-fix + // ((LLDrawable*)entry->getEntry()->getDrawable())->getVObj()->loadFlags(flags); + // split each get...() to include a !null check + const auto *octeeEntry = entry->getEntry(); + if(octeeEntry) + { + LLDrawable * drawable = (LLDrawable *)octeeEntry->getDrawable(); + if(drawable) + { + auto vobj = drawable->getVObj(); + if(vobj) + { + vobj->loadFlags(flags); + } + } + } + // return true; } diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 478a133dd3..1121b07476 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -912,10 +912,11 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag min_scale = llmax(min_scale*min_scale, 0.1f); vsize /= min_scale; - -#if LL_DARWIN - vsize /= 1.f + LLViewerTexture::sDesiredDiscardBias*(1.f+face->getDrawable()->mDistanceWRTCamera*bias_distance_scale); -#else +// Fix Blurry texture on Mac +// #if LL_DARWIN +// vsize /= 1.f + LLViewerTexture::sDesiredDiscardBias*(1.f+face->getDrawable()->mDistanceWRTCamera*bias_distance_scale); +// #else +// vsize /= LLViewerTexture::sDesiredDiscardBias; vsize /= llmax(1.f, (LLViewerTexture::sDesiredDiscardBias-1.f) * (1.f + face->getDrawable()->mDistanceWRTCamera * bias_distance_scale)); @@ -926,7 +927,7 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag { // further reduce by discard bias when off screen or occluded vsize /= LLViewerTexture::sDesiredDiscardBias; } -#endif +// #endif // // if a GLTF material is present, ignore that face // as far as this texture stats go, but update the GLTF material // stats diff --git a/indra/newview/llviewertexturelist.h b/indra/newview/llviewertexturelist.h index 0675c60069..5deaf5efa8 100644 --- a/indra/newview/llviewertexturelist.h +++ b/indra/newview/llviewertexturelist.h @@ -253,9 +253,9 @@ class LLUIImageList : public LLImageProviderInterface, public LLSingleton getUIImageByID(const LLUUID& id, S32 priority); - /*virtual*/ LLPointer getUIImage(const std::string& name, S32 priority); - void cleanUp(); + /*virtual*/ LLPointer getUIImageByID(const LLUUID& id, S32 priority) override; + /*virtual*/ LLPointer 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 299a68de18..7ef40dbe7f 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -1058,8 +1058,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); } } @@ -3456,6 +3455,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 &participants); - virtual bool isParticipant(const LLUUID& speaker_id); + virtual void getParticipantList(std::set &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 //@{ // Bypass LLCachedControls for voice status update - //virtual bool voiceEnabled(); - virtual bool voiceEnabled(bool no_cache = false); + //virtual bool voiceEnabled() override; + virtual bool voiceEnabled(bool no_cache = false) override; // - 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 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; // Add callback for user volume change boost::signals2::connection setUserVolumeUpdateCallback(const user_voice_volume_change_callback_t::slot_type& cb) @@ -216,7 +216,7 @@ public: // //@} - virtual std::string sipURIFromID(const LLUUID &id); + virtual std::string sipURIFromID(const LLUUID &id) override; //@} /// @name LLVoiceEffectInterface virtual implementations @@ -226,32 +226,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; //@} //@} @@ -760,7 +760,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 8f1fbc87ef..a932e4ab6d 100644 --- a/indra/newview/llvotree.cpp +++ b/indra/newview/llvotree.cpp @@ -876,6 +876,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 3a27f23519..9a9f459983 100644 --- a/indra/newview/llwearableitemslist.h +++ b/indra/newview/llwearableitemslist.h @@ -474,7 +474,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); @@ -487,7 +487,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); @@ -520,7 +520,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 2c50e25165..1822ced2ca 100644 --- a/indra/newview/llworldmapview.cpp +++ b/indra/newview/llworldmapview.cpp @@ -608,7 +608,7 @@ void LLWorldMapView::draw() S32_MAX, //max_chars mMapScale, //max_pixels NULL, - TRUE); //use ellipses + /*use_ellipses*/TRUE); if (drawAdvancedRegionInfo) { @@ -639,7 +639,8 @@ void LLWorldMapView::draw() S32_MAX, //max_chars mMapScale, //max_pixels NULL, - TRUE); //use ellipses + TRUE, //use ellipses + FALSE); } } // Show the grid coordinates (in units of regions) diff --git a/indra/newview/skins/ansastorm/xui/es/panel_main_inventory.xml b/indra/newview/skins/ansastorm/xui/es/panel_main_inventory.xml index c336d30872..06d3f2eb1c 100644 --- a/indra/newview/skins/ansastorm/xui/es/panel_main_inventory.xml +++ b/indra/newview/skins/ansastorm/xui/es/panel_main_inventory.xml @@ -61,7 +61,7 @@ - + diff --git a/indra/newview/skins/ansastorm/xui/pl/panel_main_inventory.xml b/indra/newview/skins/ansastorm/xui/pl/panel_main_inventory.xml index 1ccf340066..8a8d3d2a36 100644 --- a/indra/newview/skins/ansastorm/xui/pl/panel_main_inventory.xml +++ b/indra/newview/skins/ansastorm/xui/pl/panel_main_inventory.xml @@ -35,6 +35,7 @@ + @@ -83,7 +84,7 @@ - +