diff options
Diffstat (limited to 'indra')
49 files changed, 1953 insertions, 207 deletions
diff --git a/indra/llcommon/llversionviewer.h b/indra/llcommon/llversionviewer.h index fc9fcb2d9e..fc3ce6df7e 100644 --- a/indra/llcommon/llversionviewer.h +++ b/indra/llcommon/llversionviewer.h @@ -35,8 +35,8 @@  const S32 LL_VERSION_MAJOR = 2;  const S32 LL_VERSION_MINOR = 0; -const S32 LL_VERSION_PATCH = 1; -const S32 LL_VERSION_BUILD = 203110; +const S32 LL_VERSION_PATCH = 2; +const S32 LL_VERSION_BUILD = 0;  const char * const LL_CHANNEL = "Second Life Developer"; diff --git a/indra/llplugin/llpluginmessagepipe.cpp b/indra/llplugin/llpluginmessagepipe.cpp index e524c88cf8..89f8b44569 100644 --- a/indra/llplugin/llpluginmessagepipe.cpp +++ b/indra/llplugin/llpluginmessagepipe.cpp @@ -96,11 +96,14 @@ void LLPluginMessagePipeOwner::killMessagePipe(void)  	}  } -LLPluginMessagePipe::LLPluginMessagePipe(LLPluginMessagePipeOwner *owner, LLSocket::ptr_t socket) +LLPluginMessagePipe::LLPluginMessagePipe(LLPluginMessagePipeOwner *owner, LLSocket::ptr_t socket): +	mInputMutex(gAPRPoolp), +	mOutputMutex(gAPRPoolp), +	mOwner(owner), +	mSocket(socket)  { -	mOwner = owner; +	  	mOwner->setMessagePipe(this); -	mSocket = socket;  }  LLPluginMessagePipe::~LLPluginMessagePipe() @@ -114,6 +117,7 @@ LLPluginMessagePipe::~LLPluginMessagePipe()  bool LLPluginMessagePipe::addMessage(const std::string &message)  {  	// queue the message for later output +	LLMutexLock lock(&mOutputMutex);  	mOutput += message;  	mOutput += MESSAGE_DELIMITER;	// message separator @@ -149,6 +153,18 @@ void LLPluginMessagePipe::setSocketTimeout(apr_interval_time_t timeout_usec)  bool LLPluginMessagePipe::pump(F64 timeout)  { +	bool result = pumpOutput(); +	 +	if(result) +	{ +		result = pumpInput(timeout); +	} +	 +	return result; +} + +bool LLPluginMessagePipe::pumpOutput() +{  	bool result = true;  	if(mSocket) @@ -156,6 +172,7 @@ bool LLPluginMessagePipe::pump(F64 timeout)  		apr_status_t status;  		apr_size_t size; +		LLMutexLock lock(&mOutputMutex);  		if(!mOutput.empty())  		{  			// write any outgoing messages @@ -183,6 +200,17 @@ bool LLPluginMessagePipe::pump(F64 timeout)  				// remove the written part from the buffer and try again later.  				mOutput = mOutput.substr(size);  			} +			else if(APR_STATUS_IS_EOF(status)) +			{ +				// This is what we normally expect when a plugin exits. +				llinfos << "Got EOF from plugin socket. " << llendl; + +				if(mOwner) +				{ +					mOwner->socketError(status); +				} +				result = false; +			}  			else   			{  				// some other error @@ -196,6 +224,19 @@ bool LLPluginMessagePipe::pump(F64 timeout)  				result = false;  			}  		} +	} +	 +	return result; +} + +bool LLPluginMessagePipe::pumpInput(F64 timeout) +{ +	bool result = true; + +	if(mSocket) +	{ +		apr_status_t status; +		apr_size_t size;  		// FIXME: For some reason, the apr timeout stuff isn't working properly on windows.  		// Until such time as we figure out why, don't try to use the socket timeout -- just sleep here instead. @@ -216,8 +257,16 @@ bool LLPluginMessagePipe::pump(F64 timeout)  			char input_buf[1024];  			apr_size_t request_size; -			// Start out by reading one byte, so that any data received will wake us up. -			request_size = 1; +			if(timeout == 0.0f) +			{ +				// If we have no timeout, start out with a full read. +				request_size = sizeof(input_buf); +			} +			else +			{ +				// Start out by reading one byte, so that any data received will wake us up. +				request_size = 1; +			}  			// and use the timeout so we'll sleep if no data is available.  			setSocketTimeout((apr_interval_time_t)(timeout * 1000000)); @@ -236,11 +285,14 @@ bool LLPluginMessagePipe::pump(F64 timeout)  //				LL_INFOS("Plugin") << "after apr_socket_recv, size = " << size << LL_ENDL;  				if(size > 0) +				{ +					LLMutexLock lock(&mInputMutex);  					mInput.append(input_buf, size); +				}  				if(status == APR_SUCCESS)  				{ -//					llinfos << "success, read " << size << llendl; +					LL_DEBUGS("PluginSocket") << "success, read " << size << LL_ENDL;  					if(size != request_size)  					{ @@ -250,16 +302,28 @@ bool LLPluginMessagePipe::pump(F64 timeout)  				}  				else if(APR_STATUS_IS_TIMEUP(status))  				{ -//					llinfos << "TIMEUP, read " << size << llendl; +					LL_DEBUGS("PluginSocket") << "TIMEUP, read " << size << LL_ENDL;  					// Timeout was hit.  Since the initial read is 1 byte, this should never be a partial read.  					break;  				}  				else if(APR_STATUS_IS_EAGAIN(status))  				{ -//					llinfos << "EAGAIN, read " << size << llendl; +					LL_DEBUGS("PluginSocket") << "EAGAIN, read " << size << LL_ENDL; -					// We've been doing partial reads, and we're done now. +					// Non-blocking read returned immediately. +					break; +				} +				else if(APR_STATUS_IS_EOF(status)) +				{ +					// This is what we normally expect when a plugin exits. +					LL_INFOS("PluginSocket") << "Got EOF from plugin socket. " << LL_ENDL; + +					if(mOwner) +					{ +						mOwner->socketError(status); +					} +					result = false;  					break;  				}  				else @@ -276,22 +340,18 @@ bool LLPluginMessagePipe::pump(F64 timeout)  					break;  				} -				// Second and subsequent reads should not use the timeout -				setSocketTimeout(0); -				// and should try to fill the input buffer -				request_size = sizeof(input_buf); +				if(timeout != 0.0f) +				{ +					// Second and subsequent reads should not use the timeout +					setSocketTimeout(0); +					// and should try to fill the input buffer +					request_size = sizeof(input_buf); +				}  			}  			processInput();  		}  	} - -	if(!result) -	{ -		// If we got an error, we're done. -		LL_INFOS("Plugin") << "Error from socket, cleaning up." << LL_ENDL; -		delete this; -	}  	return result;	  } @@ -300,6 +360,7 @@ void LLPluginMessagePipe::processInput(void)  {  	// Look for input delimiter(s) in the input buffer.  	int delim; +	mInputMutex.lock();  	while((delim = mInput.find(MESSAGE_DELIMITER)) != std::string::npos)  	{	  		// Let the owner process this message @@ -310,12 +371,15 @@ void LLPluginMessagePipe::processInput(void)  			// and this guarantees that the messages will get dequeued correctly.  			std::string message(mInput, 0, delim);  			mInput.erase(0, delim + 1); +			mInputMutex.unlock();  			mOwner->receiveMessageRaw(message); +			mInputMutex.lock();  		}  		else  		{  			LL_WARNS("Plugin") << "!mOwner" << LL_ENDL;  		}  	} +	mInputMutex.unlock();  } diff --git a/indra/llplugin/llpluginmessagepipe.h b/indra/llplugin/llpluginmessagepipe.h index 1ddb38de68..1b0a08254b 100644 --- a/indra/llplugin/llpluginmessagepipe.h +++ b/indra/llplugin/llpluginmessagepipe.h @@ -35,6 +35,7 @@  #define LL_LLPLUGINMESSAGEPIPE_H  #include "lliosocket.h" +#include "llthread.h"  class LLPluginMessagePipe; @@ -51,7 +52,7 @@ public:  	virtual apr_status_t socketError(apr_status_t error);  	// called from LLPluginMessagePipe to manage the connection with LLPluginMessagePipeOwner -- do not use! -	virtual void setMessagePipe(LLPluginMessagePipe *message_pipe) ; +	virtual void setMessagePipe(LLPluginMessagePipe *message_pipe);  protected:  	// returns false if writeMessageRaw() would drop the message @@ -76,14 +77,18 @@ public:  	void clearOwner(void);  	bool pump(F64 timeout = 0.0f); -	 +	bool pumpOutput(); +	bool pumpInput(F64 timeout = 0.0f); +		  protected:	  	void processInput(void);  	// used internally by pump()  	void setSocketTimeout(apr_interval_time_t timeout_usec); +	LLMutex mInputMutex;  	std::string mInput; +	LLMutex mOutputMutex;  	std::string mOutput;  	LLPluginMessagePipeOwner *mOwner; diff --git a/indra/llplugin/llpluginprocesschild.cpp b/indra/llplugin/llpluginprocesschild.cpp index 2d078cd6ed..d1cf91b253 100644 --- a/indra/llplugin/llpluginprocesschild.cpp +++ b/indra/llplugin/llpluginprocesschild.cpp @@ -85,9 +85,14 @@ void LLPluginProcessChild::idle(void)  	bool idle_again;  	do  	{ -		if(mSocketError != APR_SUCCESS) +		if(APR_STATUS_IS_EOF(mSocketError))  		{ -			LL_INFOS("Plugin") << "message pipe is in error state, moving to STATE_ERROR"<< LL_ENDL; +			// Plugin socket was closed.  This covers both normal plugin termination and host crashes. +			setState(STATE_ERROR); +		} +		else if(mSocketError != APR_SUCCESS) +		{ +			LL_INFOS("Plugin") << "message pipe is in error state (" << mSocketError << "), moving to STATE_ERROR"<< LL_ENDL;  			setState(STATE_ERROR);  		}	 diff --git a/indra/llplugin/llpluginprocessparent.cpp b/indra/llplugin/llpluginprocessparent.cpp index e273410a1d..3589b22a77 100644 --- a/indra/llplugin/llpluginprocessparent.cpp +++ b/indra/llplugin/llpluginprocessparent.cpp @@ -45,8 +45,51 @@ LLPluginProcessParentOwner::~LLPluginProcessParentOwner()  } -LLPluginProcessParent::LLPluginProcessParent(LLPluginProcessParentOwner *owner) +bool LLPluginProcessParent::sUseReadThread = false; +apr_pollset_t *LLPluginProcessParent::sPollSet = NULL; +bool LLPluginProcessParent::sPollsetNeedsRebuild = false; +LLMutex *LLPluginProcessParent::sInstancesMutex; +std::list<LLPluginProcessParent*> LLPluginProcessParent::sInstances; +LLThread *LLPluginProcessParent::sReadThread = NULL; + + +class LLPluginProcessParentPollThread: public LLThread  { +public: +	LLPluginProcessParentPollThread() : +		LLThread("LLPluginProcessParentPollThread", gAPRPoolp) +	{ +	} +protected: +	// Inherited from LLThread +	/*virtual*/ void run(void) +	{ +		while(!isQuitting() && LLPluginProcessParent::getUseReadThread()) +		{ +			LLPluginProcessParent::poll(0.1f); +			checkPause(); +		} +		 +		// Final poll to clean up the pollset, etc. +		LLPluginProcessParent::poll(0.0f); +	}  + +	// Inherited from LLThread +	/*virtual*/ bool runCondition(void) +	{ +		return(LLPluginProcessParent::canPollThreadRun()); +	} + +}; + +LLPluginProcessParent::LLPluginProcessParent(LLPluginProcessParentOwner *owner): +	mIncomingQueueMutex(gAPRPoolp) +{ +	if(!sInstancesMutex) +	{ +		sInstancesMutex = new LLMutex(gAPRPoolp); +	} +	  	mOwner = owner;  	mBoundPort = 0;  	mState = STATE_UNINITIALIZED; @@ -55,18 +98,36 @@ LLPluginProcessParent::LLPluginProcessParent(LLPluginProcessParentOwner *owner)  	mDisableTimeout = false;  	mDebug = false;  	mBlocked = false; +	mPolledInput = false; +	mPollFD.client_data = NULL;  	mPluginLaunchTimeout = 60.0f;  	mPluginLockupTimeout = 15.0f;  	// Don't start the timer here -- start it when we actually launch the plugin process.  	mHeartbeat.stop(); +	 +	// Don't add to the global list until fully constructed. +	{ +		LLMutexLock lock(sInstancesMutex); +		sInstances.push_back(this); +	}  }  LLPluginProcessParent::~LLPluginProcessParent()  {  	LL_DEBUGS("Plugin") << "destructor" << LL_ENDL; +	// Remove from the global list before beginning destruction. +	{ +		// Make sure to get the global mutex _first_ here, to avoid a possible deadlock against LLPluginProcessParent::poll() +		LLMutexLock lock(sInstancesMutex); +		{ +			LLMutexLock lock2(&mIncomingQueueMutex); +			sInstances.remove(this); +		} +	} +  	// Destroy any remaining shared memory regions  	sharedMemoryRegionsType::iterator iter;  	while((iter = mSharedMemoryRegions.begin()) != mSharedMemoryRegions.end()) @@ -78,15 +139,17 @@ LLPluginProcessParent::~LLPluginProcessParent()  		mSharedMemoryRegions.erase(iter);  	} -	// orphaning the process means it won't be killed when the LLProcessLauncher is destructed. -	// This is what we want -- it should exit cleanly once it notices the sockets have been closed. -	mProcess.orphan(); +	mProcess.kill();  	killSockets();  }  void LLPluginProcessParent::killSockets(void)  { -	killMessagePipe(); +	{ +		LLMutexLock lock(&mIncomingQueueMutex); +		killMessagePipe(); +	} +  	mListenSocket.reset();  	mSocket.reset();  } @@ -160,21 +223,47 @@ void LLPluginProcessParent::idle(void)  	do  	{ +		// process queued messages +		mIncomingQueueMutex.lock(); +		while(!mIncomingQueue.empty()) +		{ +			LLPluginMessage message = mIncomingQueue.front(); +			mIncomingQueue.pop(); +			mIncomingQueueMutex.unlock(); +				 +			receiveMessage(message); +			 +			mIncomingQueueMutex.lock(); +		} + +		mIncomingQueueMutex.unlock(); +		  		// Give time to network processing  		if(mMessagePipe)  		{ -			if(!mMessagePipe->pump()) +			// Drain any queued outgoing messages +			mMessagePipe->pumpOutput(); +			 +			// Only do input processing here if this instance isn't in a pollset. +			if(!mPolledInput)  			{ -//				LL_WARNS("Plugin") << "Message pipe hit an error state" << LL_ENDL; -				errorState(); +				mMessagePipe->pumpInput();  			}  		} - -		if((mSocketError != APR_SUCCESS) && (mState <= STATE_RUNNING)) +		 +		if(mState <= STATE_RUNNING)  		{ -			// The socket is in an error state -- the plugin is gone. -			LL_WARNS("Plugin") << "Socket hit an error state (" << mSocketError << ")" << LL_ENDL; -			errorState(); +			if(APR_STATUS_IS_EOF(mSocketError)) +			{ +				// Plugin socket was closed.  This covers both normal plugin termination and plugin crashes. +				errorState(); +			} +			else if(mSocketError != APR_SUCCESS) +			{ +				// The socket is in an error state -- the plugin is gone. +				LL_WARNS("Plugin") << "Socket hit an error state (" << mSocketError << ")" << LL_ENDL; +				errorState(); +			}  		}	  		// If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState(). @@ -355,7 +444,7 @@ void LLPluginProcessParent::idle(void)  			break;  			case STATE_HELLO: -				LL_DEBUGS("Plugin") << "received hello message" << llendl; +				LL_DEBUGS("Plugin") << "received hello message" << LL_ENDL;  				// Send the message to load the plugin  				{ @@ -389,7 +478,7 @@ void LLPluginProcessParent::idle(void)  				}  				else if(pluginLockedUp())  				{ -					LL_WARNS("Plugin") << "timeout in exiting state, bailing out" << llendl; +					LL_WARNS("Plugin") << "timeout in exiting state, bailing out" << LL_ENDL;  					errorState();  				}  			break; @@ -411,8 +500,7 @@ void LLPluginProcessParent::idle(void)  			break;  			case STATE_CLEANUP: -				// Don't do a kill here anymore -- closing the sockets is the new 'kill'. -				mProcess.orphan(); +				mProcess.kill();  				killSockets();  				setState(STATE_DONE);  			break; @@ -491,29 +579,317 @@ void LLPluginProcessParent::sendMessage(const LLPluginMessage &message)  	std::string buffer = message.generate();  	LL_DEBUGS("Plugin") << "Sending: " << buffer << LL_ENDL;	  	writeMessageRaw(buffer); +	 +	// Try to send message immediately. +	if(mMessagePipe) +	{ +		mMessagePipe->pumpOutput(); +	} +} + +//virtual  +void LLPluginProcessParent::setMessagePipe(LLPluginMessagePipe *message_pipe) +{ +	bool update_pollset = false; +	 +	if(mMessagePipe) +	{ +		// Unsetting an existing message pipe -- remove from the pollset		 +		mPollFD.client_data = NULL; + +		// pollset needs an update +		update_pollset = true; +	} +	if(message_pipe != NULL) +	{ +		// Set up the apr_pollfd_t +		mPollFD.p = gAPRPoolp; +		mPollFD.desc_type = APR_POLL_SOCKET; +		mPollFD.reqevents = APR_POLLIN|APR_POLLERR|APR_POLLHUP; +		mPollFD.rtnevents = 0; +		mPollFD.desc.s = mSocket->getSocket(); +		mPollFD.client_data = (void*)this;	 +		 +		// pollset needs an update +		update_pollset = true; +	} + +	mMessagePipe = message_pipe; +	 +	if(update_pollset) +	{ +		dirtyPollSet(); +	} +} + +//static  +void LLPluginProcessParent::dirtyPollSet() +{ +	sPollsetNeedsRebuild = true; +	 +	if(sReadThread) +	{ +		LL_DEBUGS("PluginPoll") << "unpausing read thread " << LL_ENDL; +		sReadThread->unpause(); +	} +} + +void LLPluginProcessParent::updatePollset() +{ +	if(!sInstancesMutex) +	{ +		// No instances have been created yet.  There's no work to do. +		return; +	} +		 +	LLMutexLock lock(sInstancesMutex); + +	if(sPollSet) +	{ +		LL_DEBUGS("PluginPoll") << "destroying pollset " << sPollSet << LL_ENDL; +		// delete the existing pollset. +		apr_pollset_destroy(sPollSet); +		sPollSet = NULL; +	} +	 +	std::list<LLPluginProcessParent*>::iterator iter; +	int count = 0; +	 +	// Count the number of instances that want to be in the pollset +	for(iter = sInstances.begin(); iter != sInstances.end(); iter++) +	{ +		(*iter)->mPolledInput = false; +		if((*iter)->mPollFD.client_data) +		{ +			// This instance has a socket that needs to be polled. +			++count; +		} +	} + +	if(sUseReadThread && sReadThread && !sReadThread->isQuitting()) +	{ +		if(!sPollSet && (count > 0)) +		{ +#ifdef APR_POLLSET_NOCOPY +			// The pollset doesn't exist yet.  Create it now. +			apr_status_t status = apr_pollset_create(&sPollSet, count, gAPRPoolp, APR_POLLSET_NOCOPY); +			if(status != APR_SUCCESS) +			{ +#endif // APR_POLLSET_NOCOPY +				LL_WARNS("PluginPoll") << "Couldn't create pollset.  Falling back to non-pollset mode." << LL_ENDL; +				sPollSet = NULL; +#ifdef APR_POLLSET_NOCOPY +			} +			else +			{ +				LL_DEBUGS("PluginPoll") << "created pollset " << sPollSet << LL_ENDL; +				 +				// Pollset was created, add all instances to it. +				for(iter = sInstances.begin(); iter != sInstances.end(); iter++) +				{ +					if((*iter)->mPollFD.client_data) +					{ +						status = apr_pollset_add(sPollSet, &((*iter)->mPollFD)); +						if(status == APR_SUCCESS) +						{ +							(*iter)->mPolledInput = true; +						} +						else +						{ +							LL_WARNS("PluginPoll") << "apr_pollset_add failed with status " << status << LL_ENDL; +						} +					} +				} +			} +#endif // APR_POLLSET_NOCOPY +		} +	} +} + +void LLPluginProcessParent::setUseReadThread(bool use_read_thread) +{ +	if(sUseReadThread != use_read_thread) +	{ +		sUseReadThread = use_read_thread; +		 +		if(sUseReadThread) +		{ +			if(!sReadThread) +			{ +				// start up the read thread +				LL_INFOS("PluginPoll") << "creating read thread " << LL_ENDL; + +				// make sure the pollset gets rebuilt. +				sPollsetNeedsRebuild = true; +				 +				sReadThread = new LLPluginProcessParentPollThread; +				sReadThread->start(); +			} +		} +		else +		{ +			if(sReadThread) +			{ +				// shut down the read thread +				LL_INFOS("PluginPoll") << "destroying read thread " << LL_ENDL; +				delete sReadThread; +				sReadThread = NULL; +			} +		} + +	} +} + +void LLPluginProcessParent::poll(F64 timeout) +{ +	if(sPollsetNeedsRebuild || !sUseReadThread) +	{ +		sPollsetNeedsRebuild = false; +		updatePollset(); +	} +	 +	if(sPollSet) +	{ +		apr_status_t status; +		apr_int32_t count; +		const apr_pollfd_t *descriptors; +		status = apr_pollset_poll(sPollSet, (apr_interval_time_t)(timeout * 1000000), &count, &descriptors); +		if(status == APR_SUCCESS) +		{ +			// One or more of the descriptors signalled.  Call them. +			for(int i = 0; i < count; i++) +			{ +				LLPluginProcessParent *self = (LLPluginProcessParent *)(descriptors[i].client_data); +				// NOTE: the descriptor returned here is actually a COPY of the original (even though we create the pollset with APR_POLLSET_NOCOPY). +				// This means that even if the parent has set its mPollFD.client_data to NULL, the old pointer may still there in this descriptor. +				// It's even possible that the old pointer no longer points to a valid LLPluginProcessParent. +				// This means that we can't safely dereference the 'self' pointer here without some extra steps... +				if(self) +				{ +					// Make sure this pointer is still in the instances list +					bool valid = false; +					{ +						LLMutexLock lock(sInstancesMutex); +						for(std::list<LLPluginProcessParent*>::iterator iter = sInstances.begin(); iter != sInstances.end(); ++iter) +						{ +							if(*iter == self) +							{ +								// Lock the instance's mutex before unlocking the global mutex.   +								// This avoids a possible race condition where the instance gets deleted between this check and the servicePoll() call. +								self->mIncomingQueueMutex.lock(); +								valid = true; +								break; +							} +						} +					} +					 +					if(valid) +					{ +						// The instance is still valid. +						// Pull incoming messages off the socket +						self->servicePoll(); +						self->mIncomingQueueMutex.unlock(); +					} +					else +					{ +						LL_DEBUGS("PluginPoll") << "detected deleted instance " << self << LL_ENDL; +					} + +				} +			} +		} +		else if(APR_STATUS_IS_TIMEUP(status)) +		{ +			// timed out with no incoming data.  Just return. +		} +		else if(status == EBADF) +		{ +			// This happens when one of the file descriptors in the pollset is destroyed, which happens whenever a plugin's socket is closed. +			// The pollset has been or will be recreated, so just return. +			LL_DEBUGS("PluginPoll") << "apr_pollset_poll returned EBADF" << LL_ENDL; +		} +		else if(status != APR_SUCCESS) +		{ +			LL_WARNS("PluginPoll") << "apr_pollset_poll failed with status " << status << LL_ENDL; +		} +	}  } +void LLPluginProcessParent::servicePoll() +{ +	bool result = true; +	 +	// poll signalled on this object's socket.  Try to process incoming messages. +	if(mMessagePipe) +	{ +		result = mMessagePipe->pumpInput(0.0f); +	} + +	if(!result) +	{ +		// If we got a read error on input, remove this pipe from the pollset +		apr_pollset_remove(sPollSet, &mPollFD); + +		// and tell the code not to re-add it +		mPollFD.client_data = NULL; +	} +}  void LLPluginProcessParent::receiveMessageRaw(const std::string &message)  {  	LL_DEBUGS("Plugin") << "Received: " << message << LL_ENDL; - -	// FIXME: should this go into a queue instead?  	LLPluginMessage parsed;  	if(parsed.parse(message) != -1)  	{ -		receiveMessage(parsed); +		if(parsed.hasValue("blocking_request")) +		{ +			mBlocked = true; +		} + +		if(mPolledInput) +		{ +			// This is being called on the polling thread -- only do minimal processing/queueing. +			receiveMessageEarly(parsed); +		} +		else +		{ +			// This is not being called on the polling thread -- do full message processing at this time. +			receiveMessage(parsed); +		}  	}  } -void LLPluginProcessParent::receiveMessage(const LLPluginMessage &message) +void LLPluginProcessParent::receiveMessageEarly(const LLPluginMessage &message)  { -	if(message.hasValue("blocking_request")) +	// NOTE: this function will be called from the polling thread.  It will be called with mIncomingQueueMutex _already locked_.  + +	bool handled = false; +	 +	std::string message_class = message.getClass(); +	if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL) +	{ +		// no internal messages need to be handled early. +	} +	else  	{ -		mBlocked = true; +		// Call out to the owner and see if they to reply +		// TODO: Should this only happen when blocked? +		if(mOwner != NULL) +		{ +			handled = mOwner->receivePluginMessageEarly(message); +		}  	} +	 +	if(!handled) +	{ +		// any message that wasn't handled early needs to be queued. +		mIncomingQueue.push(message); +	} +} +void LLPluginProcessParent::receiveMessage(const LLPluginMessage &message) +{  	std::string message_class = message.getClass();  	if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL)  	{ @@ -704,12 +1080,12 @@ bool LLPluginProcessParent::pluginLockedUpOrQuit()  	if(!mProcess.isRunning())  	{ -		LL_WARNS("Plugin") << "child exited" << llendl; +		LL_WARNS("Plugin") << "child exited" << LL_ENDL;  		result = true;  	}  	else if(pluginLockedUp())  	{ -		LL_WARNS("Plugin") << "timeout" << llendl; +		LL_WARNS("Plugin") << "timeout" << LL_ENDL;  		result = true;  	} diff --git a/indra/llplugin/llpluginprocessparent.h b/indra/llplugin/llpluginprocessparent.h index 31f125bfb3..4dff835b6a 100644 --- a/indra/llplugin/llpluginprocessparent.h +++ b/indra/llplugin/llpluginprocessparent.h @@ -41,12 +41,14 @@  #include "llpluginsharedmemory.h"  #include "lliosocket.h" +#include "llthread.h"  class LLPluginProcessParentOwner  {  public:  	virtual ~LLPluginProcessParentOwner();  	virtual void receivePluginMessage(const LLPluginMessage &message) = 0; +	virtual bool receivePluginMessageEarly(const LLPluginMessage &message) {return false;};  	// This will only be called when the plugin has died unexpectedly   	virtual void pluginLaunchFailed() {};  	virtual void pluginDied() {}; @@ -90,7 +92,9 @@ public:  	void receiveMessage(const LLPluginMessage &message);  	// Inherited from LLPluginMessagePipeOwner -	void receiveMessageRaw(const std::string &message); +	/*virtual*/ void receiveMessageRaw(const std::string &message); +	/*virtual*/ void receiveMessageEarly(const LLPluginMessage &message); +	/*virtual*/ void setMessagePipe(LLPluginMessagePipe *message_pipe) ;  	// This adds a memory segment shared with the client, generating a name for the segment.  The name generated is guaranteed to be unique on the host.  	// The caller must call removeSharedMemory first (and wait until getSharedMemorySize returns 0 for the indicated name) before re-adding a segment with the same name. @@ -113,7 +117,11 @@ public:  	void setLockupTimeout(F32 timeout) { mPluginLockupTimeout = timeout; };  	F64 getCPUUsage() { return mCPUUsage; }; - +	 +	static void poll(F64 timeout); +	static bool canPollThreadRun() { return (sPollSet || sPollsetNeedsRebuild || sUseReadThread); }; +	static void setUseReadThread(bool use_read_thread); +	static bool getUseReadThread() { return sUseReadThread; };  private:  	enum EState @@ -164,12 +172,26 @@ private:  	bool mDisableTimeout;  	bool mDebug;  	bool mBlocked; +	bool mPolledInput;  	LLProcessLauncher mDebugger;  	F32 mPluginLaunchTimeout;		// Somewhat longer timeout for initial launch.  	F32 mPluginLockupTimeout;		// If we don't receive a heartbeat in this many seconds, we declare the plugin locked up. +	static bool sUseReadThread; +	apr_pollfd_t mPollFD; +	static apr_pollset_t *sPollSet; +	static bool sPollsetNeedsRebuild; +	static LLMutex *sInstancesMutex; +	static std::list<LLPluginProcessParent*> sInstances; +	static void dirtyPollSet(); +	static void updatePollset(); +	void servicePoll(); +	static LLThread *sReadThread; +	 +	LLMutex mIncomingQueueMutex; +	std::queue<LLPluginMessage> mIncomingQueue;  };  #endif // LL_LLPLUGINPROCESSPARENT_H diff --git a/indra/llui/llaccordionctrltab.cpp b/indra/llui/llaccordionctrltab.cpp index 3c706ce90e..596da782ce 100644 --- a/indra/llui/llaccordionctrltab.cpp +++ b/indra/llui/llaccordionctrltab.cpp @@ -436,6 +436,34 @@ void LLAccordionCtrlTab::setAccordionView(LLView* panel)  	addChild(panel,0);  } +void LLAccordionCtrlTab::setTitle(const std::string& title) +{ +	LLAccordionCtrlTabHeader* header = findChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME); +	if (header) +	{ +		header->setTitle(title); +	} +} + +boost::signals2::connection LLAccordionCtrlTab::setFocusReceivedCallback(const focus_signal_t::slot_type& cb) +{ +	LLAccordionCtrlTabHeader* header = findChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME); +	if (header) +	{ +		return header->setFocusReceivedCallback(cb); +	} +	return boost::signals2::connection(); +} + +boost::signals2::connection LLAccordionCtrlTab::setFocusLostCallback(const focus_signal_t::slot_type& cb) +{ +	LLAccordionCtrlTabHeader* header = findChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME); +	if (header) +	{ +		return header->setFocusLostCallback(cb); +	} +	return boost::signals2::connection(); +}  LLView*	LLAccordionCtrlTab::findContainerView()  { diff --git a/indra/llui/llaccordionctrltab.h b/indra/llui/llaccordionctrltab.h index fb19d17e99..de254ed3eb 100644 --- a/indra/llui/llaccordionctrltab.h +++ b/indra/llui/llaccordionctrltab.h @@ -113,6 +113,12 @@ public:  	void		setAccordionView(LLView* panel);  	LLView*		getAccordionView() { return mContainerPanel; }; +	// Set text in LLAccordionCtrlTabHeader +	void setTitle(const std::string& title); + +	boost::signals2::connection setFocusReceivedCallback(const focus_signal_t::slot_type& cb); +	boost::signals2::connection setFocusLostCallback(const focus_signal_t::slot_type& cb); +  	bool getCollapsible() {return mCollapsible;};  	void setCollapsible(bool collapsible) {mCollapsible = collapsible;}; diff --git a/indra/media_plugins/webkit/mac_volume_catcher.cpp b/indra/media_plugins/webkit/mac_volume_catcher.cpp index 9788f10a58..38727e5965 100644 --- a/indra/media_plugins/webkit/mac_volume_catcher.cpp +++ b/indra/media_plugins/webkit/mac_volume_catcher.cpp @@ -1,5 +1,5 @@  /**  - * @file dummy_volume_catcher.cpp + * @file mac_volume_catcher.cpp   * @brief A Mac OS X specific hack to control the volume level of all audio channels opened by a process.   *   * @cond @@ -98,7 +98,7 @@ VolumeCatcherImpl *VolumeCatcherImpl::getInstance()  VolumeCatcherImpl::VolumeCatcherImpl()  {  	mVolume = 1.0;	// default to full volume -	mPan = 0.5;		// and center pan +	mPan = 0.0;		// and center pan  	ComponentDescription desc;  	desc.componentType = kAudioUnitType_Output; diff --git a/indra/media_plugins/winmmshim/winmm_shim.cpp b/indra/media_plugins/winmmshim/winmm_shim.cpp index 88c2a28443..6fbf517d7c 100644 --- a/indra/media_plugins/winmmshim/winmm_shim.cpp +++ b/indra/media_plugins/winmmshim/winmm_shim.cpp @@ -144,10 +144,11 @@ extern "C"  						// copy volume level 4 times into 64 bit MMX register  						__m64 volume_64 = _mm_set_pi16(volume_16, volume_16, volume_16, volume_16); -						__m64 *sample_64; +						__m64* sample_64; +						__m64* last_sample_64 =  (__m64*)(pwh->lpData + pwh->dwBufferLength - sizeof(__m64));  						// for everything that can be addressed in 64 bit multiples...  						for (sample_64 = (__m64*)pwh->lpData; -							sample_64 < (__m64*)(pwh->lpData + pwh->dwBufferLength); +							sample_64 <= last_sample_64;  							++sample_64)  						{  							//...multiply the samples by the volume... diff --git a/indra/newview/English.lproj/InfoPlist.strings b/indra/newview/English.lproj/InfoPlist.strings index 02c3dfc6e0..4831dc7273 100644 --- a/indra/newview/English.lproj/InfoPlist.strings +++ b/indra/newview/English.lproj/InfoPlist.strings @@ -2,6 +2,6 @@  CFBundleName = "Second Life"; -CFBundleShortVersionString = "Second Life version 2.0.0.203110"; -CFBundleGetInfoString = "Second Life version 2.0.0.203110, Copyright 2004-2009 Linden Research, Inc."; +CFBundleShortVersionString = "Second Life version 2.0.2.0"; +CFBundleGetInfoString = "Second Life version 2.0.2.0, Copyright 2004-2009 Linden Research, Inc."; diff --git a/indra/newview/Info-SecondLife.plist b/indra/newview/Info-SecondLife.plist index 4cb01a0f33..a7241cac89 100644 --- a/indra/newview/Info-SecondLife.plist +++ b/indra/newview/Info-SecondLife.plist @@ -32,7 +32,7 @@  		</dict>  	</array>  	<key>CFBundleVersion</key> -	<string>2.0.0.203110</string> +	<string>2.0.2.0</string>  	<key>CSResourcesFileMapped</key>  	<true/>  </dict> diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 6f11a6d616..6cc06bab09 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -5585,6 +5585,19 @@        <key>Value</key>        <integer>8</integer>      </map> + +   <key>PluginUseReadThread</key> +    <map> +      <key>Comment</key> +      <string>Use a separate thread to read incoming messages from plugins</string> +      <key>Persist</key> +      <integer>1</integer> +      <key>Type</key> +      <string>Boolean</string> +      <key>Value</key> +      <integer>0</integer> +    </map> +      <key>PrecachingDelay</key>      <map>        <key>Comment</key> @@ -5838,7 +5851,7 @@          <key>Value</key>              <real>1.0</real>          </map> -  <key>MediaRollOffFactor</key> +  <key>MediaRollOffRate</key>    <map>      <key>Comment</key>      <string>Multiplier to change rate of media attenuation</string> @@ -5847,7 +5860,29 @@      <key>Type</key>      <string>F32</string>      <key>Value</key> -    <real>10.0</real> +    <real>0.2</real> +  </map> +  <key>MediaRollOffMin</key> +  <map> +    <key>Comment</key> +    <string>Adjusts the distance at which media attentuation starts</string> +    <key>Persist</key> +    <integer>1</integer> +    <key>Type</key> +    <string>F32</string> +    <key>Value</key> +    <real>7.0</real> +  </map> +  <key>MediaRollOffMax</key> +  <map> +    <key>Comment</key> +    <string>Distance at which media volume is set to 0</string> +    <key>Persist</key> +    <integer>1</integer> +    <key>Type</key> +    <string>F32</string> +    <key>Value</key> +    <real>30.0</real>    </map>    <key>RecentItemsSortOrder</key>      <map> diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 2f9bbb1407..4f7c0c3549 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1530,6 +1530,9 @@ bool LLAppViewer::cleanup()  	LLViewerMedia::saveCookieFile(); +	// Stop the plugin read thread if it's running. +	LLPluginProcessParent::setUseReadThread(false); +  	llinfos << "Shutting down Threads" << llendflush;  	// Let threads finish diff --git a/indra/newview/llchannelmanager.cpp b/indra/newview/llchannelmanager.cpp index 4f9434030f..fafa315a59 100644 --- a/indra/newview/llchannelmanager.cpp +++ b/indra/newview/llchannelmanager.cpp @@ -243,3 +243,19 @@ void LLChannelManager::killToastsFromChannel(const LLUUID& channel_id, const LLS  	}  } +// static +LLNotificationsUI::LLScreenChannel* LLChannelManager::getNotificationScreenChannel() +{ +	LLNotificationsUI::LLScreenChannel* channel = static_cast<LLNotificationsUI::LLScreenChannel*> +	(LLNotificationsUI::LLChannelManager::getInstance()-> +										findChannelByID(LLUUID(gSavedSettings.getString("NotificationChannelUUID")))); + +	if (channel == NULL) +	{ +		llwarns << "Can't find screen channel by NotificationChannelUUID" << llendl; +		llassert(!"Can't find screen channel by NotificationChannelUUID"); +	} + +	return channel; +} + diff --git a/indra/newview/llchannelmanager.h b/indra/newview/llchannelmanager.h index c2be39122f..8c725f2660 100644 --- a/indra/newview/llchannelmanager.h +++ b/indra/newview/llchannelmanager.h @@ -114,6 +114,11 @@ public:  	 */  	void killToastsFromChannel(const LLUUID& channel_id, const LLScreenChannel::Matcher& matcher); +	/** +	 * Returns notification screen channel. +	 */ +	static LLNotificationsUI::LLScreenChannel* getNotificationScreenChannel(); +  private:  	LLScreenChannel* createChannel(LLChannelManager::Params& p); diff --git a/indra/newview/llcofwearables.cpp b/indra/newview/llcofwearables.cpp index e21644e119..b8222ebb18 100644 --- a/indra/newview/llcofwearables.cpp +++ b/indra/newview/llcofwearables.cpp @@ -89,6 +89,7 @@ void LLCOFWearables::onSelectionChange(LLFlatListView* selected_list)  	onCommit();  } +#include "llwearableitemslist.h"  void LLCOFWearables::refresh()  {  	clear(); @@ -117,16 +118,15 @@ void LLCOFWearables::populateAttachmentsAndBodypartsLists(const LLInventoryModel  		const LLAssetType::EType item_type = item->getType();  		if (item_type == LLAssetType::AT_CLOTHING) continue; - -		LLPanelInventoryListItem* item_panel = LLPanelInventoryListItem::createItemPanel(item); -		if (!item_panel) continue; - +		LLPanelInventoryListItemBase* item_panel = NULL;  		if (item_type == LLAssetType::AT_OBJECT)  		{ +				item_panel = LLPanelInventoryListItemBase::create(item);  			mAttachments->addItem(item_panel, item->getUUID(), ADD_BOTTOM, false);  		}  		else if (item_type == LLAssetType::AT_BODYPART)  		{ +			item_panel = LLPanelBodyPartsListItem::create(item);  			mBodyParts->addItem(item_panel, item->getUUID(), ADD_BOTTOM, false);  			addWearableTypeSeparator(mBodyParts);  		} @@ -165,7 +165,7 @@ void LLCOFWearables::populateClothingList(LLAppearanceMgr::wearables_by_type_t&  		{  			LLViewerInventoryItem* item = clothing_by_type[type][i]; -			LLPanelInventoryListItem* item_panel = LLPanelInventoryListItem::createItemPanel(item); +			LLPanelInventoryListItemBase* item_panel = LLPanelClothingListItem::create(item);  			if (!item_panel) continue;  			mClothing->addItem(item_panel, item->getUUID(), ADD_BOTTOM, false); @@ -204,10 +204,10 @@ void LLCOFWearables::addClothingTypesDummies(const LLAppearanceMgr::wearables_by  		U32 size = clothing_by_type[type].size();  		if (size) continue; -		//*TODO create dummy item panel -		 -		//*TODO add dummy item panel -> mClothing->addItem(dummy_item_panel, item->getUUID(), ADD_BOTTOM, false); - +		EWearableType w_type = static_cast<EWearableType>(type); +		LLPanelInventoryListItemBase* item_panel = LLPanelDummyClothingListItem::create(w_type); +		if(!item_panel) continue; +		mClothing->addItem(item_panel, LLUUID::null, ADD_BOTTOM, false);  		addWearableTypeSeparator(mClothing);  	}  } diff --git a/indra/newview/llimfloater.cpp b/indra/newview/llimfloater.cpp index 19dbc564d1..c0cc3f1985 100644 --- a/indra/newview/llimfloater.cpp +++ b/indra/newview/llimfloater.cpp @@ -53,6 +53,7 @@  #include "llsyswellwindow.h"  #include "lltrans.h"  #include "llchathistory.h" +#include "llnotifications.h"  #include "llviewerwindow.h"  #include "llvoicechannel.h"  #include "lltransientfloatermgr.h" @@ -371,6 +372,8 @@ void LLIMFloater::onSlide()  //static  LLIMFloater* LLIMFloater::show(const LLUUID& session_id)  { +	closeHiddenIMToasts(); +  	if (!gIMMgr->hasSession(session_id)) return NULL;  	if(!isChatMultiTab()) @@ -1084,6 +1087,26 @@ void LLIMFloater::removeTypingIndicator(const LLIMInfo* im_info)  }  // static +void LLIMFloater::closeHiddenIMToasts() +{ +	class IMToastMatcher: public LLNotificationsUI::LLScreenChannel::Matcher +	{ +	public: +		bool matches(const LLNotificationPtr notification) const +		{ +			// "notifytoast" type of notifications is reserved for IM notifications +			return "notifytoast" == notification->getType(); +		} +	}; + +	LLNotificationsUI::LLScreenChannel* channel = LLNotificationsUI::LLChannelManager::getNotificationScreenChannel(); +	if (channel != NULL) +	{ +		channel->closeHiddenToasts(IMToastMatcher()); +	} +} + +// static  bool LLIMFloater::isChatMultiTab()  {  	// Restart is required in order to change chat window type. diff --git a/indra/newview/llimfloater.h b/indra/newview/llimfloater.h index 763dd5655b..f9dd8b9b85 100644 --- a/indra/newview/llimfloater.h +++ b/indra/newview/llimfloater.h @@ -148,6 +148,8 @@ private:  	// Remove the "User is typing..." indicator.  	void removeTypingIndicator(const LLIMInfo* im_info = NULL); +	static void closeHiddenIMToasts(); +  	LLPanelChatControlPanel* mControlPanel;  	LLUUID mSessionID;  	S32 mLastMessageIndex; diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index c6f806178c..2d08c0a01a 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -853,21 +853,7 @@ void LLInvFVBridge::changeItemParent(LLInventoryModel* model,  									 const LLUUID& new_parent_id,  									 BOOL restamp)  { -	if (item->getParentUUID() != new_parent_id) -	{ -		LLInventoryModel::update_list_t update; -		LLInventoryModel::LLCategoryUpdate old_folder(item->getParentUUID(),-1); -		update.push_back(old_folder); -		LLInventoryModel::LLCategoryUpdate new_folder(new_parent_id, 1); -		update.push_back(new_folder); -		gInventory.accountForUpdate(update); - -		LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item); -		new_item->setParent(new_parent_id); -		new_item->updateParentOnServer(restamp); -		model->updateItem(new_item); -		model->notifyObservers(); -	} +	change_item_parent(model, item, new_parent_id, restamp);  }  // static diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index 8487588404..8010d1f43d 100644 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -582,3 +582,26 @@ BOOL get_is_item_worn(const LLUUID& id)  	}  	return FALSE;  } + + +void change_item_parent(LLInventoryModel* model, +									 LLViewerInventoryItem* item, +									 const LLUUID& new_parent_id, +									 BOOL restamp) +{ +	if (item->getParentUUID() != new_parent_id) +	{ +		LLInventoryModel::update_list_t update; +		LLInventoryModel::LLCategoryUpdate old_folder(item->getParentUUID(),-1); +		update.push_back(old_folder); +		LLInventoryModel::LLCategoryUpdate new_folder(new_parent_id, 1); +		update.push_back(new_folder); +		gInventory.accountForUpdate(update); + +		LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item); +		new_item->setParent(new_parent_id); +		new_item->updateParentOnServer(restamp); +		model->updateItem(new_item); +		model->notifyObservers(); +	} +} diff --git a/indra/newview/llinventoryfunctions.h b/indra/newview/llinventoryfunctions.h index e3cd988e39..6f373f7392 100644 --- a/indra/newview/llinventoryfunctions.h +++ b/indra/newview/llinventoryfunctions.h @@ -310,6 +310,12 @@ LLUIImagePtr get_item_icon(LLAssetType::EType asset_type,  // Is this item or its baseitem is worn, attached, etc...  BOOL get_is_item_worn(const LLUUID& id); + +void change_item_parent(LLInventoryModel* model, +									 LLViewerInventoryItem* item, +									 const LLUUID& new_parent_id, +									 BOOL restamp); +  #endif // LL_LLINVENTORYFUNCTIONS_H diff --git a/indra/newview/llinventoryitemslist.cpp b/indra/newview/llinventoryitemslist.cpp index dca130c672..8dfdb0788a 100644 --- a/indra/newview/llinventoryitemslist.cpp +++ b/indra/newview/llinventoryitemslist.cpp @@ -50,76 +50,224 @@  ////////////////////////////////////////////////////////////////////////////////  //////////////////////////////////////////////////////////////////////////////// -// static -LLPanelInventoryListItem* LLPanelInventoryListItem::createItemPanel(const LLViewerInventoryItem* item) +static const S32 WIDGET_SPACING = 3; + +LLPanelInventoryListItemBase* LLPanelInventoryListItemBase::create(LLViewerInventoryItem* item)  { +	LLPanelInventoryListItemBase* list_item = NULL;  	if (item)  	{ -		return new LLPanelInventoryListItem(item); +		list_item = new LLPanelInventoryListItemBase(item); +		list_item->init();  	} -	else +	return list_item; +} + +void LLPanelInventoryListItemBase::updateItem() +{ +	setIconImage(mIconImage); +	setTitle(mItem->getName(), mHighlightedText); +} + +void LLPanelInventoryListItemBase::addWidgetToLeftSide(const std::string& name, bool show_widget/* = true*/) +{ +	LLUICtrl* ctrl = findChild<LLUICtrl>(name); +	if(ctrl)  	{ -		return NULL; +		addWidgetToLeftSide(ctrl, show_widget);  	}  } -LLPanelInventoryListItem::~LLPanelInventoryListItem() -{} +void LLPanelInventoryListItemBase::addWidgetToLeftSide(LLUICtrl* ctrl, bool show_widget/* = true*/) +{ +	mLeftSideWidgets.push_back(ctrl); +	setShowWidget(ctrl, show_widget); +} + +void LLPanelInventoryListItemBase::addWidgetToRightSide(const std::string& name, bool show_widget/* = true*/) +{ +	LLUICtrl* ctrl = findChild<LLUICtrl>(name); +	if(ctrl) +	{ +		addWidgetToRightSide(ctrl, show_widget); +	} +} + +void LLPanelInventoryListItemBase::addWidgetToRightSide(LLUICtrl* ctrl, bool show_widget/* = true*/) +{ +	mRightSideWidgets.push_back(ctrl); +	setShowWidget(ctrl, show_widget); +} + +void LLPanelInventoryListItemBase::setShowWidget(const std::string& name, bool show) +{ +	LLUICtrl* widget = findChild<LLUICtrl>(name); +	if(widget) +	{ +		setShowWidget(widget, show); +	} +} + +void LLPanelInventoryListItemBase::setShowWidget(LLUICtrl* ctrl, bool show) +{ +	// Enable state determines whether widget may become visible in setWidgetsVisible() +	ctrl->setEnabled(show); +} -//virtual -BOOL LLPanelInventoryListItem::postBuild() +BOOL LLPanelInventoryListItemBase::postBuild()  { -	mIcon = getChild<LLIconCtrl>("item_icon"); -	mTitle = getChild<LLTextBox>("item_name"); +	setIconCtrl(getChild<LLIconCtrl>("item_icon")); +	setTitleCtrl(getChild<LLTextBox>("item_name")); + +	mIconImage = get_item_icon(mItem->getType(), mItem->getInventoryType(), mItem->getFlags(), FALSE);  	updateItem(); +	setWidgetsVisible(false); +	reshapeWidgets(); +  	return TRUE;  } -//virtual -void LLPanelInventoryListItem::setValue(const LLSD& value) +void LLPanelInventoryListItemBase::setValue(const LLSD& value)  {  	if (!value.isMap()) return;  	if (!value.has("selected")) return;  	childSetVisible("selected_icon", value["selected"]);  } -void LLPanelInventoryListItem::updateItem() +void LLPanelInventoryListItemBase::onMouseEnter(S32 x, S32 y, MASK mask)  { -	if (mItemIcon.notNull()) -		mIcon->setImage(mItemIcon); +	childSetVisible("hovered_icon", true); +	LLPanel::onMouseEnter(x, y, mask); +} +void LLPanelInventoryListItemBase::onMouseLeave(S32 x, S32 y, MASK mask) +{ +	childSetVisible("hovered_icon", false); +	LLPanel::onMouseLeave(x, y, mask); +} + +LLPanelInventoryListItemBase::LLPanelInventoryListItemBase(LLViewerInventoryItem* item) +: LLPanel() +, mItem(item) +, mIconCtrl(NULL) +, mTitleCtrl(NULL) +, mWidgetSpacing(WIDGET_SPACING) +, mLeftWidgetsWidth(0) +, mRightWidgetsWidth(0) +{ +} + +void LLPanelInventoryListItemBase::init() +{ +	LLUICtrlFactory::getInstance()->buildPanel(this, "panel_inventory_item.xml"); +} + +class WidgetVisibilityChanger +{ +public: +	WidgetVisibilityChanger(bool visible) : mVisible(visible){} +	void operator()(LLUICtrl* widget) +	{ +		// Disabled widgets never become visible. see LLPanelInventoryListItemBase::setShowWidget() +		widget->setVisible(mVisible && widget->getEnabled()); +	} +private: +	bool mVisible; +}; + +void LLPanelInventoryListItemBase::setWidgetsVisible(bool visible) +{ +	std::for_each(mLeftSideWidgets.begin(), mLeftSideWidgets.end(), WidgetVisibilityChanger(visible)); +	std::for_each(mRightSideWidgets.begin(), mRightSideWidgets.end(), WidgetVisibilityChanger(visible)); +} + +void LLPanelInventoryListItemBase::reshapeWidgets() +{ +	// disabled reshape left for now to reserve space for 'delete' button in LLPanelClothingListItem +	/*reshapeLeftWidgets();*/ +	reshapeRightWidgets(); +	reshapeMiddleWidgets(); +} + +void LLPanelInventoryListItemBase::setIconImage(const LLUIImagePtr& image) +{ +	if(image) +	{ +		mIconImage = image;  +		mIconCtrl->setImage(mIconImage); +	} +} + +void LLPanelInventoryListItemBase::setTitle(const std::string& title, const std::string& highlit_text) +{  	LLTextUtil::textboxSetHighlightedVal( -		mTitle, +		mTitleCtrl,  		LLStyle::Params(), -		mItemName, -		mHighlightedText); +		title, +		highlit_text);  } -void LLPanelInventoryListItem::onMouseEnter(S32 x, S32 y, MASK mask) +void LLPanelInventoryListItemBase::reshapeLeftWidgets()  { -	childSetVisible("hovered_icon", true); +	S32 widget_left = 0; +	mLeftWidgetsWidth = 0; -	LLPanel::onMouseEnter(x, y, mask); +	widget_array_t::const_iterator it = mLeftSideWidgets.begin(); +	const widget_array_t::const_iterator it_end = mLeftSideWidgets.end(); +	for( ; it_end != it; ++it) +	{ +		LLUICtrl* widget = *it; +		if(!widget->getVisible()) +		{ +			continue; +		} +		LLRect widget_rect(widget->getRect()); +		widget_rect.setLeftTopAndSize(widget_left, widget_rect.mTop, widget_rect.getWidth(), widget_rect.getHeight()); +		widget->setShape(widget_rect); + +		widget_left += widget_rect.getWidth() + getWidgetSpacing(); +		mLeftWidgetsWidth = widget_rect.mRight; +	}  } -void LLPanelInventoryListItem::onMouseLeave(S32 x, S32 y, MASK mask) +void LLPanelInventoryListItemBase::reshapeRightWidgets()  { -	childSetVisible("hovered_icon", false); +	S32 widget_right = getLocalRect().getWidth(); +	S32 widget_left = widget_right; -	LLPanel::onMouseLeave(x, y, mask); +	widget_array_t::const_reverse_iterator it = mRightSideWidgets.rbegin(); +	const widget_array_t::const_reverse_iterator it_end = mRightSideWidgets.rend(); +	for( ; it_end != it; ++it) +	{ +		LLUICtrl* widget = *it; +		if(!widget->getVisible()) +		{ +			continue; +		} +		LLRect widget_rect(widget->getRect()); +		widget_left = widget_right - widget_rect.getWidth(); +		widget_rect.setLeftTopAndSize(widget_left, widget_rect.mTop, widget_rect.getWidth(), widget_rect.getHeight()); +		widget->setShape(widget_rect); + +		widget_right = widget_left - getWidgetSpacing(); +	} +	mRightWidgetsWidth = getLocalRect().getWidth() - widget_left;  } -LLPanelInventoryListItem::LLPanelInventoryListItem(const LLViewerInventoryItem* item) -:	 LLPanel() -	,mIcon(NULL) -	,mTitle(NULL) +void LLPanelInventoryListItemBase::reshapeMiddleWidgets()  { -	mItemName = item->getName(); -	mItemIcon = get_item_icon(item->getType(), item->getInventoryType(), item->getFlags(), FALSE); - -	LLUICtrlFactory::getInstance()->buildPanel(this, "panel_inventory_item.xml"); +	LLRect icon_rect(mIconCtrl->getRect()); +	icon_rect.setLeftTopAndSize(mLeftWidgetsWidth + getWidgetSpacing(), icon_rect.mTop,  +		icon_rect.getWidth(), icon_rect.getHeight()); +	mIconCtrl->setShape(icon_rect); + +	S32 name_left = icon_rect.mRight + getWidgetSpacing(); +	S32 name_right = getLocalRect().getWidth() - mRightWidgetsWidth - getWidgetSpacing(); +	LLRect name_rect(mTitleCtrl->getRect()); +	name_rect.set(name_left, name_rect.mTop, name_right, name_rect.mBottom); +	mTitleCtrl->setShape(name_rect);  }  //////////////////////////////////////////////////////////////////////////////// @@ -220,17 +368,18 @@ void LLInventoryItemsList::addNewItem(LLViewerInventoryItem* item)  	if (!item)  	{  		llwarns << "No inventory item. Couldn't create flat list item." << llendl; -		llassert(!"No inventory item. Couldn't create flat list item."); +		llassert(item != NULL);  	} -	LLPanelInventoryListItem *list_item = LLPanelInventoryListItem::createItemPanel(item); +	LLPanelInventoryListItemBase *list_item = LLPanelInventoryListItemBase::create(item);  	if (!list_item)  		return; -	if (!addItem(list_item, item->getUUID())) +	bool is_item_added = addItem(list_item, item->getUUID()); +	if (!is_item_added)  	{  		llwarns << "Couldn't add flat list item." << llendl; -		llassert(!"Couldn't add flat list item."); +		llassert(is_item_added);  	}  } diff --git a/indra/newview/llinventoryitemslist.h b/indra/newview/llinventoryitemslist.h index b496f4b9e9..152aafbd7e 100644 --- a/indra/newview/llinventoryitemslist.h +++ b/indra/newview/llinventoryitemslist.h @@ -47,33 +47,141 @@ class LLIconCtrl;  class LLTextBox;  class LLViewerInventoryItem; -class LLPanelInventoryListItem : public LLPanel +/** + * @class LLPanelInventoryListItemBase + * + * Base class for Inventory flat list item. Panel consists of inventory icon + * and inventory item name. + * This class is able to display widgets(buttons) on left(before icon) and right(after text-box) sides  + * of panel. + * + * How to use (see LLPanelClothingListItem for example): + * - implement init() to build panel from xml + * - create new xml file, fill it with widgets you want to dynamically show/hide/reshape on left/right sides + * - redefine postBuild()(call base implementation) and add needed widgets to needed sides, + * + */ +class LLPanelInventoryListItemBase : public LLPanel  {  public: -	static LLPanelInventoryListItem* createItemPanel(const LLViewerInventoryItem* item); -	virtual ~LLPanelInventoryListItem(); +	static LLPanelInventoryListItemBase* create(LLViewerInventoryItem* item); +	/** +	 * Called after inventory item was updated, update panel widgets to reflect inventory changes. +	 */ +	virtual void updateItem(); + +	/** +	 * Add widget to left side +	 */ +	void addWidgetToLeftSide(const std::string& name, bool show_widget = true); +	void addWidgetToLeftSide(LLUICtrl* ctrl, bool show_widget = true); + +	/** +	 * Add widget to right side, widget is supposed to be child of calling panel +	 */ +	void addWidgetToRightSide(const std::string& name, bool show_widget = true); +	void addWidgetToRightSide(LLUICtrl* ctrl, bool show_widget = true); + +	/** +	 * Mark widgets as visible. Only visible widgets take part in reshaping children +	 */ +	void setShowWidget(const std::string& name, bool show); +	void setShowWidget(LLUICtrl* ctrl, bool show); + +	/** +	 * Set spacing between widgets during reshape +	 */ +	void setWidgetSpacing(S32 spacing) { mWidgetSpacing = spacing; } + +	S32 getWidgetSpacing() { return mWidgetSpacing; } + +	/** +	 * Inheritors need to call base implementation of postBuild() +	 */  	/*virtual*/ BOOL postBuild(); + +	/** +	 * Handles item selection +	 */  	/*virtual*/ void setValue(const LLSD& value); -	void updateItem(); +	 /* Highlights item */ +	/*virtual*/ void onMouseEnter(S32 x, S32 y, MASK mask); +	/* Removes item highlight */ +	/*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); -	void onMouseEnter(S32 x, S32 y, MASK mask); -	void onMouseLeave(S32 x, S32 y, MASK mask); +	virtual ~LLPanelInventoryListItemBase(){}  protected: -	LLPanelInventoryListItem(const LLViewerInventoryItem* item); + +	LLPanelInventoryListItemBase(LLViewerInventoryItem* item); + +	typedef std::vector<LLUICtrl*> widget_array_t; + +	/** +	 * Use it from a factory function to build panel, do not build panel in constructor +	 */ +	virtual void init(); + +	/** setter for mIconCtrl */ +	void setIconCtrl(LLIconCtrl* icon) { mIconCtrl = icon; } +	/** setter for MTitleCtrl */ +	void setTitleCtrl(LLTextBox* tb) { mTitleCtrl = tb; } + +	void setLeftWidgetsWidth(S32 width) { mLeftWidgetsWidth = width; } +	void setRightWidgetsWidth(S32 width) { mRightWidgetsWidth = width; } + +	/** +	 * Set all widgets from both side visible/invisible. Only enabled widgets +	 * (see setShowWidget()) can become visible +	 */ +	virtual void setWidgetsVisible(bool visible); + +	/** +	 * Reshape all child widgets - icon, text-box and side widgets +	 */ +	virtual void reshapeWidgets(); + +	/** set wearable type icon image */ +	void setIconImage(const LLUIImagePtr& image); + +	/** Set item title - inventory item name usually */ +	void setTitle(const std::string& title, const std::string& highlit_text);  private: -	LLIconCtrl*		mIcon; -	LLTextBox*		mTitle; -	LLUIImagePtr	mItemIcon; -	std::string		mItemName; +	/** reshape left side widgets +	 * Deprecated for now. Disabled reshape left for now to reserve space for 'delete'  +	 * button in LLPanelClothingListItem according to Neal's comment (https://codereview.productengine.com/secondlife/r/325/) +	 */ +	void reshapeLeftWidgets(); + +	/** reshape right side widgets */ +	void reshapeRightWidgets(); + +	/** reshape remaining widgets */ +	void reshapeMiddleWidgets(); + +	LLViewerInventoryItem* mItem; + +	LLIconCtrl*		mIconCtrl; +	LLTextBox*		mTitleCtrl; + +	LLUIImagePtr	mIconImage;  	std::string		mHighlightedText; + +	widget_array_t	mLeftSideWidgets; +	widget_array_t	mRightSideWidgets; +	S32				mWidgetSpacing; + +	S32				mLeftWidgetsWidth; +	S32				mRightWidgetsWidth;  }; +////////////////////////////////////////////////////////////////////////// +  class LLInventoryItemsList : public LLFlatListView  {  public: @@ -117,7 +225,7 @@ protected:  	/**  	 * Add an item to the list  	 */ -	void addNewItem(LLViewerInventoryItem* item); +	virtual void addNewItem(LLViewerInventoryItem* item);  private:  	uuid_vec_t mIDs; // IDs of items that were added in refreshList(). diff --git a/indra/newview/llinventoryobserver.cpp b/indra/newview/llinventoryobserver.cpp index 214b5d317a..86147d65e6 100644 --- a/indra/newview/llinventoryobserver.cpp +++ b/indra/newview/llinventoryobserver.cpp @@ -215,7 +215,7 @@ void LLInventoryFetchItemsObserver::changed(U32 mask)  void fetch_items_from_llsd(const LLSD& items_llsd)  { -	if (!items_llsd.size()) return; +	if (!items_llsd.size() || gDisconnected) return;  	LLSD body;  	body[0]["cap_name"] = "FetchInventory";  	body[1]["cap_name"] = "FetchLib"; @@ -235,6 +235,11 @@ void fetch_items_from_llsd(const LLSD& items_llsd)  	for (S32 i=0; i<body.size(); i++)  	{ +		if(!gAgent.getRegion()) +		{ +			llwarns<<"Agent's region is null"<<llendl; +			break; +		}  		if (0 >= body[i].size()) continue;  		std::string url = gAgent.getRegion()->getCapability(body[i]["cap_name"].asString()); @@ -664,36 +669,87 @@ void LLInventoryCategoriesObserver::changed(U32 mask)  		if (!category)  			continue; -		S32 version = category->getVersion(); -		if (version != (*iter).second.mVersion) +		const S32 version = category->getVersion(); +		const S32 expected_num_descendents = category->getDescendentCount(); +		if ((version == LLViewerInventoryCategory::VERSION_UNKNOWN) || +			(expected_num_descendents == LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN))  		{ -			// Update category version in map. -			(*iter).second.mVersion = version; -			(*iter).second.mCallback(); +			continue; +		} + +		// Check number of known descendents to find out whether it has changed. +		LLInventoryModel::cat_array_t* cats; +		LLInventoryModel::item_array_t* items; +		gInventory.getDirectDescendentsOf((*iter).first, cats, items); +		if (!cats || !items) +		{ +			llwarns << "Category '" << category->getName() << "' descendents corrupted, fetch failed." << llendl; +			// NULL means the call failed -- cats/items map doesn't exist (note: this does NOT mean +			// that the cat just doesn't have any items or subfolders). +			// Unrecoverable, so just skip this category. + +			llassert(cats != NULL && items != NULL); +		} +		const S32 current_num_known_descendents = cats->count() + items->count(); + +		LLCategoryData cat_data = (*iter).second; + +		// If category version or descendents count has changed +		// update category data in mCategoryMap and fire a callback. +		if (version != cat_data.mVersion || current_num_known_descendents != cat_data.mDescendentsCount) +		{ +			cat_data.mVersion = version; +			cat_data.mDescendentsCount = current_num_known_descendents; + +			cat_data.mCallback();  		}  	}  } -void LLInventoryCategoriesObserver::addCategory(const LLUUID& cat_id, callback_t cb) +bool LLInventoryCategoriesObserver::addCategory(const LLUUID& cat_id, callback_t cb)  {  	S32 version; +	S32 current_num_known_descendents; +	bool can_be_added = true; +  	LLViewerInventoryCategory* category = gInventory.getCategory(cat_id);  	if (category)  	{  		// Inventory category version is used to find out if some changes  		// to a category have been made.  		version = category->getVersion(); + +		LLInventoryModel::cat_array_t* cats; +		LLInventoryModel::item_array_t* items; +		gInventory.getDirectDescendentsOf(cat_id, cats, items); +		if (!cats || !items) +		{ +			llwarns << "Category '" << category->getName() << "' descendents corrupted, fetch failed." << llendl; +			// NULL means the call failed -- cats/items map doesn't exist (note: this does NOT mean +			// that the cat just doesn't have any items or subfolders). +			// Unrecoverable, so just return "false" meaning that the category can't be observed. +			can_be_added = false; + +			llassert(cats != NULL && items != NULL); +		} +		current_num_known_descendents = cats->count() + items->count();  	}  	else  	{  		// If category could not be retrieved it might mean that  		// inventory is unusable at the moment so the category is -		// stored with VERSION_UNKNOWN and it may be updated later. +		// stored with VERSION_UNKNOWN and DESCENDENT_COUNT_UNKNOWN, +		// it may be updated later.  		version = LLViewerInventoryCategory::VERSION_UNKNOWN; +		current_num_known_descendents = LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN; +	} + +	if (can_be_added) +	{ +		mCategoryMap.insert(category_map_value_t(cat_id, LLCategoryData(cb, version, current_num_known_descendents)));  	} -	version = category->getVersion(); -	mCategoryMap.insert(category_map_value_t(cat_id, LLCategoryData(cb, version))); +	return can_be_added;  }  void LLInventoryCategoriesObserver::removeCategory(const LLUUID& cat_id) diff --git a/indra/newview/llinventoryobserver.h b/indra/newview/llinventoryobserver.h index e63b67d2ad..036e6ca40d 100644 --- a/indra/newview/llinventoryobserver.h +++ b/indra/newview/llinventoryobserver.h @@ -276,19 +276,28 @@ public:  	LLInventoryCategoriesObserver() {};  	virtual void changed(U32 mask); -	void addCategory(const LLUUID& cat_id, callback_t cb); +	/** +	 * Add cat_id to the list of observed categories with a +	 * callback fired on category being changed. +	 * +	 * @return "true" if category was added, "false" if it could +	 * not be found. +	 */ +	bool addCategory(const LLUUID& cat_id, callback_t cb);  	void removeCategory(const LLUUID& cat_id);  protected:  	struct LLCategoryData  	{ -		LLCategoryData(callback_t cb, S32 version) +		LLCategoryData(callback_t cb, S32 version, S32 num_descendents)  		: mCallback(cb)  		, mVersion(version) +		, mDescendentsCount(num_descendents)  		{}  		callback_t	mCallback;  		S32			mVersion; +		S32			mDescendentsCount;  	};  	typedef	std::map<LLUUID, LLCategoryData>	category_map_t; diff --git a/indra/newview/lloutfitslist.cpp b/indra/newview/lloutfitslist.cpp index 1215272685..b103ec45d0 100644 --- a/indra/newview/lloutfitslist.cpp +++ b/indra/newview/lloutfitslist.cpp @@ -41,6 +41,7 @@  #include "llaccordionctrl.h"  #include "llaccordionctrltab.h" +#include "llappearancemgr.h"  #include "llinventoryfunctions.h"  #include "llinventorymodel.h"  #include "llwearableitemslist.h" @@ -51,6 +52,7 @@ LLOutfitsList::LLOutfitsList()  	:	LLPanel()  	,	mAccordion(NULL)  	,	mListCommands(NULL) +	,	mSelectedList(NULL)  {  	mCategoriesObserver = new LLInventoryCategoriesObserver();  	gInventory.addObserver(mCategoriesObserver); @@ -135,30 +137,37 @@ void LLOutfitsList::refreshList(const LLUUID& category_id)  	{  		const LLUUID cat_id = (*iter);  		LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); -		if (!cat) -			continue; +		if (!cat) continue;  		std::string name = cat->getName();  		static LLXMLNodePtr accordionXmlNode = getAccordionTabXMLNode(); - -		accordionXmlNode->setAttributeString("name", name); -		accordionXmlNode->setAttributeString("title", name);  		LLAccordionCtrlTab* tab = LLUICtrlFactory::defaultBuilder<LLAccordionCtrlTab>(accordionXmlNode, NULL, NULL); +		tab->setName(name); +		tab->setTitle(name); +  		// *TODO: LLUICtrlFactory::defaultBuilder does not use "display_children" from xml. Should be investigated.  		tab->setDisplayChildren(false);  		mAccordion->addCollapsibleCtrl(tab); +		// Start observing the new outfit category. +		LLWearableItemsList* list  = tab->getChild<LLWearableItemsList>("wearable_items_list"); +		if (!mCategoriesObserver->addCategory(cat_id, boost::bind(&LLWearableItemsList::updateList, list, cat_id))) +		{ +			// Remove accordion tab if category could not be added to observer. +			mAccordion->removeCollapsibleCtrl(tab); +			continue; +		} +  		// Map the new tab with outfit category UUID.  		mOutfitsMap.insert(LLOutfitsList::outfits_map_value_t(cat_id, tab)); -		// Start observing the new outfit category. -		LLWearableItemsList* list  = tab->getChild<LLWearableItemsList>("wearable_items_list"); -		mCategoriesObserver->addCategory(cat_id, boost::bind(&LLWearableItemsList::updateList, list, cat_id)); +		// Setting tab focus callback to monitor currently selected outfit. +		tab->setFocusReceivedCallback(boost::bind(&LLOutfitsList::changeOutfitSelection, this, list, cat_id)); -		// Setting drop down callback to monitor currently selected outfit. -		tab->setDropDownStateChangedCallback(boost::bind(&LLOutfitsList::onTabExpandedCollapsed, this, list)); +		// Setting list commit callback to monitor currently selected wearable item. +		list->setCommitCallback(boost::bind(&LLOutfitsList::onSelectionChange, this, _1));  		// Fetch the new outfit contents.  		cat->fetch(); @@ -178,10 +187,18 @@ void LLOutfitsList::refreshList(const LLUUID& category_id)  			// 1. Remove outfit accordion tab from accordion.  			mAccordion->removeCollapsibleCtrl(outfits_iter->second); +			const LLUUID& outfit_id = outfits_iter->first; +  			// 2. Remove outfit category from observer to stop monitoring its changes. -			mCategoriesObserver->removeCategory(outfits_iter->first); +			mCategoriesObserver->removeCategory(outfit_id); -			// 3. Remove category UUID to accordion tab mapping. +			// 3. Reset selection if selected outfit is being removed. +			if (mSelectedOutfitUUID == outfit_id) +			{ +				changeOutfitSelection(NULL, LLUUID()); +			} + +			// 4. Remove category UUID to accordion tab mapping.  			mOutfitsMap.erase(outfits_iter);  		}  	} @@ -199,40 +216,30 @@ void LLOutfitsList::refreshList(const LLUUID& category_id)  	mAccordion->arrange();  } -void LLOutfitsList::updateOutfitTab(const LLUUID& category_id) +void LLOutfitsList::onSelectionChange(LLUICtrl* ctrl)  { -	outfits_map_t::iterator outfits_iter = mOutfitsMap.find(category_id); -	if (outfits_iter != mOutfitsMap.end()) -	{ -		LLViewerInventoryCategory *cat = gInventory.getCategory(category_id); -		if (!cat) -			return; +	LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(ctrl); +	if (!list) return; -		std::string name = cat->getName(); +	LLViewerInventoryItem *item = gInventory.getItem(list->getSelectedUUID()); +	if (!item) return; -		// Update tab name with the new category name. -		LLAccordionCtrlTab* tab = outfits_iter->second; -		if (tab) -		{ -			tab->setName(name); -		} - -		// Update tab title with the new category name using textbox -		// in accordion tab header. -		LLTextBox* tab_title = tab->findChild<LLTextBox>("dd_textbox"); -		if (tab_title) -		{ -			tab_title->setText(name); -		} -	} +	changeOutfitSelection(list, item->getParentUUID());  } -void LLOutfitsList::onTabExpandedCollapsed(LLWearableItemsList* list) +void LLOutfitsList::performAction(std::string action)  { -	if (!list) -		return; +	LLViewerInventoryCategory* cat = gInventory.getCategory(mSelectedOutfitUUID); +	if (!cat) return; -	// TODO: Add outfit selection handling. +	if ("replaceoutfit" == action) +	{ +		LLAppearanceMgr::instance().wearInventoryCategory( cat, FALSE, FALSE ); +	} +	else if ("addtooutfit" == action) +	{ +		LLAppearanceMgr::instance().wearInventoryCategory( cat, FALSE, TRUE ); +	}  }  void LLOutfitsList::setFilterSubString(const std::string& string) @@ -240,7 +247,6 @@ void LLOutfitsList::setFilterSubString(const std::string& string)  	mFilterSubString = string;  } -  //////////////////////////////////////////////////////////////////////////  // Private methods  ////////////////////////////////////////////////////////////////////////// @@ -283,4 +289,37 @@ void LLOutfitsList::computeDifference(  	LLCommonUtils::computeDifference(vnew, vcur, vadded, vremoved);  } +void LLOutfitsList::updateOutfitTab(const LLUUID& category_id) +{ +	outfits_map_t::iterator outfits_iter = mOutfitsMap.find(category_id); +	if (outfits_iter != mOutfitsMap.end()) +	{ +		LLViewerInventoryCategory *cat = gInventory.getCategory(category_id); +		if (!cat) return; + +		std::string name = cat->getName(); + +		// Update tab name with the new category name. +		LLAccordionCtrlTab* tab = outfits_iter->second; +		if (tab) +		{ +			tab->setName(name); +			tab->setTitle(name); +		} +	} +} + +void LLOutfitsList::changeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id) +{ +	// Reset selection in previously selected tab +	// if a new one is selected. +	if (list && mSelectedList && mSelectedList != list) +	{ +		mSelectedList->resetSelection(); +	} + +	mSelectedList = list; +	mSelectedOutfitUUID = category_id; +} +  // EOF diff --git a/indra/newview/lloutfitslist.h b/indra/newview/lloutfitslist.h index 2d103ea356..d86cf5a703 100644 --- a/indra/newview/lloutfitslist.h +++ b/indra/newview/lloutfitslist.h @@ -65,10 +65,9 @@ public:  	void refreshList(const LLUUID& category_id); -	// Update tab displaying outfit identified by category_id. -	void updateOutfitTab(const LLUUID& category_id); +	void onSelectionChange(LLUICtrl* ctrl); -	void onTabExpandedCollapsed(LLWearableItemsList* list); +	void performAction(std::string action);  	void setFilterSubString(const std::string& string); @@ -85,12 +84,24 @@ private:  	 */  	void computeDifference(const LLInventoryModel::cat_array_t& vcats, uuid_vec_t& vadded, uuid_vec_t& vremoved); +	/** +	 * Updates tab displaying outfit identified by category_id. +	 */ +	void updateOutfitTab(const LLUUID& category_id); + +	/** +	 * Resets previous selection and stores newly selected list and outfit id. +	 */ +	void changeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id);  	LLInventoryCategoriesObserver* 	mCategoriesObserver;  	LLAccordionCtrl*				mAccordion;  	LLPanel*						mListCommands; +	LLWearableItemsList*			mSelectedList; +	LLUUID							mSelectedOutfitUUID; +  	std::string 					mFilterSubString;  	typedef	std::map<LLUUID, LLAccordionCtrlTab*>		outfits_map_t; diff --git a/indra/newview/llpaneloutfitsinventory.cpp b/indra/newview/llpaneloutfitsinventory.cpp index 789e85b46f..80964938f5 100644 --- a/indra/newview/llpaneloutfitsinventory.cpp +++ b/indra/newview/llpaneloutfitsinventory.cpp @@ -188,19 +188,37 @@ void LLPanelOutfitsInventory::onSearchEdit(const std::string& string)  void LLPanelOutfitsInventory::onWearButtonClick()  { -	LLFolderViewEventListener* listenerp = getCorrectListenerForAction(); -	if (listenerp) +	// TODO: Remove if/else, add common interface +	// for "My Outfits" and "Wearing" tabs. +	if (!isCOFPanelActive()) +	{ +		mMyOutfitsPanel->performAction("replaceoutfit"); +	} +	else  	{ -		listenerp->performAction(NULL, "replaceoutfit"); +		LLFolderViewEventListener* listenerp = getCorrectListenerForAction(); +		if (listenerp) +		{ +			listenerp->performAction(NULL, "replaceoutfit"); +		}  	}  }  void LLPanelOutfitsInventory::onAdd()  { -	LLFolderViewEventListener* listenerp = getCorrectListenerForAction(); -	if (listenerp) +	// TODO: Remove if/else, add common interface +	// for "My Outfits" and "Wearing" tabs. +	if (!isCOFPanelActive()) +	{ +		mMyOutfitsPanel->performAction("addtooutfit"); +	} +	else  	{ -		listenerp->performAction(NULL, "addtooutfit"); +		LLFolderViewEventListener* listenerp = getCorrectListenerForAction(); +		if (listenerp) +		{ +			listenerp->performAction(NULL, "addtooutfit"); +		}  	}  } diff --git a/indra/newview/llpreviewnotecard.cpp b/indra/newview/llpreviewnotecard.cpp index 75702dc8e5..fb7ac0d86b 100644 --- a/indra/newview/llpreviewnotecard.cpp +++ b/indra/newview/llpreviewnotecard.cpp @@ -35,6 +35,7 @@  #include "llpreviewnotecard.h"  #include "llinventory.h" +#include "llinventoryfunctions.h" // for change_item_parent()  #include "llagent.h"  #include "llassetuploadresponders.h" @@ -92,11 +93,17 @@ BOOL LLPreviewNotecard::postBuild()  	childSetAction("Save", onClickSave, this);  	childSetVisible("lock", FALSE);	 +	childSetAction("Delete", onClickDelete, this); +	childSetEnabled("Delete", false); +  	const LLInventoryItem* item = getItem();  	childSetCommitCallback("desc", LLPreview::onText, this);  	if (item) +	{  		childSetText("desc", item->getDescription()); +		childSetEnabled("Delete", true); +	}  	childSetPrevalidate("desc", &LLTextValidate::validateASCIIPrintableNoPipe);  	return LLPreview::postBuild(); @@ -374,6 +381,17 @@ void LLPreviewNotecard::onClickSave(void* user_data)  	}  } + +// static +void LLPreviewNotecard::onClickDelete(void* user_data) +{ +	LLPreviewNotecard* preview = (LLPreviewNotecard*)user_data; +	if(preview) +	{ +		preview->deleteNotecard(); +	} +} +  struct LLSaveNotecardInfo  {  	LLPreviewNotecard* mSelf; @@ -466,6 +484,18 @@ bool LLPreviewNotecard::saveIfNeeded(LLInventoryItem* copyitem)  	return true;  } +void LLPreviewNotecard::deleteNotecard() +{ +	LLViewerInventoryItem* item = gInventory.getItem(mItemUUID); +	if (item != NULL) +	{ +		const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); +		change_item_parent(&gInventory, item, trash_id, FALSE); +	} + +	closeFloater(); +} +  // static  void LLPreviewNotecard::onSaveComplete(const LLUUID& asset_uuid, void* user_data, S32 status, LLExtStat ext_status) // StoreAssetData callback (fixed)  { diff --git a/indra/newview/llpreviewnotecard.h b/indra/newview/llpreviewnotecard.h index e0363eef54..98de99aa33 100644 --- a/indra/newview/llpreviewnotecard.h +++ b/indra/newview/llpreviewnotecard.h @@ -83,6 +83,8 @@ protected:  	virtual void loadAsset();  	bool saveIfNeeded(LLInventoryItem* copyitem = NULL); +	void deleteNotecard(); +  	static void onLoadComplete(LLVFS *vfs,  							   const LLUUID& asset_uuid,  							   LLAssetType::EType type, @@ -90,6 +92,8 @@ protected:  	static void onClickSave(void* data); +	static void onClickDelete(void* data); +  	static void onSaveComplete(const LLUUID& asset_uuid,  							   void* user_data,  							   S32 status, LLExtStat ext_status); diff --git a/indra/newview/llscreenchannel.cpp b/indra/newview/llscreenchannel.cpp index af440a3689..de1da248c1 100644 --- a/indra/newview/llscreenchannel.cpp +++ b/indra/newview/llscreenchannel.cpp @@ -706,6 +706,31 @@ void LLScreenChannel::hideToast(const LLUUID& notification_id)  	}  } +void LLScreenChannel::closeHiddenToasts(const Matcher& matcher) +{ +	// since we can't guarantee that close toast operation doesn't change mToastList +	// we collect matched toasts that should be closed into separate list +	std::list<ToastElem> toasts; +	for (std::vector<ToastElem>::iterator it = mToastList.begin(); it +			!= mToastList.end(); it++) +	{ +		LLToast * toast = it->toast; +		// add to list valid toast that match to provided matcher criteria +		if (toast != NULL && !toast->isDead() && toast->getNotification() != NULL +				&& !toast->getVisible() && matcher.matches(toast->getNotification())) +		{ +			toasts.push_back(*it); +		} +	} + +	// close collected toasts +	for (std::list<ToastElem>::iterator it = toasts.begin(); it +			!= toasts.end(); it++) +	{ +		it->toast->closeFloater(); +	} +} +  //--------------------------------------------------------------------------  void LLScreenChannel::removeToastsFromChannel()  { diff --git a/indra/newview/llscreenchannel.h b/indra/newview/llscreenchannel.h index 88053d87d9..46c5fed7b6 100644 --- a/indra/newview/llscreenchannel.h +++ b/indra/newview/llscreenchannel.h @@ -173,6 +173,12 @@ public:  	void		hideToastsFromScreen();  	// hide toast by notification id  	void		hideToast(const LLUUID& notification_id); + +	/** +	 * Closes hidden matched toasts from channel. +	 */ +	void closeHiddenToasts(const Matcher& matcher); +  	// removes all toasts from a channel  	void		removeToastsFromChannel();  	// show all toasts in a channel diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index fd2bb0fdf9..c6f8ce2e15 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -33,6 +33,7 @@  #include "llviewerprecompiledheaders.h"  #include "llagent.h" +#include "llagentcamera.h"  #include "llviewermedia.h"  #include "llviewermediafocus.h"  #include "llmimetypes.h" @@ -732,10 +733,17 @@ static bool proximity_comparitor(const LLViewerMediaImpl* i1, const LLViewerMedi  	}  } +static LLFastTimer::DeclareTimer FTM_MEDIA_UPDATE("Update Media"); +  //////////////////////////////////////////////////////////////////////////////////////////  // static  void LLViewerMedia::updateMedia(void *dummy_arg)  { +	LLFastTimer t1(FTM_MEDIA_UPDATE); +	 +	// Enable/disable the plugin read thread +	LLPluginProcessParent::setUseReadThread(gSavedSettings.getBOOL("PluginUseReadThread")); +	  	sAnyMediaShowing = false;  	sUpdatedCookies = getCookieStore()->getChangedCookies();  	if(!sUpdatedCookies.empty()) @@ -1914,15 +1922,28 @@ void LLViewerMediaImpl::updateVolume()  {  	if(mMediaSource)  	{ -		F32 attenuation_multiplier = 1.0; +		// always scale the volume by the global media volume  +		F32 volume = mRequestedVolume * LLViewerMedia::getVolume(); -		if (mProximityDistance > 0) +		if (mProximityCamera > 0)   		{ -			// the attenuation multiplier should never be more than one since that would increase volume -			attenuation_multiplier = llmin(1.0, gSavedSettings.getF32("MediaRollOffFactor")/mProximityDistance); +			if (mProximityCamera > gSavedSettings.getF32("MediaRollOffMax")) +			{ +				volume = 0; +			} +			else if (mProximityCamera > gSavedSettings.getF32("MediaRollOffMin")) +			{ +				// attenuated_volume = v / ( 1 + (roll_off_rate * (d - min))^2 +				// the +1 is there so that for distance 0 the volume stays the same +				F64 adjusted_distance = mProximityCamera - gSavedSettings.getF32("MediaRollOffMin"); +				F64 attenuation = gSavedSettings.getF32("MediaRollOffRate") * adjusted_distance; +				attenuation = attenuation * attenuation; +				// the attenuation multiplier should never be more than one since that would increase volume +				volume = volume * llmin(1.0, 1 /(attenuation + 1)); +			}  		} -		mMediaSource->setVolume(mRequestedVolume * LLViewerMedia::getVolume() * attenuation_multiplier); +		mMediaSource->setVolume(volume);  	}  } @@ -3009,6 +3030,9 @@ void LLViewerMediaImpl::calculateInterest()  		LLVector3d agent_global = gAgent.getPositionGlobal() ;  		LLVector3d global_delta = agent_global - obj_global ;  		mProximityDistance = global_delta.magVecSquared();  // use distance-squared because it's cheaper and sorts the same. + +		LLVector3d camera_delta = gAgentCamera.getCameraPositionGlobal() - obj_global; +		mProximityCamera = camera_delta.magVec();  	}  	if(mNeedsMuteCheck) diff --git a/indra/newview/llviewermedia.h b/indra/newview/llviewermedia.h index e829d7a5b4..8626f4469e 100644 --- a/indra/newview/llviewermedia.h +++ b/indra/newview/llviewermedia.h @@ -431,6 +431,7 @@ private:  	bool mIsParcelMedia;  	S32 mProximity;  	F64 mProximityDistance; +	F64 mProximityCamera;  	LLMimeDiscoveryResponder *mMimeTypeProbe;  	bool mMediaAutoPlay;  	std::string mMediaEntryURL; diff --git a/indra/newview/llwearableitemslist.cpp b/indra/newview/llwearableitemslist.cpp index 3d110dcc78..56b2791993 100644 --- a/indra/newview/llwearableitemslist.cpp +++ b/indra/newview/llwearableitemslist.cpp @@ -33,8 +33,11 @@  #include "llwearableitemslist.h" +#include "lliconctrl.h" +  #include "llinventoryfunctions.h"  #include "llinventorymodel.h" +#include "lltransutil.h"  class LLFindOutfitItems : public LLInventoryCollectFunctor  { @@ -60,6 +63,239 @@ bool LLFindOutfitItems::operator()(LLInventoryCategory* cat,  	return FALSE;  } +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +void LLPanelWearableListItem::onMouseEnter(S32 x, S32 y, MASK mask) +{ +	LLPanelInventoryListItemBase::onMouseEnter(x, y, mask); +	setWidgetsVisible(true); +	reshapeWidgets(); +} + +void LLPanelWearableListItem::onMouseLeave(S32 x, S32 y, MASK mask) +{ +	LLPanelInventoryListItemBase::onMouseLeave(x, y, mask); +	setWidgetsVisible(false); +	reshapeWidgets(); +} + +LLPanelWearableListItem::LLPanelWearableListItem(LLViewerInventoryItem* item) +: LLPanelInventoryListItemBase(item) +{ +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +// static +LLPanelClothingListItem* LLPanelClothingListItem::create(LLViewerInventoryItem* item) +{ +	LLPanelClothingListItem* list_item = NULL; +	if(item) +	{ +		list_item = new LLPanelClothingListItem(item); +		list_item->init(); +	} +	return list_item; +} + +LLPanelClothingListItem::LLPanelClothingListItem(LLViewerInventoryItem* item) + : LLPanelWearableListItem(item) +{ +} + +LLPanelClothingListItem::~LLPanelClothingListItem() +{ +} + +void LLPanelClothingListItem::init() +{ +	LLUICtrlFactory::getInstance()->buildPanel(this, "panel_clothing_list_item.xml"); +} + +BOOL LLPanelClothingListItem::postBuild() +{ +	LLPanelInventoryListItemBase::postBuild(); + +	addWidgetToLeftSide("btn_delete"); +	addWidgetToRightSide("btn_move_up"); +	addWidgetToRightSide("btn_move_down"); +	addWidgetToRightSide("btn_lock"); +	addWidgetToRightSide("btn_edit"); + +	LLButton* delete_btn = getChild<LLButton>("btn_delete"); +	// Reserve space for 'delete' button event if it is invisible. +	setLeftWidgetsWidth(delete_btn->getRect().mRight); + +	setWidgetsVisible(false); +	reshapeWidgets(); + +	return TRUE; +} + +void LLPanelClothingListItem::setShowDeleteButton(bool show) +{ +	setShowWidget("btn_delete", show); +} + +void LLPanelClothingListItem::setShowMoveUpButton(bool show) +{ +	setShowWidget("btn_move_up", show); +} + +void LLPanelClothingListItem::setShowMoveDownButton(bool show) +{ +	setShowWidget("btn_move_down", show); +} + +void LLPanelClothingListItem::setShowLockButton(bool show) +{ +	setShowWidget("btn_lock", show); +} + +void LLPanelClothingListItem::setShowEditButton(bool show) +{ +	setShowWidget("btn_edit", show); +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +// static +LLPanelBodyPartsListItem* LLPanelBodyPartsListItem::create(LLViewerInventoryItem* item) +{ +	LLPanelBodyPartsListItem* list_item = NULL; +	if(item) +	{ +		list_item = new LLPanelBodyPartsListItem(item); +		list_item->init(); +	} +	return list_item; +} + +LLPanelBodyPartsListItem::LLPanelBodyPartsListItem(LLViewerInventoryItem* item) +: LLPanelWearableListItem(item) +{ +} + +LLPanelBodyPartsListItem::~LLPanelBodyPartsListItem() +{ +} + +void LLPanelBodyPartsListItem::init() +{ +	LLUICtrlFactory::getInstance()->buildPanel(this, "panel_body_parts_list_item.xml"); +} + +BOOL LLPanelBodyPartsListItem::postBuild() +{ +	LLPanelInventoryListItemBase::postBuild(); + +	addWidgetToRightSide("btn_lock"); +	addWidgetToRightSide("btn_edit"); + +	return TRUE; +} + +void LLPanelBodyPartsListItem::setShowLockButton(bool show) +{ +	setShowWidget("btn_lock", show); +} + +void LLPanelBodyPartsListItem::setShowEditButton(bool show) +{ +	setShowWidget("btn_edit", show); +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +LLPanelDummyClothingListItem* LLPanelDummyClothingListItem::create(EWearableType w_type) +{ +	LLPanelDummyClothingListItem* list_item = new LLPanelDummyClothingListItem(w_type); +	list_item->init(); +	return list_item; +} + +void LLPanelDummyClothingListItem::updateItem() +{ +	std::string title = wearableTypeToString(mWearableType); +	setTitle(title, LLStringUtil::null); +} + +BOOL LLPanelDummyClothingListItem::postBuild() +{ +	LLIconCtrl* icon = getChild<LLIconCtrl>("item_icon"); +	setIconCtrl(icon); +	setTitleCtrl(getChild<LLTextBox>("item_name")); + +	addWidgetToRightSide("btn_add"); + +	setIconImage(get_item_icon(LLAssetType::AT_CLOTHING, LLInventoryType::IT_NONE, mWearableType, FALSE)); +	updateItem(); + +	// Make it look loke clothing item - reserve space for 'delete' button +	setLeftWidgetsWidth(icon->getRect().mLeft); + +	setWidgetsVisible(false); +	reshapeWidgets(); + +	return TRUE; +} + +LLPanelDummyClothingListItem::LLPanelDummyClothingListItem(EWearableType w_type) + : LLPanelWearableListItem(NULL) + , mWearableType(w_type) +{ +} + +void LLPanelDummyClothingListItem::init() +{ +	LLUICtrlFactory::getInstance()->buildPanel(this, "panel_dummy_clothing_list_item.xml"); +} + +typedef std::map<EWearableType, std::string> clothing_to_string_map_t; + +clothing_to_string_map_t init_clothing_string_map() +{ +	clothing_to_string_map_t w_map; +	w_map.insert(std::make_pair(WT_SHIRT, "shirt_not_worn")); +	w_map.insert(std::make_pair(WT_PANTS, "pants_not_worn")); +	w_map.insert(std::make_pair(WT_SHOES, "shoes_not_worn")); +	w_map.insert(std::make_pair(WT_SOCKS, "socks_not_worn")); +	w_map.insert(std::make_pair(WT_JACKET, "jacket_not_worn")); +	w_map.insert(std::make_pair(WT_GLOVES, "gloves_not_worn")); +	w_map.insert(std::make_pair(WT_UNDERSHIRT, "undershirt_not_worn")); +	w_map.insert(std::make_pair(WT_UNDERPANTS, "underpants_not_worn")); +	w_map.insert(std::make_pair(WT_SKIRT, "skirt_not_worn")); +	w_map.insert(std::make_pair(WT_ALPHA, "alpha_not_worn")); +	w_map.insert(std::make_pair(WT_TATTOO, "tattoo_not_worn")); +	return w_map; +} + +std::string LLPanelDummyClothingListItem::wearableTypeToString(EWearableType w_type) +{ +	static const clothing_to_string_map_t w_map = init_clothing_string_map(); +	static const std::string invalid_str = LLTrans::getString("invalid_not_worn"); +	 +	std::string type_str = invalid_str; +	clothing_to_string_map_t::const_iterator it = w_map.find(w_type); +	if(w_map.end() != it) +	{ +		type_str = LLTrans::getString(it->second); +	} +	return type_str; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +  static const LLDefaultChildRegistry::Register<LLWearableItemsList> r("wearable_items_list");  LLWearableItemsList::Params::Params() @@ -89,3 +325,5 @@ void LLWearableItemsList::updateList(const LLUUID& category_id)  	refreshList(item_array);  } + +// EOF diff --git a/indra/newview/llwearableitemslist.h b/indra/newview/llwearableitemslist.h index e7ccba8e6c..c4a415dfbf 100644 --- a/indra/newview/llwearableitemslist.h +++ b/indra/newview/llwearableitemslist.h @@ -36,6 +36,115 @@  // newview  #include "llinventoryitemslist.h" +#include "llinventorymodel.h" +#include "llwearabledictionary.h" + +/** + * @class LLPanelWearableListItem + * + * Extends LLPanelInventoryListItemBase: + * - makes side widgets show on mouse_enter and hide on  + *   mouse_leave events. + * - provides callback for button clicks + */ +class LLPanelWearableListItem : public LLPanelInventoryListItemBase +{ +	LOG_CLASS(LLPanelWearableListItem); +public: + +	/** +	* Shows buttons when mouse is over +	*/ +	/*virtual*/ void onMouseEnter(S32 x, S32 y, MASK mask); + +	/** +	* Hides buttons when mouse is out +	*/ +	/*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); + +protected: + +	LLPanelWearableListItem(LLViewerInventoryItem* item); +}; + +/** + * @class LLPanelClothingListItem + * + * Provides buttons for editing, moving, deleting a wearable. + */ +class LLPanelClothingListItem : public LLPanelWearableListItem +{ +	LOG_CLASS(LLPanelClothingListItem); +public: + +	static LLPanelClothingListItem* create(LLViewerInventoryItem* item); + +	virtual ~LLPanelClothingListItem(); + +	/*virtual*/ BOOL postBuild(); + +	/** +	 * Make button visible during mouse over event. +	 */ +	inline void setShowDeleteButton(bool show); +	inline void setShowMoveUpButton(bool show); +	inline void setShowMoveDownButton(bool show); +	inline void setShowLockButton(bool show); +	inline void setShowEditButton(bool show); + +protected: + +	LLPanelClothingListItem(LLViewerInventoryItem* item); +	 +	/*virtual*/ void init(); +}; + +class LLPanelBodyPartsListItem : public LLPanelWearableListItem +{ +	LOG_CLASS(LLPanelBodyPartsListItem); +public: + +	static LLPanelBodyPartsListItem* create(LLViewerInventoryItem* item); + +	virtual ~LLPanelBodyPartsListItem(); + +	/*virtual*/ BOOL postBuild(); + +	/** +	* Make button visible during mouse over event. +	*/ +	inline void setShowLockButton(bool show); +	inline void setShowEditButton(bool show); + +protected: +	LLPanelBodyPartsListItem(LLViewerInventoryItem* item); + +	/*virtual*/ void init(); +}; + +/** + * @class LLPanelDummyClothingListItem + * + * A dummy item panel - displays grayed clothing icon, grayed title '<clothing> not worn' and 'add' button + */ +class LLPanelDummyClothingListItem : public LLPanelWearableListItem +{ +public: +	static LLPanelDummyClothingListItem* create(EWearableType w_type); + +	/*virtual*/ void updateItem(); +	/*virtual*/ BOOL postBuild(); + +protected: +	LLPanelDummyClothingListItem(EWearableType w_type); + +	/*virtual*/ void init(); + +	static std::string wearableTypeToString(EWearableType w_type); + +private: +	EWearableType mWearableType; +};  /**   * @class LLWearableItemsList diff --git a/indra/newview/res/viewerRes.rc b/indra/newview/res/viewerRes.rc index 12a09392f6..731953f9bb 100644 --- a/indra/newview/res/viewerRes.rc +++ b/indra/newview/res/viewerRes.rc @@ -129,8 +129,8 @@ TOOLSIT                 CURSOR                  "toolsit.cur"  //  VS_VERSION_INFO VERSIONINFO - FILEVERSION 2,0,0,203110 - PRODUCTVERSION 2,0,0,203110 + FILEVERSION 2,0,2,0 + PRODUCTVERSION 2,0,2,0   FILEFLAGSMASK 0x3fL  #ifdef _DEBUG   FILEFLAGS 0x1L @@ -147,12 +147,12 @@ BEGIN          BEGIN              VALUE "CompanyName", "Linden Lab"              VALUE "FileDescription", "Second Life" -            VALUE "FileVersion", "2.0.0.203110" +            VALUE "FileVersion", "2.0.2.0"              VALUE "InternalName", "Second Life"              VALUE "LegalCopyright", "Copyright © 2001-2008, Linden Research, Inc."              VALUE "OriginalFilename", "SecondLife.exe"              VALUE "ProductName", "Second Life" -            VALUE "ProductVersion", "2.0.0.203110" +            VALUE "ProductVersion", "2.0.2.0"          END      END      BLOCK "VarFileInfo" diff --git a/indra/newview/skins/default/xui/en/floater_preview_notecard.xml b/indra/newview/skins/default/xui/en/floater_preview_notecard.xml index 14c0081c0d..0e8eef2a21 100644 --- a/indra/newview/skins/default/xui/en/floater_preview_notecard.xml +++ b/indra/newview/skins/default/xui/en/floater_preview_notecard.xml @@ -84,8 +84,18 @@       label="Save"       label_selected="Save"       layout="topleft" -     left="288" +     left="178"       name="Save"       top="332"       width="100" /> +    <button +     follows="right|bottom" +     height="22" +     label="Delete" +     label_selected="Delete" +     layout="topleft" +     left="288" +     name="Delete" +     top="332" +     width="100" />  </floater> diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 3af80f63fe..df4f33adf0 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -1339,6 +1339,16 @@               function="ToggleControl"               parameter="RunMultipleThreads" />          </menu_item_check> +        <menu_item_check +         label="Use Plugin Read Thread" +         name="Use Plugin Read Thread"> +            <menu_item_check.on_check +             function="CheckControl" +             parameter="PluginUseReadThread" /> +            <menu_item_check.on_click +             function="ToggleControl" +             parameter="PluginUseReadThread" /> +        </menu_item_check>          <menu_item_call           label="Clear Group Cache"           name="ClearGroupCache"> diff --git a/indra/newview/skins/default/xui/en/panel_body_parts_list_item.xml b/indra/newview/skins/default/xui/en/panel_body_parts_list_item.xml new file mode 100644 index 0000000000..4313d450fb --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_body_parts_list_item.xml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + follows="top|right|left" + height="20" + layout="topleft" + left="0" + name="wearable_item" + top="0" + width="380"> +    <icon +     follows="top|right|left" +     height="20" +     image_name="ListItem_Over" +     layout="topleft" +     left="0" +     name="hovered_icon" +     top="0" +     visible="false" +     width="380" /> +    <icon +     height="20" +     follows="top|right|left" +     image_name="ListItem_Select" +     layout="topleft" +     left="0" +     name="selected_icon" +     top="0" +     visible="false" +     width="380" /> +    <icon +     height="16" +     follows="top|left" +     image_name="Inv_Object" +     layout="topleft" +     left="0" +     name="item_icon" +     top="2" +     width="16" /> +    <text +     follows="left|right" +     height="16" +     layout="topleft" +     left_pad="5" +     allow_html="false" +     use_ellipses="true" +     name="item_name" +     text_color="white" +     top="4" +     value="..." +     width="359" /> +    <icon  +     name="btn_lock" +     layout="topleft" +     follows="top|right" +     image_name="Lock2" +     top="0" +     left="0" +     height="20" +     width="20" +     tab_stop="false" /> +    <button  +     name="btn_edit" +     layout="topleft" +     follows="top|right" +     image_unselected="Icon_Gear_Background" +     image_selected="Icon_Gear_Background" +     top="0" +     left_pad="3" +     height="20" +     width="20" +     tab_stop="false" /> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_clothing_list_item.xml b/indra/newview/skins/default/xui/en/panel_clothing_list_item.xml new file mode 100644 index 0000000000..8dc67de06f --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_clothing_list_item.xml @@ -0,0 +1,104 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + follows="top|right|left" + height="20" + layout="topleft" + left="0" + name="wearable_item" + top="0" + width="380"> +    <icon +     follows="top|right|left" +     height="20" +     image_name="ListItem_Over" +     layout="topleft" +     left="0" +     name="hovered_icon" +     top="0" +     visible="false" +     width="380" /> +    <icon +     height="20" +     follows="top|right|left" +     image_name="ListItem_Select" +     layout="topleft" +     left="0" +     name="selected_icon" +     top="0" +     visible="false" +     width="380" /> +    <button  +     name="btn_delete" +     layout="topleft" +     follows="top|left" +     image_unselected="Toast_CloseBtn" +     image_selected="Toast_CloseBtn" +     top="0" +     left="0" +     height="20" +     width="20" +     tab_stop="false" /> +    <icon +     height="16" +     follows="top|left" +     image_name="Inv_Object" +     layout="topleft" +     left_pad="3" +     name="item_icon" +     top="2" +     width="16" /> +    <text +     follows="left|right" +     height="16" +     layout="topleft" +     left_pad="5" +     allow_html="false" +     use_ellipses="true" +     name="item_name" +     text_color="white" +     top="4" +     value="..." +     width="359" /> +    <button  +     name="btn_move_up" +     layout="topleft" +     follows="top|right" +     image_unselected="Movement_Up_Off" +     image_selected="Movement_Up_Off" +     top="0" +     left="0" +     height="20" +     width="20" +     tab_stop="false" /> +    <button  +     name="btn_move_down" +     layout="topleft" +     follows="top|right" +     image_unselected="Movement_Down_Off" +     image_selected="Movement_Down_Off" +     top="0" +     left_pad="3" +     height="20" +     width="20" +     tab_stop="false" /> +    <icon +     name="btn_lock" +     layout="topleft" +     follows="top|right" +     image_name="Lock2" +     top="0" +     left_pad="3" +     height="20" +     width="20" /> +    <button  +     name="btn_edit" +     layout="topleft" +     follows="top|right" +     image_unselected="Icon_Gear_Background" +     image_selected="Icon_Gear_Background" +     top="0" +     left_pad="3" +     height="20" +     width="20" +     tab_stop="false" /> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_dummy_clothing_list_item.xml b/indra/newview/skins/default/xui/en/panel_dummy_clothing_list_item.xml new file mode 100644 index 0000000000..dbbfa8f2e2 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_dummy_clothing_list_item.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + follows="top|right|left" + height="20" + layout="topleft" + left="0" + name="dummy_clothing_item" + top="0" + width="380"> +    <icon +     follows="top|right|left" +     height="20" +     image_name="ListItem_Over" +     layout="topleft" +     left="0" +     name="hovered_icon" +     top="0" +     visible="false" +     width="380" /> +    <icon +     height="20" +     follows="top|right|left" +     image_name="ListItem_Select" +     layout="topleft" +     left="0" +     name="selected_icon" +     top="0" +     visible="false" +     width="380" /> +    <icon +     height="16" +     color="0.75 0.75 0.75 1" +     follows="top|left" +     image_name="Inv_Object" +     layout="topleft" +     left="20" +     name="item_icon" +     top="2" +     width="16" /> +    <text +     follows="left|right" +     height="16" +     layout="topleft" +     left_pad="5" +     allow_html="false" +     use_ellipses="true" +     name="item_name" +     text_color="LtGray_50" +     top="4" +     value="..." +     width="359" /> +    <button  +     name="btn_add" +     layout="topleft" +     follows="top|right" +     label="+" +     top="0" +     left="0" +     height="20" +     width="20" +     tab_stop="false" /> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_outfit_edit.xml b/indra/newview/skins/default/xui/en/panel_outfit_edit.xml index 73181392c9..a9f588698a 100644 --- a/indra/newview/skins/default/xui/en/panel_outfit_edit.xml +++ b/indra/newview/skins/default/xui/en/panel_outfit_edit.xml @@ -234,6 +234,14 @@                   name="move_further_btn"                   top="1"                   width="31" /> +                <icon +                 follows="bottom|left" +                 height="25" +                 image_name="Toolbar_Middle_Off" +                 layout="topleft" +                 left_pad="1" +                 name="dummy_icon" +                 width="105" />                  <button                   follows="bottom|right"                   height="25" @@ -440,6 +448,22 @@                   name="add_to_outfit_btn"                   top="1"                   width="31" /> +                <icon +                 follows="bottom|left" +                 height="25" +                 image_name="Toolbar_Middle_Off" +                 layout="topleft" +                 left_pad="1" +                 name="dummy_middle_icon" +                 width="140" /> +                <icon +                 follows="bottom|left" +                 height="25" +                 image_name="Toolbar_Right_Off" +                 layout="topleft" +                 left_pad="1" +                 name="dummy_right_icon" +                 width="31" />              </panel>          </layout_panel>      </layout_stack> diff --git a/indra/newview/skins/default/xui/en/panel_people.xml b/indra/newview/skins/default/xui/en/panel_people.xml index 066ea3be6e..7e212c9383 100644 --- a/indra/newview/skins/default/xui/en/panel_people.xml +++ b/indra/newview/skins/default/xui/en/panel_people.xml @@ -490,6 +490,7 @@ Looking for people to hang out with? Try the [secondlife:///app/worldmap World M           label="Share"           layout="topleft"           name="share_btn" +         tool_tip="Share an inventory item"           width="62" />          <button           follows="bottom|left" diff --git a/indra/newview/skins/default/xui/en/panel_preferences_chat.xml b/indra/newview/skins/default/xui/en/panel_preferences_chat.xml index 3ef16d2dec..ba967d3e2c 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_chat.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_chat.xml @@ -330,7 +330,7 @@      <check_box       enabled="false"       height="16" -     label="Enable plain text chat history" +     label="Enable plain text IM and chat history"       layout="topleft"       left_delta="0"       name="plain_text_chat_history" diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 3cba76cbfa..e0d58a16c8 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -1802,6 +1802,20 @@ Clears (deletes) the media and all params from the given face.  	<string name="alpha">Alpha</string>  	<string name="tattoo">Tattoo</string>  	<string name="invalid">invalid</string> +   +  <!-- Not Worn Wearable Types --> +	<string name="shirt_not_worn">Shirt not worn</string> +	<string name="pants_not_worn">Pants not worn</string> +	<string name="shoes_not_worn">Shoes not worn</string> +	<string name="socks_not_worn">Socks not worn</string> +	<string name="jacket_not_worn">Jacket not worn</string> +	<string name="gloves_not_worn">Gloves not worn</string> +	<string name="undershirt_not_worn">Undershirt not worn</string> +	<string name="underpants_not_worn">Underpants not worn</string> +	<string name="skirt_not_worn">Skirt not worn</string> +	<string name="alpha_not_worn">Alpha not worn</string> +	<string name="tattoo_not_worn">Tattoo not worn</string> +	<string name="invalid_not_worn">invalid</string>    <!-- Wearable List-->    <string name="NewWearable">New [WEARABLE_ITEM]</string> diff --git a/indra/test_apps/llplugintest/llmediaplugintest.cpp b/indra/test_apps/llplugintest/llmediaplugintest.cpp index 7e9a8336e7..7a544debb2 100644 --- a/indra/test_apps/llplugintest/llmediaplugintest.cpp +++ b/indra/test_apps/llplugintest/llmediaplugintest.cpp @@ -241,6 +241,9 @@ LLMediaPluginTest::~LLMediaPluginTest()  	{  		remMediaPanel( mMediaPanels[ i ] );  	}; +	 +	// Stop the plugin read thread if it's running. +	LLPluginProcessParent::setUseReadThread(false);  }  //////////////////////////////////////////////////////////////////////////////// @@ -1047,6 +1050,11 @@ void LLMediaPluginTest::gluiCallback( int control_id )  		}  	}  	else +	if ( control_id == mIdUsePluginReadThread ) +	{ +		LLPluginProcessParent::setUseReadThread(mUsePluginReadThread); +	} +	else  	if ( control_id == mIdControlCrashPlugin )  	{  		// send message to plugin and ask it to crash @@ -1431,6 +1439,12 @@ void LLMediaPluginTest::makeChrome()  	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 ); diff --git a/indra/test_apps/llplugintest/llmediaplugintest.h b/indra/test_apps/llplugintest/llmediaplugintest.h index e7c7699343..5d08e42148 100644 --- a/indra/test_apps/llplugintest/llmediaplugintest.h +++ b/indra/test_apps/llplugintest/llmediaplugintest.h @@ -164,6 +164,8 @@ class LLMediaPluginTest : public LLPluginClassMediaOwner  		int mRandomBookmarks;  		int mIdDisableTimeout;  		int mDisableTimeout; +		int mIdUsePluginReadThread; +		int mUsePluginReadThread;  		int mIdLargePanelSpacing;  		int mLargePanelSpacing;  		int mIdControlCrashPlugin;  | 
