diff options
75 files changed, 2456 insertions, 415 deletions
diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp index 1c664e093b..2cd29448ae 100644 --- a/indra/llcommon/llassettype.cpp +++ b/indra/llcommon/llassettype.cpp @@ -77,23 +77,23 @@ LLAssetDictionary::LLAssetDictionary() { // DESCRIPTION TYPE NAME HUMAN NAME CAN LINK? CAN FETCH? CAN KNOW? // |--------------------|-----------|-------------------|-----------|-----------|---------| - addEntry(LLAssetType::AT_TEXTURE, new AssetEntry("TEXTURE", "texture", "texture", false, false, true)); - addEntry(LLAssetType::AT_SOUND, new AssetEntry("SOUND", "sound", "sound", false, true, true)); - addEntry(LLAssetType::AT_CALLINGCARD, new AssetEntry("CALLINGCARD", "callcard", "calling card", false, false, false)); - addEntry(LLAssetType::AT_LANDMARK, new AssetEntry("LANDMARK", "landmark", "landmark", false, true, true)); - addEntry(LLAssetType::AT_SCRIPT, new AssetEntry("SCRIPT", "script", "legacy script", false, false, false)); + addEntry(LLAssetType::AT_TEXTURE, new AssetEntry("TEXTURE", "texture", "texture", true, false, true)); + addEntry(LLAssetType::AT_SOUND, new AssetEntry("SOUND", "sound", "sound", true, true, true)); + addEntry(LLAssetType::AT_CALLINGCARD, new AssetEntry("CALLINGCARD", "callcard", "calling card", true, false, false)); + addEntry(LLAssetType::AT_LANDMARK, new AssetEntry("LANDMARK", "landmark", "landmark", true, true, true)); + addEntry(LLAssetType::AT_SCRIPT, new AssetEntry("SCRIPT", "script", "legacy script", true, false, false)); addEntry(LLAssetType::AT_CLOTHING, new AssetEntry("CLOTHING", "clothing", "clothing", true, true, true)); addEntry(LLAssetType::AT_OBJECT, new AssetEntry("OBJECT", "object", "object", true, false, false)); - addEntry(LLAssetType::AT_NOTECARD, new AssetEntry("NOTECARD", "notecard", "note card", false, false, true)); + addEntry(LLAssetType::AT_NOTECARD, new AssetEntry("NOTECARD", "notecard", "note card", true, false, true)); addEntry(LLAssetType::AT_CATEGORY, new AssetEntry("CATEGORY", "category", "folder", true, false, false)); - addEntry(LLAssetType::AT_LSL_TEXT, new AssetEntry("LSL_TEXT", "lsltext", "lsl2 script", false, false, false)); - addEntry(LLAssetType::AT_LSL_BYTECODE, new AssetEntry("LSL_BYTECODE", "lslbyte", "lsl bytecode", false, false, false)); - addEntry(LLAssetType::AT_TEXTURE_TGA, new AssetEntry("TEXTURE_TGA", "txtr_tga", "tga texture", false, false, false)); + addEntry(LLAssetType::AT_LSL_TEXT, new AssetEntry("LSL_TEXT", "lsltext", "lsl2 script", true, false, false)); + addEntry(LLAssetType::AT_LSL_BYTECODE, new AssetEntry("LSL_BYTECODE", "lslbyte", "lsl bytecode", true, false, false)); + addEntry(LLAssetType::AT_TEXTURE_TGA, new AssetEntry("TEXTURE_TGA", "txtr_tga", "tga texture", true, false, false)); addEntry(LLAssetType::AT_BODYPART, new AssetEntry("BODYPART", "bodypart", "body part", true, true, true)); - addEntry(LLAssetType::AT_SOUND_WAV, new AssetEntry("SOUND_WAV", "snd_wav", "sound", false, false, false)); - addEntry(LLAssetType::AT_IMAGE_TGA, new AssetEntry("IMAGE_TGA", "img_tga", "targa image", false, false, false)); - addEntry(LLAssetType::AT_IMAGE_JPEG, new AssetEntry("IMAGE_JPEG", "jpeg", "jpeg image", false, false, false)); - addEntry(LLAssetType::AT_ANIMATION, new AssetEntry("ANIMATION", "animatn", "animation", false, true, true)); + addEntry(LLAssetType::AT_SOUND_WAV, new AssetEntry("SOUND_WAV", "snd_wav", "sound", true, false, false)); + addEntry(LLAssetType::AT_IMAGE_TGA, new AssetEntry("IMAGE_TGA", "img_tga", "targa image", true, false, false)); + addEntry(LLAssetType::AT_IMAGE_JPEG, new AssetEntry("IMAGE_JPEG", "jpeg", "jpeg image", true, false, false)); + addEntry(LLAssetType::AT_ANIMATION, new AssetEntry("ANIMATION", "animatn", "animation", true, true, true)); addEntry(LLAssetType::AT_GESTURE, new AssetEntry("GESTURE", "gesture", "gesture", true, true, true)); addEntry(LLAssetType::AT_SIMSTATE, new AssetEntry("SIMSTATE", "simstate", "simstate", false, false, false)); diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp index 0874f574c5..aa7c8c789a 100644 --- a/indra/llimage/llimage.cpp +++ b/indra/llimage/llimage.cpp @@ -1338,7 +1338,7 @@ LLImageFormatted::LLImageFormatted(S8 codec) mCodec(codec), mDecoding(0), mDecoded(0), - mDiscardLevel(0) + mDiscardLevel(-1) { mMemType = LLMemType::MTYPE_IMAGEFORMATTED; } diff --git a/indra/llinventory/llinventorytype.cpp b/indra/llinventory/llinventorytype.cpp index 4ef5df0b28..c050f20a9d 100644 --- a/indra/llinventory/llinventorytype.cpp +++ b/indra/llinventory/llinventorytype.cpp @@ -181,6 +181,10 @@ bool LLInventoryType::cannotRestrictPermissions(LLInventoryType::EType type) bool inventory_and_asset_types_match(LLInventoryType::EType inventory_type, LLAssetType::EType asset_type) { + // Links can be of any inventory type. + if (LLAssetType::lookupIsLinkType(asset_type)) + return true; + const InventoryEntry *entry = LLInventoryDictionary::getInstance()->lookup(inventory_type); if (!entry) return false; diff --git a/indra/llplugin/llpluginclassmedia.cpp b/indra/llplugin/llpluginclassmedia.cpp index e09b511a6e..0c9b325b68 100644 --- a/indra/llplugin/llpluginclassmedia.cpp +++ b/indra/llplugin/llpluginclassmedia.cpp @@ -160,7 +160,7 @@ void LLPluginClassMedia::idle(void) mPlugin->idle(); } - if((mMediaWidth == -1) || (!mTextureParamsReceived) || (mPlugin == NULL)) + if((mMediaWidth == -1) || (!mTextureParamsReceived) || (mPlugin == NULL) || (mPlugin->isBlocked())) { // Can't process a size change at this time } @@ -437,6 +437,12 @@ void LLPluginClassMedia::mouseEvent(EMouseEventType type, int button, int x, int { if(type == MOUSE_EVENT_MOVE) { + if(!mPlugin || !mPlugin->isRunning() || mPlugin->isBlocked()) + { + // Don't queue up mouse move events that can't be delivered. + return; + } + if((x == mLastMouseX) && (y == mLastMouseY)) { // Don't spam unnecessary mouse move events. diff --git a/indra/llplugin/llpluginmessagepipe.cpp b/indra/llplugin/llpluginmessagepipe.cpp index 1d7ddc5592..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; } @@ -299,26 +359,27 @@ bool LLPluginMessagePipe::pump(F64 timeout) void LLPluginMessagePipe::processInput(void) { // Look for input delimiter(s) in the input buffer. - int start = 0; int delim; - while((delim = mInput.find(MESSAGE_DELIMITER, start)) != std::string::npos) + mInputMutex.lock(); + while((delim = mInput.find(MESSAGE_DELIMITER)) != std::string::npos) { // Let the owner process this message if (mOwner) { - mOwner->receiveMessageRaw(mInput.substr(start, delim - start)); + // Pull the message out of the input buffer before calling receiveMessageRaw. + // It's now possible for this function to get called recursively (in the case where the plugin makes a blocking request) + // 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; } - - start = delim + 1; } - - // Remove delivered messages from the input buffer. - if(start != 0) - mInput = mInput.substr(start); - + 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 ccaf95b36d..d1cf91b253 100644 --- a/indra/llplugin/llpluginprocesschild.cpp +++ b/indra/llplugin/llpluginprocesschild.cpp @@ -48,6 +48,8 @@ LLPluginProcessChild::LLPluginProcessChild() mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); mSleepTime = PLUGIN_IDLE_SECONDS; // default: send idle messages at 100Hz mCPUElapsed = 0.0f; + mBlockingRequest = false; + mBlockingResponseReceived = false; } LLPluginProcessChild::~LLPluginProcessChild() @@ -83,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); } @@ -226,6 +233,7 @@ void LLPluginProcessChild::idle(void) void LLPluginProcessChild::sleep(F64 seconds) { + deliverQueuedMessages(); if(mMessagePipe) { mMessagePipe->pump(seconds); @@ -238,6 +246,7 @@ void LLPluginProcessChild::sleep(F64 seconds) void LLPluginProcessChild::pump(void) { + deliverQueuedMessages(); if(mMessagePipe) { mMessagePipe->pump(0.0f); @@ -309,15 +318,32 @@ void LLPluginProcessChild::receiveMessageRaw(const std::string &message) LL_DEBUGS("Plugin") << "Received from parent: " << message << LL_ENDL; + // Decode this message + LLPluginMessage parsed; + parsed.parse(message); + + if(mBlockingRequest) + { + // We're blocking the plugin waiting for a response. + + if(parsed.hasValue("blocking_response")) + { + // This is the message we've been waiting for -- fall through and send it immediately. + mBlockingResponseReceived = true; + } + else + { + // Still waiting. Queue this message and don't process it yet. + mMessageQueue.push(message); + return; + } + } + bool passMessage = true; // FIXME: how should we handle queueing here? { - // Decode this message - LLPluginMessage parsed; - parsed.parse(message); - std::string message_class = parsed.getClass(); if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL) { @@ -425,7 +451,13 @@ void LLPluginProcessChild::receiveMessageRaw(const std::string &message) void LLPluginProcessChild::receivePluginMessage(const std::string &message) { LL_DEBUGS("Plugin") << "Received from plugin: " << message << LL_ENDL; - + + if(mBlockingRequest) + { + // + LL_ERRS("Plugin") << "Can't send a message while already waiting on a blocking request -- aborting!" << LL_ENDL; + } + // Incoming message from the plugin instance bool passMessage = true; @@ -436,6 +468,12 @@ void LLPluginProcessChild::receivePluginMessage(const std::string &message) // Decode this message LLPluginMessage parsed; parsed.parse(message); + + if(parsed.hasValue("blocking_request")) + { + mBlockingRequest = true; + } + std::string message_class = parsed.getClass(); if(message_class == "base") { @@ -494,6 +532,19 @@ void LLPluginProcessChild::receivePluginMessage(const std::string &message) LL_DEBUGS("Plugin") << "Passing through to parent: " << message << LL_ENDL; writeMessageRaw(message); } + + while(mBlockingRequest) + { + // The plugin wants to block and wait for a response to this message. + sleep(mSleepTime); // this will pump the message pipe and process messages + + if(mBlockingResponseReceived || mSocketError != APR_SUCCESS || (mMessagePipe == NULL)) + { + // Response has been received, or we've hit an error state. Stop waiting. + mBlockingRequest = false; + mBlockingResponseReceived = false; + } + } } @@ -502,3 +553,15 @@ void LLPluginProcessChild::setState(EState state) LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL; mState = state; }; + +void LLPluginProcessChild::deliverQueuedMessages() +{ + if(!mBlockingRequest) + { + while(!mMessageQueue.empty()) + { + receiveMessageRaw(mMessageQueue.front()); + mMessageQueue.pop(); + } + } +} diff --git a/indra/llplugin/llpluginprocesschild.h b/indra/llplugin/llpluginprocesschild.h index 0e5e85406a..1430ad7a5d 100644 --- a/indra/llplugin/llpluginprocesschild.h +++ b/indra/llplugin/llpluginprocesschild.h @@ -106,6 +106,11 @@ private: LLTimer mHeartbeat; F64 mSleepTime; F64 mCPUElapsed; + bool mBlockingRequest; + bool mBlockingResponseReceived; + std::queue<std::string> mMessageQueue; + + void deliverQueuedMessages(); }; diff --git a/indra/llplugin/llpluginprocessparent.cpp b/indra/llplugin/llpluginprocessparent.cpp index 895c858979..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; @@ -54,18 +97,37 @@ LLPluginProcessParent::LLPluginProcessParent(LLPluginProcessParentOwner *owner) mCPUUsage = 0.0; 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()) @@ -77,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(); } @@ -159,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(). @@ -354,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 { @@ -388,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; @@ -410,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; @@ -479,23 +568,323 @@ void LLPluginProcessParent::setSleepTime(F64 sleep_time, bool force_send) void LLPluginProcessParent::sendMessage(const LLPluginMessage &message) { + if(message.hasValue("blocking_response")) + { + mBlocked = false; + + // reset the heartbeat timer, since there will have been no heartbeats while the plugin was blocked. + mHeartbeat.setTimerExpirySec(mPluginLockupTimeout); + } 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::receiveMessageEarly(const LLPluginMessage &message) +{ + // 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 + { + // 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); } } @@ -689,18 +1078,15 @@ bool LLPluginProcessParent::pluginLockedUpOrQuit() { bool result = false; - if(!mDisableTimeout && !mDebug) + if(!mProcess.isRunning()) { - if(!mProcess.isRunning()) - { - LL_WARNS("Plugin") << "child exited" << llendl; - result = true; - } - else if(pluginLockedUp()) - { - LL_WARNS("Plugin") << "timeout" << llendl; - result = true; - } + LL_WARNS("Plugin") << "child exited" << LL_ENDL; + result = true; + } + else if(pluginLockedUp()) + { + LL_WARNS("Plugin") << "timeout" << LL_ENDL; + result = true; } return result; @@ -708,6 +1094,12 @@ bool LLPluginProcessParent::pluginLockedUpOrQuit() bool LLPluginProcessParent::pluginLockedUp() { + if(mDisableTimeout || mDebug || mBlocked) + { + // Never time out a plugin process in these cases. + return false; + } + // If the timer is running and has expired, the plugin has locked up. return (mHeartbeat.getStarted() && mHeartbeat.hasExpired()); } diff --git a/indra/llplugin/llpluginprocessparent.h b/indra/llplugin/llpluginprocessparent.h index cc6c513615..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() {}; @@ -74,6 +76,9 @@ public: // returns true if the process has exited or we've had a fatal error bool isDone(void); + // returns true if the process is currently waiting on a blocking request + bool isBlocked(void) { return mBlocked; }; + void killSockets(void); // Go to the proper error state @@ -87,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. @@ -110,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 @@ -160,12 +171,27 @@ 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/llui/llspinctrl.cpp b/indra/llui/llspinctrl.cpp index 491cd7b6f3..b47f21ed8a 100644 --- a/indra/llui/llspinctrl.cpp +++ b/indra/llui/llspinctrl.cpp @@ -457,3 +457,8 @@ BOOL LLSpinCtrl::handleKeyHere(KEY key, MASK mask) return FALSE; } +BOOL LLSpinCtrl::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + // just treat a double click as a second click + return handleMouseDown(x, y, mask); +} diff --git a/indra/llui/llspinctrl.h b/indra/llui/llspinctrl.h index 00d6f86f83..06201255d2 100644 --- a/indra/llui/llspinctrl.h +++ b/indra/llui/llspinctrl.h @@ -94,6 +94,7 @@ public: virtual BOOL handleScrollWheel(S32 x,S32 y,S32 clicks); virtual BOOL handleKeyHere(KEY key, MASK mask); + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); void onEditorCommit(const LLSD& data); static void onEditorGainFocus(LLFocusableElement* caller, void *userdata); 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/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index a6dbe00759..280c3d642c 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -5585,6 +5585,19 @@ <key>Value</key> <integer>8</integer> </map> + + <key>PluginUseReadThread</key> + <map> + <key>Comment</key> + <string>Use a separate thread to read incoming messages from plugins</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>PrecachingDelay</key> <map> <key>Comment</key> @@ -5838,7 +5851,18 @@ <key>Value</key> <real>1.0</real> </map> - <key>RecentItemsSortOrder</key> + <key>MediaRollOffFactor</key> + <map> + <key>Comment</key> + <string>Multiplier to change rate of media attenuation</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>10.0</real> + </map> + <key>RecentItemsSortOrder</key> <map> <key>Comment</key> <string>Specifies sort key for recent inventory items (+0 = name, +1 = date, +2 = folders always by name, +4 = system folders to top)</string> diff --git a/indra/newview/llagentwearables.cpp b/indra/newview/llagentwearables.cpp index 8a880e5ace..b5fde0baca 100644 --- a/indra/newview/llagentwearables.cpp +++ b/indra/newview/llagentwearables.cpp @@ -1974,7 +1974,11 @@ void LLAgentWearables::userAttachMultipleAttachments(LLInventoryModel::item_arra msg->nextBlockFast(_PREHASH_ObjectData ); msg->addUUIDFast(_PREHASH_ItemID, item->getLinkedUUID()); msg->addUUIDFast(_PREHASH_OwnerID, item->getPermissions().getOwner()); +#if ENABLE_MULTIATTACHMENTS + msg->addU8Fast(_PREHASH_AttachmentPt, 0 | ATTACHMENT_ADD ); +#else msg->addU8Fast(_PREHASH_AttachmentPt, 0 ); // Wear at the previous or default attachment point +#endif pack_permissions_slam(msg, item->getFlags(), item->getPermissions()); msg->addStringFast(_PREHASH_Name, item->getName()); msg->addStringFast(_PREHASH_Description, item->getDescription()); 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..36a8031cce 100644 --- a/indra/newview/llcofwearables.cpp +++ b/indra/newview/llcofwearables.cpp @@ -34,10 +34,13 @@ #include "llcofwearables.h" +#include "llagentdata.h" #include "llappearancemgr.h" #include "llinventory.h" -#include "llinventoryitemslist.h" #include "llinventoryfunctions.h" +#include "llwearableitemslist.h" + +static LLRegisterPanelClassWrapper<LLCOFAccordionListAdaptor> t_cof_accodion_list_adaptor("accordion_list_adaptor"); static LLRegisterPanelClassWrapper<LLCOFWearables> t_cof_wearables("cof_wearables"); @@ -117,18 +120,18 @@ 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 = buildBodypartListItem(item); + if (!item_panel) continue; + mBodyParts->addItem(item_panel, item->getUUID(), ADD_BOTTOM, false); - addWearableTypeSeparator(mBodyParts); } } @@ -143,17 +146,69 @@ void LLCOFWearables::populateAttachmentsAndBodypartsLists(const LLInventoryModel mBodyParts->sort(); //*TODO by name } - addListButtonBar(mBodyParts, "panel_bodyparts_list_button_bar.xml"); mBodyParts->notify(REARRANGE); } +//create a clothing list item, update verbs and show/hide line separator +LLPanelClothingListItem* LLCOFWearables::buildClothingListItem(LLViewerInventoryItem* item, bool first, bool last) +{ + llassert(item); + + LLPanelClothingListItem* item_panel = LLPanelClothingListItem::create(item); + if (!item_panel) return NULL; + + //updating verbs + //we don't need to use permissions of a link but of an actual/linked item + if (item->getLinkedItem()) item = item->getLinkedItem(); + + bool allow_modify = item->getPermissions().allowModifyBy(gAgentID); + + item_panel->setShowLockButton(!allow_modify); + item_panel->setShowEditButton(allow_modify); + + item_panel->setShowMoveUpButton(!first); + item_panel->setShowMoveDownButton(!last); + + //setting callbacks + //*TODO move that item panel's inner structure disclosing stuff into the panels + item_panel->childSetAction("btn_delete", mCOFCallbacks.mDeleteWearable); + item_panel->childSetAction("btn_move_up", mCOFCallbacks.mMoveWearableCloser); + item_panel->childSetAction("btn_move_down", mCOFCallbacks.mMoveWearableFurther); + item_panel->childSetAction("btn_edit", mCOFCallbacks.mEditWearable); + + //turning on gray separator line for the last item in the items group of the same wearable type + item_panel->childSetVisible("wearable_type_separator_panel", last); + + return item_panel; +} + +LLPanelBodyPartsListItem* LLCOFWearables::buildBodypartListItem(LLViewerInventoryItem* item) +{ + llassert(item); + + LLPanelBodyPartsListItem* item_panel = LLPanelBodyPartsListItem::create(item); + if (!item_panel) return NULL; + + //updating verbs + //we don't need to use permissions of a link but of an actual/linked item + if (item->getLinkedItem()) item = item->getLinkedItem(); + + bool allow_modify = item->getPermissions().allowModifyBy(gAgentID); + item_panel->setShowLockButton(!allow_modify); + item_panel->setShowEditButton(allow_modify); + + //setting callbacks + //*TODO move that item panel's inner structure disclosing stuff into the panels + item_panel->childSetAction("btn_delete", mCOFCallbacks.mDeleteWearable); + item_panel->childSetAction("btn_edit", mCOFCallbacks.mEditWearable); + + return item_panel; +} void LLCOFWearables::populateClothingList(LLAppearanceMgr::wearables_by_type_t& clothing_by_type) { llassert(clothing_by_type.size() == WT_COUNT); - addListButtonBar(mClothing, "panel_clothing_list_button_bar.xml"); - for (U32 type = WT_SHIRT; type < WT_COUNT; ++type) { U32 size = clothing_by_type[type].size(); @@ -165,13 +220,11 @@ void LLCOFWearables::populateClothingList(LLAppearanceMgr::wearables_by_type_t& { LLViewerInventoryItem* item = clothing_by_type[type][i]; - LLPanelInventoryListItem* item_panel = LLPanelInventoryListItem::createItemPanel(item); + LLPanelClothingListItem* item_panel = buildClothingListItem(item, i == 0, i == size - 1); if (!item_panel) continue; mClothing->addItem(item_panel, item->getUUID(), ADD_BOTTOM, false); } - - addWearableTypeSeparator(mClothing); } addClothingTypesDummies(clothing_by_type); @@ -179,21 +232,6 @@ void LLCOFWearables::populateClothingList(LLAppearanceMgr::wearables_by_type_t& mClothing->notify(REARRANGE); } -void LLCOFWearables::addListButtonBar(LLFlatListView* list, std::string xml_filename) -{ - llassert(list); - llassert(xml_filename.length()); - - LLPanel::Params params; - LLPanel* button_bar = LLUICtrlFactory::create<LLPanel>(params); - LLUICtrlFactory::instance().buildPanel(button_bar, xml_filename); - - LLRect rc = button_bar->getRect(); - button_bar->reshape(list->getItemsRect().getWidth(), rc.getHeight()); - - list->addItem(button_bar, LLUUID::null, ADD_TOP, false); -} - //adding dummy items for missing wearable types void LLCOFWearables::addClothingTypesDummies(const LLAppearanceMgr::wearables_by_type_t& clothing_by_type) { @@ -204,30 +242,13 @@ 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); - - addWearableTypeSeparator(mClothing); + 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); } } -void LLCOFWearables::addWearableTypeSeparator(LLFlatListView* list) -{ - llassert(list); - - static LLXMLNodePtr separator_xml_node = getXMLNode("panel_wearable_type_separator.xml"); - if (separator_xml_node->isNull()) return; - - LLPanel* separator = LLUICtrlFactory::defaultBuilder<LLPanel>(separator_xml_node, NULL, NULL); - - LLRect rc = separator->getRect(); - rc.setOriginAndSize(0, 0, list->getItemsRect().getWidth(), rc.getHeight()); - separator->setRect(rc); - - list->addItem(separator, LLUUID::null, ADD_BOTTOM, false); -} - LLUUID LLCOFWearables::getSelectedUUID() { if (!mLastSelectedList) return LLUUID::null; @@ -242,17 +263,4 @@ void LLCOFWearables::clear() mBodyParts->clear(); } -LLXMLNodePtr LLCOFWearables::getXMLNode(std::string xml_filename) -{ - LLXMLNodePtr xmlNode = NULL; - bool success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, xmlNode); - if (!success) - { - llwarning("Failed to read xml", 0); - return NULL; - } - - return xmlNode; -} - //EOF diff --git a/indra/newview/llcofwearables.h b/indra/newview/llcofwearables.h index fec6d34db2..2d26bf781f 100644 --- a/indra/newview/llcofwearables.h +++ b/indra/newview/llcofwearables.h @@ -36,12 +36,80 @@ #include "llpanel.h" #include "llinventorymodel.h" #include "llappearancemgr.h" +#include "llwearableitemslist.h" class LLFlatListView; +/** + * Adaptor between LLAccordionCtrlTab and LLFlatListView to facilitate communication between them + * (notify, notifyParent) regarding size changes of a list and selection changes across accordion tabs. + * Besides that it acts as a container for the LLFlatListView and a button bar on top of it. + */ +class LLCOFAccordionListAdaptor : public LLPanel +{ +public: + LLCOFAccordionListAdaptor() : LLPanel() {}; + ~LLCOFAccordionListAdaptor() {}; + + S32 notifyParent(const LLSD& info) + { + LLView* parent = getParent(); + if (!parent) return -1; + + if (!(info.has("action") && "size_changes" == info["action"].asString())) + { + return parent->notifyParent(info); + } + + LLRect rc; + childGetRect("button_bar", rc); + + LLSD params; + params["action"] = "size_changes"; + params["width"] = info["width"]; + params["height"] = info["height"].asInteger() + rc.getHeight(); + + return parent->notifyParent(params); + } + + + S32 notify(const LLSD& info) + { + for (child_list_const_iter_t iter = beginChild(); iter != endChild(); iter++) + { + if (dynamic_cast<LLFlatListView*>(*iter)) + { + return (*iter)->notify(info); + } + } + return LLPanel::notify(info); + }; +}; + + class LLCOFWearables : public LLPanel { public: + + /** + * Represents a collection of callbacks assigned to inventory panel item's buttons + */ + class LLCOFCallbacks + { + public: + LLCOFCallbacks() {}; + virtual ~LLCOFCallbacks() {}; + + typedef boost::function<void (void*)> cof_callback_t; + + cof_callback_t mMoveWearableCloser; + cof_callback_t mMoveWearableFurther; + cof_callback_t mEditWearable; + cof_callback_t mDeleteWearable; + }; + + + LLCOFWearables(); virtual ~LLCOFWearables() {}; @@ -52,17 +120,18 @@ public: void refresh(); void clear(); + LLCOFCallbacks& getCOFCallbacks() { return mCOFCallbacks; } + protected: void populateAttachmentsAndBodypartsLists(const LLInventoryModel::item_array_t& cof_items); void populateClothingList(LLAppearanceMgr::wearables_by_type_t& clothing_by_type); - void addListButtonBar(LLFlatListView* list, std::string xml_filename); void addClothingTypesDummies(const LLAppearanceMgr::wearables_by_type_t& clothing_by_type); - void addWearableTypeSeparator(LLFlatListView* list); void onSelectionChange(LLFlatListView* selected_list); - LLXMLNodePtr getXMLNode(std::string xml_filename); + LLPanelClothingListItem* buildClothingListItem(LLViewerInventoryItem* item, bool first, bool last); + LLPanelBodyPartsListItem* buildBodypartListItem(LLViewerInventoryItem* item); LLFlatListView* mAttachments; LLFlatListView* mClothing; @@ -70,6 +139,8 @@ protected: LLFlatListView* mLastSelectedList; + LLCOFCallbacks mCOFCallbacks; + }; diff --git a/indra/newview/llfavoritesbar.cpp b/indra/newview/llfavoritesbar.cpp index 4c1e3461a5..6b7a257a4b 100644 --- a/indra/newview/llfavoritesbar.cpp +++ b/indra/newview/llfavoritesbar.cpp @@ -1165,6 +1165,17 @@ void LLFavoritesBarCtrl::pastFromClipboard() const void LLFavoritesBarCtrl::onButtonMouseDown(LLUUID id, LLUICtrl* ctrl, S32 x, S32 y, MASK mask) { + // EXT-6997 (Fav bar: Pop-up menu for LM in overflow dropdown is kept after LM was dragged away) + // mInventoryItemsPopupMenuHandle.get() - is a pop-up menu (of items) in already opened dropdown menu. + // We have to check and set visibility of pop-up menu in such a way instead of using + // LLMenuHolderGL::hideMenus() because it will close both menus(dropdown and pop-up), but + // we need to close only pop-up menu while dropdown one should be still opened. + LLMenuGL* menu = (LLMenuGL*)mInventoryItemsPopupMenuHandle.get(); + if(menu && menu->getVisible()) + { + menu->setVisible(FALSE); + } + mDragItemId = id; mStartDrag = TRUE; 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 b4a1bf2758..2d08c0a01a 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -211,10 +211,14 @@ BOOL LLInvFVBridge::isItemRemovable() const return FALSE; } - // Disable delete from COF folder; have users explicitly choose "detach/take off". + // Disable delete from COF folder; have users explicitly choose "detach/take off", + // unless the item is not worn but in the COF (i.e. is bugged). if (LLAppearanceMgr::instance().getIsProtectedCOFItem(mUUID)) { - return FALSE; + if (get_is_item_worn(mUUID)) + { + return FALSE; + } } const LLInventoryObject *obj = model->getItem(mUUID); @@ -494,7 +498,7 @@ BOOL LLInvFVBridge::isClipboardPasteableAsLink() const } } const LLViewerInventoryCategory *cat = model->getCategory(objects.get(i)); - if (cat && !LLFolderType::lookupIsProtectedType(cat->getPreferredType())) + if (cat && LLFolderType::lookupIsProtectedType(cat->getPreferredType())) { return FALSE; } @@ -641,13 +645,10 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, disabled_items.push_back(std::string("Paste")); } - if (gAgent.isGodlike()) + items.push_back(std::string("Paste As Link")); + if (!isClipboardPasteableAsLink() || (flags & FIRST_SELECTED_ITEM) == 0) { - items.push_back(std::string("Paste As Link")); - if (!isClipboardPasteableAsLink() || (flags & FIRST_SELECTED_ITEM) == 0) - { - disabled_items.push_back(std::string("Paste As Link")); - } + disabled_items.push_back(std::string("Paste As Link")); } items.push_back(std::string("Paste Separator")); @@ -677,7 +678,8 @@ void LLInvFVBridge::buildContextMenu(LLMenuGL& menu, U32 flags) { disabled_items.push_back(std::string("Share")); } - items.push_back(std::string("Open")); + + addOpenRightClickMenuOption(items); items.push_back(std::string("Properties")); getClipboardEntries(true, items, disabled_items, flags); @@ -712,7 +714,7 @@ void LLInvFVBridge::addDeleteContextMenuOptions(menuentry_vec_t &items, const LLInventoryObject *obj = getInventoryObject(); // Don't allow delete as a direct option from COF folder. - if (obj && obj->getIsLinkType() && isCOFFolder()) + if (obj && obj->getIsLinkType() && isCOFFolder() && get_is_item_worn(mUUID)) { return; } @@ -733,6 +735,17 @@ void LLInvFVBridge::addDeleteContextMenuOptions(menuentry_vec_t &items, } } +void LLInvFVBridge::addOpenRightClickMenuOption(menuentry_vec_t &items) +{ + const LLInventoryObject *obj = getInventoryObject(); + const BOOL is_link = (obj && obj->getIsLinkType()); + + if (is_link) + items.push_back(std::string("Open Original")); + else + items.push_back(std::string("Open")); +} + // *TODO: remove this BOOL LLInvFVBridge::startDrag(EDragAndDropType* type, LLUUID* id) const { @@ -840,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 @@ -1099,7 +1098,7 @@ void LLItemBridge::performAction(LLInventoryModel* model, std::string action) gotoItem(); } - if ("open" == action) + if ("open" == action || "open_original" == action) { openItem(); return; @@ -1445,17 +1444,11 @@ BOOL LLItemBridge::isItemCopyable() const return FALSE; } - if (gAgent.isGodlike()) - { - // All items can be copied in god mode since you can - // at least paste-as-link the item, though you - // still may not be able paste the item. - return TRUE; - } - else - { - return (item->getPermissions().allowCopyBy(gAgent.getID())); - } + // All items can be copied in god mode since you can + // at least paste-as-link the item, though you + // still may not be able paste the item. + return TRUE; + // return (item->getPermissions().allowCopyBy(gAgent.getID())); } return FALSE; } @@ -1596,7 +1589,8 @@ BOOL LLFolderBridge::isUpToDate() const BOOL LLFolderBridge::isItemCopyable() const { - return FALSE; + // Can copy folders to paste-as-link, but not for straight paste. + return TRUE; } BOOL LLFolderBridge::copyToClipboard() const @@ -1827,11 +1821,13 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, else { LLPointer<LLInventoryCallback> cb = NULL; + const std::string empty_description = ""; link_inventory_item( gAgent.getID(), inv_cat->getUUID(), mUUID, inv_cat->getName(), + empty_description, LLAssetType::AT_LINK_FOLDER, cb); } @@ -2500,30 +2496,29 @@ void LLFolderBridge::pasteLinkFromClipboard() ++iter) { const LLUUID &object_id = (*iter); -#if SUPPORT_ENSEMBLES if (LLInventoryCategory *cat = model->getCategory(object_id)) { + const std::string empty_description = ""; link_inventory_item( gAgent.getID(), cat->getUUID(), parent_id, cat->getName(), + empty_description, LLAssetType::AT_LINK_FOLDER, LLPointer<LLInventoryCallback>(NULL)); } - else -#endif - if (LLInventoryItem *item = model->getItem(object_id)) - { - link_inventory_item( - gAgent.getID(), - item->getLinkedUUID(), - parent_id, - item->getName(), - item->getDescription(), - LLAssetType::AT_LINK, - LLPointer<LLInventoryCallback>(NULL)); - } + else if (LLInventoryItem *item = model->getItem(object_id)) + { + link_inventory_item( + gAgent.getID(), + item->getLinkedUUID(), + parent_id, + item->getName(), + item->getDescription(), + LLAssetType::AT_LINK, + LLPointer<LLInventoryCallback>(NULL)); + } } } } @@ -3333,7 +3328,7 @@ void LLTextureBridge::buildContextMenu(LLMenuGL& menu, U32 flags) disabled_items.push_back(std::string("Share")); } - items.push_back(std::string("Open")); + addOpenRightClickMenuOption(items); items.push_back(std::string("Properties")); getClipboardEntries(true, items, disabled_items, flags); @@ -3699,7 +3694,7 @@ void LLCallingCardBridge::buildContextMenu(LLMenuGL& menu, U32 flags) { disabled_items.push_back(std::string("Share")); } - items.push_back(std::string("Open")); + addOpenRightClickMenuOption(items); items.push_back(std::string("Properties")); getClipboardEntries(true, items, disabled_items, flags); @@ -3978,7 +3973,7 @@ void LLGestureBridge::buildContextMenu(LLMenuGL& menu, U32 flags) if (!is_sidepanel) { - items.push_back(std::string("Open")); + addOpenRightClickMenuOption(items); items.push_back(std::string("Properties")); } @@ -4716,7 +4711,7 @@ void LLWearableBridge::buildContextMenu(LLMenuGL& menu, U32 flags) if (can_open && !is_sidepanel) { - items.push_back(std::string("Open")); + addOpenRightClickMenuOption(items); } if (!is_sidepanel) @@ -5177,9 +5172,13 @@ const LLUUID &LLLinkFolderBridge::getFolderID() const // static void LLInvFVBridgeAction::doAction(LLAssetType::EType asset_type, - const LLUUID& uuid,LLInventoryModel* model) + const LLUUID& uuid, + LLInventoryModel* model) { - LLInvFVBridgeAction* action = createAction(asset_type,uuid,model); + // Perform indirection in case of link. + const LLUUID& linked_uuid = gInventory.getLinkedItemID(uuid); + + LLInvFVBridgeAction* action = createAction(asset_type,linked_uuid,model); if(action) { action->doIt(); @@ -5292,7 +5291,8 @@ protected: }; -class LLNotecardBridgeAction: public LLInvFVBridgeAction +class LLNotecardBridgeAction +: public LLInvFVBridgeAction { friend class LLInvFVBridgeAction; public: diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h index f378d219f6..de63bdd76b 100644 --- a/indra/newview/llinventorybridge.h +++ b/indra/newview/llinventorybridge.h @@ -135,7 +135,7 @@ protected: menuentry_vec_t &disabled_items); virtual void addDeleteContextMenuOptions(menuentry_vec_t &items, menuentry_vec_t &disabled_items); - + virtual void addOpenRightClickMenuOption(menuentry_vec_t &items); protected: LLInvFVBridge(LLInventoryPanel* inventory, LLFolderView* root, const LLUUID& uuid); 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/llmutelist.cpp b/indra/newview/llmutelist.cpp index 95094f6b52..7cb192e026 100644 --- a/indra/newview/llmutelist.cpp +++ b/indra/newview/llmutelist.cpp @@ -145,6 +145,9 @@ std::string LLMute::getDisplayType() const case GROUP: return LLTrans::getString("MuteGroup"); break; + case EXTERNAL: + return LLTrans::getString("MuteExternal"); + break; } } @@ -303,6 +306,12 @@ BOOL LLMuteList::add(const LLMute& mute, U32 flags) void LLMuteList::updateAdd(const LLMute& mute) { + // External mutes (e.g. Avaline callers) are local only, don't send them to the server. + if (mute.mType == LLMute::EXTERNAL) + { + return; + } + // Update the database LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_UpdateMuteListEntry); @@ -390,6 +399,12 @@ BOOL LLMuteList::remove(const LLMute& mute, U32 flags) void LLMuteList::updateRemove(const LLMute& mute) { + // External mutes are not sent to the server anyway, no need to remove them. + if (mute.mType == LLMute::EXTERNAL) + { + return; + } + LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_RemoveMuteListEntry); msg->nextBlockFast(_PREHASH_AgentData); @@ -573,9 +588,14 @@ BOOL LLMuteList::saveToFile(const std::string& filename) it != mMutes.end(); ++it) { - it->mID.toString(id_string); - const std::string& name = it->mName; - fprintf(fp, "%d %s %s|%u\n", (S32)it->mType, id_string.c_str(), name.c_str(), it->mFlags); + // Don't save external mutes as they are not sent to the server and probably won't + //be valid next time anyway. + if (it->mType != LLMute::EXTERNAL) + { + it->mID.toString(id_string); + const std::string& name = it->mName; + fprintf(fp, "%d %s %s|%u\n", (S32)it->mType, id_string.c_str(), name.c_str(), it->mFlags); + } } fclose(fp); return TRUE; diff --git a/indra/newview/llmutelist.h b/indra/newview/llmutelist.h index 7cb11e6031..79b556bdbb 100644 --- a/indra/newview/llmutelist.h +++ b/indra/newview/llmutelist.h @@ -45,7 +45,8 @@ class LLMute { public: // Legacy mutes are BY_NAME and have null UUID. - enum EType { BY_NAME = 0, AGENT = 1, OBJECT = 2, GROUP = 3, COUNT = 4 }; + // EXTERNAL mutes are only processed through an external system (e.g. Voice) and not stored. + enum EType { BY_NAME = 0, AGENT = 1, OBJECT = 2, GROUP = 3, EXTERNAL = 4, COUNT = 5 }; // Bits in the mute flags. For backwards compatibility (since any mute list entries that were created before the flags existed // will have a flags field of 0), some of the flags are "inverted". 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/llpaneloutfitedit.cpp b/indra/newview/llpaneloutfitedit.cpp index dbccd243da..fe93f45c89 100644 --- a/indra/newview/llpaneloutfitedit.cpp +++ b/indra/newview/llpaneloutfitedit.cpp @@ -174,13 +174,20 @@ BOOL LLPanelOutfitEdit::postBuild() mCurrentOutfitName = getChild<LLTextBox>("curr_outfit_name"); - childSetCommitCallback("add_btn", boost::bind(&LLPanelOutfitEdit::showAddWearablesPanel, this), NULL); childSetCommitCallback("filter_button", boost::bind(&LLPanelOutfitEdit::showWearablesFilter, this), NULL); childSetCommitCallback("list_view_btn", boost::bind(&LLPanelOutfitEdit::showFilteredWearablesPanel, this), NULL); mCOFWearables = getChild<LLCOFWearables>("cof_wearables_list"); mCOFWearables->setCommitCallback(boost::bind(&LLPanelOutfitEdit::onOutfitItemSelectionChange, this)); + mCOFWearables->getCOFCallbacks().mEditWearable = boost::bind(&LLPanelOutfitEdit::onEditWearableClicked, this); + mCOFWearables->getCOFCallbacks().mDeleteWearable = boost::bind(&LLPanelOutfitEdit::onRemoveFromOutfitClicked, this); + mCOFWearables->getCOFCallbacks().mMoveWearableCloser = boost::bind(&LLPanelOutfitEdit::moveWearable, this, true); + mCOFWearables->getCOFCallbacks().mMoveWearableFurther = boost::bind(&LLPanelOutfitEdit::moveWearable, this, false); + + mCOFWearables->childSetAction("add_btn", boost::bind(&LLPanelOutfitEdit::toggleAddWearablesPanel, this)); + + mInventoryItemsPanel = getChild<LLInventoryPanel>("inventory_items"); mInventoryItemsPanel->setFilterTypes(ALL_ITEMS_MASK); mInventoryItemsPanel->setShowFolderState(LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); @@ -233,9 +240,6 @@ BOOL LLPanelOutfitEdit::postBuild() mWearableListManager = new LLFilteredWearableListManager( getChild<LLInventoryItemsList>("filtered_wearables_list"), ALL_ITEMS_MASK); - - childSetAction("move_closer_btn", boost::bind(&LLPanelOutfitEdit::moveWearable, this, true)); - childSetAction("move_further_btn", boost::bind(&LLPanelOutfitEdit::moveWearable, this, false)); return TRUE; } @@ -252,9 +256,9 @@ void LLPanelOutfitEdit::moveWearable(bool closer_to_body) updateLookInfo(); } -void LLPanelOutfitEdit::showAddWearablesPanel() +void LLPanelOutfitEdit::toggleAddWearablesPanel() { - childSetVisible("add_wearables_panel", childGetValue("add_btn")); + childSetVisible("add_wearables_panel", !childIsVisible("add_wearables_panel")); } void LLPanelOutfitEdit::showWearablesFilter() diff --git a/indra/newview/llpaneloutfitedit.h b/indra/newview/llpaneloutfitedit.h index 21fa849289..3d01303ee1 100644 --- a/indra/newview/llpaneloutfitedit.h +++ b/indra/newview/llpaneloutfitedit.h @@ -90,7 +90,7 @@ public: void moveWearable(bool closer_to_body); - void showAddWearablesPanel(); + void toggleAddWearablesPanel(); void showWearablesFilter(); void showFilteredWearablesPanel(); void saveOutfit(bool as_new = false); 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/llparticipantlist.cpp b/indra/newview/llparticipantlist.cpp index c3748ca81d..a058548459 100644 --- a/indra/newview/llparticipantlist.cpp +++ b/indra/newview/llparticipantlist.cpp @@ -722,7 +722,21 @@ void LLParticipantList::LLParticipantListMenu::toggleMute(const LLSD& userdata, name = speakerp->mDisplayName; - LLMute mute(speaker_id, name, speakerp->mType == LLSpeaker::SPEAKER_AGENT ? LLMute::AGENT : LLMute::OBJECT); + LLMute::EType mute_type; + switch (speakerp->mType) + { + case LLSpeaker::SPEAKER_AGENT: + mute_type = LLMute::AGENT; + break; + case LLSpeaker::SPEAKER_OBJECT: + mute_type = LLMute::OBJECT; + break; + case LLSpeaker::SPEAKER_EXTERNAL: + default: + mute_type = LLMute::EXTERNAL; + break; + } + LLMute mute(speaker_id, name, mute_type); if (!is_muted) { 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/llselectmgr.cpp b/indra/newview/llselectmgr.cpp index d03a492cd1..2c26bada20 100644 --- a/indra/newview/llselectmgr.cpp +++ b/indra/newview/llselectmgr.cpp @@ -3624,14 +3624,14 @@ void LLSelectMgr::sendAttach(U8 attachment_point) return; } -#if ENABLE_MULTIATTACHMENTS - attachment_point |= ATTACHMENT_ADD; -#endif BOOL build_mode = LLToolMgr::getInstance()->inEdit(); // Special case: Attach to default location for this object. if (0 == attachment_point || get_if_there(gAgentAvatarp->mAttachmentPoints, (S32)attachment_point, (LLViewerJointAttachment*)NULL)) { +#if ENABLE_MULTIATTACHMENTS + attachment_point |= ATTACHMENT_ADD; +#endif sendListToRegions( "ObjectAttach", packAgentIDAndSessionAndAttachment, diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index e64696b120..3d447dd411 100644 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -167,8 +167,6 @@ public: } protected: - LLTextureFetchWorker(LLTextureFetch* fetcher, const LLUUID& id, const LLHost& host, - F32 priority, S32 discard, S32 size); LLTextureFetchWorker(LLTextureFetch* fetcher, const std::string& url, const LLUUID& id, const LLHost& host, F32 priority, S32 discard, S32 size); @@ -215,8 +213,15 @@ private: QUEUED = 1, SENT_SIM = 2 }; + enum e_write_to_cache_state //mWriteToCacheState + { + NOT_WRITE = 0, + CAN_WRITE = 1, + SHOULD_WRITE = 2 + }; static const char* sStateDescs[]; e_state mState; + e_write_to_cache_state mWriteToCacheState; LLTextureFetch* mFetcher; LLPointer<LLImageFormatted> mFormattedImage; LLPointer<LLImageRaw> mRawImage; @@ -377,6 +382,7 @@ LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher, S32 size) // Desired size : LLWorkerClass(fetcher, "TextureFetch"), mState(INIT), + mWriteToCacheState(NOT_WRITE), mFetcher(fetcher), mID(id), mHost(host), @@ -595,7 +601,7 @@ bool LLTextureFetchWorker::doWork(S32 param) } if (mState == INIT) - { + { mRawImage = NULL ; mRequestedDiscard = -1; mLoadedDiscard = -1; @@ -636,17 +642,18 @@ bool LLTextureFetchWorker::doWork(S32 param) mFileSize = 0; mLoaded = FALSE; setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it - - CacheReadResponder* responder = new CacheReadResponder(mFetcher, mID, mFormattedImage); + if (mUrl.compare(0, 7, "file://") == 0) { // read file from local disk std::string filename = mUrl.substr(7, std::string::npos); + CacheReadResponder* responder = new CacheReadResponder(mFetcher, mID, mFormattedImage); mCacheReadHandle = mFetcher->mTextureCache->readFromCache(filename, mID, cache_priority, offset, size, responder); } else if (mUrl.empty()) { + CacheReadResponder* responder = new CacheReadResponder(mFetcher, mID, mFormattedImage); mCacheReadHandle = mFetcher->mTextureCache->readFromCache(mID, cache_priority, offset, size, responder); } @@ -659,8 +666,6 @@ bool LLTextureFetchWorker::doWork(S32 param) } setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); mState = SEND_HTTP_REQ; - delete responder; - responder = NULL; } } @@ -694,6 +699,7 @@ bool LLTextureFetchWorker::doWork(S32 param) llassert_always(mFormattedImage->getDataSize() > 0); mLoadedDiscard = mDesiredDiscard; mState = DECODE_IMAGE; + mWriteToCacheState = NOT_WRITE ; LL_DEBUGS("Texture") << mID << ": Cached. Bytes: " << mFormattedImage->getDataSize() << " Size: " << llformat("%dx%d",mFormattedImage->getWidth(),mFormattedImage->getHeight()) << " Desired Discard: " << mDesiredDiscard << " Desired Size: " << mDesiredSize << LL_ENDL; @@ -735,6 +741,7 @@ bool LLTextureFetchWorker::doWork(S32 param) if (!http_url.empty()) { mUrl = http_url + "/?texture_id=" + mID.asString().c_str(); + mWriteToCacheState = CAN_WRITE ; //because this texture has a fixed texture id. } } else @@ -747,12 +754,17 @@ bool LLTextureFetchWorker::doWork(S32 param) { mState = LLTextureFetchWorker::SEND_HTTP_REQ; setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); + if(mWriteToCacheState != NOT_WRITE) + { + mWriteToCacheState = CAN_WRITE ; + } // don't return, fall through to next state } else if (mSentRequest == UNSENT) { // Add this to the network queue and sit here. // LLTextureFetch::update() will send off a request which will change our state + mWriteToCacheState = CAN_WRITE ; mRequestedSize = mDesiredSize; mRequestedDiscard = mDesiredDiscard; mSentRequest = QUEUED; @@ -789,6 +801,7 @@ bool LLTextureFetchWorker::doWork(S32 param) } setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); mState = DECODE_IMAGE; + mWriteToCacheState = SHOULD_WRITE ; } else { @@ -850,7 +863,6 @@ bool LLTextureFetchWorker::doWork(S32 param) mState = WAIT_HTTP_REQ; mFetcher->addToHTTPQueue(mID); - mSentRequest = QUEUED; // Will call callbackHttpGet when curl request completes std::vector<std::string> headers; headers.push_back("Accept: image/x-j2c"); @@ -933,15 +945,15 @@ bool LLTextureFetchWorker::doWork(S32 param) } llassert_always(mBufferSize == cur_size + mRequestedSize); - if (mHaveAllData) + if (mHaveAllData && mRequestedDiscard == 0) //the image file is fully loaded. { mFileSize = mBufferSize; } - else //the file size is unknown + else //the file size is unknown. { - mFileSize = S32_MAX ; //flag the file is not fully loaded. + mFileSize = mBufferSize + 1 ; //flag the file is not fully loaded. } - + U8* buffer = new U8[mBufferSize]; if (cur_size > 0) { @@ -956,6 +968,10 @@ bool LLTextureFetchWorker::doWork(S32 param) mBufferSize = 0; mLoadedDiscard = mRequestedDiscard; mState = DECODE_IMAGE; + if(mWriteToCacheState != NOT_WRITE) + { + mWriteToCacheState = SHOULD_WRITE ; + } setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); return false; } @@ -1055,7 +1071,7 @@ bool LLTextureFetchWorker::doWork(S32 param) if (mState == WRITE_TO_CACHE) { - if (mInLocalCache || mSentRequest == UNSENT || mFormattedImage.isNull()) + if (mWriteToCacheState != SHOULD_WRITE || mFormattedImage.isNull()) { // If we're in a local cache or we didn't actually receive any new data, // or we failed to load anything, skip @@ -1063,6 +1079,17 @@ bool LLTextureFetchWorker::doWork(S32 param) return false; } S32 datasize = mFormattedImage->getDataSize(); + if(mFileSize < datasize)//This could happen when http fetching and sim fetching mixed. + { + if(mHaveAllData) + { + mFileSize = datasize ; + } + else + { + mFileSize = datasize + 1 ; //flag not fully loaded. + } + } llassert_always(datasize); setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it U32 cache_priority = mWorkPriority; diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index 3c0345df90..a4d8dddfe4 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -732,10 +732,17 @@ static bool proximity_comparitor(const LLViewerMediaImpl* i1, const LLViewerMedi } } +static LLFastTimer::DeclareTimer FTM_MEDIA_UPDATE("Update Media"); + ////////////////////////////////////////////////////////////////////////////////////////// // static void LLViewerMedia::updateMedia(void *dummy_arg) { + LLFastTimer t1(FTM_MEDIA_UPDATE); + + // Enable/disable the plugin read thread + LLPluginProcessParent::setUseReadThread(gSavedSettings.getBOOL("PluginUseReadThread")); + sAnyMediaShowing = false; sUpdatedCookies = getCookieStore()->getChangedCookies(); if(!sUpdatedCookies.empty()) @@ -1914,7 +1921,15 @@ void LLViewerMediaImpl::updateVolume() { if(mMediaSource) { - mMediaSource->setVolume(mRequestedVolume * LLViewerMedia::getVolume()); + F32 attenuation_multiplier = 1.0; + + if (mProximityDistance > 0) + { + // the attenuation multiplier should never be more than one since that would increase volume + attenuation_multiplier = llmin(1.0, gSavedSettings.getF32("MediaRollOffFactor")/mProximityDistance); + } + + mMediaSource->setVolume(mRequestedVolume * LLViewerMedia::getVolume() * attenuation_multiplier); } } @@ -2427,6 +2442,8 @@ void LLViewerMediaImpl::update() } else { + updateVolume(); + // If we didn't just create the impl, it may need to get cookie updates. if(!sUpdatedCookies.empty()) { diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 3d042a8d8c..5dd9623955 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -1091,7 +1091,7 @@ void open_inventory_offer(const uuid_vec_t& items, const std::string& from_name) //////////////////////////////////////////////////////////////////////////////// // Special handling for various types. - const LLAssetType::EType asset_type = item->getType(); + const LLAssetType::EType asset_type = item->getActualType(); if (check_offer_throttle(from_name, false)) // If we are throttled, don't display { LL_DEBUGS("Messaging") << "Highlighting inventory item: " << item->getUUID() << LL_ENDL; diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp index 298ce3fcec..542ec16547 100644 --- a/indra/newview/llvoiceclient.cpp +++ b/indra/newview/llvoiceclient.cpp @@ -5143,9 +5143,6 @@ LLVoiceClient::participantState *LLVoiceClient::sessionState::addParticipant(con { result->mAvatarIDValid = true; result->mAvatarID = id; - - if(result->updateMuteState()) - mMuteDirty = true; } else { @@ -5154,7 +5151,12 @@ LLVoiceClient::participantState *LLVoiceClient::sessionState::addParticipant(con setUUIDFromStringHash(result->mAvatarID, uri); } } - + + if(result->updateMuteState()) + { + mMuteDirty = true; + } + mParticipantsByUUID.insert(participantUUIDMap::value_type(&(result->mAvatarID), result)); if (LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(result->mAvatarID, result->mVolume)) @@ -5173,15 +5175,12 @@ bool LLVoiceClient::participantState::updateMuteState() { bool result = false; - if(mAvatarIDValid) + bool isMuted = LLMuteList::getInstance()->isMuted(mAvatarID, LLMute::flagVoiceChat); + if(mOnMuteList != isMuted) { - bool isMuted = LLMuteList::getInstance()->isMuted(mAvatarID, LLMute::flagVoiceChat); - if(mOnMuteList != isMuted) - { - mOnMuteList = isMuted; - mVolumeDirty = true; - result = true; - } + mOnMuteList = isMuted; + mVolumeDirty = true; + result = true; } return result; } diff --git a/indra/newview/llwearableitemslist.cpp b/indra/newview/llwearableitemslist.cpp index 3d110dcc78..bd5d8d9357 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,204 @@ 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; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +// 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; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +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 +290,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..29532a15c1 100644 --- a/indra/newview/llwearableitemslist.h +++ b/indra/newview/llwearableitemslist.h @@ -36,6 +36,117 @@ // 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) { setShowWidget("btn_delete", show); } + inline void setShowMoveUpButton(bool show) { setShowWidget("btn_move_up", show); } + + inline void setShowMoveDownButton(bool show) { setShowWidget("btn_move_down", show); } + inline void setShowLockButton(bool show) { setShowWidget("btn_lock", show); } + inline void setShowEditButton(bool show) { setShowWidget("btn_edit", 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) { setShowWidget("btn_lock", show); } + inline void setShowEditButton(bool show) { setShowWidget("btn_edit", 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/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_inventory.xml b/indra/newview/skins/default/xui/en/menu_inventory.xml index 5e1f6b58e8..11459ad0e6 100644 --- a/indra/newview/skins/default/xui/en/menu_inventory.xml +++ b/indra/newview/skins/default/xui/en/menu_inventory.xml @@ -410,6 +410,14 @@ parameter="open" /> </menu_item_call> <menu_item_call + label="Open Original" + layout="topleft" + name="Open Original"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="open_original" /> + </menu_item_call> + <menu_item_call label="Properties" layout="topleft" name="Properties"> 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_adhoc_control_panel.xml b/indra/newview/skins/default/xui/en/panel_adhoc_control_panel.xml index 28a6995186..e70abc0975 100644 --- a/indra/newview/skins/default/xui/en/panel_adhoc_control_panel.xml +++ b/indra/newview/skins/default/xui/en/panel_adhoc_control_panel.xml @@ -37,7 +37,7 @@ layout="topleft" name="speakers_list" opaque="false" - show_info_btn="false" + show_info_btn="true" show_profile_btn="false" show_speaking_indicator="false" width="147" /> 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..115964e5f2 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_body_parts_list_item.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + follows="top|right|left" + height="22" + 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 + background_visible="true" + bg_alpha_color="0.4 0.4 0.4 1.0" + bottom="0" + follows="left|right|top" + height="1" + layout="bottomleft" + left="0" + name="wearable_type_separator_panel" + width="380"/> +</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..7cc9c46c08 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_clothing_list_item.xml @@ -0,0 +1,115 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + follows="top|right|left" + height="23" + 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 + background_visible="true" + bg_alpha_color="0.4 0.4 0.4 1.0" + bottom="0" + follows="left|right|top" + height="1" + layout="bottomleft" + left="0" + name="wearable_type_separator_panel" + visible="false" + width="380"/> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_cof_wearables.xml b/indra/newview/skins/default/xui/en/panel_cof_wearables.xml index d8a8dbbea4..e13847e412 100644 --- a/indra/newview/skins/default/xui/en/panel_cof_wearables.xml +++ b/indra/newview/skins/default/xui/en/panel_cof_wearables.xml @@ -3,7 +3,6 @@ background_visible="true" bg_alpha_color="DkGray" border="false" - bottom="0" follows="all" height="200" left="0" @@ -11,7 +10,7 @@ width="313"> <accordion follows="all" - height="373" + height="200" layout="topleft" left="3" single_expansion="true" @@ -27,7 +26,7 @@ <flat_list_view allow_select="true" follows="all" - height="150" + height="10" layout="topleft" left="0" name="list_attachments" @@ -38,29 +37,71 @@ layout="topleft" name="tab_clothing" title="Clothing"> - <flat_list_view - allow_select="true" + + <!-- *NOTE there should be no any gaps between the button bar and the list - + accordiong-list adaptor won't employ them while calculating new height when the size of the list changes --> + <panel + background_visible="false" + class="accordion_list_adaptor" follows="all" - height="150" + height="45" layout="topleft" left="0" - name="list_clothing" + name="button_bar_adaptor" top="0" - width="307" /> + width="307"> + <panel + bevel="none" + filename="panel_clothing_list_button_bar.xml" + height="35" + name="button_bar" + top="0" + width="307" /> + <flat_list_view + allow_select="true" + follows="all" + height="10" + layout="topleft" + left="0" + name="list_clothing" + top_pad="0" + width="307" /> + </panel> </accordion_tab> <accordion_tab layout="topleft" name="tab_body_parts" title="Body Parts"> - <flat_list_view - allow_select="true" + + <!-- *NOTE there should be no any gaps between the button bar and the list - + accordiong-list adaptor won't employ them while calculating new height when the size of the list changes --> + <panel + background_visible="false" + class="accordion_list_adaptor" follows="all" - height="150" + height="45" layout="topleft" left="0" - name="list_body_parts" + name="button_bar_adaptor" top="0" - width="307" /> + width="307"> + <panel + bevel="none" + filename="panel_bodyparts_list_button_bar.xml" + height="35" + name="button_bar" + top="0" + width="307"/> + <flat_list_view + allow_select="true" + follows="all" + height="10" + layout="topleft" + left="0" + name="list_body_parts" + top_pad="0" + width="307" /> + </panel> </accordion_tab> </accordion> </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..c5a60ced88 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_dummy_clothing_list_item.xml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + follows="top|right|left" + height="22" + 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 + background_visible="true" + bg_alpha_color="0.4 0.4 0.4 1.0" + bottom="0" + follows="left|right|top" + height="1" + layout="bottomleft" + left="0" + name="wearable_type_separator_panel" + width="380"/> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_group_control_panel.xml b/indra/newview/skins/default/xui/en/panel_group_control_panel.xml index aa7d621e4c..c1dc2aaaf7 100644 --- a/indra/newview/skins/default/xui/en/panel_group_control_panel.xml +++ b/indra/newview/skins/default/xui/en/panel_group_control_panel.xml @@ -36,7 +36,7 @@ layout="topleft" name="speakers_list" opaque="false" - show_info_btn="false" + show_info_btn="true" show_profile_btn="false" show_speaking_indicator="false" width="145" /> diff --git a/indra/newview/skins/default/xui/en/panel_main_inventory.xml b/indra/newview/skins/default/xui/en/panel_main_inventory.xml index 27d66945d9..46625144e1 100644 --- a/indra/newview/skins/default/xui/en/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/en/panel_main_inventory.xml @@ -3,7 +3,7 @@ background_visible="true" default_tab_group="1" follows="all" - height="408" + height="423" label="Things" layout="topleft" min_height="350" @@ -48,7 +48,7 @@ left="10" max_length="300" name="inventory search editor" - top="3" + top="18" width="303" /> <tab_container bg_alpha_color="DkGray" 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..1da9304f03 100644 --- a/indra/newview/skins/default/xui/en/panel_outfit_edit.xml +++ b/indra/newview/skins/default/xui/en/panel_outfit_edit.xml @@ -130,7 +130,7 @@ animate="false" default_tab_group="2" follows="all" - height="470" + height="450" width="300" layout="topleft" orientation="vertical" @@ -140,8 +140,7 @@ left="5"> <layout_panel layout="topleft" - follows="left|top|right" - height="220" + height="225" label="IM Control Panel" min_height="100" name="outfit_wearables_panel" @@ -151,27 +150,26 @@ <!-- List containing items from the COF and Base outfit --> <panel - background_visible="false" class="cof_wearables" filename="panel_cof_wearables.xml" follows="left|top|right|bottom" - height="193" + height="198" layout="topleft" left="0" name="cof_wearables_list" top="0" width="300" /> - <panel + <panel background_visible="true" bevel_style="none" + bottom="0" follows="bottom|left|right" height="27" label="bottom_panel" - layout="topleft" + layout="bottomleft" left="0" name="edit_panel" - top_pad="0" width="300"> <button follows="bottom|left" @@ -186,11 +184,10 @@ top="1" width="31" /> <button - is_toggle="true" follows="bottom|left" height="25" image_hover_unselected="Toolbar_Middle_Over" - image_overlay="AddItem_Off" + image_overlay="" image_selected="Toolbar_Middle_Selected" image_unselected="Toolbar_Middle_Off" layout="topleft" @@ -214,7 +211,7 @@ follows="bottom|left" height="25" image_hover_unselected="Toolbar_Middle_Over" - image_overlay="Movement_Forward_On" + image_overlay="" image_selected="Toolbar_Middle_Selected" image_unselected="Toolbar_Middle_Off" layout="topleft" @@ -226,7 +223,7 @@ follows="bottom|left" height="25" image_hover_unselected="Toolbar_Middle_Over" - image_overlay="Movement_Backward_On" + image_overlay="" image_selected="Toolbar_Middle_Selected" image_unselected="Toolbar_Middle_Off" layout="topleft" @@ -234,6 +231,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" @@ -253,8 +258,8 @@ <layout_panel auto_resize="true" default_tab_group="3" - height="210" - min_height="210" + height="225" + min_height="225" name="add_wearables_panel" width="300" tab_group="2" @@ -440,6 +445,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..f544449d02 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> @@ -2149,10 +2163,11 @@ Clears (deletes) the media and all params from the given face. <string name="BusyModeResponseDefault">The Resident you messaged is in 'busy mode' which means they have requested not to be disturbed. Your message will still be shown in their IM panel for later viewing.</string> <!-- Mute --> - <string name="MuteByName">(by name)</string> + <string name="MuteByName">(By name)</string> <string name="MuteAgent">(Resident)</string> - <string name="MuteObject">(object)</string> - <string name="MuteGroup">(group)</string> + <string name="MuteObject">(Object)</string> + <string name="MuteGroup">(Group)</string> + <string name="MuteExternal">(External)</string> <!-- Region/Estate Covenant --> <string name="RegionNoCovenant">There is no Covenant provided for this Estate.</string> diff --git a/indra/newview/skins/default/xui/it/menu_participant_list.xml b/indra/newview/skins/default/xui/it/menu_participant_list.xml index e641d38508..0da1d116b4 100644 --- a/indra/newview/skins/default/xui/it/menu_participant_list.xml +++ b/indra/newview/skins/default/xui/it/menu_participant_list.xml @@ -15,6 +15,6 @@ <menu_item_call label="Disattiva audio di questo participante" name="ModerateVoiceMuteSelected"/> <menu_item_call label="Disattiva audio di tutti gli altri" name="ModerateVoiceMuteOthers"/> <menu_item_call label="Riattiva audio di questo participante" name="ModerateVoiceUnMuteSelected"/> - <menu_item_call label="Disattiva audio di tutti gli altri" name="ModerateVoiceUnMuteOthers"/> + <menu_item_call label="Riattiva audio di tutti gli altri" name="ModerateVoiceUnMuteOthers"/> </context_menu> </context_menu> diff --git a/indra/newview/skins/default/xui/it/notifications.xml b/indra/newview/skins/default/xui/it/notifications.xml index 6736c6a6f1..f1b87bc002 100644 --- a/indra/newview/skins/default/xui/it/notifications.xml +++ b/indra/newview/skins/default/xui/it/notifications.xml @@ -898,7 +898,7 @@ Unisci il terreno? In genere si tratta di un problema temporaneo. Attendi alcuni minuti per modificare e salvare nuovamente gli elementi indossabili. </notification> <notification name="YouHaveBeenLoggedOut"> - Accidenti. Sei stato scollegato da [SECOND_LIFE] + Sei stato scollegato da [SECOND_LIFE]. [MESSAGE] <usetemplate name="okcancelbuttons" notext="Esci" yestext="Vedi IM & Chat"/> </notification> diff --git a/indra/newview/skins/default/xui/it/panel_edit_pick.xml b/indra/newview/skins/default/xui/it/panel_edit_pick.xml index d2d97cfc71..f93b953eac 100644 --- a/indra/newview/skins/default/xui/it/panel_edit_pick.xml +++ b/indra/newview/skins/default/xui/it/panel_edit_pick.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> -<panel label="Modifica scelta" name="panel_edit_pick"> +<panel label="Modifica preferito" name="panel_edit_pick"> <panel.string name="location_notice"> (si aggiornerà dopo il salvataggio) </panel.string> <text name="title"> - Modifica scelta + Modifica preferito </text> <scroll_container name="profile_scroll"> <panel name="scroll_content_panel"> diff --git a/indra/newview/skins/default/xui/it/panel_preferences_advanced.xml b/indra/newview/skins/default/xui/it/panel_preferences_advanced.xml index c24d3f656a..1d05f28d46 100644 --- a/indra/newview/skins/default/xui/it/panel_preferences_advanced.xml +++ b/indra/newview/skins/default/xui/it/panel_preferences_advanced.xml @@ -13,10 +13,10 @@ </text> <check_box label="Costruire/Modificare" name="edit_camera_movement" tool_tip="Utilizza il posizionamento automatico della fotocamera entrando o uscendo dalla modalità modifica"/> <check_box label="Aspetto fisico" name="appearance_camera_movement" tool_tip="Utilizza il posizionamento automatico della camera in modalità modifica"/> - <check_box label="Mostra in modalità Mouselook" name="first_person_avatar_visible"/> + <check_box label="Visualizzami in modalità soggettiva" name="first_person_avatar_visible"/> <check_box label="Le frecce di direzione mi fanno sempre spostare" name="arrow_keys_move_avatar_check"/> <check_box label="Doppio click e tieni premuto per correre" name="tap_tap_hold_to_run"/> - <check_box label="Consente il movimento delle labbra dell'avatar quando parla" name="enable_lip_sync"/> + <check_box label="Movimento delle labbra dell'avatar quando parla" name="enable_lip_sync"/> <check_box label="Chat a bolla" name="bubble_text_chat"/> <slider label="Opacità" name="bubble_chat_opacity"/> <color_swatch name="background" tool_tip="Scegli il colore delle vignette della chat"/> diff --git a/indra/newview/skins/default/xui/it/strings.xml b/indra/newview/skins/default/xui/it/strings.xml index a1b570d716..de9c5ba45b 100644 --- a/indra/newview/skins/default/xui/it/strings.xml +++ b/indra/newview/skins/default/xui/it/strings.xml @@ -886,13 +886,13 @@ Alto </string> <string name="LeaveMouselook"> - Premi ESC per tornare in visulizzazione normale + Premi ESC per tornare in visualizzazione normale </string> <string name="InventoryNoMatchingItems"> Nessun oggetto corrispondente trovato in inventario. Prova [secondlife:///app/search/groups "Cerca"]. </string> <string name="FavoritesNoMatchingItems"> - Trascina qui un punto di riferimento per aggiungerlo ai tuoi preferiti. + Trascina qui un punto di riferimento per aggiungerlo ai Preferiti. </string> <string name="InventoryNoTexture"> Non hai una copia di questa texture nel tuo inventario @@ -1566,7 +1566,7 @@ (si aggiornerà dopo la pubblicazione) </string> <string name="NoPicksClassifiedsText"> - Non hai creato luoghi preferiti né inserzioni. Clicca il pulsante più qui sotto per creare un luogo preferito o un'inserzione. + Non hai creato luoghi preferiti né inserzioni. Clicca il pulsante + qui sotto per creare un luogo preferito o un'inserzione. </string> <string name="NoAvatarPicksClassifiedsText"> L'utente non ha luoghi preferiti né inserzioni diff --git a/indra/newview/skins/default/xui/it/teleport_strings.xml b/indra/newview/skins/default/xui/it/teleport_strings.xml index c11d41f6b9..7a1046abd3 100644 --- a/indra/newview/skins/default/xui/it/teleport_strings.xml +++ b/indra/newview/skins/default/xui/it/teleport_strings.xml @@ -66,7 +66,7 @@ Se si continua a visualizzare questo messaggio, consulta la pagina [SUPPORT_SITE Elaborazione della destinazione in corso... </message> <message name="contacting"> - Contattando la nuova regione. + Contatto in corso con la nuova regione. </message> <message name="arriving"> In arrivo a destinazione... 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; |