PageRenderTime 72ms CodeModel.GetById 6ms app.highlight 60ms RepoModel.GetById 2ms app.codeStats 0ms

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