PageRenderTime 30ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 1ms

/indra/newview/llwindowlistener.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 505 lines | 367 code | 47 blank | 91 comment | 44 complexity | 4c320253888ea97b5eee27c975cb119a MD5 | raw file
Possible License(s): LGPL-2.1
  1. /**
  2. * @file llwindowlistener.cpp
  3. * @brief EventAPI interface for injecting input into LLWindow
  4. *
  5. * $LicenseInfo:firstyear=2001&license=viewerlgpl$
  6. * Second Life Viewer Source Code
  7. * Copyright (C) 2010, Linden Research, Inc.
  8. *
  9. * This library is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU Lesser General Public
  11. * License as published by the Free Software Foundation;
  12. * version 2.1 of the License only.
  13. *
  14. * This library is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  17. * Lesser General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Lesser General Public
  20. * License along with this library; if not, write to the Free Software
  21. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  22. *
  23. * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
  24. * $/LicenseInfo$
  25. */
  26. #include "llviewerprecompiledheaders.h"
  27. #include "linden_common.h"
  28. #include "llwindowlistener.h"
  29. #include "llcoord.h"
  30. #include "llfocusmgr.h"
  31. #include "llkeyboard.h"
  32. #include "llwindowcallbacks.h"
  33. #include "llui.h"
  34. #include "llview.h"
  35. #include "llviewinject.h"
  36. #include "llviewerwindow.h"
  37. #include "llviewerkeyboard.h"
  38. #include "llrootview.h"
  39. #include "llsdutil.h"
  40. #include "stringize.h"
  41. #include <typeinfo>
  42. #include <map>
  43. #include <boost/scoped_ptr.hpp>
  44. #include <boost/lambda/core.hpp>
  45. #include <boost/lambda/bind.hpp>
  46. namespace bll = boost::lambda;
  47. LLWindowListener::LLWindowListener(LLViewerWindow *window, const KeyboardGetter& kbgetter)
  48. : LLEventAPI("LLWindow", "Inject input events into the LLWindow instance"),
  49. mWindow(window),
  50. mKbGetter(kbgetter)
  51. {
  52. std::string keySomething =
  53. "Given [\"keysym\"], [\"keycode\"] or [\"char\"], inject the specified ";
  54. std::string keyExplain =
  55. "(integer keycode values, or keysym string from any addKeyName() call in\n"
  56. "http://hg.secondlife.com/viewer-development/src/tip/indra/llwindow/llkeyboard.cpp )\n";
  57. std::string mask =
  58. "Specify optional [\"mask\"] as an array containing any of \"CTL\", \"ALT\",\n"
  59. "\"SHIFT\" or \"MAC_CONTROL\"; the corresponding modifier bits will be combined\n"
  60. "to form the mask used with the event.";
  61. std::string given = "Given ";
  62. std::string mouseParams =
  63. "optional [\"path\"], optional [\"x\"] and [\"y\"], inject the requested mouse ";
  64. std::string buttonParams =
  65. std::string("[\"button\"], ") + mouseParams;
  66. std::string buttonExplain =
  67. "(button values \"LEFT\", \"MIDDLE\", \"RIGHT\")\n";
  68. std::string paramsExplain =
  69. "[\"path\"] is as for LLUI::resolvePath(), described in\n"
  70. "http://hg.secondlife.com/viewer-development/src/tip/indra/llui/llui.h\n"
  71. "If you omit [\"path\"], you must specify both [\"x\"] and [\"y\"].\n"
  72. "If you specify [\"path\"] without both [\"x\"] and [\"y\"], will synthesize (x, y)\n"
  73. "in the center of the LLView selected by [\"path\"].\n"
  74. "You may specify [\"path\"] with both [\"x\"] and [\"y\"], will use your (x, y).\n"
  75. "This may cause the LLView selected by [\"path\"] to reject the event.\n"
  76. "Optional [\"reply\"] requests a reply event on the named LLEventPump.\n"
  77. "reply[\"error\"] isUndefined (None) on success, else an explanatory message.\n";
  78. add("getInfo",
  79. "Get information about the ui element specified by [\"path\"]",
  80. &LLWindowListener::getInfo,
  81. LLSDMap("reply", LLSD()));
  82. add("getPaths",
  83. "Send on [\"reply\"] an event in which [\"paths\"] is an array of valid LLView\n"
  84. "pathnames. Optional [\"under\"] pathname specifies the base node under which\n"
  85. "to list; all nodes from root if no [\"under\"].",
  86. &LLWindowListener::getPaths,
  87. LLSDMap("reply", LLSD()));
  88. add("keyDown",
  89. keySomething + "keypress event.\n" + keyExplain + mask,
  90. &LLWindowListener::keyDown);
  91. add("keyUp",
  92. keySomething + "key release event.\n" + keyExplain + mask,
  93. &LLWindowListener::keyUp);
  94. add("mouseDown",
  95. given + buttonParams + "click event.\n" + buttonExplain + paramsExplain + mask,
  96. &LLWindowListener::mouseDown);
  97. add("mouseUp",
  98. given + buttonParams + "release event.\n" + buttonExplain + paramsExplain + mask,
  99. &LLWindowListener::mouseUp);
  100. add("mouseMove",
  101. given + mouseParams + "movement event.\n" + paramsExplain + mask,
  102. &LLWindowListener::mouseMove);
  103. add("mouseScroll",
  104. "Given an integer number of [\"clicks\"], inject the requested mouse scroll event.\n"
  105. "(positive clicks moves downward through typical content)",
  106. &LLWindowListener::mouseScroll);
  107. }
  108. template <typename MAPPED>
  109. class StringLookup
  110. {
  111. private:
  112. std::string mDesc;
  113. typedef std::map<std::string, MAPPED> Map;
  114. Map mMap;
  115. public:
  116. StringLookup(const std::string& desc): mDesc(desc) {}
  117. MAPPED lookup(const typename Map::key_type& key) const
  118. {
  119. typename Map::const_iterator found = mMap.find(key);
  120. if (found == mMap.end())
  121. {
  122. LL_WARNS("LLWindowListener") << "Unknown " << mDesc << " '" << key << "'" << LL_ENDL;
  123. return MAPPED();
  124. }
  125. return found->second;
  126. }
  127. protected:
  128. void add(const typename Map::key_type& key, const typename Map::mapped_type& value)
  129. {
  130. mMap.insert(typename Map::value_type(key, value));
  131. }
  132. };
  133. namespace {
  134. // helper for getMask()
  135. MASK lookupMask_(const std::string& maskname)
  136. {
  137. // It's unclear to me whether MASK_MAC_CONTROL is important, but it's not
  138. // supported by maskFromString(). Handle that specially.
  139. if (maskname == "MAC_CONTROL")
  140. {
  141. return MASK_MAC_CONTROL;
  142. }
  143. else
  144. {
  145. // In case of lookup failure, return MASK_NONE, which won't affect our
  146. // caller's OR.
  147. MASK mask(MASK_NONE);
  148. LLKeyboard::maskFromString(maskname, &mask);
  149. return mask;
  150. }
  151. }
  152. MASK getMask(const LLSD& event)
  153. {
  154. LLSD masknames(event["mask"]);
  155. if (! masknames.isArray())
  156. {
  157. // If event["mask"] is a single string, perform normal lookup on it.
  158. return lookupMask_(masknames);
  159. }
  160. // Here event["mask"] is an array of mask-name strings. OR together their
  161. // corresponding bits.
  162. MASK mask(MASK_NONE);
  163. for (LLSD::array_const_iterator ai(masknames.beginArray()), aend(masknames.endArray());
  164. ai != aend; ++ai)
  165. {
  166. mask |= lookupMask_(*ai);
  167. }
  168. return mask;
  169. }
  170. KEY getKEY(const LLSD& event)
  171. {
  172. if (event.has("keysym"))
  173. {
  174. // Initialize to KEY_NONE; that way we can ignore the bool return from
  175. // keyFromString() and, in the lookup-fail case, simply return KEY_NONE.
  176. KEY key(KEY_NONE);
  177. LLKeyboard::keyFromString(event["keysym"], &key);
  178. return key;
  179. }
  180. else if (event.has("keycode"))
  181. {
  182. return KEY(event["keycode"].asInteger());
  183. }
  184. else
  185. {
  186. return KEY(event["char"].asString()[0]);
  187. }
  188. }
  189. } // namespace
  190. void LLWindowListener::getInfo(LLSD const & evt)
  191. {
  192. Response response(LLSD(), evt);
  193. if (evt.has("path"))
  194. {
  195. std::string path(evt["path"]);
  196. LLView * target_view = LLUI::resolvePath(LLUI::getRootView(), path);
  197. if (target_view != 0)
  198. {
  199. response.setResponse(target_view->getInfo());
  200. }
  201. else
  202. {
  203. response.error(STRINGIZE(evt["op"].asString() << " request "
  204. "specified invalid \"path\": '" << path << "'"));
  205. }
  206. }
  207. else
  208. {
  209. response.error(
  210. STRINGIZE(evt["op"].asString() << "request did not provide a path" ));
  211. }
  212. }
  213. void LLWindowListener::getPaths(LLSD const & request)
  214. {
  215. Response response(LLSD(), request);
  216. LLView *root(LLUI::getRootView()), *base(NULL);
  217. // Capturing request["under"] as string means we conflate the case in
  218. // which there is no ["under"] key with the case in which its value is the
  219. // empty string. That seems to make sense to me.
  220. std::string under(request["under"]);
  221. // Deal with optional "under" parameter
  222. if (under.empty())
  223. {
  224. base = root;
  225. }
  226. else
  227. {
  228. base = LLUI::resolvePath(root, under);
  229. if (! base)
  230. {
  231. return response.error(STRINGIZE(request["op"].asString() << " request "
  232. "specified invalid \"under\" path: '" << under << "'"));
  233. }
  234. }
  235. // Traverse the entire subtree under 'base', collecting pathnames
  236. for (LLView::tree_iterator_t ti(base->beginTreeDFS()), tend(base->endTreeDFS());
  237. ti != tend; ++ti)
  238. {
  239. response["paths"].append((*ti)->getPathname());
  240. }
  241. }
  242. void LLWindowListener::keyDown(LLSD const & evt)
  243. {
  244. Response response(LLSD(), evt);
  245. if (evt.has("path"))
  246. {
  247. std::string path(evt["path"]);
  248. LLView * target_view = LLUI::resolvePath(LLUI::getRootView(), path);
  249. if (target_view == 0)
  250. {
  251. response.error(STRINGIZE(evt["op"].asString() << " request "
  252. "specified invalid \"path\": '" << path << "'"));
  253. }
  254. else if(target_view->isAvailable())
  255. {
  256. response.setResponse(target_view->getInfo());
  257. gFocusMgr.setKeyboardFocus(target_view);
  258. KEY key = getKEY(evt);
  259. MASK mask = getMask(evt);
  260. gViewerKeyboard.handleKey(key, mask, false);
  261. if(key < 0x80) mWindow->handleUnicodeChar(key, mask);
  262. }
  263. else
  264. {
  265. response.error(STRINGIZE(evt["op"].asString() << " request "
  266. "element specified by \"path\": '" << path << "'"
  267. << " is not visible"));
  268. }
  269. }
  270. else
  271. {
  272. mKbGetter()->handleTranslatedKeyDown(getKEY(evt), getMask(evt));
  273. }
  274. }
  275. void LLWindowListener::keyUp(LLSD const & evt)
  276. {
  277. Response response(LLSD(), evt);
  278. if (evt.has("path"))
  279. {
  280. std::string path(evt["path"]);
  281. LLView * target_view = LLUI::resolvePath(LLUI::getRootView(), path);
  282. if (target_view == 0 )
  283. {
  284. response.error(STRINGIZE(evt["op"].asString() << " request "
  285. "specified invalid \"path\": '" << path << "'"));
  286. }
  287. else if (target_view->isAvailable())
  288. {
  289. response.setResponse(target_view->getInfo());
  290. gFocusMgr.setKeyboardFocus(target_view);
  291. mKbGetter()->handleTranslatedKeyUp(getKEY(evt), getMask(evt));
  292. }
  293. else
  294. {
  295. response.error(STRINGIZE(evt["op"].asString() << " request "
  296. "element specified byt \"path\": '" << path << "'"
  297. << " is not visible"));
  298. }
  299. }
  300. else
  301. {
  302. mKbGetter()->handleTranslatedKeyUp(getKEY(evt), getMask(evt));
  303. }
  304. }
  305. // for WhichButton
  306. typedef BOOL (LLWindowCallbacks::*MouseMethod)(LLWindow *, LLCoordGL, MASK);
  307. struct Actions
  308. {
  309. Actions(const MouseMethod& d, const MouseMethod& u): down(d), up(u), valid(true) {}
  310. Actions(): valid(false) {}
  311. MouseMethod down, up;
  312. bool valid;
  313. };
  314. struct WhichButton: public StringLookup<Actions>
  315. {
  316. WhichButton(): StringLookup<Actions>("mouse button")
  317. {
  318. add("LEFT", Actions(&LLWindowCallbacks::handleMouseDown,
  319. &LLWindowCallbacks::handleMouseUp));
  320. add("RIGHT", Actions(&LLWindowCallbacks::handleRightMouseDown,
  321. &LLWindowCallbacks::handleRightMouseUp));
  322. add("MIDDLE", Actions(&LLWindowCallbacks::handleMiddleMouseDown,
  323. &LLWindowCallbacks::handleMiddleMouseUp));
  324. }
  325. };
  326. static WhichButton buttons;
  327. typedef boost::function<bool(LLCoordGL, MASK)> MouseFunc;
  328. static void mouseEvent(const MouseFunc& func, const LLSD& request)
  329. {
  330. // Ensure we send response
  331. LLEventAPI::Response response(LLSD(), request);
  332. // We haven't yet established whether the incoming request has "x" and "y",
  333. // but capture this anyway, with 0 for omitted values.
  334. LLCoordGL pos(request["x"].asInteger(), request["y"].asInteger());
  335. bool has_pos(request.has("x") && request.has("y"));
  336. boost::scoped_ptr<LLView::TemporaryDrilldownFunc> tempfunc;
  337. // Documentation for mouseDown(), mouseUp() and mouseMove() claims you
  338. // must either specify ["path"], or both of ["x"] and ["y"]. You MAY
  339. // specify all. Let's say that passing "path" as an empty string is
  340. // equivalent to not passing it at all.
  341. std::string path(request["path"]);
  342. if (path.empty())
  343. {
  344. // Without "path", you must specify both "x" and "y".
  345. if (! has_pos)
  346. {
  347. return response.error(STRINGIZE(request["op"].asString() << " request "
  348. "without \"path\" must specify both \"x\" and \"y\": "
  349. << request));
  350. }
  351. }
  352. else // ! path.empty()
  353. {
  354. LLView* root = LLUI::getRootView();
  355. LLView* target = LLUI::resolvePath(root, path);
  356. if (! target)
  357. {
  358. return response.error(STRINGIZE(request["op"].asString() << " request "
  359. "specified invalid \"path\": '" << path << "'"));
  360. }
  361. response.setResponse(target->getInfo());
  362. // The intent of this test is to prevent trying to drill down to a
  363. // widget in a hidden floater, or on a tab that's not current, etc.
  364. if (! target->isInVisibleChain())
  365. {
  366. return response.error(STRINGIZE(request["op"].asString() << " request "
  367. "specified \"path\" not currently visible: '"
  368. << path << "'"));
  369. }
  370. // This test isn't folded in with the above error case since you can
  371. // (e.g.) pop up a tooltip even for a disabled widget.
  372. if (! target->isInEnabledChain())
  373. {
  374. response.warn(STRINGIZE(request["op"].asString() << " request "
  375. "specified \"path\" not currently enabled: '"
  376. << path << "'"));
  377. }
  378. if (! has_pos)
  379. {
  380. LLRect rect(target->calcScreenRect());
  381. pos.set(rect.getCenterX(), rect.getCenterY());
  382. // nonstandard warning tactic: probably usual case; we want event
  383. // sender to know synthesized (x, y), but maybe don't need to log?
  384. response["warnings"].append(STRINGIZE("using center point ("
  385. << pos.mX << ", " << pos.mY << ")"));
  386. }
  387. /*==========================================================================*|
  388. // NEVER MIND: the LLView tree defines priority handler layers in
  389. // front of the normal widget set, so this has never yet produced
  390. // anything but spam warnings. (sigh)
  391. // recursive childFromPoint() should give us the frontmost, leafmost
  392. // widget at the specified (x, y).
  393. LLView* frontmost = root->childFromPoint(pos.mX, pos.mY, true);
  394. if (frontmost != target)
  395. {
  396. response.warn(STRINGIZE(request["op"].asString() << " request "
  397. "specified \"path\" = '" << path
  398. << "', but frontmost LLView at (" << pos.mX << ", " << pos.mY
  399. << ") is '" << LLView::getPathname(frontmost) << "'"));
  400. }
  401. |*==========================================================================*/
  402. // Instantiate a TemporaryDrilldownFunc to route incoming mouse events
  403. // to the target LLView*. But put it on the heap since "path" is
  404. // optional. Nonetheless, manage it with a boost::scoped_ptr so it
  405. // will be destroyed when we leave.
  406. tempfunc.reset(new LLView::TemporaryDrilldownFunc(llview::TargetEvent(target)));
  407. }
  408. // The question of whether the requested LLView actually handled the
  409. // specified event is important enough, and its handling unclear enough,
  410. // to warrant a separate response attribute. Instead of deciding here to
  411. // make it a warning, or an error, let caller decide.
  412. response["handled"] = func(pos, getMask(request));
  413. // On exiting this scope, response will send, tempfunc will restore the
  414. // normal pointInView(x, y) containment logic, etc.
  415. }
  416. void LLWindowListener::mouseDown(LLSD const & request)
  417. {
  418. Actions actions(buttons.lookup(request["button"]));
  419. if (actions.valid)
  420. {
  421. // Normally you can pass NULL to an LLWindow* without compiler
  422. // complaint, but going through boost::lambda::bind() evidently
  423. // bypasses that special case: it only knows you're trying to pass an
  424. // int to a pointer. Explicitly cast NULL to the desired pointer type.
  425. mouseEvent(bll::bind(actions.down, mWindow,
  426. static_cast<LLWindow*>(NULL), bll::_1, bll::_2),
  427. request);
  428. }
  429. }
  430. void LLWindowListener::mouseUp(LLSD const & request)
  431. {
  432. Actions actions(buttons.lookup(request["button"]));
  433. if (actions.valid)
  434. {
  435. mouseEvent(bll::bind(actions.up, mWindow,
  436. static_cast<LLWindow*>(NULL), bll::_1, bll::_2),
  437. request);
  438. }
  439. }
  440. void LLWindowListener::mouseMove(LLSD const & request)
  441. {
  442. // We want to call the same central mouseEvent() routine for
  443. // handleMouseMove() as for button clicks. But handleMouseMove() returns
  444. // void, whereas mouseEvent() accepts a function returning bool -- and
  445. // uses that bool return. Use (void-lambda-expression, true) to construct
  446. // a callable that returns bool anyway. Pass 'true' because we expect that
  447. // our caller will usually treat 'false' as a problem.
  448. mouseEvent((bll::bind(&LLWindowCallbacks::handleMouseMove, mWindow,
  449. static_cast<LLWindow*>(NULL), bll::_1, bll::_2),
  450. true),
  451. request);
  452. }
  453. void LLWindowListener::mouseScroll(LLSD const & request)
  454. {
  455. S32 clicks = request["clicks"].asInteger();
  456. mWindow->handleScrollWheel(NULL, clicks);
  457. }