From f7a6ed85e40f53e5e28868bf34ac4dbc9bb204fb Mon Sep 17 00:00:00 2001
From: Nat Goodspeed <nat@lindenlab.com>
Date: Thu, 25 Aug 2011 14:40:53 -0400
Subject: [PATCH] CHOP-763: Add LLView::TemporaryDrilldownFunc to support UI
 injection. Instead of unconditionally calling LLView::pointInView(),
 LLView::visibleAndContains() now consults a class-static boost::function
 called sDrilldown -- which is initialized to LLView::pointInView(). Introduce
 LLView::TemporaryDrilldownFunc, instantiated with a callable whose signature
 is compatible with LLView::pointInView(). This replaces sDrilldown, but only
 for the life of the TemporaryDrilldownFunc object. Introduce
 llview::TargetEvent, an object intended to serve as a TemporaryDrilldownFunc
 callable. Construct it with a desired target LLView* and pass it to
 TemporaryDrilldownFunc. When called with each candidate child LLView*,
 instead of selecting the one containing the particular (x, y) point, it
 selects the one that will lead to the ultimate desired target LLView*. Add
 optional 'recur' param to LLView::childFromPoint(); default is current
 one-level behavior. But when you pass recur=true, it should return the
 frontmost visible leaf LLView containing the passed (x, y) point.

---
 indra/llui/CMakeLists.txt   |  2 ++
 indra/llui/llview.cpp       | 18 ++++++++++--
 indra/llui/llview.h         | 31 +++++++++++++++++++-
 indra/llui/llviewinject.cpp | 49 ++++++++++++++++++++++++++++++++
 indra/llui/llviewinject.h   | 56 +++++++++++++++++++++++++++++++++++++
 5 files changed, 152 insertions(+), 4 deletions(-)
 create mode 100644 indra/llui/llviewinject.cpp
 create mode 100644 indra/llui/llviewinject.h

diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index 0bbdcfd6ffe..9419f24809a 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -111,6 +111,7 @@ set(llui_SOURCE_FILES
     llurlmatch.cpp
     llurlregistry.cpp
     llviewborder.cpp
+    llviewinject.cpp
     llviewmodel.cpp
     llview.cpp
     llviewquery.cpp
@@ -214,6 +215,7 @@ set(llui_HEADER_FILES
     llurlmatch.h
     llurlregistry.h
     llviewborder.h
+    llviewinject.h
     llviewmodel.h
     llview.h
     llviewquery.h
diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp
index 6d0bd4d520d..56b09791a4d 100644
--- a/indra/llui/llview.cpp
+++ b/indra/llui/llview.cpp
@@ -33,6 +33,7 @@
 #include <cassert>
 #include <boost/tokenizer.hpp>
 #include <boost/foreach.hpp>
+#include <boost/bind.hpp>
 
 #include "llrender.h"
 #include "llevent.h"
@@ -67,6 +68,8 @@ S32		LLView::sLastLeftXML = S32_MIN;
 S32		LLView::sLastBottomXML = S32_MIN;
 std::vector<LLViewDrawContext*> LLViewDrawContext::sDrawContextStack;
 
+LLView::DrilldownFunc LLView::sDrilldown =
+	boost::bind(&LLView::pointInView, _1, _2, _3, HIT_TEST_USE_BOUNDING_RECT);
 
 //#if LL_DEBUG
 BOOL LLView::sIsDrawing = FALSE;
@@ -645,7 +648,7 @@ void LLView::onMouseLeave(S32 x, S32 y, MASK mask)
 
 bool LLView::visibleAndContains(S32 local_x, S32 local_y)
 {
-	return pointInView(local_x, local_y)
+	return sDrilldown(this, local_x, local_y)
 		&& getVisible();
 }
 
@@ -789,10 +792,11 @@ LLView* LLView::childrenHandleHover(S32 x, S32 y, MASK mask)
 	return NULL;
 }
 
-LLView*	LLView::childFromPoint(S32 x, S32 y)
+LLView*	LLView::childFromPoint(S32 x, S32 y, bool recur)
 {
-	if (!getVisible()  )
+	if (!getVisible())
 		return false;
+
 	BOOST_FOREACH(LLView* viewp, mChildList)
 	{
 		S32 local_x = x - viewp->getRect().mLeft;
@@ -801,6 +805,14 @@ LLView*	LLView::childFromPoint(S32 x, S32 y)
 		{
 			continue;
 		}
+		// Here we've found the first (frontmost) visible child at this level
+		// containing the specified point. Is the caller asking us to drill
+		// down and return the innermost leaf child at this point, or just the
+		// top-level child?
+		if (recur)
+		{
+			return viewp->childFromPoint(local_x, local_y, recur);
+		}
 		return viewp;
 
 	}
diff --git a/indra/llui/llview.h b/indra/llui/llview.h
index daea46d330a..67634938fbc 100644
--- a/indra/llui/llview.h
+++ b/indra/llui/llview.h
@@ -50,6 +50,7 @@
 #include "llfocusmgr.h"
 
 #include <list>
+#include <boost/function.hpp>
 
 class LLSD;
 
@@ -437,7 +438,7 @@ class LLView : public LLMouseHandler, public LLMortician, public LLFocusableElem
 	/*virtual*/ void	screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const;
 	/*virtual*/ void	localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const;
 
-	virtual		LLView*	childFromPoint(S32 x, S32 y);
+	virtual		LLView*	childFromPoint(S32 x, S32 y, bool recur=false);
 
 	// view-specific handlers 
 	virtual void	onMouseEnter(S32 x, S32 y, MASK mask);
@@ -599,7 +600,35 @@ class LLView : public LLMouseHandler, public LLMortician, public LLFocusableElem
 
 	LLView& getDefaultWidgetContainer() const;
 
+	// This allows special mouse-event targeting logic for testing.
+	typedef boost::function<bool(const LLView*, S32 x, S32 y)> DrilldownFunc;
+	static DrilldownFunc sDrilldown;
+
 public:
+	// This is the only public accessor to alter sDrilldown. This is not
+	// an accident. The intended usage pattern is like:
+	// {
+	//     LLView::TemporaryDrilldownFunc scoped_func(myfunctor);
+	//     // ... test with myfunctor ...
+	// } // exiting block restores original LLView::sDrilldown
+	class TemporaryDrilldownFunc
+	{
+	public:
+		TemporaryDrilldownFunc(const DrilldownFunc& func):
+			mOldDrilldown(sDrilldown)
+		{
+			sDrilldown = func;
+		}
+
+		~TemporaryDrilldownFunc()
+		{
+			sDrilldown = mOldDrilldown;
+		}
+
+	private:
+		DrilldownFunc mOldDrilldown;
+	};
+
 	// Depth in view hierarchy during rendering
 	static S32	sDepth;
 
diff --git a/indra/llui/llviewinject.cpp b/indra/llui/llviewinject.cpp
new file mode 100644
index 00000000000..46c5839f8ee
--- /dev/null
+++ b/indra/llui/llviewinject.cpp
@@ -0,0 +1,49 @@
+/**
+ * @file   llviewinject.cpp
+ * @author Nat Goodspeed
+ * @date   2011-08-16
+ * @brief  Implementation for llviewinject.
+ * 
+ * $LicenseInfo:firstyear=2011&license=viewerlgpl$
+ * Copyright (c) 2011, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llviewinject.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+
+llview::TargetEvent::TargetEvent(LLView* view)
+{
+    // Walk up the view tree from target LLView to the root (NULL). If
+    // passed NULL, iterate 0 times.
+    for (; view; view = view->getParent())
+    {
+        // At each level, operator() is going to ask: for a particular parent
+        // LLView*, which of its children should I select? So for this view's
+        // parent, select this view.
+        mChildMap[view->getParent()] = view;
+    }
+}
+
+bool llview::TargetEvent::operator()(const LLView* view, S32 /*x*/, S32 /*y*/) const
+{
+    // We are being called to decide whether to direct an incoming mouse event
+    // to this child view. (Normal LLView processing is to check whether the
+    // incoming (x, y) is within the view.) Look up the parent to decide
+    // whether, for that parent, this is the previously-selected child.
+    ChildMap::const_iterator found(mChildMap.find(view->getParent()));
+    // If we're looking at a child whose parent isn't even in the map, never
+    // mind.
+    if (found == mChildMap.end())
+    {
+        return false;
+    }
+    // So, is this the predestined child for this parent?
+    return (view == found->second);
+}
diff --git a/indra/llui/llviewinject.h b/indra/llui/llviewinject.h
new file mode 100644
index 00000000000..0de3d155c41
--- /dev/null
+++ b/indra/llui/llviewinject.h
@@ -0,0 +1,56 @@
+/**
+ * @file   llviewinject.h
+ * @author Nat Goodspeed
+ * @date   2011-08-16
+ * @brief  Supplemental LLView functionality used for simulating UI events.
+ * 
+ * $LicenseInfo:firstyear=2011&license=viewerlgpl$
+ * Copyright (c) 2011, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLVIEWINJECT_H)
+#define LL_LLVIEWINJECT_H
+
+#include "llview.h"
+#include <map>
+
+namespace llview
+{
+
+    /**
+     * TargetEvent is a callable with state, specifically intended for use as
+     * an LLView::TemporaryDrilldownFunc. Instantiate it with the desired
+     * target LLView*; pass it to a TemporaryDrilldownFunc instance;
+     * TargetEvent::operator() will then attempt to direct subsequent mouse
+     * events to the desired target LLView*. (This is an "attempt" because
+     * LLView will still balk unless the target LLView and every parent are
+     * visible and enabled.)
+     */
+    class TargetEvent
+    {
+    public:
+        /**
+         * Construct TargetEvent with the desired target LLView*. (See
+         * LLUI::resolvePath() to obtain an LLView* given a string pathname.)
+         * This sets up for operator().
+         */
+        TargetEvent(LLView* view);
+
+        /**
+         * This signature must match LLView::DrilldownFunc. When you install
+         * this TargetEvent instance using LLView::TemporaryDrilldownFunc,
+         * LLView will call this method to decide whether to propagate an
+         * incoming mouse event to the passed child LLView*.
+         */
+        bool operator()(const LLView*, S32 x, S32 y) const;
+
+    private:
+        // For a given parent LLView, identify which child to select.
+        typedef std::map<LLView*, LLView*> ChildMap;
+        ChildMap mChildMap;
+    };
+
+} // llview namespace
+
+#endif /* ! defined(LL_LLVIEWINJECT_H) */
-- 
GitLab