From dad25bcb1e17e3dc384a9fb35d21b669bc3bbacd Mon Sep 17 00:00:00 2001
From: Kitty Barnett <develop@catznip.com>
Date: Sun, 23 Oct 2022 16:17:02 +0200
Subject: [PATCH] Add the emoji helper class which can be used by text-input
 controls to provide emoji picker support

---
 indra/llui/CMakeLists.txt    |   2 +
 indra/llui/llemojihelper.cpp | 142 +++++++++++++++++++++++++++++++++++
 indra/llui/llemojihelper.h   |  64 ++++++++++++++++
 3 files changed, 208 insertions(+)
 create mode 100644 indra/llui/llemojihelper.cpp
 create mode 100644 indra/llui/llemojihelper.h

diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index 4e007e429a2..68019734ab9 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -54,6 +54,7 @@ set(llui_SOURCE_FILES
     lldraghandle.cpp
     lleditmenuhandler.cpp
 	llemojidictionary.cpp
+	llemojihelper.cpp
     llf32uictrl.cpp
     llfiltereditor.cpp
     llflashtimer.cpp
@@ -165,6 +166,7 @@ set(llui_HEADER_FILES
     lldockcontrol.h
     lleditmenuhandler.h
 	llemojidictionary.h
+	llemojihelper.h
     llf32uictrl.h
     llfiltereditor.h 
     llflashtimer.h
diff --git a/indra/llui/llemojihelper.cpp b/indra/llui/llemojihelper.cpp
new file mode 100644
index 00000000000..d4c31ee9867
--- /dev/null
+++ b/indra/llui/llemojihelper.cpp
@@ -0,0 +1,142 @@
+/**
+* @file llemojihelper.h
+* @brief Header file for LLEmojiHelper
+*
+* $LicenseInfo:firstyear=2014&license=viewerlgpl$
+* Second Life Viewer Source Code
+* Copyright (C) 2014, Linden Research, Inc.
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation;
+* version 2.1 of the License only.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*
+* Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+* $/LicenseInfo$
+*/
+
+#include "linden_common.h"
+
+#include "llemojidictionary.h"
+#include "llemojihelper.h"
+#include "llfloater.h"
+#include "llfloaterreg.h"
+#include "lluictrl.h"
+
+// ============================================================================
+// Constants
+//
+
+constexpr char DEFAULT_EMOJI_HELPER_FLOATER[] = "emoji_complete";
+constexpr S32 HELPER_FLOATER_OFFSET_X = 20;
+constexpr S32 HELPER_FLOATER_OFFSET_Y = 0;
+
+// ============================================================================
+// LLEmojiHelper
+//
+
+std::string LLEmojiHelper::getToolTip(llwchar ch) const
+{
+	return LLEmojiDictionary::instance().getNameFromEmoji(ch);
+}
+
+bool LLEmojiHelper::isActive(const LLUICtrl* ctrl_p) const
+{
+	return mHostHandle.get() == ctrl_p;
+}
+
+// static
+bool LLEmojiHelper::isCursorInEmojiCode(const LLWString& wtext, S32 cursorPos, S32* pShortCodePos)
+{
+	S32 shortCodePos = cursorPos;
+
+	while (shortCodePos > 1 &&
+		   (LLStringOps::isAlnum(wtext[shortCodePos - 1]) || wtext[shortCodePos - 1] == L'-' || wtext[shortCodePos - 1] == L'_') )
+	{
+		shortCodePos--;
+	}
+
+	bool isShortCode = (L':' == wtext[shortCodePos - 1]) && (cursorPos - shortCodePos >= 2);
+	if (pShortCodePos)
+		*pShortCodePos = (isShortCode) ? shortCodePos - 1 : -1;
+	return isShortCode;
+}
+
+void LLEmojiHelper::showHelper(LLUICtrl* hostctrl_p, S32 local_x, S32 local_y, const std::string& short_code, std::function<void(LLWString)> cb)
+{
+	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())); }, std::placeholders::_2));
+	}
+	setHostCtrl(hostctrl_p);
+	mEmojiCommitCb = cb;
+
+	S32 floater_x, floater_y;
+	if (!hostctrl_p->localPointToOtherView(local_x, local_y, &floater_x, &floater_y, gFloaterView))
+	{
+		LL_ERRS() << "Cannot show emoji helper for non-floater controls." << LL_ENDL;
+		return;
+	}
+
+	LLFloater* pHelperFloater = mHelperHandle.get();
+	LLRect rct = pHelperFloater->getRect();
+	rct.setLeftTopAndSize(floater_x - HELPER_FLOATER_OFFSET_X, floater_y - HELPER_FLOATER_OFFSET_Y + rct.getHeight(), rct.getWidth(), rct.getHeight());
+	pHelperFloater->setRect(rct);
+	pHelperFloater->openFloater(LLSD().with("hint", short_code));
+}
+
+void LLEmojiHelper::hideHelper(const LLUICtrl* ctrl_p)
+{
+	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(const LLWString& wstr)
+{
+	if (!mHostHandle.isDead() && mEmojiCommitCb)
+	{
+		mEmojiCommitCb(wstr);
+	}
+}
+
+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 00000000000..7ed042fc5ff
--- /dev/null
+++ b/indra/llui/llemojihelper.h
@@ -0,0 +1,64 @@
+/**
+* @file llemojihelper.h
+* @brief Header file for LLEmojiHelper
+*
+* $LicenseInfo:firstyear=2014&license=viewerlgpl$
+* Second Life Viewer Source Code
+* Copyright (C) 2014, Linden Research, Inc.
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation;
+* version 2.1 of the License only.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*
+* Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+* $/LicenseInfo$
+*/
+
+#pragma once
+
+#include "llhandle.h"
+#include "llsingleton.h"
+
+#include <boost/signals2.hpp>
+
+class LLFloater;
+class LLUICtrl;
+
+class LLEmojiHelper : public LLSingleton<LLEmojiHelper>
+{
+	LLSINGLETON(LLEmojiHelper) {}
+	~LLEmojiHelper() override {}
+
+public:
+	// General
+	std::string getToolTip(llwchar ch) const;
+	bool        isActive(const LLUICtrl* ctrl_p) const;
+	static bool isCursorInEmojiCode(const LLWString& wtext, S32 cursor_pos, S32* short_code_pos_p = nullptr);
+	void        showHelper(LLUICtrl* hostctrl_p, S32 local_x, S32 local_y, const std::string& short_code, std::function<void(LLWString)> commit_cb);
+	void        hideHelper(const LLUICtrl* ctrl_p);
+
+	// Eventing
+	bool handleKey(const LLUICtrl* ctrl_p, KEY key, MASK mask);
+	void onCommitEmoji(const LLWString& wstr);
+
+protected:
+	LLUICtrl* getHostCtrl() const { return mHostHandle.get(); }
+	void      setHostCtrl(LLUICtrl* hostctrl_p);
+
+private:
+	LLHandle<LLUICtrl>  mHostHandle;
+	LLHandle<LLFloater> mHelperHandle;
+	boost::signals2::connection mHostCtrlFocusLostConn;
+	boost::signals2::connection mHelperCommitConn;
+	std::function<void(LLWString)> mEmojiCommitCb;
+};
-- 
GitLab