diff options
Diffstat (limited to 'indra/newview')
-rw-r--r-- | indra/newview/llwindowlistener.cpp | 199 |
1 files changed, 183 insertions, 16 deletions
diff --git a/indra/newview/llwindowlistener.cpp b/indra/newview/llwindowlistener.cpp index 4cadc8773d..84126b7738 100644 --- a/indra/newview/llwindowlistener.cpp +++ b/indra/newview/llwindowlistener.cpp @@ -34,10 +34,19 @@ #include "llwindowcallbacks.h" #include "llui.h" #include "llview.h" +#include "llviewinject.h" #include "llviewerwindow.h" #include "llviewerkeyboard.h" #include "llrootview.h" +#include "llsdutil.h" +#include "stringize.h" +#include <typeinfo> #include <map> +#include <boost/scoped_ptr.hpp> +#include <boost/lambda/core.hpp> +#include <boost/lambda/bind.hpp> + +namespace bll = boost::lambda; LLWindowListener::LLWindowListener(LLViewerWindow *window, const KeyboardGetter& kbgetter) : LLEventAPI("LLWindow", "Inject input events into the LLWindow instance"), @@ -233,12 +242,12 @@ void LLWindowListener::keyUp(LLSD const & evt) } // for WhichButton -typedef BOOL (LLWindowCallbacks::*MouseFunc)(LLWindow *, LLCoordGL, MASK); +typedef BOOL (LLWindowCallbacks::*MouseMethod)(LLWindow *, LLCoordGL, MASK); struct Actions { - Actions(const MouseFunc& d, const MouseFunc& u): down(d), up(u), valid(true) {} + Actions(const MouseMethod& d, const MouseMethod& u): down(d), up(u), valid(true) {} Actions(): valid(false) {} - MouseFunc down, up; + MouseMethod down, up; bool valid; }; @@ -256,38 +265,196 @@ struct WhichButton: public StringLookup<Actions> }; static WhichButton buttons; -static LLCoordGL getPos(const LLSD& event) +struct Response +{ + Response(const LLSD& seed, const LLSD& request, const LLSD::String& replyKey="reply"): + mResp(seed), + mReq(request), + mKey(replyKey) + {} + + ~Response() + { + // When you instantiate a stack Response object, if the original + // request requested a reply, send it when we leave this block, no + // matter how. + sendReply(mResp, mReq, mKey); + } + + void warn(const std::string& warning) + { + LL_WARNS("LLWindowListener") << warning << LL_ENDL; + mResp["warnings"].append(warning); + } + + void error(const std::string& error) + { + // Use LL_WARNS rather than LL_ERROR: we don't want the viewer to shut + // down altogether. + LL_WARNS("LLWindowListener") << error << LL_ENDL; + + mResp["error"] = error; + } + + // set other keys... + LLSD& operator[](const LLSD::String& key) { return mResp[key]; } + + LLSD mResp, mReq; + LLSD::String mKey; +}; + +typedef boost::function<bool(LLCoordGL, MASK)> MouseFunc; + +static void mouseEvent(const MouseFunc& func, const LLSD& request) { - return LLCoordGL(event["x"].asInteger(), event["y"].asInteger()); + // Ensure we send response + Response response(LLSD(), request); + // We haven't yet established whether the incoming request has "x" and "y", + // but capture this anyway, with 0 for omitted values. + LLCoordGL pos(request["x"].asInteger(), request["y"].asInteger()); + bool has_pos(request.has("x") && request.has("y")); + + boost::scoped_ptr<LLView::TemporaryDrilldownFunc> tempfunc; + + // Documentation for mouseDown(), mouseUp() and mouseMove() claims you + // must either specify ["path"], or both of ["x"] and ["y"]. You MAY + // specify all. Let's say that passing "path" as an empty string is + // equivalent to not passing it at all. + std::string path(request["path"]); + if (path.empty()) + { + // Without "path", you must specify both "x" and "y". + if (! has_pos) + { + return response.error(STRINGIZE(request["op"].asString() << " request " + "without \"path\" must specify both \"x\" and \"y\": " + << request)); + } + } + else // ! path.empty() + { + LLView* root = LLUI::getRootView(); + LLView* target = LLUI::resolvePath(root, path); + if (! target) + { + return response.error(STRINGIZE(request["op"].asString() << " request " + "specified invalid \"path\": '" << path << "'")); + return; + } + + // Get info about this LLView* for when we send response. + response["path"] = target->getPathname(); + response["class"] = typeid(*target).name(); + bool visible_chain(target->isInVisibleChain()); + bool enabled_chain(target->isInEnabledChain()); + response["visible"] = target->getVisible(); + response["visible_chain"] = visible_chain; + response["enabled"] = target->getEnabled(); + response["enabled_chain"] = enabled_chain; + response["available"] = target->isAvailable(); + // Don't show caller the LLView's own relative rectangle; that only + // tells its dimensions. Provide actual location on screen. + LLRect rect(target->calcScreenRect()); + response["rect"] = LLSDMap("left", rect.mLeft)("top", rect.mTop)("right", rect.mRight)("bottom", rect.mBottom); + + // The intent of this test is to prevent trying to drill down to a + // widget in a hidden floater, or on a tab that's not current, etc. + if (! visible_chain) + { + return response.error(STRINGIZE(request["op"].asString() << " request " + "specified \"path\" not currently visible: '" + << path << "'")); + } + + // This test isn't folded in with the above error case since you can + // (e.g.) pop up a tooltip even for a disabled widget. + if (! enabled_chain) + { + response.warn(STRINGIZE(request["op"].asString() << " request " + "specified \"path\" not currently enabled: '" + << path << "'")); + } + + if (! has_pos) + { + pos.set(rect.getCenterX(), rect.getCenterY()); + // nonstandard warning tactic: probably usual case; we want event + // sender to know synthesized (x, y), but maybe don't need to log? + response["warnings"].append(STRINGIZE("using center point (" + << pos.mX << ", " << pos.mY << ")")); + } + + // recursive childFromPoint() should give us the frontmost, leafmost + // widget at the specified (x, y). + LLView* frontmost = root->childFromPoint(pos.mX, pos.mY, true); + if (frontmost != target) + { + response.warn(STRINGIZE(request["op"].asString() << " request " + "specified \"path\" = '" << path + << "', but frontmost LLView at (" << pos.mX << ", " << pos.mY + << ") is '" << frontmost->getPathname() << "'")); + } + + // Instantiate a TemporaryDrilldownFunc to route incoming mouse events + // to the target LLView*. But put it on the heap since "path" is + // optional. Nonetheless, manage it with a boost::scoped_ptr so it + // will be destroyed when we leave. + tempfunc.reset(new LLView::TemporaryDrilldownFunc(llview::TargetEvent(target))); + } + + // The question of whether the requested LLView actually handled the + // specified event is important enough, and its handling unclear enough, + // to warrant a separate response attribute. Instead of deciding here to + // make it a warning, or an error, let caller decide. + response["handled"] = func(pos, getMask(request)); + + // On exiting this scope, response will send, tempfunc will restore the + // normal pointInView(x, y) containment logic, etc. } -void LLWindowListener::mouseDown(LLSD const & evt) +void LLWindowListener::mouseDown(LLSD const & request) { - Actions actions(buttons.lookup(evt["button"])); + Actions actions(buttons.lookup(request["button"])); if (actions.valid) { - (mWindow->*(actions.down))(NULL, getPos(evt), getMask(evt)); + // Normally you can pass NULL to an LLWindow* without compiler + // complaint, but going through boost::lambda::bind() evidently + // bypasses that special case: it only knows you're trying to pass an + // int to a pointer. Explicitly cast NULL to the desired pointer type. + mouseEvent(bll::bind(actions.down, mWindow, + static_cast<LLWindow*>(NULL), bll::_1, bll::_2), + request); } } -void LLWindowListener::mouseUp(LLSD const & evt) +void LLWindowListener::mouseUp(LLSD const & request) { - Actions actions(buttons.lookup(evt["button"])); + Actions actions(buttons.lookup(request["button"])); if (actions.valid) { - (mWindow->*(actions.up))(NULL, getPos(evt), getMask(evt)); + mouseEvent(bll::bind(actions.up, mWindow, + static_cast<LLWindow*>(NULL), bll::_1, bll::_2), + request); } } -void LLWindowListener::mouseMove(LLSD const & evt) +void LLWindowListener::mouseMove(LLSD const & request) { - mWindow->handleMouseMove(NULL, getPos(evt), getMask(evt)); + // We want to call the same central mouseEvent() routine for + // handleMouseMove() as for button clicks. But handleMouseMove() returns + // void, whereas mouseEvent() accepts a function returning bool -- and + // uses that bool return. Use (void-lambda-expression, true) to construct + // a callable that returns bool anyway. Pass 'true' because we expect that + // our caller will usually treat 'false' as a problem. + mouseEvent((bll::bind(&LLWindowCallbacks::handleMouseMove, mWindow, + static_cast<LLWindow*>(NULL), bll::_1, bll::_2), + true), + request); } -void LLWindowListener::mouseScroll(LLSD const & evt) +void LLWindowListener::mouseScroll(LLSD const & request) { - S32 clicks = evt["clicks"].asInteger(); + S32 clicks = request["clicks"].asInteger(); mWindow->handleScrollWheel(NULL, clicks); } - |