diff options
author | Brad Payne (Vir Linden) <vir@lindenlab.com> | 2013-11-04 11:53:49 -0500 |
---|---|---|
committer | Brad Payne (Vir Linden) <vir@lindenlab.com> | 2013-11-04 11:53:49 -0500 |
commit | 8188a8c410807debd2d7d40d0299c36b92e56ca3 (patch) | |
tree | 68a586a4f32bfd1f395c4942701813e002047622 | |
parent | 2dbf6569a77c37d7c7ecc684dd417ddbbb7c6bab (diff) | |
parent | 6ed4c84a30e8f4044ccaedba114e13e07ae5c46d (diff) |
merge
138 files changed, 8870 insertions, 1565 deletions
@@ -467,3 +467,4 @@ fe4f7c5e9fd27e09d03deb1cc9ab3e5093f6309e 3.6.3-release bf6d453046011a11de2643fac610cc5258650f82 3.6.5-release ae457ece77001767ae9613148c495e7b98cc0f4a 3.6.7-release d40c66e410741de7e90b1ed6dac28dd8a2d7e1f6 3.6.8-release +70eda3721d36df3e00730629c42a1304e5bc65b8 3.6.9-release diff --git a/BuildParams b/BuildParams index 9752e5298d..31e7e841ad 100755 --- a/BuildParams +++ b/BuildParams @@ -27,9 +27,6 @@ Linux.distcc_version = Linux.gcc_version = /usr/bin/gcc-4.6 Linux.cxx_version = /usr/bin/g++-4.6 -# Setup default sourceid so Windows can pick up the TeamCity override -sourceid = "" - ################################################################ #### Examples of how to set the viewer_channel #### # @@ -50,6 +47,18 @@ sourceid = "" ################################################################ viewer_channel = "Second Life Test" +# Setup default packaging parameters. +sourceid = "" +additional_packages = "Amazon Desura B C" +Amazon_sourceid = "1207v_Amazon" +Amazon_viewer_channel_suffix = " Amazon" +Desura_sourceid = "1208_desura" +Desura_viewer_channel_suffix = " Desura" +B_sourceid = "1301_B" +B_viewer_channel_suffix = " B" +C_sourceid = "1302_C" +C_viewer_channel_suffix = " C" + # Report changes since... viewer-development.show_changes_since = last_sprint diff --git a/autobuild.xml b/autobuild.xml index 599dd1e53b..c213dc0d99 100755 --- a/autobuild.xml +++ b/autobuild.xml @@ -1965,11 +1965,11 @@ <key>package_description</key> <map> <key>description</key> - <string>Spell checking dictionaries</string> + <string>Second Life Viewer</string> <key>license</key> - <string>various open</string> + <string>LGPL</string> <key>name</key> - <string>dictionaries</string> + <string>Second Life Viewer</string> <key>platforms</key> <map> <key>common</key> @@ -38,22 +38,22 @@ build_dir_CYGWIN() installer_Darwin() { - ls -1td "$(build_dir_Darwin ${last_built_variant:-Release})/newview/"*.dmg 2>/dev/null | sed 1q + ls -1tr "$(build_dir_Darwin ${last_built_variant:-Release})/newview/"*"$additional_package_name"*.dmg 2>/dev/null | sed 1q } installer_Linux() { - ls -1td "$(build_dir_Linux ${last_built_variant:-Release})/newview/"*.tar.bz2 2>/dev/null | sed 1q + ls -1tr "$(build_dir_Linux ${last_built_variant:-Release})/newview/"*"$additional_package_name"*.tar.bz2 2>/dev/null | grep -v symbols | sed 1q } installer_CYGWIN() { v=${last_built_variant:-Release} d=$(build_dir_CYGWIN $v) - if [ -r "$d/newview/$v/touched.bat" ] + if [ -r "$d/newview/$additional_package_name$v/touched.bat" ] then - p=$(sed 's:.*=::' "$d/newview/$v/touched.bat") - echo "$d/newview/$v/$p" + p=$(sed 's:.*=::' "$d/newview/$additional_package_name$v/touched.bat") + echo "$d/newview/$additional_package_name$v/$p" fi } @@ -359,10 +359,28 @@ then # Coverity doesn't package, so it's ok, anything else is fail succeeded=$build_coverity else + # Upload base package. upload_item installer "$package" binary/octet-stream upload_item quicklink "$package" binary/octet-stream [ -f $build_dir/summary.json ] && upload_item installer $build_dir/summary.json text/plain + # Upload additional packages. + for package_id in $additional_packages + do + case $arch in + CYGWIN) export additional_package_name="$package_id/" ;; + *) export additional_package_name=$package_id ;; + esac + package=$(installer_$arch) + if [ x"$package" != x ] + then + upload_item installer "$package" binary/octet-stream + else + record_failure "Failed to upload $package_id package." + fi + done + export additional_package_name="" + case "$last_built_variant" in Release) # Upload crash reporter files diff --git a/doc/contributions.txt b/doc/contributions.txt index 931211f622..99527c0587 100755 --- a/doc/contributions.txt +++ b/doc/contributions.txt @@ -175,8 +175,11 @@ Ansariel Hiller STORM-1685 STORM-1713 STORM-1899 + STORM-1932 + STORM-1933 MAINT-2368 STORM-1931 + MAINT-2773 Aralara Rajal Arare Chantilly CHUIBUG-191 @@ -304,9 +307,13 @@ Christopher Organiser Ciaran Laval Cinder Roxley BUG-2326 + OPEN-185 STORM-1703 STORM-1948 + STORM-1888 + STORM-1958 STORM-1952 + STORM-1951 Clara Young Coaldust Numbers VWR-1095 @@ -652,7 +659,7 @@ Jonathan Yap STORM-1809 STORM-1793 STORM-1810 - STORM-1877 + STORM-1838 STORM-1892 STORM-1894 STORM-1860 @@ -662,8 +669,11 @@ Jonathan Yap STORM-1858 STORM-1862 STORM-1918 + STORM-1929 STORM-1953 OPEN-161 + STORM-1953 + STORM-1957 Kadah Coba STORM-1060 STORM-1843 @@ -679,7 +689,9 @@ Kagehi Kohn Kaimen Takahe Katharine Berry STORM-1900 + OPEN-149 STORM-1940 + OPEN-149 STORM-1941 Keklily Longfall Ken Lavender @@ -928,7 +940,9 @@ Nicky Dasmijn STORM-1936 BUG-3605 CHUIBUG-197 + OPEN-187 STORM-1937 + OPEN-187 Nicky Perian OPEN-1 STORM-1087 @@ -1125,6 +1139,7 @@ Slee Mayo snowy Sidran Sovereign Engineer MAINT-2334 + OPEN-189 SpacedOut Frye VWR-34 VWR-45 @@ -1293,6 +1308,7 @@ Westley Streeter Whimsy Winx Whirly Fizzle STORM-1895 + VWR-29543 MAINT-873 STORM-1930 Whoops Babii @@ -1356,6 +1372,7 @@ YongYong Francois Zak Westminster Zai Lynch VWR-19505 + STORM-1902 Zana Kohime Zaren Alexander Zarkonnen Decosta diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index bdbfc55fa2..410d25ad97 100755 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -63,10 +63,11 @@ add_subdirectory(${VIEWER_PREFIX}test) # viewer media plugins add_subdirectory(${LIBS_OPEN_PREFIX}media_plugins) -# llplugin testbed code (is this the right way to include it?) -if (LL_TESTS AND NOT LINUX) - add_subdirectory(${VIEWER_PREFIX}test_apps/llplugintest) -endif (LL_TESTS AND NOT LINUX) + # llplugin testbed code (is this the right way to include it?) + if (LL_TESTS AND NOT LINUX) + add_subdirectory(${VIEWER_PREFIX}test_apps/llplugintest) + add_subdirectory(${VIEWER_PREFIX}test_apps/llfbconnecttest) + endif (LL_TESTS AND NOT LINUX) if (LINUX) add_subdirectory(${VIEWER_PREFIX}linux_crash_logger) diff --git a/indra/cmake/BuildVersion.cmake b/indra/cmake/BuildVersion.cmake index 969670b1b9..e618a988b8 100755 --- a/indra/cmake/BuildVersion.cmake +++ b/indra/cmake/BuildVersion.cmake @@ -16,22 +16,26 @@ if (NOT DEFINED VIEWER_SHORT_VERSION) # will be true in indra/, false in indra/n else (DEFINED ENV{revision}) find_program(MERCURIAL hg) - if (DEFINED MERCURIAL) + find_program(WORDCOUNT wc) + find_program(SED sed) + if (DEFINED MERCURIAL AND DEFINED WORDCOUNT AND DEFINED SED) execute_process( - COMMAND ${MERCURIAL} log -r tip --template "{rev}" + COMMAND ${MERCURIAL} log -r tip:0 --template '\\n' + COMMAND ${WORDCOUNT} -l + COMMAND ${SED} "s/ //g" OUTPUT_VARIABLE VIEWER_VERSION_REVISION OUTPUT_STRIP_TRAILING_WHITESPACE ) if ("${VIEWER_VERSION_REVISION}" MATCHES "^[0-9]+$") message("Revision (from hg) ${VIEWER_VERSION_REVISION}") else ("${VIEWER_VERSION_REVISION}" MATCHES "^[0-9]+$") + message("Revision not set (repository not found?); using 0") set(VIEWER_VERSION_REVISION 0 ) - message("Revision not set, repository not found, using ${VIEWER_VERSION_REVISION}") endif ("${VIEWER_VERSION_REVISION}" MATCHES "^[0-9]+$") - else (DEFINED MERCURIAL) + else (DEFINED MERCURIAL AND DEFINED WORDCOUNT AND DEFINED SED) + message("Revision not set: 'hg', 'wc' or 'sed' not found; using 0") set(VIEWER_VERSION_REVISION 0) - message("Revision not set, 'hg' not found (${MERCURIAL}), using ${VIEWER_VERSION_REVISION}") - endif (DEFINED MERCURIAL) + endif (DEFINED MERCURIAL AND DEFINED WORDCOUNT AND DEFINED SED) endif (DEFINED ENV{revision}) message("Building '${VIEWER_CHANNEL}' Version ${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}") else ( EXISTS ${VIEWER_VERSION_BASE_FILE} ) diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py index 54049b5545..52b4acbc94 100755 --- a/indra/lib/python/indra/util/llmanifest.py +++ b/indra/lib/python/indra/util/llmanifest.py @@ -224,15 +224,98 @@ def main(): for opt in args: print "Option:", opt, "=", args[opt] + # pass in sourceid as an argument now instead of an environment variable + try: + args['sourceid'] = os.environ["sourceid"] + except KeyError: + args['sourceid'] = "" + + # Build base package. + touch = args.get('touch') + if touch: + print 'Creating base package' + args['package_id'] = "" # base package has no package ID wm = LLManifest.for_platform(args['platform'], args.get('arch'))(args) wm.do(*args['actions']) - + # Store package file for later if making touched file. + base_package_file = "" + if touch: + print 'Created base package ', wm.package_file + base_package_file = "" + wm.package_file + + # handle multiple packages if set + try: + additional_packages = os.environ["additional_packages"] + except KeyError: + additional_packages = "" + if additional_packages: + # Determine destination prefix / suffix for additional packages. + base_dest_postfix = args['dest'] + base_dest_prefix = "" + base_dest_parts = args['dest'].split(os.sep) + if len(base_dest_parts) > 1: + base_dest_postfix = base_dest_parts[len(base_dest_parts) - 1] + base_dest_prefix = base_dest_parts[0] + i = 1 + while i < len(base_dest_parts) - 1: + base_dest_prefix = base_dest_prefix + os.sep + base_dest_parts[i] + i = i + 1 + # Determine touched prefix / suffix for additional packages. + base_touch_postfix = "" + base_touch_prefix = "" + if touch: + base_touch_postfix = touch + base_touch_parts = touch.split('/') + if "arwin" in args['platform']: + if len(base_touch_parts) > 1: + base_touch_postfix = base_touch_parts[len(base_touch_parts) - 1] + base_touch_prefix = base_touch_parts[0] + i = 1 + while i < len(base_touch_parts) - 1: + base_touch_prefix = base_touch_prefix + '/' + base_touch_parts[i] + i = i + 1 + else: + if len(base_touch_parts) > 2: + base_touch_postfix = base_touch_parts[len(base_touch_parts) - 2] + '/' + base_touch_parts[len(base_touch_parts) - 1] + base_touch_prefix = base_touch_parts[0] + i = 1 + while i < len(base_touch_parts) - 2: + base_touch_prefix = base_touch_prefix + '/' + base_touch_parts[i] + i = i + 1 + # Store base channel name. + base_channel_name = args['channel'] + # Build each additional package. + package_id_list = additional_packages.split(" ") + for package_id in package_id_list: + try: + args['package_id'] = package_id + args['channel'] = base_channel_name + os.environ[package_id + "_viewer_channel_suffix"] + if package_id + "_sourceid" in os.environ: + args['sourceid'] = os.environ[package_id + "_sourceid"] + else: + args['sourceid'] = "" + args['dest'] = base_dest_prefix + os.sep + package_id + os.sep + base_dest_postfix + except KeyError: + sys.stderr.write("Failed to create package for package_id: %s" % package_id) + sys.stderr.flush() + continue + if touch: + print 'Creating additional package for ', package_id, ' in ', args['dest'] + wm = LLManifest.for_platform(args['platform'], args.get('arch'))(args) + wm.do(*args['actions']) + if touch: + print 'Created additional package ', wm.package_file, ' for ', package_id + faketouch = base_touch_prefix + '/' + package_id + '/' + base_touch_postfix + fp = open(faketouch, 'w') + fp.write('set package_file=%s\n' % wm.package_file) + fp.close() + # Write out the package file in this format, so that it can easily be called # and used in a .bat file - yeah, it sucks, but this is the simplest... touch = args.get('touch') if touch: fp = open(touch, 'w') - fp.write('set package_file=%s\n' % wm.package_file) + fp.write('set package_file=%s\n' % base_package_file) fp.close() print 'touched', touch return 0 diff --git a/indra/llcommon/llversionviewer.h b/indra/llcommon/llversionviewer.h deleted file mode 100644 index 6a5ff314e4..0000000000 --- a/indra/llcommon/llversionviewer.h +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @file llversionviewer.h - * @brief - * - * $LicenseInfo:firstyear=2002&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_LLVERSIONVIEWER_H -#define LL_LLVERSIONVIEWER_H - -const S32 LL_VERSION_MAJOR = 3; -const S32 LL_VERSION_MINOR = 4; -const S32 LL_VERSION_PATCH = 6; -const S32 LL_VERSION_BUILD = 0; - -const char * const LL_CHANNEL = "Second Life Developer"; - -#if LL_DARWIN -const char * const LL_VERSION_BUNDLE_ID = "com.secondlife.indra.viewer"; -#endif - -#endif diff --git a/indra/llmath/v4color.h b/indra/llmath/v4color.h index b047f86e6e..8c8c315808 100755 --- a/indra/llmath/v4color.h +++ b/indra/llmath/v4color.h @@ -50,7 +50,7 @@ class LLColor4 LLColor4(F32 r, F32 g, F32 b); // Initializes LLColor4 to (r, g, b, 1) LLColor4(F32 r, F32 g, F32 b, F32 a); // Initializes LLColor4 to (r. g, b, a) LLColor4(U32 clr); // Initializes LLColor4 to (r=clr>>24, etc)) - LLColor4(const F32 *vec); // Initializes LLColor4 to (vec[0]. vec[1], vec[2], 1) + LLColor4(const F32 *vec); // Initializes LLColor4 to (vec[0]. vec[1], vec[2], vec[3]) LLColor4(const LLColor3 &vec, F32 a = 1.f); // Initializes LLColor4 to (vec, a) explicit LLColor4(const LLSD& sd); explicit LLColor4(const LLColor4U& color4u); // "explicit" to avoid automatic conversion diff --git a/indra/llmessage/llhttpclient.cpp b/indra/llmessage/llhttpclient.cpp index 70c890a8de..5e87c3398d 100755 --- a/indra/llmessage/llhttpclient.cpp +++ b/indra/llmessage/llhttpclient.cpp @@ -220,7 +220,8 @@ static void request( Injector* body_injector, LLCurl::ResponderPtr responder, const F32 timeout = HTTP_REQUEST_EXPIRY_SECS, - const LLSD& headers = LLSD() + const LLSD& headers = LLSD(), + bool follow_redirects = true ) { if (!LLHTTPClient::hasPump()) @@ -234,7 +235,7 @@ static void request( } LLPumpIO::chain_t chain; - LLURLRequest* req = new LLURLRequest(method, url); + LLURLRequest* req = new LLURLRequest(method, url, follow_redirects); if(!req->isValid())//failed { if (responder) @@ -330,7 +331,8 @@ void LLHTTPClient::getByteRange( S32 bytes, ResponderPtr responder, const LLSD& hdrs, - const F32 timeout) + const F32 timeout, + bool follow_redirects /* = true */) { LLSD headers = hdrs; if(offset > 0 || bytes > 0) @@ -338,37 +340,42 @@ void LLHTTPClient::getByteRange( std::string range = llformat("bytes=%d-%d", offset, offset+bytes-1); headers[HTTP_OUT_HEADER_RANGE] = range; } - request(url,HTTP_GET, NULL, responder, timeout, headers); + request(url,HTTP_GET, NULL, responder, timeout, headers, follow_redirects); } void LLHTTPClient::head( const std::string& url, ResponderPtr responder, const LLSD& headers, - const F32 timeout) + const F32 timeout, + bool follow_redirects /* = true */) { - request(url, HTTP_HEAD, NULL, responder, timeout, headers); + request(url, HTTP_HEAD, NULL, responder, timeout, headers, follow_redirects); } -void LLHTTPClient::get(const std::string& url, ResponderPtr responder, const LLSD& headers, const F32 timeout) +void LLHTTPClient::get(const std::string& url, ResponderPtr responder, const LLSD& headers, const F32 timeout, + bool follow_redirects /* = true */) { - request(url, HTTP_GET, NULL, responder, timeout, headers); + request(url, HTTP_GET, NULL, responder, timeout, headers, follow_redirects); } -void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const LLSD& headers, const F32 timeout) +void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const LLSD& headers, + const F32 timeout, bool follow_redirects /* = true */) { - request(url, HTTP_HEAD, NULL, responder, timeout, headers); + request(url, HTTP_HEAD, NULL, responder, timeout, headers, follow_redirects); } -void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const F32 timeout) +void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const F32 timeout, + bool follow_redirects /* = true */) { - getHeaderOnly(url, responder, LLSD(), timeout); + getHeaderOnly(url, responder, LLSD(), timeout, follow_redirects); } -void LLHTTPClient::get(const std::string& url, const LLSD& query, ResponderPtr responder, const LLSD& headers, const F32 timeout) +void LLHTTPClient::get(const std::string& url, const LLSD& query, ResponderPtr responder, const LLSD& headers, + const F32 timeout, bool follow_redirects /* = true */) { LLURI uri; uri = LLURI::buildHTTP(url, LLSD::emptyArray(), query); - get(uri.asString(), responder, headers, timeout); + get(uri.asString(), responder, headers, timeout, follow_redirects); } // A simple class for managing data returned from a curl http request. diff --git a/indra/llmessage/llhttpclient.h b/indra/llmessage/llhttpclient.h index 5ace2d74c9..b18258fd7b 100755 --- a/indra/llmessage/llhttpclient.h +++ b/indra/llmessage/llhttpclient.h @@ -63,10 +63,15 @@ public: const std::string& url, ResponderPtr, const LLSD& headers = LLSD(), - const F32 timeout=HTTP_REQUEST_EXPIRY_SECS); - static void getByteRange(const std::string& url, S32 offset, S32 bytes, ResponderPtr, const LLSD& headers=LLSD(), const F32 timeout=HTTP_REQUEST_EXPIRY_SECS); - static void get(const std::string& url, ResponderPtr, const LLSD& headers = LLSD(), const F32 timeout=HTTP_REQUEST_EXPIRY_SECS); - static void get(const std::string& url, const LLSD& query, ResponderPtr, const LLSD& headers = LLSD(), const F32 timeout=HTTP_REQUEST_EXPIRY_SECS); + const F32 timeout=HTTP_REQUEST_EXPIRY_SECS, + bool follow_redirects = true); + static void getByteRange(const std::string& url, S32 offset, S32 bytes, ResponderPtr, + const LLSD& headers=LLSD(), const F32 timeout=HTTP_REQUEST_EXPIRY_SECS, + bool follow_redirects = true); + static void get(const std::string& url, ResponderPtr, const LLSD& headers = LLSD(), + const F32 timeout=HTTP_REQUEST_EXPIRY_SECS, bool follow_redirects = true); + static void get(const std::string& url, const LLSD& query, ResponderPtr, const LLSD& headers = LLSD(), + const F32 timeout=HTTP_REQUEST_EXPIRY_SECS, bool follow_redirects = true); static void put( const std::string& url, @@ -82,8 +87,10 @@ public: const LLSD& headers = LLSD(), const F32 timeout=HTTP_REQUEST_EXPIRY_SECS); - static void getHeaderOnly(const std::string& url, ResponderPtr, const F32 timeout=HTTP_REQUEST_EXPIRY_SECS); - static void getHeaderOnly(const std::string& url, ResponderPtr, const LLSD& headers, const F32 timeout=HTTP_REQUEST_EXPIRY_SECS); + static void getHeaderOnly(const std::string& url, ResponderPtr, const F32 timeout=HTTP_REQUEST_EXPIRY_SECS, + bool follow_redirects = true); + static void getHeaderOnly(const std::string& url, ResponderPtr, const LLSD& headers, + const F32 timeout=HTTP_REQUEST_EXPIRY_SECS, bool follow_redirects = true); static void post( const std::string& url, diff --git a/indra/llmessage/llinstantmessage.h b/indra/llmessage/llinstantmessage.h index db4a38ea9e..f7118f8ccf 100755 --- a/indra/llmessage/llinstantmessage.h +++ b/indra/llmessage/llinstantmessage.h @@ -126,7 +126,7 @@ enum EInstantMessage IM_LURE_ACCEPTED = 23, IM_LURE_DECLINED = 24, IM_GODLIKE_LURE_USER = 25, - IM_YET_TO_BE_USED = 26, + IM_TELEPORT_REQUEST = 26, // IM that notifie of a new group election. // Name is name of person who called vote. diff --git a/indra/llmessage/llurlrequest.cpp b/indra/llmessage/llurlrequest.cpp index d6448e83fe..ae4c40cdb0 100755 --- a/indra/llmessage/llurlrequest.cpp +++ b/indra/llmessage/llurlrequest.cpp @@ -131,16 +131,19 @@ CURLcode LLURLRequest::_sslCtxCallback(CURL * curl, void *sslctx, void *param) */ -LLURLRequest::LLURLRequest(EHTTPMethod action) : - mAction(action) +LLURLRequest::LLURLRequest(EHTTPMethod action, bool follow_redirects /* = true */) : + mAction(action), + mFollowRedirects(follow_redirects) { initialize(); } LLURLRequest::LLURLRequest( EHTTPMethod action, - const std::string& url) : - mAction(action) + const std::string& url, + bool follow_redirects /* = true */) : + mAction(action), + mFollowRedirects(follow_redirects) { initialize(); setURL(url); @@ -465,12 +468,18 @@ bool LLURLRequest::configure() case HTTP_HEAD: mDetail->mCurlRequest->setopt(CURLOPT_HEADER, 1); mDetail->mCurlRequest->setopt(CURLOPT_NOBODY, 1); - mDetail->mCurlRequest->setopt(CURLOPT_FOLLOWLOCATION, 1); + if (mFollowRedirects) + { + mDetail->mCurlRequest->setopt(CURLOPT_FOLLOWLOCATION, 1); + } rv = true; break; case HTTP_GET: mDetail->mCurlRequest->setopt(CURLOPT_HTTPGET, 1); - mDetail->mCurlRequest->setopt(CURLOPT_FOLLOWLOCATION, 1); + if (mFollowRedirects) + { + mDetail->mCurlRequest->setopt(CURLOPT_FOLLOWLOCATION, 1); + } // Set Accept-Encoding to allow response compression mDetail->mCurlRequest->setoptString(CURLOPT_ENCODING, ""); diff --git a/indra/llmessage/llurlrequest.h b/indra/llmessage/llurlrequest.h index f3d32f5419..88fccd4bf6 100755 --- a/indra/llmessage/llurlrequest.h +++ b/indra/llmessage/llurlrequest.h @@ -76,7 +76,7 @@ public: * * @param action One of the EHTTPMethod enumerations. */ - LLURLRequest(EHTTPMethod action); + LLURLRequest(EHTTPMethod action, bool follow_redirects = true); /** * @brief Constructor. @@ -84,7 +84,7 @@ public: * @param action One of the EHTTPMethod enumerations. * @param url The url of the request. It should already be encoded. */ - LLURLRequest(EHTTPMethod action, const std::string& url); + LLURLRequest(EHTTPMethod action, const std::string& url, bool follow_redirects = true); /** * @brief Destructor. @@ -200,10 +200,11 @@ protected: }; EState mState; EHTTPMethod mAction; + bool mFollowRedirects; LLURLRequestDetail* mDetail; LLIOPipe::ptr_t mCompletionCallback; - S32 mRequestTransferedBytes; - S32 mResponseTransferedBytes; + S32 mRequestTransferedBytes; + S32 mResponseTransferedBytes; static CURLcode _sslCtxCallback(CURL * curl, void *sslctx, void *param); diff --git a/indra/llmessage/tests/llhttpclientadapter_test.cpp b/indra/llmessage/tests/llhttpclientadapter_test.cpp index cc7feeab4f..e9ce116bb3 100755 --- a/indra/llmessage/tests/llhttpclientadapter_test.cpp +++ b/indra/llmessage/tests/llhttpclientadapter_test.cpp @@ -34,7 +34,7 @@ float const HTTP_REQUEST_EXPIRY_SECS = 1.0F; std::vector<std::string> get_urls; std::vector< LLCurl::ResponderPtr > get_responders; -void LLHTTPClient::get(const std::string& url, LLCurl::ResponderPtr responder, const LLSD& headers, const F32 timeout) +void LLHTTPClient::get(const std::string& url, LLCurl::ResponderPtr responder, const LLSD& headers, const F32 timeout, bool follow_redirects) { get_urls.push_back(url); get_responders.push_back(responder); diff --git a/indra/llplugin/llplugincookiestore.cpp b/indra/llplugin/llplugincookiestore.cpp index 82017ab3fa..9f4d65e723 100755 --- a/indra/llplugin/llplugincookiestore.cpp +++ b/indra/llplugin/llplugincookiestore.cpp @@ -87,6 +87,16 @@ std::string LLPluginCookieStore::Cookie::getKey() const return result; } +std::string LLPluginCookieStore::Cookie::getDomain() const +{ + std::string result; + if(mDomainEnd > mDomainStart) + { + result += mCookie.substr(mDomainStart, mDomainEnd - mDomainStart); + } + return result; +} + bool LLPluginCookieStore::Cookie::parse(const std::string &host) { bool first_field = true; @@ -662,3 +672,21 @@ void LLPluginCookieStore::removeCookie(const std::string &key) } } +void LLPluginCookieStore::removeCookiesByDomain(const std::string &domain) +{ + cookie_map_t::iterator iter = mCookies.begin(); + while(iter != mCookies.end()) + { + if(iter->second->getDomain() == domain) + { + cookie_map_t::iterator doErase = iter; + iter++; + delete doErase->second; + mCookies.erase(doErase); + } + else + { + iter++; + } + } +} diff --git a/indra/llplugin/llplugincookiestore.h b/indra/llplugin/llplugincookiestore.h index 91289d38a5..a2fdeab647 100755 --- a/indra/llplugin/llplugincookiestore.h +++ b/indra/llplugin/llplugincookiestore.h @@ -67,6 +67,8 @@ public: // quote or unquote a string as per the definition of 'quoted-string' in rfc2616 static std::string quoteString(const std::string &s); static std::string unquoteString(const std::string &s); + + void removeCookiesByDomain(const std::string &domain); private: @@ -79,6 +81,7 @@ private: // Construct a string from the cookie that uniquely represents it, to be used as a key in a std::map. std::string getKey() const; + std::string getDomain() const; const std::string &getCookie() const { return mCookie; }; bool isSessionCookie() const { return mDate.isNull(); }; diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 34a08603fa..589ceac501 100755 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -75,6 +75,7 @@ set(llui_SOURCE_FILES llmultislider.cpp llmultisliderctrl.cpp llnotifications.cpp + llnotificationslistener.cpp llnotificationsutil.cpp llpanel.cpp llprogressbar.cpp @@ -128,6 +129,7 @@ set(llui_SOURCE_FILES llviewmodel.cpp llview.cpp llviewquery.cpp + llviewereventrecorder.cpp llwindowshade.cpp llxuiparser.cpp ) @@ -183,6 +185,7 @@ set(llui_HEADER_FILES llmultislider.h llnotificationptr.h llnotifications.h + llnotificationslistener.h llnotificationsutil.h llnotificationtemplate.h llnotificationvisibilityrule.h @@ -240,6 +243,7 @@ set(llui_HEADER_FILES llviewinject.h llviewmodel.h llview.h + llviewereventrecorder.h llviewquery.h llwindowshade.h llxuiparser.h diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp index 44f2c1efe9..50ac511d18 100755 --- a/indra/llui/llbutton.cpp +++ b/indra/llui/llbutton.cpp @@ -49,6 +49,7 @@ #include "lluictrlfactory.h" #include "llhelp.h" #include "lldockablefloater.h" +#include "llviewereventrecorder.h" static LLDefaultChildRegistry::Register<LLButton> r("button"); @@ -443,6 +444,8 @@ BOOL LLButton::handleMouseDown(S32 x, S32 y, MASK mask) */ LLUICtrl::handleMouseDown(x, y, mask); + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); + if(mMouseDownSignal) (*mMouseDownSignal)(this, LLSD()); mMouseDownTimer.start(); @@ -473,6 +476,7 @@ BOOL LLButton::handleMouseUp(S32 x, S32 y, MASK mask) * by calling LLUICtrl::mMouseUpSignal(x, y, mask); */ LLUICtrl::handleMouseUp(x, y, mask); + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); // Regardless of where mouseup occurs, handle callback if(mMouseUpSignal) (*mMouseUpSignal)(this, LLSD()); diff --git a/indra/llui/llchatentry.h b/indra/llui/llchatentry.h index e67f39b21b..3f13691a30 100755 --- a/indra/llui/llchatentry.h +++ b/indra/llui/llchatentry.h @@ -62,8 +62,8 @@ public: virtual void draw(); virtual void onCommit(); - /*virtual*/ void onFocusReceived(); - /*virtual*/ void onFocusLost(); + /*virtual*/ void onFocusReceived(); + /*virtual*/ void onFocusLost(); void enableSingleLineMode(bool single_line_mode); boost::signals2::connection setTextExpandedCallback(const commit_signal_t::slot_type& cb); diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index 8919af32ec..162a22cde3 100755 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -29,7 +29,7 @@ // mini-map floater, etc. #include "linden_common.h" - +#include "llviewereventrecorder.h" #include "llfloater.h" #include "llfocusmgr.h" @@ -509,8 +509,8 @@ LLFloater::~LLFloater() if( gFocusMgr.childHasKeyboardFocus(this)) { - // Just in case we might still have focus here, release it. - releaseFocus(); + // Just in case we might still have focus here, release it. + releaseFocus(); } // This is important so that floaters with persistent rects (i.e., those @@ -642,7 +642,10 @@ void LLFloater::handleVisibilityChange ( BOOL new_visibility ) void LLFloater::openFloater(const LLSD& key) { - llinfos << "Opening floater " << getName() << llendl; + llinfos << "Opening floater " << getName() << " full path: " << getPathname() << llendl; + + LLViewerEventRecorder::instance().logVisibilityChange( getPathname(), getName(), true,"floater"); // Last param is event subtype or empty string + mKey = key; // in case we need to open ourselves again if (getSoundFlags() != SILENT @@ -696,6 +699,7 @@ void LLFloater::openFloater(const LLSD& key) void LLFloater::closeFloater(bool app_quitting) { llinfos << "Closing floater " << getName() << llendl; + LLViewerEventRecorder::instance().logVisibilityChange( getPathname(), getName(), false,"floater"); // Last param is event subtype or empty string if (app_quitting) { LLFloater::sQuitting = true; @@ -1335,7 +1339,7 @@ void LLFloater::setFocus( BOOL b ) { return; } - LLUICtrl* last_focus = gFocusMgr.getLastFocusForGroup(this); + LLView* last_focus = gFocusMgr.getLastFocusForGroup(this); // a descendent already has focus BOOL child_had_focus = hasFocus(); @@ -1543,6 +1547,17 @@ BOOL LLFloater::handleScrollWheel(S32 x, S32 y, S32 clicks) } // virtual +BOOL LLFloater::handleMouseUp(S32 x, S32 y, MASK mask) +{ + lldebugs << "LLFloater::handleMouseUp calling LLPanel (really LLView)'s handleMouseUp (first initialized xui to: " << getPathname() << " )" << llendl; + BOOL handled = LLPanel::handleMouseUp(x,y,mask); // Not implemented in LLPanel so this actually calls LLView + if (handled) { + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); + } + return handled; +} + +// virtual BOOL LLFloater::handleMouseDown(S32 x, S32 y, MASK mask) { if( mMinimized ) @@ -1562,7 +1577,11 @@ BOOL LLFloater::handleMouseDown(S32 x, S32 y, MASK mask) else { bringToFront( x, y ); - return LLPanel::handleMouseDown( x, y, mask ); + BOOL handled = LLPanel::handleMouseDown( x, y, mask ); + if (handled) { + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); + } + return handled; } } diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h index f5c3079204..9c6ff456f3 100755 --- a/indra/llui/llfloater.h +++ b/indra/llui/llfloater.h @@ -289,6 +289,7 @@ public: S32 getHeaderHeight() const { return mHeaderHeight; } virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); virtual BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); virtual BOOL handleMiddleMouseDown(S32 x, S32 y, MASK mask); diff --git a/indra/llui/llfocusmgr.cpp b/indra/llui/llfocusmgr.cpp index 724d190307..f03c8d444b 100755 --- a/indra/llui/llfocusmgr.cpp +++ b/indra/llui/llfocusmgr.cpp @@ -469,7 +469,7 @@ void LLFocusMgr::setAppHasFocus(BOOL focus) mAppHasFocus = focus; } -LLUICtrl* LLFocusMgr::getLastFocusForGroup(LLView* subtree_root) const +LLView* LLFocusMgr::getLastFocusForGroup(LLView* subtree_root) const { if (subtree_root) { @@ -477,7 +477,7 @@ LLUICtrl* LLFocusMgr::getLastFocusForGroup(LLView* subtree_root) const if (found_it != mImpl->mFocusHistory.end()) { // found last focus for this subtree - return static_cast<LLUICtrl*>(found_it->second.get()); + return found_it->second.get(); } } return NULL; diff --git a/indra/llui/llfocusmgr.h b/indra/llui/llfocusmgr.h index 25ae1d2579..1c7326260c 100755 --- a/indra/llui/llfocusmgr.h +++ b/indra/llui/llfocusmgr.h @@ -97,7 +97,7 @@ public: void triggerFocusFlash(); BOOL getAppHasFocus() const { return mAppHasFocus; } void setAppHasFocus(BOOL focus); - LLUICtrl* getLastFocusForGroup(LLView* subtree_root) const; + LLView* getLastFocusForGroup(LLView* subtree_root) const; void clearLastFocusForGroup(LLView* subtree_root); // If setKeyboardFocus(NULL) is called, and there is a non-NULL default diff --git a/indra/llui/llfolderview.cpp b/indra/llui/llfolderview.cpp index cf449217f5..f32a52e6c6 100755 --- a/indra/llui/llfolderview.cpp +++ b/indra/llui/llfolderview.cpp @@ -185,7 +185,7 @@ LLFolderView::LLFolderView(const Params& p) mAutoOpenCandidate = NULL; mAutoOpenTimer.stop(); mKeyboardSelection = FALSE; - mIndentation = p.folder_indentation; + mIndentation = getParentFolder() ? getParentFolder()->getIndentation() + mLocalIndentation : 0; //clear label // go ahead and render root folder as usual @@ -1613,7 +1613,7 @@ void LLFolderView::update() { getFolderViewModel()->getFilter().clearModified(); } - + // automatically show matching items, and select first one if we had a selection if (mNeedsAutoSelect) { @@ -1653,13 +1653,13 @@ void LLFolderView::update() BOOL is_visible = isInVisibleChain(); - // Puts folders/items in proper positions + //Puts folders/items in proper positions // arrange() takes the model filter flag into account and call sort() if necessary (CHUI-849) // It also handles the open/close folder animation - if (is_visible) + if ( is_visible ) { sanitizeSelection(); - if (needsArrange()) + if( needsArrange() ) { S32 height = 0; S32 width = 0; diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index 6c147ccc12..f061313645 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -307,7 +307,12 @@ std::set<LLFolderViewItem*> LLFolderViewItem::getSelectionList() const // addToFolder() returns TRUE if it succeeds. FALSE otherwise void LLFolderViewItem::addToFolder(LLFolderViewFolder* folder) { - folder->addItem(this); + folder->addItem(this); + + // Compute indentation since parent folder changed + mIndentation = (getParentFolder()) + ? getParentFolder()->getIndentation() + mLocalIndentation + : 0; } @@ -940,6 +945,11 @@ LLFolderViewFolder::~LLFolderViewFolder( void ) void LLFolderViewFolder::addToFolder(LLFolderViewFolder* folder) { folder->addFolder(this); + + // Compute indentation since parent folder changed + mIndentation = (getParentFolder()) + ? getParentFolder()->getIndentation() + mLocalIndentation + : 0; } static LLFastTimer::DeclareTimer FTM_ARRANGE("Arrange"); @@ -1109,7 +1119,7 @@ S32 LLFolderViewFolder::arrange( S32* width, S32* height ) BOOL LLFolderViewFolder::needsArrange() { - return mLastArrangeGeneration < getRoot()->getArrangeGeneration(); + return mLastArrangeGeneration < getRoot()->getArrangeGeneration(); } // Passes selection information on to children and record selection diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 3186c5460a..5c288c3f03 100755 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -1206,6 +1206,7 @@ LLNotifications::LLNotifications() : LLNotificationChannelBase(LLNotificationFilters::includeEverything), mIgnoreAllNotifications(false) { + mListener.reset(new LLNotificationsListener(*this)); LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Notification.Show", boost::bind(&LLNotifications::addFromCallback, this, _2)); } diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index 3b620084ee..6ac4a98806 100755 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -98,6 +98,8 @@ #include "llrefcount.h" #include "llsdparam.h" +#include "llnotificationslistener.h" + class LLAvatarName; typedef enum e_notification_priority { @@ -978,6 +980,8 @@ private: bool mIgnoreAllNotifications; + boost::scoped_ptr<LLNotificationsListener> mListener; + std::vector<LLNotificationChannelPtr> mDefaultChannels; }; diff --git a/indra/llui/llnotificationslistener.cpp b/indra/llui/llnotificationslistener.cpp new file mode 100644 index 0000000000..9e8e943ee6 --- /dev/null +++ b/indra/llui/llnotificationslistener.cpp @@ -0,0 +1,359 @@ +/** + * @file llnotificationslistener.cpp + * @author Brad Kittenbrink + * @date 2009-07-08 + * @brief Implementation for llnotificationslistener. + * + * $LicenseInfo:firstyear=2009&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 "linden_common.h" +#include "llnotificationslistener.h" +#include "llnotifications.h" +#include "llnotificationtemplate.h" +#include "llsd.h" +#include "llui.h" + +LLNotificationsListener::LLNotificationsListener(LLNotifications & notifications) : + LLEventAPI("LLNotifications", + "LLNotifications listener to (e.g.) pop up a notification"), + mNotifications(notifications) +{ + add("requestAdd", + "Add a notification with specified [\"name\"], [\"substitutions\"] and [\"payload\"].\n" + "If optional [\"reply\"] specified, arrange to send user response on that LLEventPump.", + &LLNotificationsListener::requestAdd); + /* add("listChannels", + "Post to [\"reply\"] a map of info on existing channels", + &LLNotificationsListener::listChannels, + LLSD().with("reply", LLSD())); + */ + add("listChannelNotifications", + "Post to [\"reply\"] an array of info on notifications in channel [\"channel\"]", + &LLNotificationsListener::listChannelNotifications, + LLSD().with("reply", LLSD()).with("channel", LLSD())); + add("respond", + "Respond to notification [\"uuid\"] with data in [\"response\"]", + &LLNotificationsListener::respond, + LLSD().with("uuid", LLSD())); + add("cancel", + "Cancel notification [\"uuid\"]", + &LLNotificationsListener::cancel, + LLSD().with("uuid", LLSD())); + add("ignore", + "Ignore future notification [\"name\"]\n" + "(from <notification name= > in notifications.xml)\n" + "according to boolean [\"ignore\"].\n" + "If [\"name\"] is omitted or undefined, [un]ignore all future notifications.\n" + "Note that ignored notifications are not forwarded unless intercepted before\n" + "the \"Ignore\" channel.", + &LLNotificationsListener::ignore); + add("forward", + "Forward to [\"pump\"] future notifications on channel [\"channel\"]\n" + "according to boolean [\"forward\"]. When enabled, only types matching\n" + "[\"types\"] are forwarded, as follows:\n" + "omitted or undefined: forward all notifications\n" + "string: forward only the specific named [sig]type\n" + "array of string: forward any notification matching any named [sig]type.\n" + "When boolean [\"respond\"] is true, we auto-respond to each forwarded\n" + "notification.", + &LLNotificationsListener::forward, + LLSD().with("channel", LLSD())); +} + +// This is here in the .cpp file so we don't need the definition of class +// Forwarder in the header file. +LLNotificationsListener::~LLNotificationsListener() +{ +} + +void LLNotificationsListener::requestAdd(const LLSD& event_data) const +{ + if(event_data.has("reply")) + { + mNotifications.add(event_data["name"], + event_data["substitutions"], + event_data["payload"], + boost::bind(&LLNotificationsListener::NotificationResponder, + this, + event_data["reply"].asString(), + _1, _2 + ) + ); + } + else + { + mNotifications.add(event_data["name"], + event_data["substitutions"], + event_data["payload"]); + } +} + +void LLNotificationsListener::NotificationResponder(const std::string& reply_pump, + const LLSD& notification, + const LLSD& response) const +{ + LLSD reponse_event; + reponse_event["notification"] = notification; + reponse_event["response"] = response; + LLEventPumps::getInstance()->obtain(reply_pump).post(reponse_event); +} +/* +void LLNotificationsListener::listChannels(const LLSD& params) const +{ + LLReqID reqID(params); + LLSD response(reqID.makeResponse()); + for (LLNotifications:: + + + + for (LLNotifications::ChannelMap::const_iterator cmi(mNotifications.mChannels.begin()), + cmend(mNotifications.mChannels.end()); + cmi != cmend; ++cmi) + { + LLSD channelInfo; + channelInfo["parent"] = cmi->second->getParentChannelName(); + response[cmi->first] = channelInfo; + } + LLEventPumps::instance().obtain(params["reply"]).post(response); +} +*/ +void LLNotificationsListener::listChannelNotifications(const LLSD& params) const +{ + LLReqID reqID(params); + LLSD response(reqID.makeResponse()); + LLNotificationChannelPtr channel(mNotifications.getChannel(params["channel"])); + if (channel) + { + LLSD notifications(LLSD::emptyArray()); + for (LLNotificationChannel::Iterator ni(channel->begin()), nend(channel->end()); + ni != nend; ++ni) + { + notifications.append(asLLSD(*ni)); + } + response["notifications"] = notifications; + } + LLEventPumps::instance().obtain(params["reply"]).post(response); +} + +void LLNotificationsListener::respond(const LLSD& params) const +{ + LLNotificationPtr notification(mNotifications.find(params["uuid"])); + if (notification) + { + notification->respond(params["response"]); + } +} + +void LLNotificationsListener::cancel(const LLSD& params) const +{ + LLNotificationPtr notification(mNotifications.find(params["uuid"])); + if (notification) + { + mNotifications.cancel(notification); + } +} + +void LLNotificationsListener::ignore(const LLSD& params) const +{ + // Calling a method named "ignore", but omitting its "ignore" Boolean + // argument, should by default cause something to be ignored. Explicitly + // pass ["ignore"] = false to cancel ignore. + bool ignore = true; + if (params.has("ignore")) + { + ignore = params["ignore"].asBoolean(); + } + // This method can be used to affect either a single notification name or + // all future notifications. The two use substantially different mechanisms. + if (params["name"].isDefined()) + { + // ["name"] was passed: ignore just that notification + LLNotificationTemplatePtr templatep = mNotifications.getTemplate(params["name"]); + if (templatep) + { + templatep->mForm->setIgnored(ignore); + } + } + else + { + // no ["name"]: ignore all future notifications + mNotifications.setIgnoreAllNotifications(ignore); + } +} + +class LLNotificationsListener::Forwarder: public LLEventTrackable +{ + LOG_CLASS(LLNotificationsListener::Forwarder); +public: + Forwarder(LLNotifications& llnotifications, const std::string& channel): + mNotifications(llnotifications), + mRespond(false) + { + // Connect to the specified channel on construction. Because + // LLEventTrackable is a base, we should automatically disconnect when + // destroyed. + LLNotificationChannelPtr channelptr(llnotifications.getChannel(channel)); + if (channelptr) + { + // Insert our processing as a "passed filter" listener. This way + // we get to run before all the "changed" listeners, and we get to + // swipe it (hide it from the other listeners) if desired. + channelptr->connectPassedFilter(boost::bind(&Forwarder::handle, this, _1)); + } + } + + void setPumpName(const std::string& name) { mPumpName = name; } + void setTypes(const LLSD& types) { mTypes = types; } + void setRespond(bool respond) { mRespond = respond; } + +private: + bool handle(const LLSD& notification) const; + bool matchType(const LLSD& filter, const std::string& type) const; + + LLNotifications& mNotifications; + std::string mPumpName; + LLSD mTypes; + bool mRespond; +}; + +void LLNotificationsListener::forward(const LLSD& params) +{ + std::string channel(params["channel"]); + // First decide whether we're supposed to start forwarding or stop it. + // Default to true. + bool forward = true; + if (params.has("forward")) + { + forward = params["forward"].asBoolean(); + } + if (! forward) + { + // This is a request to stop forwarding notifications on the specified + // channel. The rest of the params don't matter. + // Because mForwarders contains scoped_ptrs, erasing the map entry + // DOES delete the heap Forwarder object. Because Forwarder derives + // from LLEventTrackable, destroying it disconnects it from the + // channel. + mForwarders.erase(channel); + return; + } + // From here on, we know we're being asked to start (or modify) forwarding + // on the specified channel. Find or create an appropriate Forwarder. + ForwarderMap::iterator + entry(mForwarders.insert(ForwarderMap::value_type(channel, ForwarderMap::mapped_type())).first); + if (! entry->second) + { + entry->second.reset(new Forwarder(mNotifications, channel)); + } + // Now, whether this Forwarder is brand-new or not, update it with the new + // request info. + Forwarder& fwd(*entry->second); + fwd.setPumpName(params["pump"]); + fwd.setTypes(params["types"]); + fwd.setRespond(params["respond"]); +} + +bool LLNotificationsListener::Forwarder::handle(const LLSD& notification) const +{ + LL_INFOS("LLNotificationsListener") << "handle(" << notification << ")" << LL_ENDL; + if (notification["sigtype"].asString() == "delete") + { + LL_INFOS("LLNotificationsListener") << "ignoring delete" << LL_ENDL; + // let other listeners see the "delete" operation + return false; + } + LLNotificationPtr note(mNotifications.find(notification["id"])); + if (! note) + { + LL_INFOS("LLNotificationsListener") << notification["id"] << " not found" << LL_ENDL; + return false; + } + if (! matchType(mTypes, note->getType())) + { + LL_INFOS("LLNotificationsListener") << "didn't match types " << mTypes << LL_ENDL; + // We're not supposed to intercept this particular notification. Let + // other listeners process it. + return false; + } + LL_INFOS("LLNotificationsListener") << "sending via '" << mPumpName << "'" << LL_ENDL; + // This is a notification we care about. Forward it through specified + // LLEventPump. + LLEventPumps::instance().obtain(mPumpName).post(asLLSD(note)); + // Are we also being asked to auto-respond? + if (mRespond) + { + LL_INFOS("LLNotificationsListener") << "should respond" << LL_ENDL; + note->respond(LLSD::emptyMap()); + // Did that succeed in removing the notification? Only cancel() if + // it's still around -- otherwise we get an LL_ERRS crash! + note = mNotifications.find(notification["id"]); + if (note) + { + LL_INFOS("LLNotificationsListener") << "respond() didn't clear, canceling" << LL_ENDL; + mNotifications.cancel(note); + } + } + // If we've auto-responded to this notification, then it's going to be + // deleted. Other listeners would get the change operation, try to look it + // up and be baffled by lookup failure. So when we auto-respond, suppress + // this notification: don't pass it to other listeners. + return mRespond; +} + +bool LLNotificationsListener::Forwarder::matchType(const LLSD& filter, const std::string& type) const +{ + // Decide whether this notification matches filter: + // undefined: forward all notifications + if (filter.isUndefined()) + { + return true; + } + // array of string: forward any notification matching any named type + if (filter.isArray()) + { + for (LLSD::array_const_iterator ti(filter.beginArray()), tend(filter.endArray()); + ti != tend; ++ti) + { + if (ti->asString() == type) + { + return true; + } + } + // Didn't match any entry in the array + return false; + } + // string: forward only the specific named type + return (filter.asString() == type); +} + +LLSD LLNotificationsListener::asLLSD(LLNotificationPtr note) +{ + LLSD notificationInfo(note->asLLSD()); + // For some reason the following aren't included in LLNotification::asLLSD(). + notificationInfo["summary"] = note->summarize(); + notificationInfo["id"] = note->id(); + notificationInfo["type"] = note->getType(); + notificationInfo["message"] = note->getMessage(); + notificationInfo["label"] = note->getLabel(); + return notificationInfo; +} diff --git a/indra/llui/llnotificationslistener.h b/indra/llui/llnotificationslistener.h new file mode 100644 index 0000000000..f9f7641de6 --- /dev/null +++ b/indra/llui/llnotificationslistener.h @@ -0,0 +1,69 @@ +/** + * @file llnotificationslistener.h + * @author Brad Kittenbrink + * @date 2009-07-08 + * @brief Wrap subset of LLNotifications API in event API for test scripts. + * + * $LicenseInfo:firstyear=2009&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_LLNOTIFICATIONSLISTENER_H +#define LL_LLNOTIFICATIONSLISTENER_H + +#include "lleventapi.h" +#include "llnotificationptr.h" +#include <boost/shared_ptr.hpp> +#include <map> +#include <string> + +class LLNotifications; +class LLSD; + +class LLNotificationsListener : public LLEventAPI +{ +public: + LLNotificationsListener(LLNotifications & notifications); + ~LLNotificationsListener(); + +private: + void requestAdd(LLSD const & event_data) const; + + void NotificationResponder(const std::string& replypump, + const LLSD& notification, + const LLSD& response) const; + + void listChannels(const LLSD& params) const; + void listChannelNotifications(const LLSD& params) const; + void respond(const LLSD& params) const; + void cancel(const LLSD& params) const; + void ignore(const LLSD& params) const; + void forward(const LLSD& params); + + static LLSD asLLSD(LLNotificationPtr); + + class Forwarder; + typedef std::map<std::string, boost::shared_ptr<Forwarder> > ForwarderMap; + ForwarderMap mForwarders; + LLNotifications & mNotifications; +}; + +#endif // LL_LLNOTIFICATIONSLISTENER_H diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp index fd98155704..76ba53ec32 100755 --- a/indra/llui/lltabcontainer.cpp +++ b/indra/llui/lltabcontainer.cpp @@ -27,7 +27,7 @@ #include "linden_common.h" #include "lltabcontainer.h" - +#include "llviewereventrecorder.h" #include "llfocusmgr.h" #include "lllocalcliprect.h" #include "llrect.h" @@ -578,6 +578,11 @@ BOOL LLTabContainer::handleMouseDown( S32 x, S32 y, MASK mask ) tab_button->setFocus(TRUE); } } + if (handled) { + // Note: May need to also capture local coords right here ? + LLViewerEventRecorder::instance().update_xui(getPathname( )); + } + return handled; } @@ -629,30 +634,33 @@ BOOL LLTabContainer::handleMouseUp( S32 x, S32 y, MASK mask ) BOOL handled = FALSE; BOOL has_scroll_arrows = (getMaxScrollPos() > 0) && !getTabsHidden(); + S32 local_x = x - getRect().mLeft; + S32 local_y = y - getRect().mBottom; + if (has_scroll_arrows) { if (mJumpPrevArrowBtn && mJumpPrevArrowBtn->getRect().pointInRect(x, y)) { - S32 local_x = x - mJumpPrevArrowBtn->getRect().mLeft; - S32 local_y = y - mJumpPrevArrowBtn->getRect().mBottom; + local_x = x - mJumpPrevArrowBtn->getRect().mLeft; + local_y = y - mJumpPrevArrowBtn->getRect().mBottom; handled = mJumpPrevArrowBtn->handleMouseUp(local_x, local_y, mask); } else if (mJumpNextArrowBtn && mJumpNextArrowBtn->getRect().pointInRect(x, y)) { - S32 local_x = x - mJumpNextArrowBtn->getRect().mLeft; - S32 local_y = y - mJumpNextArrowBtn->getRect().mBottom; + local_x = x - mJumpNextArrowBtn->getRect().mLeft; + local_y = y - mJumpNextArrowBtn->getRect().mBottom; handled = mJumpNextArrowBtn->handleMouseUp(local_x, local_y, mask); } else if (mPrevArrowBtn && mPrevArrowBtn->getRect().pointInRect(x, y)) { - S32 local_x = x - mPrevArrowBtn->getRect().mLeft; - S32 local_y = y - mPrevArrowBtn->getRect().mBottom; + local_x = x - mPrevArrowBtn->getRect().mLeft; + local_y = y - mPrevArrowBtn->getRect().mBottom; handled = mPrevArrowBtn->handleMouseUp(local_x, local_y, mask); } else if (mNextArrowBtn && mNextArrowBtn->getRect().pointInRect(x, y)) { - S32 local_x = x - mNextArrowBtn->getRect().mLeft; - S32 local_y = y - mNextArrowBtn->getRect().mBottom; + local_x = x - mNextArrowBtn->getRect().mLeft; + local_y = y - mNextArrowBtn->getRect().mBottom; handled = mNextArrowBtn->handleMouseUp(local_x, local_y, mask); } } @@ -676,6 +684,10 @@ BOOL LLTabContainer::handleMouseUp( S32 x, S32 y, MASK mask ) } gFocusMgr.setMouseCapture(NULL); } + if (handled) { + // Note: may need to capture local coords here + LLViewerEventRecorder::instance().update_xui(getPathname( )); + } return handled; } @@ -1059,21 +1071,21 @@ void LLTabContainer::addTabPanel(const TabPanelParams& panel) if (mIsVertical) { - p.name(std::string("vert tab button")); - p.image_unselected(mMiddleTabParams.tab_left_image_unselected); - p.image_selected(mMiddleTabParams.tab_left_image_selected); - p.follows.flags = p.follows.flags() | FOLLOWS_TOP; + p.name("vtab_"+std::string(child->getName())); + p.image_unselected(mMiddleTabParams.tab_left_image_unselected); + p.image_selected(mMiddleTabParams.tab_left_image_selected); + p.follows.flags = p.follows.flags() | FOLLOWS_TOP; } else - { - p.name(std::string(child->getName()) + " tab"); - p.visible(false); - p.image_unselected(tab_img); - p.image_selected(tab_selected_img); - p.follows.flags = p.follows.flags() | (getTabPosition() == TOP ? FOLLOWS_TOP : FOLLOWS_BOTTOM); - // Try to squeeze in a bit more text - p.pad_left( mLabelPadLeft ); - p.pad_right(2); + { + p.name("htab_"+std::string(child->getName())); + p.visible(false); + p.image_unselected(tab_img); + p.image_selected(tab_selected_img); + p.follows.flags = p.follows.flags() | (getTabPosition() == TOP ? FOLLOWS_TOP : FOLLOWS_BOTTOM); + // Try to squeeze in a bit more text + p.pad_left( mLabelPadLeft ); + p.pad_right(2); } // *TODO : It seems wrong not to use p in both cases considering the way p is initialized diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index b9c843e931..1722bf27bd 100755 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -29,7 +29,7 @@ #define LLUICTRL_CPP #include "lluictrl.h" - +#include "llviewereventrecorder.h" #include "llfocusmgr.h" #include "llpanel.h" #include "lluictrlfactory.h" @@ -308,22 +308,40 @@ void LLUICtrl::onMouseLeave(S32 x, S32 y, MASK mask) //virtual BOOL LLUICtrl::handleMouseDown(S32 x, S32 y, MASK mask) { + + lldebugs << "LLUICtrl::handleMouseDown calling LLView)'s handleMouseUp (first initialized xui to: " << getPathname() << " )" << llendl; + BOOL handled = LLView::handleMouseDown(x,y,mask); + if (mMouseDownSignal) { (*mMouseDownSignal)(this,x,y,mask); } + lldebugs << "LLUICtrl::handleMousedown - handled is returning as: " << handled << " " << llendl; + + if (handled) { + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-56,-56,getPathname()); + } return handled; } //virtual BOOL LLUICtrl::handleMouseUp(S32 x, S32 y, MASK mask) { + + lldebugs << "LLUICtrl::handleMouseUp calling LLView)'s handleMouseUp (first initialized xui to: " << getPathname() << " )" << llendl; + BOOL handled = LLView::handleMouseUp(x,y,mask); + if (handled) { + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-56,-56,getPathname()); + } if (mMouseUpSignal) { (*mMouseUpSignal)(this,x,y,mask); } + + lldebugs << "LLUICtrl::handleMouseUp - handled for xui " << getPathname() << " - is returning as: " << handled << " " << llendl; + return handled; } diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index 3613a40e2c..20015dca1a 100755 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -48,7 +48,9 @@ #include "lluictrlfactory.h" #include "lltooltip.h" #include "llsdutil.h" - +#include "llsdserialize.h" +#include "llviewereventrecorder.h" +#include "llkeyboard.h" // for ui edit hack #include "llbutton.h" #include "lllineeditor.h" @@ -642,13 +644,27 @@ void LLView::setVisible(BOOL visible) // virtual void LLView::handleVisibilityChange ( BOOL new_visibility ) { + BOOL old_visibility; BOOST_FOREACH(LLView* viewp, mChildList) { // only views that are themselves visible will have their overall visibility affected by their ancestors - if (viewp->getVisible()) + old_visibility=viewp->getVisible(); + + if (old_visibility!=new_visibility) + { + LLViewerEventRecorder::instance().logVisibilityChange( viewp->getPathname(), viewp->getName(), new_visibility,"widget"); + } + + if (old_visibility) { viewp->handleVisibilityChange ( new_visibility ); } + + // Consider changing returns to confirm success and know which widget grabbed it + // For now assume success and log at highest xui possible + // NOTE we log actual state - which may differ if it somehow failed to set visibility + lldebugs << "LLView::handleVisibilityChange - now: " << getVisible() << " xui: " << viewp->getPathname() << " name: " << viewp->getName() << llendl; + } } @@ -697,6 +713,7 @@ bool LLView::visibleEnabledAndContains(S32 local_x, S32 local_y) && getEnabled(); } +// This is NOT event recording related void LLView::logMouseEvent() { if (sDebugMouseHandling) @@ -743,7 +760,14 @@ LLView* LLView::childrenHandleMouseEvent(const METHOD& method, S32 x, S32 y, XDA if ((viewp->*method)( local_x, local_y, extra ) || (allow_mouse_block && viewp->blockMouseEvent( local_x, local_y ))) { + lldebugs << "LLView::childrenHandleMouseEvent calling updatemouseeventinfo - local_x|global x "<< local_x << " " << x << "local/global y " << local_y << " " << y << llendl; + lldebugs << "LLView::childrenHandleMouseEvent getPathname for viewp result: " << viewp->getPathname() << "for this view: " << getPathname() << llendl; + + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); + + // This is NOT event recording related viewp->logMouseEvent(); + return viewp; } } @@ -766,6 +790,7 @@ LLView* LLView::childrenHandleToolTip(S32 x, S32 y, MASK mask) if (viewp->handleToolTip(local_x, local_y, mask) || viewp->blockMouseEvent(local_x, local_y)) { + // This is NOT event recording related viewp->logMouseEvent(); return viewp; } @@ -824,6 +849,7 @@ LLView* LLView::childrenHandleHover(S32 x, S32 y, MASK mask) if (viewp->handleHover(local_x, local_y, mask) || viewp->blockMouseEvent(local_x, local_y)) { + // This is NOT event recording related viewp->logMouseEvent(); return viewp; } @@ -907,10 +933,12 @@ BOOL LLView::handleKey(KEY key, MASK mask, BOOL called_from_parent) if (!handled) { + // For event logging we don't care which widget handles it + // So we capture the key at the end of this function once we know if it was handled handled = handleKeyHere( key, mask ); - if (handled && LLView::sDebugKeys) + if (handled) { - llinfos << "Key handled by " << getName() << llendl; + llwarns << "Key handled by " << getName() << llendl; } } } @@ -958,6 +986,11 @@ BOOL LLView::handleUnicodeChar(llwchar uni_char, BOOL called_from_parent) handled = mParentView->handleUnicodeChar(uni_char, FALSE); } + if (handled) + { + LLViewerEventRecorder::instance().logKeyUnicodeEvent(uni_char); + } + return handled; } @@ -987,12 +1020,16 @@ BOOL LLView::hasMouseCapture() BOOL LLView::handleMouseUp(S32 x, S32 y, MASK mask) { - return childrenHandleMouseUp( x, y, mask ) != NULL; + LLView* r = childrenHandleMouseUp( x, y, mask ); + + return (r!=NULL); } BOOL LLView::handleMouseDown(S32 x, S32 y, MASK mask) { - return childrenHandleMouseDown( x, y, mask ) != NULL; + LLView* r= childrenHandleMouseDown(x, y, mask ); + + return (r!=NULL); } BOOL LLView::handleDoubleClick(S32 x, S32 y, MASK mask) @@ -1065,7 +1102,7 @@ LLView* LLView::childrenHandleDoubleClick(S32 x, S32 y, MASK mask) LLView* LLView::childrenHandleMouseUp(S32 x, S32 y, MASK mask) { - return childrenHandleMouseEvent(&LLView::handleMouseUp, x, y, mask); + return childrenHandleMouseEvent(&LLView::handleMouseUp, x, y, mask); } LLView* LLView::childrenHandleRightMouseUp(S32 x, S32 y, MASK mask) diff --git a/indra/llui/llviewereventrecorder.cpp b/indra/llui/llviewereventrecorder.cpp new file mode 100644 index 0000000000..a352f621eb --- /dev/null +++ b/indra/llui/llviewereventrecorder.cpp @@ -0,0 +1,296 @@ +/** + * @file llviewereventrecorder.cpp + * @brief Viewer event recording and playback support for mouse and keyboard events + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * + * Copyright (c) 2013, 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 "llviewereventrecorder.h" +#include "llui.h" +#include "llleap.h" + +LLViewerEventRecorder::LLViewerEventRecorder() { + + clear(UNDEFINED); + + // Remove any previous event log file + std::string old_log_ui_events_to_llsd_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLife_Events_log.old"); + LLFile::remove(old_log_ui_events_to_llsd_file); + + + mLogFilename = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLife_Events_log.llsd"); + LLFile::rename(mLogFilename, old_log_ui_events_to_llsd_file); + +} + + +bool LLViewerEventRecorder::displayViewerEventRecorderMenuItems() { + return LLUI::sSettingGroups["config"]->getBOOL("ShowEventRecorderMenuItems"); +} + + +void LLViewerEventRecorder::setEventLoggingOn() { + if (! mLog.is_open()) { + mLog.open(mLogFilename, llofstream::out); + } + logEvents=true; + lldebugs << "LLViewerEventRecorder::setEventLoggingOn event logging turned on" << llendl; +} + +void LLViewerEventRecorder::setEventLoggingOff() { + logEvents=false; + mLog.flush(); + mLog.close(); + lldebugs << "LLViewerEventRecorder::setEventLoggingOff event logging turned off" << llendl; +} + + + LLViewerEventRecorder::~LLViewerEventRecorder() { + if (mLog.is_open()) { + mLog.close(); + } +} + +void LLViewerEventRecorder::clear_xui() { + xui.clear(); +} + +void LLViewerEventRecorder::clear(S32 r) { + + xui.clear(); + + local_x=r; + local_y=r; + + global_x=r; + global_y=r; + + +} + +void LLViewerEventRecorder::setMouseLocalCoords(S32 x, S32 y) { + local_x=x; + local_y=y; +} + +void LLViewerEventRecorder::setMouseGlobalCoords(S32 x, S32 y) { + global_x=x; + global_y=y; +} + +void LLViewerEventRecorder::updateMouseEventInfo(S32 local_x, S32 local_y, S32 global_x, S32 global_y, std::string mName) { + + LLView * target_view = LLUI::resolvePath(LLUI::getRootView(), xui); + if (! target_view) { + lldebugs << "LLViewerEventRecorder::updateMouseEventInfo - xui path on file at moment is NOT valid - so DO NOT record these local coords" << llendl; + return; + } + lldebugs << "LLViewerEventRecorder::updateMouseEventInfo b4 updatemouseeventinfo - local_x|global x "<< this->local_x << " " << this->global_x << "local/global y " << this->local_y << " " << this->global_y << " mname: " << mName << " xui: " << xui << llendl; + + + if (this->local_x < 1 && this->local_y<1 && local_x && local_y) { + this->local_x=local_x; + this->local_y=local_y; + } + this->global_x=global_x; + this->global_y=global_y; + + // ONLY record deepest xui path for hierarchy searches - or first/only xui for floaters/panels reached via mouse captor - and llmousehandler + if (mName!="" && mName!="/" && xui=="") { + // xui=std::string("/")+mName+xui; + //xui=mName+xui; + xui = mName; // TODO review confirm we never call with partial path - also cAN REMOVE CHECK FOR "" - ON OTHER HAND IT'S PRETTY HARMLESS + } + + lldebugs << "LLViewerEventRecorder::updateMouseEventInfo after updatemouseeventinfo - local_x|global x "<< this->local_x << " " << this->global_x << "local/global y " << this->local_y << " " << this->global_y << " mname: " << mName << " xui: " << xui << llendl; +} + +void LLViewerEventRecorder::logVisibilityChange(std::string xui, std::string name, BOOL visibility, std::string event_subtype) { + + LLSD event=LLSD::emptyMap(); + + event.insert("event",LLSD(std::string("visibility"))); + + if (visibility) { + event.insert("visibility",LLSD(true)); + } else { + event.insert("visibility",LLSD(false)); + } + + if (event_subtype!="") { + event.insert("event_subtype", LLSD(event_subtype)); + } + + if(name!="") { + event.insert("name",LLSD(name)); + } + + if (xui!="") { + event.insert("path",LLSD(xui)); + } + + event.insert("timestamp",LLSD(LLDate::now().asString())); + recordEvent(event); +} + + +std::string LLViewerEventRecorder::get_xui() { + return xui; +} +void LLViewerEventRecorder::update_xui(std::string xui) { + if (xui!="" && this->xui=="" ) { + lldebugs << "LLViewerEventRecorder::update_xui to " << xui << llendl; + this->xui=xui; + } else { + lldebugs << "LLViewerEventRecorder::update_xui called with empty string" << llendl; + } +} + +void LLViewerEventRecorder::logKeyEvent(KEY key, MASK mask) { + + // NOTE: Event recording only logs keydown events - the viewer itself hides keyup events at a fairly low level in the code and does not appear to care about them anywhere + + LLSD event = LLSD::emptyMap(); + + event.insert("event",LLSD("type")); + + // keysym ...or + // keycode...or + // char + event.insert("keysym",LLSD(LLKeyboard::stringFromKey(key))); + + // path (optional) - for now we are not recording path for key events during record - should not be needed for full record and playback of recorded steps + // as a vita script - it does become useful if you edit the resulting vita script and wish to remove some steps leading to a key event - that sort of edit might + // break the test script and it would be useful to have more context to make these sorts of edits safer + + // TODO replace this with a call which extracts to an array of names of masks (just like vita expects during playback) + // This is looking more and more like an object is a good idea, for this part a handy method call to setMask(mask) would be nice :-) + // call the func - llkeyboard::llsdStringarrayFromMask + + LLSD key_mask=LLSD::emptyArray(); + + if (mask & MASK_CONTROL) { key_mask.append(LLSD("CTL")); } // Mac command key - has code of 0x1 in llcommon/indra_contstants + if (mask & MASK_ALT) { key_mask.append(LLSD("ALT")); } + if (mask & MASK_SHIFT) { key_mask.append(LLSD("SHIFT")); } + if (mask & MASK_MAC_CONTROL) { key_mask.append(LLSD("MAC_CONTROL")); } + + event.insert("mask",key_mask); + event.insert("timestamp",LLSD(LLDate::now().asString())); + + // Although vita has keyDown and keyUp requests it does not have type as a high-level concept + // (maybe it should) - instead it has a convenience method that generates the keydown and keyup events + // Here we will use "type" as our event type + + lldebugs << "LLVIewerEventRecorder::logKeyEvent Serialized LLSD for event " << event.asString() << "\n" << llendl; + + + //lldebugs << "[VITA] key_name: " << LLKeyboard::stringFromKey(key) << "mask: "<< mask << "handled by " << getName() << llendl; + lldebugs << "LLVIewerEventRecorder::logKeyEvent key_name: " << LLKeyboard::stringFromKey(key) << "mask: "<< mask << llendl; + + + recordEvent(event); + +} + +void LLViewerEventRecorder::playbackRecording() { + + LLSD LeapCommand; + + // ivita sets this on startup, it also sends commands to the viewer to make start, stop, and playback menu items visible in viewer + LeapCommand =LLUI::sSettingGroups["config"]->getLLSD("LeapPlaybackEventsCommand"); + + lldebugs << "[VITA] launching playback - leap command is: " << LLSDXMLStreamer(LeapCommand) << llendl; + LLLeap::create("", LeapCommand, false); // exception=false + +} + + +void LLViewerEventRecorder::recordEvent(LLSD event) { + lldebugs << "LLViewerEventRecorder::recordEvent event written to log: " << LLSDXMLStreamer(event) << llendl; + mLog << event << std::endl; + +} +void LLViewerEventRecorder::logKeyUnicodeEvent(llwchar uni_char) { + if (! logEvents) return; + + // Note: keyUp is not captured since the viewer seems to not care about keyUp events + + LLSD event=LLSD::emptyMap(); + + event.insert("timestamp",LLSD(LLDate::now().asString())); + + + // keysym ...or + // keycode...or + // char + + lldebugs << "Wrapped in conversion to wstring " << wstring_to_utf8str(LLWString( 1, uni_char)) << "\n" << llendl; + + event.insert("char", + LLSD( wstring_to_utf8str(LLWString( 1,uni_char)) ) + ); + + // path (optional) - for now we are not recording path for key events during record - should not be needed for full record and playback of recorded steps + // as a vita script - it does become useful if you edit the resulting vita script and wish to remove some steps leading to a key event - that sort of edit might + // break the test script and it would be useful to have more context to make these sorts of edits safer + + // TODO need to consider mask keys too? Doesn't seem possible - at least not easily at this point + + event.insert("event",LLSD("keyDown")); + + lldebugs << "[VITA] unicode key: " << uni_char << llendl; + lldebugs << "[VITA] dumpxml " << LLSDXMLStreamer(event) << "\n" << llendl; + + + recordEvent(event); + +} + +void LLViewerEventRecorder::logMouseEvent(std::string button_state,std::string button_name) +{ + if (! logEvents) return; + + LLSD event=LLSD::emptyMap(); + + event.insert("event",LLSD(std::string("mouse"+ button_state))); + event.insert("button",LLSD(button_name)); + if (xui!="") { + event.insert("path",LLSD(xui)); + } + + if (local_x>0 && local_y>0) { + event.insert("local_x",LLSD(local_x)); + event.insert("local_y",LLSD(local_y)); + } + + if (global_x>0 && global_y>0) { + event.insert("global_x",LLSD(global_x)); + event.insert("global_y",LLSD(global_y)); + } + event.insert("timestamp",LLSD(LLDate::now().asString())); + recordEvent(event); + + + clear(UNDEFINED); + + +} diff --git a/indra/llui/llviewereventrecorder.h b/indra/llui/llviewereventrecorder.h new file mode 100644 index 0000000000..72ca643ced --- /dev/null +++ b/indra/llui/llviewereventrecorder.h @@ -0,0 +1,103 @@ +/** + * @file llviewereventrecorder.h + * @brief Viewer event recording and playback support for mouse and keyboard events + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * + * Copyright (c) 2013, 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_VIEWER_EVENT_RECORDER +#define LL_VIEWER_EVENT_RECORDER + + +#include "linden_common.h" + +#include "lldir.h" +#include "llsd.h" +#include "llfile.h" +#include "llvfile.h" +#include "lldate.h" +#include "llsdserialize.h" +#include "llkeyboard.h" +#include "llstring.h" + +#include <sstream> + +#include "llsingleton.h" // includes llerror which we need here so we can skip the include here + +class LLViewerEventRecorder : public LLSingleton<LLViewerEventRecorder> +{ + + public: + + LLViewerEventRecorder(); // TODO Protect constructor better if we can (not happy in private section) - could add a factory... - we are singleton + ~LLViewerEventRecorder(); + + + void updateMouseEventInfo(S32 local_x,S32 local_y, S32 global_x, S32 global_y, std::string mName); + void setMouseLocalCoords(S32 x,S32 y); + void setMouseGlobalCoords(S32 x,S32 y); + + void logMouseEvent(std::string button_state, std::string button_name ); + void logKeyEvent(KEY key, MASK mask); + void logKeyUnicodeEvent(llwchar uni_char); + + void logVisibilityChange(std::string xui, std::string name, BOOL visibility, std::string event_subtype); + + void clear_xui(); + std::string get_xui(); + void update_xui(std::string xui); + + bool getLoggingStatus(); + void setEventLoggingOn(); + void setEventLoggingOff(); + + void playbackRecording(); + + bool displayViewerEventRecorderMenuItems(); + + + protected: + // On if we wish to log events at the moment - toggle via Develop/Recorder submenu + bool logEvents; + + std::string mLogFilename; + llofstream mLog; + + + private: + + // Mouse event info + S32 global_x; + S32 global_y; + S32 local_x; + S32 local_y; + + // XUI path of UI element + std::string xui; + + // Actually write the event out to llsd log file + void recordEvent(LLSD event); + + void clear(S32 r); + + static const S32 UNDEFINED=-1; +}; +#endif diff --git a/indra/llwindow/llwindowmacosx-objc.h b/indra/llwindow/llwindowmacosx-objc.h index 2cae6f3f72..81b25601a9 100755 --- a/indra/llwindow/llwindowmacosx-objc.h +++ b/indra/llwindow/llwindowmacosx-objc.h @@ -25,6 +25,9 @@ * $/LicenseInfo$ */ +#ifndef LL_LLWINDOWMACOSX_OBJC_H +#define LL_LLWINDOWMACOSX_OBJC_H + #include <map> #include <vector> @@ -144,3 +147,5 @@ NSWindowRef getMainAppWindow(); GLViewRef getGLView(); unsigned int getModifiers(); + +#endif // LL_LLWINDOWMACOSX_OBJC_H diff --git a/indra/llwindow/llwindowmacosx.cpp b/indra/llwindow/llwindowmacosx.cpp index 56472e6b45..d5b62bd3a7 100755 --- a/indra/llwindow/llwindowmacosx.cpp +++ b/indra/llwindow/llwindowmacosx.cpp @@ -1300,6 +1300,8 @@ void LLWindowMacOSX::setupFailure(const std::string& text, const std::string& ca OSMessageBox(text, caption, type); } + // Note on event recording - QUIT is a known special case and we are choosing NOT to record it for the record and playback feature + // it is handled at a very low-level const char* cursorIDToName(int id) { switch (id) diff --git a/indra/llxml/llcontrol.cpp b/indra/llxml/llcontrol.cpp index 16f2290787..97fad7feb0 100755 --- a/indra/llxml/llcontrol.cpp +++ b/indra/llxml/llcontrol.cpp @@ -391,7 +391,7 @@ LLControlVariable* LLControlGroup::declareControl(const std::string& name, eCont } else { - llwarns << "Control named " << name << " already exists, ignoring new declaration." << llendl; + LL_WARNS("Settings") << "Control named " << name << " already exists, ignoring new declaration." << LL_ENDL; } return existing_control; } @@ -630,14 +630,14 @@ U32 LLControlGroup::loadFromFileLegacy(const std::string& filename, BOOL require if (!xml_controls.parseFile(filename)) { - llwarns << "Unable to open control file " << filename << llendl; + LL_WARNS("Settings") << "Unable to open control file " << filename << LL_ENDL; return 0; } LLXmlTreeNode* rootp = xml_controls.getRoot(); if (!rootp || !rootp->hasAttribute("version")) { - llwarns << "No valid settings header found in control file " << filename << llendl; + LL_WARNS("Settings") << "No valid settings header found in control file " << filename << LL_ENDL; return 0; } @@ -650,7 +650,7 @@ U32 LLControlGroup::loadFromFileLegacy(const std::string& filename, BOOL require // Check file version if (version != CURRENT_VERSION) { - llinfos << filename << " does not appear to be a version " << CURRENT_VERSION << " controls file" << llendl; + LL_INFOS("Settings") << filename << " does not appear to be a version " << CURRENT_VERSION << " controls file" << LL_ENDL; return 0; } @@ -668,7 +668,7 @@ U32 LLControlGroup::loadFromFileLegacy(const std::string& filename, BOOL require if (!name.empty()) { //read in to end of line - llwarns << "LLControlGroup::loadFromFile() : Trying to set \"" << name << "\", setting doesn't exist." << llendl; + LL_WARNS("Settings") << "LLControlGroup::loadFromFile() : Trying to set \"" << name << "\", setting doesn't exist." << LL_ENDL; } child_nodep = rootp->getNextChild(); continue; @@ -822,7 +822,7 @@ U32 LLControlGroup::saveToFile(const std::string& filename, BOOL nondefault_only LLControlVariable* control = iter->second; if (!control) { - llwarns << "Tried to save invalid control: " << iter->first << llendl; + LL_WARNS("Settings") << "Tried to save invalid control: " << iter->first << LL_ENDL; } else if( control->shouldSave(nondefault_only) ) { @@ -838,12 +838,12 @@ U32 LLControlGroup::saveToFile(const std::string& filename, BOOL nondefault_only { LLSDSerialize::toPrettyXML(settings, file); file.close(); - llinfos << "Saved to " << filename << llendl; + LL_INFOS("Settings") << "Saved to " << filename << LL_ENDL; } else { // This is a warning because sometime we want to use settings files which can't be written... - llwarns << "Unable to open settings file: " << filename << llendl; + LL_WARNS("Settings") << "Unable to open settings file: " << filename << LL_ENDL; return 0; } return num_saved; @@ -856,14 +856,14 @@ U32 LLControlGroup::loadFromFile(const std::string& filename, bool set_default_v infile.open(filename); if(!infile.is_open()) { - llwarns << "Cannot find file " << filename << " to load." << llendl; + LL_WARNS("Settings") << "Cannot find file " << filename << " to load." << LL_ENDL; return 0; } if (LLSDParser::PARSE_FAILURE == LLSDSerialize::fromXML(settings, infile)) { infile.close(); - llwarns << "Unable to parse LLSD control file " << filename << ". Trying Legacy Method." << llendl; + LL_WARNS("Settings") << "Unable to parse LLSD control file " << filename << ". Trying Legacy Method." << LL_ENDL; return loadFromFileLegacy(filename, TRUE, TYPE_STRING); } @@ -976,6 +976,7 @@ U32 LLControlGroup::loadFromFile(const std::string& filename, bool set_default_v ++validitems; } + LL_DEBUGS("Settings") << "Loaded " << validitems << " settings from " << filename << LL_ENDL; return validitems; } @@ -1012,7 +1013,7 @@ void main() BOOL_CONTROL baz; U32 count = gGlobals.loadFromFile("controls.ini"); - llinfos << "Loaded " << count << " controls" << llendl; + LL_INFOS("Settings") << "Loaded " << count << " controls" << LL_ENDL; // test insertion foo = new LLControlVariable<F32>("gFoo", 5.f, 1.f, 20.f); @@ -1273,19 +1274,19 @@ LLColor4 convert_from_llsd<LLColor4>(const LLSD& sd, eControlType type, const st LLColor4 color(sd); if (color.mV[VRED] < 0.f || color.mV[VRED] > 1.f) { - llwarns << "Color " << control_name << " red value out of range: " << color << llendl; + LL_WARNS("Settings") << "Color " << control_name << " red value out of range: " << color << LL_ENDL; } else if (color.mV[VGREEN] < 0.f || color.mV[VGREEN] > 1.f) { - llwarns << "Color " << control_name << " green value out of range: " << color << llendl; + LL_WARNS("Settings") << "Color " << control_name << " green value out of range: " << color << LL_ENDL; } else if (color.mV[VBLUE] < 0.f || color.mV[VBLUE] > 1.f) { - llwarns << "Color " << control_name << " blue value out of range: " << color << llendl; + LL_WARNS("Settings") << "Color " << control_name << " blue value out of range: " << color << LL_ENDL; } else if (color.mV[VALPHA] < 0.f || color.mV[VALPHA] > 1.f) { - llwarns << "Color " << control_name << " alpha value out of range: " << color << llendl; + LL_WARNS("Settings") << "Color " << control_name << " alpha value out of range: " << color << LL_ENDL; } return LLColor4(sd); diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index acb9c57af8..5c9171f209 100755 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -191,6 +191,7 @@ set(viewer_SOURCE_FILES llexpandabletextbox.cpp llexternaleditor.cpp llface.cpp + llfacebookconnect.cpp llfasttimerview.cpp llfavoritesbar.cpp llfeaturemanager.cpp @@ -273,6 +274,7 @@ set(viewer_SOURCE_FILES llfloatersettingsdebug.cpp llfloatersidepanelcontainer.cpp llfloatersnapshot.cpp + llfloatersocial.cpp llfloatersounddevices.cpp llfloaterspellchecksettings.cpp llfloatertelehub.cpp @@ -509,6 +511,7 @@ set(viewer_SOURCE_FILES llsidetraypanelcontainer.cpp llsky.cpp llslurl.cpp + llsnapshotlivepreview.cpp llspatialpartition.cpp llspeakers.cpp llspeakingindicatormanager.cpp @@ -777,6 +780,7 @@ set(viewer_HEADER_FILES llexpandabletextbox.h llexternaleditor.h llface.h + llfacebookconnect.h llfasttimerview.h llfavoritesbar.h llfeaturemanager.h @@ -859,6 +863,7 @@ set(viewer_HEADER_FILES llfloatersettingsdebug.h llfloatersidepanelcontainer.h llfloatersnapshot.h + llfloatersocial.h llfloatersounddevices.h llfloaterspellchecksettings.h llfloatertelehub.h @@ -1084,6 +1089,7 @@ set(viewer_HEADER_FILES llsidetraypanelcontainer.h llsky.h llslurl.h + llsnapshotlivepreview.h llspatialpartition.h llspeakers.h llspeakingindicatormanager.h diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index cff2619cfb..c47e8b5872 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -3.6.9 +3.6.10 diff --git a/indra/newview/app_settings/commands.xml b/indra/newview/app_settings/commands.xml index 4659673333..60c942094a 100755 --- a/indra/newview/app_settings/commands.xml +++ b/indra/newview/app_settings/commands.xml @@ -216,6 +216,16 @@ is_running_function="Floater.IsOpen" is_running_parameters="snapshot" /> + <command name="social" + available_in_toybox="true" + icon="Command_Social_Icon" + label_ref="Command_Social_Label" + tooltip_ref="Command_Social_Tooltip" + execute_function="Floater.ToggleOrBringToFront" + execute_parameters="social" + is_running_function="Floater.IsOpen" + is_running_parameters="social" + /> <command name="speak" available_in_toybox="true" icon="Command_Speak_Icon" diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 44bf682908..8973cda667 100755 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -4941,6 +4941,16 @@ <key>Value</key> <array /> </map> + <key>LeapPlaybackEventsCommand</key> + <map> + <key>Comment</key> + <string>Command line to use leap to launch playback of event recordings</string> + <key>Persist</key> + <integer>0</integer> + <key>Type</key> + <string>LLSD</string> + <key>Value</key> + </map> <key>LSLFindCaseInsensitivity</key> <map> <key>Comment</key> @@ -10444,6 +10454,17 @@ <key>Value</key> <integer>0</integer> </map> + <key>ShowEventRecorderMenuItems</key> + <map> + <key>Comment</key> + <string>Whether or not Event Recorder menu choices - Start / Stop event recording should appear in the (currently) Develop menu</string> + <key>Persist</key> + <integer>0</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>ShowGestureButton</key> <map> <key>Comment</key> @@ -13095,6 +13116,17 @@ <key>Value</key> <integer>0</integer> </map> + <key>SocialPhotoResolution</key> + <map> + <key>Comment</key> + <string>Default resolution when sharing photo using the social floater</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>String</string> + <key>Value</key> + <string>[i800,i600]</string> + </map> <key>sourceid</key> <map> <key>Comment</key> diff --git a/indra/newview/app_settings/toolbars.xml b/indra/newview/app_settings/toolbars.xml index 29c019719d..86f9912815 100755 --- a/indra/newview/app_settings/toolbars.xml +++ b/indra/newview/app_settings/toolbars.xml @@ -6,6 +6,7 @@ <command name="speak"/> <command name="destinations"/> <command name="people"/> + <command name="social"/> <command name="profile"/> <command name="move"/> <command name="view"/> diff --git a/indra/newview/character/avatar_lad.xml b/indra/newview/character/avatar_lad.xml index e28bb6969a..594880a831 100755 --- a/indra/newview/character/avatar_lad.xml +++ b/indra/newview/character/avatar_lad.xml @@ -4371,7 +4371,7 @@ group="1" sex="female" name="Breast_Physics_UpDown_Driven" - wearable="shape" + wearable="physics" edit_group="driven" value_default="0" value_min="-3" @@ -4384,7 +4384,7 @@ group="1" sex="female" name="Breast_Physics_InOut_Driven" - wearable="shape" + wearable="physics" edit_group="driven" value_default="0" value_min="-1.25" @@ -4397,7 +4397,6 @@ group="1" name="Belly_Physics_Torso_UpDown_Driven" wearable="physics" - cross_wearable="true" edit_group="driven" value_default="0" value_min="-1" @@ -4410,7 +4409,6 @@ group="1" name="Breast_Physics_LeftRight_Driven" wearable="physics" - cross_wearable="true" edit_group="driven" value_default="0" value_min="-2" diff --git a/indra/newview/llagentui.cpp b/indra/newview/llagentui.cpp index b9ec304b7e..3410a37890 100755 --- a/indra/newview/llagentui.cpp +++ b/indra/newview/llagentui.cpp @@ -112,6 +112,11 @@ BOOL LLAgentUI::buildLocationString(std::string& str, ELocationFormat fmt,const case LOCATION_FORMAT_NORMAL: buffer = llformat("%s", region_name.c_str()); break; + case LOCATION_FORMAT_NORMAL_COORDS: + buffer = llformat("%s (%d, %d, %d)", + region_name.c_str(), + pos_x, pos_y, pos_z); + break; case LOCATION_FORMAT_NO_COORDS: buffer = llformat("%s%s%s", region_name.c_str(), @@ -143,6 +148,11 @@ BOOL LLAgentUI::buildLocationString(std::string& str, ELocationFormat fmt,const case LOCATION_FORMAT_NORMAL: buffer = llformat("%s, %s", parcel_name.c_str(), region_name.c_str()); break; + case LOCATION_FORMAT_NORMAL_COORDS: + buffer = llformat("%s (%d, %d, %d)", + parcel_name.c_str(), + pos_x, pos_y, pos_z); + break; case LOCATION_FORMAT_NO_MATURITY: buffer = llformat("%s, %s (%d, %d, %d)", parcel_name.c_str(), diff --git a/indra/newview/llagentui.h b/indra/newview/llagentui.h index dda5dc1fd1..bb48dad14c 100755 --- a/indra/newview/llagentui.h +++ b/indra/newview/llagentui.h @@ -35,6 +35,7 @@ public: enum ELocationFormat { LOCATION_FORMAT_NORMAL, // Parcel + LOCATION_FORMAT_NORMAL_COORDS, // Parcel (x, y, z) LOCATION_FORMAT_LANDMARK, // Parcel, Region LOCATION_FORMAT_NO_MATURITY, // Parcel, Region (x, y, z) LOCATION_FORMAT_NO_COORDS, // Parcel, Region - Maturity diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index f67142d1ed..539d186441 100755 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -223,6 +223,10 @@ #include "llmachineid.h" #include "llmainlooprepeater.h" + +#include "llviewereventrecorder.h" + + // *FIX: These extern globals should be cleaned up. // The globals either represent state/config/resource-storage of either // this app, or another 'component' of the viewer. App globals should be @@ -696,6 +700,7 @@ LLAppViewer::LLAppViewer() : LLAppViewer::~LLAppViewer() { delete mSettingsLocationList; + LLViewerEventRecorder::instance().~LLViewerEventRecorder(); LLLoginInstance::instance().setUpdaterService(0); @@ -2250,13 +2255,13 @@ bool LLAppViewer::loadSettingsFromDirectory(const std::string& location_key, BOOST_FOREACH(const SettingsFile& file, group.files) { - llinfos << "Attempting to load settings for the group " << file.name() - << " - from location " << location_key << llendl; + LL_INFOS("Settings") << "Attempting to load settings for the group " << file.name() + << " - from location " << location_key << LL_ENDL; LLControlGroup* settings_group = LLControlGroup::getInstance(file.name); if(!settings_group) { - llwarns << "No matching settings group for name " << file.name() << llendl; + LL_WARNS("Settings") << "No matching settings group for name " << file.name() << LL_ENDL; continue; } @@ -2285,7 +2290,7 @@ bool LLAppViewer::loadSettingsFromDirectory(const std::string& location_key, if(settings_group->loadFromFile(full_settings_path, set_defaults, file.persistent)) { // success! - llinfos << "Loaded settings file " << full_settings_path << llendl; + LL_INFOS("Settings") << "Loaded settings file " << full_settings_path << LL_ENDL; } else { // failed to load @@ -2299,7 +2304,7 @@ bool LLAppViewer::loadSettingsFromDirectory(const std::string& location_key, // only complain if we actually have a filename at this point if (!full_settings_path.empty()) { - llinfos << "Cannot load " << full_settings_path << " - No settings found." << llendl; + LL_INFOS("Settings") << "Cannot load " << full_settings_path << " - No settings found." << LL_ENDL; } } } @@ -2395,8 +2400,6 @@ bool LLAppViewer::initConfiguration() gSavedSettings.setString("ClientSettingsFile", gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, getSettingsFilename("Default", "Global"))); - gSavedSettings.setString("VersionChannelName", LLVersionInfo::getChannel()); - #ifndef LL_RELEASE_FOR_DOWNLOAD // provide developer build only overrides for these control variables that are not // persisted to settings.xml @@ -2460,8 +2463,8 @@ bool LLAppViewer::initConfiguration() gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, clp.getOption("settings")[0]); gSavedSettings.setString("ClientSettingsFile", user_settings_filename); - llinfos << "Using command line specified settings filename: " - << user_settings_filename << llendl; + LL_INFOS("Settings") << "Using command line specified settings filename: " + << user_settings_filename << LL_ENDL; } // - load overrides from user_settings @@ -2477,8 +2480,8 @@ bool LLAppViewer::initConfiguration() { std::string session_settings_filename = clp.getOption("sessionsettings")[0]; gSavedSettings.setString("SessionSettingsFile", session_settings_filename); - llinfos << "Using session settings filename: " - << session_settings_filename << llendl; + LL_INFOS("Settings") << "Using session settings filename: " + << session_settings_filename << LL_ENDL; } loadSettingsFromDirectory("Session"); @@ -2486,8 +2489,8 @@ bool LLAppViewer::initConfiguration() { std::string user_session_settings_filename = clp.getOption("usersessionsettings")[0]; gSavedSettings.setString("UserSessionSettingsFile", user_session_settings_filename); - llinfos << "Using user session settings filename: " - << user_session_settings_filename << llendl; + LL_INFOS("Settings") << "Using user session settings filename: " + << user_session_settings_filename << LL_ENDL; } loadSettingsFromDirectory("UserSession"); @@ -2575,9 +2578,13 @@ bool LLAppViewer::initConfiguration() } } + if (clp.hasOption("logevents")) { + LLViewerEventRecorder::instance().setEventLoggingOn(); + } + std::string CmdLineChannel(gSavedSettings.getString("CmdLineChannel")); if(! CmdLineChannel.empty()) - { + { LLVersionInfo::resetChannel(CmdLineChannel); } @@ -2589,16 +2596,16 @@ bool LLAppViewer::initConfiguration() LLFastTimer::sLog = TRUE; LLFastTimer::sLogName = std::string("performance"); } - + std::string test_name(gSavedSettings.getString("LogMetrics")); if (! test_name.empty()) - { - LLFastTimer::sMetricLog = TRUE ; + { + LLFastTimer::sMetricLog = TRUE ; // '--logmetrics' is specified with a named test metric argument so the data gathering is done only on that test // In the absence of argument, every metric would be gathered (makes for a rather slow run and hard to decipher report...) llinfos << "'--logmetrics' argument : " << test_name << llendl; - LLFastTimer::sLogName = test_name; - } + LLFastTimer::sLogName = test_name; + } if (clp.hasOption("graphicslevel")) { @@ -2607,14 +2614,14 @@ bool LLAppViewer::initConfiguration() // that value for validity. U32 graphicslevel = gSavedSettings.getU32("RenderQualityPerformance"); if (LLFeatureManager::instance().isValidGraphicsLevel(graphicslevel)) - { + { // graphicslevel is valid: save it and engage it later. Capture // the requested value separately from the settings variable // because, if this is the first run, LLViewerWindow's constructor // will call LLFeatureManager::applyRecommendedSettings(), which // overwrites this settings variable! mForceGraphicsLevel = graphicslevel; - } + } } LLFastTimerView::sAnalyzePerformance = gSavedSettings.getBOOL("AnalyzePerformance"); @@ -2645,16 +2652,32 @@ bool LLAppViewer::initConfiguration() // What can happen is that someone can use IE (or potentially // other browsers) and do the rough equivalent of command // injection and steal passwords. Phoenix. SL-55321 + LLSLURL start_slurl; std::string CmdLineLoginLocation(gSavedSettings.getString("CmdLineLoginLocation")); if(! CmdLineLoginLocation.empty()) - { - LLSLURL start_slurl(CmdLineLoginLocation); + { + start_slurl = CmdLineLoginLocation; LLStartUp::setStartSLURL(start_slurl); if(start_slurl.getType() == LLSLURL::LOCATION) { LLGridManager::getInstance()->setGridChoice(start_slurl.getGrid()); + } + } + + //RN: if we received a URL, hand it off to the existing instance. + // don't call anotherInstanceRunning() when doing URL handoff, as + // it relies on checking a marker file which will not work when running + // out of different directories + + if (start_slurl.isValid() && + (gSavedSettings.getBOOL("SLURLPassToOtherInstance"))) + { + if (sendURLToOtherInstance(start_slurl.getSLURLString())) + { + // successfully handed off URL to existing instance, exit + return false; } - } + } const LLControlVariable* skinfolder = gSavedSettings.getControl("SkinCurrent"); if(skinfolder && LLStringUtil::null != skinfolder->getValue().asString()) @@ -2795,7 +2818,7 @@ bool LLAppViewer::initConfiguration() LL_DEBUGS("AppInit")<<"set start from NextLoginLocation: "<<nextLoginLocation<<LL_ENDL; LLStartUp::setStartSLURL(LLSLURL(nextLoginLocation)); } - else if ((clp.hasOption("login") || clp.hasOption("autologin")) + else if ( ( clp.hasOption("login") || clp.hasOption("autologin")) && gSavedSettings.getString("CmdLineLoginLocation").empty()) { // If automatic login from command line with --login switch @@ -3179,7 +3202,7 @@ bool LLAppViewer::initWindow() LLFeatureManager::getInstance()->setGraphicsLevel(*mForceGraphicsLevel, false); gSavedSettings.setU32("RenderQualityPerformance", *mForceGraphicsLevel); } - + // Set this flag in case we crash while initializing GL gSavedSettings.setBOOL("RenderInitError", TRUE); gSavedSettings.saveToFile( gSavedSettings.getString("ClientSettingsFile"), TRUE ); diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 3cf3c739d9..80a80f4298 100755 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -172,21 +172,20 @@ void ll_nvapi_init(NvDRSSessionHandle hSession) nvapi_error(status); return; } + + // (5) Now we apply (or save) our changes to the system + status = NvAPI_DRS_SaveSettings(hSession); + if (status != NVAPI_OK) + { + nvapi_error(status); + return; + } } else if (status != NVAPI_OK) { nvapi_error(status); return; } - - - - // (5) Now we apply (or save) our changes to the system - status = NvAPI_DRS_SaveSettings(hSession); - if (status != NVAPI_OK) - { - nvapi_error(status); - } } //#define DEBUGGING_SEH_FILTER 1 diff --git a/indra/newview/llavataractions.cpp b/indra/newview/llavataractions.cpp index b513a52ff7..70cc48f12b 100755 --- a/indra/newview/llavataractions.cpp +++ b/indra/newview/llavataractions.cpp @@ -73,6 +73,8 @@ #include "llcallingcard.h" #include "llslurl.h" // IDEVO #include "llsidepanelinventory.h" +#include "llavatarname.h" +#include "llagentui.h" // static void LLAvatarActions::requestFriendshipDialog(const LLUUID& id, const std::string& name) @@ -391,6 +393,72 @@ void LLAvatarActions::pay(const LLUUID& id) } } +void LLAvatarActions::teleport_request_callback(const LLSD& notification, const LLSD& response) +{ + S32 option; + if (response.isInteger()) + { + option = response.asInteger(); + } + else + { + option = LLNotificationsUtil::getSelectedOption(notification, response); + } + + if (0 == option) + { + LLMessageSystem* msg = gMessageSystem; + + msg->newMessageFast(_PREHASH_ImprovedInstantMessage); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + + msg->nextBlockFast(_PREHASH_MessageBlock); + msg->addBOOLFast(_PREHASH_FromGroup, FALSE); + msg->addUUIDFast(_PREHASH_ToAgentID, notification["substitutions"]["uuid"] ); + msg->addU8Fast(_PREHASH_Offline, IM_ONLINE); + msg->addU8Fast(_PREHASH_Dialog, IM_TELEPORT_REQUEST); + msg->addUUIDFast(_PREHASH_ID, LLUUID::null); + msg->addU32Fast(_PREHASH_Timestamp, NO_TIMESTAMP); // no timestamp necessary + + std::string name; + LLAgentUI::buildFullname(name); + + msg->addStringFast(_PREHASH_FromAgentName, name); + msg->addStringFast(_PREHASH_Message, response["message"]); + msg->addU32Fast(_PREHASH_ParentEstateID, 0); + msg->addUUIDFast(_PREHASH_RegionID, LLUUID::null); + msg->addVector3Fast(_PREHASH_Position, gAgent.getPositionAgent()); + + gMessageSystem->addBinaryDataFast( + _PREHASH_BinaryBucket, + EMPTY_BINARY_BUCKET, + EMPTY_BINARY_BUCKET_SIZE); + + gAgent.sendReliableMessage(); + } +} + +// static +void LLAvatarActions::teleportRequest(const LLUUID& id) +{ + LLSD notification; + notification["uuid"] = id; + LLAvatarName av_name; + if (!LLAvatarNameCache::get(id, &av_name)) + { + // unlikely ... they just picked this name from somewhere... + LLAvatarNameCache::get(id, boost::bind(&LLAvatarActions::teleportRequest, id)); + return; // reinvoke this when the name resolves + } + notification["NAME"] = av_name.getCompleteName(); + + LLSD payload; + + LLNotificationsUtil::add("TeleportRequestPrompt", notification, payload, teleport_request_callback); +} + // static void LLAvatarActions::kick(const LLUUID& id) { diff --git a/indra/newview/llavataractions.h b/indra/newview/llavataractions.h index 6e1198cd09..403414558e 100755 --- a/indra/newview/llavataractions.h +++ b/indra/newview/llavataractions.h @@ -111,6 +111,12 @@ public: static void pay(const LLUUID& id); /** + * Request teleport from other avatar + */ + static void teleportRequest(const LLUUID& id); + static void teleport_request_callback(const LLSD& notification, const LLSD& response); + + /** * Share items with the avatar. */ static void share(const LLUUID& id); diff --git a/indra/newview/llchiclet.cpp b/indra/newview/llchiclet.cpp index 88884042d4..131aea9da3 100755 --- a/indra/newview/llchiclet.cpp +++ b/indra/newview/llchiclet.cpp @@ -220,18 +220,25 @@ void LLNotificationChiclet::setCounter(S32 counter) bool LLNotificationChiclet::ChicletNotificationChannel::filterNotification( LLNotificationPtr notification ) { - if (notification->getName() == "ScriptDialog") + bool displayNotification; + if ( (notification->getName() == "ScriptDialog") // special case for scripts + // if there is no toast window for the notification, filter it + || (!LLNotificationWellWindow::getInstance()->findItemByID(notification->getID())) + ) { - return false; + displayNotification = false; } - - if( !(notification->canLogToIM() && notification->hasFormElements()) - && (!notification->getPayload().has("give_inventory_notification") - || notification->getPayload()["give_inventory_notification"])) + else if( !(notification->canLogToIM() && notification->hasFormElements()) + && (!notification->getPayload().has("give_inventory_notification") + || notification->getPayload()["give_inventory_notification"])) { - return true; + displayNotification = true; } - return false; + else + { + displayNotification = false; + } + return displayNotification; } ////////////////////////////////////////////////////////////////////////// diff --git a/indra/newview/llconversationloglist.cpp b/indra/newview/llconversationloglist.cpp index 5ab108b39f..44212298cf 100755 --- a/indra/newview/llconversationloglist.cpp +++ b/indra/newview/llconversationloglist.cpp @@ -313,6 +313,10 @@ void LLConversationLogList::onCustomAction(const LLSD& userdata) { LLAvatarActions::offerTeleport(selected_conversation_participant_id); } + else if ("request_teleport" == command_name) + { + LLAvatarActions::teleportRequest(selected_conversation_participant_id); + } else if("add_friend" == command_name) { if (!LLAvatarActions::isFriend(selected_conversation_participant_id)) diff --git a/indra/newview/llconversationmodel.cpp b/indra/newview/llconversationmodel.cpp index 192a594c9d..affa24f78c 100755 --- a/indra/newview/llconversationmodel.cpp +++ b/indra/newview/llconversationmodel.cpp @@ -132,6 +132,7 @@ void LLConversationItem::buildParticipantMenuOptions(menuentry_vec_t& items, U32 items.push_back(std::string("view_profile")); items.push_back(std::string("im")); items.push_back(std::string("offer_teleport")); + items.push_back(std::string("request_teleport")); items.push_back(std::string("voice_call")); items.push_back(std::string("chat_history")); items.push_back(std::string("separator_chat_history")); diff --git a/indra/newview/lleventpoll.cpp b/indra/newview/lleventpoll.cpp index c3b53d5e4a..2f7f98ec7b 100755 --- a/indra/newview/lleventpoll.cpp +++ b/indra/newview/lleventpoll.cpp @@ -261,7 +261,7 @@ namespace } // was llinfos but now that CoarseRegionUpdate is TCP @ 1/second, it'd be too verbose for viewer logs. -MG - lldebugs << "LLEventPollResponder::completed <" << mCount << "> " << events.size() << "events (id " + lldebugs << "LLEventPollResponder::httpSuccess <" << mCount << "> " << events.size() << "events (id " << LLSDXMLStreamer(mAcknowledge) << ")" << llendl; LLSD::array_const_iterator i = events.beginArray(); diff --git a/indra/newview/llfacebookconnect.cpp b/indra/newview/llfacebookconnect.cpp new file mode 100644 index 0000000000..ffcf3c076a --- /dev/null +++ b/indra/newview/llfacebookconnect.cpp @@ -0,0 +1,615 @@ +/** + * @file llfacebookconnect.h + * @author Merov, Cho, Gil + * @brief Connection to Facebook Service + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, 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 "llfacebookconnect.h" + +#include "llagent.h" +#include "llcallingcard.h" // for LLAvatarTracker +#include "llcommandhandler.h" +#include "llhttpclient.h" +#include "llnotificationsutil.h" +#include "llurlaction.h" +#include "llimagepng.h" +#include "llimagejpeg.h" +#include "lltrans.h" +#include "llevents.h" +#include "llviewerregion.h" + +#include "llfloaterwebcontent.h" +#include "llfloaterreg.h" + +boost::scoped_ptr<LLEventPump> LLFacebookConnect::sStateWatcher(new LLEventStream("FacebookConnectState")); +boost::scoped_ptr<LLEventPump> LLFacebookConnect::sInfoWatcher(new LLEventStream("FacebookConnectInfo")); +boost::scoped_ptr<LLEventPump> LLFacebookConnect::sContentWatcher(new LLEventStream("FacebookConnectContent")); + +// Local functions +void log_facebook_connect_error(const std::string& request, U32 status, const std::string& reason, const std::string& code, const std::string& description) +{ + // Note: 302 (redirect) is *not* an error that warrants logging + if (status != 302) + { + LL_WARNS("FacebookConnect") << request << " request failed with a " << status << " " << reason << ". Reason: " << code << " (" << description << ")" << LL_ENDL; + } +} + +void toast_user_for_success() +{ + LLSD args; + args["MESSAGE"] = LLTrans::getString("facebook_post_success"); + LLNotificationsUtil::add("FacebookConnect", args); +} + +/////////////////////////////////////////////////////////////////////////////// +// +class LLFacebookConnectHandler : public LLCommandHandler +{ +public: + LLFacebookConnectHandler() : LLCommandHandler("fbc", UNTRUSTED_THROTTLE) { } + + bool handle(const LLSD& tokens, const LLSD& query_map, LLMediaCtrl* web) + { + if (tokens.size() > 0) + { + if (tokens[0].asString() == "connect") + { + // this command probably came from the fbc_web browser, so close it + LLFloater* fbc_web = LLFloaterReg::getInstance("fbc_web"); + if (fbc_web) + { + fbc_web->closeFloater(); + } + + // connect to facebook + if (query_map.has("code")) + { + LLFacebookConnect::instance().connectToFacebook(query_map["code"], query_map.get("state")); + } + return true; + } + } + return false; + } +}; +LLFacebookConnectHandler gFacebookConnectHandler; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLFacebookConnectResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLFacebookConnectResponder); +public: + + LLFacebookConnectResponder() + { + LLFacebookConnect::instance().setConnectionState(LLFacebookConnect::FB_CONNECTION_IN_PROGRESS); + } + + /* virtual */ void httpSuccess() + { + LL_DEBUGS("FacebookConnect") << "Connect successful. " << dumpResponse() << LL_ENDL; + LLFacebookConnect::instance().setConnectionState(LLFacebookConnect::FB_CONNECTED); + } + + /* virtual */ void httpFailure() + { + if ( HTTP_FOUND == getStatus() ) + { + const std::string& location = getResponseHeader(HTTP_IN_HEADER_LOCATION); + if (location.empty()) + { + LL_WARNS("FacebookConnect") << "Missing Location header " << dumpResponse() + << "[headers:" << getResponseHeaders() << "]" << LL_ENDL; + } + else + { + LLFacebookConnect::instance().openFacebookWeb(location); + } + } + else + { + LL_WARNS("FacebookConnect") << dumpResponse() << LL_ENDL; + LLFacebookConnect::instance().setConnectionState(LLFacebookConnect::FB_CONNECTION_FAILED); + const LLSD& content = getContent(); + log_facebook_connect_error("Connect", getStatus(), getReason(), + content.get("error_code"), content.get("error_description")); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLFacebookShareResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLFacebookShareResponder); +public: + + LLFacebookShareResponder() + { + LLFacebookConnect::instance().setConnectionState(LLFacebookConnect::FB_POSTING); + } + + /* virtual */ void httpSuccess() + { + toast_user_for_success(); + LL_DEBUGS("FacebookConnect") << "Post successful. " << dumpResponse() << LL_ENDL; + LLFacebookConnect::instance().setConnectionState(LLFacebookConnect::FB_POSTED); + } + + /* virtual */ void httpFailure() + { + if ( HTTP_FOUND == getStatus() ) + { + const std::string& location = getResponseHeader(HTTP_IN_HEADER_LOCATION); + if (location.empty()) + { + LL_WARNS("FacebookConnect") << "Missing Location header " << dumpResponse() + << "[headers:" << getResponseHeaders() << "]" << LL_ENDL; + } + else + { + LLFacebookConnect::instance().openFacebookWeb(location); + } + } + else if ( HTTP_NOT_FOUND == getStatus() ) + { + LLFacebookConnect::instance().connectToFacebook(); + } + else + { + LL_WARNS("FacebookConnect") << dumpResponse() << LL_ENDL; + LLFacebookConnect::instance().setConnectionState(LLFacebookConnect::FB_POST_FAILED); + const LLSD& content = getContent(); + log_facebook_connect_error("Share", getStatus(), getReason(), + content.get("error_code"), content.get("error_description")); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLFacebookDisconnectResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLFacebookDisconnectResponder); +public: + + LLFacebookDisconnectResponder() + { + LLFacebookConnect::instance().setConnectionState(LLFacebookConnect::FB_DISCONNECTING); + } + + void setUserDisconnected() + { + // Clear data + LLFacebookConnect::instance().clearInfo(); + LLFacebookConnect::instance().clearContent(); + //Notify state change + LLFacebookConnect::instance().setConnectionState(LLFacebookConnect::FB_NOT_CONNECTED); + } + + /* virtual */ void httpSuccess() + { + LL_DEBUGS("FacebookConnect") << "Disconnect successful. " << dumpResponse() << LL_ENDL; + setUserDisconnected(); + } + + /* virtual */ void httpFailure() + { + //User not found so already disconnected + if ( HTTP_NOT_FOUND == getStatus() ) + { + LL_DEBUGS("FacebookConnect") << "Already disconnected. " << dumpResponse() << LL_ENDL; + setUserDisconnected(); + } + else + { + LL_WARNS("FacebookConnect") << dumpResponse() << LL_ENDL; + LLFacebookConnect::instance().setConnectionState(LLFacebookConnect::FB_DISCONNECT_FAILED); + const LLSD& content = getContent(); + log_facebook_connect_error("Disconnect", getStatus(), getReason(), + content.get("error_code"), content.get("error_description")); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLFacebookConnectedResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLFacebookConnectedResponder); +public: + + LLFacebookConnectedResponder(bool auto_connect) : mAutoConnect(auto_connect) + { + LLFacebookConnect::instance().setConnectionState(LLFacebookConnect::FB_CONNECTION_IN_PROGRESS); + } + + /* virtual */ void httpSuccess() + { + LL_DEBUGS("FacebookConnect") << "Connect successful. " << dumpResponse() << LL_ENDL; + LLFacebookConnect::instance().setConnectionState(LLFacebookConnect::FB_CONNECTED); + } + + /* virtual */ void httpFailure() + { + // show the facebook login page if not connected yet + if ( HTTP_NOT_FOUND == getStatus() ) + { + LL_DEBUGS("FacebookConnect") << "Not connected. " << dumpResponse() << LL_ENDL; + if (mAutoConnect) + { + LLFacebookConnect::instance().connectToFacebook(); + } + else + { + LLFacebookConnect::instance().setConnectionState(LLFacebookConnect::FB_NOT_CONNECTED); + } + } + else + { + LL_WARNS("FacebookConnect") << dumpResponse() << LL_ENDL; + LLFacebookConnect::instance().setConnectionState(LLFacebookConnect::FB_DISCONNECT_FAILED); + const LLSD& content = getContent(); + log_facebook_connect_error("Connected", getStatus(), getReason(), + content.get("error_code"), content.get("error_description")); + } + } + +private: + bool mAutoConnect; +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLFacebookInfoResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLFacebookInfoResponder); +public: + + /* virtual */ void httpSuccess() + { + LL_INFOS("FacebookConnect") << "Facebook: Info received" << LL_ENDL; + LL_DEBUGS("FacebookConnect") << "Getting Facebook info successful. " << dumpResponse() << LL_ENDL; + LLFacebookConnect::instance().storeInfo(getContent()); + } + + /* virtual */ void httpFailure() + { + if ( HTTP_FOUND == getStatus() ) + { + const std::string& location = getResponseHeader(HTTP_IN_HEADER_LOCATION); + if (location.empty()) + { + LL_WARNS("FacebookConnect") << "Missing Location header " << dumpResponse() + << "[headers:" << getResponseHeaders() << "]" << LL_ENDL; + } + else + { + LLFacebookConnect::instance().openFacebookWeb(location); + } + } + else + { + LL_WARNS("FacebookConnect") << dumpResponse() << LL_ENDL; + const LLSD& content = getContent(); + log_facebook_connect_error("Info", getStatus(), getReason(), + content.get("error_code"), content.get("error_description")); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLFacebookFriendsResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLFacebookFriendsResponder); +public: + + /* virtual */ void httpSuccess() + { + LL_DEBUGS("FacebookConnect") << "Getting Facebook friends successful. " << dumpResponse() << LL_ENDL; + LLFacebookConnect::instance().storeContent(getContent()); + } + + /* virtual */ void httpFailure() + { + if ( HTTP_FOUND == getStatus() ) + { + const std::string& location = getResponseHeader(HTTP_IN_HEADER_LOCATION); + if (location.empty()) + { + LL_WARNS("FacebookConnect") << "Missing Location header " << dumpResponse() + << "[headers:" << getResponseHeaders() << "]" << LL_ENDL; + } + else + { + LLFacebookConnect::instance().openFacebookWeb(location); + } + } + else + { + LL_WARNS("FacebookConnect") << dumpResponse() << LL_ENDL; + const LLSD& content = getContent(); + log_facebook_connect_error("Friends", getStatus(), getReason(), + content.get("error_code"), content.get("error_description")); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// +LLFacebookConnect::LLFacebookConnect() +: mConnectionState(FB_NOT_CONNECTED), + mConnected(false), + mInfo(), + mContent(), + mRefreshInfo(false), + mRefreshContent(false), + mReadFromMaster(false) +{ +} + +void LLFacebookConnect::openFacebookWeb(std::string url) +{ + // Open the URL in an internal browser window without navigation UI + LLFloaterWebContent::Params p; + p.url(url).show_chrome(true); + p.url(url).allow_address_entry(false); + p.url(url).allow_back_forward_navigation(false); + p.url(url).trusted_content(true); + LLFloater *floater = LLFloaterReg::showInstance("fbc_web", p); + //the internal web browser has a bug that prevents it from gaining focus unless a mouse event occurs first (it seems). + //So when showing the internal web browser, set focus to it's containing floater "fbc_web". When a mouse event + //occurs on the "webbrowser" panel part of the floater, a mouse cursor will properly show and the "webbrowser" will gain focus. + //fbc_web floater contains the "webbrowser" panel. JIRA: ACME-744 + gFocusMgr.setKeyboardFocus( floater ); + + //LLUrlAction::openURLExternal(url); +} + +std::string LLFacebookConnect::getFacebookConnectURL(const std::string& route, bool include_read_from_master) +{ + std::string url = gAgent.getRegion()->getCapability("FacebookConnect"); + url += route; + + if (include_read_from_master && mReadFromMaster) + { + url += "?read_from_master=true"; + } + return url; +} + +void LLFacebookConnect::connectToFacebook(const std::string& auth_code, const std::string& auth_state) +{ + LLSD body; + if (!auth_code.empty()) + body["code"] = auth_code; + if (!auth_state.empty()) + body["state"] = auth_state; + + LLHTTPClient::put(getFacebookConnectURL("/connection"), body, new LLFacebookConnectResponder()); +} + +void LLFacebookConnect::disconnectFromFacebook() +{ + LLHTTPClient::del(getFacebookConnectURL("/connection"), new LLFacebookDisconnectResponder()); +} + +void LLFacebookConnect::checkConnectionToFacebook(bool auto_connect) +{ + const bool follow_redirects = false; + const F32 timeout = HTTP_REQUEST_EXPIRY_SECS; + LLHTTPClient::get(getFacebookConnectURL("/connection", true), new LLFacebookConnectedResponder(auto_connect), + LLSD(), timeout, follow_redirects); +} + +void LLFacebookConnect::loadFacebookInfo() +{ + if(mRefreshInfo) + { + const bool follow_redirects = false; + const F32 timeout = HTTP_REQUEST_EXPIRY_SECS; + LLHTTPClient::get(getFacebookConnectURL("/info", true), new LLFacebookInfoResponder(), + LLSD(), timeout, follow_redirects); + } +} + +void LLFacebookConnect::loadFacebookFriends() +{ + if(mRefreshContent) + { + const bool follow_redirects = false; + const F32 timeout = HTTP_REQUEST_EXPIRY_SECS; + LLHTTPClient::get(getFacebookConnectURL("/friends", true), new LLFacebookFriendsResponder(), + LLSD(), timeout, follow_redirects); + } +} + +void LLFacebookConnect::postCheckin(const std::string& location, const std::string& name, const std::string& description, const std::string& image, const std::string& message) +{ + LLSD body; + if (!location.empty()) + body["location"] = location; + if (!name.empty()) + body["name"] = name; + if (!description.empty()) + body["description"] = description; + if (!image.empty()) + body["image"] = image; + if (!message.empty()) + body["message"] = message; + + // Note: we can use that route for different publish action. We should be able to use the same responder. + LLHTTPClient::post(getFacebookConnectURL("/share/checkin", true), body, new LLFacebookShareResponder()); +} + +void LLFacebookConnect::sharePhoto(const std::string& image_url, const std::string& caption) +{ + LLSD body; + body["image"] = image_url; + body["caption"] = caption; + + // Note: we can use that route for different publish action. We should be able to use the same responder. + LLHTTPClient::post(getFacebookConnectURL("/share/photo", true), body, new LLFacebookShareResponder()); +} + +void LLFacebookConnect::sharePhoto(LLPointer<LLImageFormatted> image, const std::string& caption) +{ + std::string imageFormat; + if (dynamic_cast<LLImagePNG*>(image.get())) + { + imageFormat = "png"; + } + else if (dynamic_cast<LLImageJPEG*>(image.get())) + { + imageFormat = "jpg"; + } + else + { + llwarns << "Image to upload is not a PNG or JPEG" << llendl; + return; + } + + // All this code is mostly copied from LLWebProfile::post() + const std::string boundary = "----------------------------0123abcdefab"; + + LLSD headers; + headers["Content-Type"] = "multipart/form-data; boundary=" + boundary; + + std::ostringstream body; + + // *NOTE: The order seems to matter. + body << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"caption\"\r\n\r\n" + << caption << "\r\n"; + + body << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"image\"; filename=\"snapshot." << imageFormat << "\"\r\n" + << "Content-Type: image/" << imageFormat << "\r\n\r\n"; + + // Insert the image data. + // *FIX: Treating this as a string will probably screw it up ... + U8* image_data = image->getData(); + for (S32 i = 0; i < image->getDataSize(); ++i) + { + body << image_data[i]; + } + + body << "\r\n--" << boundary << "--\r\n"; + + // postRaw() takes ownership of the buffer and releases it later. + size_t size = body.str().size(); + U8 *data = new U8[size]; + memcpy(data, body.str().data(), size); + + // Note: we can use that route for different publish action. We should be able to use the same responder. + LLHTTPClient::postRaw(getFacebookConnectURL("/share/photo", true), data, size, new LLFacebookShareResponder(), headers); +} + +void LLFacebookConnect::updateStatus(const std::string& message) +{ + LLSD body; + body["message"] = message; + + // Note: we can use that route for different publish action. We should be able to use the same responder. + LLHTTPClient::post(getFacebookConnectURL("/share/wall", true), body, new LLFacebookShareResponder()); +} + +void LLFacebookConnect::storeInfo(const LLSD& info) +{ + mInfo = info; + mRefreshInfo = false; + + sInfoWatcher->post(info); +} + +const LLSD& LLFacebookConnect::getInfo() const +{ + return mInfo; +} + +void LLFacebookConnect::clearInfo() +{ + mInfo = LLSD(); +} + +void LLFacebookConnect::storeContent(const LLSD& content) +{ + mContent = content; + mRefreshContent = false; + + sContentWatcher->post(content); +} + +const LLSD& LLFacebookConnect::getContent() const +{ + return mContent; +} + +void LLFacebookConnect::clearContent() +{ + mContent = LLSD(); +} + +void LLFacebookConnect::setDataDirty() +{ + mRefreshInfo = true; + mRefreshContent = true; +} + +void LLFacebookConnect::setConnectionState(LLFacebookConnect::EConnectionState connection_state) +{ + if(connection_state == FB_CONNECTED) + { + mReadFromMaster = true; + setConnected(true); + setDataDirty(); + } + else if(connection_state == FB_NOT_CONNECTED) + { + setConnected(false); + } + else if(connection_state == FB_POSTED) + { + mReadFromMaster = false; + } + + if (mConnectionState != connection_state) + { + LLSD state_info; + state_info["enum"] = connection_state; + sStateWatcher->post(state_info); + } + + mConnectionState = connection_state; +} + +void LLFacebookConnect::setConnected(bool connected) +{ + mConnected = connected; +} diff --git a/indra/newview/llfacebookconnect.h b/indra/newview/llfacebookconnect.h new file mode 100644 index 0000000000..a77ac24167 --- /dev/null +++ b/indra/newview/llfacebookconnect.h @@ -0,0 +1,106 @@ +/** + * @file llfacebookconnect.h + * @author Merov, Cho, Gil + * @brief Connection to Facebook Service + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, 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_LLFACEBOOKCONNECT_H +#define LL_LLFACEBOOKCONNECT_H + +#include "llsingleton.h" +#include "llimage.h" + +class LLEventPump; + +/** + * @class LLFacebookConnect + * + * Manages authentication to, and interaction with, a web service allowing the + * the viewer to get Facebook OpenGraph data. + */ +class LLFacebookConnect : public LLSingleton<LLFacebookConnect> +{ + LOG_CLASS(LLFacebookConnect); +public: + enum EConnectionState + { + FB_NOT_CONNECTED = 0, + FB_CONNECTION_IN_PROGRESS = 1, + FB_CONNECTED = 2, + FB_CONNECTION_FAILED = 3, + FB_POSTING = 4, + FB_POSTED = 5, + FB_POST_FAILED = 6, + FB_DISCONNECTING = 7, + FB_DISCONNECT_FAILED = 8 + }; + + void connectToFacebook(const std::string& auth_code = "", const std::string& auth_state = ""); // Initiate the complete FB connection. Please use checkConnectionToFacebook() in normal use. + void disconnectFromFacebook(); // Disconnect from the FBC service. + void checkConnectionToFacebook(bool auto_connect = false); // Check if an access token is available on the FBC service. If not, call connectToFacebook(). + + void loadFacebookInfo(); + void loadFacebookFriends(); + void postCheckin(const std::string& location, const std::string& name, const std::string& description, const std::string& picture, const std::string& message); + void sharePhoto(const std::string& image_url, const std::string& caption); + void sharePhoto(LLPointer<LLImageFormatted> image, const std::string& caption); + void updateStatus(const std::string& message); + + void storeInfo(const LLSD& info); + const LLSD& getInfo() const; + void clearInfo(); + void storeContent(const LLSD& content); + const LLSD& getContent() const; + void clearContent(); + void setDataDirty(); + + void setConnectionState(EConnectionState connection_state); + void setConnected(bool connected); + bool isConnected() { return mConnected; } + bool isTransactionOngoing() { return ((mConnectionState == FB_CONNECTION_IN_PROGRESS) || (mConnectionState == FB_POSTING) || (mConnectionState == FB_DISCONNECTING)); } + EConnectionState getConnectionState() { return mConnectionState; } + + void openFacebookWeb(std::string url); + +private: + friend class LLSingleton<LLFacebookConnect>; + + LLFacebookConnect(); + ~LLFacebookConnect() {}; + std::string getFacebookConnectURL(const std::string& route = "", bool include_read_from_master = false); + + EConnectionState mConnectionState; + BOOL mConnected; + LLSD mInfo; + LLSD mContent; + bool mRefreshInfo; + bool mRefreshContent; + bool mReadFromMaster; + + static boost::scoped_ptr<LLEventPump> sStateWatcher; + static boost::scoped_ptr<LLEventPump> sInfoWatcher; + static boost::scoped_ptr<LLEventPump> sContentWatcher; +}; + +#endif // LL_LLFACEBOOKCONNECT_H diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index 2c3b34e128..90b4490e1d 100755 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -662,10 +662,10 @@ void LLFloaterIMContainer::setVisible(BOOL visible) LLFloater* session_floater = widget->getSessionFloater(); if (session_floater != nearby_chat) { - widget->setVisibleIfDetached(visible); - } + widget->setVisibleIfDetached(visible); } } + } // Now, do the normal multifloater show/hide LLMultiFloater::setVisible(visible); @@ -700,13 +700,13 @@ void LLFloaterIMContainer::setVisibleAndFrontmost(BOOL take_focus, const LLSD& k // Only select other sessions if (!getSelectedSession().isNull()) { - selectConversationPair(getSelectedSession(), false, take_focus); + selectConversationPair(getSelectedSession(), false, take_focus); } if (mInitialized && mIsFirstLaunch) { collapseMessagesPane(gSavedPerAccountSettings.getBOOL("ConversationsMessagePaneCollapsed")); mIsFirstLaunch = false; - } +} } void LLFloaterIMContainer::updateResizeLimits() @@ -834,7 +834,7 @@ void LLFloaterIMContainer::assignResizeLimits() S32 conv_pane_target_width = is_conv_pane_expanded ? ( is_msg_pane_expanded?mConversationsPane->getRect().getWidth():mConversationsPane->getExpandedMinDim() ) - : mConversationsPane->getMinDim(); + : mConversationsPane->getMinDim(); S32 msg_pane_min_width = is_msg_pane_expanded ? mMessagesPane->getExpandedMinDim() : 0; S32 new_min_width = conv_pane_target_width + msg_pane_min_width + summary_width_of_visible_borders; @@ -995,7 +995,7 @@ void LLFloaterIMContainer::setSortOrder(const LLConversationSort& order) conversation_floater->setSortOrder(order); } } - + gSavedSettings.setU32("ConversationSortOrder", (U32)order); } @@ -1080,6 +1080,10 @@ void LLFloaterIMContainer::doToParticipants(const std::string& command, uuid_vec { LLAvatarActions::offerTeleport(selectedIDS); } + else if ("request_teleport" == command) + { + LLAvatarActions::teleportRequest(selectedIDS.front()); + } else if ("voice_call" == command) { LLAvatarActions::startCall(userID); @@ -1182,7 +1186,7 @@ void LLFloaterIMContainer::doToSelectedConversation(const std::string& command, } else if("chat_history" == command) { - if (selectedIDS.size() > 0) + if (selectedIDS.size() > 0) { LLAvatarActions::viewChatHistory(selectedIDS.front()); } @@ -1204,7 +1208,7 @@ void LLFloaterIMContainer::doToSelectedConversation(const std::string& command, { LLFloaterReg::showInstance("preview_conversation", LLSD(LLUUID::null), true); } - } +} } } @@ -1235,7 +1239,7 @@ void LLFloaterIMContainer::doToSelectedGroup(const LLSD& userdata) if (action == "group_profile") { - LLGroupActions::show(mSelectedSession); + LLGroupActions::show(mSelectedSession); } else if (action == "activate_group") { diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp index 6ef4d8717d..8f20d4e082 100755 --- a/indra/newview/llfloaterland.cpp +++ b/indra/newview/llfloaterland.cpp @@ -2097,7 +2097,6 @@ void LLPanelLandOptions::refresh() // virtual void LLPanelLandOptions::draw() { - refreshSearch(); // Is this necessary? JC LLPanel::draw(); } @@ -2111,9 +2110,8 @@ void LLPanelLandOptions::refreshSearch() mCheckShowDirectory->set(FALSE); mCheckShowDirectory->setEnabled(FALSE); - // *TODO:Translate - const std::string& none_string = LLParcel::getCategoryUIString(LLParcel::C_NONE); - mCategoryCombo->setSimple(none_string); + const std::string& none_string = LLParcel::getCategoryString(LLParcel::C_NONE); + mCategoryCombo->setValue(none_string); mCategoryCombo->setEnabled(FALSE); return; } @@ -2140,10 +2138,9 @@ void LLPanelLandOptions::refreshSearch() mCheckShowDirectory ->set(show_directory); // Set by string in case the order in UI doesn't match the order by index. - // *TODO:Translate LLParcel::ECategory cat = parcel->getCategory(); - const std::string& category_string = LLParcel::getCategoryUIString(cat); - mCategoryCombo->setSimple(category_string); + const std::string& category_string = LLParcel::getCategoryString(cat); + mCategoryCombo->setValue(category_string); std::string tooltip; bool enable_show_directory = false; diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index 1969435ba1..87ae36716d 100755 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -618,9 +618,12 @@ void LLFloaterPreference::cancel() // hide translation settings floater LLFloaterReg::hideInstance("prefs_translation"); - // hide translation settings floater + // hide autoreplace settings floater LLFloaterReg::hideInstance("prefs_autoreplace"); + // hide spellchecker settings folder + LLFloaterReg::hideInstance("prefs_spellchecker"); + // cancel hardware menu LLFloaterHardwareSettings* hardware_settings = LLFloaterReg::getTypedInstance<LLFloaterHardwareSettings>("prefs_hardware_settings"); if (hardware_settings) diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp index ddfae005bb..eb0a201e69 100755 --- a/indra/newview/llfloaterregioninfo.cpp +++ b/indra/newview/llfloaterregioninfo.cpp @@ -1734,7 +1734,7 @@ void LLPanelEstateInfo::accessAddCore3(const uuid_vec_t& ids, void* data) LLSD args; args["NUM_ADDED"] = llformat("%d",ids.size()); args["MAX_AGENTS"] = llformat("%d",ESTATE_MAX_ACCESS_IDS); - args["LIST_TYPE"] = "Allowed Residents"; + args["LIST_TYPE"] = LLTrans::getString("RegionInfoListTypeAllowedAgents"); args["NUM_EXCESS"] = llformat("%d",(ids.size()+currentCount)-ESTATE_MAX_ACCESS_IDS); LLNotificationsUtil::add("MaxAgentOnRegionBatch", args); delete change_info; @@ -1750,7 +1750,7 @@ void LLPanelEstateInfo::accessAddCore3(const uuid_vec_t& ids, void* data) LLSD args; args["NUM_ADDED"] = llformat("%d",ids.size()); args["MAX_AGENTS"] = llformat("%d",ESTATE_MAX_ACCESS_IDS); - args["LIST_TYPE"] = "Banned Residents"; + args["LIST_TYPE"] = LLTrans::getString("RegionInfoListTypeBannedAgents"); args["NUM_EXCESS"] = llformat("%d",(ids.size()+currentCount)-ESTATE_MAX_ACCESS_IDS); LLNotificationsUtil::add("MaxAgentOnRegionBatch", args); delete change_info; @@ -2816,9 +2816,10 @@ bool LLDispatchSetEstateAccess::operator()( } - std::string msg = llformat("Banned residents: (%d, max %d)", - totalBannedAgents, - ESTATE_MAX_ACCESS_IDS); + LLStringUtil::format_map_t args; + args["[BANNEDAGENTS]"] = llformat("%d", totalBannedAgents); + args["[MAXBANNED]"] = llformat("%d", ESTATE_MAX_ACCESS_IDS); + std::string msg = LLTrans::getString("RegionInfoBannedResidents", args); panel->getChild<LLUICtrl>("ban_resident_label")->setValue(LLSD(msg)); if (banned_agent_name_list) @@ -2838,9 +2839,10 @@ bool LLDispatchSetEstateAccess::operator()( if (access_flags & ESTATE_ACCESS_MANAGERS) { - std::string msg = llformat("Estate Managers: (%d, max %d)", - num_estate_managers, - ESTATE_MAX_MANAGERS); + LLStringUtil::format_map_t args; + args["[ESTATEMANAGERS]"] = llformat("%d", num_estate_managers); + args["[MAXMANAGERS]"] = llformat("%d", ESTATE_MAX_MANAGERS); + std::string msg = LLTrans::getString("RegionInfoEstateManagers", args); panel->getChild<LLUICtrl>("estate_manager_label")->setValue(LLSD(msg)); LLNameListCtrl* estate_manager_name_list = diff --git a/indra/newview/llfloatersnapshot.cpp b/indra/newview/llfloatersnapshot.cpp index d8d62e5bbb..ea385d7baf 100755 --- a/indra/newview/llfloatersnapshot.cpp +++ b/indra/newview/llfloatersnapshot.cpp @@ -28,60 +28,23 @@ #include "llfloatersnapshot.h" -#include "llfloaterreg.h" - -// Viewer includes #include "llagent.h" -#include "llagentcamera.h" -#include "llcallbacklist.h" -#include "llcriticaldamp.h" -#include "llfloaterperms.h" -#include "llui.h" -#include "llfocusmgr.h" -#include "llbutton.h" +#include "llfacebookconnect.h" +#include "llfloaterreg.h" +#include "llfloatersocial.h" +#include "llcheckboxctrl.h" #include "llcombobox.h" -#include "lleconomy.h" -#include "lllandmarkactions.h" -#include "llpanelsnapshot.h" +#include "llpostcard.h" +#include "llresmgr.h" // LLLocale +#include "llsdserialize.h" #include "llsidetraypanelcontainer.h" -#include "llsliderctrl.h" +#include "llsnapshotlivepreview.h" #include "llspinctrl.h" #include "llviewercontrol.h" -#include "lluictrlfactory.h" -#include "llviewerstats.h" -#include "llviewercamera.h" -#include "llviewerwindow.h" -#include "llviewermenufile.h" // upload_new_resource() -#include "llcheckboxctrl.h" -#include "llslurl.h" #include "lltoolfocus.h" #include "lltoolmgr.h" -#include "llwebsharing.h" -#include "llworld.h" -#include "llagentui.h" - -// Linden library includes -#include "llfontgl.h" -#include "llsys.h" -#include "llrender.h" -#include "v3dmath.h" -#include "llmath.h" -#include "lldir.h" -#include "llsdserialize.h" -#include "llgl.h" -#include "llglheaders.h" -#include "llimagejpeg.h" -#include "llimagepng.h" -#include "llimagebmp.h" -#include "llimagej2c.h" -#include "lllocalcliprect.h" -#include "llnotificationsutil.h" -#include "llpostcard.h" -#include "llresmgr.h" // LLLocale -#include "llvfile.h" -#include "llvfs.h" #include "llwebprofile.h" -#include "llwindow.h" +#include "llwebsharing.h" ///---------------------------------------------------------------------------- /// Local function declarations, constants, enums, and typedefs @@ -91,949 +54,12 @@ LLSnapshotFloaterView* gSnapshotFloaterView = NULL; const F32 AUTO_SNAPSHOT_TIME_DELAY = 1.f; -F32 SHINE_TIME = 0.5f; -F32 SHINE_WIDTH = 0.6f; -F32 SHINE_OPACITY = 0.3f; -F32 FALL_TIME = 0.6f; -S32 BORDER_WIDTH = 6; - const S32 MAX_POSTCARD_DATASIZE = 1024 * 1024; // one megabyte const S32 MAX_TEXTURE_SIZE = 512 ; //max upload texture size 512 * 512 static LLDefaultChildRegistry::Register<LLSnapshotFloaterView> r("snapshot_floater_view"); -///---------------------------------------------------------------------------- -/// Class LLSnapshotLivePreview -///---------------------------------------------------------------------------- -class LLSnapshotLivePreview : public LLView -{ - LOG_CLASS(LLSnapshotLivePreview); -public: - enum ESnapshotType - { - SNAPSHOT_POSTCARD, - SNAPSHOT_TEXTURE, - SNAPSHOT_LOCAL, - SNAPSHOT_WEB - }; - - - struct Params : public LLInitParam::Block<Params, LLView::Params> - { - Params() - { - name = "snapshot_live_preview"; - mouse_opaque = false; - } - }; - - - LLSnapshotLivePreview(const LLSnapshotLivePreview::Params& p); - ~LLSnapshotLivePreview(); - - /*virtual*/ void draw(); - /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent); - - void setSize(S32 w, S32 h); - void setWidth(S32 w) { mWidth[mCurImageIndex] = w; } - void setHeight(S32 h) { mHeight[mCurImageIndex] = h; } - void getSize(S32& w, S32& h) const; - S32 getWidth() const { return mWidth[mCurImageIndex]; } - S32 getHeight() const { return mHeight[mCurImageIndex]; } - S32 getDataSize() const { return mDataSize; } - void setMaxImageSize(S32 size) ; - S32 getMaxImageSize() {return mMaxImageSize ;} - - ESnapshotType getSnapshotType() const { return mSnapshotType; } - LLFloaterSnapshot::ESnapshotFormat getSnapshotFormat() const { return mSnapshotFormat; } - BOOL getSnapshotUpToDate() const { return mSnapshotUpToDate; } - BOOL isSnapshotActive() { return mSnapshotActive; } - LLViewerTexture* getThumbnailImage() const { return mThumbnailImage ; } - S32 getThumbnailWidth() const { return mThumbnailWidth ; } - S32 getThumbnailHeight() const { return mThumbnailHeight ; } - BOOL getThumbnailLock() const { return mThumbnailUpdateLock ; } - BOOL getThumbnailUpToDate() const { return mThumbnailUpToDate ;} - LLViewerTexture* getCurrentImage(); - F32 getImageAspect(); - F32 getAspect() ; - const LLRect& getImageRect() const { return mImageRect[mCurImageIndex]; } - BOOL isImageScaled() const { return mImageScaled[mCurImageIndex]; } - void setImageScaled(BOOL scaled) { mImageScaled[mCurImageIndex] = scaled; } - const LLVector3d& getPosTakenGlobal() const { return mPosTakenGlobal; } - - void setSnapshotType(ESnapshotType type) { mSnapshotType = type; } - void setSnapshotFormat(LLFloaterSnapshot::ESnapshotFormat type) { mSnapshotFormat = type; } - void setSnapshotQuality(S32 quality); - void setSnapshotBufferType(LLViewerWindow::ESnapshotType type) { mSnapshotBufferType = type; } - void updateSnapshot(BOOL new_snapshot, BOOL new_thumbnail = FALSE, F32 delay = 0.f); - void saveWeb(); - void saveTexture(); - BOOL saveLocal(); - - LLPointer<LLImageFormatted> getFormattedImage() const { return mFormattedImage; } - LLPointer<LLImageRaw> getEncodedImage() const { return mPreviewImageEncoded; } - - /// Sets size of preview thumbnail image and thhe surrounding rect. - BOOL setThumbnailImageSize() ; - void generateThumbnailImage(BOOL force_update = FALSE) ; - void resetThumbnailImage() { mThumbnailImage = NULL ; } - void drawPreviewRect(S32 offset_x, S32 offset_y) ; - - // Returns TRUE when snapshot generated, FALSE otherwise. - static BOOL onIdle( void* snapshot_preview ); - - // callback for region name resolve - void regionNameCallback(LLImageJPEG* snapshot, LLSD& metadata, const std::string& name, S32 x, S32 y, S32 z); - -private: - LLColor4 mColor; - LLPointer<LLViewerTexture> mViewerImage[2]; //used to represent the scene when the frame is frozen. - LLRect mImageRect[2]; - S32 mWidth[2]; - S32 mHeight[2]; - BOOL mImageScaled[2]; - S32 mMaxImageSize ; - - //thumbnail image - LLPointer<LLViewerTexture> mThumbnailImage ; - S32 mThumbnailWidth ; - S32 mThumbnailHeight ; - LLRect mPreviewRect ; - BOOL mThumbnailUpdateLock ; - BOOL mThumbnailUpToDate ; - - S32 mCurImageIndex; - LLPointer<LLImageRaw> mPreviewImage; - LLPointer<LLImageRaw> mPreviewImageEncoded; - LLPointer<LLImageFormatted> mFormattedImage; - LLFrameTimer mSnapshotDelayTimer; - S32 mShineCountdown; - LLFrameTimer mShineAnimTimer; - F32 mFlashAlpha; - BOOL mNeedsFlash; - LLVector3d mPosTakenGlobal; - S32 mSnapshotQuality; - S32 mDataSize; - ESnapshotType mSnapshotType; - LLFloaterSnapshot::ESnapshotFormat mSnapshotFormat; - BOOL mSnapshotUpToDate; - LLFrameTimer mFallAnimTimer; - LLVector3 mCameraPos; - LLQuaternion mCameraRot; - BOOL mSnapshotActive; - LLViewerWindow::ESnapshotType mSnapshotBufferType; - -public: - static std::set<LLSnapshotLivePreview*> sList; - BOOL mKeepAspectRatio ; -}; - -std::set<LLSnapshotLivePreview*> LLSnapshotLivePreview::sList; - -LLSnapshotLivePreview::LLSnapshotLivePreview (const LLSnapshotLivePreview::Params& p) -: LLView(p), - mColor(1.f, 0.f, 0.f, 0.5f), - mCurImageIndex(0), - mPreviewImage(NULL), - mThumbnailImage(NULL) , - mThumbnailWidth(0), - mThumbnailHeight(0), - mPreviewImageEncoded(NULL), - mFormattedImage(NULL), - mShineCountdown(0), - mFlashAlpha(0.f), - mNeedsFlash(TRUE), - mSnapshotQuality(gSavedSettings.getS32("SnapshotQuality")), - mDataSize(0), - mSnapshotType(SNAPSHOT_POSTCARD), - mSnapshotFormat(LLFloaterSnapshot::ESnapshotFormat(gSavedSettings.getS32("SnapshotFormat"))), - mSnapshotUpToDate(FALSE), - mCameraPos(LLViewerCamera::getInstance()->getOrigin()), - mCameraRot(LLViewerCamera::getInstance()->getQuaternion()), - mSnapshotActive(FALSE), - mSnapshotBufferType(LLViewerWindow::SNAPSHOT_TYPE_COLOR) -{ - setSnapshotQuality(gSavedSettings.getS32("SnapshotQuality")); - mSnapshotDelayTimer.setTimerExpirySec(0.0f); - mSnapshotDelayTimer.start(); -// gIdleCallbacks.addFunction( &LLSnapshotLivePreview::onIdle, (void*)this ); - sList.insert(this); - setFollowsAll(); - mWidth[0] = gViewerWindow->getWindowWidthRaw(); - mWidth[1] = gViewerWindow->getWindowWidthRaw(); - mHeight[0] = gViewerWindow->getWindowHeightRaw(); - mHeight[1] = gViewerWindow->getWindowHeightRaw(); - mImageScaled[0] = FALSE; - mImageScaled[1] = FALSE; - - mMaxImageSize = MAX_SNAPSHOT_IMAGE_SIZE ; - mKeepAspectRatio = gSavedSettings.getBOOL("KeepAspectForSnapshot") ; - mThumbnailUpdateLock = FALSE ; - mThumbnailUpToDate = FALSE ; -} - -LLSnapshotLivePreview::~LLSnapshotLivePreview() -{ - // delete images - mPreviewImage = NULL; - mPreviewImageEncoded = NULL; - mFormattedImage = NULL; - -// gIdleCallbacks.deleteFunction( &LLSnapshotLivePreview::onIdle, (void*)this ); - sList.erase(this); -} - -void LLSnapshotLivePreview::setMaxImageSize(S32 size) -{ - if(size < MAX_SNAPSHOT_IMAGE_SIZE) - { - mMaxImageSize = size; - } - else - { - mMaxImageSize = MAX_SNAPSHOT_IMAGE_SIZE ; - } -} - -LLViewerTexture* LLSnapshotLivePreview::getCurrentImage() -{ - return mViewerImage[mCurImageIndex]; -} - -F32 LLSnapshotLivePreview::getAspect() -{ - F32 image_aspect_ratio = ((F32)getWidth()) / ((F32)getHeight()); - F32 window_aspect_ratio = ((F32)getRect().getWidth()) / ((F32)getRect().getHeight()); - - if (!mKeepAspectRatio)//gSavedSettings.getBOOL("KeepAspectForSnapshot")) - { - return image_aspect_ratio; - } - else - { - return window_aspect_ratio; - } -} - -F32 LLSnapshotLivePreview::getImageAspect() -{ - if (!getCurrentImage()) - { - return 0.f; - } - - return getAspect() ; -} - -void LLSnapshotLivePreview::updateSnapshot(BOOL new_snapshot, BOOL new_thumbnail, F32 delay) -{ - // Invalidate current image. - lldebugs << "updateSnapshot: mSnapshotUpToDate = " << getSnapshotUpToDate() << llendl; - if (getSnapshotUpToDate()) - { - S32 old_image_index = mCurImageIndex; - mCurImageIndex = (mCurImageIndex + 1) % 2; - setSize(mWidth[old_image_index], mHeight[old_image_index]); - mFallAnimTimer.start(); - } - mSnapshotUpToDate = FALSE; - - // Update snapshot source rect depending on whether we keep the aspect ratio. - LLRect& rect = mImageRect[mCurImageIndex]; - rect.set(0, getRect().getHeight(), getRect().getWidth(), 0); - - F32 image_aspect_ratio = ((F32)getWidth()) / ((F32)getHeight()); - F32 window_aspect_ratio = ((F32)getRect().getWidth()) / ((F32)getRect().getHeight()); - - if (mKeepAspectRatio)//gSavedSettings.getBOOL("KeepAspectForSnapshot")) - { - if (image_aspect_ratio > window_aspect_ratio) - { - // trim off top and bottom - S32 new_height = llround((F32)getRect().getWidth() / image_aspect_ratio); - rect.mBottom += (getRect().getHeight() - new_height) / 2; - rect.mTop -= (getRect().getHeight() - new_height) / 2; - } - else if (image_aspect_ratio < window_aspect_ratio) - { - // trim off left and right - S32 new_width = llround((F32)getRect().getHeight() * image_aspect_ratio); - rect.mLeft += (getRect().getWidth() - new_width) / 2; - rect.mRight -= (getRect().getWidth() - new_width) / 2; - } - } - - // Stop shining animation. - mShineAnimTimer.stop(); - - // Update snapshot if requested. - if (new_snapshot) - { - mSnapshotDelayTimer.start(); - mSnapshotDelayTimer.setTimerExpirySec(delay); - LLFloaterSnapshot::preUpdate(); - } - - // Update thumbnail if requested. - if(new_thumbnail) - { - mThumbnailUpToDate = FALSE ; - } -} - -void LLSnapshotLivePreview::setSnapshotQuality(S32 quality) -{ - llclamp(quality, 0, 100); - if (quality != mSnapshotQuality) - { - mSnapshotQuality = quality; - gSavedSettings.setS32("SnapshotQuality", quality); - mSnapshotUpToDate = FALSE; - } -} - -void LLSnapshotLivePreview::drawPreviewRect(S32 offset_x, S32 offset_y) -{ - F32 line_width ; - glGetFloatv(GL_LINE_WIDTH, &line_width) ; - glLineWidth(2.0f * line_width) ; - LLColor4 color(0.0f, 0.0f, 0.0f, 1.0f) ; - gl_rect_2d( mPreviewRect.mLeft + offset_x, mPreviewRect.mTop + offset_y, - mPreviewRect.mRight + offset_x, mPreviewRect.mBottom + offset_y, color, FALSE ) ; - glLineWidth(line_width) ; - - //draw four alpha rectangles to cover areas outside of the snapshot image - if(!mKeepAspectRatio) - { - LLColor4 alpha_color(0.5f, 0.5f, 0.5f, 0.8f) ; - S32 dwl = 0, dwr = 0 ; - if(mThumbnailWidth > mPreviewRect.getWidth()) - { - dwl = (mThumbnailWidth - mPreviewRect.getWidth()) >> 1 ; - dwr = mThumbnailWidth - mPreviewRect.getWidth() - dwl ; - - gl_rect_2d(mPreviewRect.mLeft + offset_x - dwl, mPreviewRect.mTop + offset_y, - mPreviewRect.mLeft + offset_x, mPreviewRect.mBottom + offset_y, alpha_color, TRUE ) ; - gl_rect_2d( mPreviewRect.mRight + offset_x, mPreviewRect.mTop + offset_y, - mPreviewRect.mRight + offset_x + dwr, mPreviewRect.mBottom + offset_y, alpha_color, TRUE ) ; - } - - if(mThumbnailHeight > mPreviewRect.getHeight()) - { - S32 dh = (mThumbnailHeight - mPreviewRect.getHeight()) >> 1 ; - gl_rect_2d(mPreviewRect.mLeft + offset_x - dwl, mPreviewRect.mBottom + offset_y , - mPreviewRect.mRight + offset_x + dwr, mPreviewRect.mBottom + offset_y - dh, alpha_color, TRUE ) ; - - dh = mThumbnailHeight - mPreviewRect.getHeight() - dh ; - gl_rect_2d( mPreviewRect.mLeft + offset_x - dwl, mPreviewRect.mTop + offset_y + dh, - mPreviewRect.mRight + offset_x + dwr, mPreviewRect.mTop + offset_y, alpha_color, TRUE ) ; - } - } -} - -//called when the frame is frozen. -void LLSnapshotLivePreview::draw() -{ - if (getCurrentImage() && - mPreviewImageEncoded.notNull() && - getSnapshotUpToDate()) - { - LLColor4 bg_color(0.f, 0.f, 0.3f, 0.4f); - gl_rect_2d(getRect(), bg_color); - const LLRect& rect = getImageRect(); - LLRect shadow_rect = rect; - shadow_rect.stretch(BORDER_WIDTH); - gl_drop_shadow(shadow_rect.mLeft, shadow_rect.mTop, shadow_rect.mRight, shadow_rect.mBottom, LLColor4(0.f, 0.f, 0.f, mNeedsFlash ? 0.f :0.5f), 10); - - LLColor4 image_color(1.f, 1.f, 1.f, 1.f); - gGL.color4fv(image_color.mV); - gGL.getTexUnit(0)->bind(getCurrentImage()); - // calculate UV scale - F32 uv_width = isImageScaled() ? 1.f : llmin((F32)getWidth() / (F32)getCurrentImage()->getWidth(), 1.f); - F32 uv_height = isImageScaled() ? 1.f : llmin((F32)getHeight() / (F32)getCurrentImage()->getHeight(), 1.f); - gGL.pushMatrix(); - { - gGL.translatef((F32)rect.mLeft, (F32)rect.mBottom, 0.f); - gGL.begin(LLRender::QUADS); - { - gGL.texCoord2f(uv_width, uv_height); - gGL.vertex2i(rect.getWidth(), rect.getHeight() ); - - gGL.texCoord2f(0.f, uv_height); - gGL.vertex2i(0, rect.getHeight() ); - - gGL.texCoord2f(0.f, 0.f); - gGL.vertex2i(0, 0); - - gGL.texCoord2f(uv_width, 0.f); - gGL.vertex2i(rect.getWidth(), 0); - } - gGL.end(); - } - gGL.popMatrix(); - - gGL.color4f(1.f, 1.f, 1.f, mFlashAlpha); - gl_rect_2d(getRect()); - if (mNeedsFlash) - { - if (mFlashAlpha < 1.f) - { - mFlashAlpha = lerp(mFlashAlpha, 1.f, LLCriticalDamp::getInterpolant(0.02f)); - } - else - { - mNeedsFlash = FALSE; - } - } - else - { - mFlashAlpha = lerp(mFlashAlpha, 0.f, LLCriticalDamp::getInterpolant(0.15f)); - } - - // Draw shining animation if appropriate. - if (mShineCountdown > 0) - { - mShineCountdown--; - if (mShineCountdown == 0) - { - mShineAnimTimer.start(); - } - } - else if (mShineAnimTimer.getStarted()) - { - lldebugs << "Drawing shining animation" << llendl; - F32 shine_interp = llmin(1.f, mShineAnimTimer.getElapsedTimeF32() / SHINE_TIME); - - // draw "shine" effect - LLLocalClipRect clip(getLocalRect()); - { - // draw diagonal stripe with gradient that passes over screen - S32 x1 = gViewerWindow->getWindowWidthScaled() * llround((clamp_rescale(shine_interp, 0.f, 1.f, -1.f - SHINE_WIDTH, 1.f))); - S32 x2 = x1 + llround(gViewerWindow->getWindowWidthScaled() * SHINE_WIDTH); - S32 x3 = x2 + llround(gViewerWindow->getWindowWidthScaled() * SHINE_WIDTH); - S32 y1 = 0; - S32 y2 = gViewerWindow->getWindowHeightScaled(); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.begin(LLRender::QUADS); - { - gGL.color4f(1.f, 1.f, 1.f, 0.f); - gGL.vertex2i(x1, y1); - gGL.vertex2i(x1 + gViewerWindow->getWindowWidthScaled(), y2); - gGL.color4f(1.f, 1.f, 1.f, SHINE_OPACITY); - gGL.vertex2i(x2 + gViewerWindow->getWindowWidthScaled(), y2); - gGL.vertex2i(x2, y1); - - gGL.color4f(1.f, 1.f, 1.f, SHINE_OPACITY); - gGL.vertex2i(x2, y1); - gGL.vertex2i(x2 + gViewerWindow->getWindowWidthScaled(), y2); - gGL.color4f(1.f, 1.f, 1.f, 0.f); - gGL.vertex2i(x3 + gViewerWindow->getWindowWidthScaled(), y2); - gGL.vertex2i(x3, y1); - } - gGL.end(); - } - - // if we're at the end of the animation, stop - if (shine_interp >= 1.f) - { - mShineAnimTimer.stop(); - } - } - } - - // draw framing rectangle - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.color4f(1.f, 1.f, 1.f, 1.f); - const LLRect& outline_rect = getImageRect(); - gGL.begin(LLRender::QUADS); - { - gGL.vertex2i(outline_rect.mLeft - BORDER_WIDTH, outline_rect.mTop + BORDER_WIDTH); - gGL.vertex2i(outline_rect.mRight + BORDER_WIDTH, outline_rect.mTop + BORDER_WIDTH); - gGL.vertex2i(outline_rect.mRight, outline_rect.mTop); - gGL.vertex2i(outline_rect.mLeft, outline_rect.mTop); - - gGL.vertex2i(outline_rect.mLeft, outline_rect.mBottom); - gGL.vertex2i(outline_rect.mRight, outline_rect.mBottom); - gGL.vertex2i(outline_rect.mRight + BORDER_WIDTH, outline_rect.mBottom - BORDER_WIDTH); - gGL.vertex2i(outline_rect.mLeft - BORDER_WIDTH, outline_rect.mBottom - BORDER_WIDTH); - - gGL.vertex2i(outline_rect.mLeft, outline_rect.mTop); - gGL.vertex2i(outline_rect.mLeft, outline_rect.mBottom); - gGL.vertex2i(outline_rect.mLeft - BORDER_WIDTH, outline_rect.mBottom - BORDER_WIDTH); - gGL.vertex2i(outline_rect.mLeft - BORDER_WIDTH, outline_rect.mTop + BORDER_WIDTH); - - gGL.vertex2i(outline_rect.mRight, outline_rect.mBottom); - gGL.vertex2i(outline_rect.mRight, outline_rect.mTop); - gGL.vertex2i(outline_rect.mRight + BORDER_WIDTH, outline_rect.mTop + BORDER_WIDTH); - gGL.vertex2i(outline_rect.mRight + BORDER_WIDTH, outline_rect.mBottom - BORDER_WIDTH); - } - gGL.end(); - } - - // draw old image dropping away - if (mFallAnimTimer.getStarted()) - { - S32 old_image_index = (mCurImageIndex + 1) % 2; - if (mViewerImage[old_image_index].notNull() && mFallAnimTimer.getElapsedTimeF32() < FALL_TIME) - { - lldebugs << "Drawing fall animation" << llendl; - F32 fall_interp = mFallAnimTimer.getElapsedTimeF32() / FALL_TIME; - F32 alpha = clamp_rescale(fall_interp, 0.f, 1.f, 0.8f, 0.4f); - LLColor4 image_color(1.f, 1.f, 1.f, alpha); - gGL.color4fv(image_color.mV); - gGL.getTexUnit(0)->bind(mViewerImage[old_image_index]); - // calculate UV scale - // *FIX get this to work with old image - BOOL rescale = !mImageScaled[old_image_index] && mViewerImage[mCurImageIndex].notNull(); - F32 uv_width = rescale ? llmin((F32)mWidth[old_image_index] / (F32)mViewerImage[mCurImageIndex]->getWidth(), 1.f) : 1.f; - F32 uv_height = rescale ? llmin((F32)mHeight[old_image_index] / (F32)mViewerImage[mCurImageIndex]->getHeight(), 1.f) : 1.f; - gGL.pushMatrix(); - { - LLRect& rect = mImageRect[old_image_index]; - gGL.translatef((F32)rect.mLeft, (F32)rect.mBottom - llround(getRect().getHeight() * 2.f * (fall_interp * fall_interp)), 0.f); - gGL.rotatef(-45.f * fall_interp, 0.f, 0.f, 1.f); - gGL.begin(LLRender::QUADS); - { - gGL.texCoord2f(uv_width, uv_height); - gGL.vertex2i(rect.getWidth(), rect.getHeight() ); - - gGL.texCoord2f(0.f, uv_height); - gGL.vertex2i(0, rect.getHeight() ); - - gGL.texCoord2f(0.f, 0.f); - gGL.vertex2i(0, 0); - gGL.texCoord2f(uv_width, 0.f); - gGL.vertex2i(rect.getWidth(), 0); - } - gGL.end(); - } - gGL.popMatrix(); - } - } -} - -/*virtual*/ -void LLSnapshotLivePreview::reshape(S32 width, S32 height, BOOL called_from_parent) -{ - LLRect old_rect = getRect(); - LLView::reshape(width, height, called_from_parent); - if (old_rect.getWidth() != width || old_rect.getHeight() != height) - { - lldebugs << "window reshaped, updating thumbnail" << llendl; - updateSnapshot(FALSE, TRUE); - } -} - -BOOL LLSnapshotLivePreview::setThumbnailImageSize() -{ - if(getWidth() < 10 || getHeight() < 10) - { - return FALSE ; - } - S32 window_width = gViewerWindow->getWindowWidthRaw() ; - S32 window_height = gViewerWindow->getWindowHeightRaw() ; - - F32 window_aspect_ratio = ((F32)window_width) / ((F32)window_height); - - // UI size for thumbnail - // *FIXME: the rect does not change, so maybe there's no need to recalculate max w/h. - const LLRect& thumbnail_rect = LLFloaterSnapshot::getThumbnailPlaceholderRect(); - S32 max_width = thumbnail_rect.getWidth(); - S32 max_height = thumbnail_rect.getHeight(); - - if (window_aspect_ratio > (F32)max_width / max_height) - { - // image too wide, shrink to width - mThumbnailWidth = max_width; - mThumbnailHeight = llround((F32)max_width / window_aspect_ratio); - } - else - { - // image too tall, shrink to height - mThumbnailHeight = max_height; - mThumbnailWidth = llround((F32)max_height * window_aspect_ratio); - } - - if(mThumbnailWidth > window_width || mThumbnailHeight > window_height) - { - return FALSE ;//if the window is too small, ignore thumbnail updating. - } - - S32 left = 0 , top = mThumbnailHeight, right = mThumbnailWidth, bottom = 0 ; - if(!mKeepAspectRatio) - { - F32 ratio_x = (F32)getWidth() / window_width ; - F32 ratio_y = (F32)getHeight() / window_height ; - - //if(getWidth() > window_width || - // getHeight() > window_height ) - { - if(ratio_x > ratio_y) - { - top = (S32)(top * ratio_y / ratio_x) ; - } - else - { - right = (S32)(right * ratio_x / ratio_y) ; - } - } - //else - //{ - // right = (S32)(right * ratio_x) ; - // top = (S32)(top * ratio_y) ; - //} - left = (S32)((mThumbnailWidth - right) * 0.5f) ; - bottom = (S32)((mThumbnailHeight - top) * 0.5f) ; - top += bottom ; - right += left ; - } - mPreviewRect.set(left - 1, top + 1, right + 1, bottom - 1) ; - - return TRUE ; -} - -void LLSnapshotLivePreview::generateThumbnailImage(BOOL force_update) -{ - if(mThumbnailUpdateLock) //in the process of updating - { - return ; - } - if(getThumbnailUpToDate() && !force_update)//already updated - { - return ; - } - if(getWidth() < 10 || getHeight() < 10) - { - return ; - } - - ////lock updating - mThumbnailUpdateLock = TRUE ; - - if(!setThumbnailImageSize()) - { - mThumbnailUpdateLock = FALSE ; - mThumbnailUpToDate = TRUE ; - return ; - } - - if(mThumbnailImage) - { - resetThumbnailImage() ; - } - - LLPointer<LLImageRaw> raw = new LLImageRaw; - if(!gViewerWindow->thumbnailSnapshot(raw, - mThumbnailWidth, mThumbnailHeight, - gSavedSettings.getBOOL("RenderUIInSnapshot"), - FALSE, - mSnapshotBufferType) ) - { - raw = NULL ; - } - - if(raw) - { - raw->expandToPowerOfTwo(); - mThumbnailImage = LLViewerTextureManager::getLocalTexture(raw.get(), FALSE); - mThumbnailUpToDate = TRUE ; - } - - //unlock updating - mThumbnailUpdateLock = FALSE ; -} - - -// Called often. Checks whether it's time to grab a new snapshot and if so, does it. -// Returns TRUE if new snapshot generated, FALSE otherwise. -//static -BOOL LLSnapshotLivePreview::onIdle( void* snapshot_preview ) -{ - LLSnapshotLivePreview* previewp = (LLSnapshotLivePreview*)snapshot_preview; - if (previewp->getWidth() == 0 || previewp->getHeight() == 0) - { - llwarns << "Incorrect dimensions: " << previewp->getWidth() << "x" << previewp->getHeight() << llendl; - return FALSE; - } - - // If we're in freeze-frame mode and camera has moved, update snapshot. - LLVector3 new_camera_pos = LLViewerCamera::getInstance()->getOrigin(); - LLQuaternion new_camera_rot = LLViewerCamera::getInstance()->getQuaternion(); - if (gSavedSettings.getBOOL("FreezeTime") && - (new_camera_pos != previewp->mCameraPos || dot(new_camera_rot, previewp->mCameraRot) < 0.995f)) - { - previewp->mCameraPos = new_camera_pos; - previewp->mCameraRot = new_camera_rot; - // request a new snapshot whenever the camera moves, with a time delay - BOOL autosnap = gSavedSettings.getBOOL("AutoSnapshot"); - lldebugs << "camera moved, updating thumbnail" << llendl; - previewp->updateSnapshot( - autosnap, // whether a new snapshot is needed or merely invalidate the existing one - FALSE, // or if 1st arg is false, whether to produce a new thumbnail image. - autosnap ? AUTO_SNAPSHOT_TIME_DELAY : 0.f); // shutter delay if 1st arg is true. - } - - // see if it's time yet to snap the shot and bomb out otherwise. - previewp->mSnapshotActive = - (previewp->mSnapshotDelayTimer.getStarted() && previewp->mSnapshotDelayTimer.hasExpired()) - && !LLToolCamera::getInstance()->hasMouseCapture(); // don't take snapshots while ALT-zoom active - if ( ! previewp->mSnapshotActive) - { - return FALSE; - } - - // time to produce a snapshot - previewp->setThumbnailImageSize(); - - lldebugs << "producing snapshot" << llendl; - if (!previewp->mPreviewImage) - { - previewp->mPreviewImage = new LLImageRaw; - } - - if (!previewp->mPreviewImageEncoded) - { - previewp->mPreviewImageEncoded = new LLImageRaw; - } - - previewp->setVisible(FALSE); - previewp->setEnabled(FALSE); - - previewp->getWindow()->incBusyCount(); - previewp->setImageScaled(FALSE); - - // grab the raw image and encode it into desired format - if(gViewerWindow->rawSnapshot( - previewp->mPreviewImage, - previewp->getWidth(), - previewp->getHeight(), - previewp->mKeepAspectRatio,//gSavedSettings.getBOOL("KeepAspectForSnapshot"), - previewp->getSnapshotType() == LLSnapshotLivePreview::SNAPSHOT_TEXTURE, - gSavedSettings.getBOOL("RenderUIInSnapshot"), - FALSE, - previewp->mSnapshotBufferType, - previewp->getMaxImageSize())) - { - previewp->mPreviewImageEncoded->resize( - previewp->mPreviewImage->getWidth(), - previewp->mPreviewImage->getHeight(), - previewp->mPreviewImage->getComponents()); - - if(previewp->getSnapshotType() == SNAPSHOT_TEXTURE) - { - lldebugs << "Encoding new image of format J2C" << llendl; - LLPointer<LLImageJ2C> formatted = new LLImageJ2C; - LLPointer<LLImageRaw> scaled = new LLImageRaw( - previewp->mPreviewImage->getData(), - previewp->mPreviewImage->getWidth(), - previewp->mPreviewImage->getHeight(), - previewp->mPreviewImage->getComponents()); - - scaled->biasedScaleToPowerOfTwo(MAX_TEXTURE_SIZE); - previewp->setImageScaled(TRUE); - if (formatted->encode(scaled, 0.f)) - { - previewp->mDataSize = formatted->getDataSize(); - formatted->decode(previewp->mPreviewImageEncoded, 0); - } - } - else - { - // delete any existing image - previewp->mFormattedImage = NULL; - // now create the new one of the appropriate format. - LLFloaterSnapshot::ESnapshotFormat format = previewp->getSnapshotFormat(); - lldebugs << "Encoding new image of format " << format << llendl; - - switch(format) - { - case LLFloaterSnapshot::SNAPSHOT_FORMAT_PNG: - previewp->mFormattedImage = new LLImagePNG(); - break; - case LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG: - previewp->mFormattedImage = new LLImageJPEG(previewp->mSnapshotQuality); - break; - case LLFloaterSnapshot::SNAPSHOT_FORMAT_BMP: - previewp->mFormattedImage = new LLImageBMP(); - break; - } - if (previewp->mFormattedImage->encode(previewp->mPreviewImage, 0)) - { - previewp->mDataSize = previewp->mFormattedImage->getDataSize(); - // special case BMP to copy instead of decode otherwise decode will crash. - if(format == LLFloaterSnapshot::SNAPSHOT_FORMAT_BMP) - { - previewp->mPreviewImageEncoded->copy(previewp->mPreviewImage); - } - else - { - previewp->mFormattedImage->decode(previewp->mPreviewImageEncoded, 0); - } - } - } - - LLPointer<LLImageRaw> scaled = new LLImageRaw( - previewp->mPreviewImageEncoded->getData(), - previewp->mPreviewImageEncoded->getWidth(), - previewp->mPreviewImageEncoded->getHeight(), - previewp->mPreviewImageEncoded->getComponents()); - - if(!scaled->isBufferInvalid()) - { - // leave original image dimensions, just scale up texture buffer - if (previewp->mPreviewImageEncoded->getWidth() > 1024 || previewp->mPreviewImageEncoded->getHeight() > 1024) - { - // go ahead and shrink image to appropriate power of 2 for display - scaled->biasedScaleToPowerOfTwo(1024); - previewp->setImageScaled(TRUE); - } - else - { - // expand image but keep original image data intact - scaled->expandToPowerOfTwo(1024, FALSE); - } - - previewp->mViewerImage[previewp->mCurImageIndex] = LLViewerTextureManager::getLocalTexture(scaled.get(), FALSE); - LLPointer<LLViewerTexture> curr_preview_image = previewp->mViewerImage[previewp->mCurImageIndex]; - gGL.getTexUnit(0)->bind(curr_preview_image); - if (previewp->getSnapshotType() != SNAPSHOT_TEXTURE) - { - curr_preview_image->setFilteringOption(LLTexUnit::TFO_POINT); - } - else - { - curr_preview_image->setFilteringOption(LLTexUnit::TFO_ANISOTROPIC); - } - curr_preview_image->setAddressMode(LLTexUnit::TAM_CLAMP); - - previewp->mSnapshotUpToDate = TRUE; - previewp->generateThumbnailImage(TRUE) ; - - previewp->mPosTakenGlobal = gAgentCamera.getCameraPositionGlobal(); - previewp->mShineCountdown = 4; // wait a few frames to avoid animation glitch due to readback this frame - } - } - previewp->getWindow()->decBusyCount(); - // only show fullscreen preview when in freeze frame mode - previewp->setVisible(gSavedSettings.getBOOL("UseFreezeFrame")); - previewp->mSnapshotDelayTimer.stop(); - previewp->mSnapshotActive = FALSE; - - if(!previewp->getThumbnailUpToDate()) - { - previewp->generateThumbnailImage() ; - } - lldebugs << "done creating snapshot" << llendl; - LLFloaterSnapshot::postUpdate(); - - return TRUE; -} - -void LLSnapshotLivePreview::setSize(S32 w, S32 h) -{ - lldebugs << "setSize(" << w << ", " << h << ")" << llendl; - setWidth(w); - setHeight(h); -} - -void LLSnapshotLivePreview::getSize(S32& w, S32& h) const -{ - w = getWidth(); - h = getHeight(); -} - -void LLSnapshotLivePreview::saveTexture() -{ - lldebugs << "saving texture: " << mPreviewImage->getWidth() << "x" << mPreviewImage->getHeight() << llendl; - // gen a new uuid for this asset - LLTransactionID tid; - tid.generate(); - LLAssetID new_asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); - - LLPointer<LLImageJ2C> formatted = new LLImageJ2C; - LLPointer<LLImageRaw> scaled = new LLImageRaw(mPreviewImage->getData(), - mPreviewImage->getWidth(), - mPreviewImage->getHeight(), - mPreviewImage->getComponents()); - - scaled->biasedScaleToPowerOfTwo(MAX_TEXTURE_SIZE); - lldebugs << "scaled texture to " << scaled->getWidth() << "x" << scaled->getHeight() << llendl; - - if (formatted->encode(scaled, 0.0f)) - { - LLVFile::writeFile(formatted->getData(), formatted->getDataSize(), gVFS, new_asset_id, LLAssetType::AT_TEXTURE); - std::string pos_string; - LLAgentUI::buildLocationString(pos_string, LLAgentUI::LOCATION_FORMAT_FULL); - std::string who_took_it; - LLAgentUI::buildFullname(who_took_it); - LLAssetStorage::LLStoreAssetCallback callback = NULL; - S32 expected_upload_cost = LLGlobalEconomy::Singleton::getInstance()->getPriceUpload(); - void *userdata = NULL; - upload_new_resource(tid, // tid - LLAssetType::AT_TEXTURE, - "Snapshot : " + pos_string, - "Taken by " + who_took_it + " at " + pos_string, - 0, - LLFolderType::FT_SNAPSHOT_CATEGORY, - LLInventoryType::IT_SNAPSHOT, - PERM_ALL, // Note: Snapshots to inventory is a special case of content upload - LLFloaterPerms::getGroupPerms(), // that is more permissive than other uploads - LLFloaterPerms::getEveryonePerms(), - "Snapshot : " + pos_string, - callback, expected_upload_cost, userdata); - gViewerWindow->playSnapshotAnimAndSound(); - } - else - { - LLNotificationsUtil::add("ErrorEncodingSnapshot"); - llwarns << "Error encoding snapshot" << llendl; - } - - LLViewerStats::getInstance()->incStat(LLViewerStats::ST_SNAPSHOT_COUNT ); - - mDataSize = 0; -} - -BOOL LLSnapshotLivePreview::saveLocal() -{ - BOOL success = gViewerWindow->saveImageNumbered(mFormattedImage); - - if(success) - { - gViewerWindow->playSnapshotAnimAndSound(); - } - return success; -} - -void LLSnapshotLivePreview::saveWeb() -{ - // *FIX: Will break if the window closes because of CloseSnapshotOnKeep! - // Needs to pass on ownership of the image. - LLImageJPEG* jpg = dynamic_cast<LLImageJPEG*>(mFormattedImage.get()); - if(!jpg) - { - llwarns << "Formatted image not a JPEG" << llendl; - return; - } - - LLSD metadata; - metadata["description"] = getChild<LLLineEditor>("description")->getText(); - - LLLandmarkActions::getRegionNameAndCoordsFromPosGlobal(gAgentCamera.getCameraPositionGlobal(), - boost::bind(&LLSnapshotLivePreview::regionNameCallback, this, jpg, metadata, _1, _2, _3, _4)); - - gViewerWindow->playSnapshotAnimAndSound(); -} - -void LLSnapshotLivePreview::regionNameCallback(LLImageJPEG* snapshot, LLSD& metadata, const std::string& name, S32 x, S32 y, S32 z) -{ - metadata["slurl"] = LLSLURL(name, LLVector3d(x, y, z)).getSLURLString(); - - LLWebSharing::instance().shareSnapshot(snapshot, metadata); -} ///---------------------------------------------------------------------------- /// Class LLFloaterSnapshot::Impl @@ -2037,7 +1063,7 @@ BOOL LLFloaterSnapshot::postBuild() getChild<LLUICtrl>("auto_snapshot_check")->setValue(gSavedSettings.getBOOL("AutoSnapshot")); childSetCommitCallback("auto_snapshot_check", Impl::onClickAutoSnap, this); - + LLWebProfile::setImageUploadResultCallback(boost::bind(&LLFloaterSnapshot::Impl::onSnapshotUploadFinished, _1)); LLPostCard::setPostResultCallback(boost::bind(&LLFloaterSnapshot::Impl::onSendingPostcardFinished, _1)); @@ -2070,6 +1096,9 @@ BOOL LLFloaterSnapshot::postBuild() impl.updateControls(this); impl.updateLayout(this); + + previewp->setThumbnailPlaceholderRect(getThumbnailPlaceholderRect()); + return TRUE; } @@ -2235,7 +1264,9 @@ S32 LLFloaterSnapshot::notify(const LLSD& info) void LLFloaterSnapshot::update() { LLFloaterSnapshot* inst = LLFloaterReg::findTypedInstance<LLFloaterSnapshot>("snapshot"); - if (!inst) + LLFloaterSocial* floater_social = LLFloaterReg::findTypedInstance<LLFloaterSocial>("social"); + + if (!inst && !floater_social) return; BOOL changed = FALSE; @@ -2245,7 +1276,8 @@ void LLFloaterSnapshot::update() { changed |= LLSnapshotLivePreview::onIdle(*iter); } - if(changed) + + if (inst && changed) { lldebugs << "changed" << llendl; inst->impl.updateControls(inst); diff --git a/indra/newview/llfloatersnapshot.h b/indra/newview/llfloatersnapshot.h index afe135fa40..82af8c7a9d 100755 --- a/indra/newview/llfloatersnapshot.h +++ b/indra/newview/llfloatersnapshot.h @@ -27,7 +27,6 @@ #ifndef LL_LLFLOATERSNAPSHOT_H #define LL_LLFLOATERSNAPSHOT_H -#include "llimage.h" #include "llfloater.h" class LLSpinCtrl; diff --git a/indra/newview/llfloatersocial.cpp b/indra/newview/llfloatersocial.cpp new file mode 100644 index 0000000000..2a74c8e3ea --- /dev/null +++ b/indra/newview/llfloatersocial.cpp @@ -0,0 +1,920 @@ +/** +* @file llfloatersocial.cpp +* @brief Implementation of llfloatersocial +* @author Gilbert@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, 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 "llfloatersocial.h" + +#include "llagent.h" +#include "llagentui.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llfacebookconnect.h" +#include "llfloaterreg.h" +#include "lliconctrl.h" +#include "llresmgr.h" // LLLocale +#include "llsdserialize.h" +#include "llloadingindicator.h" +#include "llplugincookiestore.h" +#include "llslurl.h" +#include "lltrans.h" +#include "llsnapshotlivepreview.h" +#include "llviewerregion.h" +#include "llviewercontrol.h" +#include "llviewermedia.h" + +static LLRegisterPanelClassWrapper<LLSocialStatusPanel> t_panel_status("llsocialstatuspanel"); +static LLRegisterPanelClassWrapper<LLSocialPhotoPanel> t_panel_photo("llsocialphotopanel"); +static LLRegisterPanelClassWrapper<LLSocialCheckinPanel> t_panel_checkin("llsocialcheckinpanel"); +static LLRegisterPanelClassWrapper<LLSocialAccountPanel> t_panel_account("llsocialaccountpanel"); + +const S32 MAX_POSTCARD_DATASIZE = 1024 * 1024; // one megabyte +const std::string DEFAULT_CHECKIN_LOCATION_URL = "http://maps.secondlife.com/"; +const std::string DEFAULT_CHECKIN_ICON_URL = "http://map.secondlife.com.s3.amazonaws.com/map_placeholder.png"; +const std::string DEFAULT_CHECKIN_QUERY_PARAMETERS = "?sourceid=slshare_checkin&utm_source=facebook&utm_medium=checkin&utm_campaign=slshare"; +const std::string DEFAULT_PHOTO_QUERY_PARAMETERS = "?sourceid=slshare_photo&utm_source=facebook&utm_medium=photo&utm_campaign=slshare"; + +std::string get_map_url() +{ + LLVector3d center_agent; + if (gAgent.getRegion()) + { + center_agent = gAgent.getRegion()->getCenterGlobal(); + } + int x_pos = center_agent[0] / 256.0; + int y_pos = center_agent[1] / 256.0; + std::string map_url = gSavedSettings.getString("CurrentMapServerURL") + llformat("map-1-%d-%d-objects.jpg", x_pos, y_pos); + return map_url; +} + +/////////////////////////// +//LLSocialStatusPanel////// +/////////////////////////// + +LLSocialStatusPanel::LLSocialStatusPanel() : + mMessageTextEditor(NULL), + mPostButton(NULL), + mCancelButton(NULL) +{ + mCommitCallbackRegistrar.add("SocialSharing.SendStatus", boost::bind(&LLSocialStatusPanel::onSend, this)); +} + +BOOL LLSocialStatusPanel::postBuild() +{ + mMessageTextEditor = getChild<LLUICtrl>("status_message"); + mPostButton = getChild<LLUICtrl>("post_status_btn"); + mCancelButton = getChild<LLUICtrl>("cancel_status_btn"); + + return LLPanel::postBuild(); +} + +void LLSocialStatusPanel::draw() +{ + if (mMessageTextEditor && mPostButton && mCancelButton) + { + bool no_ongoing_connection = !(LLFacebookConnect::instance().isTransactionOngoing()); + std::string message = mMessageTextEditor->getValue().asString(); + mMessageTextEditor->setEnabled(no_ongoing_connection); + mCancelButton->setEnabled(no_ongoing_connection); + mPostButton->setEnabled(no_ongoing_connection && !message.empty()); + } + + LLPanel::draw(); +} + +void LLSocialStatusPanel::onSend() +{ + LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLSocialStatusPanel"); // just in case it is already listening + LLEventPumps::instance().obtain("FacebookConnectState").listen("LLSocialStatusPanel", boost::bind(&LLSocialStatusPanel::onFacebookConnectStateChange, this, _1)); + + // Connect to Facebook if necessary and then post + if (LLFacebookConnect::instance().isConnected()) + { + sendStatus(); + } + else + { + LLFacebookConnect::instance().checkConnectionToFacebook(true); + } +} + +bool LLSocialStatusPanel::onFacebookConnectStateChange(const LLSD& data) +{ + switch (data.get("enum").asInteger()) + { + case LLFacebookConnect::FB_CONNECTED: + sendStatus(); + break; + + case LLFacebookConnect::FB_POSTED: + LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLSocialStatusPanel"); + clearAndClose(); + break; + } + + return false; +} + +void LLSocialStatusPanel::sendStatus() +{ + std::string message = mMessageTextEditor->getValue().asString(); + if (!message.empty()) + { + LLFacebookConnect::instance().updateStatus(message); + } +} + +void LLSocialStatusPanel::clearAndClose() +{ + mMessageTextEditor->setValue(""); + + LLFloater* floater = getParentByType<LLFloater>(); + if (floater) + { + floater->closeFloater(); + } +} + +/////////////////////////// +//LLSocialPhotoPanel/////// +/////////////////////////// + +LLSocialPhotoPanel::LLSocialPhotoPanel() : +mSnapshotPanel(NULL), +mResolutionComboBox(NULL), +mRefreshBtn(NULL), +mWorkingLabel(NULL), +mThumbnailPlaceholder(NULL), +mCaptionTextBox(NULL), +mLocationCheckbox(NULL), +mPostButton(NULL) +{ + mCommitCallbackRegistrar.add("SocialSharing.SendPhoto", boost::bind(&LLSocialPhotoPanel::onSend, this)); + mCommitCallbackRegistrar.add("SocialSharing.RefreshPhoto", boost::bind(&LLSocialPhotoPanel::onClickNewSnapshot, this)); +} + +LLSocialPhotoPanel::~LLSocialPhotoPanel() +{ + if(mPreviewHandle.get()) + { + mPreviewHandle.get()->die(); + } +} + +BOOL LLSocialPhotoPanel::postBuild() +{ + setVisibleCallback(boost::bind(&LLSocialPhotoPanel::onVisibilityChange, this, _2)); + + mSnapshotPanel = getChild<LLUICtrl>("snapshot_panel"); + mResolutionComboBox = getChild<LLUICtrl>("resolution_combobox"); + mResolutionComboBox->setCommitCallback(boost::bind(&LLSocialPhotoPanel::updateResolution, this, TRUE)); + mRefreshBtn = getChild<LLUICtrl>("new_snapshot_btn"); + mWorkingLabel = getChild<LLUICtrl>("working_lbl"); + mThumbnailPlaceholder = getChild<LLUICtrl>("thumbnail_placeholder"); + mCaptionTextBox = getChild<LLUICtrl>("photo_caption"); + mLocationCheckbox = getChild<LLUICtrl>("add_location_cb"); + mPostButton = getChild<LLUICtrl>("post_photo_btn"); + mCancelButton = getChild<LLUICtrl>("cancel_photo_btn"); + + return LLPanel::postBuild(); +} + +void LLSocialPhotoPanel::draw() +{ + LLSnapshotLivePreview * previewp = static_cast<LLSnapshotLivePreview *>(mPreviewHandle.get()); + + // Enable interaction only if no transaction with the service is on-going (prevent duplicated posts) + bool no_ongoing_connection = !(LLFacebookConnect::instance().isTransactionOngoing()); + mCancelButton->setEnabled(no_ongoing_connection); + mCaptionTextBox->setEnabled(no_ongoing_connection); + mResolutionComboBox->setEnabled(no_ongoing_connection); + mRefreshBtn->setEnabled(no_ongoing_connection); + mLocationCheckbox->setEnabled(no_ongoing_connection); + + // Display the preview if one is available + if (previewp && previewp->getThumbnailImage()) + { + const LLRect& thumbnail_rect = mThumbnailPlaceholder->getRect(); + const S32 thumbnail_w = previewp->getThumbnailWidth(); + const S32 thumbnail_h = previewp->getThumbnailHeight(); + + // calc preview offset within the preview rect + const S32 local_offset_x = (thumbnail_rect.getWidth() - thumbnail_w) / 2 ; + const S32 local_offset_y = (thumbnail_rect.getHeight() - thumbnail_h) / 2 ; + + // calc preview offset within the floater rect + // Hack : To get the full offset, we need to take into account each and every offset of each widgets up to the floater. + // This is almost as arbitrary as using a fixed offset so that's what we do here for the sake of simplicity. + // *TODO : Get the offset looking through the hierarchy of widgets, should be done in postBuild() so to avoid traversing the hierarchy each time. + S32 offset_x = thumbnail_rect.mLeft + local_offset_x - 1; + S32 offset_y = thumbnail_rect.mBottom + local_offset_y - 39; + + mSnapshotPanel->localPointToOtherView(offset_x, offset_y, &offset_x, &offset_y, getParentByType<LLFloater>()); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + // Apply floater transparency to the texture unless the floater is focused. + F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); + LLColor4 color = LLColor4::white; + gl_draw_scaled_image(offset_x, offset_y, + thumbnail_w, thumbnail_h, + previewp->getThumbnailImage(), color % alpha); + + previewp->drawPreviewRect(offset_x, offset_y) ; + } + + // Update the visibility of the working (computing preview) label + mWorkingLabel->setVisible(!(previewp && previewp->getSnapshotUpToDate())); + + // Enable Post if we have a preview to send and no on going connection being processed + mPostButton->setEnabled(no_ongoing_connection && (previewp && previewp->getSnapshotUpToDate())); + + // Draw the rest of the panel on top of it + LLPanel::draw(); +} + +LLSnapshotLivePreview* LLSocialPhotoPanel::getPreviewView() +{ + LLSnapshotLivePreview* previewp = (LLSnapshotLivePreview*)mPreviewHandle.get(); + return previewp; +} + +void LLSocialPhotoPanel::onVisibilityChange(const LLSD& new_visibility) +{ + bool visible = new_visibility.asBoolean(); + if (visible) + { + if (mPreviewHandle.get()) + { + LLSnapshotLivePreview* preview = getPreviewView(); + if(preview) + { + lldebugs << "opened, updating snapshot" << llendl; + preview->updateSnapshot(TRUE); + } + } + else + { + LLRect full_screen_rect = getRootView()->getRect(); + LLSnapshotLivePreview::Params p; + p.rect(full_screen_rect); + LLSnapshotLivePreview* previewp = new LLSnapshotLivePreview(p); + mPreviewHandle = previewp->getHandle(); + + previewp->setSnapshotType(previewp->SNAPSHOT_WEB); + previewp->setSnapshotFormat(LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG); + //previewp->setSnapshotQuality(98); + previewp->setThumbnailPlaceholderRect(mThumbnailPlaceholder->getRect()); + + updateControls(); + } + } +} + +void LLSocialPhotoPanel::onClickNewSnapshot() +{ + LLSnapshotLivePreview* previewp = getPreviewView(); + if (previewp) + { + //setStatus(Impl::STATUS_READY); + lldebugs << "updating snapshot" << llendl; + previewp->updateSnapshot(TRUE); + } +} + +void LLSocialPhotoPanel::onSend() +{ + LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLSocialPhotoPanel"); // just in case it is already listening + LLEventPumps::instance().obtain("FacebookConnectState").listen("LLSocialPhotoPanel", boost::bind(&LLSocialPhotoPanel::onFacebookConnectStateChange, this, _1)); + + // Connect to Facebook if necessary and then post + if (LLFacebookConnect::instance().isConnected()) + { + sendPhoto(); + } + else + { + LLFacebookConnect::instance().checkConnectionToFacebook(true); + } +} + +bool LLSocialPhotoPanel::onFacebookConnectStateChange(const LLSD& data) +{ + switch (data.get("enum").asInteger()) + { + case LLFacebookConnect::FB_CONNECTED: + sendPhoto(); + break; + + case LLFacebookConnect::FB_POSTED: + LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLSocialPhotoPanel"); + clearAndClose(); + break; + } + + return false; +} + +void LLSocialPhotoPanel::sendPhoto() +{ + // Get the caption + std::string caption = mCaptionTextBox->getValue().asString(); + + // Add the location if required + bool add_location = mLocationCheckbox->getValue().asBoolean(); + if (add_location) + { + // Get the SLURL for the location + LLSLURL slurl; + LLAgentUI::buildSLURL(slurl); + std::string slurl_string = slurl.getSLURLString(); + + // Add query parameters so Google Analytics can track incoming clicks! + slurl_string += DEFAULT_PHOTO_QUERY_PARAMETERS; + + // Add it to the caption (pretty crude, but we don't have a better option with photos) + if (caption.empty()) + caption = slurl_string; + else + caption = caption + " " + slurl_string; + } + + // Get the image + LLSnapshotLivePreview* previewp = getPreviewView(); + + // Post to Facebook + LLFacebookConnect::instance().sharePhoto(previewp->getFormattedImage(), caption); + + updateControls(); +} + +void LLSocialPhotoPanel::clearAndClose() +{ + mCaptionTextBox->setValue(""); + + LLFloater* floater = getParentByType<LLFloater>(); + if (floater) + { + floater->closeFloater(); + } +} + +void LLSocialPhotoPanel::updateControls() +{ + LLSnapshotLivePreview* previewp = getPreviewView(); + BOOL got_bytes = previewp && previewp->getDataSize() > 0; + BOOL got_snap = previewp && previewp->getSnapshotUpToDate(); + LLSnapshotLivePreview::ESnapshotType shot_type = (previewp ? previewp->getSnapshotType() : LLSnapshotLivePreview::SNAPSHOT_POSTCARD); + + // *TODO: Separate maximum size for Web images from postcards + lldebugs << "Is snapshot up-to-date? " << got_snap << llendl; + + LLLocale locale(LLLocale::USER_LOCALE); + std::string bytes_string; + if (got_snap) + { + LLResMgr::getInstance()->getIntegerString(bytes_string, (previewp->getDataSize()) >> 10 ); + } + + //getChild<LLUICtrl>("file_size_label")->setTextArg("[SIZE]", got_snap ? bytes_string : getString("unknown")); <---uses localized string + getChild<LLUICtrl>("file_size_label")->setTextArg("[SIZE]", got_snap ? bytes_string : "unknown"); + getChild<LLUICtrl>("file_size_label")->setColor( + shot_type == LLSnapshotLivePreview::SNAPSHOT_POSTCARD + && got_bytes + && previewp->getDataSize() > MAX_POSTCARD_DATASIZE ? LLUIColor(LLColor4::red) : LLUIColorTable::instance().getColor( "LabelTextColor" )); + + updateResolution(FALSE); +} + +void LLSocialPhotoPanel::updateResolution(BOOL do_update) +{ + LLComboBox* combobox = static_cast<LLComboBox *>(mResolutionComboBox); + + std::string sdstring = combobox->getSelectedValue(); + LLSD sdres; + std::stringstream sstream(sdstring); + LLSDSerialize::fromNotation(sdres, sstream, sdstring.size()); + + S32 width = sdres[0]; + S32 height = sdres[1]; + + LLSnapshotLivePreview * previewp = static_cast<LLSnapshotLivePreview *>(mPreviewHandle.get()); + if (previewp && combobox->getCurrentIndex() >= 0) + { + S32 original_width = 0 , original_height = 0 ; + previewp->getSize(original_width, original_height) ; + + if (width == 0 || height == 0) + { + // take resolution from current window size + lldebugs << "Setting preview res from window: " << gViewerWindow->getWindowWidthRaw() << "x" << gViewerWindow->getWindowHeightRaw() << llendl; + previewp->setSize(gViewerWindow->getWindowWidthRaw(), gViewerWindow->getWindowHeightRaw()); + } + else + { + // use the resolution from the selected pre-canned drop-down choice + lldebugs << "Setting preview res selected from combo: " << width << "x" << height << llendl; + previewp->setSize(width, height); + } + + checkAspectRatio(width); + + previewp->getSize(width, height); + + if(original_width != width || original_height != height) + { + previewp->setSize(width, height); + + // hide old preview as the aspect ratio could be wrong + lldebugs << "updating thumbnail" << llendl; + + previewp->updateSnapshot(FALSE, TRUE); + if(do_update) + { + lldebugs << "Will update controls" << llendl; + updateControls(); + LLSocialPhotoPanel::onClickNewSnapshot(); + } + } + + } +} + +void LLSocialPhotoPanel::checkAspectRatio(S32 index) +{ + LLSnapshotLivePreview *previewp = getPreviewView() ; + + BOOL keep_aspect = FALSE; + + if (0 == index) // current window size + { + keep_aspect = TRUE; + } + else // predefined resolution + { + keep_aspect = FALSE; + } + + if (previewp) + { + previewp->mKeepAspectRatio = keep_aspect; + } +} + +LLUICtrl* LLSocialPhotoPanel::getRefreshBtn() +{ + return mRefreshBtn; +} + +//////////////////////// +//LLSocialCheckinPanel// +//////////////////////// + +LLSocialCheckinPanel::LLSocialCheckinPanel() : + mMapUrl(""), + mReloadingMapTexture(false) +{ + mCommitCallbackRegistrar.add("SocialSharing.SendCheckin", boost::bind(&LLSocialCheckinPanel::onSend, this)); +} + +BOOL LLSocialCheckinPanel::postBuild() +{ + // Keep pointers to widgets so we don't traverse the UI hierarchy too often + mPostButton = getChild<LLUICtrl>("post_place_btn"); + mCancelButton = getChild<LLUICtrl>("cancel_place_btn"); + mMessageTextEditor = getChild<LLUICtrl>("place_caption"); + mMapLoadingIndicator = getChild<LLUICtrl>("map_loading_indicator"); + mMapPlaceholder = getChild<LLIconCtrl>("map_placeholder"); + mMapDefault = getChild<LLIconCtrl>("map_default"); + mMapCheckBox = getChild<LLCheckBoxCtrl>("add_place_view_cb"); + + return LLPanel::postBuild(); +} + +void LLSocialCheckinPanel::draw() +{ + bool no_ongoing_connection = !(LLFacebookConnect::instance().isTransactionOngoing()); + mPostButton->setEnabled(no_ongoing_connection); + mCancelButton->setEnabled(no_ongoing_connection); + mMessageTextEditor->setEnabled(no_ongoing_connection); + mMapCheckBox->setEnabled(no_ongoing_connection); + + std::string map_url = get_map_url(); + // Did we change location? + if (map_url != mMapUrl) + { + mMapUrl = map_url; + // Load the map tile + mMapTexture = LLViewerTextureManager::getFetchedTextureFromUrl(mMapUrl, FTT_MAP_TILE, TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + mMapTexture->setBoostLevel(LLGLTexture::BOOST_MAP); + mReloadingMapTexture = true; + // In the meantime, put the "loading" indicator on, hide the tile map and disable the checkbox + mMapLoadingIndicator->setVisible(true); + mMapPlaceholder->setVisible(false); + } + // Are we done loading the map tile? + if (mReloadingMapTexture && mMapTexture->isFullyLoaded()) + { + // Don't do it again next time around + mReloadingMapTexture = false; + // Convert the map texture to the appropriate image object + LLPointer<LLUIImage> ui_image = new LLUIImage(mMapUrl, mMapTexture); + // Load the map widget with the correct map tile image + mMapPlaceholder->setImage(ui_image); + // Now hide the loading indicator, bring the tile in view and reenable the checkbox with its previous value + mMapLoadingIndicator->setVisible(false); + mMapPlaceholder->setVisible(true); + } + // Show the default icon if that's the checkbox value (the real one...) + // This will hide/show the loading indicator and/or tile underneath + mMapDefault->setVisible(!(mMapCheckBox->get())); + + LLPanel::draw(); +} + +void LLSocialCheckinPanel::onSend() +{ + LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLSocialCheckinPanel"); // just in case it is already listening + LLEventPumps::instance().obtain("FacebookConnectState").listen("LLSocialCheckinPanel", boost::bind(&LLSocialCheckinPanel::onFacebookConnectStateChange, this, _1)); + + // Connect to Facebook if necessary and then post + if (LLFacebookConnect::instance().isConnected()) + { + sendCheckin(); + } + else + { + LLFacebookConnect::instance().checkConnectionToFacebook(true); + } +} + +bool LLSocialCheckinPanel::onFacebookConnectStateChange(const LLSD& data) +{ + switch (data.get("enum").asInteger()) + { + case LLFacebookConnect::FB_CONNECTED: + sendCheckin(); + break; + + case LLFacebookConnect::FB_POSTED: + LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLSocialCheckinPanel"); + clearAndClose(); + break; + } + + return false; +} + +void LLSocialCheckinPanel::sendCheckin() +{ + // Get the location SLURL + LLSLURL slurl; + LLAgentUI::buildSLURL(slurl); + std::string slurl_string = slurl.getSLURLString(); + + // Use a valid http:// URL if the scheme is secondlife:// + LLURI slurl_uri(slurl_string); + if (slurl_uri.scheme() == LLSLURL::SLURL_SECONDLIFE_SCHEME) + { + slurl_string = DEFAULT_CHECKIN_LOCATION_URL; + } + + // Add query parameters so Google Analytics can track incoming clicks! + slurl_string += DEFAULT_CHECKIN_QUERY_PARAMETERS; + + // Get the region name + std::string region_name = gAgent.getRegion()->getName(); + + // Get the region description + std::string description; + LLAgentUI::buildLocationString(description, LLAgentUI::LOCATION_FORMAT_NORMAL_COORDS, gAgent.getPositionAgent()); + + // Optionally add the region map view + bool add_map_view = mMapCheckBox->getValue().asBoolean(); + std::string map_url = (add_map_view ? get_map_url() : DEFAULT_CHECKIN_ICON_URL); + + // Get the caption + std::string caption = mMessageTextEditor->getValue().asString(); + + // Post to Facebook + LLFacebookConnect::instance().postCheckin(slurl_string, region_name, description, map_url, caption); +} + +void LLSocialCheckinPanel::clearAndClose() +{ + mMessageTextEditor->setValue(""); + + LLFloater* floater = getParentByType<LLFloater>(); + if (floater) + { + floater->closeFloater(); + } +} + +/////////////////////////// +//LLSocialAccountPanel////// +/////////////////////////// + +LLSocialAccountPanel::LLSocialAccountPanel() : +mAccountCaptionLabel(NULL), +mAccountNameLabel(NULL), +mPanelButtons(NULL), +mConnectButton(NULL), +mDisconnectButton(NULL) +{ + mCommitCallbackRegistrar.add("SocialSharing.Connect", boost::bind(&LLSocialAccountPanel::onConnect, this)); + mCommitCallbackRegistrar.add("SocialSharing.Disconnect", boost::bind(&LLSocialAccountPanel::onDisconnect, this)); + + setVisibleCallback(boost::bind(&LLSocialAccountPanel::onVisibilityChange, this, _2)); +} + +BOOL LLSocialAccountPanel::postBuild() +{ + mAccountCaptionLabel = getChild<LLTextBox>("account_caption_label"); + mAccountNameLabel = getChild<LLTextBox>("account_name_label"); + mPanelButtons = getChild<LLUICtrl>("panel_buttons"); + mConnectButton = getChild<LLUICtrl>("connect_btn"); + mDisconnectButton = getChild<LLUICtrl>("disconnect_btn"); + + return LLPanel::postBuild(); +} + +void LLSocialAccountPanel::draw() +{ + LLFacebookConnect::EConnectionState connection_state = LLFacebookConnect::instance().getConnectionState(); + + //Disable the 'disconnect' button and the 'use another account' button when disconnecting in progress + bool disconnecting = connection_state == LLFacebookConnect::FB_DISCONNECTING; + mDisconnectButton->setEnabled(!disconnecting); + + //Disable the 'connect' button when a connection is in progress + bool connecting = connection_state == LLFacebookConnect::FB_CONNECTION_IN_PROGRESS; + mConnectButton->setEnabled(!connecting); + + LLPanel::draw(); +} + +void LLSocialAccountPanel::onVisibilityChange(const LLSD& new_visibility) +{ + bool visible = new_visibility.asBoolean(); + + if(visible) + { + LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLSocialAccountPanel"); + LLEventPumps::instance().obtain("FacebookConnectState").listen("LLSocialAccountPanel", boost::bind(&LLSocialAccountPanel::onFacebookConnectStateChange, this, _1)); + + LLEventPumps::instance().obtain("FacebookConnectInfo").stopListening("LLSocialAccountPanel"); + LLEventPumps::instance().obtain("FacebookConnectInfo").listen("LLSocialAccountPanel", boost::bind(&LLSocialAccountPanel::onFacebookConnectInfoChange, this)); + + //Connected + if(LLFacebookConnect::instance().isConnected()) + { + showConnectedLayout(); + } + //Check if connected (show disconnected layout in meantime) + else + { + showDisconnectedLayout(); + } + if ((LLFacebookConnect::instance().getConnectionState() == LLFacebookConnect::FB_NOT_CONNECTED) || + (LLFacebookConnect::instance().getConnectionState() == LLFacebookConnect::FB_CONNECTION_FAILED)) + { + LLFacebookConnect::instance().checkConnectionToFacebook(); + } + } + else + { + LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLSocialAccountPanel"); + LLEventPumps::instance().obtain("FacebookConnectInfo").stopListening("LLSocialAccountPanel"); + } +} + +bool LLSocialAccountPanel::onFacebookConnectStateChange(const LLSD& data) +{ + if(LLFacebookConnect::instance().isConnected()) + { + //In process of disconnecting so leave the layout as is + if(data.get("enum").asInteger() != LLFacebookConnect::FB_DISCONNECTING) + { + showConnectedLayout(); + } + } + else + { + showDisconnectedLayout(); + } + + return false; +} + +bool LLSocialAccountPanel::onFacebookConnectInfoChange() +{ + LLSD info = LLFacebookConnect::instance().getInfo(); + std::string clickable_name; + + //Strings of format [http://www.somewebsite.com Click Me] become clickable text + if(info.has("link") && info.has("name")) + { + clickable_name = "[" + info["link"].asString() + " " + info["name"].asString() + "]"; + } + + mAccountNameLabel->setText(clickable_name); + + return false; +} + +void LLSocialAccountPanel::showConnectButton() +{ + if(!mConnectButton->getVisible()) + { + mConnectButton->setVisible(TRUE); + mDisconnectButton->setVisible(FALSE); + } +} + +void LLSocialAccountPanel::hideConnectButton() +{ + if(mConnectButton->getVisible()) + { + mConnectButton->setVisible(FALSE); + mDisconnectButton->setVisible(TRUE); + } +} + +void LLSocialAccountPanel::showDisconnectedLayout() +{ + mAccountCaptionLabel->setText(getString("facebook_disconnected")); + mAccountNameLabel->setText(std::string("")); + showConnectButton(); +} + +void LLSocialAccountPanel::showConnectedLayout() +{ + LLFacebookConnect::instance().loadFacebookInfo(); + + mAccountCaptionLabel->setText(getString("facebook_connected")); + hideConnectButton(); +} + +void LLSocialAccountPanel::onConnect() +{ + LLFacebookConnect::instance().checkConnectionToFacebook(true); + + //Clear only the facebook browser cookies so that the facebook login screen appears + LLViewerMedia::getCookieStore()->removeCookiesByDomain(".facebook.com"); +} + +void LLSocialAccountPanel::onDisconnect() +{ + LLFacebookConnect::instance().disconnectFromFacebook(); + + LLViewerMedia::getCookieStore()->removeCookiesByDomain(".facebook.com"); +} + +//////////////////////// +//LLFloaterSocial/////// +//////////////////////// + +LLFloaterSocial::LLFloaterSocial(const LLSD& key) : LLFloater(key), + mSocialPhotoPanel(NULL), + mStatusErrorText(NULL), + mStatusLoadingText(NULL), + mStatusLoadingIndicator(NULL) +{ + mCommitCallbackRegistrar.add("SocialSharing.Cancel", boost::bind(&LLFloaterSocial::onCancel, this)); +} + +void LLFloaterSocial::onCancel() +{ + closeFloater(); +} + +BOOL LLFloaterSocial::postBuild() +{ + // Keep tab of the Photo Panel + mSocialPhotoPanel = static_cast<LLSocialPhotoPanel*>(getChild<LLUICtrl>("panel_social_photo")); + // Connection status widgets + mStatusErrorText = getChild<LLTextBox>("connection_error_text"); + mStatusLoadingText = getChild<LLTextBox>("connection_loading_text"); + mStatusLoadingIndicator = getChild<LLUICtrl>("connection_loading_indicator"); + return LLFloater::postBuild(); +} + +// static +void LLFloaterSocial::preUpdate() +{ + LLFloaterSocial* instance = LLFloaterReg::findTypedInstance<LLFloaterSocial>("social"); + if (instance) + { + //Will set file size text to 'unknown' + instance->mSocialPhotoPanel->updateControls(); + } +} + +// static +void LLFloaterSocial::postUpdate() +{ + LLFloaterSocial* instance = LLFloaterReg::findTypedInstance<LLFloaterSocial>("social"); + if (instance) + { + //Will set the file size text + instance->mSocialPhotoPanel->updateControls(); + + // The refresh button is initially hidden. We show it after the first update, + // i.e. after snapshot is taken + LLUICtrl * refresh_button = instance->mSocialPhotoPanel->getRefreshBtn(); + + if (!refresh_button->getVisible()) + { + refresh_button->setVisible(true); + } + + } +} + +void LLFloaterSocial::draw() +{ + if (mStatusErrorText && mStatusLoadingText && mStatusLoadingIndicator) + { + mStatusErrorText->setVisible(false); + mStatusLoadingText->setVisible(false); + mStatusLoadingIndicator->setVisible(false); + LLFacebookConnect::EConnectionState connection_state = LLFacebookConnect::instance().getConnectionState(); + std::string status_text; + + switch (connection_state) + { + case LLFacebookConnect::FB_NOT_CONNECTED: + // No status displayed when first opening the panel and no connection done + case LLFacebookConnect::FB_CONNECTED: + // When successfully connected, no message is displayed + case LLFacebookConnect::FB_POSTED: + // No success message to show since we actually close the floater after successful posting completion + break; + case LLFacebookConnect::FB_CONNECTION_IN_PROGRESS: + // Connection loading indicator + mStatusLoadingText->setVisible(true); + status_text = LLTrans::getString("SocialFacebookConnecting"); + mStatusLoadingText->setValue(status_text); + mStatusLoadingIndicator->setVisible(true); + break; + case LLFacebookConnect::FB_POSTING: + // Posting indicator + mStatusLoadingText->setVisible(true); + status_text = LLTrans::getString("SocialFacebookPosting"); + mStatusLoadingText->setValue(status_text); + mStatusLoadingIndicator->setVisible(true); + break; + case LLFacebookConnect::FB_CONNECTION_FAILED: + // Error connecting to the service + mStatusErrorText->setVisible(true); + status_text = LLTrans::getString("SocialFacebookErrorConnecting"); + mStatusErrorText->setValue(status_text); + break; + case LLFacebookConnect::FB_POST_FAILED: + // Error posting to the service + mStatusErrorText->setVisible(true); + status_text = LLTrans::getString("SocialFacebookErrorPosting"); + mStatusErrorText->setValue(status_text); + break; + case LLFacebookConnect::FB_DISCONNECTING: + // Disconnecting loading indicator + mStatusLoadingText->setVisible(true); + status_text = LLTrans::getString("SocialFacebookDisconnecting"); + mStatusLoadingText->setValue(status_text); + mStatusLoadingIndicator->setVisible(true); + break; + case LLFacebookConnect::FB_DISCONNECT_FAILED: + // Error disconnecting from the service + mStatusErrorText->setVisible(true); + status_text = LLTrans::getString("SocialFacebookErrorDisconnecting"); + mStatusErrorText->setValue(status_text); + break; + } + } + LLFloater::draw(); +} + diff --git a/indra/newview/llfloatersocial.h b/indra/newview/llfloatersocial.h new file mode 100644 index 0000000000..bbe07c9704 --- /dev/null +++ b/indra/newview/llfloatersocial.h @@ -0,0 +1,165 @@ +/** +* @file llfloatersocial.h +* @brief Header file for llfloatersocial +* @author Gilbert@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, 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_LLFLOATERSOCIAL_H +#define LL_LLFLOATERSOCIAL_H + +#include "llfloater.h" +#include "lltextbox.h" +#include "llviewertexture.h" + +class LLIconCtrl; +class LLCheckBoxCtrl; +class LLSnapshotLivePreview; + +class LLSocialStatusPanel : public LLPanel +{ +public: + LLSocialStatusPanel(); + BOOL postBuild(); + void draw(); + void onSend(); + bool onFacebookConnectStateChange(const LLSD& data); + + void sendStatus(); + void clearAndClose(); + +private: + LLUICtrl* mMessageTextEditor; + LLUICtrl* mPostButton; + LLUICtrl* mCancelButton; +}; + +class LLSocialPhotoPanel : public LLPanel +{ +public: + LLSocialPhotoPanel(); + ~LLSocialPhotoPanel(); + + BOOL postBuild(); + void draw(); + + LLSnapshotLivePreview* getPreviewView(); + void onVisibilityChange(const LLSD& new_visibility); + void onClickNewSnapshot(); + void onSend(); + bool onFacebookConnectStateChange(const LLSD& data); + + void sendPhoto(); + void clearAndClose(); + + void updateControls(); + void updateResolution(BOOL do_update); + void checkAspectRatio(S32 index); + LLUICtrl* getRefreshBtn(); + +private: + LLHandle<LLView> mPreviewHandle; + + LLUICtrl * mSnapshotPanel; + LLUICtrl * mResolutionComboBox; + LLUICtrl * mRefreshBtn; + LLUICtrl * mWorkingLabel; + LLUICtrl * mThumbnailPlaceholder; + LLUICtrl * mCaptionTextBox; + LLUICtrl * mLocationCheckbox; + LLUICtrl * mPostButton; + LLUICtrl* mCancelButton; +}; + +class LLSocialCheckinPanel : public LLPanel +{ +public: + LLSocialCheckinPanel(); + BOOL postBuild(); + void draw(); + void onSend(); + bool onFacebookConnectStateChange(const LLSD& data); + + void sendCheckin(); + void clearAndClose(); + +private: + std::string mMapUrl; + LLPointer<LLViewerFetchedTexture> mMapTexture; + LLUICtrl* mPostButton; + LLUICtrl* mCancelButton; + LLUICtrl* mMessageTextEditor; + LLUICtrl* mMapLoadingIndicator; + LLIconCtrl* mMapPlaceholder; + LLIconCtrl* mMapDefault; + LLCheckBoxCtrl* mMapCheckBox; + bool mReloadingMapTexture; +}; + +class LLSocialAccountPanel : public LLPanel +{ +public: + LLSocialAccountPanel(); + BOOL postBuild(); + void draw(); + +private: + void onVisibilityChange(const LLSD& new_visibility); + bool onFacebookConnectStateChange(const LLSD& data); + bool onFacebookConnectInfoChange(); + void onConnect(); + void onUseAnotherAccount(); + void onDisconnect(); + + void showConnectButton(); + void hideConnectButton(); + void showDisconnectedLayout(); + void showConnectedLayout(); + + LLTextBox * mAccountCaptionLabel; + LLTextBox * mAccountNameLabel; + LLUICtrl * mPanelButtons; + LLUICtrl * mConnectButton; + LLUICtrl * mDisconnectButton; +}; + + +class LLFloaterSocial : public LLFloater +{ +public: + LLFloaterSocial(const LLSD& key); + BOOL postBuild(); + void draw(); + void onCancel(); + + static void preUpdate(); + static void postUpdate(); + +private: + LLSocialPhotoPanel* mSocialPhotoPanel; + LLTextBox* mStatusErrorText; + LLTextBox* mStatusLoadingText; + LLUICtrl* mStatusLoadingIndicator; +}; + +#endif // LL_LLFLOATERSOCIAL_H + diff --git a/indra/newview/llfloaterwebcontent.cpp b/indra/newview/llfloaterwebcontent.cpp index 21b171446f..3e3ac7eb90 100755 --- a/indra/newview/llfloaterwebcontent.cpp +++ b/indra/newview/llfloaterwebcontent.cpp @@ -30,6 +30,7 @@ #include "lliconctrl.h" #include "llfloaterreg.h" #include "llhttpconstants.h" +#include "llfacebookconnect.h" #include "lllayoutstack.h" #include "llpluginclassmedia.h" #include "llprogressbar.h" @@ -47,7 +48,8 @@ LLFloaterWebContent::_Params::_Params() id("id"), window_class("window_class", "web_content"), show_chrome("show_chrome", true), - allow_address_entry("allow_address_entry", true), + allow_address_entry("allow_address_entry", true), + allow_back_forward_navigation("allow_back_forward_navigation", true), preferred_media_size("preferred_media_size"), trusted_content("trusted_content", false), show_page_title("show_page_title", true) @@ -66,7 +68,11 @@ LLFloaterWebContent::LLFloaterWebContent( const Params& params ) mBtnReload(NULL), mBtnStop(NULL), mUUID(params.id()), - mShowPageTitle(params.show_page_title) + mShowPageTitle(params.show_page_title), + mAllowNavigation(true), + mCurrentURL(""), + mDisplayURL(""), + mSecureURL(false) { mCommitCallbackRegistrar.add( "WebContent.Back", boost::bind( &LLFloaterWebContent::onClickBack, this )); mCommitCallbackRegistrar.add( "WebContent.Forward", boost::bind( &LLFloaterWebContent::onClickForward, this )); @@ -98,7 +104,7 @@ BOOL LLFloaterWebContent::postBuild() // cache image for secure browsing mSecureLockIcon = getChild< LLIconCtrl >("media_secure_lock_flag"); - + // initialize the URL history using the system URL History manager initializeURLHistory(); @@ -244,6 +250,7 @@ void LLFloaterWebContent::open_media(const Params& p) getChild<LLLayoutPanel>("status_bar")->setVisible(p.show_chrome); getChild<LLLayoutPanel>("nav_controls")->setVisible(p.show_chrome); bool address_entry_enabled = p.allow_address_entry && !p.trusted_content; + mAllowNavigation = p.allow_back_forward_navigation; getChildView("address")->setEnabled(address_entry_enabled); getChildView("popexternal")->setEnabled(address_entry_enabled); @@ -288,6 +295,16 @@ void LLFloaterWebContent::onOpen(const LLSD& key) //virtual void LLFloaterWebContent::onClose(bool app_quitting) { + // If we close the web browsing window showing the facebook login, we need to signal to this object that the connection will not happen + LLFloater* fbc_web = LLFloaterReg::getInstance("fbc_web"); + if (fbc_web == this) + { + if (!LLFacebookConnect::instance().isConnected()) + { + LLFacebookConnect::instance().setConnectionState(LLFacebookConnect::FB_CONNECTION_FAILED); + } + } + LLViewerMedia::proxyWindowClosed(mUUID); destroy(); } @@ -296,8 +313,11 @@ void LLFloaterWebContent::onClose(bool app_quitting) void LLFloaterWebContent::draw() { // this is asynchronous so we need to keep checking - mBtnBack->setEnabled( mWebBrowser->canNavigateBack() ); - mBtnForward->setEnabled( mWebBrowser->canNavigateForward() ); + mBtnBack->setEnabled( mWebBrowser->canNavigateBack() && mAllowNavigation); + mBtnForward->setEnabled( mWebBrowser->canNavigateForward() && mAllowNavigation); + + // Show/hide the lock icon + mSecureLockIcon->setVisible(mSecureURL && !mAddressCombo->hasFocus()); LLFloater::draw(); } @@ -343,19 +363,6 @@ void LLFloaterWebContent::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent // we populate the status bar with URLs as they change so clear it now we're done const std::string end_str = ""; mStatusBarText->setText( end_str ); - - // decide if secure browsing icon should be displayed - std::string prefix = std::string("https://"); - std::string test_prefix = mCurrentURL.substr(0, prefix.length()); - LLStringUtil::toLower(test_prefix); - if(test_prefix == prefix) - { - mSecureLockIcon->setVisible(true); - } - else - { - mSecureLockIcon->setVisible(false); - } } else if(event == MEDIA_EVENT_CLOSE_REQUEST) { @@ -398,15 +405,40 @@ void LLFloaterWebContent::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent void LLFloaterWebContent::set_current_url(const std::string& url) { - mCurrentURL = url; - - // serialize url history into the system URL History manager - LLURLHistory::removeURL("browser", mCurrentURL); - LLURLHistory::addURL("browser", mCurrentURL); - - mAddressCombo->remove( mCurrentURL ); - mAddressCombo->add( mCurrentURL ); - mAddressCombo->selectByValue( mCurrentURL ); + if (!url.empty()) + { + if (!mCurrentURL.empty()) + { + // Clean up the current browsing list to show true URL + mAddressCombo->remove(mDisplayURL); + mAddressCombo->add(mCurrentURL); + } + + // Update current URL + mCurrentURL = url; + LLStringUtil::trim(mCurrentURL); + + // Serialize url history into the system URL History manager + LLURLHistory::removeURL("browser", mCurrentURL); + LLURLHistory::addURL("browser", mCurrentURL); + + // Check if this is a secure URL + static const std::string secure_prefix = std::string("https://"); + std::string prefix = mCurrentURL.substr(0, secure_prefix.length()); + LLStringUtil::toLower(prefix); + mSecureURL = (prefix == secure_prefix); + + // Hack : we move the text a bit to make space for the lock icon in the secure URL case + mDisplayURL = (mSecureURL ? " " + mCurrentURL : mCurrentURL); + + // Clean up browsing list (prevent dupes) and add/select the new URL to it + mAddressCombo->remove(mCurrentURL); + mAddressCombo->add(mDisplayURL); + mAddressCombo->selectByValue(mDisplayURL); + + // Set the focus back to the web page. When setting the url, there's no point to leave the focus anywhere else. + mWebBrowser->setFocus(TRUE); + } } void LLFloaterWebContent::onClickForward() @@ -450,6 +482,7 @@ void LLFloaterWebContent::onEnterAddress() // make sure there is at least something there. // (perhaps this test should be for minimum length of a URL) std::string url = mAddressCombo->getValue().asString(); + LLStringUtil::trim(url); if ( url.length() > 0 ) { mWebBrowser->navigateTo(url, HTTP_CONTENT_TEXT_HTML); @@ -461,6 +494,7 @@ void LLFloaterWebContent::onPopExternal() // make sure there is at least something there. // (perhaps this test should be for minimum length of a URL) std::string url = mAddressCombo->getValue().asString(); + LLStringUtil::trim(url); if ( url.length() > 0 ) { LLWeb::loadURLExternal( url ); diff --git a/indra/newview/llfloaterwebcontent.h b/indra/newview/llfloaterwebcontent.h index 86b5a5e00b..f22940cd07 100755 --- a/indra/newview/llfloaterwebcontent.h +++ b/indra/newview/llfloaterwebcontent.h @@ -55,6 +55,7 @@ public: id; Optional<bool> show_chrome, allow_address_entry, + allow_back_forward_navigation, trusted_content, show_page_title; Optional<LLRect> preferred_media_size; @@ -106,9 +107,12 @@ protected: LLView* mBtnReload; LLView* mBtnStop; - std::string mCurrentURL; + std::string mCurrentURL; // Current URL + std::string mDisplayURL; // URL being displayed in the address bar (can differ by trailing / leading space) std::string mUUID; bool mShowPageTitle; + bool mAllowNavigation; + bool mSecureURL; // true when the current url is prefixed "https://" }; #endif // LL_LLFLOATERWEBCONTENT_H diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 4bf4f6edfc..9ba0a184a5 100755 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -130,10 +130,10 @@ void process_dnd_im(const LLSD& notification) fromID, false, false); //will need slight refactor to retrieve whether offline message or not (assume online for now) - } + } notify_of_message(data, true); -} + } @@ -155,22 +155,22 @@ static void on_avatar_name_cache_toast(const LLUUID& agent_id, void notify_of_message(const LLSD& msg, bool is_dnd_msg) { - std::string user_preferences; + std::string user_preferences; LLUUID participant_id = msg[is_dnd_msg ? "FROM_ID" : "from_id"].asUUID(); LLUUID session_id = msg[is_dnd_msg ? "SESSION_ID" : "session_id"].asUUID(); - LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id); + LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id); - // do not show notification which goes from agent - if (gAgent.getID() == participant_id) - { - return; - } + // do not show notification which goes from agent + if (gAgent.getID() == participant_id) + { + return; + } - // determine state of conversations floater - enum {CLOSED, NOT_ON_TOP, ON_TOP, ON_TOP_AND_ITEM_IS_SELECTED} conversations_floater_status; + // determine state of conversations floater + enum {CLOSED, NOT_ON_TOP, ON_TOP, ON_TOP_AND_ITEM_IS_SELECTED} conversations_floater_status; - LLFloaterIMContainer* im_box = LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container"); + LLFloaterIMContainer* im_box = LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container"); LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(session_id); bool store_dnd_message = false; // flag storage of a dnd message bool is_session_focused = session_floater->isTornOff() && session_floater->hasFocus(); @@ -179,23 +179,23 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) conversations_floater_status = CLOSED; } else if (!im_box->hasFocus() && - !(session_floater && LLFloater::isVisible(session_floater) - && !session_floater->isMinimized() && session_floater->hasFocus())) + !(session_floater && LLFloater::isVisible(session_floater) + && !session_floater->isMinimized() && session_floater->hasFocus())) { conversations_floater_status = NOT_ON_TOP; } else if (im_box->getSelectedSession() != session_id) { conversations_floater_status = ON_TOP; - } + } else { conversations_floater_status = ON_TOP_AND_ITEM_IS_SELECTED; } - // determine user prefs for this session - if (session_id.isNull()) - { + // determine user prefs for this session + if (session_id.isNull()) + { if (msg["source_type"].asInteger() == CHAT_SOURCE_OBJECT) { user_preferences = gSavedSettings.getString("NotificationObjectIMOptions"); @@ -206,50 +206,50 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) } else { - user_preferences = gSavedSettings.getString("NotificationNearbyChatOptions"); + user_preferences = gSavedSettings.getString("NotificationNearbyChatOptions"); if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNearbyChatIM") == TRUE)) { make_ui_sound("UISndNewIncomingIMSession"); - } + } } } - else if(session->isP2PSessionType()) - { - if (LLAvatarTracker::instance().isBuddy(participant_id)) - { - user_preferences = gSavedSettings.getString("NotificationFriendIMOptions"); + else if(session->isP2PSessionType()) + { + if (LLAvatarTracker::instance().isBuddy(participant_id)) + { + user_preferences = gSavedSettings.getString("NotificationFriendIMOptions"); if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundFriendIM") == TRUE)) { make_ui_sound("UISndNewIncomingIMSession"); } - } - else - { - user_preferences = gSavedSettings.getString("NotificationNonFriendIMOptions"); + } + else + { + user_preferences = gSavedSettings.getString("NotificationNonFriendIMOptions"); if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNonFriendIM") == TRUE)) { make_ui_sound("UISndNewIncomingIMSession"); - } - } + } + } } - else if(session->isAdHocSessionType()) - { - user_preferences = gSavedSettings.getString("NotificationConferenceIMOptions"); + else if(session->isAdHocSessionType()) + { + user_preferences = gSavedSettings.getString("NotificationConferenceIMOptions"); if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundConferenceIM") == TRUE)) { make_ui_sound("UISndNewIncomingIMSession"); - } + } } - else if(session->isGroupSessionType()) - { - user_preferences = gSavedSettings.getString("NotificationGroupChatOptions"); + else if(session->isGroupSessionType()) + { + user_preferences = gSavedSettings.getString("NotificationGroupChatOptions"); if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundGroupChatIM") == TRUE)) { make_ui_sound("UISndNewIncomingIMSession"); } - } + } - // actions: + // actions: // 0. nothing - exit if (("noaction" == user_preferences || @@ -287,23 +287,23 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) } } } - else - { + else + { store_dnd_message = true; - } + } - } + } - // 2. Flash line item - if ("openconversations" == user_preferences - || ON_TOP == conversations_floater_status - || ("toast" == user_preferences && ON_TOP != conversations_floater_status) + // 2. Flash line item + if ("openconversations" == user_preferences + || ON_TOP == conversations_floater_status + || ("toast" == user_preferences && ON_TOP != conversations_floater_status) || ("flash" == user_preferences && (CLOSED == conversations_floater_status || NOT_ON_TOP == conversations_floater_status)) || is_dnd_msg) - { - if(!LLMuteList::getInstance()->isMuted(participant_id)) - { + { + if(!LLMuteList::getInstance()->isMuted(participant_id)) + { if(gAgent.isDoNotDisturb()) { store_dnd_message = true; @@ -318,43 +318,43 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) } else { - im_box->flashConversationItemWidget(session_id, true); - } - } + im_box->flashConversationItemWidget(session_id, true); + } + } } } - // 3. Flash FUI button - if (("toast" == user_preferences || "flash" == user_preferences) && - (CLOSED == conversations_floater_status + // 3. Flash FUI button + if (("toast" == user_preferences || "flash" == user_preferences) && + (CLOSED == conversations_floater_status || NOT_ON_TOP == conversations_floater_status) && !is_session_focused && !is_dnd_msg) //prevent flashing FUI button because the conversation floater will have already opened { if(!LLMuteList::getInstance()->isMuted(participant_id)) - { + { if(!gAgent.isDoNotDisturb()) - { + { gToolBarView->flashCommand(LLCommandId("chat"), true, im_box->isMinimized()); - } + } else { store_dnd_message = true; } - } + } } - // 4. Toast - if ((("toast" == user_preferences) && + // 4. Toast + if ((("toast" == user_preferences) && (ON_TOP_AND_ITEM_IS_SELECTED != conversations_floater_status) && (!session_floater->isTornOff() || !LLFloater::isVisible(session_floater))) - || !session_floater->isMessagePaneExpanded()) + || !session_floater->isMessagePaneExpanded()) - { - //Show IM toasts (upper right toasts) - // Skip toasting for system messages and for nearby chat - if(session_id.notNull() && participant_id.notNull()) - { + { + //Show IM toasts (upper right toasts) + // Skip toasting for system messages and for nearby chat + if(session_id.notNull() && participant_id.notNull()) + { if(!is_dnd_msg) { if(gAgent.isDoNotDisturb()) @@ -363,10 +363,10 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) } else { - LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg)); - } - } - } + LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg)); + } + } +} } if (store_dnd_message) { diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index c9fe2127fe..3877f9f040 100755 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -4627,6 +4627,16 @@ void LLCallingCardBridge::performAction(LLInventoryModel* model, std::string act LLAvatarActions::offerTeleport(item->getCreatorUUID()); } } + else if ("request_lure" == action) + { + LLViewerInventoryItem *item = getItem(); + if (item && (item->getCreatorUUID() != gAgent.getID()) && + (!item->getCreatorUUID().isNull())) + { + LLAvatarActions::teleportRequest(item->getCreatorUUID()); + } + } + else LLItemBridge::performAction(model, action); } @@ -4713,6 +4723,7 @@ void LLCallingCardBridge::buildContextMenu(LLMenuGL& menu, U32 flags) items.push_back(std::string("Send Instant Message Separator")); items.push_back(std::string("Send Instant Message")); items.push_back(std::string("Offer Teleport...")); + items.push_back(std::string("Request Teleport...")); items.push_back(std::string("Conference Chat")); if (!good_card) @@ -4722,6 +4733,7 @@ void LLCallingCardBridge::buildContextMenu(LLMenuGL& menu, U32 flags) if (!good_card || !user_online) { disabled_items.push_back(std::string("Offer Teleport...")); + disabled_items.push_back(std::string("Request Teleport...")); disabled_items.push_back(std::string("Conference Chat")); } } diff --git a/indra/newview/llnetmap.cpp b/indra/newview/llnetmap.cpp index dea90b9042..08b5eaedbb 100755 --- a/indra/newview/llnetmap.cpp +++ b/indra/newview/llnetmap.cpp @@ -343,8 +343,11 @@ void LLNetMap::draw() // Draw avatars for (U32 i = 0; i < avatar_ids.size(); i++) { - pos_map = globalPosToView(positions[i]); LLUUID uuid = avatar_ids[i]; + // Skip self, we'll draw it later + if (uuid == gAgent.getID()) continue; + + pos_map = globalPosToView(positions[i]); bool show_as_friend = (LLAvatarTracker::instance().getBuddyInfo(uuid) != NULL); diff --git a/indra/newview/llnotificationscripthandler.cpp b/indra/newview/llnotificationscripthandler.cpp index 08c98e4f28..a3b15931c6 100755 --- a/indra/newview/llnotificationscripthandler.cpp +++ b/indra/newview/llnotificationscripthandler.cpp @@ -35,6 +35,9 @@ #include "llnotificationmanager.h" #include "llnotifications.h" #include "llscriptfloater.h" +#include "llfacebookconnect.h" +#include "llavatarname.h" +#include "llavatarnamecache.h" using namespace LLNotificationsUI; @@ -87,7 +90,7 @@ bool LLScriptHandler::processNotification(const LLNotificationPtr& notification) { LLScriptFloaterManager::getInstance()->onAddNotification(notification->getID()); } - else + else if (notification->canShowToast()) { LLToastPanel* notify_box = LLToastPanel::buidPanelFromNotification(notification); diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp index d7c634d619..f551fc96ee 100755 --- a/indra/newview/llpanelpeople.cpp +++ b/indra/newview/llpanelpeople.cpp @@ -28,6 +28,8 @@ // libs #include "llavatarname.h" +#include "llconversationview.h" +#include "llfloaterimcontainer.h" #include "llfloaterreg.h" #include "llfloatersidepanelcontainer.h" #include "llmenubutton.h" @@ -48,15 +50,19 @@ #include "llavataractions.h" #include "llavatarlist.h" #include "llavatarlistitem.h" +#include "llavatarnamecache.h" #include "llcallingcard.h" // for LLAvatarTracker +#include "llcallbacklist.h" +#include "llerror.h" +#include "llfacebookconnect.h" #include "llfloateravatarpicker.h" -//#include "llfloaterminiinspector.h" #include "llfriendcard.h" #include "llgroupactions.h" #include "llgrouplist.h" #include "llinventoryobserver.h" #include "llnetmap.h" #include "llpanelpeoplemenus.h" +#include "llparticipantlist.h" #include "llsidetraypanelcontainer.h" #include "llrecentpeople.h" #include "llviewercontrol.h" // for gSavedSettings @@ -64,6 +70,10 @@ #include "llvoiceclient.h" #include "llworld.h" #include "llspeakers.h" +#include "llfloaterwebcontent.h" + +#include "llagentui.h" +#include "llslurl.h" #define FRIEND_LIST_UPDATE_TIMEOUT 0.5 #define NEARBY_LIST_UPDATE_INTERVAL 1 @@ -73,9 +83,9 @@ static const std::string FRIENDS_TAB_NAME = "friends_panel"; static const std::string GROUP_TAB_NAME = "groups_panel"; static const std::string RECENT_TAB_NAME = "recent_panel"; static const std::string BLOCKED_TAB_NAME = "blocked_panel"; // blocked avatars - static const std::string COLLAPSED_BY_USER = "collapsed_by_user"; + extern S32 gMaxAgentGroups; /** Comparator for comparing avatar items by last interaction date */ @@ -495,6 +505,7 @@ public: LLPanelPeople::LLPanelPeople() : LLPanel(), + mTryToConnectToFbc(true), mTabContainer(NULL), mOnlineFriendList(NULL), mAllFriendList(NULL), @@ -573,6 +584,7 @@ BOOL LLPanelPeople::postBuild() getChild<LLFilterEditor>("friends_filter_input")->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2)); getChild<LLFilterEditor>("groups_filter_input")->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2)); getChild<LLFilterEditor>("recent_filter_input")->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2)); + getChild<LLFilterEditor>("fbc_filter_input")->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2)); mTabContainer = getChild<LLTabContainer>("tabs"); mTabContainer->setCommitCallback(boost::bind(&LLPanelPeople::onTabSelected, this, _2)); @@ -583,8 +595,11 @@ BOOL LLPanelPeople::postBuild() // updater is active only if panel is visible to user. friends_tab->setVisibleCallback(boost::bind(&Updater::setActive, mFriendListUpdater, _2)); friends_tab->setVisibleCallback(boost::bind(&LLPanelPeople::removePicker, this)); + friends_tab->setVisibleCallback(boost::bind(&LLPanelPeople::updateFacebookList, this, _2)); + mOnlineFriendList = friends_tab->getChild<LLAvatarList>("avatars_online"); mAllFriendList = friends_tab->getChild<LLAvatarList>("avatars_all"); + mSuggestedFriends = friends_tab->getChild<LLAvatarList>("suggested_friends"); mOnlineFriendList->setNoItemsCommentText(getString("no_friends_online")); mOnlineFriendList->setShowIcons("FriendsListShowIcons"); mOnlineFriendList->showPermissions("FriendsListShowPermissions"); @@ -617,6 +632,7 @@ BOOL LLPanelPeople::postBuild() mRecentList->setContextMenu(&LLPanelPeopleMenus::gPeopleContextMenu); mAllFriendList->setContextMenu(&LLPanelPeopleMenus::gPeopleContextMenu); mOnlineFriendList->setContextMenu(&LLPanelPeopleMenus::gPeopleContextMenu); + mSuggestedFriends->setContextMenu(&LLPanelPeopleMenus::gSuggestedFriendsContextMenu); setSortOrder(mRecentList, (ESortOrder)gSavedSettings.getU32("RecentPeopleSortOrder"), false); setSortOrder(mAllFriendList, (ESortOrder)gSavedSettings.getU32("FriendsSortOrder"), false); @@ -695,7 +711,7 @@ void LLPanelPeople::updateFriendListHelpText() // Seems sometimes all_friends can be empty because of issue with Inventory loading (clear cache, slow connection...) // So, lets check all lists to avoid overlapping the text with online list. See EXT-6448. - bool any_friend_exists = mAllFriendList->filterHasMatches() || mOnlineFriendList->filterHasMatches(); + bool any_friend_exists = mAllFriendList->filterHasMatches() || mOnlineFriendList->filterHasMatches() || mSuggestedFriends->filterHasMatches(); no_friends_text->setVisible(!any_friend_exists); if (no_friends_text->getVisible()) { @@ -762,9 +778,40 @@ void LLPanelPeople::updateFriendList() mAllFriendList->setDirty(true, !mAllFriendList->filterHasMatches()); //update trash and other buttons according to a selected item updateButtons(); + updateSuggestedFriendList(); showFriendsAccordionsIfNeeded(); } +bool LLPanelPeople::updateSuggestedFriendList() +{ + const LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); + uuid_vec_t& suggested_friends = mSuggestedFriends->getIDs(); + suggested_friends.clear(); + + //Add suggested friends + LLSD friends = LLFacebookConnect::instance().getContent(); + for (LLSD::array_const_iterator i = friends.beginArray(); i != friends.endArray(); ++i) + { + LLUUID agent_id = (*i).asUUID(); + bool second_life_buddy = agent_id.notNull() ? av_tracker.isBuddy(agent_id) : false; + + if(!second_life_buddy) + { + //FB+SL but not SL friend + if (agent_id.notNull()) + { + suggested_friends.push_back(agent_id); + } + } + } + + //Force a refresh when there aren't any filter matches (prevent displaying content that shouldn't display) + mSuggestedFriends->setDirty(true, !mSuggestedFriends->filterHasMatches()); + showFriendsAccordionsIfNeeded(); + + return false; +} + void LLPanelPeople::updateNearbyList() { if (!mNearbyList) @@ -788,6 +835,51 @@ void LLPanelPeople::updateRecentList() mRecentList->setDirty(); } +bool LLPanelPeople::onConnectedToFacebook(const LLSD& data) +{ + LLSD::Integer connection_state = data.get("enum").asInteger(); + + if (connection_state == LLFacebookConnect::FB_CONNECTED) + { + LLFacebookConnect::instance().loadFacebookFriends(); + } + else if(connection_state == LLFacebookConnect::FB_NOT_CONNECTED) + { + updateSuggestedFriendList(); + }; + + return false; +} + +void LLPanelPeople::updateFacebookList(bool visible) +{ + if (visible) + { + LLEventPumps::instance().obtain("FacebookConnectContent").stopListening("LLPanelPeople"); // just in case it is already listening + LLEventPumps::instance().obtain("FacebookConnectContent").listen("LLPanelPeople", boost::bind(&LLPanelPeople::updateSuggestedFriendList, this)); + + LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLPanelPeople"); // just in case it is already listening + LLEventPumps::instance().obtain("FacebookConnectState").listen("LLPanelPeople", boost::bind(&LLPanelPeople::onConnectedToFacebook, this, _1)); + + if (LLFacebookConnect::instance().isConnected()) + { + LLFacebookConnect::instance().loadFacebookFriends(); + } + else if(mTryToConnectToFbc) + { + LLFacebookConnect::instance().checkConnectionToFacebook(); + mTryToConnectToFbc = false; + } + + updateSuggestedFriendList(); + } + else + { + LLEventPumps::instance().obtain("FacebookConnectState").stopListening("LLPanelPeople"); + LLEventPumps::instance().obtain("FacebookConnectContent").stopListening("LLPanelPeople"); + } +} + void LLPanelPeople::updateButtons() { std::string cur_tab = getActiveTabName(); @@ -993,23 +1085,25 @@ void LLPanelPeople::onFilterEdit(const std::string& search_string) { // store accordion tabs opened/closed state before any manipulation with accordion tabs if (!saved_filter.empty()) - { - notifyChildren(LLSD().with("action","store_state")); - } + { + notifyChildren(LLSD().with("action","store_state")); + } mOnlineFriendList->setNameFilter(filter); mAllFriendList->setNameFilter(filter); + mSuggestedFriends->setNameFilter(filter); - setAccordionCollapsedByUser("tab_online", false); - setAccordionCollapsedByUser("tab_all", false); - showFriendsAccordionsIfNeeded(); + setAccordionCollapsedByUser("tab_online", false); + setAccordionCollapsedByUser("tab_all", false); + setAccordionCollapsedByUser("tab_suggested_friends", false); + showFriendsAccordionsIfNeeded(); // restore accordion tabs state _after_ all manipulations if(saved_filter.empty()) - { - notifyChildren(LLSD().with("action","restore_state")); - } -} + { + notifyChildren(LLSD().with("action","restore_state")); + } + } else if (cur_tab == GROUP_TAB_NAME) { mGroupList->setNameFilter(filter); @@ -1229,7 +1323,7 @@ void LLPanelPeople::onFriendsViewSortMenuItemClicked(const LLSD& userdata) mAllFriendList->showPermissions(show_permissions); mOnlineFriendList->showPermissions(show_permissions); } -} + } void LLPanelPeople::onGroupsViewSortMenuItemClicked(const LLSD& userdata) { @@ -1387,6 +1481,7 @@ void LLPanelPeople::showFriendsAccordionsIfNeeded() // Expand and show accordions if needed, else - hide them showAccordion("tab_online", mOnlineFriendList->filterHasMatches()); showAccordion("tab_all", mAllFriendList->filterHasMatches()); + showAccordion("tab_suggested_friends", mSuggestedFriends->filterHasMatches()); // Rearrange accordions LLAccordionCtrl* accordion = getChild<LLAccordionCtrl>("friends_accordion"); @@ -1450,4 +1545,5 @@ bool LLPanelPeople::isAccordionCollapsedByUser(const std::string& name) return isAccordionCollapsedByUser(getChild<LLUICtrl>(name)); } + // EOF diff --git a/indra/newview/llpanelpeople.h b/indra/newview/llpanelpeople.h index 4740964dee..c7141f36ee 100755 --- a/indra/newview/llpanelpeople.h +++ b/indra/newview/llpanelpeople.h @@ -22,7 +22,7 @@ * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ - */ + */ #ifndef LL_LLPANELPEOPLE_H #define LL_LLPANELPEOPLE_H @@ -30,6 +30,7 @@ #include <llpanel.h> #include "llcallingcard.h" // for avatar tracker +#include "llfloaterwebcontent.h" #include "llvoiceclient.h" class LLAvatarList; @@ -55,6 +56,8 @@ public: // when voice is available /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); + bool mTryToConnectToFbc; + // internals class Updater; @@ -73,8 +76,10 @@ private: // methods indirectly called by the updaters void updateFriendListHelpText(); void updateFriendList(); + bool updateSuggestedFriendList(); void updateNearbyList(); void updateRecentList(); + void updateFacebookList(bool visible); bool isItemsFreeOfFriends(const uuid_vec_t& uuids); @@ -121,6 +126,8 @@ private: void onFriendListRefreshComplete(LLUICtrl*ctrl, const LLSD& param); + bool onConnectedToFacebook(const LLSD& data); + void setAccordionCollapsedByUser(LLUICtrl* acc_tab, bool collapsed); void setAccordionCollapsedByUser(const std::string& name, bool collapsed); bool isAccordionCollapsedByUser(LLUICtrl* acc_tab); @@ -129,6 +136,7 @@ private: LLTabContainer* mTabContainer; LLAvatarList* mOnlineFriendList; LLAvatarList* mAllFriendList; + LLAvatarList* mSuggestedFriends; LLAvatarList* mNearbyList; LLAvatarList* mRecentList; LLGroupList* mGroupList; @@ -140,6 +148,7 @@ private: Updater* mFriendListUpdater; Updater* mNearbyListUpdater; Updater* mRecentListUpdater; + Updater* mFacebookListUpdater; Updater* mButtonsUpdater; LLHandle< LLFloater > mPicker; }; diff --git a/indra/newview/llpanelpeoplemenus.cpp b/indra/newview/llpanelpeoplemenus.cpp index 49f7361c4a..6979ae06e0 100755 --- a/indra/newview/llpanelpeoplemenus.cpp +++ b/indra/newview/llpanelpeoplemenus.cpp @@ -47,6 +47,7 @@ namespace LLPanelPeopleMenus PeopleContextMenu gPeopleContextMenu; NearbyPeopleContextMenu gNearbyPeopleContextMenu; +SuggestedFriendsContextMenu gSuggestedFriendsContextMenu; //== PeopleContextMenu =============================================================== @@ -74,6 +75,7 @@ LLContextMenu* PeopleContextMenu::createMenu() registrar.add("Avatar.Pay", boost::bind(&LLAvatarActions::pay, id)); registrar.add("Avatar.BlockUnblock", boost::bind(&LLAvatarActions::toggleBlock, id)); registrar.add("Avatar.InviteToGroup", boost::bind(&LLAvatarActions::inviteToGroup, id)); + registrar.add("Avatar.TeleportRequest", boost::bind(&PeopleContextMenu::requestTeleport, this)); registrar.add("Avatar.Calllog", boost::bind(&LLAvatarActions::viewChatHistory, id)); enable_registrar.add("Avatar.EnableItem", boost::bind(&PeopleContextMenu::enableContextMenuItem, this, _2)); @@ -125,6 +127,7 @@ void PeopleContextMenu::buildContextMenu(class LLMenuGL& menu, U32 flags) items.push_back(std::string("view_profile")); items.push_back(std::string("im")); items.push_back(std::string("offer_teleport")); + items.push_back(std::string("request_teleport")); items.push_back(std::string("voice_call")); items.push_back(std::string("chat_history")); items.push_back(std::string("separator_chat_history")); @@ -255,6 +258,13 @@ bool PeopleContextMenu::checkContextMenuItem(const LLSD& userdata) return false; } +void PeopleContextMenu::requestTeleport() +{ + // boost::bind cannot recognize overloaded method LLAvatarActions::teleportRequest(), + // so we have to use a wrapper. + LLAvatarActions::teleportRequest(mUUIDs.front()); +} + void PeopleContextMenu::offerTeleport() { // boost::bind cannot recognize overloaded method LLAvatarActions::offerTeleport(), @@ -284,6 +294,7 @@ void NearbyPeopleContextMenu::buildContextMenu(class LLMenuGL& menu, U32 flags) items.push_back(std::string("view_profile")); items.push_back(std::string("im")); items.push_back(std::string("offer_teleport")); + items.push_back(std::string("request_teleport")); items.push_back(std::string("voice_call")); items.push_back(std::string("chat_history")); items.push_back(std::string("separator_chat_history")); @@ -301,4 +312,36 @@ void NearbyPeopleContextMenu::buildContextMenu(class LLMenuGL& menu, U32 flags) hide_context_entries(menu, items, disabled_items); } +//== SuggestedFriendsContextMenu =============================================================== + +LLContextMenu* SuggestedFriendsContextMenu::createMenu() +{ + // set up the callbacks for all of the avatar menu items + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + LLContextMenu* menu; + + // Set up for one person selected menu + const LLUUID& id = mUUIDs.front(); + registrar.add("Avatar.Profile", boost::bind(&LLAvatarActions::showProfile, id)); + registrar.add("Avatar.AddFriend", boost::bind(&LLAvatarActions::requestFriendshipDialog, id)); + + // create the context menu from the XUI + menu = createFromFile("menu_people_nearby.xml"); + buildContextMenu(*menu, 0x0); + + return menu; +} + +void SuggestedFriendsContextMenu::buildContextMenu(class LLMenuGL& menu, U32 flags) +{ + menuentry_vec_t items; + menuentry_vec_t disabled_items; + + items.push_back(std::string("view_profile")); + items.push_back(std::string("add_friend")); + + hide_context_entries(menu, items, disabled_items); +} + } // namespace LLPanelPeopleMenus diff --git a/indra/newview/llpanelpeoplemenus.h b/indra/newview/llpanelpeoplemenus.h index 0a1dcef303..945382ebc5 100755 --- a/indra/newview/llpanelpeoplemenus.h +++ b/indra/newview/llpanelpeoplemenus.h @@ -47,6 +47,7 @@ private: bool enableContextMenuItem(const LLSD& userdata); bool checkContextMenuItem(const LLSD& userdata); void offerTeleport(); + void requestTeleport(); }; /** @@ -58,8 +59,21 @@ protected: /*virtual*/ void buildContextMenu(class LLMenuGL& menu, U32 flags); }; +/** + * Menu used in the suggested friends list. + */ +class SuggestedFriendsContextMenu : public PeopleContextMenu +{ +public: + /*virtual*/ LLContextMenu * createMenu(); + +protected: + /*virtual*/ void buildContextMenu(class LLMenuGL& menu, U32 flags); +}; + extern PeopleContextMenu gPeopleContextMenu; extern NearbyPeopleContextMenu gNearbyPeopleContextMenu; +extern SuggestedFriendsContextMenu gSuggestedFriendsContextMenu; } // namespace LLPanelPeopleMenus diff --git a/indra/newview/llpanelprimmediacontrols.cpp b/indra/newview/llpanelprimmediacontrols.cpp index 76d38f067d..9504f22a1d 100755 --- a/indra/newview/llpanelprimmediacontrols.cpp +++ b/indra/newview/llpanelprimmediacontrols.cpp @@ -94,7 +94,8 @@ LLPanelPrimMediaControls::LLPanelPrimMediaControls() : mZoomObjectFace(0), mVolumeSliderVisible(0), mWindowShade(NULL), - mHideImmediately(false) + mHideImmediately(false), + mSecureURL(false) { mCommitCallbackRegistrar.add("MediaCtrl.Close", boost::bind(&LLPanelPrimMediaControls::onClickClose, this)); mCommitCallbackRegistrar.add("MediaCtrl.Back", boost::bind(&LLPanelPrimMediaControls::onClickBack, this)); @@ -345,7 +346,7 @@ void LLPanelPrimMediaControls::updateShape() // Disable zoom if HUD mZoomCtrl->setEnabled(!is_hud); mUnzoomCtrl->setEnabled(!is_hud); - mSecureLockIcon->setVisible(false); + mSecureURL = false; mCurrentURL = media_impl->getCurrentMediaURL(); mBackCtrl->setEnabled((media_impl != NULL) && media_impl->canNavigateBack() && can_navigate); @@ -382,7 +383,7 @@ void LLPanelPrimMediaControls::updateShape() mVolumeSliderCtrl->setVisible(has_focus && shouldVolumeSliderBeVisible()); mWhitelistIcon->setVisible(false); - mSecureLockIcon->setVisible(false); + mSecureURL = false; if (mMediaPanelScroll) { mMediaPanelScroll->setVisible(false); @@ -416,7 +417,7 @@ void LLPanelPrimMediaControls::updateShape() mMediaPlaySliderCtrl->setEnabled(true); } - // video vloume + // video volume if(volume <= 0.0) { mMuteBtn->setToggleState(true); @@ -492,10 +493,8 @@ void LLPanelPrimMediaControls::updateShape() std::string prefix = std::string("https://"); std::string test_prefix = mCurrentURL.substr(0, prefix.length()); LLStringUtil::toLower(test_prefix); - if(test_prefix == prefix) - { - mSecureLockIcon->setVisible(has_focus); - } + mSecureURL = has_focus && (test_prefix == prefix); + mCurrentURL = (mSecureURL ? " " + mCurrentURL : mCurrentURL); if(mCurrentURL!=mPreviousURL) { @@ -746,6 +745,9 @@ void LLPanelPrimMediaControls::draw() clearFaceOnFade(); } } + + // Show/hide the lock icon for secure browsing + mSecureLockIcon->setVisible(mSecureURL && !mMediaAddress->hasFocus()); // Build rect for icon area in coord system of this panel // Assumes layout_stack is a direct child of this panel diff --git a/indra/newview/llpanelprimmediacontrols.h b/indra/newview/llpanelprimmediacontrols.h index eeb433e306..6d2eb3430e 100755 --- a/indra/newview/llpanelprimmediacontrols.h +++ b/indra/newview/llpanelprimmediacontrols.h @@ -191,6 +191,7 @@ private: bool mUpdateSlider; bool mClearFaceOnFade; bool mHideImmediately; + bool mSecureURL; LLMatrix4 mLastCameraMat; EZoomLevel mCurrentZoom; diff --git a/indra/newview/llparticipantlist.cpp b/indra/newview/llparticipantlist.cpp index c53760bca1..ee6893907e 100755 --- a/indra/newview/llparticipantlist.cpp +++ b/indra/newview/llparticipantlist.cpp @@ -27,6 +27,7 @@ #include "llviewerprecompiledheaders.h" #include "llavatarnamecache.h" +#include "llerror.h" #include "llimview.h" #include "llfloaterimcontainer.h" #include "llparticipantlist.h" @@ -401,6 +402,8 @@ void LLParticipantList::addAvatarIDExceptAgent(const LLUUID& avatar_id) adjustParticipant(avatar_id); } +static LLFastTimer::DeclareTimer FTM_FOLDERVIEW_TEST("add test avatar agents"); + void LLParticipantList::adjustParticipant(const LLUUID& speaker_id) { LLPointer<LLSpeaker> speakerp = mSpeakerMgr->findSpeaker(speaker_id); diff --git a/indra/newview/llsechandler_basic.cpp b/indra/newview/llsechandler_basic.cpp index 928d26646b..c4d5450e2b 100755 --- a/indra/newview/llsechandler_basic.cpp +++ b/indra/newview/llsechandler_basic.cpp @@ -207,6 +207,7 @@ LLSD _basic_constraints_ext(X509* cert) } } + BASIC_CONSTRAINTS_free( bs ); } return result; } @@ -268,6 +269,8 @@ LLSD _ext_key_usage_ext(X509* cert) ASN1_OBJECT_free(usage); } } + + EXTENDED_KEY_USAGE_free( eku ); } return result; } @@ -280,6 +283,8 @@ LLSD _subject_key_identifier_ext(X509 *cert) if(skeyid) { result = cert_string_from_octet_string(skeyid); + + ASN1_OCTET_STRING_free( skeyid ); } return result; } @@ -300,6 +305,9 @@ LLSD _authority_key_identifier_ext(X509* cert) { result[CERT_AUTHORITY_KEY_IDENTIFIER_SERIAL] = cert_string_from_asn1_integer(akeyid->serial); } + + + AUTHORITY_KEYID_free( akeyid ); } // we ignore the issuer name in the authority key identifier, we check the issue name via @@ -1049,6 +1057,8 @@ void LLBasicCertificateStore::validate(int validation_policy, throw LLInvalidCertificate((*current_cert)); } std::string sha1_hash((const char *)cert_x509->sha1_hash, SHA_DIGEST_LENGTH); + X509_free( cert_x509 ); + cert_x509 = NULL; t_cert_cache::iterator cache_entry = mTrustedCertCache.find(sha1_hash); if(cache_entry != mTrustedCertCache.end()) { diff --git a/indra/newview/llsnapshotlivepreview.cpp b/indra/newview/llsnapshotlivepreview.cpp new file mode 100644 index 0000000000..7532ebfc57 --- /dev/null +++ b/indra/newview/llsnapshotlivepreview.cpp @@ -0,0 +1,874 @@ +/** +* @file llsnapshotlivepreview.cpp +* @brief Implementation of llsnapshotlivepreview +* @author Gilbert@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, 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 "llagent.h" +#include "llagentcamera.h" +#include "llagentui.h" +#include "llcombobox.h" +#include "lleconomy.h" +#include "llfloaterperms.h" +#include "llfloaterreg.h" +#include "llfloatersocial.h" +#include "llimagebmp.h" +#include "llimagej2c.h" +#include "llimagejpeg.h" +#include "llimagepng.h" +#include "lllandmarkactions.h" +#include "lllocalcliprect.h" +#include "llnotificationsutil.h" +#include "llslurl.h" +#include "llsnapshotlivepreview.h" +#include "lltoolfocus.h" +#include "llviewercontrol.h" +#include "llviewermenufile.h" // upload_new_resource() +#include "llviewerstats.h" +#include "llvfile.h" +#include "llvfs.h" +#include "llwebsharing.h" +#include "llwindow.h" +#include "llworld.h" + +const F32 AUTO_SNAPSHOT_TIME_DELAY = 1.f; + +F32 SHINE_TIME = 0.5f; +F32 SHINE_WIDTH = 0.6f; +F32 SHINE_OPACITY = 0.3f; +F32 FALL_TIME = 0.6f; +S32 BORDER_WIDTH = 6; + +const S32 MAX_TEXTURE_SIZE = 512 ; //max upload texture size 512 * 512 + +std::set<LLSnapshotLivePreview*> LLSnapshotLivePreview::sList; + +LLSnapshotLivePreview::LLSnapshotLivePreview (const LLSnapshotLivePreview::Params& p) + : LLView(p), + mColor(1.f, 0.f, 0.f, 0.5f), + mCurImageIndex(0), + mPreviewImage(NULL), + mThumbnailImage(NULL) , + mThumbnailWidth(0), + mThumbnailHeight(0), + mPreviewImageEncoded(NULL), + mFormattedImage(NULL), + mShineCountdown(0), + mFlashAlpha(0.f), + mNeedsFlash(TRUE), + mSnapshotQuality(gSavedSettings.getS32("SnapshotQuality")), + mDataSize(0), + mSnapshotType(SNAPSHOT_POSTCARD), + mSnapshotFormat(LLFloaterSnapshot::ESnapshotFormat(gSavedSettings.getS32("SnapshotFormat"))), + mSnapshotUpToDate(FALSE), + mCameraPos(LLViewerCamera::getInstance()->getOrigin()), + mCameraRot(LLViewerCamera::getInstance()->getQuaternion()), + mSnapshotActive(FALSE), + mSnapshotBufferType(LLViewerWindow::SNAPSHOT_TYPE_COLOR) +{ + setSnapshotQuality(gSavedSettings.getS32("SnapshotQuality")); + mSnapshotDelayTimer.setTimerExpirySec(0.0f); + mSnapshotDelayTimer.start(); + // gIdleCallbacks.addFunction( &LLSnapshotLivePreview::onIdle, (void*)this ); + sList.insert(this); + setFollowsAll(); + mWidth[0] = gViewerWindow->getWindowWidthRaw(); + mWidth[1] = gViewerWindow->getWindowWidthRaw(); + mHeight[0] = gViewerWindow->getWindowHeightRaw(); + mHeight[1] = gViewerWindow->getWindowHeightRaw(); + mImageScaled[0] = FALSE; + mImageScaled[1] = FALSE; + + mMaxImageSize = MAX_SNAPSHOT_IMAGE_SIZE ; + mKeepAspectRatio = gSavedSettings.getBOOL("KeepAspectForSnapshot") ; + mThumbnailUpdateLock = FALSE ; + mThumbnailUpToDate = FALSE ; +} + +LLSnapshotLivePreview::~LLSnapshotLivePreview() +{ + // delete images + mPreviewImage = NULL; + mPreviewImageEncoded = NULL; + mFormattedImage = NULL; + + // gIdleCallbacks.deleteFunction( &LLSnapshotLivePreview::onIdle, (void*)this ); + sList.erase(this); +} + +void LLSnapshotLivePreview::setMaxImageSize(S32 size) +{ + if(size < MAX_SNAPSHOT_IMAGE_SIZE) + { + mMaxImageSize = size; + } + else + { + mMaxImageSize = MAX_SNAPSHOT_IMAGE_SIZE ; + } +} + +LLViewerTexture* LLSnapshotLivePreview::getCurrentImage() +{ + return mViewerImage[mCurImageIndex]; +} + +F32 LLSnapshotLivePreview::getAspect() +{ + F32 image_aspect_ratio = ((F32)getWidth()) / ((F32)getHeight()); + F32 window_aspect_ratio = ((F32)getRect().getWidth()) / ((F32)getRect().getHeight()); + + if (!mKeepAspectRatio)//gSavedSettings.getBOOL("KeepAspectForSnapshot")) + { + return image_aspect_ratio; + } + else + { + return window_aspect_ratio; + } +} + +F32 LLSnapshotLivePreview::getImageAspect() +{ + if (!getCurrentImage()) + { + return 0.f; + } + + return getAspect() ; +} + +void LLSnapshotLivePreview::updateSnapshot(BOOL new_snapshot, BOOL new_thumbnail, F32 delay) +{ + // Invalidate current image. + lldebugs << "updateSnapshot: mSnapshotUpToDate = " << getSnapshotUpToDate() << llendl; + if (getSnapshotUpToDate()) + { + S32 old_image_index = mCurImageIndex; + mCurImageIndex = (mCurImageIndex + 1) % 2; + setSize(mWidth[old_image_index], mHeight[old_image_index]); + mFallAnimTimer.start(); + } + mSnapshotUpToDate = FALSE; + + // Update snapshot source rect depending on whether we keep the aspect ratio. + LLRect& rect = mImageRect[mCurImageIndex]; + rect.set(0, getRect().getHeight(), getRect().getWidth(), 0); + + F32 image_aspect_ratio = ((F32)getWidth()) / ((F32)getHeight()); + F32 window_aspect_ratio = ((F32)getRect().getWidth()) / ((F32)getRect().getHeight()); + + if (mKeepAspectRatio)//gSavedSettings.getBOOL("KeepAspectForSnapshot")) + { + if (image_aspect_ratio > window_aspect_ratio) + { + // trim off top and bottom + S32 new_height = llround((F32)getRect().getWidth() / image_aspect_ratio); + rect.mBottom += (getRect().getHeight() - new_height) / 2; + rect.mTop -= (getRect().getHeight() - new_height) / 2; + } + else if (image_aspect_ratio < window_aspect_ratio) + { + // trim off left and right + S32 new_width = llround((F32)getRect().getHeight() * image_aspect_ratio); + rect.mLeft += (getRect().getWidth() - new_width) / 2; + rect.mRight -= (getRect().getWidth() - new_width) / 2; + } + } + + // Stop shining animation. + mShineAnimTimer.stop(); + + // Update snapshot if requested. + if (new_snapshot) + { + mSnapshotDelayTimer.start(); + mSnapshotDelayTimer.setTimerExpirySec(delay); + LLFloaterSnapshot::preUpdate(); + LLFloaterSocial::preUpdate(); + } + + // Update thumbnail if requested. + if(new_thumbnail) + { + mThumbnailUpToDate = FALSE ; + } +} + +void LLSnapshotLivePreview::setSnapshotQuality(S32 quality) +{ + llclamp(quality, 0, 100); + if (quality != mSnapshotQuality) + { + mSnapshotQuality = quality; + gSavedSettings.setS32("SnapshotQuality", quality); + mSnapshotUpToDate = FALSE; + } +} + +void LLSnapshotLivePreview::drawPreviewRect(S32 offset_x, S32 offset_y) +{ + F32 line_width ; + glGetFloatv(GL_LINE_WIDTH, &line_width) ; + glLineWidth(2.0f * line_width) ; + LLColor4 color(0.0f, 0.0f, 0.0f, 1.0f) ; + gl_rect_2d( mPreviewRect.mLeft + offset_x, mPreviewRect.mTop + offset_y, + mPreviewRect.mRight + offset_x, mPreviewRect.mBottom + offset_y, color, FALSE ) ; + glLineWidth(line_width) ; + + //draw four alpha rectangles to cover areas outside of the snapshot image + if(!mKeepAspectRatio) + { + LLColor4 alpha_color(0.5f, 0.5f, 0.5f, 0.8f) ; + S32 dwl = 0, dwr = 0 ; + if(mThumbnailWidth > mPreviewRect.getWidth()) + { + dwl = (mThumbnailWidth - mPreviewRect.getWidth()) >> 1 ; + dwr = mThumbnailWidth - mPreviewRect.getWidth() - dwl ; + + gl_rect_2d(mPreviewRect.mLeft + offset_x - dwl, mPreviewRect.mTop + offset_y, + mPreviewRect.mLeft + offset_x, mPreviewRect.mBottom + offset_y, alpha_color, TRUE ) ; + gl_rect_2d( mPreviewRect.mRight + offset_x, mPreviewRect.mTop + offset_y, + mPreviewRect.mRight + offset_x + dwr, mPreviewRect.mBottom + offset_y, alpha_color, TRUE ) ; + } + + if(mThumbnailHeight > mPreviewRect.getHeight()) + { + S32 dh = (mThumbnailHeight - mPreviewRect.getHeight()) >> 1 ; + gl_rect_2d(mPreviewRect.mLeft + offset_x - dwl, mPreviewRect.mBottom + offset_y , + mPreviewRect.mRight + offset_x + dwr, mPreviewRect.mBottom + offset_y - dh, alpha_color, TRUE ) ; + + dh = mThumbnailHeight - mPreviewRect.getHeight() - dh ; + gl_rect_2d( mPreviewRect.mLeft + offset_x - dwl, mPreviewRect.mTop + offset_y + dh, + mPreviewRect.mRight + offset_x + dwr, mPreviewRect.mTop + offset_y, alpha_color, TRUE ) ; + } + } +} + +//called when the frame is frozen. +void LLSnapshotLivePreview::draw() +{ + if (getCurrentImage() && + mPreviewImageEncoded.notNull() && + getSnapshotUpToDate()) + { + LLColor4 bg_color(0.f, 0.f, 0.3f, 0.4f); + gl_rect_2d(getRect(), bg_color); + const LLRect& rect = getImageRect(); + LLRect shadow_rect = rect; + shadow_rect.stretch(BORDER_WIDTH); + gl_drop_shadow(shadow_rect.mLeft, shadow_rect.mTop, shadow_rect.mRight, shadow_rect.mBottom, LLColor4(0.f, 0.f, 0.f, mNeedsFlash ? 0.f :0.5f), 10); + + LLColor4 image_color(1.f, 1.f, 1.f, 1.f); + gGL.color4fv(image_color.mV); + gGL.getTexUnit(0)->bind(getCurrentImage()); + // calculate UV scale + F32 uv_width = isImageScaled() ? 1.f : llmin((F32)getWidth() / (F32)getCurrentImage()->getWidth(), 1.f); + F32 uv_height = isImageScaled() ? 1.f : llmin((F32)getHeight() / (F32)getCurrentImage()->getHeight(), 1.f); + gGL.pushMatrix(); + { + gGL.translatef((F32)rect.mLeft, (F32)rect.mBottom, 0.f); + gGL.begin(LLRender::QUADS); + { + gGL.texCoord2f(uv_width, uv_height); + gGL.vertex2i(rect.getWidth(), rect.getHeight() ); + + gGL.texCoord2f(0.f, uv_height); + gGL.vertex2i(0, rect.getHeight() ); + + gGL.texCoord2f(0.f, 0.f); + gGL.vertex2i(0, 0); + + gGL.texCoord2f(uv_width, 0.f); + gGL.vertex2i(rect.getWidth(), 0); + } + gGL.end(); + } + gGL.popMatrix(); + + gGL.color4f(1.f, 1.f, 1.f, mFlashAlpha); + gl_rect_2d(getRect()); + if (mNeedsFlash) + { + if (mFlashAlpha < 1.f) + { + mFlashAlpha = lerp(mFlashAlpha, 1.f, LLCriticalDamp::getInterpolant(0.02f)); + } + else + { + mNeedsFlash = FALSE; + } + } + else + { + mFlashAlpha = lerp(mFlashAlpha, 0.f, LLCriticalDamp::getInterpolant(0.15f)); + } + + // Draw shining animation if appropriate. + if (mShineCountdown > 0) + { + mShineCountdown--; + if (mShineCountdown == 0) + { + mShineAnimTimer.start(); + } + } + else if (mShineAnimTimer.getStarted()) + { + lldebugs << "Drawing shining animation" << llendl; + F32 shine_interp = llmin(1.f, mShineAnimTimer.getElapsedTimeF32() / SHINE_TIME); + + // draw "shine" effect + LLLocalClipRect clip(getLocalRect()); + { + // draw diagonal stripe with gradient that passes over screen + S32 x1 = gViewerWindow->getWindowWidthScaled() * llround((clamp_rescale(shine_interp, 0.f, 1.f, -1.f - SHINE_WIDTH, 1.f))); + S32 x2 = x1 + llround(gViewerWindow->getWindowWidthScaled() * SHINE_WIDTH); + S32 x3 = x2 + llround(gViewerWindow->getWindowWidthScaled() * SHINE_WIDTH); + S32 y1 = 0; + S32 y2 = gViewerWindow->getWindowHeightScaled(); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.begin(LLRender::QUADS); + { + gGL.color4f(1.f, 1.f, 1.f, 0.f); + gGL.vertex2i(x1, y1); + gGL.vertex2i(x1 + gViewerWindow->getWindowWidthScaled(), y2); + gGL.color4f(1.f, 1.f, 1.f, SHINE_OPACITY); + gGL.vertex2i(x2 + gViewerWindow->getWindowWidthScaled(), y2); + gGL.vertex2i(x2, y1); + + gGL.color4f(1.f, 1.f, 1.f, SHINE_OPACITY); + gGL.vertex2i(x2, y1); + gGL.vertex2i(x2 + gViewerWindow->getWindowWidthScaled(), y2); + gGL.color4f(1.f, 1.f, 1.f, 0.f); + gGL.vertex2i(x3 + gViewerWindow->getWindowWidthScaled(), y2); + gGL.vertex2i(x3, y1); + } + gGL.end(); + } + + // if we're at the end of the animation, stop + if (shine_interp >= 1.f) + { + mShineAnimTimer.stop(); + } + } + } + + // draw framing rectangle + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.color4f(1.f, 1.f, 1.f, 1.f); + const LLRect& outline_rect = getImageRect(); + gGL.begin(LLRender::QUADS); + { + gGL.vertex2i(outline_rect.mLeft - BORDER_WIDTH, outline_rect.mTop + BORDER_WIDTH); + gGL.vertex2i(outline_rect.mRight + BORDER_WIDTH, outline_rect.mTop + BORDER_WIDTH); + gGL.vertex2i(outline_rect.mRight, outline_rect.mTop); + gGL.vertex2i(outline_rect.mLeft, outline_rect.mTop); + + gGL.vertex2i(outline_rect.mLeft, outline_rect.mBottom); + gGL.vertex2i(outline_rect.mRight, outline_rect.mBottom); + gGL.vertex2i(outline_rect.mRight + BORDER_WIDTH, outline_rect.mBottom - BORDER_WIDTH); + gGL.vertex2i(outline_rect.mLeft - BORDER_WIDTH, outline_rect.mBottom - BORDER_WIDTH); + + gGL.vertex2i(outline_rect.mLeft, outline_rect.mTop); + gGL.vertex2i(outline_rect.mLeft, outline_rect.mBottom); + gGL.vertex2i(outline_rect.mLeft - BORDER_WIDTH, outline_rect.mBottom - BORDER_WIDTH); + gGL.vertex2i(outline_rect.mLeft - BORDER_WIDTH, outline_rect.mTop + BORDER_WIDTH); + + gGL.vertex2i(outline_rect.mRight, outline_rect.mBottom); + gGL.vertex2i(outline_rect.mRight, outline_rect.mTop); + gGL.vertex2i(outline_rect.mRight + BORDER_WIDTH, outline_rect.mTop + BORDER_WIDTH); + gGL.vertex2i(outline_rect.mRight + BORDER_WIDTH, outline_rect.mBottom - BORDER_WIDTH); + } + gGL.end(); + } + + // draw old image dropping away + if (mFallAnimTimer.getStarted()) + { + S32 old_image_index = (mCurImageIndex + 1) % 2; + if (mViewerImage[old_image_index].notNull() && mFallAnimTimer.getElapsedTimeF32() < FALL_TIME) + { + lldebugs << "Drawing fall animation" << llendl; + F32 fall_interp = mFallAnimTimer.getElapsedTimeF32() / FALL_TIME; + F32 alpha = clamp_rescale(fall_interp, 0.f, 1.f, 0.8f, 0.4f); + LLColor4 image_color(1.f, 1.f, 1.f, alpha); + gGL.color4fv(image_color.mV); + gGL.getTexUnit(0)->bind(mViewerImage[old_image_index]); + // calculate UV scale + // *FIX get this to work with old image + BOOL rescale = !mImageScaled[old_image_index] && mViewerImage[mCurImageIndex].notNull(); + F32 uv_width = rescale ? llmin((F32)mWidth[old_image_index] / (F32)mViewerImage[mCurImageIndex]->getWidth(), 1.f) : 1.f; + F32 uv_height = rescale ? llmin((F32)mHeight[old_image_index] / (F32)mViewerImage[mCurImageIndex]->getHeight(), 1.f) : 1.f; + gGL.pushMatrix(); + { + LLRect& rect = mImageRect[old_image_index]; + gGL.translatef((F32)rect.mLeft, (F32)rect.mBottom - llround(getRect().getHeight() * 2.f * (fall_interp * fall_interp)), 0.f); + gGL.rotatef(-45.f * fall_interp, 0.f, 0.f, 1.f); + gGL.begin(LLRender::QUADS); + { + gGL.texCoord2f(uv_width, uv_height); + gGL.vertex2i(rect.getWidth(), rect.getHeight() ); + + gGL.texCoord2f(0.f, uv_height); + gGL.vertex2i(0, rect.getHeight() ); + + gGL.texCoord2f(0.f, 0.f); + gGL.vertex2i(0, 0); + + gGL.texCoord2f(uv_width, 0.f); + gGL.vertex2i(rect.getWidth(), 0); + } + gGL.end(); + } + gGL.popMatrix(); + } + } +} + +/*virtual*/ +void LLSnapshotLivePreview::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLRect old_rect = getRect(); + LLView::reshape(width, height, called_from_parent); + if (old_rect.getWidth() != width || old_rect.getHeight() != height) + { + lldebugs << "window reshaped, updating thumbnail" << llendl; + updateSnapshot(FALSE, TRUE); + } +} + +BOOL LLSnapshotLivePreview::setThumbnailImageSize() +{ + if(getWidth() < 10 || getHeight() < 10) + { + return FALSE ; + } + S32 window_width = gViewerWindow->getWindowWidthRaw() ; + S32 window_height = gViewerWindow->getWindowHeightRaw() ; + + F32 window_aspect_ratio = ((F32)window_width) / ((F32)window_height); + + // UI size for thumbnail + // *FIXME: the rect does not change, so maybe there's no need to recalculate max w/h. + const LLRect& thumbnail_rect = mThumbnailPlaceholderRect; + S32 max_width = thumbnail_rect.getWidth(); + S32 max_height = thumbnail_rect.getHeight(); + + if (window_aspect_ratio > (F32)max_width / max_height) + { + // image too wide, shrink to width + mThumbnailWidth = max_width; + mThumbnailHeight = llround((F32)max_width / window_aspect_ratio); + } + else + { + // image too tall, shrink to height + mThumbnailHeight = max_height; + mThumbnailWidth = llround((F32)max_height * window_aspect_ratio); + } + + if(mThumbnailWidth > window_width || mThumbnailHeight > window_height) + { + return FALSE ;//if the window is too small, ignore thumbnail updating. + } + + S32 left = 0 , top = mThumbnailHeight, right = mThumbnailWidth, bottom = 0 ; + if(!mKeepAspectRatio) + { + F32 ratio_x = (F32)getWidth() / window_width ; + F32 ratio_y = (F32)getHeight() / window_height ; + + //if(getWidth() > window_width || + // getHeight() > window_height ) + { + if(ratio_x > ratio_y) + { + top = (S32)(top * ratio_y / ratio_x) ; + } + else + { + right = (S32)(right * ratio_x / ratio_y) ; + } + } + //else + //{ + // right = (S32)(right * ratio_x) ; + // top = (S32)(top * ratio_y) ; + //} + left = (S32)((mThumbnailWidth - right) * 0.5f) ; + bottom = (S32)((mThumbnailHeight - top) * 0.5f) ; + top += bottom ; + right += left ; + } + mPreviewRect.set(left - 1, top + 1, right + 1, bottom - 1) ; + + return TRUE ; +} + +void LLSnapshotLivePreview::generateThumbnailImage(BOOL force_update) +{ + if(mThumbnailUpdateLock) //in the process of updating + { + return ; + } + if(getThumbnailUpToDate() && !force_update)//already updated + { + return ; + } + if(getWidth() < 10 || getHeight() < 10) + { + return ; + } + + ////lock updating + mThumbnailUpdateLock = TRUE ; + + if(!setThumbnailImageSize()) + { + mThumbnailUpdateLock = FALSE ; + mThumbnailUpToDate = TRUE ; + return ; + } + + if(mThumbnailImage) + { + resetThumbnailImage() ; + } + + LLPointer<LLImageRaw> raw = new LLImageRaw; + if(!gViewerWindow->thumbnailSnapshot(raw, + mThumbnailWidth, mThumbnailHeight, + gSavedSettings.getBOOL("RenderUIInSnapshot"), + FALSE, + mSnapshotBufferType) ) + { + raw = NULL ; + } + + if(raw) + { + raw->expandToPowerOfTwo(); + mThumbnailImage = LLViewerTextureManager::getLocalTexture(raw.get(), FALSE); + mThumbnailUpToDate = TRUE ; + } + + //unlock updating + mThumbnailUpdateLock = FALSE ; +} + + +// Called often. Checks whether it's time to grab a new snapshot and if so, does it. +// Returns TRUE if new snapshot generated, FALSE otherwise. +//static +BOOL LLSnapshotLivePreview::onIdle( void* snapshot_preview ) +{ + LLSnapshotLivePreview* previewp = (LLSnapshotLivePreview*)snapshot_preview; + if (previewp->getWidth() == 0 || previewp->getHeight() == 0) + { + llwarns << "Incorrect dimensions: " << previewp->getWidth() << "x" << previewp->getHeight() << llendl; + return FALSE; + } + + // If we're in freeze-frame mode and camera has moved, update snapshot. + LLVector3 new_camera_pos = LLViewerCamera::getInstance()->getOrigin(); + LLQuaternion new_camera_rot = LLViewerCamera::getInstance()->getQuaternion(); + if (gSavedSettings.getBOOL("FreezeTime") && + (new_camera_pos != previewp->mCameraPos || dot(new_camera_rot, previewp->mCameraRot) < 0.995f)) + { + previewp->mCameraPos = new_camera_pos; + previewp->mCameraRot = new_camera_rot; + // request a new snapshot whenever the camera moves, with a time delay + BOOL autosnap = gSavedSettings.getBOOL("AutoSnapshot"); + lldebugs << "camera moved, updating thumbnail" << llendl; + previewp->updateSnapshot( + autosnap, // whether a new snapshot is needed or merely invalidate the existing one + FALSE, // or if 1st arg is false, whether to produce a new thumbnail image. + autosnap ? AUTO_SNAPSHOT_TIME_DELAY : 0.f); // shutter delay if 1st arg is true. + } + + // see if it's time yet to snap the shot and bomb out otherwise. + previewp->mSnapshotActive = + (previewp->mSnapshotDelayTimer.getStarted() && previewp->mSnapshotDelayTimer.hasExpired()) + && !LLToolCamera::getInstance()->hasMouseCapture(); // don't take snapshots while ALT-zoom active + if ( ! previewp->mSnapshotActive) + { + return FALSE; + } + + // time to produce a snapshot + previewp->setThumbnailImageSize(); + + lldebugs << "producing snapshot" << llendl; + if (!previewp->mPreviewImage) + { + previewp->mPreviewImage = new LLImageRaw; + } + + if (!previewp->mPreviewImageEncoded) + { + previewp->mPreviewImageEncoded = new LLImageRaw; + } + + previewp->setVisible(FALSE); + previewp->setEnabled(FALSE); + + previewp->getWindow()->incBusyCount(); + previewp->setImageScaled(FALSE); + + // grab the raw image and encode it into desired format + if(gViewerWindow->rawSnapshot( + previewp->mPreviewImage, + previewp->getWidth(), + previewp->getHeight(), + previewp->mKeepAspectRatio,//gSavedSettings.getBOOL("KeepAspectForSnapshot"), + previewp->getSnapshotType() == LLSnapshotLivePreview::SNAPSHOT_TEXTURE, + gSavedSettings.getBOOL("RenderUIInSnapshot"), + FALSE, + previewp->mSnapshotBufferType, + previewp->getMaxImageSize())) + { + previewp->mPreviewImageEncoded->resize( + previewp->mPreviewImage->getWidth(), + previewp->mPreviewImage->getHeight(), + previewp->mPreviewImage->getComponents()); + + if(previewp->getSnapshotType() == SNAPSHOT_TEXTURE) + { + lldebugs << "Encoding new image of format J2C" << llendl; + LLPointer<LLImageJ2C> formatted = new LLImageJ2C; + LLPointer<LLImageRaw> scaled = new LLImageRaw( + previewp->mPreviewImage->getData(), + previewp->mPreviewImage->getWidth(), + previewp->mPreviewImage->getHeight(), + previewp->mPreviewImage->getComponents()); + + scaled->biasedScaleToPowerOfTwo(MAX_TEXTURE_SIZE); + previewp->setImageScaled(TRUE); + if (formatted->encode(scaled, 0.f)) + { + previewp->mDataSize = formatted->getDataSize(); + formatted->decode(previewp->mPreviewImageEncoded, 0); + } + } + else + { + // delete any existing image + previewp->mFormattedImage = NULL; + // now create the new one of the appropriate format. + LLFloaterSnapshot::ESnapshotFormat format = previewp->getSnapshotFormat(); + lldebugs << "Encoding new image of format " << format << llendl; + + switch(format) + { + case LLFloaterSnapshot::SNAPSHOT_FORMAT_PNG: + previewp->mFormattedImage = new LLImagePNG(); + break; + case LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG: + previewp->mFormattedImage = new LLImageJPEG(previewp->mSnapshotQuality); + break; + case LLFloaterSnapshot::SNAPSHOT_FORMAT_BMP: + previewp->mFormattedImage = new LLImageBMP(); + break; + } + if (previewp->mFormattedImage->encode(previewp->mPreviewImage, 0)) + { + previewp->mDataSize = previewp->mFormattedImage->getDataSize(); + // special case BMP to copy instead of decode otherwise decode will crash. + if(format == LLFloaterSnapshot::SNAPSHOT_FORMAT_BMP) + { + previewp->mPreviewImageEncoded->copy(previewp->mPreviewImage); + } + else + { + previewp->mFormattedImage->decode(previewp->mPreviewImageEncoded, 0); + } + } + } + + LLPointer<LLImageRaw> scaled = new LLImageRaw( + previewp->mPreviewImageEncoded->getData(), + previewp->mPreviewImageEncoded->getWidth(), + previewp->mPreviewImageEncoded->getHeight(), + previewp->mPreviewImageEncoded->getComponents()); + + if(!scaled->isBufferInvalid()) + { + // leave original image dimensions, just scale up texture buffer + if (previewp->mPreviewImageEncoded->getWidth() > 1024 || previewp->mPreviewImageEncoded->getHeight() > 1024) + { + // go ahead and shrink image to appropriate power of 2 for display + scaled->biasedScaleToPowerOfTwo(1024); + previewp->setImageScaled(TRUE); + } + else + { + // expand image but keep original image data intact + scaled->expandToPowerOfTwo(1024, FALSE); + } + + previewp->mViewerImage[previewp->mCurImageIndex] = LLViewerTextureManager::getLocalTexture(scaled.get(), FALSE); + LLPointer<LLViewerTexture> curr_preview_image = previewp->mViewerImage[previewp->mCurImageIndex]; + gGL.getTexUnit(0)->bind(curr_preview_image); + if (previewp->getSnapshotType() != SNAPSHOT_TEXTURE) + { + curr_preview_image->setFilteringOption(LLTexUnit::TFO_POINT); + } + else + { + curr_preview_image->setFilteringOption(LLTexUnit::TFO_ANISOTROPIC); + } + curr_preview_image->setAddressMode(LLTexUnit::TAM_CLAMP); + + previewp->mSnapshotUpToDate = TRUE; + previewp->generateThumbnailImage(TRUE) ; + + previewp->mPosTakenGlobal = gAgentCamera.getCameraPositionGlobal(); + previewp->mShineCountdown = 4; // wait a few frames to avoid animation glitch due to readback this frame + } + } + previewp->getWindow()->decBusyCount(); + // only show fullscreen preview when in freeze frame mode + previewp->setVisible(gSavedSettings.getBOOL("UseFreezeFrame")); + previewp->mSnapshotDelayTimer.stop(); + previewp->mSnapshotActive = FALSE; + + if(!previewp->getThumbnailUpToDate()) + { + previewp->generateThumbnailImage() ; + } + lldebugs << "done creating snapshot" << llendl; + LLFloaterSnapshot::postUpdate(); + LLFloaterSocial::postUpdate(); + + return TRUE; +} + +void LLSnapshotLivePreview::setSize(S32 w, S32 h) +{ + lldebugs << "setSize(" << w << ", " << h << ")" << llendl; + setWidth(w); + setHeight(h); +} + +void LLSnapshotLivePreview::getSize(S32& w, S32& h) const +{ + w = getWidth(); + h = getHeight(); +} + +void LLSnapshotLivePreview::saveTexture() +{ + lldebugs << "saving texture: " << mPreviewImage->getWidth() << "x" << mPreviewImage->getHeight() << llendl; + // gen a new uuid for this asset + LLTransactionID tid; + tid.generate(); + LLAssetID new_asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); + + LLPointer<LLImageJ2C> formatted = new LLImageJ2C; + LLPointer<LLImageRaw> scaled = new LLImageRaw(mPreviewImage->getData(), + mPreviewImage->getWidth(), + mPreviewImage->getHeight(), + mPreviewImage->getComponents()); + + scaled->biasedScaleToPowerOfTwo(MAX_TEXTURE_SIZE); + lldebugs << "scaled texture to " << scaled->getWidth() << "x" << scaled->getHeight() << llendl; + + if (formatted->encode(scaled, 0.0f)) + { + LLVFile::writeFile(formatted->getData(), formatted->getDataSize(), gVFS, new_asset_id, LLAssetType::AT_TEXTURE); + std::string pos_string; + LLAgentUI::buildLocationString(pos_string, LLAgentUI::LOCATION_FORMAT_FULL); + std::string who_took_it; + LLAgentUI::buildFullname(who_took_it); + LLAssetStorage::LLStoreAssetCallback callback = NULL; + S32 expected_upload_cost = LLGlobalEconomy::Singleton::getInstance()->getPriceUpload(); + void *userdata = NULL; + upload_new_resource(tid, // tid + LLAssetType::AT_TEXTURE, + "Snapshot : " + pos_string, + "Taken by " + who_took_it + " at " + pos_string, + 0, + LLFolderType::FT_SNAPSHOT_CATEGORY, + LLInventoryType::IT_SNAPSHOT, + PERM_ALL, // Note: Snapshots to inventory is a special case of content upload + LLFloaterPerms::getGroupPerms(), // that is more permissive than other uploads + LLFloaterPerms::getEveryonePerms(), + "Snapshot : " + pos_string, + callback, expected_upload_cost, userdata); + gViewerWindow->playSnapshotAnimAndSound(); + } + else + { + LLNotificationsUtil::add("ErrorEncodingSnapshot"); + llwarns << "Error encoding snapshot" << llendl; + } + + LLViewerStats::getInstance()->incStat(LLViewerStats::ST_SNAPSHOT_COUNT ); + + mDataSize = 0; +} + +BOOL LLSnapshotLivePreview::saveLocal() +{ + BOOL success = gViewerWindow->saveImageNumbered(mFormattedImage); + + if(success) + { + gViewerWindow->playSnapshotAnimAndSound(); + } + return success; +} + +void LLSnapshotLivePreview::saveWeb() +{ + // *FIX: Will break if the window closes because of CloseSnapshotOnKeep! + // Needs to pass on ownership of the image. + LLImageJPEG* jpg = dynamic_cast<LLImageJPEG*>(mFormattedImage.get()); + if(!jpg) + { + llwarns << "Formatted image not a JPEG" << llendl; + return; + } + + LLSD metadata; + metadata["description"] = getChild<LLLineEditor>("description")->getText(); + + LLLandmarkActions::getRegionNameAndCoordsFromPosGlobal(gAgentCamera.getCameraPositionGlobal(), + boost::bind(&LLSnapshotLivePreview::regionNameCallback, this, jpg, metadata, _1, _2, _3, _4)); + + gViewerWindow->playSnapshotAnimAndSound(); +} + +void LLSnapshotLivePreview::regionNameCallback(LLImageJPEG* snapshot, LLSD& metadata, const std::string& name, S32 x, S32 y, S32 z) +{ + metadata["slurl"] = LLSLURL(name, LLVector3d(x, y, z)).getSLURLString(); + + LLWebSharing::instance().shareSnapshot(snapshot, metadata); +} diff --git a/indra/newview/llsnapshotlivepreview.h b/indra/newview/llsnapshotlivepreview.h new file mode 100644 index 0000000000..fe3d257b02 --- /dev/null +++ b/indra/newview/llsnapshotlivepreview.h @@ -0,0 +1,164 @@ +/** +* @file llsnapshotlivepreview.h +* @brief Header file for llsnapshotlivepreview +* @author Gilbert@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, 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_LLSNAPSHOTLIVEPREVIEW_H +#define LL_LLSNAPSHOTLIVEPREVIEW_H + +#include "llpanelsnapshot.h" +#include "llviewerwindow.h" + +class LLImageJPEG; + +///---------------------------------------------------------------------------- +/// Class LLSnapshotLivePreview +///---------------------------------------------------------------------------- +class LLSnapshotLivePreview : public LLView +{ + LOG_CLASS(LLSnapshotLivePreview); +public: + enum ESnapshotType + { + SNAPSHOT_POSTCARD, + SNAPSHOT_TEXTURE, + SNAPSHOT_LOCAL, + SNAPSHOT_WEB + }; + + + struct Params : public LLInitParam::Block<Params, LLView::Params> + { + Params() + { + name = "snapshot_live_preview"; + mouse_opaque = false; + } + }; + + + LLSnapshotLivePreview(const LLSnapshotLivePreview::Params& p); + ~LLSnapshotLivePreview(); + + /*virtual*/ void draw(); + /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent); + + void setSize(S32 w, S32 h); + void setWidth(S32 w) { mWidth[mCurImageIndex] = w; } + void setHeight(S32 h) { mHeight[mCurImageIndex] = h; } + void getSize(S32& w, S32& h) const; + S32 getWidth() const { return mWidth[mCurImageIndex]; } + S32 getHeight() const { return mHeight[mCurImageIndex]; } + S32 getDataSize() const { return mDataSize; } + void setMaxImageSize(S32 size) ; + S32 getMaxImageSize() {return mMaxImageSize ;} + + ESnapshotType getSnapshotType() const { return mSnapshotType; } + LLFloaterSnapshot::ESnapshotFormat getSnapshotFormat() const { return mSnapshotFormat; } + BOOL getSnapshotUpToDate() const { return mSnapshotUpToDate; } + BOOL isSnapshotActive() { return mSnapshotActive; } + LLViewerTexture* getThumbnailImage() const { return mThumbnailImage ; } + S32 getThumbnailWidth() const { return mThumbnailWidth ; } + S32 getThumbnailHeight() const { return mThumbnailHeight ; } + BOOL getThumbnailLock() const { return mThumbnailUpdateLock ; } + BOOL getThumbnailUpToDate() const { return mThumbnailUpToDate ;} + LLViewerTexture* getCurrentImage(); + F32 getImageAspect(); + F32 getAspect() ; + const LLRect& getImageRect() const { return mImageRect[mCurImageIndex]; } + BOOL isImageScaled() const { return mImageScaled[mCurImageIndex]; } + void setImageScaled(BOOL scaled) { mImageScaled[mCurImageIndex] = scaled; } + const LLVector3d& getPosTakenGlobal() const { return mPosTakenGlobal; } + + void setSnapshotType(ESnapshotType type) { mSnapshotType = type; } + void setSnapshotFormat(LLFloaterSnapshot::ESnapshotFormat type) { mSnapshotFormat = type; } + void setSnapshotQuality(S32 quality); + void setSnapshotBufferType(LLViewerWindow::ESnapshotType type) { mSnapshotBufferType = type; } + void updateSnapshot(BOOL new_snapshot, BOOL new_thumbnail = FALSE, F32 delay = 0.f); + void saveWeb(); + void saveTexture(); + BOOL saveLocal(); + + LLPointer<LLImageFormatted> getFormattedImage() const { return mFormattedImage; } + LLPointer<LLImageRaw> getEncodedImage() const { return mPreviewImageEncoded; } + + /// Sets size of preview thumbnail image and thhe surrounding rect. + void setThumbnailPlaceholderRect(const LLRect& rect) {mThumbnailPlaceholderRect = rect; } + BOOL setThumbnailImageSize() ; + void generateThumbnailImage(BOOL force_update = FALSE) ; + void resetThumbnailImage() { mThumbnailImage = NULL ; } + void drawPreviewRect(S32 offset_x, S32 offset_y) ; + + // Returns TRUE when snapshot generated, FALSE otherwise. + static BOOL onIdle( void* snapshot_preview ); + + // callback for region name resolve + void regionNameCallback(LLImageJPEG* snapshot, LLSD& metadata, const std::string& name, S32 x, S32 y, S32 z); + +private: + LLColor4 mColor; + LLPointer<LLViewerTexture> mViewerImage[2]; //used to represent the scene when the frame is frozen. + LLRect mImageRect[2]; + S32 mWidth[2]; + S32 mHeight[2]; + BOOL mImageScaled[2]; + S32 mMaxImageSize ; + + //thumbnail image + LLPointer<LLViewerTexture> mThumbnailImage ; + S32 mThumbnailWidth ; + S32 mThumbnailHeight ; + LLRect mPreviewRect ; + BOOL mThumbnailUpdateLock ; + BOOL mThumbnailUpToDate ; + LLRect mThumbnailPlaceholderRect; + + S32 mCurImageIndex; + LLPointer<LLImageRaw> mPreviewImage; + LLPointer<LLImageRaw> mPreviewImageEncoded; + LLPointer<LLImageFormatted> mFormattedImage; + LLFrameTimer mSnapshotDelayTimer; + S32 mShineCountdown; + LLFrameTimer mShineAnimTimer; + F32 mFlashAlpha; + BOOL mNeedsFlash; + LLVector3d mPosTakenGlobal; + S32 mSnapshotQuality; + S32 mDataSize; + ESnapshotType mSnapshotType; + LLFloaterSnapshot::ESnapshotFormat mSnapshotFormat; + BOOL mSnapshotUpToDate; + LLFrameTimer mFallAnimTimer; + LLVector3 mCameraPos; + LLQuaternion mCameraRot; + BOOL mSnapshotActive; + LLViewerWindow::ESnapshotType mSnapshotBufferType; + +public: + static std::set<LLSnapshotLivePreview*> sList; + BOOL mKeepAspectRatio ; +}; + +#endif // LL_LLSNAPSHOTLIVEPREVIEW_H + diff --git a/indra/newview/llsyswellwindow.cpp b/indra/newview/llsyswellwindow.cpp index 1a3add2bfb..626d69aca4 100755 --- a/indra/newview/llsyswellwindow.cpp +++ b/indra/newview/llsyswellwindow.cpp @@ -118,6 +118,11 @@ void LLSysWellWindow::removeItemByID(const LLUUID& id) } } + LLPanel * LLSysWellWindow::findItemByID(const LLUUID& id) +{ + return mMessageList->getItemByValue(id); +} + //--------------------------------------------------------------------------------- //--------------------------------------------------------------------------------- void LLSysWellWindow::initChannel() diff --git a/indra/newview/llsyswellwindow.h b/indra/newview/llsyswellwindow.h index cc5c057d8b..71b41476f5 100755 --- a/indra/newview/llsyswellwindow.h +++ b/indra/newview/llsyswellwindow.h @@ -55,6 +55,7 @@ public: // Operating with items void removeItemByID(const LLUUID& id); + LLPanel * findItemByID(const LLUUID& id); // Operating with outfit virtual void setVisible(BOOL visible); diff --git a/indra/newview/lltoastimpanel.cpp b/indra/newview/lltoastimpanel.cpp index 09ab31df36..bdbd8f1f83 100755 --- a/indra/newview/lltoastimpanel.cpp +++ b/indra/newview/lltoastimpanel.cpp @@ -93,7 +93,7 @@ LLToastIMPanel::LLToastIMPanel(LLToastIMPanel::Params &p) : LLToastPanel(p.notif if(!mIsGroupMsg) { - mAvatarName->setValue(p.from); + mAvatarName->setValue(p.from); } mTime->setValue(p.time); mSessionID = p.session_id; @@ -164,7 +164,7 @@ void LLToastIMPanel::spawnNameToolTip() params.background_visible(false); if(!mIsGroupMsg) { - params.click_callback(boost::bind(&LLFloaterReg::showInstance, "inspect_avatar", LLSD().with("avatar_id", mAvatarID), FALSE)); + params.click_callback(boost::bind(&LLFloaterReg::showInstance, "inspect_avatar", LLSD().with("avatar_id", mAvatarID), FALSE)); } else { diff --git a/indra/newview/llurlhistory.cpp b/indra/newview/llurlhistory.cpp index dd17068be5..a80b9da13c 100755 --- a/indra/newview/llurlhistory.cpp +++ b/indra/newview/llurlhistory.cpp @@ -103,22 +103,29 @@ LLSD LLURLHistory::getURLHistory(const std::string& collection) // static void LLURLHistory::addURL(const std::string& collection, const std::string& url) { - if(! url.empty()) + if(!url.empty()) { - sHistorySD[collection].insert(0, url); + LLURI u(url); + std::string simplified_url = u.scheme() + "://" + u.authority() + u.path(); + sHistorySD[collection].insert(0, simplified_url); LLURLHistory::limitSize(collection); } } // static void LLURLHistory::removeURL(const std::string& collection, const std::string& url) { - for(int index = 0; index < sHistorySD[collection].size(); index++) + if(!url.empty()) { - if(sHistorySD[collection].get(index).asString() == url) - { - sHistorySD[collection].erase(index); - } - } + LLURI u(url); + std::string simplified_url = u.scheme() + "://" + u.authority() + u.path(); + for(int index = 0; index < sHistorySD[collection].size(); index++) + { + if(sHistorySD[collection].get(index).asString() == simplified_url) + { + sHistorySD[collection].erase(index); + } + } + } } // static diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index c6b28b9e5e..4ce049df03 100755 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -103,6 +103,7 @@ #include "llfloatersettingsdebug.h" #include "llfloatersidepanelcontainer.h" #include "llfloatersnapshot.h" +#include "llfloatersocial.h" #include "llfloatersounddevices.h" #include "llfloaterspellchecksettings.h" #include "llfloatertelehub.h" @@ -303,6 +304,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("sell_land", "floater_sell_land.xml", &LLFloaterSellLand::buildFloater); LLFloaterReg::add("settings_debug", "floater_settings_debug.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSettingsDebug>); LLFloaterReg::add("sound_devices", "floater_sound_devices.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSoundDevices>); + LLFloaterReg::add("social", "floater_social.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSocial>); LLFloaterReg::add("stats", "floater_stats.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloater>); LLFloaterReg::add("start_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterRunQueue>); LLFloaterReg::add("stop_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterNotRunQueue>); @@ -311,7 +313,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("my_profile", "floater_my_web_profile.xml", (LLFloaterBuildFunc)&LLFloaterWebProfile::create); LLFloaterReg::add("profile", "floater_web_profile.xml", (LLFloaterBuildFunc)&LLFloaterWebProfile::create); LLFloaterReg::add("how_to", "floater_how_to.xml", (LLFloaterBuildFunc)&LLFloaterWebContent::create); - + LLFloaterReg::add("fbc_web", "floater_fbc_web.xml", (LLFloaterBuildFunc)&LLFloaterWebContent::create); LLFloaterUIPreviewUtil::registerFloater(); LLFloaterReg::add("upload_anim_bvh", "floater_animation_bvh_preview.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterBvhPreview>, "upload"); diff --git a/indra/newview/llviewerkeyboard.cpp b/indra/newview/llviewerkeyboard.cpp index 2fbb3bf868..aa9de076b8 100755 --- a/indra/newview/llviewerkeyboard.cpp +++ b/indra/newview/llviewerkeyboard.cpp @@ -687,7 +687,10 @@ BOOL LLViewerKeyboard::handleKey(KEY translated_key, MASK translated_mask, BOOL { // it is sufficient to set this value once per call to handlekey // without clearing it, as it is only used in the subsequent call to scanKey - mKeyHandledByUI[translated_key] = gViewerWindow->handleKey(translated_key, translated_mask); + mKeyHandledByUI[translated_key] = gViewerWindow->handleKey(translated_key, translated_mask); + // mKeyHandledByUI is not what you think ... this indicates whether the UI has handled this keypress yet (any keypress) + // NOT whether some UI shortcut wishes to handle the keypress + } return mKeyHandledByUI[translated_key]; } diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index 2cec808f19..139e3c3a2d 100755 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -1994,7 +1994,12 @@ void LLViewerMediaImpl::loadURI() "<>#%" ";/?:@&=", false); - llinfos << "Asking media source to load URI: " << uri << llendl; + { + // Do not log the query parts + LLURI u(uri); + std::string sanitized_uri = (u.query().empty() ? uri : u.scheme() + "://" + u.authority() + u.path()); + llinfos << "Asking media source to load URI: " << sanitized_uri << llendl; + } mMediaSource->loadURI( uri ); @@ -2561,7 +2566,12 @@ void LLViewerMediaImpl::navigateTo(const std::string& url, const std::string& mi if(mPriority == LLPluginClassMedia::PRIORITY_UNLOADED) { // Helpful to have media urls in log file. Shouldn't be spammy. - llinfos << "NOT LOADING media id= " << mTextureId << " url=" << url << " mime_type=" << mime_type << llendl; + { + // Do not log the query parts + LLURI u(url); + std::string sanitized_url = (u.query().empty() ? url : u.scheme() + "://" + u.authority() + u.path()); + llinfos << "NOT LOADING media id= " << mTextureId << " url=" << sanitized_url << ", mime_type=" << mime_type << llendl; + } // This impl should not be loaded at this time. LL_DEBUGS("PluginPriority") << this << "Not loading (PRIORITY_UNLOADED)" << LL_ENDL; @@ -2576,7 +2586,12 @@ void LLViewerMediaImpl::navigateTo(const std::string& url, const std::string& mi void LLViewerMediaImpl::navigateInternal() { // Helpful to have media urls in log file. Shouldn't be spammy. - llinfos << "media id= " << mTextureId << " url=" << mMediaURL << " mime_type=" << mMimeType << llendl; + { + // Do not log the query parts + LLURI u(mMediaURL); + std::string sanitized_url = (u.query().empty() ? mMediaURL : u.scheme() + "://" + u.authority() + u.path()); + llinfos << "media id= " << mTextureId << " url=" << sanitized_url << ", mime_type=" << mMimeType << llendl; + } if(mNavigateSuspended) { diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index dc8a27c5c8..2b1c106932 100755 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -40,11 +40,13 @@ #include "llinventorypanel.h" #include "llnotifications.h" #include "llnotificationsutil.h" +#include "llviewereventrecorder.h" // newview includes #include "llagent.h" #include "llagentaccess.h" #include "llagentcamera.h" +#include "llagentui.h" #include "llagentwearables.h" #include "llagentpilot.h" #include "llcompilequeue.h" @@ -52,6 +54,7 @@ #include "lldaycyclemanager.h" #include "lldebugview.h" #include "llenvmanager.h" +#include "llfacebookconnect.h" #include "llfilepicker.h" #include "llfirstuse.h" #include "llfloaterbuy.h" @@ -1952,6 +1955,43 @@ class LLAdvancedDropPacket : public view_listener_t }; +//////////////////// +// EVENT Recorder // +/////////////////// + + +class LLAdvancedViewerEventRecorder : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string command = userdata.asString(); + if ("start playback" == command) + { + llinfos << "Event Playback starting" << llendl; + LLViewerEventRecorder::instance().playbackRecording(); + llinfos << "Event Playback completed" << llendl; + } + else if ("stop playback" == command) + { + // Future + } + else if ("start recording" == command) + { + LLViewerEventRecorder::instance().setEventLoggingOn(); + llinfos << "Event recording started" << llendl; + } + else if ("stop recording" == command) + { + LLViewerEventRecorder::instance().setEventLoggingOff(); + llinfos << "Event recording stopped" << llendl; + } + + return true; + } +}; + + + ///////////////// // AGENT PILOT // @@ -8421,6 +8461,8 @@ void initialize_menus() // Don't prepend MenuName.Foo because these can be used in any menu. enable.add("IsGodCustomerService", boost::bind(&is_god_customer_service)); + enable.add("displayViewerEventRecorderMenuItems",boost::bind(&LLViewerEventRecorder::displayViewerEventRecorderMenuItems,&LLViewerEventRecorder::instance())); + view_listener_t::addEnable(new LLUploadCostCalculator(), "Upload.CalculateCosts"); enable.add("Conversation.IsConversationLoggingAllowed", boost::bind(&LLFloaterIMContainer::isConversationLoggingAllowed)); @@ -8679,6 +8721,7 @@ void initialize_menus() view_listener_t::addMenu(new LLAdvancedAgentPilot(), "Advanced.AgentPilot"); view_listener_t::addMenu(new LLAdvancedToggleAgentPilotLoop(), "Advanced.ToggleAgentPilotLoop"); view_listener_t::addMenu(new LLAdvancedCheckAgentPilotLoop(), "Advanced.CheckAgentPilotLoop"); + view_listener_t::addMenu(new LLAdvancedViewerEventRecorder(), "Advanced.EventRecorder"); // Advanced > Debugging view_listener_t::addMenu(new LLAdvancedForceErrorBreakpoint(), "Advanced.ForceErrorBreakpoint"); diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 50862a74a0..167c33f3ff 100755 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -2220,7 +2220,7 @@ static std::string clean_name_from_im(const std::string& name, EInstantMessage t case IM_LURE_ACCEPTED: case IM_LURE_DECLINED: case IM_GODLIKE_LURE_USER: - case IM_YET_TO_BE_USED: + case IM_TELEPORT_REQUEST: case IM_GROUP_ELECTION_DEPRECATED: //IM_GOTO_URL //IM_FROM_TASK_AS_ALERT @@ -2988,6 +2988,7 @@ void process_improved_im(LLMessageSystem *msg, void **user_data) break; case IM_LURE_USER: + case IM_TELEPORT_REQUEST: { if (is_muted) { @@ -3010,7 +3011,8 @@ void process_improved_im(LLMessageSystem *msg, void **user_data) bool canUserAccessDstRegion = true; bool doesUserRequireMaturityIncrease = false; - if (parse_lure_bucket(region_info, region_handle, pos, look_at, region_access)) + // Do not parse the (empty) lure bucket for TELEPORT_REQUEST + if (IM_TELEPORT_REQUEST != dialog && parse_lure_bucket(region_info, region_handle, pos, look_at, region_access)) { region_access_str = LLViewerRegion::accessToString(region_access); region_access_icn = LLViewerRegion::getAccessIcon(region_access); @@ -3082,12 +3084,22 @@ void process_improved_im(LLMessageSystem *msg, void **user_data) } else { - LLNotification::Params params("TeleportOffered"); + LLNotification::Params params; + if (IM_LURE_USER == dialog) + { + params.name = "TeleportOffered"; + params.functor.name = "TeleportOffered"; + } + else if (IM_TELEPORT_REQUEST == dialog) + { + params.name = "TeleportRequest"; + params.functor.name = "TeleportRequest"; + } + params.substitutions = args; params.payload = payload; LLPostponedNotification::add<LLPostponedOfferNotification>( params, from_id, false); } - } } break; @@ -6874,6 +6886,51 @@ void send_group_notice(const LLUUID& group_id, bin_bucket_size); } +void send_lures(const LLSD& notification, const LLSD& response) +{ + std::string text = response["message"].asString(); + LLSLURL slurl; + LLAgentUI::buildSLURL(slurl); + text.append("\r\n").append(slurl.getSLURLString()); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_StartLure); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_Info); + msg->addU8Fast(_PREHASH_LureType, (U8)0); // sim will fill this in. + msg->addStringFast(_PREHASH_Message, text); + for(LLSD::array_const_iterator it = notification["payload"]["ids"].beginArray(); + it != notification["payload"]["ids"].endArray(); + ++it) + { + LLUUID target_id = it->asUUID(); + + msg->nextBlockFast(_PREHASH_TargetData); + msg->addUUIDFast(_PREHASH_TargetID, target_id); + + // Record the offer. + { + std::string target_name; + gCacheName->getFullName(target_id, target_name); // for im log filenames + LLSD args; + args["TO_NAME"] = LLSLURL("agent", target_id, "displayname").getSLURLString();; + + LLSD payload; + + //*TODO please rewrite all keys to the same case, lower or upper + payload["from_id"] = target_id; + payload["SUPPRESS_TOAST"] = true; + LLNotificationsUtil::add("TeleportOfferSent", args, payload); + + // Add the recepient to the recent people list. + LLRecentPeople::instance().add(target_id); + } + } + gAgent.sendReliableMessage(); +} + bool handle_lure_callback(const LLSD& notification, const LLSD& response) { static const unsigned OFFER_RECIPIENT_LIMIT = 250; @@ -6887,50 +6944,12 @@ bool handle_lure_callback(const LLSD& notification, const LLSD& response) LLNotificationsUtil::add("TooManyTeleportOffers", args); return false; } - - std::string text = response["message"].asString(); - LLSLURL slurl; - LLAgentUI::buildSLURL(slurl); - text.append("\r\n").append(slurl.getSLURLString()); + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if(0 == option) { - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_StartLure); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_Info); - msg->addU8Fast(_PREHASH_LureType, (U8)0); // sim will fill this in. - msg->addStringFast(_PREHASH_Message, text); - for(LLSD::array_const_iterator it = notification["payload"]["ids"].beginArray(); - it != notification["payload"]["ids"].endArray(); - ++it) - { - LLUUID target_id = it->asUUID(); - - msg->nextBlockFast(_PREHASH_TargetData); - msg->addUUIDFast(_PREHASH_TargetID, target_id); - - // Record the offer. - { - std::string target_name; - gCacheName->getFullName(target_id, target_name); // for im log filenames - LLSD args; - args["TO_NAME"] = LLSLURL("agent", target_id, "displayname").getSLURLString();; - - LLSD payload; - - //*TODO please rewrite all keys to the same case, lower or upper - payload["from_id"] = target_id; - LLNotificationsUtil::add("TeleportOfferSent", args, payload); - - // Add the recepient to the recent people list. - LLRecentPeople::instance().add(target_id); - } - } - gAgent.sendReliableMessage(); + send_lures(notification, response); } return false; @@ -6970,6 +6989,58 @@ void handle_lure(const uuid_vec_t& ids) } } +bool teleport_request_callback(const LLSD& notification, const LLSD& response) +{ + LLUUID from_id = notification["payload"]["from_id"].asUUID(); + if(from_id.isNull()) + { + llwarns << "from_id is NULL" << llendl; + return false; + } + + std::string from_name; + gCacheName->getFullName(from_id, from_name); + + if(LLMuteList::getInstance()->isMuted(from_id) && !LLMuteList::getInstance()->isLinden(from_name)) + { + return false; + } + + S32 option = 0; + if (response.isInteger()) + { + option = response.asInteger(); + } + else + { + option = LLNotificationsUtil::getSelectedOption(notification, response); + } + + switch(option) + { + // Yes + case 0: + { + LLSD dummy_notification; + dummy_notification["payload"]["ids"][0] = from_id; + + LLSD dummy_response; + dummy_response["message"] = response["message"]; + + send_lures(dummy_notification, dummy_response); + } + break; + + // No + case 1: + default: + break; + } + + return false; +} + +static LLNotificationFunctorRegistration teleport_request_callback_reg("TeleportRequest", teleport_request_callback); void send_improved_im(const LLUUID& to_id, const std::string& name, diff --git a/indra/newview/llviewerobjectlist.cpp b/indra/newview/llviewerobjectlist.cpp index a90694da7a..7cac4ee891 100755 --- a/indra/newview/llviewerobjectlist.cpp +++ b/indra/newview/llviewerobjectlist.cpp @@ -954,15 +954,17 @@ void LLViewerObjectList::update(LLAgent &agent, LLWorld &world) objectp = *idle_iter; llassert(objectp->isActive()); objectp->idleUpdate(agent, world, frame_time); - - } + } //update flexible objects LLVolumeImplFlexible::updateClass(); //update animated textures - LLViewerTextureAnim::updateClass(); - } + if (gAnimateTextures) + { + LLViewerTextureAnim::updateClass(); + } + } diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index e7123b746d..b8300ac5eb 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -1641,6 +1641,8 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) capabilityNames.append("EnvironmentSettings"); capabilityNames.append("EstateChangeInfo"); capabilityNames.append("EventQueueGet"); + capabilityNames.append("FacebookConnect"); + //capabilityNames.append("FacebookRedirect"); if (gSavedSettings.getBOOL("UseHTTPInventory")) { diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 427ec361ec..78768fdbc8 100755 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -198,6 +198,8 @@ #include "llagentui.h" #include "llwearablelist.h" +#include "llviewereventrecorder.h" + #include "llnotifications.h" #include "llnotificationsutil.h" #include "llnotificationmanager.h" @@ -954,27 +956,18 @@ BOOL LLViewerWindow::handleAnyMouseClick(LLWindow *window, LLCoordGL pos, MASK { llinfos << buttonname << " Mouse " << buttonstatestr << " handled by captor " << mouse_captor->getName() << llendl; } - return mouse_captor->handleAnyMouseClick(local_x, local_y, mask, clicktype, down); - } - // Topmost view gets a chance before the hierarchy - //LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl(); - //if (top_ctrl) - //{ - // S32 local_x, local_y; - // top_ctrl->screenPointToLocal( x, y, &local_x, &local_y ); - // if (top_ctrl->pointInView(local_x, local_y)) - // { - // return top_ctrl->handleAnyMouseClick(local_x, local_y, mask, clicktype, down) ; - // } - // else - // { - // if (down) - // { - // gFocusMgr.setTopCtrl(NULL); - // } - // } - //} + BOOL r = mouse_captor->handleAnyMouseClick(local_x, local_y, mask, clicktype, down); + if (r) { + + lldebugs << "LLViewerWindow::handleAnyMouseClick viewer with mousecaptor calling updatemouseeventinfo - local_x|global x "<< local_x << " " << x << "local/global y " << local_y << " " << y << llendl; + + LLViewerEventRecorder::instance().setMouseGlobalCoords(x,y); + LLViewerEventRecorder::instance().logMouseEvent(std::string(buttonstatestr),std::string(buttonname)); + + } + return r; + } // Mark the click as handled and return if we aren't within the root view to avoid spurious bugs if( !mRootView->pointInView(x, y) ) @@ -982,27 +975,44 @@ BOOL LLViewerWindow::handleAnyMouseClick(LLWindow *window, LLCoordGL pos, MASK return TRUE; } // Give the UI views a chance to process the click - if( mRootView->handleAnyMouseClick(x, y, mask, clicktype, down) ) + + BOOL r= mRootView->handleAnyMouseClick(x, y, mask, clicktype, down) ; + if (r) { + + lldebugs << "LLViewerWindow::handleAnyMouseClick calling updatemouseeventinfo - global x "<< " " << x << "global y " << y << "buttonstate: " << buttonstatestr << " buttonname " << buttonname << llendl; + + LLViewerEventRecorder::instance().setMouseGlobalCoords(x,y); + + // Clear local coords - this was a click on root window so these are not needed + // By not including them, this allows the test skeleton generation tool to be smarter when generating code + // the code generator can be smarter because when local coords are present it can try the xui path with local coords + // and fallback to global coordinates only if needed. + // The drawback to this approach is sometimes a valid xui path will appear to work fine, but NOT interact with the UI element + // (VITA support not implemented yet or not visible to VITA due to widget further up xui path not being visible to VITA) + // For this reason it's best to provide hints where possible here by leaving out local coordinates + LLViewerEventRecorder::instance().setMouseLocalCoords(-1,-1); + LLViewerEventRecorder::instance().logMouseEvent(buttonstatestr,buttonname); + if (LLView::sDebugMouseHandling) { - llinfos << buttonname << " Mouse " << buttonstatestr << " " << LLView::sMouseHandlerMessage << llendl; - } + llinfos << buttonname << " Mouse " << buttonstatestr << " " << LLViewerEventRecorder::instance().get_xui() << llendl; + } return TRUE; - } - else if (LLView::sDebugMouseHandling) - { - llinfos << buttonname << " Mouse " << buttonstatestr << " not handled by view" << llendl; - } + } else if (LLView::sDebugMouseHandling) + { + llinfos << buttonname << " Mouse " << buttonstatestr << " not handled by view" << llendl; + } } // Do not allow tool manager to handle mouseclicks if we have disconnected if(!gDisconnected && LLToolMgr::getInstance()->getCurrentTool()->handleAnyMouseClick( x, y, mask, clicktype, down ) ) { + LLViewerEventRecorder::instance().clear_xui(); return TRUE; } - + // If we got this far on a down-click, it wasn't handled. // Up-clicks, though, are always handled as far as the OS is concerned. BOOL default_rtn = !down; @@ -1373,7 +1383,8 @@ BOOL LLViewerWindow::handleTranslatedKeyUp(KEY key, MASK mask) void LLViewerWindow::handleScanKey(KEY key, BOOL key_down, BOOL key_up, BOOL key_level) { LLViewerJoystick::getInstance()->setCameraNeedsUpdate(true); - return gViewerKeyboard.scanKey(key, key_down, key_up, key_level); + gViewerKeyboard.scanKey(key, key_down, key_up, key_level); + return; // Be clear this function returns nothing } @@ -2519,6 +2530,8 @@ BOOL LLViewerWindow::handleKey(KEY key, MASK mask) ||(gLoginMenuBarView && gLoginMenuBarView->handleKey(key, mask, TRUE)) ||(gMenuHolder && gMenuHolder->handleKey(key, mask, TRUE))) { + lldebugs << "LLviewerWindow::handleKey handle nav keys for nav" << llendl; + LLViewerEventRecorder::instance().logKeyEvent(key,mask); return TRUE; } @@ -2533,12 +2546,14 @@ BOOL LLViewerWindow::handleKey(KEY key, MASK mask) && keyboard_focus && keyboard_focus->handleKey(key,mask,FALSE)) { + LLViewerEventRecorder::instance().logKeyEvent(key,mask); return TRUE; } if ((gMenuBarView && gMenuBarView->handleAcceleratorKey(key, mask)) ||(gLoginMenuBarView && gLoginMenuBarView->handleAcceleratorKey(key, mask))) { + LLViewerEventRecorder::instance().logKeyEvent(key,mask); return TRUE; } } @@ -2548,6 +2563,7 @@ BOOL LLViewerWindow::handleKey(KEY key, MASK mask) // if nothing has focus, go to first or last UI element as appropriate if (key == KEY_TAB && (mask & MASK_CONTROL || gFocusMgr.getKeyboardFocus() == NULL)) { + llwarns << "LLviewerWindow::handleKey give floaters first chance at tab key " << llendl; if (gMenuHolder) gMenuHolder->hideMenus(); // if CTRL-tabbing (and not just TAB with no focus), go into window cycle mode @@ -2562,11 +2578,13 @@ BOOL LLViewerWindow::handleKey(KEY key, MASK mask) { mRootView->focusNextRoot(); } + LLViewerEventRecorder::instance().logKeyEvent(key,mask); return TRUE; } // hidden edit menu for cut/copy/paste if (gEditMenu && gEditMenu->handleAcceleratorKey(key, mask)) { + LLViewerEventRecorder::instance().logKeyEvent(key,mask); return TRUE; } @@ -2606,18 +2624,27 @@ BOOL LLViewerWindow::handleKey(KEY key, MASK mask) if (keyboard_focus->handleKey(key, mask, FALSE)) { + + lldebugs << "LLviewerWindow::handleKey - in 'traverse up' - no loops seen... just called keyboard_focus->handleKey an it returned true" << llendl; + LLViewerEventRecorder::instance().logKeyEvent(key,mask); return TRUE; + } else { + lldebugs << "LLviewerWindow::handleKey - in 'traverse up' - no loops seen... just called keyboard_focus->handleKey an it returned FALSE" << llendl; } } if( LLToolMgr::getInstance()->getCurrentTool()->handleKey(key, mask) ) { + lldebugs << "LLviewerWindow::handleKey toolbar handling?" << llendl; + LLViewerEventRecorder::instance().logKeyEvent(key,mask); return TRUE; } // Try for a new-format gesture if (LLGestureMgr::instance().triggerGesture(key, mask)) { + lldebugs << "LLviewerWindow::handleKey new gesture feature" << llendl; + LLViewerEventRecorder::instance().logKeyEvent(key,mask); return TRUE; } @@ -2625,6 +2652,8 @@ BOOL LLViewerWindow::handleKey(KEY key, MASK mask) // don't pass it down to the menus. if (gGestureList.trigger(key, mask)) { + lldebugs << "LLviewerWindow::handleKey check gesture trigger" << llendl; + LLViewerEventRecorder::instance().logKeyEvent(key,mask); return TRUE; } @@ -2673,7 +2702,7 @@ BOOL LLViewerWindow::handleUnicodeChar(llwchar uni_char, MASK mask) // HACK: Numeric keypad <enter> on Mac is Unicode 3 // HACK: Control-M on Windows is Unicode 13 if ((uni_char == 13 && mask != MASK_CONTROL) - || (uni_char == 3 && mask == MASK_NONE)) + || (uni_char == 3 && mask == MASK_NONE) ) { if (mask != MASK_ALT) { @@ -2696,14 +2725,7 @@ BOOL LLViewerWindow::handleUnicodeChar(llwchar uni_char, MASK mask) return TRUE; } - //// Topmost view gets a chance before the hierarchy - //LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl(); - //if (top_ctrl && top_ctrl->handleUnicodeChar( uni_char, FALSE ) ) - //{ - // return TRUE; - //} - - return TRUE; + return TRUE; } return FALSE; @@ -2712,8 +2734,6 @@ BOOL LLViewerWindow::handleUnicodeChar(llwchar uni_char, MASK mask) void LLViewerWindow::handleScrollWheel(S32 clicks) { - LLView::sMouseHandlerMessage.clear(); - LLUI::resetMouseIdleTimer(); LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture(); diff --git a/indra/newview/llwebprofile.cpp b/indra/newview/llwebprofile.cpp index 567138e160..3bdbb05beb 100755 --- a/indra/newview/llwebprofile.cpp +++ b/indra/newview/llwebprofile.cpp @@ -145,9 +145,6 @@ public: LL_DEBUGS("Snapshots") << "Uploading image succeeded. Response: [" << body << "]" << llendl; LLWebProfile::reportImageUploadStatus(true); } - -private: - LLPointer<LLImageFormatted> mImagep; }; diff --git a/indra/newview/llwindowlistener.cpp b/indra/newview/llwindowlistener.cpp index 28f959eb71..a8e06511d7 100755 --- a/indra/newview/llwindowlistener.cpp +++ b/indra/newview/llwindowlistener.cpp @@ -265,7 +265,9 @@ void LLWindowListener::getPaths(LLSD const & request) 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"]); @@ -280,8 +282,6 @@ void LLWindowListener::keyDown(LLSD const & evt) 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); } @@ -294,7 +294,8 @@ void LLWindowListener::keyDown(LLSD const & evt) } else { - mKbGetter()->handleTranslatedKeyDown(getKEY(evt), getMask(evt)); + gViewerKeyboard.handleKey(key, mask, false); + if(key < 0x80) mWindow->handleUnicodeChar(key, mask); } } diff --git a/indra/newview/llworldmap.cpp b/indra/newview/llworldmap.cpp index 5fa380e0e3..bfae142812 100755 --- a/indra/newview/llworldmap.cpp +++ b/indra/newview/llworldmap.cpp @@ -522,6 +522,17 @@ bool LLWorldMap::insertItem(U32 x_world, U32 y_world, std::string& name, LLUUID& tooltip_fmt.setArg("[AREA]", llformat("%d", extra)); tooltip_fmt.setArg("[PRICE]", llformat("%d", extra2)); + + // Check for division by zero + if (extra != 0) + { + tooltip_fmt.setArg("[SQMPRICE]", llformat("%.1f", (F32)extra2 / (F32)extra)); + } + else + { + tooltip_fmt.setArg("[SQMPRICE]", LLTrans::getString("Unknown")); + } + new_item.setTooltip(tooltip_fmt.getString()); if (type == MAP_ITEM_LAND_FOR_SALE) diff --git a/indra/newview/skins/default/textures/icons/Facebook.png b/indra/newview/skins/default/textures/icons/Facebook.png Binary files differnew file mode 100644 index 0000000000..8287d56f88 --- /dev/null +++ b/indra/newview/skins/default/textures/icons/Facebook.png diff --git a/indra/newview/skins/default/textures/icons/map_placeholder.png b/indra/newview/skins/default/textures/icons/map_placeholder.png Binary files differnew file mode 100644 index 0000000000..31e457aa75 --- /dev/null +++ b/indra/newview/skins/default/textures/icons/map_placeholder.png diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index 54f60f4441..94c187e21a 100755 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -148,6 +148,7 @@ with the same filename but different name <texture name="Command_Preferences_Icon" file_name="toolbar_icons/preferences.png" preload="true" /> <texture name="Command_Profile_Icon" file_name="toolbar_icons/profile.png" preload="true" /> <texture name="Command_Search_Icon" file_name="toolbar_icons/search.png" preload="true" /> + <texture name="Command_Social_Icon" file_name="toolbar_icons/facebook.png" preload="true" /> <texture name="Command_Snapshot_Icon" file_name="toolbar_icons/snapshot.png" preload="true" /> <texture name="Command_Speak_Icon" file_name="toolbar_icons/speak.png" preload="true" /> <texture name="Command_View_Icon" file_name="toolbar_icons/view.png" preload="true" /> @@ -199,6 +200,8 @@ with the same filename but different name <texture name="ExternalBrowser_Off" file_name="icons/ExternalBrowser_Off.png" preload="false" /> <texture name="Edit_Wrench" file_name="icons/Edit_Wrench.png" preload="false" /> + <texture name="Facebook_Icon" file_name="icons/Facebook.png" preload="false" /> + <texture name="Favorite_Star_Active" file_name="navbar/Favorite_Star_Active.png" preload="false" /> <texture name="Favorite_Star_Off" file_name="navbar/Favorite_Star_Off.png" preload="false" /> <texture name="Favorite_Star_Press" file_name="navbar/Favorite_Star_Press.png" preload="false" /> @@ -323,6 +326,8 @@ with the same filename but different name <texture name="Locked_Icon" file_name="icons/Locked_Icon.png" preload="false" /> + <texture name="Map_Placeholder_Icon" file_name="icons/map_placeholder.png" preload="true" /> + <texture name="MarketplaceBtn_Off" file_name="widgets/MarketplaceBtn_Off.png" preload="true" scale.left="30" scale.top="19" scale.right="35" scale.bottom="4" /> <texture name="MarketplaceBtn_Selected" file_name="widgets/MarketplaceBtn_Selected.png" preload="true" scale.left="30" scale.top="19" scale.right="35" scale.bottom="4" /> @@ -565,6 +570,7 @@ with the same filename but different name <texture name="Snapshot_Email" file_name="snapshot_email.png" preload="false" /> <texture name="Snapshot_Inventory" file_name="toolbar_icons/inventory.png" preload="false" /> <texture name="Snapshot_Profile" file_name="toolbar_icons/profile.png" preload="false" /> + <texture name="Snapshot_Facebook" file_name="toolbar_icons/facebook.png" preload="false" /> <texture name="startup_logo" file_name="windows/startup_logo.png" preload="true" /> diff --git a/indra/newview/skins/default/textures/toolbar_icons/facebook.png b/indra/newview/skins/default/textures/toolbar_icons/facebook.png Binary files differnew file mode 100644 index 0000000000..b960b834dc --- /dev/null +++ b/indra/newview/skins/default/textures/toolbar_icons/facebook.png diff --git a/indra/newview/skins/default/xui/de/strings.xml b/indra/newview/skins/default/xui/de/strings.xml index c8c20424e1..4268b95370 100755 --- a/indra/newview/skins/default/xui/de/strings.xml +++ b/indra/newview/skins/default/xui/de/strings.xml @@ -1274,7 +1274,7 @@ Warten Sie kurz und versuchen Sie dann noch einmal, sich anzumelden. https://marketplace.[MARKETPLACE_DOMAIN_NAME]/ </string> <string name="MarketplaceURL_CreateStore"> - http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.4 + http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.3 </string> <string name="MarketplaceURL_Dashboard"> https://marketplace.[MARKETPLACE_DOMAIN_NAME]/merchants/store/dashboard diff --git a/indra/newview/skins/default/xui/en/floater_about_land.xml b/indra/newview/skins/default/xui/en/floater_about_land.xml index 793a6e6fa1..a660e812cc 100755 --- a/indra/newview/skins/default/xui/en/floater_about_land.xml +++ b/indra/newview/skins/default/xui/en/floater_about_land.xml @@ -1365,7 +1365,7 @@ Only large parcels can be listed in search. <combo_box.item label="Any Category" name="item0" - value="any" /> + value="none" /> <combo_box.item label="Linden Location" name="item1" diff --git a/indra/newview/skins/default/xui/en/floater_fbc_web.xml b/indra/newview/skins/default/xui/en/floater_fbc_web.xml new file mode 100644 index 0000000000..0d35e22a19 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_fbc_web.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater name="floater_fbc_web" + help_topic="fbc_web" + width="780" + height="775" + save_rect="true" + single_instance="true" + reuse_instance="false" + filename="floater_web_content.xml"/> diff --git a/indra/newview/skins/default/xui/en/floater_snapshot.xml b/indra/newview/skins/default/xui/en/floater_snapshot.xml index 49d64767cc..853c209bca 100755 --- a/indra/newview/skins/default/xui/en/floater_snapshot.xml +++ b/indra/newview/skins/default/xui/en/floater_snapshot.xml @@ -21,7 +21,11 @@ Sending Email </string> <string - name="profile_progress_str"> + name="facebook_progress_str"> + Posting to Facebook + </string> + <string + name="profile_progress_str"> Posting </string> <string @@ -33,7 +37,11 @@ Saving to Computer </string> <string - name="profile_succeeded_str"> + name="facebook_succeeded_str"> + Image uploaded + </string> + <string + name="profile_succeeded_str"> Image uploaded </string> <string @@ -49,7 +57,11 @@ Saved to Computer! </string> <string - name="profile_failed_str"> + name="facebook_failed_str"> + Failed to upload image to your Facebook timeline. + </string> + <string + name="profile_failed_str"> Failed to upload image to your Profile Feed. </string> <string diff --git a/indra/newview/skins/default/xui/en/floater_social.xml b/indra/newview/skins/default/xui/en/floater_social.xml new file mode 100644 index 0000000000..b7ff374d5f --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_social.xml @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<floater + positioning="cascading" + can_close="true" + can_resize="false" + help_topic="floater_social" + layout="topleft" + name="floater_social" + save_rect="true" + single_instance="true" + reuse_instance="true" + title="POST TO FACEBOOK" + height="482" + width="304"> + <panel + height="482" + width="304" + visible="true" + name="background" + follows="all" + top="0" + left="0"> + <tab_container + name="tabs" + tab_group="1" + tab_min_width="70" + tab_height="30" + tab_position="top" + top="7" + height="437" + halign="center"> + <panel + filename="panel_social_status.xml" + class="llsocialstatuspanel" + follows="all" + label="STATUS" + name="panel_social_status"/> + <panel + filename="panel_social_photo.xml" + class="llsocialphotopanel" + follows="all" + label="PHOTO" + name="panel_social_photo"/> + <panel + filename="panel_social_place.xml" + class="llsocialcheckinpanel" + follows="all" + label="CHECK IN" + name="panel_social_place"/> + <panel + filename="panel_social_account.xml" + class="llsocialaccountpanel" + follows="all" + label="ACCOUNT" + name="panel_social_account"/> + </tab_container> + <panel + name="connection_status_panel" + follows="left|bottom|right" + height="24"> + <text + name="connection_error_text" + type="string" + follows="left|bottom|right" + top="5" + left="9" + width="250" + height="20" + wrap="true" + halign="left" + valign="center" + text_color="DrYellow" + font="SansSerif"> + Error + </text> + <loading_indicator + follows="left|bottom|right" + height="24" + width="24" + name="connection_loading_indicator" + top="2" + left="9" + visible="true"/> + <text + name="connection_loading_text" + type="string" + follows="left|bottom|right" + top="5" + left_pad="5" + width="250" + height="20" + wrap="true" + halign="left" + valign="center" + text_color="EmphasisColor" + font="SansSerif"> + Loading... + </text> + </panel> + </panel> +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_web_content.xml b/indra/newview/skins/default/xui/en/floater_web_content.xml index cea10adca8..a80440e844 100755 --- a/indra/newview/skins/default/xui/en/floater_web_content.xml +++ b/indra/newview/skins/default/xui/en/floater_web_content.xml @@ -125,10 +125,10 @@ <icon name="media_secure_lock_flag" height="16" - follows="top|right" + follows="top|left" image_name="Lock2" layout="topleft" - left_delta="620" + left_delta="2" top_delta="2" visible="false" tool_tip="Secured Browsing" diff --git a/indra/newview/skins/default/xui/en/menu_conversation.xml b/indra/newview/skins/default/xui/en/menu_conversation.xml index b3d28788da..31b1d091ee 100755 --- a/indra/newview/skins/default/xui/en/menu_conversation.xml +++ b/indra/newview/skins/default/xui/en/menu_conversation.xml @@ -53,6 +53,13 @@ <on_enable function="Avatar.EnableItem" parameter="can_offer_teleport"/> </menu_item_call> <menu_item_call + label="Request teleport" + layout="topleft" + name="request_teleport"> + <on_click function="Avatar.DoToSelected" parameter="request_teleport"/> + <on_enable function="Avatar.EnableItem" parameter="can_offer_teleport"/> + </menu_item_call> + <menu_item_call label="Voice call" layout="topleft" name="voice_call"> diff --git a/indra/newview/skins/default/xui/en/menu_conversation_log_gear.xml b/indra/newview/skins/default/xui/en/menu_conversation_log_gear.xml index 8796b87955..a1a3afbf68 100755 --- a/indra/newview/skins/default/xui/en/menu_conversation_log_gear.xml +++ b/indra/newview/skins/default/xui/en/menu_conversation_log_gear.xml @@ -56,6 +56,16 @@ function="Calllog.Enable" parameter="can_offer_teleport"/> </menu_item_call> + <menu_item_call + label="Request Teleport" + name="request_teleport"> + <on_click + function="Calllog.Action" + parameter="request_teleport"/> + <on_enable + function="Calllog.Enable" + parameter="can_offer_teleport"/> + </menu_item_call> <menu_item_separator /> <menu_item_call label="Add Friend" diff --git a/indra/newview/skins/default/xui/en/menu_inventory.xml b/indra/newview/skins/default/xui/en/menu_inventory.xml index 13dc0b941a..512205ba43 100755 --- a/indra/newview/skins/default/xui/en/menu_inventory.xml +++ b/indra/newview/skins/default/xui/en/menu_inventory.xml @@ -591,6 +591,14 @@ parameter="lure" /> </menu_item_call> <menu_item_call + label="Request Teleport..." + layout="topleft" + name="Request Teleport..."> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="request_lure" /> + </menu_item_call> + <menu_item_call label="Start Conference Chat" layout="topleft" name="Conference Chat"> diff --git a/indra/newview/skins/default/xui/en/menu_people_nearby.xml b/indra/newview/skins/default/xui/en/menu_people_nearby.xml index 3abb5f7bc8..f12226ebeb 100755 --- a/indra/newview/skins/default/xui/en/menu_people_nearby.xml +++ b/indra/newview/skins/default/xui/en/menu_people_nearby.xml @@ -29,6 +29,15 @@ parameter="can_offer_teleport"/> </menu_item_call> <menu_item_call + label="Request Teleport" + name="request_teleport"> + <menu_item_call.on_click + function="Avatar.TeleportRequest"/> + <menu_item_call.on_enable + function="Avatar.EnableItem" + parameter="can_offer_teleport"/> + </menu_item_call> + <menu_item_call label="Voice call" layout="topleft" name="voice_call"> @@ -134,5 +143,4 @@ function="Avatar.EnableItem" parameter="can_block" /> </menu_item_check> - <menu_item_separator /> </context_menu> diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 2d65052def..d635b8ee93 100755 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -16,6 +16,14 @@ parameter="agent" /> </menu_item_call> <menu_item_call + label="Post to Facebook..." + name="PostToFacebook"> + <menu_item_call.on_click + function="Floater.Toggle" + parameter="social"/> + </menu_item_call> + <menu_item_separator/> + <menu_item_call label="Appearance..." name="ChangeOutfit"> <menu_item_call.on_click @@ -189,7 +197,7 @@ name="Preferences" shortcut="control|P"> <menu_item_call.on_click - function="Floater.Show" + function="Floater.Toggle" parameter="preferences" /> </menu_item_call> <menu_item_call @@ -2947,6 +2955,34 @@ label="Recorder" name="Recorder" tear_off="true"> + <menu_item_call visible="false" + label="Start event recording" + name="Start event recording"> + <menu_item_call.on_visible + function="displayViewerEventRecorderMenuItems" /> + <menu_item_call.on_click + function="Advanced.EventRecorder" + parameter="start recording" /> + </menu_item_call> + <menu_item_call visible="false" + label="Stop event recording" + name="Stop event recording"> + <menu_item_call.on_visible + function="displayViewerEventRecorderMenuItems" /> + <menu_item_call.on_click + function="Advanced.EventRecorder" + parameter="stop recording" /> + </menu_item_call> + <menu_item_call visible="false" + label="Playback event recording" + name="Playback event recording"> + <menu_item_call.on_visible + function="displayViewerEventRecorderMenuItems" /> + <menu_item_call.on_click + function="Advanced.EventRecorder" + parameter="start playback" /> + </menu_item_call> + <menu_item_call label="Start Playback" name="Start Playback"> @@ -3049,6 +3085,13 @@ parameter="http://google.com"/> </menu_item_call> <menu_item_call + label="FB Connect Test" + name="FB Connect Test"> + <menu_item_call.on_click + function="Advanced.WebContentTest" + parameter="https://cryptic-ridge-1632.herokuapp.com/"/> + </menu_item_call> + <menu_item_call label="Dump SelectMgr" name="Dump SelectMgr"> <menu_item_call.on_click diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index ed7c862433..d4dcb96519 100755 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -4071,6 +4071,27 @@ Join me in [REGION] <notification icon="alertmodal.tga" + name="TeleportRequestPrompt" + type="alertmodal"> +Request a teleport to [NAME] with the following message + <tag>confirm</tag> + <form name="form"> + <input name="message" type="text"> + + </input> + <button + default="true" + index="0" + name="OK" + text="OK"/> + <button + index="1" + name="Cancel" + text="Cancel"/> + </form> + </notification> + <notification + icon="alertmodal.tga" name="TooManyTeleportOffers" type="alertmodal"> You attempted to make [OFFERS] teleport offers @@ -6017,6 +6038,13 @@ Please select at least one type of content to search (General, Moderate, or Adul [MESSAGE] </notification> + <notification + icon="notify.tga" + name="FacebookConnect" + type="notifytip"> +[MESSAGE] + </notification> + <notification icon="notify.tga" name="PaymentReceived" @@ -6637,7 +6665,7 @@ Your object named <nolink>[OBJECTFROMNAME]</nolink> has given you th sound="UISndNewIncomingIMSession"> [NAME_SLURL] has offered to teleport you to their location: -“[MESSAGE]” +"[MESSAGE]” <icon>[MATURITY_ICON]</icon> - [MATURITY_STR] <tag>confirm</tag> <form name="form"> @@ -6702,6 +6730,27 @@ However, this region contains content accessible to adults only. Teleport offer sent to [TO_NAME] </notification> + <notification + icon="notify.tga" + name="TeleportRequest" + log_to_im="true" + type="offer"> +[NAME_SLURL] is requesting to be teleported to your location. +[MESSAGE] + +Offer a teleport? + <tag>confirm</tag> + <form name="form"> + <button + index="0" + name="Yes" + text="Yes"/> + <button + index="1" + name="No" + text="No"/> + </form> + </notification> <notification icon="notify.tga" @@ -6750,7 +6799,6 @@ However, this region contains content accessible to adults only. icon="notify.tga" name="FriendshipOffered" log_to_im="true" - show_toast="false" type="notify"> <tag>friendship</tag> You have offered friendship to [TO_NAME] @@ -6800,7 +6848,6 @@ However, this region contains content accessible to adults only. icon="notify.tga" name="FriendshipAcceptedByMe" log_to_im="true" - show_toast="false" type="notify"> <tag>friendship</tag> Friendship offer accepted. @@ -6810,7 +6857,6 @@ Friendship offer accepted. icon="notify.tga" name="FriendshipDeclinedByMe" log_to_im="true" - show_toast="false" type="notify"> <tag>friendship</tag> Friendship offer declined. diff --git a/indra/newview/skins/default/xui/en/panel_people.xml b/indra/newview/skins/default/xui/en/panel_people.xml index ed274d0233..dc0e4a5947 100755 --- a/indra/newview/skins/default/xui/en/panel_people.xml +++ b/indra/newview/skins/default/xui/en/panel_people.xml @@ -365,6 +365,23 @@ Looking for people to hang out with? Try the [secondlife:///app/worldmap World M top="0" width="307" /> </accordion_tab> + <accordion_tab + layout="topleft" + height="173" + name="tab_suggested_friends" + title="People you may want to friend"> + <avatar_list + ignore_online_status="true" + allow_select="true" + follows="all" + height="173" + layout="topleft" + left="0" + name="suggested_friends" + show_permissions_granted="true" + top="0" + width="307" /> + </accordion_tab> </accordion> <text follows="all" diff --git a/indra/newview/skins/default/xui/en/panel_preferences_move.xml b/indra/newview/skins/default/xui/en/panel_preferences_move.xml index d9067b41c7..8794e3bf95 100755 --- a/indra/newview/skins/default/xui/en/panel_preferences_move.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_move.xml @@ -129,6 +129,16 @@ name="tap_tap_hold_to_run" width="237" top_pad="0"/> + <check_box + control_name="AutomaticFly" + follows="left|top" + height="20" + label="Hold jump or crouch key to start or stop flying" + layout="topleft" + left_delta="0" + name="automatic_fly" + width="237" + top_pad="0"/> <text follows="left|top" type="string" @@ -260,4 +270,4 @@ function="Floater.Show" parameter="pref_joystick" /> </button> -</panel>
\ No newline at end of file +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_prim_media_controls.xml b/indra/newview/skins/default/xui/en/panel_prim_media_controls.xml index 198ccd6e2f..8f90521bb2 100755 --- a/indra/newview/skins/default/xui/en/panel_prim_media_controls.xml +++ b/indra/newview/skins/default/xui/en/panel_prim_media_controls.xml @@ -315,7 +315,18 @@ <line_editor.commit_callback function="MediaCtrl.CommitURL"/> </line_editor> - <layout_stack + <icon + name="media_secure_lock_flag" + height="16" + follows="top|left" + image_name="Lock2" + layout="topleft" + left_delta="2" + top_delta="2" + visible="false" + tool_tip="Secured Browsing" + width="16" /> + <layout_stack name="media_address_url_icons" animate="false" follows="top|right" @@ -340,19 +351,6 @@ tool_tip="White List enabled" width="16" /> </layout_panel> - <layout_panel - layout="topleft" - width="16" - mouse_opaque="false" - auto_resize="false"> - <icon - name="media_secure_lock_flag" - height="16" - image_name="Lock2" - layout="topleft" - tool_tip="Secured Browsing" - width="16" /> - </layout_panel> </layout_stack> </layout_panel> <layout_panel diff --git a/indra/newview/skins/default/xui/en/panel_snapshot_options.xml b/indra/newview/skins/default/xui/en/panel_snapshot_options.xml index d2f29ade44..61c8c971c2 100755 --- a/indra/newview/skins/default/xui/en/panel_snapshot_options.xml +++ b/indra/newview/skins/default/xui/en/panel_snapshot_options.xml @@ -16,11 +16,11 @@ imgoverlay_label_space="10" label="Post to My Profile Feed" layout="topleft" - left="10" + left_delta="0" name="save_to_profile_btn" pad_left="10" right="-10" - top="5"> + top_pad="10"> <button.commit_callback function="Snapshot.SaveToProfile" /> </button> diff --git a/indra/newview/skins/default/xui/en/panel_social_account.xml b/indra/newview/skins/default/xui/en/panel_social_account.xml new file mode 100644 index 0000000000..d7235396fe --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_social_account.xml @@ -0,0 +1,75 @@ +<panel + height="400" + width="304" + layout="topleft" + name="panel_social_account"> + <string + name="facebook_connected" + value="You are connected to Facebook as:" /> + <string + name="facebook_disconnected" + value="Not connected to Facebook" /> + <text + layout="topleft" + length="1" + follows="top|left" + font="SansSerif" + height="16" + left="9" + name="account_caption_label" + top="21" + type="string"> + Not connected to Facebook. + </text> + <text + layout="topleft" + top_pad="2" + length="1" + follows="top|left" + font="SansSerif" + height="16" + left="9" + name="account_name_label" + parse_urls="true" + type="string"/> + <panel + layout="topleft" + name="panel_buttons" + height="345" + left="9"> + <button + layout="topleft" + follows="left|top" + top_pad="9" + visible="true" + height="23" + label="Connect..." + name="connect_btn" + width="210"> + <commit_callback function="SocialSharing.Connect"/> + </button> + + <button + layout="topleft" + follows="left|top" + top_delta="0" + height="23" + label="Disconnect" + name="disconnect_btn" + width="210" + visible="false"> + <commit_callback function="SocialSharing.Disconnect"/> + </button> + <text + layout="topleft" + length="1" + follows="top|left" + height="16" + left="0" + name="account_learn_more_label" + top_pad="20" + type="string"> + [http://community.secondlife.com/t5/English-Knowledge-Base/Second-Life-Share/ta-p/2149711 Learn about posting to Facebook] + </text> + </panel> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_social_photo.xml b/indra/newview/skins/default/xui/en/panel_social_photo.xml new file mode 100644 index 0000000000..a55613b52a --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_social_photo.xml @@ -0,0 +1,152 @@ + <panel + height="400" + width="304" + layout="topleft" + name="panel_social_photo"> + <layout_stack + layout="topleft" + border_size="0" + height="392" + follows="all" + orientation="vertical" + name="stack_photo" + top="8"> + <layout_panel + name="snapshot_panel" + height="367"> + <combo_box + control_name="SocialPhotoResolution" + follows="left|top" + top="6" + left="9" + name="resolution_combobox" + tool_tip="Image resolution" + height="21" + width="135"> + <combo_box.item + label="Current Window" + name="CurrentWindow" + value="[i0,i0]" /> + <combo_box.item + label="640x480" + name="640x480" + value="[i640,i480]" /> + <combo_box.item + label="800x600" + name="800x600" + value="[i800,i600]" /> + <combo_box.item + label="1024x768" + name="1024x768" + value="[i1024,i768]" /> + </combo_box> + <text + follows="left|top" + font="SansSerifSmall" + height="14" + left="208" + length="1" + halign="right" + name="file_size_label" + top="9" + type="string" + width="50"> + [SIZE] KB + </text> + <panel + height="150" + width="250" + visible="true" + name="thumbnail_placeholder" + top="33" + follows="left|top" + left="9"> + </panel> + <button + follows="left|top" + height="23" + label="Refresh" + left="9" + top_pad="5" + name="new_snapshot_btn" + tool_tip="Click to refresh" + visible="true" + width="100" > + <button.commit_callback + function="SocialSharing.RefreshPhoto" /> + </button> + <text + follows="left|top" + font="SansSerif" + text_color="EmphasisColor" + height="14" + top_pad="-19" + left_pad="-20" + length="1" + halign="center" + name="working_lbl" + translate="false" + type="string" + visible="true" + width="150"> + Refreshing... + </text> + <text + length="1" + follows="top|left|right" + font="SansSerif" + height="16" + left="9" + name="caption_label" + top_pad="20" + type="string"> + Comment (optional): + </text> + <text_editor + follows="left|top" + height="87" + width="250" + left="9" + length="1" + max_length="700" + name="photo_caption" + type="string" + word_wrap="true"> + </text_editor> + <check_box + follows="left|top" + initial_value="true" + label="Include location in posting" + name="add_location_cb" + left="9" + height="16" + top_pad="8"/> + </layout_panel> + <layout_panel + name="photo_button_panel" + height="25"> + <button + follows="left|top" + top="0" + left="9" + height="23" + label="Post" + name="post_photo_btn" + width="100"> + <button.commit_callback + function="SocialSharing.SendPhoto" /> + </button> + <button + follows="left|top" + height="23" + label="Cancel" + name="cancel_photo_btn" + left_pad="15" + top_delta="0" + width="100"> + <button.commit_callback + function="SocialSharing.Cancel" /> + </button> + </layout_panel> + </layout_stack> + </panel> diff --git a/indra/newview/skins/default/xui/en/panel_social_place.xml b/indra/newview/skins/default/xui/en/panel_social_place.xml new file mode 100644 index 0000000000..13e94f6998 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_social_place.xml @@ -0,0 +1,132 @@ + <panel + height="400" + width="304" + layout="topleft" + name="panel_social_place"> + <layout_stack + layout="topleft" + border_size="0" + height="392" + follows="all" + orientation="vertical" + name="stack_place" + top="8"> + <layout_panel + name="place_detail_panel" + height="181"> + <text + length="1" + follows="top|left|right" + font="SansSerif" + height="16" + left="9" + name="place_caption_label" + top="13" + type="string"> + Say something about where you are: + </text> + <text_editor + follows="top|left" + height="150" + width="250" + left="9" + length="1" + max_length="700" + name="place_caption" + type="string" + word_wrap="true"> + </text_editor> + </layout_panel> + <layout_panel + name="place_map_panel" + height="186"> + <panel + follows="left|top" + height="128" + width="128" + background_visible="true" + bg_opaque_color="Black" + bg_alpha_color="Black" + top="20" + left="9" + visible="true" + name="map_border"> + </panel> + <loading_indicator + follows="left|top" + height="24" + width="24" + name="map_loading_indicator" + top="77" + left="61" + visible="true"/> + <icon + follows="left|top" + height="128" + width="128" + image_name="Map_Placeholder_Icon" + layout="topleft" + top="20" + left="9" + visible="true" + name="map_placeholder"> + </icon> + <icon + follows="left|top" + height="128" + width="128" + image_name="Map_Placeholder_Icon" + layout="topleft" + top="20" + left="9" + visible="true" + name="map_default"> + </icon> + <check_box + follows="left|top" + initial_value="false" + top_delta="8" + width="8" + label="" + name="add_place_view_cb" + left_pad="5"/> + <text + follows="left|top" + font="SansSerif" + height="32" + width="130" + word_wrap="true" + left_pad="12" + top_delta="-8" + type="string"> + Include overhead view of location + </text> + </layout_panel> + <layout_panel + name="place_button_panel" + height="25"> + <button + follows="left|top" + top="0" + left="9" + height="23" + label="Post" + name="post_place_btn" + width="100"> + <button.commit_callback + function="SocialSharing.SendCheckin" /> + </button> + <button + follows="left|top" + height="23" + label="Cancel" + name="cancel_place_btn" + left_pad="15" + top_delta="0" + width="100"> + <button.commit_callback + function="SocialSharing.Cancel" /> + </button> + </layout_panel> + </layout_stack> + </panel> diff --git a/indra/newview/skins/default/xui/en/panel_social_status.xml b/indra/newview/skins/default/xui/en/panel_social_status.xml new file mode 100644 index 0000000000..54cfa3f524 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_social_status.xml @@ -0,0 +1,67 @@ + <panel + height="400" + width="304" + layout="topleft" + name="panel_social_status"> + <layout_stack + layout="topleft" + border_size="0" + height="392" + follows="all" + orientation="vertical" + name="stack_status" + top="8"> + <layout_panel + name="status_detail_panel" + height="367"> + <text + length="1" + follows="top|left|right" + font="SansSerif" + height="16" + left="9" + name="status_caption_label" + top="13" + type="string"> + What's on your mind? + </text> + <text_editor + follows="left|top" + height="150" + width="250" + left="9" + length="1" + max_length="700" + name="status_message" + type="string" + word_wrap="true"> + </text_editor> + </layout_panel> + <layout_panel + name="status_button_panel" + height="25"> + <button + follows="left|top" + top="0" + left="9" + height="23" + label="Post" + name="post_status_btn" + width="100"> + <button.commit_callback + function="SocialSharing.SendStatus" /> + </button> + <button + follows="left|top" + height="23" + label="Cancel" + name="cancel_status_btn" + left_pad="15" + top_delta="0" + width="100"> + <button.commit_callback + function="SocialSharing.Cancel" /> + </button> + </layout_panel> + </layout_stack> + </panel> diff --git a/indra/newview/skins/default/xui/en/panel_status_bar.xml b/indra/newview/skins/default/xui/en/panel_status_bar.xml index dd2a0c6627..064ece6e4b 100755 --- a/indra/newview/skins/default/xui/en/panel_status_bar.xml +++ b/indra/newview/skins/default/xui/en/panel_status_bar.xml @@ -113,7 +113,7 @@ image_pressed="Pause_Press" image_pressed_selected="Play_Press" is_toggle="true" - left_pad="15" + left_pad="5" top="1" name="media_toggle_btn" tool_tip="Start/Stop All Media (Music, Video, Web pages)" diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 1c46cec479..8ac95beddb 100755 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -148,6 +148,14 @@ Please try logging in again in a minute.</string> <string name="SentToInvalidRegion">You were sent to an invalid region.</string> <string name="TestingDisconnect">Testing viewer disconnect</string> + <!-- Facebook Connect and, eventually, other Social Network --> + <string name="SocialFacebookConnecting">Connecting to Facebook...</string> + <string name="SocialFacebookPosting">Posting...</string> + <string name="SocialFacebookDisconnecting">Disconnecting from Facebook...</string> + <string name="SocialFacebookErrorConnecting">Problem connecting to Facebook</string> + <string name="SocialFacebookErrorPosting">Problem posting to Facebook</string> + <string name="SocialFacebookErrorDisconnecting">Problem disconnecting from Facebook</string> + <!-- Tooltip --> <string name="TooltipPerson">Person</string><!-- Object under mouse pointer is an avatar --> <string name="TooltipNoName">(no name)</string> <!-- No name on an object --> @@ -379,7 +387,7 @@ Please try logging in again in a minute.</string> <!-- world map --> <string name="texture_loading">Loading...</string> <string name="worldmap_offline">Offline</string> - <string name="worldmap_item_tooltip_format">[AREA] m² L$[PRICE]</string> + <string name="worldmap_item_tooltip_format">[AREA] m² L$[PRICE] ([SQMPRICE] L$/m²)</string> <string name="worldmap_results_none_found">None found.</string> <!-- animations uploading status codes --> @@ -2185,7 +2193,7 @@ For AI Character: Get the closest navigable point to the point provided. <string name="InventoryNoTexture">You do not have a copy of this texture in your inventory</string> <string name="InventoryInboxNoItems">Your Marketplace purchases will appear here. You may then drag them into your inventory to use them.</string> <string name="MarketplaceURL">https://marketplace.[MARKETPLACE_DOMAIN_NAME]/</string> - <string name="MarketplaceURL_CreateStore">http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.4</string> + <string name="MarketplaceURL_CreateStore">http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.3</string> <string name="MarketplaceURL_Dashboard">https://marketplace.[MARKETPLACE_DOMAIN_NAME]/merchants/store/dashboard</string> <string name="MarketplaceURL_Imports">https://marketplace.[MARKETPLACE_DOMAIN_NAME]/merchants/store/imports</string> <string name="MarketplaceURL_LearnMore">https://marketplace.[MARKETPLACE_DOMAIN_NAME]/learn_more</string> @@ -2464,7 +2472,11 @@ Drag folders to this area and click "Send to Marketplace" to list them for sale all estates that you manage for [OWNER] </string> <string name="RegionInfoAllowedResidents">Allowed Residents: ([ALLOWEDAGENTS], max [MAXACCESS])</string> - <string name="RegionInfoAllowedGroups">Allowed groups: ([ALLOWEDGROUPS], max [MAXACCESS])</string> + <string name="RegionInfoAllowedGroups">Allowed Groups: ([ALLOWEDGROUPS], max [MAXACCESS])</string> + <string name="RegionInfoEstateManagers">Estate Managers: ([ESTATEMANAGERS], max [MAXMANAGERS])</string> + <string name="RegionInfoBannedResidents">Banned Residents: ([BANNEDAGENTS], max [MAXBANNED])</string> + <string name="RegionInfoListTypeAllowedAgents">Allowed Residents</string> + <string name="RegionInfoListTypeBannedAgents">Banned Residents</string> <!-- script limits floater --> <string name="ScriptLimitsParcelScriptMemory">Parcel Script Memory</string> @@ -3427,6 +3439,9 @@ If you continue to receive this message, contact the [SUPPORT_SITE]. Drag items from inventory here </string> + <string name="facebook_post_success"> + You posted to Facebook. + </string> <string name="no_session_message"> (IM Session Doesn't Exist) @@ -3859,6 +3874,7 @@ Try enclosing path to the editor with double quotes. <string name="Command_Profile_Label">Profile</string> <string name="Command_Search_Label">Search</string> <string name="Command_Snapshot_Label">Snapshot</string> + <string name="Command_Social_Label">Facebook</string> <string name="Command_Speak_Label">Speak</string> <string name="Command_View_Label">Camera controls</string> <string name="Command_Voice_Label">Voice settings</string> @@ -3886,6 +3902,7 @@ Try enclosing path to the editor with double quotes. <string name="Command_Profile_Tooltip">Edit or view your profile</string> <string name="Command_Search_Tooltip">Find places, events, people</string> <string name="Command_Snapshot_Tooltip">Take a picture</string> + <string name="Command_Social_Tooltip">Post to Facebook</string> <string name="Command_Speak_Tooltip">Speak with people nearby using your microphone</string> <string name="Command_View_Tooltip">Changing camera angle</string> <string name="Command_Voice_Tooltip">Volume controls for calls and people near you in world</string> diff --git a/indra/newview/skins/default/xui/en/widgets/person_tab_view.xml b/indra/newview/skins/default/xui/en/widgets/person_tab_view.xml new file mode 100644 index 0000000000..af5aec2c34 --- /dev/null +++ b/indra/newview/skins/default/xui/en/widgets/person_tab_view.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<person_tab_view + folder_arrow_image="Folder_Arrow" + folder_indentation="5" + item_height="24" + item_top_pad="3" + mouse_opaque="true" + follows="left|top|right" + text_pad="6" + text_pad_left="4" + text_pad_right="4" + arrow_size="10" + max_folder_item_overlap="2"/> diff --git a/indra/newview/skins/default/xui/en/widgets/person_view.xml b/indra/newview/skins/default/xui/en/widgets/person_view.xml new file mode 100644 index 0000000000..46c1b7ff75 --- /dev/null +++ b/indra/newview/skins/default/xui/en/widgets/person_view.xml @@ -0,0 +1,127 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<person_view + folder_arrow_image="Folder_Arrow" + folder_indentation="5" + item_height="24" + item_top_pad="3" + mouse_opaque="true" + follows="left|top|right" + icon_pad="4" + icon_width="20" + text_pad="6" + text_pad_left="4" + text_pad_right="4" + arrow_size="10" + max_folder_item_overlap="2"> + <facebook_icon + follows="left" + height="14" + image_name="Facebook_Icon" + left="5" + bottom="6" + name="facebook_icon" + tool_tip="Facebook User" + visible="false" + width="14" /> + <avatar_icon + follows="left" + layout="topleft" + height="20" + default_icon_name="Generic_Person" + left="5" + top="2" + visible="false" + width="20" /> + <last_interaction_time_textbox + layout="topleft" + follows="right" + font="SansSerifSmall" + height="15" + left_pad="5" + right="-164" + name="last_interaction_time_textbox" + text_color="LtGray_50" + value="0s" + visible="false" + width="35" /> + <permission_edit_theirs_icon + layout="topleft" + height="16" + follows="right" + image_name="Permission_Edit_Objects_Theirs" + left_pad="3" + right="-129" + name="permission_edit_theirs_icon" + tool_tip="You can edit this friend's objects" + top="4" + visible="false" + width="16" /> + <permission_edit_mine_icon + layout="topleft" + height="16" + follows="right" + image_name="Permission_Edit_Objects_Mine" + left_pad="3" + right="-110" + name="permission_edit_mine_icon" + tool_tip="This friend can edit, delete or take your objects" + top="4" + visible="false" + width="16" /> + <permission_map_icon + height="16" + follows="right" + image_name="Permission_Visible_Map" + left_pad="3" + tool_tip="This friend can locate you on the map" + right="-91" + name="permission_map_icon" + visible="false" + width="16" /> + <permission_online_icon + height="16" + follows="right" + image_name="Permission_Visible_Online" + left_pad="3" + right="-72" + name="permission_online_icon" + tool_tip="This friend can see when you're online" + visible="false" + width="16" /> + <info_btn + follows="right" + height="16" + image_pressed="Info_Press" + image_unselected="Info_Over" + left_pad="3" + right="-53" + name="info_btn" + tool_tip="More info" + tab_stop="false" + visible="false" + width="16" /> + <profile_btn + layout="topleft" + follows="right" + height="20" + image_overlay="Web_Profile_Off" + left_pad="5" + right="-28" + name="profile_btn" + tab_stop="false" + tool_tip="View profile" + top="2" + visible="false" + width="20" /> + <output_monitor + auto_update="true" + follows="right" + draw_border="false" + height="16" + right="-3" + mouse_opaque="true" + name="speaking_indicator" + visible="false" + width="20" /> + </person_view> + diff --git a/indra/newview/skins/default/xui/es/strings.xml b/indra/newview/skins/default/xui/es/strings.xml index 4686d1ed3a..484511a08b 100755 --- a/indra/newview/skins/default/xui/es/strings.xml +++ b/indra/newview/skins/default/xui/es/strings.xml @@ -1256,7 +1256,7 @@ Intenta iniciar sesión de nuevo en unos instantes. https://marketplace.[MARKETPLACE_DOMAIN_NAME]/ </string> <string name="MarketplaceURL_CreateStore"> - http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.4 + http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.3 </string> <string name="MarketplaceURL_Dashboard"> https://marketplace.[MARKETPLACE_DOMAIN_NAME]/merchants/store/dashboard diff --git a/indra/newview/skins/default/xui/fr/strings.xml b/indra/newview/skins/default/xui/fr/strings.xml index 346fa62351..78d846ff4f 100755 --- a/indra/newview/skins/default/xui/fr/strings.xml +++ b/indra/newview/skins/default/xui/fr/strings.xml @@ -1274,7 +1274,7 @@ Veuillez réessayer de vous connecter dans une minute. https://marketplace.[MARKETPLACE_DOMAIN_NAME]/ </string> <string name="MarketplaceURL_CreateStore"> - http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.4 + http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.3 </string> <string name="MarketplaceURL_Dashboard"> https://marketplace.[MARKETPLACE_DOMAIN_NAME]/merchants/store/dashboard diff --git a/indra/newview/skins/default/xui/it/strings.xml b/indra/newview/skins/default/xui/it/strings.xml index 160df911d3..60ed2b0929 100755 --- a/indra/newview/skins/default/xui/it/strings.xml +++ b/indra/newview/skins/default/xui/it/strings.xml @@ -1265,7 +1265,7 @@ Prova ad accedere nuovamente tra un minuto. https://marketplace.[MARKETPLACE_DOMAIN_NAME]/ </string> <string name="MarketplaceURL_CreateStore"> - http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.4 + http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.3 </string> <string name="MarketplaceURL_Dashboard"> https://marketplace.[MARKETPLACE_DOMAIN_NAME]/merchants/store/dashboard diff --git a/indra/newview/skins/default/xui/ja/strings.xml b/indra/newview/skins/default/xui/ja/strings.xml index ab2fd461ab..a0f45e5a55 100755 --- a/indra/newview/skins/default/xui/ja/strings.xml +++ b/indra/newview/skins/default/xui/ja/strings.xml @@ -1274,7 +1274,7 @@ support@secondlife.com にお問い合わせください。 https://marketplace.[MARKETPLACE_DOMAIN_NAME]/ </string> <string name="MarketplaceURL_CreateStore"> - http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.4 + http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.3 </string> <string name="MarketplaceURL_Dashboard"> https://marketplace.[MARKETPLACE_DOMAIN_NAME]/merchants/store/dashboard diff --git a/indra/newview/skins/default/xui/pt/strings.xml b/indra/newview/skins/default/xui/pt/strings.xml index adc3ee14e0..2eb4c0a02e 100755 --- a/indra/newview/skins/default/xui/pt/strings.xml +++ b/indra/newview/skins/default/xui/pt/strings.xml @@ -1220,7 +1220,7 @@ Pessoas com contas gratuitas não poderão acessar o Second Life no momento para https://marketplace.[MARKETPLACE_DOMAIN_NAME]/ </string> <string name="MarketplaceURL_CreateStore"> - http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.4 + http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.3 </string> <string name="MarketplaceURL_Dashboard"> https://marketplace.[MARKETPLACE_DOMAIN_NAME]/merchants/store/dashboard diff --git a/indra/newview/skins/default/xui/ru/strings.xml b/indra/newview/skins/default/xui/ru/strings.xml index 49ce33ec96..0f71edcee0 100755 --- a/indra/newview/skins/default/xui/ru/strings.xml +++ b/indra/newview/skins/default/xui/ru/strings.xml @@ -1271,7 +1271,7 @@ support@secondlife.com. https://marketplace.[MARKETPLACE_DOMAIN_NAME]/ </string> <string name="MarketplaceURL_CreateStore"> - http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.4 + http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.3 </string> <string name="MarketplaceURL_Dashboard"> https://marketplace.[MARKETPLACE_DOMAIN_NAME]/merchants/store/dashboard diff --git a/indra/newview/skins/default/xui/tr/strings.xml b/indra/newview/skins/default/xui/tr/strings.xml index 5a809ea783..c4b1530b2b 100755 --- a/indra/newview/skins/default/xui/tr/strings.xml +++ b/indra/newview/skins/default/xui/tr/strings.xml @@ -1271,7 +1271,7 @@ Lütfen bir dakika içerisinde tekrar oturum açmayı deneyin. https://marketplace.[MARKETPLACE_DOMAIN_NAME]/ </string> <string name="MarketplaceURL_CreateStore"> - http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.4 + http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.3 </string> <string name="MarketplaceURL_Dashboard"> https://marketplace.[MARKETPLACE_DOMAIN_NAME]/merchants/store/dashboard diff --git a/indra/newview/tests/lltranslate_test.cpp b/indra/newview/tests/lltranslate_test.cpp index b28eb5db43..5e73dbb981 100755 --- a/indra/newview/tests/lltranslate_test.cpp +++ b/indra/newview/tests/lltranslate_test.cpp @@ -308,8 +308,8 @@ void LLCurl::Responder::httpCompleted() { } void LLCurl::Responder::completedRaw(LLChannelDescriptors const &,boost::shared_ptr<LLBufferArray> const &) { } LLCurl::Responder::~Responder() {} -void LLHTTPClient::get(const std::string&, const LLSD&, ResponderPtr, const LLSD&, const F32) {} -void LLHTTPClient::get(const std::string&, LLPointer<LLCurl::Responder>, const LLSD&, const F32) {} +void LLHTTPClient::get(const std::string&, const LLSD&, ResponderPtr, const LLSD&, const F32, bool) {} +void LLHTTPClient::get(const std::string&, LLPointer<LLCurl::Responder>, const LLSD&, const F32, bool) {} LLBufferStream::LLBufferStream(const LLChannelDescriptors& channels, LLBufferArray* buffer) : std::iostream(&mStreamBuf), mStreamBuf(channels, buffer) {} diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 19863dd845..894e368427 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -104,26 +104,37 @@ class ViewerManifest(LLManifest): self.path("dictionaries") self.end_prefix(pkgdir) - # CHOP-955: If we have "sourceid" in the build process - # environment, generate it into settings_install.xml. - try: - sourceid = os.environ["sourceid"] - except KeyError: - # no sourceid, no settings_install.xml file - pass - else: - if sourceid: - # Single-entry subset of the LLSD content of settings.xml - content = dict(sourceid=dict(Comment='Identify referring agency to Linden web servers', - Persist=1, - Type='String', - Value=sourceid)) - # put_in_file(src=) need not be an actual pathname; it - # only needs to be non-empty - settings_install = self.put_in_file(llsd.format_pretty_xml(content), - "settings_install.xml", - src="environment") - print "Put sourceid '%s' in %s" % (sourceid, settings_install) + # CHOP-955: If we have "sourceid" or "viewer_channel" in the + # build process environment, generate it into + # settings_install.xml. + settings_template = dict( + sourceid=dict(Comment='Identify referring agency to Linden web servers', + Persist=1, + Type='String', + Value=''), + CmdLineChannel=dict(Comment='Command line specified channel name', + Persist=0, + Type='String', + Value='')) + settings_install = {} + for key, setting in (("sourceid", "sourceid"), + ("channel", "CmdLineChannel")): + if key in self.args: + # only set if value is non-empty + if self.args[key]: + # copy corresponding setting from settings_template + settings_install[setting] = settings_template[setting].copy() + # then fill in Value + settings_install[setting]["Value"] = self.args[key] + print "Put %s '%s' in settings_install.xml" % (setting, self.args[key]) + + # did we actually copy anything into settings_install dict? + if settings_install: + # put_in_file(src=) need not be an actual pathname; it + # only needs to be non-empty + self.put_in_file(llsd.format_pretty_xml(settings_install), + "settings_install.xml", + src="environment") self.end_prefix("app_settings") @@ -611,6 +622,9 @@ class WindowsManifest(ViewerManifest): installer_file = self.args['installer_name'] else: installer_file = installer_file % substitution_strings + if len(self.args['package_id']) > 0: + installer_file = installer_file.replace(self.args['package_id'], "") + installer_file = installer_file.replace(".exe", self.args['package_id'] + ".exe") substitution_strings['installer_file'] = installer_file tempfile = "secondlife_setup_tmp.nsi" @@ -838,7 +852,9 @@ class DarwinManifest(ViewerManifest): volname="Second Life Installer" # DO NOT CHANGE without understanding comment above - if self.default_channel(): + if len(self.args['package_id']) > 0: + imagename = imagename + self.args['package_id'] + elif self.default_channel(): if not self.default_grid(): # beta case imagename = imagename + '_' + self.args['grid'].upper() @@ -851,7 +867,7 @@ class DarwinManifest(ViewerManifest): # make sure we don't have stale files laying about self.remove(sparsename, finalname) - self.run_command('hdiutil create %(sparse)r -volname %(vol)r -fs HFS+ -type SPARSE -megabytes 700 -layout SPUD' % { + self.run_command('hdiutil create %(sparse)r -volname %(vol)r -fs HFS+ -type SPARSE -megabytes 1000 -layout SPUD' % { 'sparse':sparsename, 'vol':volname}) @@ -926,6 +942,7 @@ class DarwinManifest(ViewerManifest): print "Converting temp disk image to final disk image" self.run_command('hdiutil convert %(sparse)r -format UDZO -imagekey zlib-level=9 -o %(final)r' % {'sparse':sparsename, 'final':finalname}) + self.run_command('hdiutil internet-enable -yes %(final)r' % {'final':finalname}) # get rid of the temp file self.package_file = finalname self.remove(sparsename) @@ -998,6 +1015,7 @@ class LinuxManifest(ViewerManifest): installer_name += '_' + self.args['grid'].upper() else: installer_name += '_' + self.channel_oneword().upper() + installer_name = installer_name + self.args['package_id'] self.strip_binaries() diff --git a/indra/test_apps/llfbconnecttest/CMakeLists.txt b/indra/test_apps/llfbconnecttest/CMakeLists.txt new file mode 100644 index 0000000000..f56329a010 --- /dev/null +++ b/indra/test_apps/llfbconnecttest/CMakeLists.txt @@ -0,0 +1,304 @@ +# -*- cmake -*- +project(llfbconnecttest) + +include(00-Common) +include(FindOpenGL) +include(LLCommon) +include(LLPlugin) +include(Linking) +include(LLSharedLibs) +include(PluginAPI) +include(LLImage) +include(LLMath) +include(LLMessage) +include(LLRender) +include(LLWindow) +include(Glut) +include(Glui) + +include_directories( + ${LLPLUGIN_INCLUDE_DIRS} + ${LLCOMMON_INCLUDE_DIRS} + ${LLIMAGE_INCLUDE_DIRS} + ${LLMATH_INCLUDE_DIRS} + ${LLMESSAGE_INCLUDE_DIRS} + ${LLRENDER_INCLUDE_DIRS} + ${LLWINDOW_INCLUDE_DIRS} +) + +if (DARWIN) + include(CMakeFindFrameworks) + find_library(COREFOUNDATION_LIBRARY CoreFoundation) +endif (DARWIN) + +### llfbconnecttest + +set(llfbconnecttest_SOURCE_FILES + llfbconnecttest.cpp + llfbconnecttest.h + bookmarks.txt + ) + +add_executable(llfbconnecttest + WIN32 + MACOSX_BUNDLE + ${llfbconnecttest_SOURCE_FILES} +) + +set_target_properties(llfbconnecttest + PROPERTIES + WIN32_EXECUTABLE + FALSE +) + +target_link_libraries(llfbconnecttest + ${GLUT_LIBRARY} + ${GLUI_LIBRARY} + ${OPENGL_LIBRARIES} + ${LLPLUGIN_LIBRARIES} + ${LLMESSAGE_LIBRARIES} + ${LLCOMMON_LIBRARIES} + ${PLUGIN_API_WINDOWS_LIBRARIES} +) + +if (DARWIN) + # The testbed needs to use a couple of CoreFoundation calls now, to deal with being a bundled app. + target_link_libraries(llfbconnecttest + ${COREFOUNDATION_LIBRARY} + ) +endif (DARWIN) + +add_dependencies(llfbconnecttest + stage_third_party_libs + SLPlugin + media_plugin_webkit + ${LLPLUGIN_LIBRARIES} + ${LLMESSAGE_LIBRARIES} + ${LLCOMMON_LIBRARIES} +) + +# turn off weird GLUI pragma +add_definitions(-DGLUI_NO_LIB_PRAGMA) + +if (DARWIN OR LINUX) + # glui.h contains code that triggers the "overloaded-virtual" warning in gcc. + set_source_files_properties(llfbconnecttest.cpp PROPERTIES COMPILE_FLAGS "-Wno-overloaded-virtual") +endif (DARWIN OR LINUX) + +# Gather build products of the various dependencies into the build directory for the testbed. + +if (DARWIN) + # path inside the app bundle where we'll need to copy plugins and other related files + set(PLUGINS_DESTINATION_DIR + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/llfbconnecttest.app/Contents/Resources + ) + + # create the Contents/Resources directory + add_custom_command( + TARGET llfbconnecttest POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS + -E + make_directory + ${PLUGINS_DESTINATION_DIR} + COMMENT "Creating Resources directory in app bundle." + ) +else (DARWIN) + set(PLUGINS_DESTINATION_DIR + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/ + ) +endif (DARWIN) + +get_target_property(BUILT_SLPLUGIN SLPlugin LOCATION) +add_custom_command(TARGET llfbconnecttest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BUILT_SLPLUGIN} ${PLUGINS_DESTINATION_DIR} + DEPENDS ${BUILT_SLPLUGIN} +) + +get_target_property(BUILT_LLCOMMON llcommon LOCATION) +add_custom_command(TARGET llfbconnecttest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BUILT_LLCOMMON} ${PLUGINS_DESTINATION_DIR} + DEPENDS ${BUILT_LLCOMMON} +) + + +if (DARWIN OR WINDOWS) + get_target_property(BUILT_WEBKIT_PLUGIN media_plugin_webkit LOCATION) + add_custom_command(TARGET llfbconnecttest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BUILT_WEBKIT_PLUGIN} ${PLUGINS_DESTINATION_DIR} + DEPENDS ${BUILT_WEBKIT_PLUGIN} + ) + + # copy over bookmarks file if llfbconnecttest gets built + get_target_property(BUILT_LLFBCONNECTTEST llfbconnecttest LOCATION) + add_custom_command(TARGET llfbconnecttest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/bookmarks.txt ${CMAKE_CURRENT_BINARY_DIR}/ + DEPENDS ${BUILT_LLFBCONNECTTEST} + ) + # also copy it to the same place as SLPlugin, which is what the mac wants... + add_custom_command(TARGET llfbconnecttest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/bookmarks.txt ${PLUGINS_DESTINATION_DIR} + DEPENDS ${BUILT_LLFBCONNECTTEST} + ) +endif (DARWIN OR WINDOWS) + +if (DARWIN) + add_custom_command(TARGET llfbconnecttest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${ARCH_PREBUILT_DIRS_RELEASE}/libllqtwebkit.dylib ${PLUGINS_DESTINATION_DIR} + DEPENDS ${ARCH_PREBUILT_DIRS_RELEASE}/libllqtwebkit.dylib + ) +endif (DARWIN) + +if(WINDOWS) + #******************** + # Plugin test library deploy + # + # Debug config runtime files required for the FB connect test + set(fbconnecttest_debug_src_dir "${ARCH_PREBUILT_DIRS_DEBUG}") + set(fbconnecttest_debug_files + libeay32.dll + libglib-2.0-0.dll + libgmodule-2.0-0.dll + libgobject-2.0-0.dll + libgthread-2.0-0.dll + qtcored4.dll + qtguid4.dll + qtnetworkd4.dll + qtopengld4.dll + qtwebkitd4.dll + ssleay32.dll + ) + copy_if_different( + ${fbconnecttest_debug_src_dir} + "${CMAKE_CURRENT_BINARY_DIR}/Debug" + out_targets + ${fbconnecttest_debug_files} + ) + set(fbconnect_test_targets ${fbconnect_test_targets} ${out_targets}) + + # Debug config runtime files required for the FB connect test (Qt image format plugins) + set(fbconecttest_debug_src_dir "${ARCH_PREBUILT_DIRS_DEBUG}/imageformats") + set(fbconecttest_debug_files + qgifd4.dll + qicod4.dll + qjpegd4.dll + qmngd4.dll + qsvgd4.dll + qtiffd4.dll + ) + copy_if_different( + ${fbconecttest_debug_src_dir} + "${CMAKE_CURRENT_BINARY_DIR}/Debug/imageformats" + out_targets + ${fbconecttest_debug_files} + ) + set(fbconnect_test_targets ${fbconnect_test_targets} ${out_targets}) + + # Debug config runtime files required for the FB connect test (Qt codec plugins) + set(fbconnecttest_debug_src_dir "${ARCH_PREBUILT_DIRS_DEBUG}/codecs") + set(fbconnecttest_debug_files + qcncodecsd4.dll + qjpcodecsd4.dll + qkrcodecsd4.dll + qtwcodecsd4.dll + ) + copy_if_different( + ${fbconnecttest_debug_src_dir} + "${CMAKE_CURRENT_BINARY_DIR}/Debug/codecs" + out_targets + ${fbconnecttest_debug_files} + ) + set(fbconnect_test_targets ${fbconnect_test_targets} ${out_targets}) + + # Release & ReleaseDebInfo config runtime files required for the FB connect test + set(fbconnecttest_release_src_dir "${ARCH_PREBUILT_DIRS_RELEASE}") + set(fbconnecttest_release_files + libeay32.dll + libglib-2.0-0.dll + libgmodule-2.0-0.dll + libgobject-2.0-0.dll + libgthread-2.0-0.dll + qtcore4.dll + qtgui4.dll + qtnetwork4.dll + qtopengl4.dll + qtwebkit4.dll + qtxmlpatterns4.dll + ssleay32.dll + ) + copy_if_different( + ${fbconnecttest_release_src_dir} + "${CMAKE_CURRENT_BINARY_DIR}/Release" + out_targets + ${fbconnecttest_release_files} + ) + set(fbconnect_test_targets ${fbconnect_test_targets} ${out_targets}) + + copy_if_different( + ${fbconnecttest_release_src_dir} + "${CMAKE_CURRENT_BINARY_DIR}/RelWithDebInfo" + out_targets + ${fbconnecttest_release_files} + ) + set(fbconnect_test_targets ${fbconnect_test_targets} ${out_targets}) + + # Release & ReleaseDebInfo config runtime files required for the FB connect test (Qt image format plugins) + set(fbconnecttest_release_src_dir "${ARCH_PREBUILT_DIRS_RELEASE}/imageformats") + set(fbconnecttest_release_files + qgif4.dll + qico4.dll + qjpeg4.dll + qmng4.dll + qsvg4.dll + qtiff4.dll + ) + copy_if_different( + ${fbconnecttest_release_src_dir} + "${CMAKE_CURRENT_BINARY_DIR}/Release/imageformats" + out_targets + ${fbconnecttest_release_files} + ) + set(fbconnect_test_targets ${fbconnect_test_targets} ${out_targets}) + + copy_if_different( + ${fbconnecttest_release_src_dir} + "${CMAKE_CURRENT_BINARY_DIR}/RelWithDebInfo/imageformats" + out_targets + ${fbconnecttest_release_files} + ) + set(fbconnect_test_targets ${fbconnect_test_targets} ${out_targets}) + + # Release & ReleaseDebInfo config runtime files required for the FB connect test (Qt codec plugins) + set(fbconnecttest_release_src_dir "${ARCH_PREBUILT_DIRS_RELEASE}/codecs") + set(fbconnecttest_release_files + qcncodecs4.dll + qjpcodecs4.dll + qkrcodecs4.dll + qtwcodecs4.dll + ) + copy_if_different( + ${fbconnecttest_release_src_dir} + "${CMAKE_CURRENT_BINARY_DIR}/Release/codecs" + out_targets + ${fbconnecttest_release_files} + ) + set(fbconnect_test_targets ${fbconnect_test_targets} ${out_targets}) + + copy_if_different( + ${fbconnecttest_release_src_dir} + "${CMAKE_CURRENT_BINARY_DIR}/RelWithDebInfo/codecs" + out_targets + ${fbconnecttest_release_files} + ) + set(fbconnect_test_targets ${fbconnect_test_targets} ${out_targets}) + + add_custom_target(copy_fbconnecttest_libs ALL + DEPENDS + ${fbconnect_test_targets} + ) + + add_dependencies(llfbconnecttest copy_fbconnecttest_libs) + +endif(WINDOWS) + +ll_deploy_sharedlibs_command(llfbconnecttest) diff --git a/indra/test_apps/llfbconnecttest/bookmarks.txt b/indra/test_apps/llfbconnecttest/bookmarks.txt new file mode 100644 index 0000000000..3995627ea9 --- /dev/null +++ b/indra/test_apps/llfbconnecttest/bookmarks.txt @@ -0,0 +1,4 @@ +# format is description, url (don't put ',' chars in description :) +# if no ',' found, whole line is used for both description and url +Google Home Page,http://www.google.com +Facebook Home Page,http://www.facebook.com diff --git a/indra/test_apps/llfbconnecttest/llfbconnecttest.cpp b/indra/test_apps/llfbconnecttest/llfbconnecttest.cpp new file mode 100644 index 0000000000..483a15c468 --- /dev/null +++ b/indra/test_apps/llfbconnecttest/llfbconnecttest.cpp @@ -0,0 +1,2394 @@ +/** + * @file LLFBConnectTest.cpp + * @brief Facebook Connect Test App + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, 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 "linden_common.h" +#include "indra_constants.h" + +#include "llapr.h" +#include "llerrorcontrol.h" + +#include <math.h> +#include <iomanip> +#include <sstream> +#include <ctime> + +#include "llfbconnecttest.h" + +#if __APPLE__ + #include <GLUT/glut.h> + #include <CoreFoundation/CoreFoundation.h> +#else + #define FREEGLUT_STATIC + #include "GL/freeglut.h" + #define GLUI_FREEGLUT +#endif + +#if LL_WINDOWS +#pragma warning(disable: 4263) +#pragma warning(disable: 4264) +#endif +#include "glui.h" + + +LLFBConnectTest* gApplication = 0; +static void gluiCallbackWrapper( int control_id ); + +//////////////////////////////////////////////////////////////////////////////// +// +static bool isTexture( GLuint texture ) +{ + bool result = false; + + // glIsTexture will sometimes return false for real textures... do this instead. + if(texture != 0) + result = true; + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// +mediaPanel::mediaPanel() +{ + mMediaTextureHandle = 0; + mPickTextureHandle = 0; + mMediaSource = NULL; + mPickTexturePixels = NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +// +mediaPanel::~mediaPanel() +{ + // delete OpenGL texture handles + if ( isTexture( mPickTextureHandle ) ) + { + std::cerr << "remMediaPanel: deleting pick texture " << mPickTextureHandle << std::endl; + glDeleteTextures( 1, &mPickTextureHandle ); + mPickTextureHandle = 0; + } + + if ( isTexture( mMediaTextureHandle ) ) + { + std::cerr << "remMediaPanel: deleting media texture " << mMediaTextureHandle << std::endl; + glDeleteTextures( 1, &mMediaTextureHandle ); + mMediaTextureHandle = 0; + } + + if(mPickTexturePixels) + { + delete mPickTexturePixels; + } + + if(mMediaSource) + { + delete mMediaSource; + } + +} + +//////////////////////////////////////////////////////////////////////////////// +// +LLFBConnectTest::LLFBConnectTest( int app_window, int window_width, int window_height ) : + mVersionMajor( 2 ), + mVersionMinor( 0 ), + mVersionPatch( 0 ), + mMaxPanels( 25 ), + mViewportAspect( 0 ), + mAppWindow( app_window ), + mCurMouseX( 0 ), + mCurMouseY( 0 ), + mFuzzyMedia( true ), + mSelectedPanel( 0 ), + mDistanceCameraToSelectedGeometry( 0.0f ), + //mMediaBrowserControlEnableCookies( 0 ), + mMediaBrowserControlBackButton( 0 ), + mMediaBrowserControlForwardButton( 0 ), + //mMediaTimeControlVolume( 100 ), + //mMediaTimeControlSeekSeconds( 0 ), + //mGluiMediaTimeControlWindowFlag( true ), + mGluiMediaBrowserControlWindowFlag( true ), + mMediaBrowserControlBackButtonFlag( true ), + mMediaBrowserControlForwardButtonFlag( true ), + mHomeWebUrl( "https://cryptic-ridge-1632.herokuapp.com/" ) +{ + // debugging spam + std::cout << std::endl << " GLUT version: " << "3.7.6" << std::endl; // no way to get real version from GLUT + std::cout << std::endl << " GLUI version: " << GLUI_Master.get_version() << std::endl; + std::cout << std::endl << "Media Plugin Test version: " << mVersionMajor << "." << mVersionMinor << "." << mVersionPatch << std::endl; + + // bookmark title + mBookmarks.push_back( std::pair< std::string, std::string >( "--- Bookmarks ---", "" ) ); + + // insert hardcoded URLs here as required for testing + //mBookmarks.push_back( std::pair< std::string, std::string >( "description", "url" ) ); + + // read bookmarks from file. + // note: uses command in ./CmakeLists.txt which copies bookmmarks file from source directory + // to app directory (WITHOUT build configuration dir) (this is cwd in Windows within MSVC) + // For example, test_apps\llplugintest and not test_apps\llplugintest\Release + // This may need to be changed for Mac/Linux builds. + // See https://jira.lindenlab.com/browse/DEV-31350 for large list of media URLs from AGNI + const std::string bookmarks_filename( "bookmarks.txt" ); + std::ifstream file_handle( bookmarks_filename.c_str() ); + if ( file_handle.is_open() ) + { + std::cout << "Reading bookmarks for test" << std::endl; + while( ! file_handle.eof() ) + { + std::string line; + std::getline( file_handle, line ); + if ( file_handle.eof() ) + break; + + if ( line.substr( 0, 1 ) != "#" ) + { + size_t comma_pos = line.find_first_of( ',' ); + if ( comma_pos != std::string::npos ) + { + std::string description = line.substr( 0, comma_pos ); + std::string url = line.substr( comma_pos + 1 ); + mBookmarks.push_back( std::pair< std::string, std::string >( description, url ) ); + } + else + { + mBookmarks.push_back( std::pair< std::string, std::string >( line, line ) ); + }; + }; + }; + std::cout << "Read " << mBookmarks.size() << " bookmarks" << std::endl; + } + else + { + std::cout << "Unable to read bookmarks from file: " << bookmarks_filename << std::endl; + }; + + // initialize linden lab APR module + ll_init_apr(); + + // Set up llerror logging + { + LLError::initForApplication("."); + LLError::setDefaultLevel(LLError::LEVEL_INFO); + //LLError::setTagLevel("Plugin", LLError::LEVEL_DEBUG); + } + + // lots of randomness in this app + srand( ( unsigned int )time( 0 ) ); + + // build GUI + makeChrome(); + + // OpenGL initialilzation + glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); + glClearDepth( 1.0f ); + glEnable( GL_DEPTH_TEST ); + glEnable( GL_COLOR_MATERIAL ); + glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE ); + glDepthFunc( GL_LEQUAL ); + glEnable( GL_TEXTURE_2D ); + glDisable( GL_BLEND ); + glColor3f( 1.0f, 1.0f, 1.0f ); + glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); + glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 ); + + // start with a sane view + resetView(); + + // initial media panel + const int num_initial_panels = 1; + for( int i = 0; i < num_initial_panels; ++i ) + { + //addMediaPanel( mBookmarks[ rand() % ( mBookmarks.size() - 1 ) + 1 ].second ); + addMediaPanel( mHomeWebUrl ); + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +LLFBConnectTest::~LLFBConnectTest() +{ + // delete all media panels + for( int i = 0; i < (int)mMediaPanels.size(); ++i ) + { + remMediaPanel( mMediaPanels[ i ] ); + }; + + // Stop the plugin read thread if it's running. + LLPluginProcessParent::setUseReadThread(false); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::reshape( int width, int height ) +{ + // update viewport (the active window inside the chrome) + int viewport_x, viewport_y; + int viewport_height, viewport_width; + GLUI_Master.get_viewport_area( &viewport_x, &viewport_y, &viewport_width, &viewport_height ); + mViewportAspect = (float)( viewport_width ) / (float)( viewport_height ); + glViewport( viewport_x, viewport_y, viewport_width, viewport_height ); + + // save these as we'll need them later + mWindowWidth = width; + mWindowHeight = height; + + // adjust size of URL bar so it doesn't get clipped + mUrlEdit->set_w( mWindowWidth - 360 ); + + // GLUI requires this + if ( glutGetWindow() != mAppWindow ) + glutSetWindow( mAppWindow ); + + // trigger re-display + glutPostRedisplay(); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::bindTexture(GLuint texture, GLint row_length, GLint alignment) +{ + glEnable( GL_TEXTURE_2D ); + + glBindTexture( GL_TEXTURE_2D, texture ); + glPixelStorei( GL_UNPACK_ROW_LENGTH, row_length ); + glPixelStorei( GL_UNPACK_ALIGNMENT, alignment ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLFBConnectTest::checkGLError(const char *name) +{ + bool result = false; + GLenum error = glGetError(); + + if(error != GL_NO_ERROR) + { + // For some reason, glGenTextures is returning GL_INVALID_VALUE... + std::cout << name << " ERROR 0x" << std::hex << error << std::dec << std::endl; + result = true; + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// +GLfloat LLFBConnectTest::distanceToCamera( GLfloat point_x, GLfloat point_y, GLfloat point_z ) +{ + GLdouble camera_pos_x = 0.0f; + GLdouble camera_pos_y = 0.0f; + GLdouble camera_pos_z = 0.0f; + + GLdouble modelMatrix[16]; + GLdouble projMatrix[16]; + GLint viewport[4]; + + glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix); + glGetDoublev(GL_PROJECTION_MATRIX, projMatrix); + glGetIntegerv(GL_VIEWPORT, viewport); + + gluUnProject( + (viewport[2]-viewport[0])/2 , (viewport[3]-viewport[1])/2, + 0.0, + modelMatrix, projMatrix, viewport, + &camera_pos_x, &camera_pos_y, &camera_pos_z ); + + GLfloat distance = + sqrt( ( camera_pos_x - point_x ) * ( camera_pos_x - point_x ) + + ( camera_pos_y - point_y ) * ( camera_pos_y - point_y ) + + ( camera_pos_z - point_z ) * ( camera_pos_z - point_z ) ); + + return distance; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::drawGeometry( int panel, bool selected ) +{ + // texture coordinates for each panel + GLfloat non_opengl_texture_coords[ 8 ] = { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f }; + GLfloat opengl_texture_coords[ 8 ] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f }; + + GLfloat *texture_coords = mMediaPanels[ panel ]->mAppTextureCoordsOpenGL?opengl_texture_coords:non_opengl_texture_coords; + + // base coordinates for each panel + GLfloat base_vertex_pos[ 8 ] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f }; + + // calculate posiitons + const int num_panels = (int)mMediaPanels.size(); + const int num_rows = (int)sqrt( (float)num_panels ); + const int num_cols = num_panels / num_rows; + const int panel_x = ( panel / num_rows ); + const int panel_y = ( panel % num_rows ); + + // default spacing is small - make it larger if checkbox set - for testing positional audio + float spacing = 0.1f; + //if ( mLargePanelSpacing ) + // spacing = 2.0f; + + const GLfloat offset_x = num_cols * ( 1.0 + spacing ) / 2; + const GLfloat offset_y = num_rows * ( 1.0 + spacing ) / 2; + + // Adjust for media aspect ratios + { + float aspect = 1.0f; + + if(mMediaPanels[ panel ]->mMediaHeight != 0) + { + aspect = (float)mMediaPanels[ panel ]->mMediaWidth / (float)mMediaPanels[ panel ]->mMediaHeight; + } + + if(aspect > 1.0f) + { + // media is wider than it is high -- adjust the top and bottom in + for( int corner = 0; corner < 4; ++corner ) + { + float temp = base_vertex_pos[corner * 2 + 1]; + + if(temp < 0.5f) + temp += 0.5 - (0.5f / aspect); + else + temp -= 0.5 - (0.5f / aspect); + + base_vertex_pos[corner * 2 + 1] = temp; + } + } + else if(aspect < 1.0f) + { + // media is higher than it is wide -- adjust the left and right sides in + for( int corner = 0; corner < 4; ++corner ) + { + float temp = base_vertex_pos[corner * 2]; + + if(temp < 0.5f) + temp += 0.5f - (0.5f * aspect); + else + temp -= 0.5f - (0.5f * aspect); + + base_vertex_pos[corner * 2] = temp; + } + } + } + + glBegin( GL_QUADS ); + for( int corner = 0; corner < 4; ++corner ) + { + glTexCoord2f( texture_coords[ corner * 2 ], texture_coords[ corner * 2 + 1 ] ); + GLfloat x = base_vertex_pos[ corner * 2 ] + panel_x * ( 1.0 + spacing ) - offset_x + spacing / 2.0f; + GLfloat y = base_vertex_pos[ corner * 2 + 1 ] + panel_y * ( 1.0 + spacing ) - offset_y + spacing / 2.0f; + + glVertex3f( x, y, 0.0f ); + }; + glEnd(); + + // calculate distance to this panel if it's selected + if ( selected ) + { + GLfloat point_x = base_vertex_pos[ 0 ] + panel_x * ( 1.0 + spacing ) - offset_x + spacing / 2.0f; + GLfloat point_y = base_vertex_pos[ 0 + 1 ] + panel_y * ( 1.0 + spacing ) - offset_y + spacing / 2.0f; + GLfloat point_z = 0.0f; + mDistanceCameraToSelectedGeometry = distanceToCamera( point_x, point_y, point_z ); + }; +} + +////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::startPanelHighlight( float red, float green, float blue, float line_width ) +{ + glPushAttrib( GL_ALL_ATTRIB_BITS ); + glEnable( GL_POLYGON_OFFSET_FILL ); + glPolygonOffset( -2.5f, -2.5f ); + glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + glLineWidth( line_width ); + glColor3f( red, green, blue ); + glDisable( GL_TEXTURE_2D ); +} + +////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::endPanelHighlight() +{ + glPopAttrib(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::draw( int draw_type ) +{ + for( int panel = 0; panel < (int)mMediaPanels.size(); ++panel ) + { + // drawing pick texture + if ( draw_type == DrawTypePickTexture ) + { + // only bother with pick if we have something to render + // Actually, we need to pick even if we're not ready to render. + // Otherwise you can't select and remove a panel which has gone bad. + //if ( mMediaPanels[ panel ]->mReadyToRender ) + { + glMatrixMode( GL_TEXTURE ); + glPushMatrix(); + + // pick texture is a power of 2 so no need to scale + glLoadIdentity(); + + // bind to media texture + glLoadIdentity(); + bindTexture( mMediaPanels[ panel ]->mPickTextureHandle ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + + // draw geometry using pick texture + drawGeometry( panel, false ); + + glMatrixMode( GL_TEXTURE ); + glPopMatrix(); + }; + } + else + if ( draw_type == DrawTypeMediaTexture ) + { + bool texture_valid = false; + bool plugin_exited = false; + + if(mMediaPanels[ panel ]->mMediaSource) + { + texture_valid = mMediaPanels[ panel ]->mMediaSource->textureValid(); + plugin_exited = mMediaPanels[ panel ]->mMediaSource->isPluginExited(); + } + + // save texture matrix (changes for each panel) + glMatrixMode( GL_TEXTURE ); + glPushMatrix(); + + // only process texture if the media is ready to draw + // (we still want to draw the geometry) + if ( mMediaPanels[ panel ]->mReadyToRender && texture_valid ) + { + // bind to media texture + bindTexture( mMediaPanels[ panel ]->mMediaTextureHandle ); + + if ( mFuzzyMedia ) + { + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + } + else + { + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + } + + // scale to fit panel + glScalef( mMediaPanels[ panel ]->mTextureScaleX, + mMediaPanels[ panel ]->mTextureScaleY, + 1.0f ); + }; + + float intensity = plugin_exited?0.25f:1.0f; + + // highlight the selected panel + if ( mSelectedPanel && ( mMediaPanels[ panel ]->mId == mSelectedPanel->mId ) ) + { + startPanelHighlight( intensity, intensity, 0.0f, 5.0f ); + drawGeometry( panel, true ); + endPanelHighlight(); + } + else + // this panel not able to render yet since it + // doesn't have enough information + if ( !mMediaPanels[ panel ]->mReadyToRender ) + { + startPanelHighlight( intensity, 0.0f, 0.0f, 2.0f ); + drawGeometry( panel, false ); + endPanelHighlight(); + } + else + // just display a border around the media + { + startPanelHighlight( 0.0f, intensity, 0.0f, 2.0f ); + drawGeometry( panel, false ); + endPanelHighlight(); + }; + + if ( mMediaPanels[ panel ]->mReadyToRender && texture_valid ) + { + // draw visual geometry + drawGeometry( panel, false ); + } + + // restore texture matrix (changes for each panel) + glMatrixMode( GL_TEXTURE ); + glPopMatrix(); + }; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::display() +{ + // GLUI requires this + if ( glutGetWindow() != mAppWindow ) + glutSetWindow( mAppWindow ); + + // start with a clean slate + glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + + // set up OpenGL view + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); + glFrustum( -mViewportAspect * 0.04f, mViewportAspect * 0.04f, -0.04f, 0.04f, 0.1f, 50.0f ); + glMatrixMode( GL_MODELVIEW ); + glLoadIdentity(); + glTranslatef( 0.0, 0.0, 0.0f ); + glTranslatef( mViewPos[ 0 ], mViewPos[ 1 ], -mViewPos[ 2 ] ); + glMultMatrixf( mViewRotation ); + + // draw pick texture + draw( DrawTypePickTexture ); + + // read colors and get coordinate values + glReadPixels( mCurMouseX, mCurMouseY, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, mPixelReadColor ); + + // clear the pick render (otherwise it may depth-fight with the textures rendered later) + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + + // draw visible geometry + draw( DrawTypeMediaTexture ); + + glutSwapBuffers(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::idle() +{ +// checkGLError("LLFBConnectTest::idle"); + + // GLUI requires this + if ( glutGetWindow() != mAppWindow ) + glutSetWindow( mAppWindow ); + + // random creation/destruction of panels enabled? +/* + const time_t panel_timeout_time = 5; + if ( mRandomPanelCount ) + { + // time for a change + static time_t last_panel_time = 0; + if ( time( NULL ) - last_panel_time > panel_timeout_time ) + { + if ( rand() % 2 == 0 ) + { + if ( mMediaPanels.size() < 16 ) + { + std::cout << "Randomly adding new panel" << std::endl; + addMediaPanel( mBookmarks[ rand() % ( mBookmarks.size() - 1 ) + 1 ].second ); + }; + } + else + { + if ( mMediaPanels.size() > 0 ) + { + std::cout << "Deleting selected panel" << std::endl; + remMediaPanel( mSelectedPanel ); + }; + }; + time( &last_panel_time ); + }; + }; + + // random selection of bookmarks enabled? + const time_t bookmark_timeout_time = 5; + if ( mRandomBookmarks ) + { + // time for a change + static time_t last_bookmark_time = 0; + if ( time( NULL ) - last_bookmark_time > bookmark_timeout_time ) + { + // go to a different random bookmark on each panel + for( int panel = 0; panel < (int)mMediaPanels.size(); ++panel ) + { + std::string uri = mBookmarks[ rand() % ( mBookmarks.size() - 1 ) + 1 ].second; + + std::cout << "Random: navigating to : " << uri << std::endl; + + std::string mime_type = mimeTypeFromUrl( uri ); + + if ( mime_type != mMediaPanels[ panel ]->mMimeType ) + { + replaceMediaPanel( mMediaPanels[ panel ], uri ); + } + else + { + mMediaPanels[ panel ]->mMediaSource->loadURI( uri ); + mMediaPanels[ panel ]->mMediaSource->start(); + }; + }; + + time( &last_bookmark_time ); + }; + }; +*/ + // update UI + if ( mSelectedPanel ) + { + // set volume based on slider if we have time media + //if ( mGluiMediaTimeControlWindowFlag ) + //{ + // mSelectedPanel->mMediaSource->setVolume( (float)mMediaTimeControlVolume / 100.0f ); + //}; + + // NOTE: it is absurd that we need cache the state of GLUI controls + // but enabling/disabling controls drags framerate from 500+ + // down to 15. Not a problem for plugin system - only this test + // enable/disable time based UI controls based on type of plugin + if ( mSelectedPanel->mMediaSource->pluginSupportsMediaTime() ) + { + /* + if ( ! mGluiMediaTimeControlWindowFlag ) + { + mGluiMediaTimeControlWindow->enable(); + mGluiMediaTimeControlWindowFlag = true; + }; + */ + } + else + { + /* + if ( mGluiMediaTimeControlWindowFlag ) + { + mGluiMediaTimeControlWindow->disable(); + mGluiMediaTimeControlWindowFlag = false; + }; + */ + }; + + // enable/disable browser based UI controls based on type of plugin + if ( mSelectedPanel->mMediaSource->pluginSupportsMediaBrowser() ) + { + if ( ! mGluiMediaBrowserControlWindowFlag ) + { + mGluiMediaBrowserControlWindow->enable(); + mGluiMediaBrowserControlWindowFlag = true; + }; + } + else + { + if ( mGluiMediaBrowserControlWindowFlag ) + { + mGluiMediaBrowserControlWindow->disable(); + mGluiMediaBrowserControlWindowFlag = false; + }; + }; + + // enable/disable browser back button depending on browser history + if ( mSelectedPanel->mMediaSource->getHistoryBackAvailable() ) + { + if ( ! mMediaBrowserControlBackButtonFlag ) + { + mMediaBrowserControlBackButton->enable(); + mMediaBrowserControlBackButtonFlag = true; + }; + } + else + { + if ( mMediaBrowserControlBackButtonFlag ) + { + mMediaBrowserControlBackButton->disable(); + mMediaBrowserControlBackButtonFlag = false; + }; + }; + + // enable/disable browser forward button depending on browser history + if ( mSelectedPanel->mMediaSource->getHistoryForwardAvailable() ) + { + if ( ! mMediaBrowserControlForwardButtonFlag ) + { + mMediaBrowserControlForwardButton->enable(); + mMediaBrowserControlForwardButtonFlag = true; + }; + } + else + { + if ( mMediaBrowserControlForwardButtonFlag ) + { + mMediaBrowserControlForwardButton->disable(); + mMediaBrowserControlForwardButtonFlag = false; + }; + }; + + // NOTE: This is *very* slow and not worth optimising + updateStatusBar(); + }; + + // update all the panels + for( int panel_index = 0; panel_index < (int)mMediaPanels.size(); ++panel_index ) + { + mediaPanel *panel = mMediaPanels[ panel_index ]; + + // call plugins idle function so it can potentially update itself + panel->mMediaSource->idle(); + + // update each media panel + updateMediaPanel( panel ); + + LLRect dirty_rect; + if ( ! panel->mMediaSource->textureValid() ) + { + //std::cout << "texture invalid, skipping update..." << std::endl; + } + else + if ( panel && + ( panel->mMediaWidth != panel->mMediaSource->getWidth() || + panel->mMediaHeight != panel->mMediaSource->getHeight() ) ) + { + //std::cout << "Resize in progress, skipping update..." << std::endl; + } + else + if ( panel->mMediaSource->getDirty( &dirty_rect ) ) + { + const unsigned char* pixels = panel->mMediaSource->getBitsData(); + if ( pixels && isTexture(panel->mMediaTextureHandle)) + { + int x_offset = dirty_rect.mLeft; + int y_offset = dirty_rect.mBottom; + int width = dirty_rect.mRight - dirty_rect.mLeft; + int height = dirty_rect.mTop - dirty_rect.mBottom; + + if((dirty_rect.mRight <= panel->mTextureWidth) && (dirty_rect.mTop <= panel->mTextureHeight)) + { + // Offset the pixels pointer properly + pixels += ( y_offset * panel->mMediaSource->getTextureDepth() * panel->mMediaSource->getBitsWidth() ); + pixels += ( x_offset * panel->mMediaSource->getTextureDepth() ); + + // set up texture + bindTexture( panel->mMediaTextureHandle, panel->mMediaSource->getBitsWidth() ); + if ( mFuzzyMedia ) + { + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + } + else + { + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + }; + + checkGLError("glTexParameteri"); + + if(panel->mMediaSource->getTextureFormatSwapBytes()) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 1); + checkGLError("glPixelStorei"); + } + + // draw portion that changes into texture + glTexSubImage2D( GL_TEXTURE_2D, 0, + x_offset, + y_offset, + width, + height, + panel->mMediaSource->getTextureFormatPrimary(), + panel->mMediaSource->getTextureFormatType(), + pixels ); + + if(checkGLError("glTexSubImage2D")) + { + std::cerr << " panel ID=" << panel->mId << std::endl; + std::cerr << " texture size = " << panel->mTextureWidth << " x " << panel->mTextureHeight << std::endl; + std::cerr << " media size = " << panel->mMediaWidth << " x " << panel->mMediaHeight << std::endl; + std::cerr << " dirty rect = " << dirty_rect.mLeft << ", " << dirty_rect.mBottom << ", " << dirty_rect.mRight << ", " << dirty_rect.mTop << std::endl; + std::cerr << " texture width = " << panel->mMediaSource->getBitsWidth() << std::endl; + std::cerr << " format primary = 0x" << std::hex << panel->mMediaSource->getTextureFormatPrimary() << std::dec << std::endl; + std::cerr << " format type = 0x" << std::hex << panel->mMediaSource->getTextureFormatType() << std::dec << std::endl; + std::cerr << " pixels = " << (void*)pixels << std::endl; + } + + if(panel->mMediaSource->getTextureFormatSwapBytes()) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 0); + checkGLError("glPixelStorei"); + } + + panel->mMediaSource->resetDirty(); + + panel->mReadyToRender = true; + } + else + { + std::cerr << "dirty rect is outside current media size, skipping update" << std::endl; + } + }; + }; + }; + + // GLUI requires this + if ( glutGetWindow() != mAppWindow ) + glutSetWindow( mAppWindow ); + + // trigger re-display + glutPostRedisplay(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::windowPosToTexturePos( int window_x, int window_y, + int& media_x, int& media_y, + int& id ) +{ + if ( ! mSelectedPanel ) + { + media_x = 0; + media_y = 0; + id = 0; + return; + }; + + // record cursor poisiton for a readback next frame + mCurMouseX = window_x; + // OpenGL app == coordinate system this way + // NOTE: unrelated to settings in plugin - this + // is just for this app + mCurMouseY = mWindowHeight - window_y; + + // extract x (0..1023, y (0..1023) and id (0..15) from RGB components + unsigned long pixel_read_color_bits = ( mPixelReadColor[ 0 ] << 16 ) | ( mPixelReadColor[ 1 ] << 8 ) | mPixelReadColor[ 2 ]; + int texture_x = pixel_read_color_bits & 0x3ff; + int texture_y = ( pixel_read_color_bits >> 10 ) & 0x3ff; + id = ( pixel_read_color_bits >> 20 ) & 0x0f; + + // scale to size of media (1024 because we use 10 bits for X and Y from 24) + media_x = (int)( ( (float)mSelectedPanel->mMediaWidth * (float)texture_x ) / 1024.0f ); + media_y = (int)( ( (float)mSelectedPanel->mMediaHeight * (float)texture_y ) / 1024.0f ); + + // we assume the plugin uses an inverted coordinate scheme like OpenGL + // if not, the plugin code inverts the Y coordinate for us - we don't need to + media_y = mSelectedPanel->mMediaHeight - media_y; + + if ( media_x > 0 && media_y > 0 ) + { + //std::cout << " mouse coords: " << mCurMouseX << " x " << mCurMouseY << " and id = " << id << std::endl; + //std::cout << "raw texture coords: " << texture_x << " x " << texture_y << " and id = " << id << std::endl; + //std::cout << " media coords: " << media_x << " x " << media_y << " and id = " << id << std::endl; + //std::cout << std::endl; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::selectPanelById( int id ) +{ + for( int panel = 0; panel < (int)mMediaPanels.size(); ++panel ) + { + if ( mMediaPanels[ panel ]->mId == id ) + { + selectPanel(mMediaPanels[ panel ]); + return; + }; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::selectPanel( mediaPanel* panel ) +{ + if( mSelectedPanel == panel ) + return; + + // turn off volume before we delete it + if( mSelectedPanel && mSelectedPanel->mMediaSource ) + { + mSelectedPanel->mMediaSource->setVolume( 0.0f ); + mSelectedPanel->mMediaSource->setPriority( LLPluginClassMedia::PRIORITY_LOW ); + }; + + mSelectedPanel = panel; + + if( mSelectedPanel && mSelectedPanel->mMediaSource ) + { + //mSelectedPanel->mMediaSource->setVolume( (float)mMediaTimeControlVolume / 100.0f ); + mSelectedPanel->mMediaSource->setPriority( LLPluginClassMedia::PRIORITY_NORMAL ); + + if(!mSelectedPanel->mStartUrl.empty()) + { + mUrlEdit->set_text(const_cast<char*>(mSelectedPanel->mStartUrl.c_str()) ); + } + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +mediaPanel* LLFBConnectTest::findMediaPanel( LLPluginClassMedia* source ) +{ + mediaPanel *result = NULL; + + for( int panel = 0; panel < (int)mMediaPanels.size(); ++panel ) + { + if ( mMediaPanels[ panel ]->mMediaSource == source ) + { + result = mMediaPanels[ panel ]; + } + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// +mediaPanel* LLFBConnectTest::findMediaPanel( const std::string &target_name ) +{ + mediaPanel *result = NULL; + + for( int panel = 0; panel < (int)mMediaPanels.size(); ++panel ) + { + if ( mMediaPanels[ panel ]->mTarget == target_name ) + { + result = mMediaPanels[ panel ]; + } + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::navigateToNewURI( std::string uri ) +{ + if ( uri.length() ) + { + std::string mime_type = mimeTypeFromUrl( uri ); + + if ( !mSelectedPanel->mMediaSource->isPluginExited() && (mime_type == mSelectedPanel->mMimeType) ) + { + std::cout << "MIME type is the same" << std::endl; + mSelectedPanel->mMediaSource->loadURI( uri ); + mSelectedPanel->mMediaSource->start(); + mBookmarkList->do_selection( 0 ); + } + else + { + std::cout << "MIME type changed or plugin had exited" << std::endl; + replaceMediaPanel( mSelectedPanel, uri ); + mBookmarkList->do_selection( 0 ); + } + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::initUrlHistory( std::string uris ) +{ + if ( uris.length() > 0 ) + { + std::cout << "init URL : " << uris << std::endl; + LLSD historySD; + + char *cstr, *p; + cstr = new char[uris.size()+1]; + strcpy(cstr, uris.c_str()); + const char *DELIMS = " ,;"; + p = strtok(cstr, DELIMS); + while (p != NULL) { + historySD.insert(0, p); + p = strtok(NULL, DELIMS); + } + mSelectedPanel->mMediaSource->initializeUrlHistory(historySD); + delete[] cstr; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::gluiCallback( int control_id ) +{ + if ( control_id == mIdBookmarks ) + { + std::string uri = mBookmarks[ mSelBookmark ].second; + + navigateToNewURI( uri ); + } + else + if ( control_id == mIdUrlEdit) + { + std::string uri = mUrlEdit->get_text(); + + navigateToNewURI( uri ); + } +/* + else + if ( control_id == mIdUrlInitHistoryEdit ) + { + std::string uri = mUrlInitHistoryEdit->get_text(); + + initUrlHistory( uri ); + } + else + if ( control_id == mIdControlAddPanel ) + { + addMediaPanel( mBookmarks[ rand() % ( mBookmarks.size() - 1 ) + 1 ].second ); + } + else + if ( control_id == mIdControlRemPanel ) + { + remMediaPanel( mSelectedPanel ); + } + else + if ( control_id == mIdDisableTimeout ) + { + // Set the "disable timeout" flag for all active plugins. + for( int i = 0; i < (int)mMediaPanels.size(); ++i ) + { + mMediaPanels[ i ]->mMediaSource->setDisableTimeout(mDisableTimeout); + } + } + else + if ( control_id == mIdUsePluginReadThread ) + { + LLPluginProcessParent::setUseReadThread(mUsePluginReadThread); + } + else + if ( control_id == mIdControlCrashPlugin ) + { + // send message to plugin and ask it to crash + // (switch out for ReleaseCandidate version :) ) + if(mSelectedPanel && mSelectedPanel->mMediaSource) + { + mSelectedPanel->mMediaSource->crashPlugin(); + } + } + else + if ( control_id == mIdControlHangPlugin ) + { + // send message to plugin and ask it to hang + // (switch out for ReleaseCandidate version :) ) + if(mSelectedPanel && mSelectedPanel->mMediaSource) + { + mSelectedPanel->mMediaSource->hangPlugin(); + } + } + else +*/ + if ( control_id == mIdControlExitApp ) + { + // text for exiting plugin system cleanly + delete this; // clean up + exit( 0 ); + } +/* + else + if ( control_id == mIdMediaTimeControlPlay ) + { + if ( mSelectedPanel ) + { + mSelectedPanel->mMediaSource->setLoop( false ); + mSelectedPanel->mMediaSource->start(); + }; + } + else + if ( control_id == mIdMediaTimeControlLoop ) + { + if ( mSelectedPanel ) + { + mSelectedPanel->mMediaSource->setLoop( true ); + mSelectedPanel->mMediaSource->start(); + }; + } + else + if ( control_id == mIdMediaTimeControlPause ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->pause(); + } + else + if ( control_id == mIdMediaTimeControlStop ) + { + if ( mSelectedPanel ) + { + mSelectedPanel->mMediaSource->stop(); + }; + } + else + if ( control_id == mIdMediaTimeControlSeek ) + { + if ( mSelectedPanel ) + { + // get value from spinner + float seconds_to_seek = mMediaTimeControlSeekSeconds; + mSelectedPanel->mMediaSource->seek( seconds_to_seek ); + mSelectedPanel->mMediaSource->start(); + }; + } + else + if ( control_id == mIdMediaTimeControlRewind ) + { + if ( mSelectedPanel ) + { + mSelectedPanel->mMediaSource->setLoop( false ); + mSelectedPanel->mMediaSource->start(-2.0f); + }; + } + else + if ( control_id == mIdMediaTimeControlFastForward ) + { + if ( mSelectedPanel ) + { + mSelectedPanel->mMediaSource->setLoop( false ); + mSelectedPanel->mMediaSource->start(2.0f); + }; + } + else +*/ + if ( control_id == mIdMediaBrowserControlBack ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->browse_back(); + } + else + if ( control_id == mIdMediaBrowserControlStop ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->browse_stop(); + } + else + if ( control_id == mIdMediaBrowserControlForward ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->browse_forward(); + } + else + if ( control_id == mIdMediaBrowserControlHome ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->loadURI( mHomeWebUrl ); + } + else + if ( control_id == mIdMediaBrowserControlReload ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->browse_reload( true ); + } +/* + else + if ( control_id == mIdMediaBrowserControlClearCache ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->clear_cache(); + } + else + if ( control_id == mIdMediaBrowserControlClearCookies ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->clear_cookies(); + } + else + if ( control_id == mIdMediaBrowserControlEnableCookies ) + { + if ( mSelectedPanel ) + { + if ( mMediaBrowserControlEnableCookies ) + { + mSelectedPanel->mMediaSource->enable_cookies( true ); + } + else + { + mSelectedPanel->mMediaSource->enable_cookies( false ); + } + }; + }; +*/ +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::keyboard( int key ) +{ + //if ( key == 'a' || key == 'A' ) + // addMediaPanel( mBookmarks[ rand() % ( mBookmarks.size() - 1 ) + 1 ].second ); + //else + //if ( key == 'r' || key == 'R' ) + // remMediaPanel( mSelectedPanel ); + //else + //if ( key == 'd' || key == 'D' ) + // dumpPanelInfo(); + //else + if ( key == 27 ) + { + std::cout << "Application finished - exiting..." << std::endl; + delete this; + exit( 0 ); + }; + + mSelectedPanel->mMediaSource->keyEvent( LLPluginClassMedia::KEY_EVENT_DOWN, key, 0 , LLSD()); + mSelectedPanel->mMediaSource->keyEvent( LLPluginClassMedia::KEY_EVENT_UP, key, 0, LLSD()); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::mouseButton( int button, int state, int x, int y ) +{ + if ( button == GLUT_LEFT_BUTTON ) + { + if ( state == GLUT_DOWN ) + { + int media_x, media_y, id; + windowPosToTexturePos( x, y, media_x, media_y, id ); + + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->mouseEvent( LLPluginClassMedia::MOUSE_EVENT_DOWN, 0, media_x, media_y, 0 ); + } + else + if ( state == GLUT_UP ) + { + int media_x, media_y, id; + windowPosToTexturePos( x, y, media_x, media_y, id ); + + // only select a panel if we're on a panel + // (HACK: strictly speaking this rules out clicking on + // the origin of a panel but that's very unlikely) + if ( media_x > 0 && media_y > 0 ) + { + selectPanelById( id ); + + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->mouseEvent( LLPluginClassMedia::MOUSE_EVENT_UP, 0, media_x, media_y, 0 ); + }; + }; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::mousePassive( int x, int y ) +{ + int media_x, media_y, id; + windowPosToTexturePos( x, y, media_x, media_y, id ); + + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->mouseEvent( LLPluginClassMedia::MOUSE_EVENT_MOVE, 0, media_x, media_y, 0 ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::mouseMove( int x, int y ) +{ + int media_x, media_y, id; + windowPosToTexturePos( x, y, media_x, media_y, id ); + + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->mouseEvent( LLPluginClassMedia::MOUSE_EVENT_MOVE, 0, media_x, media_y, 0 ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::makeChrome() +{ + // IDs used by GLUI + int start_id = 0x1000; + + // right side window - geometry manipulators +#if __APPLE__ + // the Apple GLUT implementation doesn't seem to set the graphic offset of subwindows correctly when they overlap in certain ways. + // Use a separate controls window in this case. + // GLUI window at right containing manipulation controls and other buttons + int x = glutGet(GLUT_WINDOW_X) + glutGet(GLUT_WINDOW_WIDTH) + 4; + int y = glutGet(GLUT_WINDOW_Y); + GLUI* right_glui_window = GLUI_Master.create_glui( "", 0, x, y ); +#else + GLUI* right_glui_window = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_RIGHT ); +#endif + mViewRotationCtrl = right_glui_window->add_rotation( "Rotation", mViewRotation ); + mViewTranslationCtrl = right_glui_window->add_translation( "Translate", GLUI_TRANSLATION_XY, mViewPos ); + mViewTranslationCtrl->set_speed( 0.01f ); + mViewScaleCtrl = right_glui_window->add_translation( "Scale", GLUI_TRANSLATION_Z, &mViewPos[ 2 ] ); + mViewScaleCtrl->set_speed( 0.05f ); + right_glui_window->set_main_gfx_window( mAppWindow ); + + // right side window - app controls + /* + mIdControlAddPanel = start_id++; + right_glui_window->add_statictext( "" ); + right_glui_window->add_separator(); + right_glui_window->add_statictext( "" ); + right_glui_window->add_button( "Add panel", mIdControlAddPanel, gluiCallbackWrapper ); + right_glui_window->add_statictext( "" ); + mIdControlRemPanel = start_id++; + right_glui_window->add_button( "Rem panel", mIdControlRemPanel, gluiCallbackWrapper ); + right_glui_window->add_statictext( "" ); + right_glui_window->add_separator(); + right_glui_window->add_statictext( "" ); + mIdControlCrashPlugin = start_id++; + right_glui_window->add_button( "Crash plugin", mIdControlCrashPlugin, gluiCallbackWrapper ); + mIdControlHangPlugin = start_id++; + right_glui_window->add_button( "Hang plugin", mIdControlHangPlugin, gluiCallbackWrapper ); + */ + right_glui_window->add_statictext( "" ); + right_glui_window->add_separator(); + right_glui_window->add_statictext( "" ); + mIdControlExitApp = start_id++; + right_glui_window->add_button( "Exit app", mIdControlExitApp, gluiCallbackWrapper ); + + //// top window - holds bookmark UI + mIdBookmarks = start_id++; + mSelBookmark = 0; + GLUI* glui_window_top = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_TOP ); + mBookmarkList = glui_window_top->add_listbox( "", &mSelBookmark, mIdBookmarks, gluiCallbackWrapper ); + // only add the first 50 bookmarks - list can be very long sometimes (30,000+) + // when testing list of media URLs from AGNI for example + for( unsigned int each = 0; each < mBookmarks.size() && each < 50; ++each ) + mBookmarkList->add_item( each, const_cast< char* >( mBookmarks[ each ].first.c_str() ) ); + glui_window_top->set_main_gfx_window( mAppWindow ); + + glui_window_top->add_column( false ); + mIdUrlEdit = start_id++; + mUrlEdit = glui_window_top->add_edittext( "Url:", GLUI_EDITTEXT_TEXT, 0, mIdUrlEdit, gluiCallbackWrapper ); + mUrlEdit->set_w( 600 ); + //GLUI* glui_window_top2 = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_TOP ); + //mIdUrlInitHistoryEdit = start_id++; + //mUrlInitHistoryEdit = glui_window_top2->add_edittext( "Init History (separate by commas or semicolons):", + // GLUI_EDITTEXT_TEXT, 0, mIdUrlInitHistoryEdit, gluiCallbackWrapper ); + //mUrlInitHistoryEdit->set_w( 800 ); + + // top window - media controls for "time" media types (e.g. movies) +/* + mGluiMediaTimeControlWindow = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_TOP ); + mGluiMediaTimeControlWindow->set_main_gfx_window( mAppWindow ); + mIdMediaTimeControlPlay = start_id++; + mGluiMediaTimeControlWindow->add_button( "PLAY", mIdMediaTimeControlPlay, gluiCallbackWrapper ); + mGluiMediaTimeControlWindow->add_column( false ); + mIdMediaTimeControlLoop = start_id++; + mGluiMediaTimeControlWindow->add_button( "LOOP", mIdMediaTimeControlLoop, gluiCallbackWrapper ); + mGluiMediaTimeControlWindow->add_column( false ); + mIdMediaTimeControlPause = start_id++; + mGluiMediaTimeControlWindow->add_button( "PAUSE", mIdMediaTimeControlPause, gluiCallbackWrapper ); + mGluiMediaTimeControlWindow->add_column( false ); + + GLUI_Button *button; + mIdMediaTimeControlRewind = start_id++; + button = mGluiMediaTimeControlWindow->add_button( "<<", mIdMediaTimeControlRewind, gluiCallbackWrapper ); + button->set_w(30); + mGluiMediaTimeControlWindow->add_column( false ); + mIdMediaTimeControlFastForward = start_id++; + button = mGluiMediaTimeControlWindow->add_button( ">>", mIdMediaTimeControlFastForward, gluiCallbackWrapper ); + button->set_w(30); + + mGluiMediaTimeControlWindow->add_column( true ); + + mIdMediaTimeControlStop = start_id++; + mGluiMediaTimeControlWindow->add_button( "STOP", mIdMediaTimeControlStop, gluiCallbackWrapper ); + mGluiMediaTimeControlWindow->add_column( false ); + mIdMediaTimeControlVolume = start_id++; + GLUI_Spinner* spinner = mGluiMediaTimeControlWindow->add_spinner( "Volume", 2, &mMediaTimeControlVolume, mIdMediaTimeControlVolume, gluiCallbackWrapper); + spinner->set_float_limits( 0, 100 ); + mGluiMediaTimeControlWindow->add_column( true ); + mIdMediaTimeControlSeekSeconds = start_id++; + spinner = mGluiMediaTimeControlWindow->add_spinner( "", 2, &mMediaTimeControlSeekSeconds, mIdMediaTimeControlSeekSeconds, gluiCallbackWrapper); + spinner->set_float_limits( 0, 200 ); + spinner->set_w( 32 ); + spinner->set_speed( 0.025f ); + mGluiMediaTimeControlWindow->add_column( false ); + mIdMediaTimeControlSeek = start_id++; + mGluiMediaTimeControlWindow->add_button( "SEEK", mIdMediaTimeControlSeek, gluiCallbackWrapper ); + mGluiMediaTimeControlWindow->add_column( false ); +*/ + + // top window - media controls for "browser" media types (e.g. web browser) + mGluiMediaBrowserControlWindow = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_TOP ); + mGluiMediaBrowserControlWindow->set_main_gfx_window( mAppWindow ); + mIdMediaBrowserControlBack = start_id++; + mMediaBrowserControlBackButton = mGluiMediaBrowserControlWindow->add_button( "BACK", mIdMediaBrowserControlBack, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlStop = start_id++; + mGluiMediaBrowserControlWindow->add_button( "STOP", mIdMediaBrowserControlStop, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlForward = start_id++; + mMediaBrowserControlForwardButton = mGluiMediaBrowserControlWindow->add_button( "FORWARD", mIdMediaBrowserControlForward, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlHome = start_id++; + mGluiMediaBrowserControlWindow->add_button( "HOME", mIdMediaBrowserControlHome, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlReload = start_id++; + mGluiMediaBrowserControlWindow->add_button( "RELOAD", mIdMediaBrowserControlReload, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + /* + mIdMediaBrowserControlClearCache = start_id++; + mGluiMediaBrowserControlWindow->add_button( "CLEAR CACHE", mIdMediaBrowserControlClearCache, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlClearCookies = start_id++; + mGluiMediaBrowserControlWindow->add_button( "CLEAR COOKIES", mIdMediaBrowserControlClearCookies, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlEnableCookies = start_id++; + mMediaBrowserControlEnableCookies = 0; + mGluiMediaBrowserControlWindow->add_checkbox( "Enable Cookies", &mMediaBrowserControlEnableCookies, mIdMediaBrowserControlEnableCookies, gluiCallbackWrapper ); + + // top window - misc controls + GLUI* glui_window_misc_control = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_TOP ); + mIdRandomPanelCount = start_id++; + mRandomPanelCount = 0; + glui_window_misc_control->add_checkbox( "Randomize panel count", &mRandomPanelCount, mIdRandomPanelCount, gluiCallbackWrapper ); + glui_window_misc_control->set_main_gfx_window( mAppWindow ); + glui_window_misc_control->add_column( true ); + mIdRandomBookmarks = start_id++; + mRandomBookmarks = 0; + glui_window_misc_control->add_checkbox( "Randomize bookmarks", &mRandomBookmarks, mIdRandomBookmarks, gluiCallbackWrapper ); + glui_window_misc_control->set_main_gfx_window( mAppWindow ); + glui_window_misc_control->add_column( true ); + + mIdDisableTimeout = start_id++; + mDisableTimeout = 0; + glui_window_misc_control->add_checkbox( "Disable plugin timeout", &mDisableTimeout, mIdDisableTimeout, gluiCallbackWrapper ); + glui_window_misc_control->set_main_gfx_window( mAppWindow ); + glui_window_misc_control->add_column( true ); + + mIdUsePluginReadThread = start_id++; + mUsePluginReadThread = 0; + glui_window_misc_control->add_checkbox( "Use plugin read thread", &mUsePluginReadThread, mIdUsePluginReadThread, gluiCallbackWrapper ); + glui_window_misc_control->set_main_gfx_window( mAppWindow ); + glui_window_misc_control->add_column( true ); + + mIdLargePanelSpacing = start_id++; + mLargePanelSpacing = 0; + glui_window_misc_control->add_checkbox( "Large Panel Spacing", &mLargePanelSpacing, mIdLargePanelSpacing, gluiCallbackWrapper ); + glui_window_misc_control->set_main_gfx_window( mAppWindow ); + glui_window_misc_control->add_column( true ); +*/ + // bottom window - status + mBottomGLUIWindow = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_BOTTOM ); + mStatusText = mBottomGLUIWindow->add_statictext( "" ); + mBottomGLUIWindow->set_main_gfx_window( mAppWindow ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::resetView() +{ + mViewRotationCtrl->reset(); + + mViewScaleCtrl->set_x( 0.0f ); + mViewScaleCtrl->set_y( 0.0f ); + mViewScaleCtrl->set_z( 1.3f ); + + mViewTranslationCtrl->set_x( 0.0f ); + mViewTranslationCtrl->set_y( 0.0f ); + mViewTranslationCtrl->set_z( 0.0f ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::makePickTexture( int id, GLuint* texture_handle, unsigned char** texture_pixels ) +{ + int pick_texture_width = 1024; + int pick_texture_height = 1024; + int pick_texture_depth = 3; + unsigned char* ptr = new unsigned char[ pick_texture_width * pick_texture_height * pick_texture_depth ]; + for( int y = 0; y < pick_texture_height; ++y ) + { + for( int x = 0; x < pick_texture_width * pick_texture_depth ; x += pick_texture_depth ) + { + unsigned long bits = 0L; + bits |= ( id << 20 ) | ( y << 10 ) | ( x / 3 ); + unsigned char r_component = ( bits >> 16 ) & 0xff; + unsigned char g_component = ( bits >> 8 ) & 0xff; + unsigned char b_component = bits & 0xff; + + ptr[ y * pick_texture_width * pick_texture_depth + x + 0 ] = r_component; + ptr[ y * pick_texture_width * pick_texture_depth + x + 1 ] = g_component; + ptr[ y * pick_texture_width * pick_texture_depth + x + 2 ] = b_component; + }; + }; + + glGenTextures( 1, texture_handle ); + + checkGLError("glGenTextures"); + std::cout << "glGenTextures returned " << *texture_handle << std::endl; + + bindTexture( *texture_handle ); + glTexImage2D( GL_TEXTURE_2D, 0, + GL_RGB, + pick_texture_width, pick_texture_height, + 0, GL_RGB, GL_UNSIGNED_BYTE, ptr ); + + *texture_pixels = ptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// +std::string LLFBConnectTest::mimeTypeFromUrl( std::string& url ) +{ + // default to web + std::string mime_type = "text/html"; + + // we may need a more advanced MIME type accessor later :-) + if ( url.find( ".mov" ) != std::string::npos ) // Movies + mime_type = "video/quicktime"; + else + if ( url.find( ".txt" ) != std::string::npos ) // Apple Text descriptors + mime_type = "video/quicktime"; + else + if ( url.find( ".mp3" ) != std::string::npos ) // Apple Text descriptors + mime_type = "video/quicktime"; + else + if ( url.find( "example://" ) != std::string::npos ) // Example plugin + mime_type = "example/example"; + + return mime_type; +} + +//////////////////////////////////////////////////////////////////////////////// +// +std::string LLFBConnectTest::pluginNameFromMimeType( std::string& mime_type ) +{ +#if LL_DARWIN + std::string plugin_name( "media_plugin_null.dylib" ); + if ( mime_type == "video/quicktime" ) + plugin_name = "media_plugin_quicktime.dylib"; + else + if ( mime_type == "text/html" ) + plugin_name = "media_plugin_webkit.dylib"; + +#elif LL_WINDOWS + std::string plugin_name( "media_plugin_null.dll" ); + + if ( mime_type == "video/quicktime" ) + plugin_name = "media_plugin_quicktime.dll"; + else + if ( mime_type == "text/html" ) + plugin_name = "media_plugin_webkit.dll"; + else + if ( mime_type == "example/example" ) + plugin_name = "media_plugin_example.dll"; + +#elif LL_LINUX + std::string plugin_name( "libmedia_plugin_null.so" ); + + if ( mime_type == "video/quicktime" ) + plugin_name = "libmedia_plugin_quicktime.so"; + else + if ( mime_type == "text/html" ) + plugin_name = "libmedia_plugin_webkit.so"; +#endif + return plugin_name; +} + +//////////////////////////////////////////////////////////////////////////////// +// +mediaPanel* LLFBConnectTest::addMediaPanel( std::string url ) +{ + // Get the plugin filename using the URL + std::string mime_type = mimeTypeFromUrl( url ); + std::string plugin_name = pluginNameFromMimeType( mime_type ); + + // create a random size for the new media + int media_width; + int media_height; + getRandomMediaSize( media_width, media_height, mime_type ); + media_width = 1024; + media_height = 1536; + + // make a new plugin + LLPluginClassMedia* media_source = new LLPluginClassMedia(this); + + // enable cookies so the FB login works + media_source->enable_cookies(true); + + // tell the plugin what size we asked for + media_source->setSize( media_width, media_height ); + + // Use the launcher start and initialize the plugin +#if LL_DARWIN || LL_LINUX + std::string launcher_name( "SLPlugin" ); +#elif LL_WINDOWS + std::string launcher_name( "SLPlugin.exe" ); +#endif + + // for this test app, use the cwd as the user data path (ugh). +#if LL_WINDOWS + std::string user_data_path = ".\\"; +#else + char cwd[ FILENAME_MAX ]; + if (NULL == getcwd( cwd, FILENAME_MAX - 1 )) + { + std::cerr << "Couldn't get cwd - probably too long - failing to init." << std::endl; + return NULL; + } + std::string user_data_path = std::string( cwd ) + "/"; +#endif + media_source->setUserDataPath(user_data_path); + media_source->init( launcher_name, user_data_path, plugin_name, false ); + //media_source->setDisableTimeout(mDisableTimeout); + + // make a new panel and save parameters + mediaPanel* panel = new mediaPanel; + panel->mMediaSource = media_source; + panel->mStartUrl = url; + panel->mMimeType = mime_type; + panel->mMediaWidth = media_width; + panel->mMediaHeight = media_height; + panel->mTextureWidth = 0; + panel->mTextureHeight = 0; + panel->mTextureScaleX = 0; + panel->mTextureScaleY = 0; + panel->mMediaTextureHandle = 0; + panel->mPickTextureHandle = 0; + panel->mAppTextureCoordsOpenGL = false; // really need an 'undefined' state here too + panel->mReadyToRender = false; + + // look through current media panels to find an unused index number + bool id_exists = true; + for( int nid = 0; nid < mMaxPanels; ++nid ) + { + // does this id exist already? + id_exists = false; + for( int pid = 0; pid < (int)mMediaPanels.size(); ++pid ) + { + if ( nid == mMediaPanels[ pid ]->mId ) + { + id_exists = true; + break; + }; + }; + + // id wasn't found so we can use it + if ( ! id_exists ) + { + panel->mId = nid; + break; + }; + }; + + // if we get here and this flag is set, there is no room for any more panels + if ( id_exists ) + { + std::cout << "No room for any more panels" << std::endl; + } + else + { + // now we have the ID we can use it to make the + // pick texture (id is baked into texture pixels) + makePickTexture( panel->mId, &panel->mPickTextureHandle, &panel->mPickTexturePixels ); + + // save this in the list of panels + mMediaPanels.push_back( panel ); + + // select the panel that was just created + selectPanel( panel ); + + // load and start the URL + panel->mMediaSource->loadURI( url ); + panel->mMediaSource->start(); + + std::cout << "Adding new media panel for " << url << "(" << media_width << "x" << media_height << ") with index " << panel->mId << " - total panels = " << mMediaPanels.size() << std::endl; + } + + return panel; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::updateMediaPanel( mediaPanel* panel ) +{ +// checkGLError("LLFBConnectTest::updateMediaPanel"); + + if ( ! panel ) + return; + + if(!panel->mMediaSource || !panel->mMediaSource->textureValid()) + { + panel->mReadyToRender = false; + return; + } + + // take a reference copy of the plugin values since they + // might change during this lifetime of this function + int plugin_media_width = panel->mMediaSource->getWidth(); + int plugin_media_height = panel->mMediaSource->getHeight(); + int plugin_texture_width = panel->mMediaSource->getBitsWidth(); + int plugin_texture_height = panel->mMediaSource->getBitsHeight(); + + // If the texture isn't created or the media or texture dimensions changed AND + // the sizes are valid then we need to delete the old media texture (if necessary) + // then make a new one. + if ((panel->mMediaTextureHandle == 0 || + panel->mMediaWidth != plugin_media_width || + panel->mMediaHeight != plugin_media_height || + panel->mTextureWidth != plugin_texture_width || + panel->mTextureHeight != plugin_texture_height) && + ( plugin_media_width > 0 && plugin_media_height > 0 && + plugin_texture_width > 0 && plugin_texture_height > 0 ) ) + { + std::cout << "Valid media size (" << plugin_media_width << " x " << plugin_media_height + << ") and texture size (" << plugin_texture_width << " x " << plugin_texture_height + << ") for panel with ID=" << panel->mId << " - making texture" << std::endl; + + // delete old GL texture + if ( isTexture( panel->mMediaTextureHandle ) ) + { + std::cerr << "updateMediaPanel: deleting texture " << panel->mMediaTextureHandle << std::endl; + glDeleteTextures( 1, &panel->mMediaTextureHandle ); + panel->mMediaTextureHandle = 0; + } + + std::cerr << "before: pick texture is " << panel->mPickTextureHandle << ", media texture is " << panel->mMediaTextureHandle << std::endl; + + // make a GL texture based on the dimensions the plugin told us + GLuint new_texture = 0; + glGenTextures( 1, &new_texture ); + + checkGLError("glGenTextures"); + + std::cout << "glGenTextures returned " << new_texture << std::endl; + + panel->mMediaTextureHandle = new_texture; + + bindTexture( panel->mMediaTextureHandle ); + + std::cout << "Setting texture size to " << plugin_texture_width << " x " << plugin_texture_height << std::endl; + glTexImage2D( GL_TEXTURE_2D, 0, + GL_RGB, + plugin_texture_width, plugin_texture_height, + 0, GL_RGB, GL_UNSIGNED_BYTE, + 0 ); + + + std::cerr << "after: pick texture is " << panel->mPickTextureHandle << ", media texture is " << panel->mMediaTextureHandle << std::endl; + }; + + // update our record of the media and texture dimensions + // NOTE: do this after we we check for sizes changes + panel->mMediaWidth = plugin_media_width; + panel->mMediaHeight = plugin_media_height; + panel->mTextureWidth = plugin_texture_width; + panel->mTextureHeight = plugin_texture_height; + if ( plugin_texture_width > 0 ) + { + panel->mTextureScaleX = (double)panel->mMediaWidth / (double)panel->mTextureWidth; + }; + if ( plugin_texture_height > 0 ) + { + panel->mTextureScaleY = (double)panel->mMediaHeight / (double)panel->mTextureHeight; + }; + + // update the flag which tells us if the media source uses OprnGL coords or not. + panel->mAppTextureCoordsOpenGL = panel->mMediaSource->getTextureCoordsOpenGL(); + + // Check to see if we have enough to render this panel. + // If we do, set a flag that the display functions use so + // they only render a panel with media if it's ready. + if ( panel->mMediaWidth < 0 || + panel->mMediaHeight < 0 || + panel->mTextureWidth < 1 || + panel->mTextureHeight < 1 || + panel->mMediaTextureHandle == 0 ) + { + panel->mReadyToRender = false; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +mediaPanel* LLFBConnectTest::replaceMediaPanel( mediaPanel* panel, std::string url ) +{ + // no media panels so we can't change anything - have to add + if ( mMediaPanels.size() == 0 ) + return NULL; + + // sanity check + if ( ! panel ) + return NULL; + + int index; + for(index = 0; index < (int)mMediaPanels.size(); index++) + { + if(mMediaPanels[index] == panel) + break; + } + + if(index >= (int)mMediaPanels.size()) + { + // panel isn't in mMediaPanels + return NULL; + } + + std::cout << "Replacing media panel with index " << panel->mId << std::endl; + + int panel_id = panel->mId; + + if(mSelectedPanel == panel) + mSelectedPanel = NULL; + + delete panel; + + // Get the plugin filename using the URL + std::string mime_type = mimeTypeFromUrl( url ); + std::string plugin_name = pluginNameFromMimeType( mime_type ); + + // create a random size for the new media + int media_width; + int media_height; + getRandomMediaSize( media_width, media_height, mime_type ); + + // make a new plugin + LLPluginClassMedia* media_source = new LLPluginClassMedia(this); + + // tell the plugin what size we asked for + media_source->setSize( media_width, media_height ); + + // Use the launcher start and initialize the plugin +#if LL_DARWIN || LL_LINUX + std::string launcher_name( "SLPlugin" ); +#elif LL_WINDOWS + std::string launcher_name( "SLPlugin.exe" ); +#endif + + // for this test app, use the cwd as the user data path (ugh). +#if LL_WINDOWS + std::string user_data_path = ".\\"; +#else + char cwd[ FILENAME_MAX ]; + if (NULL == getcwd( cwd, FILENAME_MAX - 1 )) + { + std::cerr << "Couldn't get cwd - probably too long - failing to init." << std::endl; + return NULL; + } + std::string user_data_path = std::string( cwd ) + "/"; +#endif + + media_source->setUserDataPath(user_data_path); + media_source->init( launcher_name, user_data_path, plugin_name, false ); + //media_source->setDisableTimeout(mDisableTimeout); + + // make a new panel and save parameters + panel = new mediaPanel; + panel->mMediaSource = media_source; + panel->mStartUrl = url; + panel->mMimeType = mime_type; + panel->mMediaWidth = media_width; + panel->mMediaHeight = media_height; + panel->mTextureWidth = 0; + panel->mTextureHeight = 0; + panel->mTextureScaleX = 0; + panel->mTextureScaleY = 0; + panel->mMediaTextureHandle = 0; + panel->mPickTextureHandle = 0; + panel->mAppTextureCoordsOpenGL = false; // really need an 'undefined' state here too + panel->mReadyToRender = false; + + panel->mId = panel_id; + + // Replace the entry in the panels array + mMediaPanels[index] = panel; + + // now we have the ID we can use it to make the + // pick texture (id is baked into texture pixels) + makePickTexture( panel->mId, &panel->mPickTextureHandle, &panel->mPickTexturePixels ); + + // select the panel that was just created + selectPanel( panel ); + + // load and start the URL + panel->mMediaSource->loadURI( url ); + panel->mMediaSource->start(); + + return panel; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::getRandomMediaSize( int& width, int& height, std::string mime_type ) +{ + // Make a new media source with a random size which we'll either + // directly or the media plugin will tell us what it wants later. + // Use a random size so we can test support for weird media sizes. + // (Almost everything else will get filled in later once the + // plugin responds) + // NB. Do we need to enforce that width is on 4 pixel boundary? + width = ( ( rand() % 170 ) + 30 ) * 4; + height = ( ( rand() % 170 ) + 30 ) * 4; + + // adjust this random size if it's a browser so we get + // a more useful size for testing.. + if ( mime_type == "text/html" || mime_type == "example/example" ) + { + width = ( ( rand() % 100 ) + 100 ) * 4; + height = ( width * ( ( rand() % 400 ) + 1000 ) ) / 1000; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::remMediaPanel( mediaPanel* panel ) +{ + // always leave one panel + if ( mMediaPanels.size() == 1 ) + return; + + // sanity check - don't think this can happen but see above for a case where it might... + if ( ! panel ) + return; + + std::cout << "Removing media panel with index " << panel->mId << " - total panels = " << mMediaPanels.size() - 1 << std::endl; + + if(mSelectedPanel == panel) + mSelectedPanel = NULL; + + delete panel; + + // remove from storage list + for( int i = 0; i < (int)mMediaPanels.size(); ++i ) + { + if ( mMediaPanels[ i ] == panel ) + { + mMediaPanels.erase( mMediaPanels.begin() + i ); + break; + }; + }; + + // select the first panel + selectPanel( mMediaPanels[ 0 ] ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::updateStatusBar() +{ + if ( ! mSelectedPanel ) + return; + + // cache results - this is a very slow function + static int cached_id = -1; + static int cached_media_width = -1; + static int cached_media_height = -1; + static int cached_texture_width = -1; + static int cached_texture_height = -1; + static bool cached_supports_browser_media = true; + static bool cached_supports_time_media = false; + static int cached_movie_time = -1; + static GLfloat cached_distance = -1.0f; + + static std::string cached_plugin_version = ""; + if ( + cached_id == mSelectedPanel->mId && + cached_media_width == mSelectedPanel->mMediaWidth && + cached_media_height == mSelectedPanel->mMediaHeight && + cached_texture_width == mSelectedPanel->mTextureWidth && + cached_texture_height == mSelectedPanel->mTextureHeight && + cached_supports_browser_media == mSelectedPanel->mMediaSource->pluginSupportsMediaBrowser() && + cached_supports_time_media == mSelectedPanel->mMediaSource->pluginSupportsMediaTime() && + cached_plugin_version == mSelectedPanel->mMediaSource->getPluginVersion() && + cached_movie_time == (int)mSelectedPanel->mMediaSource->getCurrentTime() && + cached_distance == mDistanceCameraToSelectedGeometry + ) + { + // nothing changed so don't spend time here + return; + }; + + std::ostringstream stream( "" ); + + stream.str( "" ); + stream.clear(); + + stream << "Id: "; + stream << std::setw( 2 ) << std::setfill( '0' ); + stream << mSelectedPanel->mId; + stream << " | "; + stream << "Media: "; + stream << std::setw( 3 ) << std::setfill( '0' ); + stream << mSelectedPanel->mMediaWidth; + stream << " x "; + stream << std::setw( 3 ) << std::setfill( '0' ); + stream << mSelectedPanel->mMediaHeight; + stream << " | "; + stream << "Texture: "; + stream << std::setw( 4 ) << std::setfill( '0' ); + stream << mSelectedPanel->mTextureWidth; + stream << " x "; + stream << std::setw( 4 ) << std::setfill( '0' ); + stream << mSelectedPanel->mTextureHeight; + + stream << " | "; + stream << "Distance: "; + stream << std::setw( 6 ); + stream << std::setprecision( 3 ); + stream << std::setprecision( 3 ); + stream << mDistanceCameraToSelectedGeometry; + stream << " | "; + + if ( mSelectedPanel->mMediaSource->pluginSupportsMediaBrowser() ) + stream << "BROWSER"; + else + if ( mSelectedPanel->mMediaSource->pluginSupportsMediaTime() ) + stream << "TIME "; + stream << " | "; + stream << mSelectedPanel->mMediaSource->getPluginVersion(); + stream << " | "; + if ( mSelectedPanel->mMediaSource->pluginSupportsMediaTime() ) + { + stream << std::setw( 3 ) << std::setfill( '0' ); + stream << (int)mSelectedPanel->mMediaSource->getCurrentTime(); + stream << " / "; + stream << std::setw( 3 ) << std::setfill( '0' ); + stream << (int)mSelectedPanel->mMediaSource->getDuration(); + stream << " @ "; + stream << (int)mSelectedPanel->mMediaSource->getCurrentPlayRate(); + stream << " | "; + }; + + glutSetWindow( mBottomGLUIWindow->get_glut_window_id() ); + mStatusText->set_text( const_cast< char*>( stream.str().c_str() ) ); + glutSetWindow( mAppWindow ); + + // caching + cached_id = mSelectedPanel->mId; + cached_media_width = mSelectedPanel->mMediaWidth; + cached_media_height = mSelectedPanel->mMediaHeight; + cached_texture_width = mSelectedPanel->mTextureWidth; + cached_texture_height = mSelectedPanel->mTextureHeight; + cached_supports_browser_media = mSelectedPanel->mMediaSource->pluginSupportsMediaBrowser(); + cached_supports_time_media = mSelectedPanel->mMediaSource->pluginSupportsMediaTime(); + cached_plugin_version = mSelectedPanel->mMediaSource->getPluginVersion(); + cached_movie_time = (int)mSelectedPanel->mMediaSource->getCurrentTime(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::dumpPanelInfo() +{ + std::cout << std::endl << "===== Media Panels =====" << std::endl; + for( int i = 0; i < (int)mMediaPanels.size(); ++i ) + { + std::cout << std::setw( 2 ) << std::setfill( '0' ); + std::cout << i + 1 << "> "; + std::cout << "Id: "; + std::cout << std::setw( 2 ) << std::setfill( '0' ); + std::cout << mMediaPanels[ i ]->mId; + std::cout << " | "; + std::cout << "Media: "; + std::cout << std::setw( 3 ) << std::setfill( '0' ); + std::cout << mMediaPanels[ i ]->mMediaWidth; + std::cout << " x "; + std::cout << std::setw( 3 ) << std::setfill( '0' ); + std::cout << mMediaPanels[ i ]->mMediaHeight; + std::cout << " | "; + std::cout << "Texture: "; + std::cout << std::setw( 4 ) << std::setfill( '0' ); + std::cout << mMediaPanels[ i ]->mTextureWidth; + std::cout << " x "; + std::cout << std::setw( 4 ) << std::setfill( '0' ); + std::cout << mMediaPanels[ i ]->mTextureHeight; + std::cout << " | "; + if ( mMediaPanels[ i ] == mSelectedPanel ) + std::cout << "(selected)"; + + std::cout << std::endl; + }; + std::cout << "========================" << std::endl; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFBConnectTest::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) +{ + // Uncomment this to make things much, much quieter. +// return; + + switch(event) + { + case MEDIA_EVENT_CONTENT_UPDATED: + // too spammy -- don't log these +// std::cerr << "Media event: MEDIA_EVENT_CONTENT_UPDATED " << std::endl; + break; + + case MEDIA_EVENT_TIME_DURATION_UPDATED: + // too spammy -- don't log these +// std::cerr << "Media event: MEDIA_EVENT_TIME_DURATION_UPDATED, time is " << self->getCurrentTime() << " of " << self->getDuration() << std::endl; + break; + + case MEDIA_EVENT_SIZE_CHANGED: + std::cerr << "Media event: MEDIA_EVENT_SIZE_CHANGED " << std::endl; + break; + + case MEDIA_EVENT_CURSOR_CHANGED: + std::cerr << "Media event: MEDIA_EVENT_CURSOR_CHANGED, new cursor is " << self->getCursorName() << std::endl; + break; + + case MEDIA_EVENT_NAVIGATE_BEGIN: + std::cerr << "Media event: MEDIA_EVENT_NAVIGATE_BEGIN " << std::endl; + break; + + case MEDIA_EVENT_NAVIGATE_COMPLETE: + std::cerr << "Media event: MEDIA_EVENT_NAVIGATE_COMPLETE, result string is: " << self->getNavigateResultString() << std::endl; + break; + + case MEDIA_EVENT_PROGRESS_UPDATED: + std::cerr << "Media event: MEDIA_EVENT_PROGRESS_UPDATED, loading at " << self->getProgressPercent() << "%" << std::endl; + break; + + case MEDIA_EVENT_STATUS_TEXT_CHANGED: + std::cerr << "Media event: MEDIA_EVENT_STATUS_TEXT_CHANGED, new status text is: " << self->getStatusText() << std::endl; + break; + + case MEDIA_EVENT_NAME_CHANGED: + std::cerr << "Media event: MEDIA_EVENT_NAME_CHANGED, new name is: " << self->getMediaName() << std::endl; + glutSetWindowTitle( self->getMediaName().c_str() ); + break; + + case MEDIA_EVENT_LOCATION_CHANGED: + { + std::cerr << "Media event: MEDIA_EVENT_LOCATION_CHANGED, new uri is: " << self->getLocation() << std::endl; + mediaPanel* panel = findMediaPanel(self); + if(panel != NULL) + { + panel->mStartUrl = self->getLocation(); + if(panel == mSelectedPanel) + { + mUrlEdit->set_text(const_cast<char*>(panel->mStartUrl.c_str()) ); + } + } + } + break; + + case MEDIA_EVENT_NAVIGATE_ERROR_PAGE: + std::cerr << "Media event: MEDIA_EVENT_NAVIGATE_ERROR_PAGE, uri is: " << self->getClickURL() << std::endl; + break; + + case MEDIA_EVENT_CLICK_LINK_HREF: + { + std::cerr << "Media event: MEDIA_EVENT_CLICK_LINK_HREF, uri is " << self->getClickURL() << ", target is " << self->getClickTarget() << std::endl; + // retrieve the event parameters + std::string url = self->getClickURL(); + std::string target = self->getClickTarget(); + + if(target == "_external") + { + // this should open in an external browser, but since this is a test app we don't care. + } + else if(target == "_blank") + { + // Create a new panel with the specified URL. + addMediaPanel(url); + } + else // other named target + { + mediaPanel *target_panel = findMediaPanel(target); + if(target_panel) + { + target_panel = replaceMediaPanel(target_panel, url); + } + else + { + target_panel = addMediaPanel(url); + } + + if(target_panel) + { + target_panel->mTarget = target; + } + } + } + break; + + case MEDIA_EVENT_CLICK_LINK_NOFOLLOW: + std::cerr << "Media event: MEDIA_EVENT_CLICK_LINK_NOFOLLOW, uri is " << self->getClickURL() << std::endl; + break; + + case MEDIA_EVENT_PLUGIN_FAILED: + std::cerr << "Media event: MEDIA_EVENT_PLUGIN_FAILED" << std::endl; + break; + + case MEDIA_EVENT_PLUGIN_FAILED_LAUNCH: + std::cerr << "Media event: MEDIA_EVENT_PLUGIN_FAILED_LAUNCH" << std::endl; + break; + + case MEDIA_EVENT_CLOSE_REQUEST: + std::cerr << "Media event: MEDIA_EVENT_CLOSE_REQUEST" << std::endl; + break; + + case MEDIA_EVENT_PICK_FILE_REQUEST: + std::cerr << "Media event: MEDIA_EVENT_PICK_FILE_REQUEST" << std::endl; + // TODO: display an actual file picker + self->sendPickFileResponse("cake"); + break; + + case MEDIA_EVENT_GEOMETRY_CHANGE: + std::cerr << "Media event: MEDIA_EVENT_GEOMETRY_CHANGE, uuid is " << self->getClickUUID() + << ", x = " << self->getGeometryX() + << ", y = " << self->getGeometryY() + << ", width = " << self->getGeometryWidth() + << ", height = " << self->getGeometryHeight() + << std::endl; + break; + + case MEDIA_EVENT_AUTH_REQUEST: + { + //std::cerr << "Media event: MEDIA_EVENT_AUTH_REQUEST, url " << self->getAuthURL() ", realm " << self->getAuthRealm() << std::endl; + + // TODO: display an auth dialog + self->sendAuthResponse(false, "", ""); + } + break; + + case MEDIA_EVENT_LINK_HOVERED: + { + std::cerr << "Media event: MEDIA_EVENT_LINK_HOVERED, hover text is: " << self->getHoverText() << std::endl; + } + break; + + default: + { + std::cerr << "Media event: <unknown>, code is: " << int(event) << std::endl; + } + break; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +static void gluiCallbackWrapper( int control_id ) +{ + if ( gApplication ) + gApplication->gluiCallback( control_id ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void glutReshape( int width, int height ) +{ + if ( gApplication ) + gApplication->reshape( width, height ); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void glutDisplay() +{ + if ( gApplication ) + gApplication->display(); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void glutIdle(int update_ms) +{ + GLUI_Master.set_glutTimerFunc( update_ms, glutIdle, update_ms); + + if ( gApplication ) + gApplication->idle(); + +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void glutKeyboard( unsigned char key, int x, int y ) +{ + if ( gApplication ) + gApplication->keyboard( key ); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void glutMousePassive( int x, int y ) +{ + if ( gApplication ) + gApplication->mousePassive( x, y ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void glutMouseMove( int x , int y ) +{ + if ( gApplication ) + gApplication->mouseMove( x, y ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void glutMouseButton( int button, int state, int x, int y ) +{ + if ( gApplication ) + gApplication->mouseButton( button, state, x, y ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +int main( int argc, char* argv[] ) +{ +#if LL_DARWIN + // Set the current working directory to <application bundle>/Contents/Resources/ + CFURLRef resources_url = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle()); + if(resources_url != NULL) + { + CFStringRef resources_string = CFURLCopyFileSystemPath(resources_url, kCFURLPOSIXPathStyle); + CFRelease(resources_url); + if(resources_string != NULL) + { + char buffer[PATH_MAX] = ""; + if(CFStringGetCString(resources_string, buffer, sizeof(buffer), kCFStringEncodingUTF8)) + { + chdir(buffer); + } + CFRelease(resources_string); + } + } +#endif + + glutInit( &argc, argv ); + glutInitDisplayMode( GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB ); + + const int app_window_x = 80; + const int app_window_y = 0; + const int app_window_width = 960; + const int app_window_height = 960; + + glutInitWindowPosition( app_window_x, app_window_y ); + glutInitWindowSize( app_window_width, app_window_height ); + + int app_window_handle = glutCreateWindow( "LLFBConnectTest" ); + + glutDisplayFunc( glutDisplay ); + + GLUI_Master.set_glutReshapeFunc( glutReshape ); + GLUI_Master.set_glutKeyboardFunc( glutKeyboard ); + GLUI_Master.set_glutMouseFunc( glutMouseButton ); + + glutPassiveMotionFunc( glutMousePassive ); + glutMotionFunc( glutMouseMove ); + + glutSetWindow( app_window_handle ); + + gApplication = new LLFBConnectTest( app_window_handle, app_window_width, app_window_height ); + + // update at approximately 60hz + int update_ms = 1000 / 60; + + GLUI_Master.set_glutTimerFunc( update_ms, glutIdle, update_ms); + + glutMainLoop(); + + delete gApplication; +} diff --git a/indra/test_apps/llfbconnecttest/llfbconnecttest.h b/indra/test_apps/llfbconnecttest/llfbconnecttest.h new file mode 100644 index 0000000000..6f442a55b3 --- /dev/null +++ b/indra/test_apps/llfbconnecttest/llfbconnecttest.h @@ -0,0 +1,207 @@ +/** + * @file LLFBConnectTest.cpp + * @brief Facebook Connect Test App + * + * $LicenseInfo:firstyear=2008&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_FB_CONNECT_H +#define LL_FB_CONNECT_H + +#include <vector> +#include <string> +#include "llpluginclassmedia.h" +#include "llgl.h" + +// Forward declarations +class GLUI_Rotation; +class GLUI_Translation; +class GLUI_Listbox; +class GLUI_EditText; +class GLUI_StaticText; +class GLUI; +class GLUI_Button; + +//////////////////////////////////////////////////////////////////////////////// +// +struct mediaPanel +{ + public: + mediaPanel(); + ~mediaPanel(); + int mId; + std::string mStartUrl; + std::string mMimeType; + std::string mTarget; + LLPluginClassMedia *mMediaSource; + int mMediaWidth; + int mMediaHeight; + int mTextureWidth; + int mTextureHeight; + double mTextureScaleX; + double mTextureScaleY; + GLuint mMediaTextureHandle; + GLuint mPickTextureHandle; + unsigned char* mPickTexturePixels; + bool mAppTextureCoordsOpenGL; + bool mReadyToRender; +}; + +//////////////////////////////////////////////////////////////////////////////// +// +class LLFBConnectTest : public LLPluginClassMediaOwner +{ + public: + LLFBConnectTest( int app_window, int window_width, int window_height ); + ~LLFBConnectTest(); + + void reshape( int width, int height ); + void display(); + void idle(); + void gluiCallback( int control_id ); + void keyboard( int key ); + void mousePassive( int x, int y ); + void mouseButton( int button, int state, int x, int y ); + void mouseMove( int x, int y ); + + void bindTexture(GLuint texture, GLint row_length = 0, GLint alignment = 1); + bool checkGLError(const char *name = "OpenGL"); + void drawGeometry( int panel, bool selected ); + void startPanelHighlight( float red, float green, float blue, float line_width ); + void endPanelHighlight(); + enum { DrawTypePickTexture, DrawTypeMediaTexture }; + void draw( int draw_type ); + void windowPosToTexturePos( int window_x, int window_y, int& media_x, int& media_y, int& id ); + + mediaPanel* addMediaPanel( std::string url ); + void updateMediaPanel( mediaPanel* panel ); + void remMediaPanel( mediaPanel* panel ); + mediaPanel* replaceMediaPanel( mediaPanel* panel, std::string url ); + void getRandomMediaSize( int& width, int& height, std::string mime_type ); + void navigateToNewURI( std::string uri ); + void initUrlHistory( std::string uri ); + void selectPanelById( int id ); + void selectPanel( mediaPanel* panel ); + mediaPanel* findMediaPanel( LLPluginClassMedia* panel ); + mediaPanel* findMediaPanel( const std::string &target_name ); + void makePickTexture( int id, GLuint* texture_handle, unsigned char** texture_pixels ); + void makeChrome(); + void resetView(); + + void dumpPanelInfo(); + void updateStatusBar(); + + GLfloat distanceToCamera( GLfloat point_x, GLfloat point_y, GLfloat point_z ); + + + // Inherited from LLPluginClassMediaOwner + /*virtual*/ void handleMediaEvent(LLPluginClassMedia* self, LLPluginClassMediaOwner::EMediaEvent); + + private: + const int mVersionMajor; + const int mVersionMinor; + const int mVersionPatch; + const int mMaxPanels; + int mAppWindow; + int mWindowWidth; + int mWindowHeight; + int mCurMouseX; + int mCurMouseY; + unsigned char mPixelReadColor[ 3 ]; + bool mFuzzyMedia; + const std::string mHomeWebUrl; + + std::vector< mediaPanel* > mMediaPanels; + mediaPanel* mSelectedPanel; + std::string mimeTypeFromUrl( std::string& url ); + std::string pluginNameFromMimeType( std::string& mime_type ); + + GLUI_Rotation* mViewRotationCtrl; + GLUI_Translation* mViewScaleCtrl; + GLUI_Translation* mViewTranslationCtrl; + float mViewportAspect; + float mViewPos[ 3 ]; + float mViewRotation[ 16 ]; + + float mDistanceCameraToSelectedGeometry; + + int mIdControlAddPanel; + int mIdControlRemPanel; + + std::vector< std::pair< std::string, std::string > > mBookmarks; + GLUI_Listbox* mBookmarkList; + int mIdBookmarks; + int mIdUrlEdit; + GLUI_EditText* mUrlEdit; + //int mIdUrlInitHistoryEdit; + //GLUI_EditText* mUrlInitHistoryEdit; + int mSelBookmark; + //int mIdRandomPanelCount; + //int mRandomPanelCount; + //int mIdRandomBookmarks; + //int mRandomBookmarks; + //int mIdDisableTimeout; + //int mDisableTimeout; + //int mIdUsePluginReadThread; + //int mUsePluginReadThread; + //int mIdLargePanelSpacing; + //int mLargePanelSpacing; + //int mIdControlCrashPlugin; + //int mIdControlHangPlugin; + int mIdControlExitApp; + + //GLUI* mGluiMediaTimeControlWindow; + //int mIdMediaTimeControlPlay; + //int mIdMediaTimeControlLoop; + //int mIdMediaTimeControlPause; + //int mIdMediaTimeControlStop; + //int mIdMediaTimeControlSeek; + //int mIdMediaTimeControlVolume; + //int mMediaTimeControlVolume; + //int mIdMediaTimeControlSeekSeconds; + //int mMediaTimeControlSeekSeconds; + //int mIdMediaTimeControlRewind; + //int mIdMediaTimeControlFastForward; + + GLUI* mGluiMediaBrowserControlWindow; + int mIdMediaBrowserControlBack; + GLUI_Button* mMediaBrowserControlBackButton; + int mIdMediaBrowserControlStop; + int mIdMediaBrowserControlForward; + GLUI_Button* mMediaBrowserControlForwardButton; + bool mGluiMediaTimeControlWindowFlag; + bool mGluiMediaBrowserControlWindowFlag; + bool mMediaBrowserControlBackButtonFlag; + bool mMediaBrowserControlForwardButtonFlag; + int mIdMediaBrowserControlHome; + int mIdMediaBrowserControlReload; + int mIdMediaBrowserControlClearCache; + int mIdMediaBrowserControlClearCookies; + int mIdMediaBrowserControlEnableCookies; + int mMediaBrowserControlEnableCookies; + + GLUI* mBottomGLUIWindow; + GLUI_StaticText* mStatusText; +}; + +#endif // LL_FB_CONNECT_H + diff --git a/indra/viewer_components/updater/llupdatechecker.cpp b/indra/viewer_components/updater/llupdatechecker.cpp index 970b55c1c7..8da4f88905 100755 --- a/indra/viewer_components/updater/llupdatechecker.cpp +++ b/indra/viewer_components/updater/llupdatechecker.cpp @@ -79,7 +79,6 @@ void LLUpdateChecker::checkVersion(std::string const & urlBase, //----------------------------------------------------------------------------- -const char * LLUpdateChecker::Implementation::sLegacyProtocolVersion = "v1.0"; const char * LLUpdateChecker::Implementation::sProtocolVersion = "v1.1"; @@ -151,40 +150,11 @@ void LLUpdateChecker::Implementation::httpCompleted() server_error += content["error_text"].asString(); } - if (status == 404) - { - if (mProtocol == sProtocolVersion) - { - mProtocol = sLegacyProtocolVersion; - std::string retryUrl = buildUrl(mUrlBase, mChannel, mVersion, mPlatform, mPlatformVersion, mUniqueId, mWillingToTest); - - LL_WARNS("UpdaterService") - << "update response using " << sProtocolVersion - << " was HTTP 404 (" << server_error - << "); retry with legacy protocol " << mProtocol - << "\n at " << retryUrl - << LL_ENDL; - - mHttpClient.get(retryUrl, this); - } - else - { - LL_WARNS("UpdaterService") - << "update response using " << sLegacyProtocolVersion - << " was 404 (" << server_error - << "); request failed" - << LL_ENDL; - mClient.error(reason); - } - } - else - { - LL_WARNS("UpdaterService") << "response error " << status - << " " << reason - << " (" << server_error << ")" - << LL_ENDL; - mClient.error(reason); - } + LL_WARNS("UpdaterService") << "response error " << status + << " " << reason + << " (" << server_error << ")" + << LL_ENDL; + mClient.error(reason); } else { @@ -215,11 +185,8 @@ std::string LLUpdateChecker::Implementation::buildUrl(std::string const & urlBas path.append(channel); path.append(version); path.append(platform); - if (mProtocol != sLegacyProtocolVersion) - { - path.append(platform_version); - path.append(willing_to_test ? "testok" : "testno"); - path.append((char*)uniqueid); - } + path.append(platform_version); + path.append(willing_to_test ? "testok" : "testno"); + path.append((char*)uniqueid); return LLURI::buildHTTP(urlBase, path).asString(); } |