diff options
48 files changed, 1906 insertions, 200 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 f7df3b19a0..54bfa652e9 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 266ed2b5fd..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> 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 076a9609e3..c6f8ce2e15 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -733,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()) 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; |