diff options
Diffstat (limited to 'indra/newview')
-rw-r--r-- | indra/newview/CMakeLists.txt | 2 | ||||
-rw-r--r-- | indra/newview/llviewercontrollistener.cpp | 231 | ||||
-rw-r--r-- | indra/newview/llviewercontrollistener.h | 10 | ||||
-rw-r--r-- | indra/newview/llviewermessage.cpp | 4 | ||||
-rw-r--r-- | indra/newview/llviewerwindow.cpp | 9 | ||||
-rw-r--r-- | indra/newview/llviewerwindow.h | 4 | ||||
-rw-r--r-- | indra/newview/llwindowlistener.cpp | 505 | ||||
-rw-r--r-- | indra/newview/llwindowlistener.h | 57 |
8 files changed, 746 insertions, 76 deletions
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index fe80a47ca4..cb0f630aa0 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -603,6 +603,7 @@ set(viewer_SOURCE_FILES llweb.cpp llwebsharing.cpp llwind.cpp + llwindowlistener.cpp llwlanimator.cpp llwldaycycle.cpp llwlhandlers.cpp @@ -1161,6 +1162,7 @@ set(viewer_HEADER_FILES llweb.h llwebsharing.h llwind.h + llwindowlistener.h llwlanimator.h llwldaycycle.h llwlhandlers.h diff --git a/indra/newview/llviewercontrollistener.cpp b/indra/newview/llviewercontrollistener.cpp index 8bc25fa281..361b96221c 100644 --- a/indra/newview/llviewercontrollistener.cpp +++ b/indra/newview/llviewercontrollistener.cpp @@ -31,99 +31,196 @@ #include "llviewercontrollistener.h" #include "llviewercontrol.h" +#include "llcontrol.h" +#include "llerror.h" +#include "llsdutil.h" +#include "stringize.h" +#include <sstream> -LLViewerControlListener gSavedSettingsListener; +namespace { + +LLViewerControlListener sSavedSettingsListener; + +} // unnamed namespace LLViewerControlListener::LLViewerControlListener() : LLEventAPI("LLViewerControl", - "LLViewerControl listener: set, toggle or set default for various controls", - "group") + "LLViewerControl listener: set, toggle or set default for various controls") { - add("Global", - "Set gSavedSettings control [\"key\"] to value [\"value\"]", - boost::bind(&LLViewerControlListener::set, &gSavedSettings, _1)); - add("PerAccount", - "Set gSavedPerAccountSettings control [\"key\"] to value [\"value\"]", - boost::bind(&LLViewerControlListener::set, &gSavedPerAccountSettings, _1)); - add("Warning", - "Set gWarningSettings control [\"key\"] to value [\"value\"]", - boost::bind(&LLViewerControlListener::set, &gWarningSettings, _1)); - add("Crash", - "Set gCrashSettings control [\"key\"] to value [\"value\"]", - boost::bind(&LLViewerControlListener::set, &gCrashSettings, _1)); - -#if 0 - add(/*"toggleControl",*/ "Global", boost::bind(&LLViewerControlListener::toggleControl, &gSavedSettings, _1)); - add(/*"toggleControl",*/ "PerAccount", boost::bind(&LLViewerControlListener::toggleControl, &gSavedPerAccountSettings, _1)); - add(/*"toggleControl",*/ "Warning", boost::bind(&LLViewerControlListener::toggleControl, &gWarningSettings, _1)); - add(/*"toggleControl",*/ "Crash", boost::bind(&LLViewerControlListener::toggleControl, &gCrashSettings, _1)); - - add(/*"setDefault",*/ "Global", boost::bind(&LLViewerControlListener::setDefault, &gSavedSettings, _1)); - add(/*"setDefault",*/ "PerAccount", boost::bind(&LLViewerControlListener::setDefault, &gSavedPerAccountSettings, _1)); - add(/*"setDefault",*/ "Warning", boost::bind(&LLViewerControlListener::setDefault, &gWarningSettings, _1)); - add(/*"setDefault",*/ "Crash", boost::bind(&LLViewerControlListener::setDefault, &gCrashSettings, _1)); -#endif // 0 + std::ostringstream groupnames; + groupnames << "[\"group\"] is one of "; + const char* delim = ""; + for (LLControlGroup::key_iter cgki(LLControlGroup::beginKeys()), + cgkend(LLControlGroup::endKeys()); + cgki != cgkend; ++cgki) + { + groupnames << delim << '"' << *cgki << '"'; + delim = ", "; + } + groupnames << '\n'; + std::string grouphelp(groupnames.str()); + std::string replyhelp("If [\"reply\"] requested, send new [\"value\"] on specified LLEventPump\n"); + + add("set", + std::string("Set [\"group\"] control [\"key\"] to optional value [\"value\"]\n" + "If [\"value\"] omitted, set to control's defined default value\n") + + grouphelp + replyhelp, + &LLViewerControlListener::set, + LLSDMap("group", LLSD())("key", LLSD())); + add("toggle", + std::string("Toggle [\"group\"] control [\"key\"], if boolean\n") + grouphelp + replyhelp, + &LLViewerControlListener::toggle, + LLSDMap("group", LLSD())("key", LLSD())); + add("get", + std::string("Query [\"group\"] control [\"key\"], replying on LLEventPump [\"reply\"]\n") + + grouphelp, + &LLViewerControlListener::get, + LLSDMap("group", LLSD())("key", LLSD())("reply", LLSD())); + add("groups", + "Send on LLEventPump [\"reply\"] an array [\"groups\"] of valid group names", + &LLViewerControlListener::groups, + LLSDMap("reply", LLSD())); + add("vars", + std::string("For [\"group\"], send on LLEventPump [\"reply\"] an array [\"vars\"],\n" + "each of whose entries looks like:\n" + " [\"name\"], [\"type\"], [\"value\"], [\"comment\"]\n") + grouphelp, + &LLViewerControlListener::vars, + LLSDMap("group", LLSD())("reply", LLSD())); } -//static -void LLViewerControlListener::set(LLControlGroup * controls, LLSD const & event_data) +struct Info { - if(event_data.has("key")) + Info(const LLSD& request): + response(LLSD(), request), + groupname(request["group"]), + group(LLControlGroup::getInstance(groupname)), + key(request["key"]), + control(NULL) { - std::string key(event_data["key"]); + if (! group) + { + response.error(STRINGIZE("Unrecognized group '" << groupname << "'")); + return; + } - if(controls->controlExists(key)) + control = group->getControl(key); + if (! control) { - controls->setUntypedValue(key, event_data["value"]); + response.error(STRINGIZE("In group '" << groupname + << "', unrecognized control key '" << key << "'")); } - else + } + + ~Info() + { + // If in fact the request passed to our constructor names a valid + // group and key, grab the final value of the indicated control and + // stuff it in our response. Since this outer destructor runs before + // the contained Response destructor, this data will go into the + // response we send. + if (control) { - llwarns << "requested unknown control: \"" << key << '\"' << llendl; + response["name"] = control->getName(); + response["type"] = group->typeEnumToString(control->type()); + response["value"] = control->get(); + response["comment"] = control->getComment(); } } + + LLEventAPI::Response response; + std::string groupname; + LLControlGroup* group; + std::string key; + LLControlVariable* control; +}; + +//static +void LLViewerControlListener::set(LLSD const & request) +{ + Info info(request); + if (! info.control) + return; + + if (request.has("value")) + { + info.control->setValue(request["value"]); + } + else + { + info.control->resetToDefault(); + } } //static -void LLViewerControlListener::toggleControl(LLControlGroup * controls, LLSD const & event_data) +void LLViewerControlListener::toggle(LLSD const & request) { - if(event_data.has("key")) + Info info(request); + if (! info.control) + return; + + if (info.control->isType(TYPE_BOOLEAN)) + { + info.control->set(! info.control->get().asBoolean()); + } + else { - std::string key(event_data["key"]); + info.response.error(STRINGIZE("toggle of non-boolean '" << info.groupname + << "' control '" << info.key + << "', type is " + << info.group->typeEnumToString(info.control->type()))); + } +} - if(controls->controlExists(key)) - { - LLControlVariable * control = controls->getControl(key); - if(control->isType(TYPE_BOOLEAN)) - { - control->set(!control->get().asBoolean()); - } - else - { - llwarns << "requested toggle of non-boolean control: \"" << key << "\", type is " << control->type() << llendl; - } - } - else - { - llwarns << "requested unknown control: \"" << key << '\"' << llendl; - } +void LLViewerControlListener::get(LLSD const & request) +{ + // The Info constructor and destructor actually do all the work here. + Info info(request); +} + +void LLViewerControlListener::groups(LLSD const & request) +{ + // No Info, we're not looking up either a group or a control name. + Response response(LLSD(), request); + for (LLControlGroup::key_iter cgki(LLControlGroup::beginKeys()), + cgkend(LLControlGroup::endKeys()); + cgki != cgkend; ++cgki) + { + response["groups"].append(*cgki); } } -//static -void LLViewerControlListener::setDefault(LLControlGroup * controls, LLSD const & event_data) +struct CollectVars: public LLControlGroup::ApplyFunctor { - if(event_data.has("key")) + CollectVars(LLControlGroup* g): + mGroup(g) + {} + + virtual void apply(const std::string& name, LLControlVariable* control) { - std::string key(event_data["key"]); + vars.append(LLSDMap + ("name", name) + ("type", mGroup->typeEnumToString(control->type())) + ("value", control->get()) + ("comment", control->getComment())); + } - if(controls->controlExists(key)) - { - LLControlVariable * control = controls->getControl(key); - control->resetToDefault(); - } - else - { - llwarns << "requested unknown control: \"" << key << '\"' << llendl; - } + LLControlGroup* mGroup; + LLSD vars; +}; + +void LLViewerControlListener::vars(LLSD const & request) +{ + // This method doesn't use Info, because we're not looking up a specific + // control name. + Response response(LLSD(), request); + std::string groupname(request["group"]); + LLControlGroup* group(LLControlGroup::getInstance(groupname)); + if (! group) + { + return response.error(STRINGIZE("Unrecognized group '" << groupname << "'")); } + + CollectVars collector(group); + group->applyToAll(&collector); + response["vars"] = collector.vars; } diff --git a/indra/newview/llviewercontrollistener.h b/indra/newview/llviewercontrollistener.h index fd211b97af..2e72046924 100644 --- a/indra/newview/llviewercontrollistener.h +++ b/indra/newview/llviewercontrollistener.h @@ -40,11 +40,11 @@ public: LLViewerControlListener(); private: - static void set(LLControlGroup *controls, LLSD const & event_data); - static void toggleControl(LLControlGroup *controls, LLSD const & event_data); - static void setDefault(LLControlGroup *controls, LLSD const & event_data); + static void set(LLSD const & event_data); + static void toggle(LLSD const & event_data); + static void get(LLSD const & event_data); + static void groups(LLSD const & event_data); + static void vars(LLSD const & event_data); }; -extern LLViewerControlListener gSavedSettingsListener; - #endif // LL_LLVIEWERCONTROLLISTENER_H diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 6435904fee..74ee918bfe 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -2836,8 +2836,8 @@ void process_improved_im(LLMessageSystem *msg, void **user_data) else { LLVector3 pos, look_at; - U64 region_handle; - U8 region_access; + U64 region_handle(0); + U8 region_access(0); std::string region_info = ll_safe_string((char*)binary_bucket, binary_bucket_size); std::string region_access_str = LLStringUtil::null; std::string region_access_icn = LLStringUtil::null; diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index a7f4209e69..98ae746ca7 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -36,6 +36,7 @@ #include <iostream> #include <fstream> #include <algorithm> +#include <boost/lambda/core.hpp> #include "llagent.h" #include "llagentcamera.h" @@ -198,6 +199,7 @@ #include "llfloaternotificationsconsole.h" #include "llnearbychat.h" +#include "llwindowlistener.h" #include "llviewerwindowlistener.h" #include "llpaneltopinfobar.h" @@ -1547,7 +1549,12 @@ LLViewerWindow::LLViewerWindow( mResDirty(false), mStatesDirty(false), mCurrResolutionIndex(0), - mViewerWindowListener(new LLViewerWindowListener(this)), + // gKeyboard is still NULL, so it doesn't do LLWindowListener any good to + // pass its value right now. Instead, pass it a nullary function that + // will, when we later need it, return the value of gKeyboard. + // boost::lambda::var() constructs such a functor on the fly. + mWindowListener(new LLWindowListener(this, boost::lambda::var(gKeyboard))), + mViewerWindowListener(new LLViewerWindowListener(this)), mProgressView(NULL) { LLNotificationChannel::buildChannel("VW_alerts", "Visible", LLNotificationFilters::filterBy<std::string>(&LLNotification::getType, "alert")); diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h index edd241a742..d35feb4667 100644 --- a/indra/newview/llviewerwindow.h +++ b/indra/newview/llviewerwindow.h @@ -62,6 +62,7 @@ class LLImageFormatted; class LLHUDIcon; class LLWindow; class LLRootView; +class LLWindowListener; class LLViewerWindowListener; class LLPopupView; @@ -456,7 +457,8 @@ protected: bool mStatesDirty; U32 mCurrResolutionIndex; - boost::scoped_ptr<LLViewerWindowListener> mViewerWindowListener; + boost::scoped_ptr<LLWindowListener> mWindowListener; + boost::scoped_ptr<LLViewerWindowListener> mViewerWindowListener; protected: static std::string sSnapshotBaseName; diff --git a/indra/newview/llwindowlistener.cpp b/indra/newview/llwindowlistener.cpp new file mode 100644 index 0000000000..28f959eb71 --- /dev/null +++ b/indra/newview/llwindowlistener.cpp @@ -0,0 +1,505 @@ +/** + * @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 "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"), + 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://hg.secondlife.com/viewer-development/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::resolvePath(), described in\n" + "http://hg.secondlife.com/viewer-development/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::resolvePath(LLUI::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::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::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); + + if (evt.has("path")) + { + std::string path(evt["path"]); + LLView * target_view = LLUI::resolvePath(LLUI::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); + KEY key = getKEY(evt); + MASK mask = getMask(evt); + gViewerKeyboard.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 + { + mKbGetter()->handleTranslatedKeyDown(getKEY(evt), getMask(evt)); + } +} + +void LLWindowListener::keyUp(LLSD const & evt) +{ + Response response(LLSD(), evt); + + if (evt.has("path")) + { + std::string path(evt["path"]); + LLView * target_view = LLUI::resolvePath(LLUI::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; + +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")); + + 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 << "'")); + } + + 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::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 & request) +{ + Actions actions(buttons.lookup(request["button"])); + if (actions.valid) + { + mouseEvent(bll::bind(actions.up, mWindow, + static_cast<LLWindow*>(NULL), bll::_1, bll::_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 (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 & request) +{ + S32 clicks = request["clicks"].asInteger(); + + mWindow->handleScrollWheel(NULL, clicks); +} diff --git a/indra/newview/llwindowlistener.h b/indra/newview/llwindowlistener.h new file mode 100644 index 0000000000..7af5ab3b9f --- /dev/null +++ b/indra/newview/llwindowlistener.h @@ -0,0 +1,57 @@ +/** + * @file llwindowlistener.h + * @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$ + */ + +#ifndef LL_LLWINDOWLISTENER_H +#define LL_LLWINDOWLISTENER_H + +#include "lleventapi.h" +#include <boost/function.hpp> + +class LLKeyboard; +class LLViewerWindow; + +class LLWindowListener : public LLEventAPI +{ +public: + typedef boost::function<LLKeyboard*()> KeyboardGetter; + LLWindowListener(LLViewerWindow * window, const KeyboardGetter& kbgetter); + + void getInfo(LLSD const & evt); + void getPaths(LLSD const & evt); + void keyDown(LLSD const & evt); + void keyUp(LLSD const & evt); + void mouseDown(LLSD const & evt); + void mouseUp(LLSD const & evt); + void mouseMove(LLSD const & evt); + void mouseScroll(LLSD const & evt); + +private: + LLViewerWindow * mWindow; + KeyboardGetter mKbGetter; +}; + + +#endif // LL_LLWINDOWLISTENER_H |