summaryrefslogtreecommitdiff
path: root/indra/newview/llwindowlistener.cpp
diff options
context:
space:
mode:
authorAnsariel <ansariel.hiller@phoenixviewer.com>2024-05-22 19:04:52 +0200
committerAnsariel <ansariel.hiller@phoenixviewer.com>2024-05-22 19:04:52 +0200
commit1b67dd855c41f5a0cda7ec2a68d98071986ca703 (patch)
treeab243607f74f78200787bba5b9b88f07ef1b966f /indra/newview/llwindowlistener.cpp
parent6d6eabca44d08d5b97bfe3e941d2b9687c2246ea (diff)
parente1623bb276f83a43ce7a197e388720c05bdefe61 (diff)
Merge remote-tracking branch 'origin/main' into DRTVWR-600-maint-A
# Conflicts: # autobuild.xml # indra/cmake/CMakeLists.txt # indra/cmake/GoogleMock.cmake # indra/llaudio/llaudioengine_fmodstudio.cpp # indra/llaudio/llaudioengine_fmodstudio.h # indra/llaudio/lllistener_fmodstudio.cpp # indra/llaudio/lllistener_fmodstudio.h # indra/llaudio/llstreamingaudio_fmodstudio.cpp # indra/llaudio/llstreamingaudio_fmodstudio.h # indra/llcharacter/llmultigesture.cpp # indra/llcharacter/llmultigesture.h # indra/llimage/llimage.cpp # indra/llimage/llimagepng.cpp # indra/llimage/llimageworker.cpp # indra/llimage/tests/llimageworker_test.cpp # indra/llmessage/tests/llmockhttpclient.h # indra/llprimitive/llgltfmaterial.h # indra/llrender/llfontfreetype.cpp # indra/llui/llcombobox.cpp # indra/llui/llfolderview.cpp # indra/llui/llfolderviewmodel.h # indra/llui/lllineeditor.cpp # indra/llui/lllineeditor.h # indra/llui/lltextbase.cpp # indra/llui/lltextbase.h # indra/llui/lltexteditor.cpp # indra/llui/lltextvalidate.cpp # indra/llui/lltextvalidate.h # indra/llui/lluictrl.h # indra/llui/llview.cpp # indra/llwindow/llwindowmacosx.cpp # indra/newview/app_settings/settings.xml # indra/newview/llappearancemgr.cpp # indra/newview/llappearancemgr.h # indra/newview/llavatarpropertiesprocessor.cpp # indra/newview/llavatarpropertiesprocessor.h # indra/newview/llbreadcrumbview.cpp # indra/newview/llbreadcrumbview.h # indra/newview/llbreastmotion.cpp # indra/newview/llbreastmotion.h # indra/newview/llconversationmodel.h # indra/newview/lldensityctrl.cpp # indra/newview/lldensityctrl.h # indra/newview/llface.inl # indra/newview/llfloatereditsky.cpp # indra/newview/llfloatereditwater.cpp # indra/newview/llfloateremojipicker.h # indra/newview/llfloaterimsessiontab.cpp # indra/newview/llfloaterprofiletexture.cpp # indra/newview/llfloaterprofiletexture.h # indra/newview/llgesturemgr.cpp # indra/newview/llgesturemgr.h # indra/newview/llimpanel.cpp # indra/newview/llimpanel.h # indra/newview/llinventorybridge.cpp # indra/newview/llinventorybridge.h # indra/newview/llinventoryclipboard.cpp # indra/newview/llinventoryclipboard.h # indra/newview/llinventoryfunctions.cpp # indra/newview/llinventoryfunctions.h # indra/newview/llinventorygallery.cpp # indra/newview/lllistbrowser.cpp # indra/newview/lllistbrowser.h # indra/newview/llpanelobjectinventory.cpp # indra/newview/llpanelprofile.cpp # indra/newview/llpanelprofile.h # indra/newview/llpreviewgesture.cpp # indra/newview/llsavedsettingsglue.cpp # indra/newview/llsavedsettingsglue.h # indra/newview/lltooldraganddrop.cpp # indra/newview/llurllineeditorctrl.cpp # indra/newview/llvectorperfoptions.cpp # indra/newview/llvectorperfoptions.h # indra/newview/llviewerparceloverlay.cpp # indra/newview/llviewertexlayer.cpp # indra/newview/llviewertexturelist.cpp # indra/newview/macmain.h # indra/test/test.cpp
Diffstat (limited to 'indra/newview/llwindowlistener.cpp')
-rw-r--r--indra/newview/llwindowlistener.cpp1050
1 files changed, 525 insertions, 525 deletions
diff --git a/indra/newview/llwindowlistener.cpp b/indra/newview/llwindowlistener.cpp
index 26799d60be..19abf8837c 100644
--- a/indra/newview/llwindowlistener.cpp
+++ b/indra/newview/llwindowlistener.cpp
@@ -1,525 +1,525 @@
-/**
- * @file llwindowlistener.cpp
- * @brief EventAPI interface for injecting input into LLWindow
- *
- * $LicenseInfo:firstyear=2001&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 "linden_common.h"
-
-#include "llwindowlistener.h"
-
-#include "llcoord.h"
-#include "llfocusmgr.h"
-#include "llkeyboard.h"
-#include "llwindowcallbacks.h"
-#include "llui.h"
-#include "llview.h"
-#include "llviewinject.h"
-#include "llviewerwindow.h"
-#include "llviewerinput.h"
-#include "llrootview.h"
-#include "llsdutil.h"
-#include "stringize.h"
-#include <typeinfo>
-#include <map>
-#include <boost/scoped_ptr.hpp>
-#include <boost/bind.hpp>
-
-LLWindowListener::LLWindowListener(LLViewerWindow *window, const KeyboardGetter& kbgetter)
- : LLEventAPI("LLWindow", "Inject input events into the LLWindow instance"),
- mWindow(window),
- mKbGetter(kbgetter)
-{
- std::string keySomething =
- "Given [\"keysym\"], [\"keycode\"] or [\"char\"], inject the specified ";
- std::string keyExplain =
- "(integer keycode values, or keysym string from any addKeyName() call in\n"
- "http://bitbucket.org/lindenlab/viewer-release/src/tip/indra/llwindow/llkeyboard.cpp )\n";
- std::string mask =
- "Specify optional [\"mask\"] as an array containing any of \"CTL\", \"ALT\",\n"
- "\"SHIFT\" or \"MAC_CONTROL\"; the corresponding modifier bits will be combined\n"
- "to form the mask used with the event.";
-
- std::string given = "Given ";
- std::string mouseParams =
- "optional [\"path\"], optional [\"x\"] and [\"y\"], inject the requested mouse ";
- std::string buttonParams =
- std::string("[\"button\"], ") + mouseParams;
- std::string buttonExplain =
- "(button values \"LEFT\", \"MIDDLE\", \"RIGHT\")\n";
- std::string paramsExplain =
- "[\"path\"] is as for LLUI::getInstance()->resolvePath(), described in\n"
- "http://bitbucket.org/lindenlab/viewer-release/src/tip/indra/llui/llui.h\n"
- "If you omit [\"path\"], you must specify both [\"x\"] and [\"y\"].\n"
- "If you specify [\"path\"] without both [\"x\"] and [\"y\"], will synthesize (x, y)\n"
- "in the center of the LLView selected by [\"path\"].\n"
- "You may specify [\"path\"] with both [\"x\"] and [\"y\"], will use your (x, y).\n"
- "This may cause the LLView selected by [\"path\"] to reject the event.\n"
- "Optional [\"reply\"] requests a reply event on the named LLEventPump.\n"
- "reply[\"error\"] isUndefined (None) on success, else an explanatory message.\n";
-
- add("getInfo",
- "Get information about the ui element specified by [\"path\"]",
- &LLWindowListener::getInfo,
- LLSDMap("reply", LLSD()));
- add("getPaths",
- "Send on [\"reply\"] an event in which [\"paths\"] is an array of valid LLView\n"
- "pathnames. Optional [\"under\"] pathname specifies the base node under which\n"
- "to list; all nodes from root if no [\"under\"].",
- &LLWindowListener::getPaths,
- LLSDMap("reply", LLSD()));
- add("keyDown",
- keySomething + "keypress event.\n" + keyExplain + mask,
- &LLWindowListener::keyDown);
- add("keyUp",
- keySomething + "key release event.\n" + keyExplain + mask,
- &LLWindowListener::keyUp);
- add("mouseDown",
- given + buttonParams + "click event.\n" + buttonExplain + paramsExplain + mask,
- &LLWindowListener::mouseDown);
- add("mouseUp",
- given + buttonParams + "release event.\n" + buttonExplain + paramsExplain + mask,
- &LLWindowListener::mouseUp);
- add("mouseMove",
- given + mouseParams + "movement event.\n" + paramsExplain + mask,
- &LLWindowListener::mouseMove);
- add("mouseScroll",
- "Given an integer number of [\"clicks\"], inject the requested mouse scroll event.\n"
- "(positive clicks moves downward through typical content)",
- &LLWindowListener::mouseScroll);
-}
-
-template <typename MAPPED>
-class StringLookup
-{
-private:
- std::string mDesc;
- typedef std::map<std::string, MAPPED> Map;
- Map mMap;
-
-public:
- StringLookup(const std::string& desc): mDesc(desc) {}
-
- MAPPED lookup(const typename Map::key_type& key) const
- {
- typename Map::const_iterator found = mMap.find(key);
- if (found == mMap.end())
- {
- LL_WARNS("LLWindowListener") << "Unknown " << mDesc << " '" << key << "'" << LL_ENDL;
- return MAPPED();
- }
- return found->second;
- }
-
-protected:
- void add(const typename Map::key_type& key, const typename Map::mapped_type& value)
- {
- mMap.insert(typename Map::value_type(key, value));
- }
-};
-
-namespace {
-
-// helper for getMask()
-MASK lookupMask_(const std::string& maskname)
-{
- // It's unclear to me whether MASK_MAC_CONTROL is important, but it's not
- // supported by maskFromString(). Handle that specially.
- if (maskname == "MAC_CONTROL")
- {
- return MASK_MAC_CONTROL;
- }
- else
- {
- // In case of lookup failure, return MASK_NONE, which won't affect our
- // caller's OR.
- MASK mask(MASK_NONE);
- LLKeyboard::maskFromString(maskname, &mask);
- return mask;
- }
-}
-
-MASK getMask(const LLSD& event)
-{
- LLSD masknames(event["mask"]);
- if (! masknames.isArray())
- {
- // If event["mask"] is a single string, perform normal lookup on it.
- return lookupMask_(masknames);
- }
-
- // Here event["mask"] is an array of mask-name strings. OR together their
- // corresponding bits.
- MASK mask(MASK_NONE);
- for (LLSD::array_const_iterator ai(masknames.beginArray()), aend(masknames.endArray());
- ai != aend; ++ai)
- {
- mask |= lookupMask_(*ai);
- }
- return mask;
-}
-
-KEY getKEY(const LLSD& event)
-{
- if (event.has("keysym"))
- {
- // Initialize to KEY_NONE; that way we can ignore the bool return from
- // keyFromString() and, in the lookup-fail case, simply return KEY_NONE.
- KEY key(KEY_NONE);
- LLKeyboard::keyFromString(event["keysym"], &key);
- return key;
- }
- else if (event.has("keycode"))
- {
- return KEY(event["keycode"].asInteger());
- }
- else
- {
- return KEY(event["char"].asString()[0]);
- }
-}
-
-} // namespace
-
-void LLWindowListener::getInfo(LLSD const & evt)
-{
- Response response(LLSD(), evt);
-
- if (evt.has("path"))
- {
- std::string path(evt["path"]);
- LLView * target_view = LLUI::getInstance()->resolvePath(LLUI::getInstance()->getRootView(), path);
- if (target_view != 0)
- {
- response.setResponse(target_view->getInfo());
- }
- else
- {
- response.error(STRINGIZE(evt["op"].asString() << " request "
- "specified invalid \"path\": '" << path << "'"));
- }
- }
- else
- {
- response.error(
- STRINGIZE(evt["op"].asString() << "request did not provide a path" ));
- }
-}
-
-void LLWindowListener::getPaths(LLSD const & request)
-{
- Response response(LLSD(), request);
- LLView *root(LLUI::getInstance()->getRootView()), *base(NULL);
- // Capturing request["under"] as string means we conflate the case in
- // which there is no ["under"] key with the case in which its value is the
- // empty string. That seems to make sense to me.
- std::string under(request["under"]);
-
- // Deal with optional "under" parameter
- if (under.empty())
- {
- base = root;
- }
- else
- {
- base = LLUI::getInstance()->resolvePath(root, under);
- if (! base)
- {
- return response.error(STRINGIZE(request["op"].asString() << " request "
- "specified invalid \"under\" path: '" << under << "'"));
- }
- }
-
- // Traverse the entire subtree under 'base', collecting pathnames
- for (LLView::tree_iterator_t ti(base->beginTreeDFS()), tend(base->endTreeDFS());
- ti != tend; ++ti)
- {
- response["paths"].append((*ti)->getPathname());
- }
-}
-
-void LLWindowListener::keyDown(LLSD const & evt)
-{
- Response response(LLSD(), evt);
- KEY key = getKEY(evt);
- MASK mask = getMask(evt);
-
- if (evt.has("path"))
- {
- std::string path(evt["path"]);
- LLView * target_view = LLUI::getInstance()->resolvePath(LLUI::getInstance()->getRootView(), path);
- if (target_view == 0)
- {
- response.error(STRINGIZE(evt["op"].asString() << " request "
- "specified invalid \"path\": '" << path << "'"));
- }
- else if(target_view->isAvailable())
- {
- response.setResponse(target_view->getInfo());
-
- gFocusMgr.setKeyboardFocus(target_view);
- gViewerInput.handleKey(key, mask, false);
- if(key < 0x80) mWindow->handleUnicodeChar(key, mask);
- }
- else
- {
- response.error(STRINGIZE(evt["op"].asString() << " request "
- "element specified by \"path\": '" << path << "'"
- << " is not visible"));
- }
- }
- else
- {
- gViewerInput.handleKey(key, mask, false);
- if(key < 0x80) mWindow->handleUnicodeChar(key, mask);
- }
-}
-
-void LLWindowListener::keyUp(LLSD const & evt)
-{
- Response response(LLSD(), evt);
-
- if (evt.has("path"))
- {
- std::string path(evt["path"]);
- LLView * target_view = LLUI::getInstance()->resolvePath(LLUI::getInstance()->getRootView(), path);
- if (target_view == 0 )
- {
- response.error(STRINGIZE(evt["op"].asString() << " request "
- "specified invalid \"path\": '" << path << "'"));
- }
- else if (target_view->isAvailable())
- {
- response.setResponse(target_view->getInfo());
-
- gFocusMgr.setKeyboardFocus(target_view);
- mKbGetter()->handleTranslatedKeyUp(getKEY(evt), getMask(evt));
- }
- else
- {
- response.error(STRINGIZE(evt["op"].asString() << " request "
- "element specified byt \"path\": '" << path << "'"
- << " is not visible"));
- }
- }
- else
- {
- mKbGetter()->handleTranslatedKeyUp(getKEY(evt), getMask(evt));
- }
-}
-
-// for WhichButton
-typedef bool (LLWindowCallbacks::*MouseMethod)(LLWindow *, LLCoordGL, MASK);
-struct Actions
-{
- Actions(const MouseMethod& d, const MouseMethod& u): down(d), up(u), valid(true) {}
- Actions(): valid(false) {}
- MouseMethod down, up;
- bool valid;
-};
-
-struct WhichButton: public StringLookup<Actions>
-{
- WhichButton(): StringLookup<Actions>("mouse button")
- {
- add("LEFT", Actions(&LLWindowCallbacks::handleMouseDown,
- &LLWindowCallbacks::handleMouseUp));
- add("RIGHT", Actions(&LLWindowCallbacks::handleRightMouseDown,
- &LLWindowCallbacks::handleRightMouseUp));
- add("MIDDLE", Actions(&LLWindowCallbacks::handleMiddleMouseDown,
- &LLWindowCallbacks::handleMiddleMouseUp));
- }
-};
-static WhichButton buttons;
-
-typedef boost::function<bool(LLCoordGL, MASK)> MouseFunc;
-
-// Wrap a function returning 'void' to return 'true' instead. I'm sure there's
-// a more generic way to accomplish this, but generically handling the
-// arguments seems to require variadic templates and perfect forwarding. (We
-// used to be able to write (boost::lambda::bind(...), true), counting on
-// boost::lambda's comma operator overload, until
-// https://svn.boost.org/trac/boost/ticket/10864. And boost::phoenix doesn't
-// seem to overload comma the same way; or at least not with bind().)
-class MouseFuncTrue
-{
- typedef boost::function<void(LLCoordGL, MASK)> MouseFuncVoid;
- MouseFuncVoid mFunc;
-
-public:
- MouseFuncTrue(const MouseFuncVoid& func):
- mFunc(func)
- {}
-
- bool operator()(LLCoordGL coords, MASK mask) const
- {
- mFunc(coords, mask);
- return true;
- }
-};
-
-static void mouseEvent(const MouseFunc& func, const LLSD& request)
-{
- // Ensure we send response
- LLEventAPI::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"));
-
- std::unique_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::getInstance()->getRootView();
- LLView* target = LLUI::getInstance()->resolvePath(root, path);
- if (! target)
- {
- return response.error(STRINGIZE(request["op"].asString() << " request "
- "specified invalid \"path\": '" << path << "'"));
- }
-
- response.setResponse(target->getInfo());
-
- // 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 (! target->isInVisibleChain())
- {
- 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 (! target->isInEnabledChain())
- {
- response.warn(STRINGIZE(request["op"].asString() << " request "
- "specified \"path\" not currently enabled: '"
- << path << "'"));
- }
-
- if (! has_pos)
- {
- LLRect rect(target->calcScreenRect());
- 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 << ")"));
- }
-
-/*==========================================================================*|
- // NEVER MIND: the LLView tree defines priority handler layers in
- // front of the normal widget set, so this has never yet produced
- // anything but spam warnings. (sigh)
-
- // 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 '" << LLView::getPathname(frontmost) << "'"));
- }
-|*==========================================================================*/
-
- // 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 & request)
-{
- Actions actions(buttons.lookup(request["button"]));
- if (actions.valid)
- {
- // Normally you can pass NULL to an LLWindow* without compiler
- // complaint, but going through boost::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(boost::bind(actions.down, mWindow,
- static_cast<LLWindow*>(NULL), _1, _2),
- request);
- }
-}
-
-void LLWindowListener::mouseUp(LLSD const & request)
-{
- Actions actions(buttons.lookup(request["button"]));
- if (actions.valid)
- {
- mouseEvent(boost::bind(actions.up, mWindow,
- static_cast<LLWindow*>(NULL), _1, _2),
- request);
- }
-}
-
-void LLWindowListener::mouseMove(LLSD const & request)
-{
- // 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 MouseFuncTrue to construct a callable that
- // returns bool anyway.
- mouseEvent(MouseFuncTrue(boost::bind(&LLWindowCallbacks::handleMouseMove, mWindow,
- static_cast<LLWindow*>(NULL), _1, _2)),
- request);
-}
-
-void LLWindowListener::mouseScroll(LLSD const & request)
-{
- S32 clicks = request["clicks"].asInteger();
-
- mWindow->handleScrollWheel(NULL, clicks);
-}
+/**
+ * @file llwindowlistener.cpp
+ * @brief EventAPI interface for injecting input into LLWindow
+ *
+ * $LicenseInfo:firstyear=2001&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 "linden_common.h"
+
+#include "llwindowlistener.h"
+
+#include "llcoord.h"
+#include "llfocusmgr.h"
+#include "llkeyboard.h"
+#include "llwindowcallbacks.h"
+#include "llui.h"
+#include "llview.h"
+#include "llviewinject.h"
+#include "llviewerwindow.h"
+#include "llviewerinput.h"
+#include "llrootview.h"
+#include "llsdutil.h"
+#include "stringize.h"
+#include <typeinfo>
+#include <map>
+#include <boost/scoped_ptr.hpp>
+#include <boost/bind.hpp>
+
+LLWindowListener::LLWindowListener(LLViewerWindow *window, const KeyboardGetter& kbgetter)
+ : LLEventAPI("LLWindow", "Inject input events into the LLWindow instance"),
+ mWindow(window),
+ mKbGetter(kbgetter)
+{
+ std::string keySomething =
+ "Given [\"keysym\"], [\"keycode\"] or [\"char\"], inject the specified ";
+ std::string keyExplain =
+ "(integer keycode values, or keysym string from any addKeyName() call in\n"
+ "http://bitbucket.org/lindenlab/viewer-release/src/tip/indra/llwindow/llkeyboard.cpp )\n";
+ std::string mask =
+ "Specify optional [\"mask\"] as an array containing any of \"CTL\", \"ALT\",\n"
+ "\"SHIFT\" or \"MAC_CONTROL\"; the corresponding modifier bits will be combined\n"
+ "to form the mask used with the event.";
+
+ std::string given = "Given ";
+ std::string mouseParams =
+ "optional [\"path\"], optional [\"x\"] and [\"y\"], inject the requested mouse ";
+ std::string buttonParams =
+ std::string("[\"button\"], ") + mouseParams;
+ std::string buttonExplain =
+ "(button values \"LEFT\", \"MIDDLE\", \"RIGHT\")\n";
+ std::string paramsExplain =
+ "[\"path\"] is as for LLUI::getInstance()->resolvePath(), described in\n"
+ "http://bitbucket.org/lindenlab/viewer-release/src/tip/indra/llui/llui.h\n"
+ "If you omit [\"path\"], you must specify both [\"x\"] and [\"y\"].\n"
+ "If you specify [\"path\"] without both [\"x\"] and [\"y\"], will synthesize (x, y)\n"
+ "in the center of the LLView selected by [\"path\"].\n"
+ "You may specify [\"path\"] with both [\"x\"] and [\"y\"], will use your (x, y).\n"
+ "This may cause the LLView selected by [\"path\"] to reject the event.\n"
+ "Optional [\"reply\"] requests a reply event on the named LLEventPump.\n"
+ "reply[\"error\"] isUndefined (None) on success, else an explanatory message.\n";
+
+ add("getInfo",
+ "Get information about the ui element specified by [\"path\"]",
+ &LLWindowListener::getInfo,
+ LLSDMap("reply", LLSD()));
+ add("getPaths",
+ "Send on [\"reply\"] an event in which [\"paths\"] is an array of valid LLView\n"
+ "pathnames. Optional [\"under\"] pathname specifies the base node under which\n"
+ "to list; all nodes from root if no [\"under\"].",
+ &LLWindowListener::getPaths,
+ LLSDMap("reply", LLSD()));
+ add("keyDown",
+ keySomething + "keypress event.\n" + keyExplain + mask,
+ &LLWindowListener::keyDown);
+ add("keyUp",
+ keySomething + "key release event.\n" + keyExplain + mask,
+ &LLWindowListener::keyUp);
+ add("mouseDown",
+ given + buttonParams + "click event.\n" + buttonExplain + paramsExplain + mask,
+ &LLWindowListener::mouseDown);
+ add("mouseUp",
+ given + buttonParams + "release event.\n" + buttonExplain + paramsExplain + mask,
+ &LLWindowListener::mouseUp);
+ add("mouseMove",
+ given + mouseParams + "movement event.\n" + paramsExplain + mask,
+ &LLWindowListener::mouseMove);
+ add("mouseScroll",
+ "Given an integer number of [\"clicks\"], inject the requested mouse scroll event.\n"
+ "(positive clicks moves downward through typical content)",
+ &LLWindowListener::mouseScroll);
+}
+
+template <typename MAPPED>
+class StringLookup
+{
+private:
+ std::string mDesc;
+ typedef std::map<std::string, MAPPED> Map;
+ Map mMap;
+
+public:
+ StringLookup(const std::string& desc): mDesc(desc) {}
+
+ MAPPED lookup(const typename Map::key_type& key) const
+ {
+ typename Map::const_iterator found = mMap.find(key);
+ if (found == mMap.end())
+ {
+ LL_WARNS("LLWindowListener") << "Unknown " << mDesc << " '" << key << "'" << LL_ENDL;
+ return MAPPED();
+ }
+ return found->second;
+ }
+
+protected:
+ void add(const typename Map::key_type& key, const typename Map::mapped_type& value)
+ {
+ mMap.insert(typename Map::value_type(key, value));
+ }
+};
+
+namespace {
+
+// helper for getMask()
+MASK lookupMask_(const std::string& maskname)
+{
+ // It's unclear to me whether MASK_MAC_CONTROL is important, but it's not
+ // supported by maskFromString(). Handle that specially.
+ if (maskname == "MAC_CONTROL")
+ {
+ return MASK_MAC_CONTROL;
+ }
+ else
+ {
+ // In case of lookup failure, return MASK_NONE, which won't affect our
+ // caller's OR.
+ MASK mask(MASK_NONE);
+ LLKeyboard::maskFromString(maskname, &mask);
+ return mask;
+ }
+}
+
+MASK getMask(const LLSD& event)
+{
+ LLSD masknames(event["mask"]);
+ if (! masknames.isArray())
+ {
+ // If event["mask"] is a single string, perform normal lookup on it.
+ return lookupMask_(masknames);
+ }
+
+ // Here event["mask"] is an array of mask-name strings. OR together their
+ // corresponding bits.
+ MASK mask(MASK_NONE);
+ for (LLSD::array_const_iterator ai(masknames.beginArray()), aend(masknames.endArray());
+ ai != aend; ++ai)
+ {
+ mask |= lookupMask_(*ai);
+ }
+ return mask;
+}
+
+KEY getKEY(const LLSD& event)
+{
+ if (event.has("keysym"))
+ {
+ // Initialize to KEY_NONE; that way we can ignore the bool return from
+ // keyFromString() and, in the lookup-fail case, simply return KEY_NONE.
+ KEY key(KEY_NONE);
+ LLKeyboard::keyFromString(event["keysym"], &key);
+ return key;
+ }
+ else if (event.has("keycode"))
+ {
+ return KEY(event["keycode"].asInteger());
+ }
+ else
+ {
+ return KEY(event["char"].asString()[0]);
+ }
+}
+
+} // namespace
+
+void LLWindowListener::getInfo(LLSD const & evt)
+{
+ Response response(LLSD(), evt);
+
+ if (evt.has("path"))
+ {
+ std::string path(evt["path"]);
+ LLView * target_view = LLUI::getInstance()->resolvePath(LLUI::getInstance()->getRootView(), path);
+ if (target_view != 0)
+ {
+ response.setResponse(target_view->getInfo());
+ }
+ else
+ {
+ response.error(STRINGIZE(evt["op"].asString() << " request "
+ "specified invalid \"path\": '" << path << "'"));
+ }
+ }
+ else
+ {
+ response.error(
+ STRINGIZE(evt["op"].asString() << "request did not provide a path" ));
+ }
+}
+
+void LLWindowListener::getPaths(LLSD const & request)
+{
+ Response response(LLSD(), request);
+ LLView *root(LLUI::getInstance()->getRootView()), *base(NULL);
+ // Capturing request["under"] as string means we conflate the case in
+ // which there is no ["under"] key with the case in which its value is the
+ // empty string. That seems to make sense to me.
+ std::string under(request["under"]);
+
+ // Deal with optional "under" parameter
+ if (under.empty())
+ {
+ base = root;
+ }
+ else
+ {
+ base = LLUI::getInstance()->resolvePath(root, under);
+ if (! base)
+ {
+ return response.error(STRINGIZE(request["op"].asString() << " request "
+ "specified invalid \"under\" path: '" << under << "'"));
+ }
+ }
+
+ // Traverse the entire subtree under 'base', collecting pathnames
+ for (LLView::tree_iterator_t ti(base->beginTreeDFS()), tend(base->endTreeDFS());
+ ti != tend; ++ti)
+ {
+ response["paths"].append((*ti)->getPathname());
+ }
+}
+
+void LLWindowListener::keyDown(LLSD const & evt)
+{
+ Response response(LLSD(), evt);
+ KEY key = getKEY(evt);
+ MASK mask = getMask(evt);
+
+ if (evt.has("path"))
+ {
+ std::string path(evt["path"]);
+ LLView * target_view = LLUI::getInstance()->resolvePath(LLUI::getInstance()->getRootView(), path);
+ if (target_view == 0)
+ {
+ response.error(STRINGIZE(evt["op"].asString() << " request "
+ "specified invalid \"path\": '" << path << "'"));
+ }
+ else if(target_view->isAvailable())
+ {
+ response.setResponse(target_view->getInfo());
+
+ gFocusMgr.setKeyboardFocus(target_view);
+ gViewerInput.handleKey(key, mask, false);
+ if(key < 0x80) mWindow->handleUnicodeChar(key, mask);
+ }
+ else
+ {
+ response.error(STRINGIZE(evt["op"].asString() << " request "
+ "element specified by \"path\": '" << path << "'"
+ << " is not visible"));
+ }
+ }
+ else
+ {
+ gViewerInput.handleKey(key, mask, false);
+ if(key < 0x80) mWindow->handleUnicodeChar(key, mask);
+ }
+}
+
+void LLWindowListener::keyUp(LLSD const & evt)
+{
+ Response response(LLSD(), evt);
+
+ if (evt.has("path"))
+ {
+ std::string path(evt["path"]);
+ LLView * target_view = LLUI::getInstance()->resolvePath(LLUI::getInstance()->getRootView(), path);
+ if (target_view == 0 )
+ {
+ response.error(STRINGIZE(evt["op"].asString() << " request "
+ "specified invalid \"path\": '" << path << "'"));
+ }
+ else if (target_view->isAvailable())
+ {
+ response.setResponse(target_view->getInfo());
+
+ gFocusMgr.setKeyboardFocus(target_view);
+ mKbGetter()->handleTranslatedKeyUp(getKEY(evt), getMask(evt));
+ }
+ else
+ {
+ response.error(STRINGIZE(evt["op"].asString() << " request "
+ "element specified byt \"path\": '" << path << "'"
+ << " is not visible"));
+ }
+ }
+ else
+ {
+ mKbGetter()->handleTranslatedKeyUp(getKEY(evt), getMask(evt));
+ }
+}
+
+// for WhichButton
+typedef bool (LLWindowCallbacks::*MouseMethod)(LLWindow *, LLCoordGL, MASK);
+struct Actions
+{
+ Actions(const MouseMethod& d, const MouseMethod& u): down(d), up(u), valid(true) {}
+ Actions(): valid(false) {}
+ MouseMethod down, up;
+ bool valid;
+};
+
+struct WhichButton: public StringLookup<Actions>
+{
+ WhichButton(): StringLookup<Actions>("mouse button")
+ {
+ add("LEFT", Actions(&LLWindowCallbacks::handleMouseDown,
+ &LLWindowCallbacks::handleMouseUp));
+ add("RIGHT", Actions(&LLWindowCallbacks::handleRightMouseDown,
+ &LLWindowCallbacks::handleRightMouseUp));
+ add("MIDDLE", Actions(&LLWindowCallbacks::handleMiddleMouseDown,
+ &LLWindowCallbacks::handleMiddleMouseUp));
+ }
+};
+static WhichButton buttons;
+
+typedef boost::function<bool(LLCoordGL, MASK)> MouseFunc;
+
+// Wrap a function returning 'void' to return 'true' instead. I'm sure there's
+// a more generic way to accomplish this, but generically handling the
+// arguments seems to require variadic templates and perfect forwarding. (We
+// used to be able to write (boost::lambda::bind(...), true), counting on
+// boost::lambda's comma operator overload, until
+// https://svn.boost.org/trac/boost/ticket/10864. And boost::phoenix doesn't
+// seem to overload comma the same way; or at least not with bind().)
+class MouseFuncTrue
+{
+ typedef boost::function<void(LLCoordGL, MASK)> MouseFuncVoid;
+ MouseFuncVoid mFunc;
+
+public:
+ MouseFuncTrue(const MouseFuncVoid& func):
+ mFunc(func)
+ {}
+
+ bool operator()(LLCoordGL coords, MASK mask) const
+ {
+ mFunc(coords, mask);
+ return true;
+ }
+};
+
+static void mouseEvent(const MouseFunc& func, const LLSD& request)
+{
+ // Ensure we send response
+ LLEventAPI::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"));
+
+ std::unique_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::getInstance()->getRootView();
+ LLView* target = LLUI::getInstance()->resolvePath(root, path);
+ if (! target)
+ {
+ return response.error(STRINGIZE(request["op"].asString() << " request "
+ "specified invalid \"path\": '" << path << "'"));
+ }
+
+ response.setResponse(target->getInfo());
+
+ // 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 (! target->isInVisibleChain())
+ {
+ 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 (! target->isInEnabledChain())
+ {
+ response.warn(STRINGIZE(request["op"].asString() << " request "
+ "specified \"path\" not currently enabled: '"
+ << path << "'"));
+ }
+
+ if (! has_pos)
+ {
+ LLRect rect(target->calcScreenRect());
+ 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 << ")"));
+ }
+
+/*==========================================================================*|
+ // NEVER MIND: the LLView tree defines priority handler layers in
+ // front of the normal widget set, so this has never yet produced
+ // anything but spam warnings. (sigh)
+
+ // 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 '" << LLView::getPathname(frontmost) << "'"));
+ }
+|*==========================================================================*/
+
+ // 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 & request)
+{
+ Actions actions(buttons.lookup(request["button"]));
+ if (actions.valid)
+ {
+ // Normally you can pass NULL to an LLWindow* without compiler
+ // complaint, but going through boost::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(boost::bind(actions.down, mWindow,
+ static_cast<LLWindow*>(NULL), _1, _2),
+ request);
+ }
+}
+
+void LLWindowListener::mouseUp(LLSD const & request)
+{
+ Actions actions(buttons.lookup(request["button"]));
+ if (actions.valid)
+ {
+ mouseEvent(boost::bind(actions.up, mWindow,
+ static_cast<LLWindow*>(NULL), _1, _2),
+ request);
+ }
+}
+
+void LLWindowListener::mouseMove(LLSD const & request)
+{
+ // 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 MouseFuncTrue to construct a callable that
+ // returns bool anyway.
+ mouseEvent(MouseFuncTrue(boost::bind(&LLWindowCallbacks::handleMouseMove, mWindow,
+ static_cast<LLWindow*>(NULL), _1, _2)),
+ request);
+}
+
+void LLWindowListener::mouseScroll(LLSD const & request)
+{
+ S32 clicks = request["clicks"].asInteger();
+
+ mWindow->handleScrollWheel(NULL, clicks);
+}