diff options
60 files changed, 2543 insertions, 247 deletions
diff --git a/doc/contributions.txt b/doc/contributions.txt index 62210f08f4..2361123343 100644 --- a/doc/contributions.txt +++ b/doc/contributions.txt @@ -18,6 +18,7 @@ Alissa Sabre VWR-171 VWR-177 VWR-213 + VWR-250 VWR-251 VWR-286 VWR-414 @@ -55,6 +56,7 @@ Catherine Pfeffer Dale Glass VWR-120 VWR-560 + VWR-2502 VWR-1358 VWR-2041 Drewan Keats @@ -91,6 +93,8 @@ Feep Larsson VWR-1314 Fremont Cunningham VWR-1147 +Gudmund Shepherd + VWR-1873 Gigs Taggart SVC-493 VWR-38 @@ -106,6 +110,7 @@ Gigs Taggart VWR-1434 VWR-1987 VWR-2065 + VWR-2502 Ginko Bayliss VWR-4 Grazer Kline @@ -216,7 +221,9 @@ Nicholaz Beresford VWR-1872 VWR-1968 VWR-2046 + VWR-2142 VWR-2152 + VWR-2614 Nounouch Hapmouche VWR-238 Paul Churchill diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py index 2408fab96f..a73fc6c293 100644 --- a/indra/lib/python/indra/util/llmanifest.py +++ b/indra/lib/python/indra/util/llmanifest.py @@ -28,6 +28,7 @@ $/LicenseInfo$ """ import commands +import errno import filecmp import fnmatch import getopt diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 6dab598341..9895a684b2 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -239,6 +239,84 @@ LLWString utf16str_to_wstring(const llutf16string &utf16str) return utf16str_to_wstring(utf16str, len); } +// Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. +S32 utf16str_wstring_length(const llutf16string &utf16str, const S32 utf16_len) +{ + S32 surrogate_pairs = 0; + // ... craziness to make gcc happy (llutf16string.c_str() is tweaked on linux): + const U16 *const utf16_chars = &(*(utf16str.begin())); + S32 i = 0; + while (i < utf16_len) + { + const U16 c = utf16_chars[i++]; + if (c >= 0xD800 && c <= 0xDBFF) // See http://en.wikipedia.org/wiki/UTF-16 + { // Have first byte of a surrogate pair + if (i >= utf16_len) + { + break; + } + const U16 d = utf16_chars[i]; + if (d >= 0xDC00 && d <= 0xDFFF) + { // Have valid second byte of a surrogate pair + surrogate_pairs++; + i++; + } + } + } + return utf16_len - surrogate_pairs; +} + +// Length in utf16string (UTF-16) of wlen wchars beginning at woffset. +S32 wstring_utf16_length(const LLWString &wstr, const S32 woffset, const S32 wlen) +{ + const S32 end = llmin((S32)wstr.length(), woffset + wlen); + if (end < woffset) + { + return 0; + } + else + { + S32 length = end - woffset; + for (S32 i = woffset; i < end; i++) + { + if (wstr[i] >= 0x10000) + { + length++; + } + } + return length; + } +} + +// Given a wstring and an offset in it, returns the length as wstring (i.e., +// number of llwchars) of the longest substring that starts at the offset +// and whose equivalent utf-16 string does not exceeds the given utf16_length. +S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, const S32 woffset, const S32 utf16_length, BOOL *unaligned) +{ + const S32 end = wstr.length(); + BOOL u = FALSE; + S32 n = woffset + utf16_length; + S32 i = woffset; + while (i < end) + { + if (wstr[i] >= 0x10000) + { + --n; + } + if (i >= n) + { + u = (i > n); + break; + } + i++; + } + if (unaligned) + { + *unaligned = u; + } + return i - woffset; +} + S32 wchar_utf8_length(const llwchar wc) { if (wc < 0x80) diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 70f7d5483e..ec4ff335c9 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -433,6 +433,15 @@ S32 wchar_utf8_length(const llwchar wc); std::string utf8str_tolower(const std::string& utf8str); +// Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. +S32 utf16str_wstring_length(const llutf16string &utf16str, S32 len); + +// Length in utf16string (UTF-16) of wlen wchars beginning at woffset. +S32 wstring_utf16_length(const LLWString & wstr, S32 woffset, S32 wlen); + +// Length in wstring (i.e., llwchar count) of a part of a wstring specified by utf16 length (i.e., utf16 units.) +S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset, S32 utf16_length, BOOL *unaligned = NULL); + /** * @brief Properly truncate a utf8 string to a maximum byte count. * diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp index 4dea08505d..7c8fe86d2b 100644 --- a/indra/llmath/llvolume.cpp +++ b/indra/llmath/llvolume.cpp @@ -86,7 +86,7 @@ const S32 SCULPT_REZ_2 = 8; const S32 SCULPT_REZ_3 = 16; const S32 SCULPT_REZ_4 = 32; -const F32 SCULPT_MIN_AREA = 0.005f; +const F32 SCULPT_MIN_AREA = 0.002f; BOOL check_same_clock_dir( const LLVector3& pt1, const LLVector3& pt2, const LLVector3& pt3, const LLVector3& norm) { @@ -1834,9 +1834,9 @@ inline LLVector3 sculpt_rgb_to_vector(U8 r, U8 g, U8 b) { // maps RGB values to vector values [0..255] -> [-0.5..0.5] LLVector3 value; - value.mV[VX] = r / 256.f - 0.5f; - value.mV[VY] = g / 256.f - 0.5f; - value.mV[VZ] = b / 256.f - 0.5f; + value.mV[VX] = r / 255.f - 0.5f; + value.mV[VY] = g / 255.f - 0.5f; + value.mV[VZ] = b / 255.f - 0.5f; return value; } diff --git a/indra/llmessage/lltransfermanager.cpp b/indra/llmessage/lltransfermanager.cpp index e80bc8cce8..ff4f8a2e66 100644 --- a/indra/llmessage/lltransfermanager.cpp +++ b/indra/llmessage/lltransfermanager.cpp @@ -106,10 +106,15 @@ void LLTransferManager::cleanup() void LLTransferManager::updateTransfers() { - host_tc_map::iterator iter; - for (iter = mTransferConnections.begin(); iter != mTransferConnections.end(); iter++) + host_tc_map::iterator iter,cur; + + iter = mTransferConnections.begin(); + + while (iter !=mTransferConnections.end()) { - iter->second->updateTransfers(); + cur = iter; + iter++; + cur->second->updateTransfers(); } } @@ -571,7 +576,6 @@ void LLTransferManager::processTransferAbort(LLMessageSystem *msgp, void **) msgp->getUUID("TransferInfo", "TransferID", transfer_id); msgp->getS32("TransferInfo", "ChannelType", (S32 &)channel_type); - // See if it's a target that we're trying to abort // Find the transfer associated with this packet. LLTransferTargetChannel *ttcp = gTransferManager.getTargetChannel(msgp->getSender(), channel_type); @@ -651,10 +655,14 @@ LLTransferConnection::~LLTransferConnection() void LLTransferConnection::updateTransfers() { // Do stuff for source transfers (basically, send data out). - tsc_iter iter; - for (iter = mTransferSourceChannels.begin(); iter != mTransferSourceChannels.end(); iter++) + tsc_iter iter, cur; + iter = mTransferSourceChannels.begin(); + + while (iter !=mTransferSourceChannels.end()) { - (*iter)->updateTransfers(); + cur = iter; + iter++; + (*cur)->updateTransfers(); } // Do stuff for target transfers @@ -768,14 +776,16 @@ void LLTransferSourceChannel::updateTransfers() return; } - LLPriQueueMap<LLTransferSource *>::pqm_iter iter; - + LLPriQueueMap<LLTransferSource *>::pqm_iter iter, next; BOOL done = FALSE; for (iter = mTransferSources.mMap.begin(); (iter != mTransferSources.mMap.end()) && !done;) { //llinfos << "LLTransferSourceChannel::updateTransfers()" << llendl; - // Do stuff. + // Do stuff. + next = iter; + next++; + LLTransferSource *tsp = iter->second; U8 *datap = NULL; S32 data_size = 0; @@ -793,11 +803,12 @@ void LLTransferSourceChannel::updateTransfers() // We don't have any data, but we're not done, just go on. // This will presumably be used for streaming or async transfers that // are stalled waiting for data from another source. - iter++; + iter=next; continue; } LLUUID *cb_uuid = new LLUUID(tsp->getID()); + LLUUID transaction_id = tsp->getID(); // Send the data now, even if it's an error. // The status code will tell the other end what to do. @@ -822,7 +833,17 @@ void LLTransferSourceChannel::updateTransfers() delete[] datap; datap = NULL; } - + + if (findTransferSource(transaction_id) == NULL) + { + //Warning! In the case of an aborted transfer, the sendReliable call above calls + //AbortTransfer which in turn calls deleteTransfer which means that somewhere way + //down the chain our current iter can get invalidated resulting in an infrequent + //sim crash. This check gets us to a valid transfer source in this event. + iter=next; + continue; + } + // Update the packet counter tsp->setLastPacketID(packet_id); @@ -839,7 +860,8 @@ void LLTransferSourceChannel::updateTransfers() tsp->completionCallback(status); delete tsp; - mTransferSources.mMap.erase(iter++); + mTransferSources.mMap.erase(iter); + iter = next; break; default: llerrs << "Unknown transfer error code!" << llendl; @@ -876,23 +898,20 @@ LLTransferSource *LLTransferSourceChannel::findTransferSource(const LLUUID &tran BOOL LLTransferSourceChannel::deleteTransfer(LLTransferSource *tsp) { + LLPriQueueMap<LLTransferSource *>::pqm_iter iter; for (iter = mTransferSources.mMap.begin(); iter != mTransferSources.mMap.end(); iter++) { if (iter->second == tsp) { - break; + delete tsp; + mTransferSources.mMap.erase(iter); + return TRUE; } } - if (iter == mTransferSources.mMap.end()) - { - llerrs << "Unable to find transfer source to delete!" << llendl; - return FALSE; - } - mTransferSources.mMap.erase(iter); - delete tsp; - return TRUE; + llerrs << "Unable to find transfer source to delete!" << llendl; + return FALSE; } @@ -1000,18 +1019,14 @@ BOOL LLTransferTargetChannel::deleteTransfer(LLTransferTarget *ttp) { if (*iter == ttp) { - break; + delete ttp; + mTransferTargets.erase(iter); + return TRUE; } } - if (iter == mTransferTargets.end()) - { - llerrs << "Unable to find transfer target to delete!" << llendl; - return FALSE; - } - mTransferTargets.erase(iter); - delete ttp; - return TRUE; + llerrs << "Unable to find transfer target to delete!" << llendl; + return FALSE; } @@ -1072,7 +1087,7 @@ void LLTransferSource::sendTransferStatus(LLTSCode status) void LLTransferSource::abortTransfer() { // Send a message down, call the completion callback - llinfos << "Aborting transfer " << getID() << " to " << mChannelp->getHost() << llendl; + llinfos << "LLTransferSource::Aborting transfer " << getID() << " to " << mChannelp->getHost() << llendl; gMessageSystem->newMessage("TransferAbort"); gMessageSystem->nextBlock("TransferInfo"); gMessageSystem->addUUID("TransferID", getID()); @@ -1204,7 +1219,7 @@ LLTransferTarget::~LLTransferTarget() void LLTransferTarget::abortTransfer() { // Send a message up, call the completion callback - llinfos << "Aborting transfer " << getID() << " from " << mChannelp->getHost() << llendl; + llinfos << "LLTransferTarget::Aborting transfer " << getID() << " from " << mChannelp->getHost() << llendl; gMessageSystem->newMessage("TransferAbort"); gMessageSystem->nextBlock("TransferInfo"); gMessageSystem->addUUID("TransferID", getID()); diff --git a/indra/llprimitive/llprimitive.cpp b/indra/llprimitive/llprimitive.cpp index b41879380f..77bca8f803 100644 --- a/indra/llprimitive/llprimitive.cpp +++ b/indra/llprimitive/llprimitive.cpp @@ -41,6 +41,7 @@ #include "llvolumemgr.h" #include "llstring.h" #include "lldatapacker.h" +#include "llsdutil.h" /** * exported constants @@ -1795,6 +1796,47 @@ void LLLightParams::copy(const LLNetworkData& data) mFalloff = param->mFalloff; } +LLSD LLLightParams::asLLSD() const +{ + LLSD sd; + + sd["color"] = ll_sd_from_color4(getColor()); + sd["radius"] = getRadius(); + sd["falloff"] = getFalloff(); + sd["cutoff"] = getCutoff(); + + return sd; +} + +bool LLLightParams::fromLLSD(LLSD& sd) +{ + const char *w; + w = "color"; + if (sd.has(w)) + { + setColor( ll_color4_from_sd(sd["color"]) ); + } else goto fail; + w = "radius"; + if (sd.has(w)) + { + setRadius( (F32)sd[w].asReal() ); + } else goto fail; + w = "falloff"; + if (sd.has(w)) + { + setFalloff( (F32)sd[w].asReal() ); + } else goto fail; + w = "cutoff"; + if (sd.has(w)) + { + setCutoff( (F32)sd[w].asReal() ); + } else goto fail; + + return true; + fail: + return false; +} + //============================================================================ LLFlexibleObjectData::LLFlexibleObjectData() @@ -1876,6 +1918,59 @@ void LLFlexibleObjectData::copy(const LLNetworkData& data) //mRenderingCollisionSphere = flex_data->mRenderingCollisionSphere; } +LLSD LLFlexibleObjectData::asLLSD() const +{ + LLSD sd; + + sd["air_friction"] = getAirFriction(); + sd["gravity"] = getGravity(); + sd["simulate_lod"] = getSimulateLOD(); + sd["tension"] = getTension(); + sd["user_force"] = getUserForce().getValue(); + sd["wind_sensitivity"] = getWindSensitivity(); + + return sd; +} + +bool LLFlexibleObjectData::fromLLSD(LLSD& sd) +{ + const char *w; + w = "air_friction"; + if (sd.has(w)) + { + setAirFriction( (F32)sd[w].asReal() ); + } else goto fail; + w = "gravity"; + if (sd.has(w)) + { + setGravity( (F32)sd[w].asReal() ); + } else goto fail; + w = "simulate_lod"; + if (sd.has(w)) + { + setSimulateLOD( sd[w].asInteger() ); + } else goto fail; + w = "tension"; + if (sd.has(w)) + { + setTension( (F32)sd[w].asReal() ); + } else goto fail; + w = "user_force"; + if (sd.has(w)) + { + LLVector3 user_force = ll_vector3_from_sd(sd[w], 0); + setUserForce( user_force ); + } else goto fail; + w = "wind_sensitivity"; + if (sd.has(w)) + { + setWindSensitivity( (F32)sd[w].asReal() ); + } else goto fail; + + return true; + fail: + return false; +} //============================================================================ @@ -1927,3 +2022,34 @@ void LLSculptParams::copy(const LLNetworkData& data) mSculptType = param->mSculptType; } + + +LLSD LLSculptParams::asLLSD() const +{ + LLSD sd; + + sd["texture"] = mSculptTexture; + sd["type"] = mSculptType; + + return sd; +} + +bool LLSculptParams::fromLLSD(LLSD& sd) +{ + const char *w; + w = "texture"; + if (sd.has(w)) + { + setSculptTexture( sd[w] ); + } else goto fail; + w = "type"; + if (sd.has(w)) + { + setSculptType( (U8)sd[w].asInteger() ); + } else goto fail; + + return true; + fail: + return false; +} + diff --git a/indra/llprimitive/llprimitive.h b/indra/llprimitive/llprimitive.h index 53b17bc2ef..eef58341e7 100644 --- a/indra/llprimitive/llprimitive.h +++ b/indra/llprimitive/llprimitive.h @@ -141,7 +141,13 @@ public: /*virtual*/ BOOL unpack(LLDataPacker &dp); /*virtual*/ bool operator==(const LLNetworkData& data) const; /*virtual*/ void copy(const LLNetworkData& data); + // LLSD implementations here are provided by Eddy Stryker. + // NOTE: there are currently unused in protocols + LLSD asLLSD() const; + operator LLSD() const { return asLLSD(); } + bool fromLLSD(LLSD& sd); + void setColor(const LLColor4& color) { mColor = color; mColor.clamp(); } void setRadius(F32 radius) { mRadius = llclamp(radius, LIGHT_MIN_RADIUS, LIGHT_MAX_RADIUS); } void setFalloff(F32 falloff) { mFalloff = llclamp(falloff, LIGHT_MIN_FALLOFF, LIGHT_MAX_FALLOFF); } @@ -229,6 +235,9 @@ public: BOOL unpack(LLDataPacker &dp); bool operator==(const LLNetworkData& data) const; void copy(const LLNetworkData& data); + LLSD asLLSD() const; + operator LLSD() const { return asLLSD(); } + bool fromLLSD(LLSD& sd); };// end of attributes structure @@ -245,6 +254,9 @@ public: /*virtual*/ BOOL unpack(LLDataPacker &dp); /*virtual*/ bool operator==(const LLNetworkData& data) const; /*virtual*/ void copy(const LLNetworkData& data); + LLSD asLLSD() const; + operator LLSD() const { return asLLSD(); } + bool fromLLSD(LLSD& sd); void setSculptTexture(const LLUUID& id) { mSculptTexture = id; } LLUUID getSculptTexture() { return mSculptTexture; } diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 420970a38a..3c7cd17b92 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -72,6 +72,15 @@ const S32 SCROLL_INCREMENT_DEL = 4; // make space for baskspacing const F32 AUTO_SCROLL_TIME = 0.05f; const F32 LABEL_HPAD = 5.f; +const F32 PREEDIT_MARKER_BRIGHTNESS = 0.4f; +const S32 PREEDIT_MARKER_GAP = 1; +const S32 PREEDIT_MARKER_POSITION = 2; +const S32 PREEDIT_MARKER_THICKNESS = 1; +const F32 PREEDIT_STANDOUT_BRIGHTNESS = 0.6f; +const S32 PREEDIT_STANDOUT_GAP = 1; +const S32 PREEDIT_STANDOUT_POSITION = 2; +const S32 PREEDIT_STANDOUT_THICKNESS = 2; + // This is a friend class of and is only used by LLLineEditor class LLLineEditorRollback { @@ -127,7 +136,6 @@ LLLineEditor::LLLineEditor(const LLString& name, const LLRect& rect, S32 border_thickness) : LLUICtrl( name, rect, TRUE, commit_callback, userdata, FOLLOWS_TOP | FOLLOWS_LEFT ), - mMaxLengthChars(max_length_bytes), mMaxLengthBytes(max_length_bytes), mCursorPos( 0 ), mScrollHPos( 0 ), @@ -223,12 +231,19 @@ LLString LLLineEditor::getWidgetTag() const return LL_LINE_EDITOR_TAG; } -void LLLineEditor::onFocusLost() +void LLLineEditor::onFocusReceived() { - // Need to notify early when loosing focus. - getWindow()->allowLanguageTextInput(FALSE); + LLUICtrl::onFocusReceived(); + updateAllowingLanguageInput(); +} - LLUICtrl::onFocusLost(); +void LLLineEditor::onFocusLost() +{ + // The call to updateAllowLanguageInput() + // when loosing the keyboard focus *may* + // indirectly invoke handleUnicodeCharHere(), + // so it must be called before onCommit. + updateAllowingLanguageInput(); if( mCommitOnFocusLost && mText.getString() != mPrevText) { @@ -241,6 +256,8 @@ void LLLineEditor::onFocusLost() } getWindow()->showCursorFromMouseMove(); + + LLUICtrl::onFocusLost(); } void LLLineEditor::onCommit() @@ -301,6 +318,7 @@ void LLLineEditor::setEnabled(BOOL enabled) { mReadOnly = !enabled; setTabStop(!mReadOnly); + updateAllowingLanguageInput(); } @@ -308,7 +326,6 @@ void LLLineEditor::setMaxTextLength(S32 max_text_length) { S32 max_len = llmax(0, max_text_length); mMaxLengthBytes = max_len; - mMaxLengthChars = max_len; } void LLLineEditor::setBorderWidth(S32 left, S32 right) @@ -337,13 +354,13 @@ void LLLineEditor::setText(const LLStringExplicit &new_text) BOOL allSelected = (len > 0) && (( mSelectionStart == 0 && mSelectionEnd == len ) || ( mSelectionStart == len && mSelectionEnd == 0 )); + // Do safe truncation so we don't split multi-byte characters LLString truncated_utf8 = new_text; if (truncated_utf8.size() > (U32)mMaxLengthBytes) - { - utf8str_truncate(truncated_utf8, mMaxLengthBytes); + { + truncated_utf8 = utf8str_truncate(new_text, mMaxLengthBytes); } mText.assign(truncated_utf8); - mText.truncate(mMaxLengthChars); if (allSelected) { @@ -735,17 +752,12 @@ void LLLineEditor::addChar(const llwchar uni_char) mText.erase(getCursor(), 1); } - S32 length_chars = mText.length(); - S32 cur_bytes = mText.getString().size();; + S32 cur_bytes = mText.getString().size(); S32 new_bytes = wchar_utf8_length(new_c); BOOL allow_char = TRUE; - // Inserting character - if (length_chars == mMaxLengthChars) - { - allow_char = FALSE; - } + // Check byte length limit if ((new_bytes + cur_bytes) > mMaxLengthBytes) { allow_char = FALSE; @@ -794,6 +806,12 @@ void LLLineEditor::setSelection(S32 start, S32 end) setCursor(start); } +void LLLineEditor::setDrawAsterixes(BOOL b) +{ + mDrawAsterixes = b; + updateAllowingLanguageInput(); +} + S32 LLLineEditor::prevWordPos(S32 cursorPos) const { const LLWString& wtext = mText.getWString(); @@ -1022,13 +1040,11 @@ void LLLineEditor::paste() // Insert the string - //check to see that the size isn't going to be larger than the - //max number of characters or bytes + // Check to see that the size isn't going to be larger than the max number of bytes U32 available_bytes = mMaxLengthBytes - wstring_utf8_length(mText); - size_t available_chars = mMaxLengthChars - mText.length(); if ( available_bytes < (U32) wstring_utf8_length(clean_string) ) - { + { // Doesn't all fit llwchar current_symbol = clean_string[0]; U32 wchars_that_fit = 0; U32 total_bytes = wchar_utf8_length(current_symbol); @@ -1043,20 +1059,13 @@ void LLLineEditor::paste() current_symbol = clean_string[++wchars_that_fit]; total_bytes += wchar_utf8_length(current_symbol); } - + // Truncate the clean string at the limit of what will fit clean_string = clean_string.substr(0, wchars_that_fit); reportBadKeystroke(); } - else if (available_chars < clean_string.length()) - { - // We can't insert all the characters. Insert as many as possible - // but make a noise to alert the user. JC - clean_string = clean_string.substr(0, available_chars); - reportBadKeystroke(); - } mText.insert(getCursor(), clean_string); - setCursor(llmin(mMaxLengthChars, getCursor() + (S32)clean_string.length())); + setCursor( getCursor() + (S32)clean_string.length() ); deselect(); // Validate new string and rollback the if needed. @@ -1523,6 +1532,41 @@ void LLLineEditor::draw() } LLColor4 label_color = mTentativeFgColor; + if (hasPreeditString()) + { + // Draw preedit markers. This needs to be before drawing letters. + for (U32 i = 0; i < mPreeditStandouts.size(); i++) + { + const S32 preedit_left = mPreeditPositions[i]; + const S32 preedit_right = mPreeditPositions[i + 1]; + if (preedit_right > mScrollHPos) + { + S32 preedit_pixels_left = findPixelNearestPos(llmax(preedit_left, mScrollHPos) - getCursor()); + S32 preedit_pixels_right = llmin(findPixelNearestPos(preedit_right - getCursor()), background.mRight); + if (preedit_pixels_left >= background.mRight) + { + break; + } + if (mPreeditStandouts[i]) + { + gl_rect_2d(preedit_pixels_left + PREEDIT_STANDOUT_GAP, + background.mBottom + PREEDIT_STANDOUT_POSITION, + preedit_pixels_right - PREEDIT_STANDOUT_GAP - 1, + background.mBottom + PREEDIT_STANDOUT_POSITION - PREEDIT_STANDOUT_THICKNESS, + (text_color * PREEDIT_STANDOUT_BRIGHTNESS + bg_color * (1 - PREEDIT_STANDOUT_BRIGHTNESS)).setAlpha(1.0f)); + } + else + { + gl_rect_2d(preedit_pixels_left + PREEDIT_MARKER_GAP, + background.mBottom + PREEDIT_MARKER_POSITION, + preedit_pixels_right - PREEDIT_MARKER_GAP - 1, + background.mBottom + PREEDIT_MARKER_POSITION - PREEDIT_MARKER_THICKNESS, + (text_color * PREEDIT_MARKER_BRIGHTNESS + bg_color * (1 - PREEDIT_MARKER_BRIGHTNESS)).setAlpha(1.0f)); + } + } + } + } + S32 rendered_text = 0; F32 rendered_pixels_right = (F32)mMinHPixels; F32 text_bottom = (F32)background.mBottom + (F32)UI_LINEEDITOR_V_PAD; @@ -1677,7 +1721,7 @@ void LLLineEditor::draw() // Returns the local screen space X coordinate associated with the text cursor position. -S32 LLLineEditor::findPixelNearestPos(const S32 cursor_offset) +S32 LLLineEditor::findPixelNearestPos(const S32 cursor_offset) const { S32 dpos = getCursor() - mScrollHPos + cursor_offset; S32 result = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos, dpos) + mMinHPixels; @@ -1715,7 +1759,7 @@ void LLLineEditor::setFocus( BOOL new_state ) if (!new_state) { - getWindow()->allowLanguageTextInput(FALSE); + getWindow()->allowLanguageTextInput(this, FALSE); } @@ -1757,7 +1801,7 @@ void LLLineEditor::setFocus( BOOL new_state ) // fine on 1.15.0.2, since all prevalidate func reject any // non-ASCII characters. I'm not sure on future versions, // however. - getWindow()->allowLanguageTextInput(mPrevalidateFunc == NULL); + getWindow()->allowLanguageTextInput(this, mPrevalidateFunc == NULL); } } @@ -1776,6 +1820,12 @@ void LLLineEditor::setRect(const LLRect& rect) } } +void LLLineEditor::setPrevalidate(BOOL (*func)(const LLWString &)) +{ + mPrevalidateFunc = func; + updateAllowingLanguageInput(); +} + // Limits what characters can be used to [1234567890.-] with [-] only valid in the first position. // Does NOT ensure that the string is a well-formed number--that's the job of post-validation--for // the simple reasons that intermediate states may be invalid even if the final result is valid. @@ -2336,6 +2386,239 @@ BOOL LLLineEditor::setLabelArg( const LLString& key, const LLStringExplicit& tex return TRUE; } + +void LLLineEditor::updateAllowingLanguageInput() +{ + // Allow Language Text Input only when this LineEditor has + // no prevalidate function attached (as long as other criteria + // common to LLTextEditor). This criterion works + // fine on 1.15.0.2, since all prevalidate func reject any + // non-ASCII characters. I'm not sure on future versions, + // however... + if (hasFocus() && !mReadOnly && !mDrawAsterixes && mPrevalidateFunc == NULL) + { + getWindow()->allowLanguageTextInput(this, TRUE); + } + else + { + getWindow()->allowLanguageTextInput(this, FALSE); + } +} + +BOOL LLLineEditor::hasPreeditString() const +{ + return (mPreeditPositions.size() > 1); +} + +void LLLineEditor::resetPreedit() +{ + if (hasPreeditString()) + { + const S32 preedit_pos = mPreeditPositions.front(); + mText.erase(preedit_pos, mPreeditPositions.back() - preedit_pos); + mText.insert(preedit_pos, mPreeditOverwrittenWString); + setCursor(preedit_pos); + + mPreeditWString.clear(); + mPreeditOverwrittenWString.clear(); + mPreeditPositions.clear(); + + mKeystrokeTimer.reset(); + if (mKeystrokeCallback) + { + mKeystrokeCallback(this, mCallbackUserData); + } + } +} + +void LLLineEditor::updatePreedit(const LLWString &preedit_string, + const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position) +{ + // Just in case. + if (mReadOnly) + { + return; + } + + if (hasSelection()) + { + if (hasPreeditString()) + { + llwarns << "Preedit and selection!" << llendl; + deselect(); + } + else + { + deleteSelection(); + } + } + + S32 insert_preedit_at = getCursor(); + if (hasPreeditString()) + { + insert_preedit_at = mPreeditPositions.front(); + //mText.replace(insert_preedit_at, mPreeditPositions.back() - insert_preedit_at, mPreeditOverwrittenWString); + mText.erase(insert_preedit_at, mPreeditPositions.back() - insert_preedit_at); + mText.insert(insert_preedit_at, mPreeditOverwrittenWString); + } + + mPreeditWString = preedit_string; + mPreeditPositions.resize(preedit_segment_lengths.size() + 1); + S32 position = insert_preedit_at; + for (segment_lengths_t::size_type i = 0; i < preedit_segment_lengths.size(); i++) + { + mPreeditPositions[i] = position; + position += preedit_segment_lengths[i]; + } + mPreeditPositions.back() = position; + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + mPreeditOverwrittenWString.assign( LLWString( mText, insert_preedit_at, mPreeditWString.length() ) ); + mText.erase(insert_preedit_at, mPreeditWString.length()); + } + else + { + mPreeditOverwrittenWString.clear(); + } + mText.insert(insert_preedit_at, mPreeditWString); + + mPreeditStandouts = preedit_standouts; + + setCursor(position); + setCursor(mPreeditPositions.front() + caret_position); + + // Update of the preedit should be caused by some key strokes. + mKeystrokeTimer.reset(); + if( mKeystrokeCallback ) + { + mKeystrokeCallback( this, mCallbackUserData ); + } +} + +BOOL LLLineEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const +{ + if (control) + { + LLRect control_rect_screen; + localRectToScreen(mRect, &control_rect_screen); + LLUI::screenRectToGL(control_rect_screen, control); + } + + S32 preedit_left_column, preedit_right_column; + if (hasPreeditString()) + { + preedit_left_column = mPreeditPositions.front(); + preedit_right_column = mPreeditPositions.back(); + } + else + { + preedit_left_column = preedit_right_column = getCursor(); + } + if (preedit_right_column < mScrollHPos) + { + // This should not occure... + return FALSE; + } + + const S32 query = (query_offset >= 0 ? preedit_left_column + query_offset : getCursor()); + if (query < mScrollHPos || query < preedit_left_column || query > preedit_right_column) + { + return FALSE; + } + + if (coord) + { + S32 query_local = findPixelNearestPos(query - getCursor()); + S32 query_screen_x, query_screen_y; + localPointToScreen(query_local, mRect.getHeight() / 2, &query_screen_x, &query_screen_y); + LLUI::screenPointToGL(query_screen_x, query_screen_y, &coord->mX, &coord->mY); + } + + if (bounds) + { + S32 preedit_left_local = findPixelNearestPos(llmax(preedit_left_column, mScrollHPos) - getCursor()); + S32 preedit_right_local = llmin(findPixelNearestPos(preedit_right_column - getCursor()), mRect.getWidth() - mBorderThickness); + if (preedit_left_local > preedit_right_local) + { + // Is this condition possible? + preedit_right_local = preedit_left_local; + } + + LLRect preedit_rect_local(preedit_left_local, mRect.getHeight(), preedit_right_local, 0); + LLRect preedit_rect_screen; + localRectToScreen(preedit_rect_local, &preedit_rect_screen); + LLUI::screenRectToGL(preedit_rect_screen, bounds); + } + + return TRUE; +} + +void LLLineEditor::getPreeditRange(S32 *position, S32 *length) const +{ + if (hasPreeditString()) + { + *position = mPreeditPositions.front(); + *length = mPreeditPositions.back() - mPreeditPositions.front(); + } + else + { + *position = mCursorPos; + *length = 0; + } +} + +void LLLineEditor::getSelectionRange(S32 *position, S32 *length) const +{ + if (hasSelection()) + { + *position = llmin(mSelectionStart, mSelectionEnd); + *length = llabs(mSelectionStart - mSelectionEnd); + } + else + { + *position = mCursorPos; + *length = 0; + } +} + +void LLLineEditor::markAsPreedit(S32 position, S32 length) +{ + deselect(); + setCursor(position); + if (hasPreeditString()) + { + llwarns << "markAsPreedit invoked when hasPreeditString is true." << llendl; + } + mPreeditWString.assign( LLWString( mText.getWString(), position, length ) ); + if (length > 0) + { + mPreeditPositions.resize(2); + mPreeditPositions[0] = position; + mPreeditPositions[1] = position + length; + mPreeditStandouts.resize(1); + mPreeditStandouts[0] = FALSE; + } + else + { + mPreeditPositions.clear(); + mPreeditStandouts.clear(); + } + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + mPreeditOverwrittenWString = mPreeditWString; + } + else + { + mPreeditOverwrittenWString.clear(); + } +} + +S32 LLLineEditor::getPreeditFontSize() const +{ + return llround(mGLFont->getLineHeight() * LLUI::sGLScaleFactor.mV[VY]); +} + + LLSearchEditor::LLSearchEditor(const LLString& name, const LLRect& rect, S32 max_length_bytes, diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h index f1b9fbe33e..a019353856 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -53,6 +53,8 @@ #include "lluistring.h" #include "llviewborder.h" +#include "llpreeditor.h" + class LLFontGL; class LLLineEditorRollback; class LLButton; @@ -63,7 +65,7 @@ typedef BOOL (*LLLinePrevalidateFunc)(const LLWString &wstr); // Classes // class LLLineEditor -: public LLUICtrl, public LLEditMenuHandler +: public LLUICtrl, public LLEditMenuHandler, protected LLPreeditor { friend class LLLineEditorRollback; @@ -120,6 +122,7 @@ public: // view overrides virtual void draw(); virtual void reshape(S32 width,S32 height,BOOL called_from_parent=TRUE); + virtual void onFocusReceived(); virtual void onFocusLost(); virtual void setEnabled(BOOL enabled); @@ -146,7 +149,7 @@ public: const LLWString& getWText() const { return mText.getWString(); } S32 getLength() const { return mText.length(); } - S32 getCursor() { return mCursorPos; } + S32 getCursor() const { return mCursorPos; } void setCursor( S32 pos ); void setCursorToEnd(); @@ -177,13 +180,13 @@ public: void setIgnoreTab(BOOL b) { mIgnoreTab = b; } void setPassDelete(BOOL b) { mPassDelete = b; } - void setDrawAsterixes(BOOL b) { mDrawAsterixes = b; } + void setDrawAsterixes(BOOL b); // get the cursor position of the beginning/end of the prev/next word in the text S32 prevWordPos(S32 cursorPos) const; S32 nextWordPos(S32 cursorPos) const; - BOOL hasSelection() { return (mSelectionStart != mSelectionEnd); } + BOOL hasSelection() const { return (mSelectionStart != mSelectionEnd); } void startSelection(); void endSelection(); void extendSelection(S32 new_cursor_pos); @@ -199,7 +202,7 @@ public: static BOOL isPartOfWord(llwchar c); // Prevalidation controls which keystrokes can affect the editor - void setPrevalidate( BOOL (*func)(const LLWString &) ) { mPrevalidateFunc = func; } + void setPrevalidate( BOOL (*func)(const LLWString &) ); static BOOL prevalidateFloat(const LLWString &str ); static BOOL prevalidateInt(const LLWString &str ); static BOOL prevalidatePositiveS32(const LLWString &str); @@ -221,7 +224,7 @@ protected: void addChar(const llwchar c); void setCursorAtLocalPos(S32 local_mouse_x); - S32 findPixelNearestPos(S32 cursor_offset = 0); + S32 findPixelNearestPos(S32 cursor_offset = 0) const; void reportBadKeystroke(); BOOL handleSpecialKey(KEY key, MASK mask); @@ -230,6 +233,19 @@ protected: S32 handleCommitKey(KEY key, MASK mask); protected: + void updateAllowingLanguageInput(); + BOOL hasPreeditString() const; + // Implementation (overrides) of LLPreeditor + virtual void resetPreedit(); + virtual void updatePreedit(const LLWString &preedit_string, + const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position); + virtual void markAsPreedit(S32 position, S32 length); + virtual void getPreeditRange(S32 *position, S32 *length) const; + virtual void getSelectionRange(S32 *position, S32 *length) const; + virtual BOOL getPreeditLocation(S32 query_position, LLCoordGL *coord, LLRect *bounds, LLRect *control) const; + virtual S32 getPreeditFontSize() const; + +protected: LLUIString mText; // The string being edited. LLString mPrevText; // Saved string for 'ESC' revert LLUIString mLabel; // text label that is visible when no user text provided @@ -241,8 +257,7 @@ protected: LLViewBorder* mBorder; const LLFontGL* mGLFont; - S32 mMaxLengthChars; // Max number of characters - S32 mMaxLengthBytes; // Max length of the UTF8 string. + S32 mMaxLengthBytes; // Max length of the UTF8 string in bytes S32 mCursorPos; // I-beam is just after the mCursorPos-th character. S32 mScrollHPos; // Horizontal offset from the start of mText. Used for scrolling. LLFrameTimer mScrollTimer; @@ -288,6 +303,11 @@ protected: BOOL mPassDelete; BOOL mReadOnly; + + LLWString mPreeditWString; + LLWString mPreeditOverwrittenWString; + std::vector<S32> mPreeditPositions; + LLPreeditor::standouts_t mPreeditStandouts; }; diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index d150f8954e..46f9f515d7 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -632,7 +632,7 @@ public: }; LLMenuItemSeparatorGL::LLMenuItemSeparatorGL( const LLString &name ) : - LLMenuItemGL( SEPARATOR_NAME, SEPARATOR_LABEL ) + LLMenuItemGL( name, SEPARATOR_LABEL ) { } @@ -2832,7 +2832,7 @@ LLMenuItemGL* LLMenuGL::highlightNextItem(LLMenuItemGL* cur_item, BOOL skip_disa while(1) { // skip separators and disabled items - if ((*next_item_iter)->getEnabled() && (*next_item_iter)->getName() != SEPARATOR_NAME) + if ((*next_item_iter)->getEnabled() && (*next_item_iter)->getType() != SEPARATOR_NAME) { if (cur_item) { diff --git a/indra/llui/lltextbox.cpp b/indra/llui/lltextbox.cpp index efd42455e5..8bd7b1509f 100644 --- a/indra/llui/lltextbox.cpp +++ b/indra/llui/lltextbox.cpp @@ -174,7 +174,7 @@ BOOL LLTextBox::handleHover(S32 x, S32 y, MASK mask) mHasHover = TRUE; // This should be set every frame during a hover. return TRUE; } - return FALSE; + return LLView::handleHover(x,y,mask); } void LLTextBox::setText(const LLStringExplicit& text) diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index d08997c3ed..5c8b7c7281 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -79,6 +79,15 @@ const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds const S32 CURSOR_THICKNESS = 2; const S32 SPACES_PER_TAB = 4; +const F32 PREEDIT_MARKER_BRIGHTNESS = 0.4f; +const S32 PREEDIT_MARKER_GAP = 1; +const S32 PREEDIT_MARKER_POSITION = 2; +const S32 PREEDIT_MARKER_THICKNESS = 1; +const F32 PREEDIT_STANDOUT_BRIGHTNESS = 0.6f; +const S32 PREEDIT_STANDOUT_GAP = 1; +const S32 PREEDIT_STANDOUT_POSITION = 2; +const S32 PREEDIT_STANDOUT_THICKNESS = 2; + LLColor4 LLTextEditor::mLinkColor = LLColor4::blue; void (* LLTextEditor::mURLcallback)(const char*) = NULL; bool (* LLTextEditor::mSecondlifeURLcallback)(const std::string&) = NULL; @@ -274,14 +283,14 @@ private: LLTextEditor::LLTextEditor( const LLString& name, const LLRect& rect, - S32 max_length, + S32 max_length, // In bytes const LLString &default_text, const LLFontGL* font, BOOL allow_embedded_items) : LLUICtrl( name, rect, TRUE, NULL, NULL, FOLLOWS_TOP | FOLLOWS_LEFT ), mTextIsUpToDate(TRUE), - mMaxTextLength( max_length ), + mMaxTextByteLength( max_length ), mBaseDocIsPristine(TRUE), mPristineCmd( NULL ), mLastCmd( NULL ), @@ -510,13 +519,27 @@ BOOL LLTextEditor::isPartOfWord(llwchar c) { return (c == '_') || isalnum(c); } -void LLTextEditor::truncate() +BOOL LLTextEditor::truncate() { - if (mWText.size() > (size_t)mMaxTextLength) - { - LLWString::truncate(mWText, mMaxTextLength); - mTextIsUpToDate = FALSE; + BOOL did_truncate = FALSE; + + // First rough check - if we're less than 1/4th the size, we're OK + if (mWText.size() >= (size_t) (mMaxTextByteLength / 4)) + { + // Have to check actual byte size + S32 utf8_byte_size = wstring_utf8_length( mWText ); + if ( utf8_byte_size > mMaxTextByteLength ) + { + // Truncate safely in UTF-8 + std::string temp_utf8_text = wstring_to_utf8str( mWText ); + temp_utf8_text = utf8str_truncate( temp_utf8_text, mMaxTextByteLength ); + mWText = utf8str_to_wstring( temp_utf8_text ); + mTextIsUpToDate = FALSE; + did_truncate = TRUE; + } } + + return did_truncate; } void LLTextEditor::setText(const LLStringExplicit &utf8str) @@ -750,12 +773,12 @@ S32 LLTextEditor::nextWordPos(S32 cursorPos) const return cursorPos; } -S32 LLTextEditor::getLineCount() +S32 LLTextEditor::getLineCount() const { return mLineStartList.size(); } -S32 LLTextEditor::getLineStart( S32 line ) +S32 LLTextEditor::getLineStart( S32 line ) const { S32 num_lines = getLineCount(); if (num_lines == 0) @@ -1604,7 +1627,7 @@ void LLTextEditor::removeChar() // Add a single character to the text S32 LLTextEditor::addChar(S32 pos, llwchar wc) { - if ((S32)mWText.length() == mMaxTextLength) + if ( (wstring_utf8_length( mWText ) + wchar_utf8_length( wc )) >= mMaxTextByteLength) { make_ui_sound("UISndBadKeystroke"); return 0; @@ -2490,11 +2513,16 @@ void LLTextEditor::redo() } } +void LLTextEditor::onFocusReceived() +{ + LLUICtrl::onFocusReceived(); + updateAllowingLanguageInput(); +} // virtual, from LLView void LLTextEditor::onFocusLost() { - getWindow()->allowLanguageTextInput(FALSE); + updateAllowingLanguageInput(); // Route menu back to the default if( gEditMenuHandler == this ) @@ -2521,6 +2549,7 @@ void LLTextEditor::setEnabled(BOOL enabled) { mReadOnly = read_only; updateSegments(); + updateAllowingLanguageInput(); } } @@ -2825,6 +2854,100 @@ void LLTextEditor::drawCursor() } } +void LLTextEditor::drawPreeditMarker() +{ + if (!hasPreeditString()) + { + return; + } + + const llwchar *text = mWText.c_str(); + const S32 text_len = getLength(); + const S32 num_lines = getLineCount(); + + S32 cur_line = mScrollbar->getDocPos(); + if (cur_line >= num_lines) + { + return; + } + + const S32 line_height = llround( mGLFont->getLineHeight() ); + + S32 line_start = getLineStart(cur_line); + S32 line_y = mTextRect.mTop - line_height; + while((mTextRect.mBottom <= line_y) && (num_lines > cur_line)) + { + S32 next_start = -1; + S32 line_end = text_len; + + if ((cur_line + 1) < num_lines) + { + next_start = getLineStart(cur_line + 1); + line_end = next_start; + } + if ( text[line_end-1] == '\n' ) + { + --line_end; + } + + // Does this line contain preedits? + if (line_start >= mPreeditPositions.back()) + { + // We have passed the preedits. + break; + } + if (line_end > mPreeditPositions.front()) + { + for (U32 i = 0; i < mPreeditStandouts.size(); i++) + { + S32 left = mPreeditPositions[i]; + S32 right = mPreeditPositions[i + 1]; + if (right <= line_start || left >= line_end) + { + continue; + } + + S32 preedit_left = mTextRect.mLeft; + if (left > line_start) + { + preedit_left += mGLFont->getWidth(text, line_start, left - line_start, mAllowEmbeddedItems); + } + S32 preedit_right = mTextRect.mLeft; + if (right < line_end) + { + preedit_right += mGLFont->getWidth(text, line_start, right - line_start, mAllowEmbeddedItems); + } + else + { + preedit_right += mGLFont->getWidth(text, line_start, line_end - line_start, mAllowEmbeddedItems); + } + + if (mPreeditStandouts[i]) + { + gl_rect_2d(preedit_left + PREEDIT_STANDOUT_GAP, + line_y + PREEDIT_STANDOUT_POSITION, + preedit_right - PREEDIT_STANDOUT_GAP - 1, + line_y + PREEDIT_STANDOUT_POSITION - PREEDIT_STANDOUT_THICKNESS, + (mCursorColor * PREEDIT_STANDOUT_BRIGHTNESS + mWriteableBgColor * (1 - PREEDIT_STANDOUT_BRIGHTNESS)).setAlpha(1.0f)); + } + else + { + gl_rect_2d(preedit_left + PREEDIT_MARKER_GAP, + line_y + PREEDIT_MARKER_POSITION, + preedit_right - PREEDIT_MARKER_GAP - 1, + line_y + PREEDIT_MARKER_POSITION - PREEDIT_MARKER_THICKNESS, + (mCursorColor * PREEDIT_MARKER_BRIGHTNESS + mWriteableBgColor * (1 - PREEDIT_MARKER_BRIGHTNESS)).setAlpha(1.0f)); + } + } + } + + // move down one line + line_y -= line_height; + line_start = next_start; + cur_line++; + } +} + void LLTextEditor::drawText() { @@ -3025,6 +3148,7 @@ void LLTextEditor::draw() drawBackground(); drawSelectionBackground(); + drawPreeditMarker(); drawText(); drawCursor(); @@ -3067,10 +3191,10 @@ void LLTextEditor::setFocus( BOOL new_state ) // Don't change anything if the focus state didn't change if (new_state == old_state) return; - // Notify early if we are loosing focus. + // Notify early if we are losing focus. if (!new_state) { - getWindow()->allowLanguageTextInput(FALSE); + getWindow()->allowLanguageTextInput(this, FALSE); } LLUICtrl::setFocus( new_state ); @@ -3093,12 +3217,6 @@ void LLTextEditor::setFocus( BOOL new_state ) endSelection(); } - - // Notify late if we are gaining focus. - if (new_state && !mReadOnly) - { - getWindow()->allowLanguageTextInput(TRUE); - } } BOOL LLTextEditor::acceptsTextInput() const @@ -3540,22 +3658,20 @@ void LLTextEditor::removeTextFromEnd(S32 num_chars) S32 LLTextEditor::insertStringNoUndo(const S32 pos, const LLWString &wstr) { - S32 len = mWText.length(); - S32 s_len = wstr.length(); - S32 new_len = len + s_len; - if( new_len > mMaxTextLength ) - { - new_len = mMaxTextLength; + S32 old_len = mWText.length(); // length() returns character length + S32 insert_len = wstr.length(); + mWText.insert(pos, wstr); + mTextIsUpToDate = FALSE; + + if ( truncate() ) + { // The user's not getting everything he's hoping for make_ui_sound("UISndBadKeystroke"); + insert_len = mWText.length() - old_len; } - mWText.insert(pos, wstr); - mTextIsUpToDate = FALSE; - truncate(); - - return new_len - len; + return insert_len; } S32 LLTextEditor::removeStringNoUndo(S32 pos, S32 length) @@ -3920,7 +4036,7 @@ BOOL LLTextEditor::importBuffer(const LLString& buffer ) return FALSE; } - if( text_len > mMaxTextLength ) + if( text_len > mMaxTextByteLength ) { llwarns << "Invalid Linden text length: " << text_len << llendl; return FALSE; @@ -4281,3 +4397,262 @@ BOOL LLTextEditor::findHTML(const LLString &line, S32 *begin, S32 *end) } return matched; } + + + +void LLTextEditor::updateAllowingLanguageInput() +{ + if (hasFocus() && !mReadOnly) + { + getWindow()->allowLanguageTextInput(this, TRUE); + } + else + { + getWindow()->allowLanguageTextInput(this, FALSE); + } +} + +// Preedit is managed off the undo/redo command stack. + +BOOL LLTextEditor::hasPreeditString() const +{ + return (mPreeditPositions.size() > 1); +} + +void LLTextEditor::resetPreedit() +{ + if (hasPreeditString()) + { + mCursorPos = mPreeditPositions.front(); + removeStringNoUndo(mCursorPos, mPreeditPositions.back() - mCursorPos); + insertStringNoUndo(mCursorPos, mPreeditOverwrittenWString); + + mPreeditWString.clear(); + mPreeditOverwrittenWString.clear(); + mPreeditPositions.clear(); + + updateLineStartList(); + setCursorPos(mCursorPos); + // updateScrollFromCursor(); + } +} + +void LLTextEditor::updatePreedit(const LLWString &preedit_string, + const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position) +{ + // Just in case. + if (mReadOnly) + { + return; + } + + if (hasSelection()) + { + if (hasPreeditString()) + { + llwarns << "Preedit and selection!" << llendl; + deselect(); + } + else + { + deleteSelection(TRUE); + } + } + + getWindow()->hideCursorUntilMouseMove(); + + S32 insert_preedit_at = mCursorPos; + if (hasPreeditString()) + { + insert_preedit_at = mPreeditPositions.front(); + removeStringNoUndo(insert_preedit_at, mPreeditPositions.back() - insert_preedit_at); + insertStringNoUndo(insert_preedit_at, mPreeditOverwrittenWString); + } + + mPreeditWString = preedit_string; + mPreeditPositions.resize(preedit_segment_lengths.size() + 1); + S32 position = insert_preedit_at; + for (segment_lengths_t::size_type i = 0; i < preedit_segment_lengths.size(); i++) + { + mPreeditPositions[i] = position; + position += preedit_segment_lengths[i]; + } + mPreeditPositions.back() = position; + + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + mPreeditOverwrittenWString = getWSubString(insert_preedit_at, mPreeditWString.length()); + removeStringNoUndo(insert_preedit_at, mPreeditWString.length()); + } + else + { + mPreeditOverwrittenWString.clear(); + } + insertStringNoUndo(insert_preedit_at, mPreeditWString); + + mPreeditStandouts = preedit_standouts; + + updateLineStartList(); + setCursorPos(insert_preedit_at + caret_position); + // updateScrollFromCursor(); + + // Update of the preedit should be caused by some key strokes. + mKeystrokeTimer.reset(); +} + +BOOL LLTextEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const +{ + if (control) + { + LLRect control_rect_screen; + localRectToScreen(mTextRect, &control_rect_screen); + LLUI::screenRectToGL(control_rect_screen, control); + } + + S32 preedit_left_position, preedit_right_position; + if (hasPreeditString()) + { + preedit_left_position = mPreeditPositions.front(); + preedit_right_position = mPreeditPositions.back(); + } + else + { + preedit_left_position = preedit_right_position = mCursorPos; + } + + const S32 query = (query_offset >= 0 ? preedit_left_position + query_offset : mCursorPos); + if (query < preedit_left_position || query > preedit_right_position) + { + return FALSE; + } + + const S32 first_visible_line = mScrollbar->getDocPos(); + if (query < getLineStart(first_visible_line)) + { + return FALSE; + } + + S32 current_line = first_visible_line; + S32 current_line_start, current_line_end; + for (;;) + { + current_line_start = getLineStart(current_line); + current_line_end = getLineStart(current_line + 1); + if (query >= current_line_start && query < current_line_end) + { + break; + } + if (current_line_start == current_line_end) + { + // We have reached on the last line. The query position must be here. + break; + } + current_line++; + } + + const llwchar * const text = mWText.c_str(); + const S32 line_height = llround(mGLFont->getLineHeight()); + + if (coord) + { + const S32 query_x = mTextRect.mLeft + mGLFont->getWidth(text, current_line_start, query - current_line_start, mAllowEmbeddedItems); + const S32 query_y = mTextRect.mTop - (current_line - first_visible_line) * line_height - line_height / 2; + S32 query_screen_x, query_screen_y; + localPointToScreen(query_x, query_y, &query_screen_x, &query_screen_y); + LLUI::screenPointToGL(query_screen_x, query_screen_y, &coord->mX, &coord->mY); + } + + if (bounds) + { + S32 preedit_left = mTextRect.mLeft; + if (preedit_left_position > current_line_start) + { + preedit_left += mGLFont->getWidth(text, current_line_start, preedit_left_position - current_line_start, mAllowEmbeddedItems); + } + + S32 preedit_right = mTextRect.mLeft; + if (preedit_right_position < current_line_end) + { + preedit_right += mGLFont->getWidth(text, current_line_start, preedit_right_position - current_line_start, mAllowEmbeddedItems); + } + else + { + preedit_right += mGLFont->getWidth(text, current_line_start, current_line_end - current_line_start, mAllowEmbeddedItems); + } + + const S32 preedit_top = mTextRect.mTop - (current_line - first_visible_line) * line_height; + const S32 preedit_bottom = preedit_top - line_height; + + const LLRect preedit_rect_local(preedit_left, preedit_top, preedit_right, preedit_bottom); + LLRect preedit_rect_screen; + localRectToScreen(preedit_rect_local, &preedit_rect_screen); + LLUI::screenRectToGL(preedit_rect_screen, bounds); + } + + return TRUE; +} + +void LLTextEditor::getSelectionRange(S32 *position, S32 *length) const +{ + if (hasSelection()) + { + *position = llmin(mSelectionStart, mSelectionEnd); + *length = llabs(mSelectionStart - mSelectionEnd); + } + else + { + *position = mCursorPos; + *length = 0; + } +} + +void LLTextEditor::getPreeditRange(S32 *position, S32 *length) const +{ + if (hasPreeditString()) + { + *position = mPreeditPositions.front(); + *length = mPreeditPositions.back() - mPreeditPositions.front(); + } + else + { + *position = mCursorPos; + *length = 0; + } +} + +void LLTextEditor::markAsPreedit(S32 position, S32 length) +{ + deselect(); + setCursorPos(position); + if (hasPreeditString()) + { + llwarns << "markAsPreedit invoked when hasPreeditString is true." << llendl; + } + mPreeditWString = LLWString( mWText, position, length ); + if (length > 0) + { + mPreeditPositions.resize(2); + mPreeditPositions[0] = position; + mPreeditPositions[1] = position + length; + mPreeditStandouts.resize(1); + mPreeditStandouts[0] = FALSE; + } + else + { + mPreeditPositions.clear(); + mPreeditStandouts.clear(); + } + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + mPreeditOverwrittenWString = mPreeditWString; + } + else + { + mPreeditOverwrittenWString.clear(); + } +} + +S32 LLTextEditor::getPreeditFontSize() const +{ + return llround(mGLFont->getLineHeight() * LLUI::sGLScaleFactor.mV[VY]); +} diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index d38accca8f..7049de7b89 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -43,6 +43,8 @@ #include "lleditmenuhandler.h" #include "lldarray.h" +#include "llpreeditor.h" + class LLFontGL; class LLScrollbar; class LLViewBorder; @@ -64,7 +66,7 @@ const S32 MAX_EMBEDDED_ITEMS = LAST_EMBEDDED_CHAR - FIRST_EMBEDDED_CHAR + 1; class LLTextSegment; class LLTextCmd; -class LLTextEditor : public LLUICtrl, LLEditMenuHandler +class LLTextEditor : public LLUICtrl, LLEditMenuHandler, protected LLPreeditor { friend class LLTextCmd; public: @@ -104,6 +106,7 @@ public: // view overrides virtual void reshape(S32 width, S32 height, BOOL called_from_parent); virtual void draw(); + virtual void onFocusReceived(); virtual void onFocusLost(); virtual void setEnabled(BOOL enabled); @@ -234,7 +237,8 @@ public: void setText(const LLStringExplicit &utf8str); void setWText(const LLWString &wtext); - S32 getMaxLength() const { return mMaxTextLength; } + // Returns byte length limit + S32 getMaxLength() const { return mMaxTextByteLength; } // Change cursor void startOfLine(); @@ -259,6 +263,7 @@ protected: void drawCursor(); void drawText(); void drawClippedSegment(const LLWString &wtext, S32 seg_start, S32 seg_end, F32 x, F32 y, S32 selection_left, S32 selection_right, const LLStyle& color, F32* right_x); + void drawPreeditMarker(); void updateLineStartList(S32 startpos = 0); void updateScrollFromCursor(); @@ -267,7 +272,7 @@ protected: void pruneSegments(); void assignEmbedded(const LLString &s); - void truncate(); + BOOL truncate(); // Returns true if truncation occurs static BOOL isPartOfWord(llwchar c); @@ -291,7 +296,7 @@ protected: BOOL handleControlKey(const KEY key, const MASK mask); BOOL handleEditKey(const KEY key, const MASK mask); - BOOL hasSelection() { return (mSelectionStart !=mSelectionEnd); } + BOOL hasSelection() const { return (mSelectionStart !=mSelectionEnd); } BOOL selectionContainsLineBreaks(); void startSelection(); void endSelection(); @@ -300,8 +305,8 @@ protected: S32 prevWordPos(S32 cursorPos) const; S32 nextWordPos(S32 cursorPos) const; - S32 getLineCount(); - S32 getLineStart( S32 line ); + S32 getLineCount() const; + S32 getLineStart( S32 line ) const; void getLineAndOffset(S32 pos, S32* linep, S32* offsetp); S32 getPos(S32 line, S32 offset); @@ -338,6 +343,20 @@ protected: S32 removeStringNoUndo(S32 pos, S32 length); S32 overwriteCharNoUndo(S32 pos, llwchar wc); +protected: + void updateAllowingLanguageInput(); + BOOL hasPreeditString() const; + + // Overrides LLPreeditor + virtual void resetPreedit(); + virtual void updatePreedit(const LLWString &preedit_string, + const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position); + virtual void markAsPreedit(S32 position, S32 length); + virtual void getPreeditRange(S32 *position, S32 *length) const; + virtual void getSelectionRange(S32 *position, S32 *length) const; + virtual BOOL getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const; + virtual S32 getPreeditFontSize() const; + public: LLKeywords mKeywords; static LLColor4 mLinkColor; @@ -349,7 +368,7 @@ protected: mutable LLString mUTF8Text; mutable BOOL mTextIsUpToDate; - S32 mMaxTextLength; // Maximum length mText is allowed to be + S32 mMaxTextByteLength; // Maximum length mText is allowed to be in bytes const LLFontGL* mGLFont; @@ -439,6 +458,11 @@ protected: BOOL mParseHTML; LLString mHTML; + + LLWString mPreeditWString; + LLWString mPreeditOverwrittenWString; + std::vector<S32> mPreeditPositions; + std::vector<BOOL> mPreeditStandouts; }; class LLTextSegment diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp index 7af0d726cb..00a230dff3 100644 --- a/indra/llui/llui.cpp +++ b/indra/llui/llui.cpp @@ -1696,6 +1696,34 @@ LLVector2 LLUI::getWindowSize() } //static +void LLUI::screenPointToGL(S32 screen_x, S32 screen_y, S32 *gl_x, S32 *gl_y) +{ + *gl_x = llround((F32)screen_x * sGLScaleFactor.mV[VX]); + *gl_y = llround((F32)screen_y * sGLScaleFactor.mV[VY]); +} + +//static +void LLUI::glPointToScreen(S32 gl_x, S32 gl_y, S32 *screen_x, S32 *screen_y) +{ + *screen_x = llround((F32)gl_x / sGLScaleFactor.mV[VX]); + *screen_y = llround((F32)gl_y / sGLScaleFactor.mV[VY]); +} + +//static +void LLUI::screenRectToGL(const LLRect& screen, LLRect *gl) +{ + screenPointToGL(screen.mLeft, screen.mTop, &gl->mLeft, &gl->mTop); + screenPointToGL(screen.mRight, screen.mBottom, &gl->mRight, &gl->mBottom); +} + +//static +void LLUI::glRectToScreen(const LLRect& gl, LLRect *screen) +{ + glPointToScreen(gl.mLeft, gl.mTop, &screen->mLeft, &screen->mTop); + glPointToScreen(gl.mRight, gl.mBottom, &screen->mRight, &screen->mBottom); +} + +//static LLUUID LLUI::findAssetUUIDByName(const LLString &asset_name) { if(asset_name == LLString::null) return LLUUID::null; diff --git a/indra/llui/llui.h b/indra/llui/llui.h index b78b046a8c..b98f4d5de2 100644 --- a/indra/llui/llui.h +++ b/indra/llui/llui.h @@ -174,6 +174,10 @@ public: static void setLineWidth(F32 width); static LLUUID findAssetUUIDByName(const LLString& name); static LLVector2 getWindowSize(); + static void screenPointToGL(S32 screen_x, S32 screen_y, S32 *gl_x, S32 *gl_y); + static void glPointToScreen(S32 gl_x, S32 gl_y, S32 *screen_x, S32 *screen_y); + static void screenRectToGL(const LLRect& screen, LLRect *gl); + static void glRectToScreen(const LLRect& gl, LLRect *screen); static void setHtmlHelp(LLHtmlHelp* html_help); private: diff --git a/indra/llwindow/llpreeditor.h b/indra/llwindow/llpreeditor.h new file mode 100644 index 0000000000..63e64ab781 --- /dev/null +++ b/indra/llwindow/llpreeditor.h @@ -0,0 +1,81 @@ +/** + * @file llpreeditor.h + * @brief abstract class that defines interface for components to feedback preedits to users. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_PREEDITOR +#define LL_PREEDITOR + +class LLPreeditor +{ +public: + + typedef std::vector<S32> segment_lengths_t; + typedef std::vector<BOOL> standouts_t; + + // We don't delete against LLPreeditor, but compilers complain without this... + + virtual ~LLPreeditor() {}; + + // Discard any preedit info. on this preeditor. + + virtual void resetPreedit() = 0; + + // Update the preedit feedback using specified details. + // Existing preedit is discarded and replaced with the new one. (I.e., updatePreedit is not cumulative.) + // All arguments are IN. + // preedit_count is the number of elements in arrays preedit_list and preedit_standouts. + // preedit list is an array of preedit texts (clauses.) + // preedit_standouts indicates whether each preedit text should be shown as standout clause. + // caret_position is the preedit-local position of text editing caret, in # of llwchar. + + virtual void updatePreedit(const LLWString &preedit_string, + const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position) = 0; + + // Turn the specified sub-contents into an active preedit. + // Both position and length are IN and count with UTF-32 (llwchar) characters. + // This method primarily facilitates reconversion. + + virtual void markAsPreedit(S32 position, S32 length) = 0; + + // Get the position and the length of the active preedit in the contents. + // Both position and length are OUT and count with UTF-32 (llwchar) characters. + // When this preeditor has no active preedit, position receives + // the caret position, and length receives 0. + + virtual void getPreeditRange(S32 *position, S32 *length) const = 0; + + // Get the position and the length of the current selection in the contents. + // Both position and length are OUT and count with UTF-32 (llwchar) characters. + // When this preeditor has no selection, position receives + // the caret position, and length receives 0. + + virtual void getSelectionRange(S32 *position, S32 *length) const = 0; + + // Get the locations where the preedit and related UI elements are displayed. + // Locations are relative to the app window and measured in GL coordinate space (before scaling.) + // query_position is IN argument, and other three are OUT. + + virtual BOOL getPreeditLocation(S32 query_position, LLCoordGL *coord, LLRect *bounds, LLRect *control) const = 0; + + // Get the size (height) of the current font used in this preeditor. + + virtual S32 getPreeditFontSize() const = 0; + + // Get the contents of this preeditor as a LLWString. If there is an active preedit, + // the returned LLWString contains it. + + virtual const LLWString & getWText() const = 0; + + // Handle a UTF-32 char on this preeditor, i.e., add the character + // to the contents. + // This is a back door of the method of same name of LLWindowCallback. + // called_from_parent should be set to FALSE if calling through LLPreeditor. + + virtual BOOL handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent) = 0; +}; + +#endif diff --git a/indra/llwindow/llwindow.h b/indra/llwindow/llwindow.h index 4db2c8105f..9a3542d3fb 100644 --- a/indra/llwindow/llwindow.h +++ b/indra/llwindow/llwindow.h @@ -81,6 +81,8 @@ class LLSplashScreen; class LLWindow; +class LLPreeditor; + class LLWindowCallbacks { public: @@ -222,8 +224,10 @@ public: virtual void *getPlatformWindow() = 0; // control platform's Language Text Input mechanisms. - virtual void allowLanguageTextInput( BOOL b ) {}; + virtual void allowLanguageTextInput(LLPreeditor *preeditor, BOOL b) {} virtual void setLanguageTextInput( const LLCoordGL & pos ) {}; + virtual void updateLanguageTextInputArea() {} + virtual void interruptLanguageTextInput() {} protected: LLWindow(BOOL fullscreen, U32 flags); diff --git a/indra/llwindow/llwindowmacosx.cpp b/indra/llwindow/llwindowmacosx.cpp index 2724cb56e4..943b98e9d5 100644 --- a/indra/llwindow/llwindowmacosx.cpp +++ b/indra/llwindow/llwindowmacosx.cpp @@ -48,6 +48,7 @@ #include "indra_constants.h" #include "llwindowmacosx-objc.h" +#include "llpreeditor.h" extern BOOL gDebugWindowProc; @@ -172,8 +173,22 @@ static EventTypeSpec WindowHandlerEventList[] = { kEventClassKeyboard, kEventRawKeyModifiersChanged }, // Text input events - { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent } - + { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent }, + { kEventClassTextInput, kEventTextInputUpdateActiveInputArea }, + { kEventClassTextInput, kEventTextInputOffsetToPos }, + { kEventClassTextInput, kEventTextInputPosToOffset }, + { kEventClassTextInput, kEventTextInputShowHideBottomWindow }, + { kEventClassTextInput, kEventTextInputGetSelectedText }, + { kEventClassTextInput, kEventTextInputFilterText }, + + // TSM Document Access events (advanced input method support) + { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetLength }, + { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetSelectedRange }, + { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetCharacters }, + { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetFont }, + { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetGlyphInfo }, + { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessLockDocument }, + { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessUnlockDocument } }; static EventTypeSpec GlobalHandlerEventList[] = @@ -195,7 +210,22 @@ static EventTypeSpec GlobalHandlerEventList[] = { kEventClassKeyboard, kEventRawKeyModifiersChanged }, // Text input events - { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent } + { kEventClassTextInput, kEventTextInputUpdateActiveInputArea }, + { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent }, + { kEventClassTextInput, kEventTextInputOffsetToPos }, + { kEventClassTextInput, kEventTextInputPosToOffset }, + { kEventClassTextInput, kEventTextInputShowHideBottomWindow }, + { kEventClassTextInput, kEventTextInputGetSelectedText }, + { kEventClassTextInput, kEventTextInputFilterText }, + + // TSM Document Access events (advanced input method support) + { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetLength }, + { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetSelectedRange }, + { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetCharacters }, + { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetFont }, + { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessGetGlyphInfo }, + { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessLockDocument }, + { kEventClassTSMDocumentAccess, kEventTSMDocumentAccessUnlockDocument } }; static EventTypeSpec CommandHandlerEventList[] = @@ -246,6 +276,7 @@ LLWindowMacOSX::LLWindowMacOSX(char *title, char *name, S32 x, S32 y, S32 width, mLanguageTextInputAllowed = FALSE; mTSMScriptCode = 0; mTSMLangCode = 0; + mPreeditor = NULL; // For reasons that aren't clear to me, LLTimers seem to be created in the "started" state. // Since the started state of this one is used to track whether the NMRec has been installed, it wants to start out in the "stopped" state. @@ -497,15 +528,16 @@ BOOL LLWindowMacOSX::createContext(int x, int y, int width, int height, int bits mTSMDocument = NULL; } static InterfaceTypeList types = { kUnicodeDocument }; - OSErr err = NewTSMDocument(1, types, &mTSMDocument, 0); + err = NewTSMDocument(1, types, &mTSMDocument, 0); if (err != noErr) { llwarns << "createContext: couldn't create a TSMDocument (" << err << ")" << llendl; } if (mTSMDocument) { - UseInputWindow(mTSMDocument, TRUE); ActivateTSMDocument(mTSMDocument); + UseInputWindow(mTSMDocument, FALSE); + allowLanguageTextInput(NULL, FALSE); } } @@ -1949,6 +1981,141 @@ OSStatus LLWindowMacOSX::eventHandler (EventHandlerCallRef myHandler, EventRef e { switch (evtKind) { + case kEventTextInputUpdateActiveInputArea: + { + EventParamType param_type; + + long fix_len; + UInt32 text_len; + if (mPreeditor + && (result = GetEventParameter(event, kEventParamTextInputSendFixLen, + typeLongInteger, ¶m_type, sizeof(fix_len), NULL, &fix_len)) == noErr + && typeLongInteger == param_type + && (result = GetEventParameter(event, kEventParamTextInputSendText, + typeUnicodeText, ¶m_type, 0, &text_len, NULL)) == noErr + && typeUnicodeText == param_type) + { + // Handle an optional (but essential to facilitate TSMDA) ReplaceRange param. + CFRange range; + if (GetEventParameter(event, kEventParamTextInputSendReplaceRange, + typeCFRange, ¶m_type, sizeof(range), NULL, &range) == noErr + && typeCFRange == param_type) + { + // Although the spec. is unclear, replace range should + // not present when there is an active preedit. We just + // ignore the case. markAsPreedit will detect the case and warn it. + const LLWString & text = mPreeditor->getWText(); + const S32 location = wstring_wstring_length_from_utf16_length(text, 0, range.location); + const S32 length = wstring_wstring_length_from_utf16_length(text, location, range.length); + mPreeditor->markAsPreedit(location, length); + } + mPreeditor->resetPreedit(); + + // Receive the text from input method. + U16 *const text = new U16[text_len / sizeof(U16)]; + GetEventParameter(event, kEventParamTextInputSendText, typeUnicodeText, NULL, text_len, NULL, text); + if (fix_len < 0) + { + // Do we still need this? Seems obsolete... + fix_len = text_len; + } + const LLWString fix_string + = utf16str_to_wstring(llutf16string(text, fix_len / sizeof(U16))); + const LLWString preedit_string + = utf16str_to_wstring(llutf16string(text + fix_len / sizeof(U16), (text_len - fix_len) / sizeof(U16))); + delete[] text; + + // Handle fixed (comitted) string. + if (fix_string.length() > 0) + { + for (LLWString::const_iterator i = fix_string.begin(); i != fix_string.end(); i++) + { + mPreeditor->handleUnicodeCharHere(*i, FALSE); + } + } + + // Receive the segment info and caret position. + LLPreeditor::segment_lengths_t preedit_segment_lengths; + LLPreeditor::standouts_t preedit_standouts; + S32 caret_position = preedit_string.length(); + UInt32 text_range_array_size; + if (GetEventParameter(event, kEventParamTextInputSendHiliteRng, typeTextRangeArray, + ¶m_type, 0, &text_range_array_size, NULL) == noErr + && typeTextRangeArray == param_type + && text_range_array_size > sizeof(TextRangeArray)) + { + // TextRangeArray is a variable-length struct. + TextRangeArray * const text_range_array = (TextRangeArray *) new char[text_range_array_size]; + GetEventParameter(event, kEventParamTextInputSendHiliteRng, typeTextRangeArray, + NULL, text_range_array_size, NULL, text_range_array); + + // WARNING: We assume ranges are in ascending order, + // although the condition is undocumented. It seems + // OK to assume this. I also assumed + // the ranges are contiguous in previous versions, but I + // have heard a rumore that older versions os ATOK may + // return ranges with some _gap_. I don't know whether + // it is true, but I'm preparing my code for the case. + + const S32 ranges = text_range_array->fNumOfRanges; + preedit_segment_lengths.reserve(ranges); + preedit_standouts.reserve(ranges); + + S32 last_bytes = 0; + S32 last_utf32 = 0; + for (S32 i = 0; i < ranges; i++) + { + const TextRange &range = text_range_array->fRange[i]; + if (range.fStart > last_bytes) + { + const S32 length_utf16 = (range.fStart - last_bytes) / sizeof(U16); + const S32 length_utf32 = wstring_wstring_length_from_utf16_length(preedit_string, last_utf32, length_utf16); + preedit_segment_lengths.push_back(length_utf32); + preedit_standouts.push_back(FALSE); + last_utf32 += length_utf32; + } + if (range.fEnd > range.fStart) + { + const S32 length_utf16 = (range.fEnd - range.fStart) / sizeof(U16); + const S32 length_utf32 = wstring_wstring_length_from_utf16_length(preedit_string, last_utf32, length_utf16); + preedit_segment_lengths.push_back(length_utf32); + preedit_standouts.push_back( + kTSMHiliteSelectedRawText == range.fHiliteStyle + || kTSMHiliteSelectedConvertedText == range.fHiliteStyle + || kTSMHiliteSelectedText == range.fHiliteStyle); + last_utf32 += length_utf32; + } + if (kTSMHiliteCaretPosition == range.fHiliteStyle) + { + caret_position = last_utf32; + } + last_bytes = range.fEnd; + } + if (preedit_string.length() > last_utf32) + { + preedit_segment_lengths.push_back(preedit_string.length() - last_utf32); + preedit_standouts.push_back(FALSE); + } + + delete[] (char *) text_range_array; + } + + // Handle preedit string. + if (preedit_string.length() > 0) + { + if (preedit_segment_lengths.size() == 0) + { + preedit_segment_lengths.push_back(preedit_string.length()); + preedit_standouts.push_back(FALSE); + } + mPreeditor->updatePreedit(preedit_string, preedit_segment_lengths, preedit_standouts, caret_position); + } + + result = noErr; + } + } + break; + case kEventTextInputUnicodeForKeyEvent: { UInt32 modifiers = 0; @@ -2021,6 +2188,63 @@ OSStatus LLWindowMacOSX::eventHandler (EventHandlerCallRef myHandler, EventRef e result = err; } break; + + case kEventTextInputOffsetToPos: + { + EventParamType param_type; + long offset; + if (mPreeditor + && GetEventParameter(event, kEventParamTextInputSendTextOffset, typeLongInteger, + ¶m_type, sizeof(offset), NULL, &offset) == noErr + && typeLongInteger == param_type) + { + S32 preedit, preedit_length; + mPreeditor->getPreeditRange(&preedit, &preedit_length); + const LLWString & text = mPreeditor->getWText(); + + LLCoordGL caret_coord; + LLRect preedit_bounds; + if (0 <= offset + && mPreeditor->getPreeditLocation(wstring_wstring_length_from_utf16_length(text, preedit, offset / sizeof(U16)), + &caret_coord, &preedit_bounds, NULL)) + { + LLCoordGL caret_base_coord(caret_coord.mX, preedit_bounds.mBottom); + LLCoordScreen caret_base_coord_screen; + convertCoords(caret_base_coord, &caret_base_coord_screen); + Point qd_point; + qd_point.h = caret_base_coord_screen.mX; + qd_point.v = caret_base_coord_screen.mY; + SetEventParameter(event, kEventParamTextInputReplyPoint, typeQDPoint, sizeof(qd_point), &qd_point); + + short line_height = (short) preedit_bounds.getHeight(); + SetEventParameter(event, kEventParamTextInputReplyLineHeight, typeShortInteger, sizeof(line_height), &line_height); + + result = noErr; + } + else + { + result = errOffsetInvalid; + } + } + } + break; + + case kEventTextInputGetSelectedText: + { + if (mPreeditor) + { + S32 selection, selection_length; + mPreeditor->getSelectionRange(&selection, &selection_length); + if (selection_length) + { + const LLWString text = mPreeditor->getWText().substr(selection, selection_length); + const llutf16string text_utf16 = wstring_to_utf16str(text); + result = SetEventParameter(event, kEventParamTextInputReplyText, typeUnicodeText, + text_utf16.length() * sizeof(U16), text_utf16.c_str()); + } + } + } + break; } } break; @@ -2194,6 +2418,13 @@ OSStatus LLWindowMacOSX::eventHandler (EventHandlerCallRef myHandler, EventRef e switch (evtKind) { case kEventMouseDown: + if (mLanguageTextInputAllowed) + { + // We need to interrupt before handling mouse events, + // so that the fixed string from IM are delivered to + // the currently focused UI component. + interruptLanguageTextInput(); + } switch(button) { case kEventMouseButtonPrimary: @@ -2287,6 +2518,10 @@ OSStatus LLWindowMacOSX::eventHandler (EventHandlerCallRef myHandler, EventRef e mCallbacks->handleFocus(this); break; case kEventWindowDeactivated: + if (mTSMDocument) + { + DeactivateTSMDocument(mTSMDocument); + } mCallbacks->handleFocusLost(this); break; case kEventWindowBoundsChanging: @@ -2359,6 +2594,109 @@ OSStatus LLWindowMacOSX::eventHandler (EventHandlerCallRef myHandler, EventRef e // BringToFront(mWindow); // result = noErr; break; + + } + break; + + case kEventClassTSMDocumentAccess: + if (mPreeditor) + { + switch(evtKind) + { + + case kEventTSMDocumentAccessGetLength: + { + // Return the number of UTF-16 units in the text, excluding those for preedit. + + S32 preedit, preedit_length; + mPreeditor->getPreeditRange(&preedit, &preedit_length); + const LLWString & text = mPreeditor->getWText(); + const CFIndex length = wstring_utf16_length(text, 0, preedit) + + wstring_utf16_length(text, preedit + preedit_length, text.length()); + result = SetEventParameter(event, kEventParamTSMDocAccessCharacterCount, typeCFIndex, sizeof(length), &length); + } + break; + + case kEventTSMDocumentAccessGetSelectedRange: + { + // Return the selected range, excluding preedit. + // In our preeditor, preedit and selection are exclusive, so, + // when it has a preedit, there is no selection and the + // insertion point is on the preedit that corrupses into the + // beginning of the preedit when the preedit was removed. + + S32 preedit, preedit_length; + mPreeditor->getPreeditRange(&preedit, &preedit_length); + const LLWString & text = mPreeditor->getWText(); + + CFRange range; + if (preedit_length) + { + range.location = wstring_utf16_length(text, 0, preedit); + range.length = 0; + } + else + { + S32 selection, selection_length; + mPreeditor->getSelectionRange(&selection, &selection_length); + range.location = wstring_utf16_length(text, 0, selection); + range.length = wstring_utf16_length(text, selection, selection_length); + } + + result = SetEventParameter(event, kEventParamTSMDocAccessReplyCharacterRange, typeCFRange, sizeof(range), &range); + } + break; + + case kEventTSMDocumentAccessGetCharacters: + { + UniChar *target_pointer; + CFRange range; + EventParamType param_type; + if ((result = GetEventParameter(event, kEventParamTSMDocAccessSendCharacterRange, + typeCFRange, ¶m_type, sizeof(range), NULL, &range)) == noErr + && typeCFRange == param_type + && (result = GetEventParameter(event, kEventParamTSMDocAccessSendCharactersPtr, + typePtr, ¶m_type, sizeof(target_pointer), NULL, &target_pointer)) == noErr + && typePtr == param_type) + { + S32 preedit, preedit_length; + mPreeditor->getPreeditRange(&preedit, &preedit_length); + const LLWString & text = mPreeditor->getWText(); + + // The GetCharacters event of TSMDA has a fundamental flaw; + // An input method need to decide the starting offset and length + // *before* it actually see the contents, so it is impossible + // to guarantee the character-aligned access. The event reply + // has no way to indicate a condition something like "Request + // was not fulfilled due to unaligned access. Please retry." + // Any error sent back to the input method stops use of TSMDA + // entirely during the session... + // We need to simulate very strictly the behaviour as if the + // underlying *text engine* holds the contents in UTF-16. + // I guess this is the reason why Apple repeats saying "all + // text handling application should use UTF-16." They are + // trying to _fix_ the flaw by changing the appliations... + // ... or, domination of UTF-16 in the industry may be a part + // of the company vision, and Apple is trying to force third + // party developers to obey their vision. Remember that use + // of 16 bits per _a_character_ was one of the very fundamental + // Unicode design policy on its early days (during late 80s) + // and the original Unicode design was by two Apple employees... + + const llutf16string text_utf16 + = wstring_to_utf16str(text, preedit) + + wstring_to_utf16str(text.substr(preedit + preedit_length)); + + llassert_always(sizeof(U16) == sizeof(UniChar)); + llassert(0 <= range.location && 0 <= range.length && range.location + range.length <= text_utf16.length()); + memcpy(target_pointer, text_utf16.c_str() + range.location, range.length * sizeof(UniChar)); + + // Note that result has already been set above. + } + } + break; + + } } break; } @@ -2995,10 +3333,34 @@ static long getDictLong (CFDictionaryRef refDict, CFStringRef key) return int_value; // otherwise return the long value } -void LLWindowMacOSX::allowLanguageTextInput(BOOL b) +void LLWindowMacOSX::allowLanguageTextInput(LLPreeditor *preeditor, BOOL b) { ScriptLanguageRecord script_language; + if (preeditor != mPreeditor && !b) + { + // This condition may occur by a call to + // setEnabled(BOOL) against LLTextEditor or LLLineEditor + // when the control is not focused. + // We need to silently ignore the case so that + // the language input status of the focused control + // is not disturbed. + return; + } + + // Take care of old and new preeditors. + if (preeditor != mPreeditor || !b) + { + // We need to interrupt before updating mPreeditor, + // so that the fix string from input method goes to + // the old preeditor. + if (mLanguageTextInputAllowed) + { + interruptLanguageTextInput(); + } + mPreeditor = (b ? preeditor : NULL); + } + if (b == mLanguageTextInputAllowed) { return; @@ -3028,4 +3390,12 @@ void LLWindowMacOSX::allowLanguageTextInput(BOOL b) } } +void LLWindowMacOSX::interruptLanguageTextInput() +{ + if (mTSMDocument) + { + FixTSMDocument(mTSMDocument); + } +} + #endif // LL_DARWIN diff --git a/indra/llwindow/llwindowmacosx.h b/indra/llwindow/llwindowmacosx.h index ee3019bd21..2a4cf97308 100644 --- a/indra/llwindow/llwindowmacosx.h +++ b/indra/llwindow/llwindowmacosx.h @@ -111,8 +111,10 @@ public: /*virtual*/ void *getPlatformWindow(); /*virtual*/ void bringToFront() {}; - /*virtual*/ void allowLanguageTextInput(BOOL b); - + /*virtual*/ void allowLanguageTextInput(LLPreeditor *preeditor, BOOL b); + /*virtual*/ void updateLanguageTextInputArea(const LLCoordGL& caret, const LLRect& bounds); + /*virtual*/ void interruptLanguageTextInput(); + protected: LLWindowMacOSX( char *title, char *name, int x, int y, int width, int height, U32 flags, @@ -193,6 +195,7 @@ protected: BOOL mLanguageTextInputAllowed; ScriptCode mTSMScriptCode; LangCode mTSMLangCode; + LLPreeditor* mPreeditor; friend class LLWindowManager; }; diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index e5fd0f7360..4e4bed6485 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -57,6 +57,8 @@ #include "indra_constants.h" +#include "llpreeditor.h" + // culled from winuser.h #ifndef WM_MOUSEWHEEL /* Added to be compatible with later SDK's */ const S32 WM_MOUSEWHEEL = 0x020A; @@ -112,6 +114,7 @@ public: public: // Wrappers for IMM API. static BOOL isIME(HKL hkl); + static HWND getDefaultIMEWnd(HWND hwnd); static HIMC getContext(HWND hwnd); static BOOL releaseContext(HWND hwnd, HIMC himc); static BOOL getOpenStatus(HIMC himc); @@ -120,6 +123,11 @@ public: static BOOL setConversionStatus(HIMC himc, DWORD conversion, DWORD sentence); static BOOL getCompositionWindow(HIMC himc, LPCOMPOSITIONFORM form); static BOOL setCompositionWindow(HIMC himc, LPCOMPOSITIONFORM form); + static LONG getCompositionString(HIMC himc, DWORD index, LPVOID data, DWORD length); + static BOOL setCompositionString(HIMC himc, DWORD index, LPVOID pComp, DWORD compLength, LPVOID pRead, DWORD readLength); + static BOOL setCompositionFont(HIMC himc, LPLOGFONTW logfont); + static BOOL setCandidateWindow(HIMC himc, LPCANDIDATEFORM candidate_form); + static BOOL notifyIME(HIMC himc, DWORD action, DWORD index, DWORD value); private: LLWinImm(); @@ -128,6 +136,7 @@ private: private: // Pointers to IMM API. BOOL (WINAPI *mImmIsIME)(HKL); + HWND (WINAPI *mImmGetDefaultIMEWnd)(HWND); HIMC (WINAPI *mImmGetContext)(HWND); BOOL (WINAPI *mImmReleaseContext)(HWND, HIMC); BOOL (WINAPI *mImmGetOpenStatus)(HIMC); @@ -136,6 +145,11 @@ private: BOOL (WINAPI *mImmSetConversionStatus)(HIMC, DWORD, DWORD); BOOL (WINAPI *mImmGetCompostitionWindow)(HIMC, LPCOMPOSITIONFORM); BOOL (WINAPI *mImmSetCompostitionWindow)(HIMC, LPCOMPOSITIONFORM); + LONG (WINAPI *mImmGetCompositionString)(HIMC, DWORD, LPVOID, DWORD); + BOOL (WINAPI *mImmSetCompositionString)(HIMC, DWORD, LPVOID, DWORD, LPVOID, DWORD); + BOOL (WINAPI *mImmSetCompositionFont)(HIMC, LPLOGFONTW); + BOOL (WINAPI *mImmSetCandidateWindow)(HIMC, LPCANDIDATEFORM); + BOOL (WINAPI *mImmNotifyIME)(HIMC, DWORD, DWORD, DWORD); private: HMODULE mHImmDll; @@ -155,6 +169,7 @@ LLWinImm::LLWinImm() : mHImmDll(NULL) if (mHImmDll != NULL) { mImmIsIME = (BOOL (WINAPI *)(HKL)) GetProcAddress(mHImmDll, "ImmIsIME"); + mImmGetDefaultIMEWnd = (HWND (WINAPI *)(HWND)) GetProcAddress(mHImmDll, "ImmGetDefaultIMEWnd"); mImmGetContext = (HIMC (WINAPI *)(HWND)) GetProcAddress(mHImmDll, "ImmGetContext"); mImmReleaseContext = (BOOL (WINAPI *)(HWND, HIMC)) GetProcAddress(mHImmDll, "ImmReleaseContext"); mImmGetOpenStatus = (BOOL (WINAPI *)(HIMC)) GetProcAddress(mHImmDll, "ImmGetOpenStatus"); @@ -163,8 +178,14 @@ LLWinImm::LLWinImm() : mHImmDll(NULL) mImmSetConversionStatus = (BOOL (WINAPI *)(HIMC, DWORD, DWORD)) GetProcAddress(mHImmDll, "ImmSetConversionStatus"); mImmGetCompostitionWindow = (BOOL (WINAPI *)(HIMC, LPCOMPOSITIONFORM)) GetProcAddress(mHImmDll, "ImmGetCompositionWindow"); mImmSetCompostitionWindow = (BOOL (WINAPI *)(HIMC, LPCOMPOSITIONFORM)) GetProcAddress(mHImmDll, "ImmSetCompositionWindow"); + mImmGetCompositionString= (LONG (WINAPI *)(HIMC, DWORD, LPVOID, DWORD)) GetProcAddress(mHImmDll, "ImmGetCompositionStringW"); + mImmSetCompositionString= (BOOL (WINAPI *)(HIMC, DWORD, LPVOID, DWORD, LPVOID, DWORD)) GetProcAddress(mHImmDll, "ImmSetCompositionStringW"); + mImmSetCompositionFont = (BOOL (WINAPI *)(HIMC, LPLOGFONTW)) GetProcAddress(mHImmDll, "ImmSetCompositionFontW"); + mImmSetCandidateWindow = (BOOL (WINAPI *)(HIMC, LPCANDIDATEFORM)) GetProcAddress(mHImmDll, "ImmSetCandidateWindow"); + mImmNotifyIME = (BOOL (WINAPI *)(HIMC, DWORD, DWORD, DWORD)) GetProcAddress(mHImmDll, "ImmNotifyIME"); if (mImmIsIME == NULL || + mImmGetDefaultIMEWnd == NULL || mImmGetContext == NULL || mImmReleaseContext == NULL || mImmGetOpenStatus == NULL || @@ -172,7 +193,12 @@ LLWinImm::LLWinImm() : mHImmDll(NULL) mImmGetConversionStatus == NULL || mImmSetConversionStatus == NULL || mImmGetCompostitionWindow == NULL || - mImmSetCompostitionWindow == NULL) + mImmSetCompostitionWindow == NULL || + mImmGetCompositionString == NULL || + mImmSetCompositionString == NULL || + mImmSetCompositionFont == NULL || + mImmSetCandidateWindow == NULL || + mImmNotifyIME == NULL) { // If any of the above API entires are not found, we can't use IMM API. // So, turn off the IMM support. We should log some warning message in @@ -186,6 +212,7 @@ LLWinImm::LLWinImm() : mHImmDll(NULL) // If we unload the library, make sure all the function pointers are cleared mImmIsIME = NULL; + mImmGetDefaultIMEWnd = NULL; mImmGetContext = NULL; mImmReleaseContext = NULL; mImmGetOpenStatus = NULL; @@ -194,6 +221,11 @@ LLWinImm::LLWinImm() : mHImmDll(NULL) mImmSetConversionStatus = NULL; mImmGetCompostitionWindow = NULL; mImmSetCompostitionWindow = NULL; + mImmGetCompositionString = NULL; + mImmSetCompositionString = NULL; + mImmSetCompositionFont = NULL; + mImmSetCandidateWindow = NULL; + mImmNotifyIME = NULL; } } } @@ -272,6 +304,50 @@ BOOL LLWinImm::setCompositionWindow(HIMC himc, LPCOMPOSITIONFORM form) } +// static +LONG LLWinImm::getCompositionString(HIMC himc, DWORD index, LPVOID data, DWORD length) +{ + if ( sTheInstance.mImmGetCompositionString ) + return sTheInstance.mImmGetCompositionString(himc, index, data, length); + return FALSE; +} + + +// static +BOOL LLWinImm::setCompositionString(HIMC himc, DWORD index, LPVOID pComp, DWORD compLength, LPVOID pRead, DWORD readLength) +{ + if ( sTheInstance.mImmSetCompositionString ) + return sTheInstance.mImmSetCompositionString(himc, index, pComp, compLength, pRead, readLength); + return FALSE; +} + +// static +BOOL LLWinImm::setCompositionFont(HIMC himc, LPLOGFONTW pFont) +{ + if ( sTheInstance.mImmSetCompositionFont ) + return sTheInstance.mImmSetCompositionFont(himc, pFont); + return FALSE; +} + +// static +BOOL LLWinImm::setCandidateWindow(HIMC himc, LPCANDIDATEFORM form) +{ + if ( sTheInstance.mImmSetCandidateWindow ) + return sTheInstance.mImmSetCandidateWindow(himc, form); + return FALSE; +} + +// static +BOOL LLWinImm::notifyIME(HIMC himc, DWORD action, DWORD index, DWORD value) +{ + if ( sTheInstance.mImmNotifyIME ) + return sTheInstance.mImmNotifyIME(himc, action, index, value); + return FALSE; +} + + + + // ---------------------------------------------------------------------------------------- LLWinImm::~LLWinImm() { @@ -304,13 +380,14 @@ LLWindowWin32::LLWindowWin32(char *title, char *name, S32 x, S32 y, S32 width, mNativeAspectRatio = 0.f; mMousePositionModified = FALSE; mInputProcessingPaused = FALSE; + mPreeditor = NULL; // Initialize the keyboard gKeyboard = new LLKeyboardWin32(); // Initialize (boot strap) the Language text input management, // based on the system's (user's) default settings. - allowLanguageTextInput(FALSE); + allowLanguageTextInput(mPreeditor, FALSE); GLuint pixel_format; WNDCLASS wc; @@ -938,6 +1015,10 @@ LLWindowWin32::LLWindowWin32(char *title, char *name, S32 x, S32 y, S32 width, initCursors(); setCursor( UI_CURSOR_ARROW ); + // Initialize (boot strap) the Language text input management, + // based on the system's (or user's) default settings. + allowLanguageTextInput(NULL, FALSE); + // Direct Input HRESULT hr; @@ -2035,6 +2116,11 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ BOOL minimized = BOOL(HIWORD(w_param)); + if (!activating && LLWinImm::isAvailable() && window_imp->mPreeditor) + { + window_imp->interruptLanguageTextInput(); + } + // JC - I'm not sure why, but if we don't report that we handled the // WM_ACTIVATE message, the WM_ACTIVATEAPP messages don't work // properly when we run fullscreen. @@ -2127,6 +2213,47 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ // pass on to windows break; + case WM_IME_SETCONTEXT: + if (LLWinImm::isAvailable() && window_imp->mPreeditor) + { + l_param &= ~ISC_SHOWUICOMPOSITIONWINDOW; + // Invoke DefWinProc with the modified LPARAM. + } + break; + + case WM_IME_STARTCOMPOSITION: + if (LLWinImm::isAvailable() && window_imp->mPreeditor) + { + window_imp->handleStartCompositionMessage(); + return 0; + } + break; + + case WM_IME_ENDCOMPOSITION: + if (LLWinImm::isAvailable() && window_imp->mPreeditor) + { + return 0; + } + break; + + case WM_IME_COMPOSITION: + if (LLWinImm::isAvailable() && window_imp->mPreeditor) + { + window_imp->handleCompositionMessage(l_param); + return 0; + } + break; + + case WM_IME_REQUEST: + if (LLWinImm::isAvailable() && window_imp->mPreeditor) + { + LRESULT result = 0; + if (window_imp->handleImeRequests(w_param, l_param, &result)) + { + return result; + } + } + break; case WM_CHAR: // Should really use WM_UNICHAR eventually, but it requires a specific Windows version and I need @@ -2154,6 +2281,11 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_LBUTTONDOWN: { + if (LLWinImm::isAvailable() && window_imp->mPreeditor) + { + window_imp->interruptLanguageTextInput(); + } + // Because we move the cursor position in the app, we need to query // to find out where the cursor at the time the event is handled. // If we don't do this, many clicks could get buffered up, and if the @@ -2236,6 +2368,11 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_RBUTTONDBLCLK: case WM_RBUTTONDOWN: { + if (LLWinImm::isAvailable() && window_imp->mPreeditor) + { + window_imp->interruptLanguageTextInput(); + } + // Because we move the cursor position in tllviewerhe app, we need to query // to find out where the cursor at the time the event is handled. // If we don't do this, many clicks could get buffered up, and if the @@ -2287,6 +2424,11 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_MBUTTONDOWN: // case WM_MBUTTONDBLCLK: { + if (LLWinImm::isAvailable() && window_imp->mPreeditor) + { + window_imp->interruptLanguageTextInput(); + } + // Because we move the cursor position in tllviewerhe app, we need to query // to find out where the cursor at the time the event is handled. // If we don't do this, many clicks could get buffered up, and if the @@ -3324,15 +3466,37 @@ void LLWindowWin32::focusClient() SetFocus ( mWindowHandle ); } -void LLWindowWin32::allowLanguageTextInput(BOOL b) +void LLWindowWin32::allowLanguageTextInput(LLPreeditor *preeditor, BOOL b) { if (b == sLanguageTextInputAllowed || !LLWinImm::isAvailable()) { return; } + + if (preeditor != mPreeditor && !b) + { + // This condition may occur with a call to + // setEnabled(BOOL) from LLTextEditor or LLLineEditor + // when the control is not focused. + // We need to silently ignore the case so that + // the language input status of the focused control + // is not disturbed. + return; + } + + // Take care of old and new preeditors. + if (preeditor != mPreeditor || !b) + { + if (sLanguageTextInputAllowed) + { + interruptLanguageTextInput(); + } + mPreeditor = (b ? preeditor : NULL); + } + sLanguageTextInputAllowed = b; - if (b) + if ( sLanguageTextInputAllowed ) { // Allowing: Restore the previous IME status, so that the user has a feeling that the previous // text input continues naturally. Be careful, however, the IME status is meaningful only during the user keeps @@ -3368,7 +3532,24 @@ void LLWindowWin32::allowLanguageTextInput(BOOL b) LLWinImm::releaseContext(mWindowHandle, himc); } } +} +void LLWindowWin32::fillCandidateForm(const LLCoordGL& caret, const LLRect& bounds, + CANDIDATEFORM *form) +{ + LLCoordWindow caret_coord, top_left, bottom_right; + convertCoords(caret, &caret_coord); + convertCoords(LLCoordGL(bounds.mLeft, bounds.mTop), &top_left); + convertCoords(LLCoordGL(bounds.mRight, bounds.mBottom), &bottom_right); + + memset(form, 0, sizeof(CANDIDATEFORM)); + form->dwStyle = CFS_EXCLUDE; + form->ptCurrentPos.x = caret_coord.mX; + form->ptCurrentPos.y = caret_coord.mY; + form->rcArea.left = top_left.mX; + form->rcArea.top = top_left.mY; + form->rcArea.right = bottom_right.mX; + form->rcArea.bottom = bottom_right.mY; } @@ -3416,4 +3597,455 @@ void LLWindowWin32::setLanguageTextInput( const LLCoordGL & position ) } } + +void LLWindowWin32::fillCharPosition(const LLCoordGL& caret, const LLRect& bounds, const LLRect& control, + IMECHARPOSITION *char_position) +{ + LLCoordScreen caret_coord, top_left, bottom_right; + convertCoords(caret, &caret_coord); + convertCoords(LLCoordGL(bounds.mLeft, bounds.mTop), &top_left); + convertCoords(LLCoordGL(bounds.mRight, bounds.mBottom), &bottom_right); + + char_position->pt.x = caret_coord.mX; + char_position->pt.y = top_left.mY; // Windows wants the coordinate of upper left corner of a character... + char_position->cLineHeight = bottom_right.mY - top_left.mY; + char_position->rcDocument.left = top_left.mX; + char_position->rcDocument.top = top_left.mY; + char_position->rcDocument.right = bottom_right.mX; + char_position->rcDocument.bottom = bottom_right.mY; +} + +void LLWindowWin32::fillCompositionLogfont(LOGFONT *logfont) +{ + // Our font is a list of FreeType recognized font files that may + // not have a corresponding ones in Windows' fonts. Hence, we + // can't simply tell Windows which font we are using. We will + // notify a _standard_ font for a current input locale instead. + // We use a hard-coded knowledge about the Windows' standard + // configuration to do so... + + memset(logfont, 0, sizeof(LOGFONT)); + + const WORD lang_id = LOWORD(GetKeyboardLayout(0)); + switch (PRIMARYLANGID(lang_id)) + { + case LANG_CHINESE: + // We need to identify one of two Chinese fonts. + switch (SUBLANGID(lang_id)) + { + case SUBLANG_CHINESE_SIMPLIFIED: + case SUBLANG_CHINESE_SINGAPORE: + logfont->lfCharSet = GB2312_CHARSET; + lstrcpy(logfont->lfFaceName, TEXT("SimHei")); + break; + case SUBLANG_CHINESE_TRADITIONAL: + case SUBLANG_CHINESE_HONGKONG: + case SUBLANG_CHINESE_MACAU: + default: + logfont->lfCharSet = CHINESEBIG5_CHARSET; + lstrcpy(logfont->lfFaceName, TEXT("MingLiU")); + break; + } + break; + case LANG_JAPANESE: + logfont->lfCharSet = SHIFTJIS_CHARSET; + lstrcpy(logfont->lfFaceName, TEXT("MS Gothic")); + break; + case LANG_KOREAN: + logfont->lfCharSet = HANGUL_CHARSET; + lstrcpy(logfont->lfFaceName, TEXT("Gulim")); + break; + default: + logfont->lfCharSet = ANSI_CHARSET; + lstrcpy(logfont->lfFaceName, TEXT("Tahoma")); + break; + } + + logfont->lfHeight = mPreeditor->getPreeditFontSize(); + logfont->lfWeight = FW_NORMAL; +} + +U32 LLWindowWin32::fillReconvertString(const LLWString &text, + S32 focus, S32 focus_length, RECONVERTSTRING *reconvert_string) +{ + const llutf16string text_utf16 = wstring_to_utf16str(text); + const DWORD required_size = sizeof(RECONVERTSTRING) + (text_utf16.length() + 1) * sizeof(WCHAR); + if (reconvert_string && reconvert_string->dwSize >= required_size) + { + const DWORD focus_utf16_at = wstring_utf16_length(text, 0, focus); + const DWORD focus_utf16_length = wstring_utf16_length(text, focus, focus_length); + + reconvert_string->dwVersion = 0; + reconvert_string->dwStrLen = text_utf16.length(); + reconvert_string->dwStrOffset = sizeof(RECONVERTSTRING); + reconvert_string->dwCompStrLen = focus_utf16_length; + reconvert_string->dwCompStrOffset = focus_utf16_at * sizeof(WCHAR); + reconvert_string->dwTargetStrLen = 0; + reconvert_string->dwTargetStrOffset = focus_utf16_at * sizeof(WCHAR); + + const LPWSTR text = (LPWSTR)((BYTE *)reconvert_string + sizeof(RECONVERTSTRING)); + memcpy(text, text_utf16.c_str(), (text_utf16.length() + 1) * sizeof(WCHAR)); + } + return required_size; +} + +void LLWindowWin32::updateLanguageTextInputArea() +{ + if (!mPreeditor || !LLWinImm::isAvailable()) + { + return; + } + + LLCoordGL caret_coord; + LLRect preedit_bounds; + if (mPreeditor->getPreeditLocation(-1, &caret_coord, &preedit_bounds, NULL)) + { + mLanguageTextInputPointGL = caret_coord; + mLanguageTextInputAreaGL = preedit_bounds; + + CANDIDATEFORM candidate_form; + fillCandidateForm(caret_coord, preedit_bounds, &candidate_form); + + HIMC himc = LLWinImm::getContext(mWindowHandle); + // Win32 document says there may be up to 4 candidate windows. + // This magic number 4 appears only in the document, and + // there are no constant/macro for the value... + for (int i = 3; i >= 0; --i) + { + candidate_form.dwIndex = i; + LLWinImm::setCandidateWindow(himc, &candidate_form); + } + LLWinImm::releaseContext(mWindowHandle, himc); + } +} + +void LLWindowWin32::interruptLanguageTextInput() +{ + if (mPreeditor) + { + if (LLWinImm::isAvailable()) + { + HIMC himc = LLWinImm::getContext(mWindowHandle); + LLWinImm::notifyIME(himc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); + LLWinImm::releaseContext(mWindowHandle, himc); + } + mPreeditor->resetPreedit(); + } +} + +void LLWindowWin32::handleStartCompositionMessage() +{ + // Let IME know the font to use in feedback UI. + LOGFONT logfont; + fillCompositionLogfont(&logfont); + HIMC himc = LLWinImm::getContext(mWindowHandle); + LLWinImm::setCompositionFont(himc, &logfont); + LLWinImm::releaseContext(mWindowHandle, himc); +} + +// Handle WM_IME_COMPOSITION message. + +void LLWindowWin32::handleCompositionMessage(const U32 indexes) +{ + BOOL needs_update = FALSE; + LLWString result_string; + LLWString preedit_string; + S32 preedit_string_utf16_length = 0; + LLPreeditor::segment_lengths_t preedit_segment_lengths; + LLPreeditor::standouts_t preedit_standouts; + + // Step I: Receive details of preedits from IME. + + HIMC himc = LLWinImm::getContext(mWindowHandle); + + if (indexes & GCS_RESULTSTR) + { + LONG size = LLWinImm::getCompositionString(himc, GCS_RESULTSTR, NULL, 0); + if (size >= 0) + { + const LPWSTR data = new WCHAR[size / sizeof(WCHAR) + 1]; + size = LLWinImm::getCompositionString(himc, GCS_RESULTSTR, data, size); + if (size > 0) + { + result_string = utf16str_to_wstring(llutf16string(data, size / sizeof(WCHAR))); + } + delete[] data; + needs_update = TRUE; + } + } + + if (indexes & GCS_COMPSTR) + { + LONG size = LLWinImm::getCompositionString(himc, GCS_COMPSTR, NULL, 0); + if (size >= 0) + { + const LPWSTR data = new WCHAR[size / sizeof(WCHAR) + 1]; + size = LLWinImm::getCompositionString(himc, GCS_COMPSTR, data, size); + if (size > 0) + { + preedit_string_utf16_length = size / sizeof(WCHAR); + preedit_string = utf16str_to_wstring(llutf16string(data, size / sizeof(WCHAR))); + } + delete[] data; + needs_update = TRUE; + } + } + + if ((indexes & GCS_COMPCLAUSE) && preedit_string.length() > 0) + { + LONG size = LLWinImm::getCompositionString(himc, GCS_COMPCLAUSE, NULL, 0); + if (size > 0) + { + const LPDWORD data = new DWORD[size / sizeof(DWORD)]; + size = LLWinImm::getCompositionString(himc, GCS_COMPCLAUSE, data, size); + if (size >= sizeof(DWORD) * 2 + && data[0] == 0 && data[size / sizeof(DWORD) - 1] == preedit_string_utf16_length) + { + preedit_segment_lengths.resize(size / sizeof(DWORD) - 1); + S32 offset = 0; + for (U32 i = 0; i < preedit_segment_lengths.size(); i++) + { + const S32 length = wstring_wstring_length_from_utf16_length(preedit_string, offset, data[i + 1] - data[i]); + preedit_segment_lengths[i] = length; + offset += length; + } + } + delete[] data; + } + } + + if ((indexes & GCS_COMPATTR) && preedit_segment_lengths.size() > 1) + { + LONG size = LLWinImm::getCompositionString(himc, GCS_COMPATTR, NULL, 0); + if (size > 0) + { + const LPBYTE data = new BYTE[size / sizeof(BYTE)]; + size = LLWinImm::getCompositionString(himc, GCS_COMPATTR, data, size); + if (size == preedit_string_utf16_length) + { + preedit_standouts.assign(preedit_segment_lengths.size(), FALSE); + S32 offset = 0; + for (U32 i = 0; i < preedit_segment_lengths.size(); i++) + { + if (ATTR_TARGET_CONVERTED == data[offset] || ATTR_TARGET_NOTCONVERTED == data[offset]) + { + preedit_standouts[i] = TRUE; + } + offset += wstring_utf16_length(preedit_string, offset, preedit_segment_lengths[i]); + } + } + delete[] data; + } + } + + S32 caret_position = preedit_string.length(); + if (indexes & GCS_CURSORPOS) + { + const S32 caret_position_utf16 = LLWinImm::getCompositionString(himc, GCS_CURSORPOS, NULL, 0); + if (caret_position_utf16 >= 0 && caret_position <= preedit_string_utf16_length) + { + caret_position = wstring_wstring_length_from_utf16_length(preedit_string, 0, caret_position_utf16); + } + } + + if (indexes == 0) + { + // I'm not sure this condition really happens, but + // Windows SDK document says it is an indication + // of "reset everything." + needs_update = TRUE; + } + + LLWinImm::releaseContext(mWindowHandle, himc); + + // Step II: Update the active preeditor. + + if (needs_update) + { + mPreeditor->resetPreedit(); + + if (result_string.length() > 0) + { + for (LLWString::const_iterator i = result_string.begin(); i != result_string.end(); i++) + { + mPreeditor->handleUnicodeCharHere(*i, FALSE); + } + } + + if (preedit_string.length() > 0) + { + if (preedit_segment_lengths.size() == 0) + { + preedit_segment_lengths.assign(1, preedit_string.length()); + } + if (preedit_standouts.size() == 0) + { + preedit_standouts.assign(preedit_segment_lengths.size(), FALSE); + } + mPreeditor->updatePreedit(preedit_string, preedit_segment_lengths, preedit_standouts, caret_position); + } + + // Some IME doesn't query char position after WM_IME_COMPOSITION, + // so we need to update them actively. + updateLanguageTextInputArea(); + } +} + +// Given a text and a focus range, find_context finds and returns a +// surrounding context of the focused subtext. A variable pointed +// to by offset receives the offset in llwchars of the beginning of +// the returned context string in the given wtext. + +static LLWString find_context(const LLWString & wtext, S32 focus, S32 focus_length, S32 *offset) +{ + static const S32 CONTEXT_EXCESS = 30; // This value is by experiences. + + const S32 e = llmin((S32) wtext.length(), focus + focus_length + CONTEXT_EXCESS); + S32 end = focus + focus_length; + while (end < e && '\n' != wtext[end]) + { + end++; + } + + const S32 s = llmax(0, focus - CONTEXT_EXCESS); + S32 start = focus; + while (start > s && '\n' != wtext[start - 1]) + { + --start; + } + + *offset = start; + return wtext.substr(start, end - start); +} + +// Handle WM_IME_REQUEST message. +// If it handled the message, returns TRUE. Otherwise, FALSE. +// When it handled the message, the value to be returned from +// the Window Procedure is set to *result. + +BOOL LLWindowWin32::handleImeRequests(U32 request, U32 param, LRESULT *result) +{ + if ( mPreeditor ) + { + switch (request) + { + case IMR_CANDIDATEWINDOW: // http://msdn2.microsoft.com/en-us/library/ms776080.aspx + { + LLCoordGL caret_coord; + LLRect preedit_bounds; + mPreeditor->getPreeditLocation(-1, &caret_coord, &preedit_bounds, NULL); + + CANDIDATEFORM *const form = (CANDIDATEFORM *)param; + DWORD const dwIndex = form->dwIndex; + fillCandidateForm(caret_coord, preedit_bounds, form); + form->dwIndex = dwIndex; + + *result = 1; + return TRUE; + } + case IMR_QUERYCHARPOSITION: + { + IMECHARPOSITION *const char_position = (IMECHARPOSITION *)param; + + // char_position->dwCharPos counts in number of + // WCHARs, i.e., UTF-16 encoding units, so we can't simply pass the + // number to getPreeditLocation. + + const LLWString & wtext = mPreeditor->getWText(); + S32 preedit, preedit_length; + mPreeditor->getPreeditRange(&preedit, &preedit_length); + LLCoordGL caret_coord; + LLRect preedit_bounds, text_control; + const S32 position = wstring_wstring_length_from_utf16_length(wtext, preedit, char_position->dwCharPos); + + if (!mPreeditor->getPreeditLocation(position, &caret_coord, &preedit_bounds, &text_control)) + { + llwarns << "*** IMR_QUERYCHARPOSITON called but getPreeditLocation failed." << llendl; + return FALSE; + } + fillCharPosition(caret_coord, preedit_bounds, text_control, char_position); + + *result = 1; + return TRUE; + } + case IMR_COMPOSITIONFONT: + { + fillCompositionLogfont((LOGFONT *)param); + + *result = 1; + return TRUE; + } + case IMR_RECONVERTSTRING: + { + mPreeditor->resetPreedit(); + const LLWString & wtext = mPreeditor->getWText(); + S32 select, select_length; + mPreeditor->getSelectionRange(&select, &select_length); + + S32 context_offset; + const LLWString context = find_context(wtext, select, select_length, &context_offset); + + RECONVERTSTRING * const reconvert_string = (RECONVERTSTRING *)param; + const U32 size = fillReconvertString(context, select - context_offset, select_length, reconvert_string); + if (reconvert_string) + { + if (select_length == 0) + { + // Let the IME to decide the reconversion range, and + // adjust the reconvert_string structure accordingly. + HIMC himc = LLWinImm::getContext(mWindowHandle); + const BOOL adjusted = LLWinImm::setCompositionString(himc, + SCS_QUERYRECONVERTSTRING, reconvert_string, size, NULL, 0); + LLWinImm::releaseContext(mWindowHandle, himc); + if (adjusted) + { + const llutf16string & text_utf16 = wstring_to_utf16str(context); + const S32 new_preedit_start = reconvert_string->dwCompStrOffset / sizeof(WCHAR); + const S32 new_preedit_end = new_preedit_start + reconvert_string->dwCompStrLen; + select = utf16str_wstring_length(text_utf16, new_preedit_start); + select_length = utf16str_wstring_length(text_utf16, new_preedit_end) - select; + select += context_offset; + } + } + mPreeditor->markAsPreedit(select, select_length); + } + + *result = size; + return TRUE; + } + case IMR_CONFIRMRECONVERTSTRING: + { + *result = FALSE; + return TRUE; + } + case IMR_DOCUMENTFEED: + { + const LLWString & wtext = mPreeditor->getWText(); + S32 preedit, preedit_length; + mPreeditor->getPreeditRange(&preedit, &preedit_length); + + S32 context_offset; + LLWString context = find_context(wtext, preedit, preedit_length, &context_offset); + preedit -= context_offset; + if (preedit_length) + { + // IMR_DOCUMENTFEED may be called when we have an active preedit. + // We should pass the context string *excluding* the preedit string. + // Otherwise, some IME are confused. + context.erase(preedit, preedit_length); + } + + RECONVERTSTRING *reconvert_string = (RECONVERTSTRING *)param; + *result = fillReconvertString(context, preedit, 0, reconvert_string); + return TRUE; + } + default: + return FALSE; + } + } + + return FALSE; +} + + #endif // LL_WINDOWS diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h index 602e06600f..9ad99b0201 100644 --- a/indra/llwindow/llwindowwin32.h +++ b/indra/llwindow/llwindowwin32.h @@ -109,8 +109,10 @@ public: /*virtual*/ void bringToFront(); /*virtual*/ void focusClient(); - /*virtual*/ void allowLanguageTextInput(BOOL b); + /*virtual*/ void allowLanguageTextInput(LLPreeditor *preeditor, BOOL b); /*virtual*/ void setLanguageTextInput( const LLCoordGL & pos ); + /*virtual*/ void updateLanguageTextInputArea(); + /*virtual*/ void interruptLanguageTextInput(); protected: LLWindowWin32( @@ -139,6 +141,14 @@ protected: BOOL shouldPostQuit() { return mPostQuit; } + void fillCompositionForm(const LLRect& bounds, COMPOSITIONFORM *form); + void fillCandidateForm(const LLCoordGL& caret, const LLRect& bounds, CANDIDATEFORM *form); + void fillCharPosition(const LLCoordGL& caret, const LLRect& bounds, const LLRect& control, IMECHARPOSITION *char_position); + void fillCompositionLogfont(LOGFONT *logfont); + U32 fillReconvertString(const LLWString &text, S32 focus, S32 focus_length, RECONVERTSTRING *reconvert_string); + void handleStartCompositionMessage(); + void handleCompositionMessage(U32 indexes); + BOOL handleImeRequests(U32 request, U32 param, LRESULT *result); protected: // @@ -189,6 +199,10 @@ protected: static DWORD sWinIMEConversionMode; static DWORD sWinIMESentenceMode; static LLCoordWindow sWinIMEWindowPosition; + LLCoordGL mLanguageTextInputPointGL; + LLRect mLanguageTextInputAreaGL; + + LLPreeditor *mPreeditor; friend class LLWindowManager; }; diff --git a/indra/mac_updater/mac_updater.cpp b/indra/mac_updater/mac_updater.cpp index d5e806f6cb..a984b597e4 100644 --- a/indra/mac_updater/mac_updater.cpp +++ b/indra/mac_updater/mac_updater.cpp @@ -496,14 +496,13 @@ bool isDirWritable(FSRef &dir) // This is kinda lame, but will pretty much always give the right answer. OSStatus err = noErr; - char temp[PATH_MAX]; /* Flawfinder: ignore */ + char temp[PATH_MAX] = ""; /* Flawfinder: ignore */ err = FSRefMakePath(&dir, (UInt8*)temp, sizeof(temp)); if(err == noErr) { - temp[0] = '\0'; - strncat(temp, "/.test_XXXXXX", sizeof(temp) - 1); + strncat(temp, "/.test_XXXXXX", (sizeof(temp) - strlen(temp)) - 1); if(mkdtemp(temp) != NULL) { @@ -557,8 +556,8 @@ static std::string HFSUniStr255_to_utf8str(const HFSUniStr255* src) int restoreObject(const char* aside, const char* target, const char* path, const char* object) { - char source[PATH_MAX]; /* Flawfinder: ignore */ - char dest[PATH_MAX]; /* Flawfinder: ignore */ + char source[PATH_MAX] = ""; /* Flawfinder: ignore */ + char dest[PATH_MAX] = ""; /* Flawfinder: ignore */ snprintf(source, sizeof(source), "%s/%s/%s", aside, path, object); snprintf(dest, sizeof(dest), "%s/%s", target, path); FSRef sourceRef; @@ -592,7 +591,7 @@ int restoreObject(const char* aside, const char* target, const char* path, const // Replace any mention of "Second Life" with the product name. void filterFile(const char* filename) { - char temp[PATH_MAX]; /* Flawfinder: ignore */ + char temp[PATH_MAX] = ""; /* Flawfinder: ignore */ // First copy the target's version, so we can run it through sed. snprintf(temp, sizeof(temp), "cp '%s' '%s.tmp'", filename, filename); system(temp); /* Flawfinder: ignore */ @@ -724,13 +723,13 @@ void *updatethreadproc(void*) { char tempDir[PATH_MAX] = ""; /* Flawfinder: ignore */ FSRef tempDirRef; - char temp[PATH_MAX]; /* Flawfinder: ignore */ + char temp[PATH_MAX] = ""; /* Flawfinder: ignore */ // *NOTE: This buffer length is used in a scanf() below. char deviceNode[1024] = ""; /* Flawfinder: ignore */ FILE *downloadFile = NULL; OSStatus err; ProcessSerialNumber psn; - char target[PATH_MAX]; /* Flawfinder: ignore */ + char target[PATH_MAX] = ""; /* Flawfinder: ignore */ FSRef targetRef; FSRef targetParentRef; FSVolumeRefNum targetVol; @@ -907,14 +906,14 @@ void *updatethreadproc(void*) if(err != noErr) throw 0; - temp[0] = '\0'; - strncat(temp, "/SecondLifeUpdate_XXXXXX", sizeof(temp) - 1); + strncat(temp, "/SecondLifeUpdate_XXXXXX", (sizeof(temp) - strlen(temp)) - 1); if(mkdtemp(temp) == NULL) { throw 0; } - strcpy(tempDir, temp); /* Flawfinder: ignore */ + strncpy(tempDir, temp, sizeof(tempDir)); + temp[sizeof(tempDir) - 1] = '\0'; llinfos << "tempDir is " << tempDir << llendl; diff --git a/indra/newview/gpu_table.txt b/indra/newview/gpu_table.txt index 066990bafa..91d59a2c6b 100644 --- a/indra/newview/gpu_table.txt +++ b/indra/newview/gpu_table.txt @@ -49,6 +49,9 @@ ATI Radeon X1600 .*ATI.*Radeon X16.* 3 ATI Radeon X1700 .*ATI.*Radeon X17.* 3 ATI Radeon X1800 .*ATI.*Radeon X18.* 3 ATI Radeon X1900 .*ATI.*Radeon X19.* 3 +ATI Radeon X2400 .*ATI.*Radeon X24.* 3 +ATI Radeon X2600 .*ATI.*Radeon X26.* 3 +ATI Radeon X2900 .*ATI.*Radeon X29.* 3 ATI Radeon X300 .*ATI.*Radeon X3.* 2 ATI Radeon X400 .*ATI.*Radeon X4.* 2 ATI Radeon X500 .*ATI.*Radeon X5.* 2 diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 63d1986dec..ad934abfa7 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1530,6 +1530,9 @@ bool LLAppViewer::cleanup() delete gGlobalEconomy; gGlobalEconomy = NULL; + delete gActiveChannelSpeakerMgr; + gActiveChannelSpeakerMgr = NULL; + delete gLocalSpeakerMgr; gLocalSpeakerMgr = NULL; @@ -3015,7 +3018,7 @@ const std::vector<std::string>& LLAppViewer::getLoginURIs() const if (gLoginURIs.empty()) { // not specified on the command line, use value from table - gLoginURIs = LLSRV::rewriteURI(gGridInfo[gGridChoice].mLoginURI); + gLoginURIs.push_back(gGridInfo[gGridChoice].mLoginURI); } return gLoginURIs; } diff --git a/indra/newview/llfeaturemanager.cpp b/indra/newview/llfeaturemanager.cpp index 1bcd1e1ab4..d957a3783a 100644 --- a/indra/newview/llfeaturemanager.cpp +++ b/indra/newview/llfeaturemanager.cpp @@ -372,10 +372,14 @@ void LLFeatureManager::loadGPUClass() char* ex = strtok(expr, ".*"); char* rnd = (char*) renderer.c_str(); - + while (ex != NULL && rnd != NULL) { rnd = strstr(rnd, ex); + if (rnd != NULL) + { + rnd += strlen(ex); + } ex = strtok(NULL, ".*"); } diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp index 543dd94f3b..3b96a4ce5e 100644 --- a/indra/newview/llfloaterland.cpp +++ b/indra/newview/llfloaterland.cpp @@ -1045,7 +1045,8 @@ BOOL LLPanelLandObjects::postBuild() mSelectedObjects = LLUICtrlFactory::getTextBoxByName(this, "selected_objects_text"); mCleanOtherObjectsTime = LLUICtrlFactory::getLineEditorByName(this, "clean other time"); - mCleanOtherObjectsTime->setFocusLostCallback(onLostFocus); + mCleanOtherObjectsTime->setFocusLostCallback(onLostFocus); + mCleanOtherObjectsTime->setCommitCallback(onCommitClean); childSetPrevalidate("clean other time", LLLineEditor::prevalidateNonNegativeS32); childSetUserData("clean other time", this); @@ -1818,6 +1819,12 @@ void LLPanelLandObjects::onClickReturnOtherObjects(void* userdata) // static void LLPanelLandObjects::onLostFocus(LLUICtrl *caller, void* user_data) { + onCommitClean(caller, user_data); +} + +// static +void LLPanelLandObjects::onCommitClean(LLUICtrl *caller, void* user_data) +{ LLPanelLandObjects *lop = (LLPanelLandObjects *)user_data; LLParcel* parcel = lop->mParcel->getParcel(); if (parcel) diff --git a/indra/newview/llfloaterland.h b/indra/newview/llfloaterland.h index 9be813f8fd..fa941caf78 100644 --- a/indra/newview/llfloaterland.h +++ b/indra/newview/llfloaterland.h @@ -258,7 +258,7 @@ public: static void onCommitList(LLUICtrl* ctrl, void* data); static void onLostFocus(LLUICtrl* caller, void* user_data); - + static void onCommitClean(LLUICtrl* caller, void* user_data); static void processParcelObjectOwnersReply(LLMessageSystem *msg, void **); virtual BOOL postBuild(); diff --git a/indra/newview/llfloatersellland.cpp b/indra/newview/llfloatersellland.cpp index 9bf1f785b0..ffa1e13bf2 100644 --- a/indra/newview/llfloatersellland.cpp +++ b/indra/newview/llfloatersellland.cpp @@ -492,7 +492,14 @@ void LLFloaterSellLandUI::doSellLand(void *userdata) args["[SALE_PRICE]"] = llformat("%d",sale_price); args["[NAME]"] = authorizedBuyerName; - gViewerWindow->alertXml("ConfirmLandSaleChange", args, onConfirmSale, self); + if (sell_to_anyone) + { + gViewerWindow->alertXml("ConfirmLandSaleToAnyoneChange", args, onConfirmSale, self); + } + else + { + gViewerWindow->alertXml("ConfirmLandSaleChange", args, onConfirmSale, self); + } } else { diff --git a/indra/newview/llfloaterworldmap.cpp b/indra/newview/llfloaterworldmap.cpp index 353020d9c4..4c03a15619 100644 --- a/indra/newview/llfloaterworldmap.cpp +++ b/indra/newview/llfloaterworldmap.cpp @@ -11,7 +11,7 @@ * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of + * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlife.com/developers/opensource/gplv2 * @@ -126,18 +126,18 @@ class LLMapInventoryObserver : public LLInventoryObserver { public: LLMapInventoryObserver() {} - virtual ~LLMapInventoryObserver() {} - virtual void changed(U32 mask); + virtual ~LLMapInventoryObserver() {} + virtual void changed(U32 mask); }; void LLMapInventoryObserver::changed(U32 mask) { - // if there's a change we're interested in. - if((mask & (LLInventoryObserver::CALLING_CARD | LLInventoryObserver::ADD | - LLInventoryObserver::REMOVE)) != 0) - { - gFloaterWorldMap->inventoryChanged(); - } + // if there's a change we're interested in. + if((mask & (LLInventoryObserver::CALLING_CARD | LLInventoryObserver::ADD | + LLInventoryObserver::REMOVE)) != 0) + { + gFloaterWorldMap->inventoryChanged(); + } } class LLMapFriendObserver : public LLFriendObserver @@ -177,8 +177,8 @@ LLFloaterWorldMap::LLFloaterWorldMap() FALSE, // drag on left TRUE, // minimize TRUE), // close - mInventory(NULL), - mInventoryObserver(NULL), + mInventory(NULL), + mInventoryObserver(NULL), mFriendObserver(NULL), mCompletingRegionName(""), mWaitingForTracker(FALSE), @@ -229,12 +229,18 @@ BOOL LLFloaterWorldMap::postBuild() { avatar_combo->selectFirstItem(); avatar_combo->setPrearrangeCallback( onAvatarComboPrearrange ); + avatar_combo->setTextEntryCallback( onComboTextEntry ); } childSetAction("DoSearch", onLocationCommit, this); childSetFocusChangedCallback("location", updateSearchEnabled); - childSetKeystrokeCallback("location", (void (*)(LLLineEditor*,void*))updateSearchEnabled, NULL); + + LLLineEditor *location_editor = LLUICtrlFactory::getLineEditorByName(this, "location"); + if (location_editor) + { + location_editor->setKeystrokeCallback( onSearchTextEntry ); + } childSetCommitCallback("search_results", onCommitSearchResult, this); childSetDoubleClickCallback("search_results", onClickTeleportBtn); @@ -249,6 +255,7 @@ BOOL LLFloaterWorldMap::postBuild() { landmark_combo->selectFirstItem(); landmark_combo->setPrearrangeCallback( onLandmarkComboPrearrange ); + landmark_combo->setTextEntryCallback( onComboTextEntry ); } childSetAction("Go Home", onGoHome, this); @@ -284,9 +291,9 @@ LLFloaterWorldMap::~LLFloaterWorldMap() // All cleaned up by LLView destructor mTabs = NULL; - // Inventory deletes all observers on shutdown - mInventory = NULL; - mInventoryObserver = NULL; + // Inventory deletes all observers on shutdown + mInventory = NULL; + mInventoryObserver = NULL; // avatar tracker will delete this for us. mFriendObserver = NULL; @@ -509,7 +516,7 @@ void LLFloaterWorldMap::draw() } childSetEnabled("Teleport", (BOOL)tracking_status); -// childSetEnabled("Clear", (BOOL)tracking_status); +// childSetEnabled("Clear", (BOOL)tracking_status); childSetEnabled("Show Destination", (BOOL)tracking_status || gWorldMap->mIsTrackingUnknownLocation); childSetEnabled("copy_slurl", (mSLURL.size() > 0) ); @@ -595,7 +602,7 @@ void LLFloaterWorldMap::trackLandmark( const LLUUID& landmark_item_id ) if (combo) name = combo->getSimple(); mTrackedStatus = LLTracker::TRACKING_LANDMARK; LLTracker::trackLandmark(mLandmarkAssetIDList.get( idx ), // assetID - mLandmarkItemIDList.get( idx ), // itemID + mLandmarkItemIDList.get( idx ), // itemID name); // name if( asset_id != sHomeID ) @@ -605,7 +612,7 @@ void LLFloaterWorldMap::trackLandmark( const LLUUID& landmark_item_id ) } // We have to download both region info and landmark data, so set busy. JC -// getWindow()->incBusyCount(); +// getWindow()->incBusyCount(); } else { @@ -1000,7 +1007,7 @@ void LLFloaterWorldMap::clearAvatarSelection(BOOL clear_ui) void LLFloaterWorldMap::adjustZoomSliderBounds() { // World size in regions - S32 world_width_regions = gWorldMap->getWorldWidth() / REGION_WIDTH_UNITS; + S32 world_width_regions = gWorldMap->getWorldWidth() / REGION_WIDTH_UNITS; S32 world_height_regions = gWorldMap->getWorldHeight() / REGION_WIDTH_UNITS; // Pad the world size a little bit, so we have a nice border on @@ -1044,7 +1051,7 @@ void LLFloaterWorldMap::adjustZoomSliderBounds() // static void LLFloaterWorldMap::onPanBtn( void* userdata ) { - if( !gFloaterWorldMap ) return; + if( !gFloaterWorldMap ) return; EPanDirection direction = (EPanDirection)(intptr_t)userdata; @@ -1055,7 +1062,7 @@ void LLFloaterWorldMap::onPanBtn( void* userdata ) case PAN_UP: pan_y = -1; break; case PAN_DOWN: pan_y = 1; break; case PAN_LEFT: pan_x = 1; break; - case PAN_RIGHT: pan_x = -1; break; + case PAN_RIGHT: pan_x = -1; break; default: llassert(0); return; } @@ -1095,6 +1102,21 @@ void LLFloaterWorldMap::onLandmarkComboPrearrange( LLUICtrl* ctrl, void* userdat } +void LLFloaterWorldMap::onComboTextEntry( LLLineEditor* ctrl, void* userdata ) +{ + // Reset the tracking whenever we start typing into any of the search fields, + // so that hitting <enter> does an auto-complete versus teleporting us to the + // previously selected landmark/friend. + LLTracker::clearFocus(); +} + +// static +void LLFloaterWorldMap::onSearchTextEntry( LLLineEditor* ctrl, void* userdata ) +{ + onComboTextEntry(ctrl, userdata); + updateSearchEnabled(ctrl, userdata); +} + // static void LLFloaterWorldMap::onLandmarkComboCommit( LLUICtrl* ctrl, void* userdata ) { @@ -1260,7 +1282,7 @@ void LLFloaterWorldMap::onClearBtn(void* data) self->mTrackedStatus = LLTracker::TRACKING_NOTHING; LLTracker::stopTracking((void *)(intptr_t)TRUE); gWorldMap->mIsTrackingUnknownLocation = FALSE; - self->mSLURL = ""; // Clear the SLURL since it's invalid + self->mSLURL = ""; // Clear the SLURL since it's invalid self->mSetToUserPosition = TRUE; // Revert back to the current user position } @@ -1329,7 +1351,7 @@ void LLFloaterWorldMap::centerOnTarget(BOOL animate) else { // We've got the position finally, so we're no longer busy. JC -// getWindow()->decBusyCount(); +// getWindow()->decBusyCount(); pos_global = LLTracker::getTrackedPositionGlobal() - gAgent.getCameraPositionGlobal(); } } diff --git a/indra/newview/llfloaterworldmap.h b/indra/newview/llfloaterworldmap.h index b98bbbbe44..c069b40929 100644 --- a/indra/newview/llfloaterworldmap.h +++ b/indra/newview/llfloaterworldmap.h @@ -10,7 +10,7 @@ * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of + * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlife.com/developers/opensource/gplv2 * @@ -90,16 +90,16 @@ public: void trackLocation(const LLVector3d& pos); void trackEvent(const LLItemInfo &event_info); void trackGenericItem(const LLItemInfo &item); - void trackURL(const LLString& region_name, S32 x_coord, S32 y_coord, S32 z_coord); + void trackURL(const LLString& region_name, S32 x_coord, S32 y_coord, S32 z_coord); static const LLUUID& getHomeID() { return sHomeID; } // A z_attenuation of 0.0f collapses the distance into the X-Y plane - F32 getDistanceToDestination(const LLVector3d& pos_global, F32 z_attenuation = 0.5f) const; + F32 getDistanceToDestination(const LLVector3d& pos_global, F32 z_attenuation = 0.5f) const; - void clearLocationSelection(BOOL clear_ui = FALSE); - void clearAvatarSelection(BOOL clear_ui = FALSE); - void clearLandmarkSelection(BOOL clear_ui = FALSE); + void clearLocationSelection(BOOL clear_ui = FALSE); + void clearAvatarSelection(BOOL clear_ui = FALSE); + void clearLandmarkSelection(BOOL clear_ui = FALSE); // Adjust the maximally zoomed out limit of the zoom slider so you can // see the whole world, plus a little. @@ -118,12 +118,15 @@ protected: static void onLandmarkComboPrearrange( LLUICtrl* ctrl, void* data ); static void onLandmarkComboCommit( LLUICtrl* ctrl, void* data ); - + static void onAvatarComboPrearrange( LLUICtrl* ctrl, void* data ); static void onAvatarComboCommit( LLUICtrl* ctrl, void* data ); static void onCommitBackground(void* data, bool from_click); + static void onComboTextEntry( LLLineEditor* ctrl, void* data ); + static void onSearchTextEntry( LLLineEditor* ctrl, void* data ); + static void onClearBtn(void*); static void onFlyBtn(void*); static void onClickTeleportBtn(void*); @@ -143,7 +146,7 @@ protected: static void onGoToLandmarkDialog(S32 option,void* userdata); void flyToLandmark(); void teleportToLandmark(); - void setLandmarkVisited(); + void setLandmarkVisited(); void buildAvatarIDList(); void flyToAvatar(); @@ -154,7 +157,7 @@ protected: static void onCommitLocation( LLUICtrl* ctrl, void* userdata ); static void onCommitSearchResult( LLUICtrl* ctrl, void* userdata ); - void cacheLandmarkPosition(); + void cacheLandmarkPosition(); protected: LLTabContainerCommon* mTabs; @@ -165,7 +168,7 @@ protected: LLDynamicArray<LLUUID> mLandmarkAssetIDList; LLDynamicArray<LLUUID> mLandmarkItemIDList; - BOOL mHasLandmarkPosition; + BOOL mHasLandmarkPosition; static const LLUUID sHomeID; diff --git a/indra/newview/llfolderview.cpp b/indra/newview/llfolderview.cpp index eb9addcf5c..2577474e24 100644 --- a/indra/newview/llfolderview.cpp +++ b/indra/newview/llfolderview.cpp @@ -3681,7 +3681,7 @@ BOOL LLFolderView::handleKeyHere( KEY key, MASK mask, BOOL called_from_parent ) // SL-51858: Key presses are not being passed to the Popup menu. // A proper fix is non-trivial so instead just close the menu. LLMenuGL* menu = (LLMenuGL*)LLView::getViewByHandle(mPopupMenuHandle); - if (menu->isOpen()) + if (menu && menu->isOpen()) { LLMenuGL::sMenuContainer->hideMenus(); } @@ -3921,6 +3921,14 @@ BOOL LLFolderView::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_pare BOOL handled = FALSE; if (gFocusMgr.childHasKeyboardFocus(getRoot())) { + // SL-51858: Key presses are not being passed to the Popup menu. + // A proper fix is non-trivial so instead just close the menu. + LLMenuGL* menu = (LLMenuGL*)LLView::getViewByHandle(mPopupMenuHandle); + if (menu && menu->isOpen()) + { + LLMenuGL::sMenuContainer->hideMenus(); + } + //do text search if (mSearchTimer.getElapsedTimeF32() > gSavedSettings.getF32("TypeAheadTimeout")) { diff --git a/indra/newview/llgroupmgr.cpp b/indra/newview/llgroupmgr.cpp index c213d26848..82a42a18b5 100644 --- a/indra/newview/llgroupmgr.cpp +++ b/indra/newview/llgroupmgr.cpp @@ -867,6 +867,12 @@ void LLGroupMgr::processGroupMembersReply(LLMessageSystem* msg, void** data) llinfos << "Received null group member data." << llendl; } } + + //if group members are loaded while titles are missing, load the titles. + if(group_datap->mTitles.size() < 1) + { + gGroupMgr->sendGroupTitlesRequest(group_id); + } } if (group_datap->mMembers.size() == (U32)group_datap->mMemberCount) diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 12617efb67..9c9b1ad257 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -2719,6 +2719,7 @@ void LLCallingCardBridge::buildContextMenu(LLMenuGL& menu, U32 flags) && (LLUUID::null != item->getCreatorUUID()) && (item->getCreatorUUID() != gAgent.getID())); BOOL user_online = (LLAvatarTracker::instance().isBuddyOnline(item->getCreatorUUID())); + items.push_back("Send Instant Message Separator"); items.push_back("Send Instant Message"); items.push_back("Offer Teleport..."); items.push_back("Conference Chat"); @@ -4294,21 +4295,24 @@ void LLWearableBridge::buildContextMenu(LLMenuGL& menu, U32 flags) getClipboardEntries(true, items, disabled_items, flags); items.push_back("Wearable Separator"); + items.push_back("Wearable Wear"); items.push_back("Wearable Edit"); + + if ((flags & FIRST_SELECTED_ITEM) == 0) { disabled_items.push_back("Wearable Edit"); } - /*menu.appendSeparator(); - menu.append(new LLMenuItemCallGL("Wear", - LLWearableBridge::onWearOnAvatar, - LLWearableBridge::canWearOnAvatar, - (void*)this)); - menu.append(new LLMenuItemCallGL("Edit", - LLWearableBridge::onEditOnAvatar, - LLWearableBridge::canEditOnAvatar, - (void*)this));*/ + //menu.appendSeparator(); + //menu.append(new LLMenuItemCallGL("Wear", + // LLWearableBridge::onWearOnAvatar, + // LLWearableBridge::canWearOnAvatar, + // (void*)this)); + //menu.append(new LLMenuItemCallGL("Edit", + // LLWearableBridge::onEditOnAvatar, + // LLWearableBridge::canEditOnAvatar, + // (void*)this)); if( item && (item->getType() == LLAssetType::AT_CLOTHING) ) { diff --git a/indra/newview/llmaniptranslate.cpp b/indra/newview/llmaniptranslate.cpp index 36e3f9a5e9..2f5f154b77 100644 --- a/indra/newview/llmaniptranslate.cpp +++ b/indra/newview/llmaniptranslate.cpp @@ -2132,15 +2132,12 @@ void LLManipTranslate::renderTranslationHandles() LLVector3 arrow_axis; getManipAxis(first_object, which_arrow[face], arrow_axis); - if (fabs(angle_between(camera_axis, arrow_axis) - F_PI_BY_TWO) < F_PI_BY_TWO - HANDLE_HIDE_ANGLE) - { - renderArrow(which_arrow[face], - mManipPart, - (face >= 3) ? -mConeSize : mConeSize, - (face >= 3) ? -mArrowLengthMeters : mArrowLengthMeters, - mConeSize, - FALSE); - } + renderArrow(which_arrow[face], + mManipPart, + (face >= 3) ? -mConeSize : mConeSize, + (face >= 3) ? -mArrowLengthMeters : mArrowLengthMeters, + mConeSize, + FALSE); } } } diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp index f36e282ea0..4d02af8fae 100644 --- a/indra/newview/llmutelist.cpp +++ b/indra/newview/llmutelist.cpp @@ -62,6 +62,8 @@ #include "llviewergenericmessage.h" // for gGenericDispatcher #include "llviewerwindow.h" #include "llworld.h" //for particle system banning +#include "llviewerobject.h" +#include "llviewerobjectlist.h" LLMuteList* gMuteListp = NULL; @@ -513,8 +515,21 @@ BOOL LLMuteList::saveToFile(const LLString& filename) BOOL LLMuteList::isMuted(const LLUUID& id, const LLString& name, U32 flags) const { + LLUUID id_to_check = id; + + // for objects, check for muting on their parent prim + LLViewerObject *objectp = gObjectList.findObject(id); + if ((objectp) && (!objectp->isAvatar())) + { + LLViewerObject *parentp = (LLViewerObject *)objectp->getParent(); + if (parentp) + { + id_to_check = parentp->getID(); + } + } + // don't need name or type for lookup - LLMute mute(id); + LLMute mute(id_to_check); mute_set_t::const_iterator mute_it = mMutes.find(mute); if (mute_it != mMutes.end()) { diff --git a/indra/newview/llpanelavatar.cpp b/indra/newview/llpanelavatar.cpp index 5b43497f03..c090fd9749 100644 --- a/indra/newview/llpanelavatar.cpp +++ b/indra/newview/llpanelavatar.cpp @@ -365,6 +365,7 @@ void LLPanelAvatarSecondLife::onDoubleClickGroup(void* data) if(group_list) { LLScrollListItem* item = group_list->getFirstSelected(); + if(item && item->getUUID().notNull()) { llinfos << "Show group info " << item->getUUID() << llendl; @@ -1565,6 +1566,8 @@ void LLPanelAvatar::resetGroupList() group_string += group_data.mName; LLSD row; + + row["id"] = id ; row["columns"][0]["value"] = group_string; row["columns"][0]["font"] = "SANSSERIF_SMALL"; row["columns"][0]["width"] = 0; @@ -2010,8 +2013,7 @@ void LLPanelAvatar::processAvatarGroupsReply(LLMessageSystem *msg, void**) LLString group_string; if (group_id.notNull()) { - group_string.assign("Member of "); - group_string.append(group_name); + group_string.assign(group_name); } else { diff --git a/indra/newview/llpanelclassified.cpp b/indra/newview/llpanelclassified.cpp index 04fb54b0a7..7163a71bc3 100644 --- a/indra/newview/llpanelclassified.cpp +++ b/indra/newview/llpanelclassified.cpp @@ -808,17 +808,21 @@ void LLPanelClassified::confirmPublish(S32 option) } // Tell all the widgets to reset their dirty state since the ad was just saved - mSnapshotCtrl->resetDirty(); - mNameEditor->resetDirty(); - mDescEditor->resetDirty(); - mLocationEditor->resetDirty(); + if (mSnapshotCtrl) + mSnapshotCtrl->resetDirty(); + if (mNameEditor) + mNameEditor->resetDirty(); + if (mDescEditor) + mDescEditor->resetDirty(); + if (mLocationEditor) + mLocationEditor->resetDirty(); mLocationChanged = false; - mCategoryCombo->resetDirty(); - mMatureCheck->resetDirty(); + if (mCategoryCombo) + mCategoryCombo->resetDirty(); + if (mMatureCheck) + mMatureCheck->resetDirty(); if (mAutoRenewCheck) - { mAutoRenewCheck->resetDirty(); - } } // static diff --git a/indra/newview/llpreview.cpp b/indra/newview/llpreview.cpp index e4f7d1e0b2..660de69fb0 100644 --- a/indra/newview/llpreview.cpp +++ b/indra/newview/llpreview.cpp @@ -66,12 +66,14 @@ LLPreview::LLPreview(const std::string& name) : mUserResized(FALSE), mCloseAfterSave(FALSE), mAssetStatus(PREVIEW_ASSET_UNLOADED), - mItem(NULL) + mItem(NULL), + mDirty(TRUE) { // don't add to instance list, since ItemID is null mAuxItem = new LLInventoryItem; // (LLPointer is auto-deleted) // don't necessarily steal focus on creation -- sometimes these guys pop up without user action mAutoFocus = FALSE; + gInventory.addObserver(this); } LLPreview::LLPreview(const std::string& name, const LLRect& rect, const std::string& title, const LLUUID& item_uuid, const LLUUID& object_uuid, BOOL allow_resize, S32 min_width, S32 min_height, LLPointer<LLViewerInventoryItem> inv_item ) @@ -84,7 +86,8 @@ LLPreview::LLPreview(const std::string& name, const LLRect& rect, const std::str mUserResized(FALSE), mCloseAfterSave(FALSE), mAssetStatus(PREVIEW_ASSET_UNLOADED), - mItem(inv_item) + mItem(inv_item), + mDirty(TRUE) { mAuxItem = new LLInventoryItem; // don't necessarily steal focus on creation -- sometimes these guys pop up without user action @@ -94,7 +97,7 @@ LLPreview::LLPreview(const std::string& name, const LLRect& rect, const std::str { sInstances[mItemUUID] = this; } - + gInventory.addObserver(this); } LLPreview::~LLPreview() @@ -118,6 +121,7 @@ LLPreview::~LLPreview() } } } + gInventory.removeObserver(this); } void LLPreview::setItemID(const LLUUID& item_id) @@ -215,6 +219,7 @@ void LLPreview::onCommit() { new_item->updateServer(FALSE); gInventory.updateItem(new_item); + gInventory.notifyObservers(); // If the item is an attachment that is currently being worn, // update the object itself. @@ -238,6 +243,34 @@ void LLPreview::onCommit() } } +void LLPreview::changed(U32 mask) +{ + mDirty = TRUE; +} + +void LLPreview::draw() +{ + LLFloater::draw(); + if (mDirty) + { + mDirty = FALSE; + const LLViewerInventoryItem *item = getItem(); + if (item) + { + refreshFromItem(item); + } + } +} + +void LLPreview::refreshFromItem(const LLInventoryItem* item) +{ + setTitle(llformat("%s: %s",getTitleName(),item->getName().c_str())); + childSetText("desc",item->getDescription()); + + BOOL can_agent_manipulate = item->getPermissions().allowModifyBy(gAgent.getID()); + childSetEnabled("desc",can_agent_manipulate); +} + // static void LLPreview::onText(LLUICtrl*, void* userdata) { diff --git a/indra/newview/llpreview.h b/indra/newview/llpreview.h index ae986f5aae..97cd2d5b07 100644 --- a/indra/newview/llpreview.h +++ b/indra/newview/llpreview.h @@ -38,6 +38,7 @@ #include "lluuid.h" #include "llviewerinventory.h" #include "lltabcontainer.h" +#include "llinventorymodel.h" #include <map> class LLLineEditor; @@ -61,7 +62,7 @@ protected: static std::map<LLUUID, LLViewHandle> sAutoOpenPreviewHandles; }; -class LLPreview : public LLFloater +class LLPreview : public LLFloater, LLInventoryObserver { public: typedef enum e_asset_status @@ -116,6 +117,10 @@ public: void setNotecardInfo(const LLUUID& notecard_inv_id, const LLUUID& object_id) { mNotecardInventoryID = notecard_inv_id; mObjectID = object_id; } + // llview + virtual void draw(); + void refreshFromItem(const LLInventoryItem* item); + protected: virtual void onCommit(); @@ -124,7 +129,11 @@ protected: static void onText(LLUICtrl*, void* userdata); static void onRadio(LLUICtrl*, void* userdata); - + // for LLInventoryObserver + virtual void changed(U32 mask); + BOOL mDirty; + virtual const char *getTitleName() const { return "Preview"; } + protected: LLUUID mItemUUID; LLUUID mSourceID; diff --git a/indra/newview/llpreviewanim.h b/indra/newview/llpreviewanim.h index bb6ec759e4..37cbd49725 100644 --- a/indra/newview/llpreviewanim.h +++ b/indra/newview/llpreviewanim.h @@ -50,7 +50,8 @@ public: protected: virtual void onClose(bool app_quitting); - + virtual const char *getTitleName() const { return "Animation"; } + LLAnimPauseRequest mPauseRequest; LLUUID mItemID; LLString mTitle; diff --git a/indra/newview/llpreviewgesture.h b/indra/newview/llpreviewgesture.h index 5c84ee0188..4dea34ba1c 100644 --- a/indra/newview/llpreviewgesture.h +++ b/indra/newview/llpreviewgesture.h @@ -137,6 +137,8 @@ protected: static void onDonePreview(LLMultiGesture* gesture, void* data); + virtual const char *getTitleName() const { return "Gesture"; } + protected: // LLPreview contains mDescEditor LLLineEditor* mTriggerEditor; diff --git a/indra/newview/llpreviewnotecard.cpp b/indra/newview/llpreviewnotecard.cpp index dc56494d7f..eef8c0d636 100644 --- a/indra/newview/llpreviewnotecard.cpp +++ b/indra/newview/llpreviewnotecard.cpp @@ -148,6 +148,10 @@ LLPreviewNotecard::LLPreviewNotecard(const std::string& name, gAgent.changeCameraToDefault(); } +LLPreviewNotecard::~LLPreviewNotecard() +{ +} + BOOL LLPreviewNotecard::postBuild() { LLViewerTextEditor *ed = (LLViewerTextEditor *)gUICtrlFactory->getTextEditorByName(this, "Notecard Editor"); @@ -213,7 +217,7 @@ BOOL LLPreviewNotecard::handleKeyHere(KEY key, MASK mask, return TRUE; } } - return FALSE; + return LLPreview::handleKeyHere(key, mask, called_from_parent); } // virtual diff --git a/indra/newview/llpreviewnotecard.h b/indra/newview/llpreviewnotecard.h index 9909284d5f..233246ceaa 100644 --- a/indra/newview/llpreviewnotecard.h +++ b/indra/newview/llpreviewnotecard.h @@ -54,7 +54,8 @@ public: const LLUUID& asset_id = LLUUID::null, BOOL show_keep_discard = FALSE, LLPointer<LLViewerInventoryItem> inv_item = NULL); - + virtual ~LLPreviewNotecard(); + // llpreview virtual bool saveItem(LLPointer<LLInventoryItem>* itemptr); @@ -102,6 +103,8 @@ protected: static void handleSaveChangesDialog(S32 option, void* userdata); + virtual const char *getTitleName() const { return "Note"; } + protected: LLViewerTextEditor* mEditor; LLButton* mSaveBtn; diff --git a/indra/newview/llpreviewscript.h b/indra/newview/llpreviewscript.h index 3dfeb2c3ac..a9b36f3978 100644 --- a/indra/newview/llpreviewscript.h +++ b/indra/newview/llpreviewscript.h @@ -128,6 +128,8 @@ protected: static void onErrorList(LLUICtrl*, void* user_data); + virtual const char *getTitleName() const { return "Script"; } + private: LLString mSampleText; std::string mHelpFile; @@ -189,6 +191,8 @@ protected: protected: + + virtual const char *getTitleName() const { return "Script"; } LLScriptEdCore* mScriptEd; // Can safely close only after both text and bytecode are uploaded S32 mPendingUploads; diff --git a/indra/newview/llpreviewsound.h b/indra/newview/llpreviewsound.h index 74df017def..b56035f34e 100644 --- a/indra/newview/llpreviewsound.h +++ b/indra/newview/llpreviewsound.h @@ -44,6 +44,9 @@ public: static void playSound( void* userdata ); static void auditionSound( void* userdata ); +protected: + virtual const char *getTitleName() const { return "Sound"; } + }; #endif // LL_LLPREVIEWSOUND_H diff --git a/indra/newview/llpreviewtexture.cpp b/indra/newview/llpreviewtexture.cpp index 4c8d4efb25..78d066f85f 100644 --- a/indra/newview/llpreviewtexture.cpp +++ b/indra/newview/llpreviewtexture.cpp @@ -286,7 +286,7 @@ void LLPreviewTexture::draw() LLFontGL::DROP_SHADOW); } } - } + } } } diff --git a/indra/newview/llpreviewtexture.h b/indra/newview/llpreviewtexture.h index b7850de92e..8ed5210c46 100644 --- a/indra/newview/llpreviewtexture.h +++ b/indra/newview/llpreviewtexture.h @@ -80,6 +80,8 @@ protected: void init(); void updateAspectRatio(); + virtual const char *getTitleName() const { return "Texture"; } + protected: LLUUID mImageID; LLPointer<LLViewerImage> mImage; diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp index 3c29cfdbfc..e2e076e364 100644 --- a/indra/newview/llselectmgr.cpp +++ b/indra/newview/llselectmgr.cpp @@ -3994,7 +3994,11 @@ void LLSelectMgr::sendListToRegions(const LLString& message_name, switch(send_type) { case SEND_ONLY_ROOTS: - getSelection()->applyToRootNodes(&pusheditable); + if(message_name == "ObjectBuy") + getSelection()->applyToRootNodes(&pushroots); + else + getSelection()->applyToRootNodes(&pusheditable); + break; case SEND_INDIVIDUALS: getSelection()->applyToNodes(&pushall); @@ -6062,23 +6066,19 @@ LLViewerObject* LLObjectSelection::getFirstDeleteableObject() bool apply(LLSelectNode* node) { LLViewerObject* obj = node->getObject(); - // you can delete an object if permissions allow it, you are - // the owner, you are an officer in the group that owns the - // object, or you are not the owner but it is on land you own - // or land owned by your group. (whew!) + // you can delete an object if you are the owner + // or you have permission to modify it. if( (obj->permModify()) || (obj->permYouOwner()) || (!obj->permAnyOwner()) // public - || (obj->isOverAgentOwnedLand()) - || (obj->isOverGroupOwnedLand()) ) { if( !obj->isAttachment() ) { - return TRUE; + return true; } } - return true; + return false; } } func; LLSelectNode* node = getFirstNode(&func); diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index c319ef97af..302291ab52 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -932,9 +932,15 @@ BOOL idle_startup() gSavedSettings.setBOOL("UseDebugMenus", TRUE); requested_options.push_back("god-connect"); } - LLAppViewer::instance()->getLoginURIs(); - sAuthUris = LLAppViewer::instance()->getLoginURIs(); - + const std::vector<std::string>& uris = LLAppViewer::instance()->getLoginURIs(); + std::vector<std::string>::const_iterator iter, end; + for (iter = uris.begin(), end = uris.end(); iter != end; ++iter) + { + std::vector<std::string> rewritten; + rewritten = LLSRV::rewriteURI(*iter); + sAuthUris.insert(sAuthUris.end(), + rewritten.begin(), rewritten.end()); + } sAuthUriNum = 0; auth_method = "login_to_simulator"; auth_desc = "Logging in. "; @@ -2161,7 +2167,7 @@ BOOL idle_startup() else { args["[TYPE]"] = "home"; - args["[HELP]"] = "\nYou may want to set a new home location."; + args["[HELP]"] = "You may want to set a new home location."; } gViewerWindow->alertXml("AvatarMoved", args); } diff --git a/indra/newview/llstatusbar.cpp b/indra/newview/llstatusbar.cpp index 856943da6e..4d49d33184 100644 --- a/indra/newview/llstatusbar.cpp +++ b/indra/newview/llstatusbar.cpp @@ -375,7 +375,7 @@ void LLStatusBar::refresh() x += buttonRect.getWidth(); } - BOOL have_voice = gVoiceClient->getAreaVoiceDisabled() ? FALSE : TRUE; + BOOL have_voice = parcel && parcel->getVoiceEnabled(); childSetVisible("status_voice", have_voice); if (have_voice) { diff --git a/indra/newview/lltooldraganddrop.cpp b/indra/newview/lltooldraganddrop.cpp index e6eca31cd0..413b26309d 100644 --- a/indra/newview/lltooldraganddrop.cpp +++ b/indra/newview/lltooldraganddrop.cpp @@ -1204,6 +1204,7 @@ BOOL LLToolDragAndDrop::handleDropTextureProtections(LLViewerObject* hit_obj, // if the asset is already in the object's inventory // then it can always be added to a side. // This saves some work if the task's inventory is already loaded + // and ensures that the texture item is only added once. return TRUE; } @@ -1241,7 +1242,10 @@ BOOL LLToolDragAndDrop::handleDropTextureProtections(LLViewerObject* hit_obj, return FALSE; } } - hit_obj->updateInventory(new_item, TASK_INVENTORY_ASSET_KEY, true); + // Add the texture item to the target object's inventory. + hit_obj->updateInventory(new_item, TASK_INVENTORY_ITEM_KEY, true); + // TODO: Check to see if adding the item was successful; if not, then + // we should return false here. } else if(!item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID())) @@ -1253,8 +1257,10 @@ BOOL LLToolDragAndDrop::handleDropTextureProtections(LLViewerObject* hit_obj, } // *FIX: may want to make sure agent can paint hit_obj. - // make sure the object has the texture in it's inventory. - hit_obj->updateInventory(new_item, TASK_INVENTORY_ASSET_KEY, true); + // Add the texture item to the target object's inventory. + hit_obj->updateInventory(new_item, TASK_INVENTORY_ITEM_KEY, true); + // TODO: Check to see if adding the item was successful; if not, then + // we should return false here. } return TRUE; } diff --git a/indra/newview/lltracker.cpp b/indra/newview/lltracker.cpp index 4bab92269c..594ecb5591 100644 --- a/indra/newview/lltracker.cpp +++ b/indra/newview/lltracker.cpp @@ -639,6 +639,10 @@ void LLTracker::stopTrackingLocation(BOOL clear_ui) mTrackingLocationType = LOCATION_NOTHING; } +void LLTracker::clearFocus() +{ + instance()->mTrackingStatus = TRACKING_NOTHING; +} void LLTracker::drawMarker(const LLVector3d& pos_global, const LLColor4& color) { diff --git a/indra/newview/lltracker.h b/indra/newview/lltracker.h index c7e09d0d3d..5f7ad98fe5 100644 --- a/indra/newview/lltracker.h +++ b/indra/newview/lltracker.h @@ -82,7 +82,8 @@ public: static ETrackingLocationType getTrackedLocationType() { return instance()->mTrackingLocationType; } static BOOL isTracking(void*) { return (BOOL) instance()->mTrackingStatus; } static void stopTracking(void*); - + static void clearFocus(); + static const LLUUID& getTrackedLandmarkAssetID() { return instance()->mTrackedLandmarkAssetID; } static const LLUUID& getTrackedLandmarkItemID() { return instance()->mTrackedLandmarkItemID; } diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index 43e8589176..b5316d29e0 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -104,14 +104,9 @@ LLViewerInventoryItem::LLViewerInventoryItem(const LLViewerInventoryItem* other) } LLViewerInventoryItem::LLViewerInventoryItem(const LLInventoryItem *other) : - LLInventoryItem(other) + LLInventoryItem(other), + mIsComplete(TRUE) { - LLInventoryItem::copy(other); - if (!mIsComplete) - { - llwarns << "LLViewerInventoryItem copy constructor for incomplete item" - << mUUID << llendl; - } } diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index b5f53e5d88..7aa25266fe 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -6053,12 +6053,39 @@ BOOL object_selected_and_point_valid(void *user_data) (selection->getFirstRootObject()->getNVPair("AssetContainer") == NULL); } + +BOOL object_is_wearable() +{ + if (!object_selected_and_point_valid(NULL)) + { + return FALSE; + } + if (sitting_on_selection()) + { + return FALSE; + } + LLObjectSelectionHandle selection = gSelectMgr->getSelection(); + for (LLObjectSelection::valid_root_iterator iter = gSelectMgr->getSelection()->valid_root_begin(); + iter != gSelectMgr->getSelection()->valid_root_end(); iter++) + { + LLSelectNode* node = *iter; + if (node->mPermissions->getOwner() == gAgent.getID()) + { + return TRUE; + } + } + return FALSE; +} + + // Also for seeing if object can be attached. See above. class LLObjectEnableWear : public view_listener_t { bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) { - return object_selected_and_point_valid(NULL); + bool is_wearable = object_selected_and_point_valid(NULL); + gMenuHolder->findControl(userdata["control"].asString())->setValue(is_wearable); + return TRUE; } }; diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index f109ea417b..fa92bfb217 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -131,6 +131,7 @@ #include "pipeline.h" #include "llappviewer.h" #include "llfloaterworldmap.h" +#include "llkeythrottle.h" #include "llviewerdisplay.h" #include "llkeythrottle.h" @@ -4407,6 +4408,10 @@ void script_question_cb(S32 option, void* user_data) notify_cautioned_script_question(cbdata, orig, allowed); } + if ( option == 2 ) + { + gMuteListp->add(LLMute(cbdata->mItemID, cbdata->mObjectName, LLMute::OBJECT)); + } delete cbdata; } diff --git a/indra/newview/llviewerparcelmgr.cpp b/indra/newview/llviewerparcelmgr.cpp index f0d6fd11c3..b0d1d3daca 100644 --- a/indra/newview/llviewerparcelmgr.cpp +++ b/indra/newview/llviewerparcelmgr.cpp @@ -66,6 +66,7 @@ #include "llworld.h" #include "lloverlaybar.h" #include "roles_constants.h" +#include "llweb.h" const F32 PARCEL_COLLISION_DRAW_SECS = 1.f; @@ -1720,6 +1721,10 @@ void LLViewerParcelMgr::processParcelProperties(LLMessageSystem *msg, void **use std::string mediaUrl = std::string ( parcel->getMediaURL () ); LLString::trim(mediaUrl); + // clean spaces and whatnot + mediaUrl = LLWeb::escapeURL(mediaUrl); + + // something changed LLMediaEngine* me = LLMediaEngine::getInstance(); if ( ( me->getUrl () != mediaUrl ) @@ -1837,6 +1842,10 @@ void prepare_video(const LLParcel *parcel) { mediaUrl = std::string ( parcel->getMediaURL () ); } + + // clean spaces and whatnot + mediaUrl = LLWeb::escapeURL(mediaUrl); + LLMediaEngine::getInstance ()->setUrl ( mediaUrl ); LLMediaEngine::getInstance ()->setImageUUID ( parcel->getMediaID () ); LLMediaEngine::getInstance ()->setAutoScaled ( parcel->getMediaAutoScale () ? TRUE : FALSE ); // (U8 instead of BOOL for future expansion) diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 49883eb8c6..a3611b2272 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -2446,6 +2446,10 @@ BOOL LLViewerWindow::handleKey(KEY key, MASK mask) return TRUE; } + //if quit from menu, turn off the Keyboardmode for the menu. + if(LLMenuGL::getKeyboardMode()) + LLMenuGL::setKeyboardMode(FALSE); + // *TODO: get this to play well with mouselook and hidden // cursor modes, etc, and re-enable. //if (gFocusMgr.getMouseCapture()) diff --git a/indra/win_updater/updater.cpp b/indra/win_updater/updater.cpp index e25383bfef..f849e4e9ad 100644 --- a/indra/win_updater/updater.cpp +++ b/indra/win_updater/updater.cpp @@ -41,6 +41,7 @@ #define BUFSIZE 8192 int gTotalBytesRead = 0; +DWORD gTotalBytes = -1; HWND gWindow = NULL; WCHAR gProgress[256]; char* gUpdateURL; @@ -129,6 +130,9 @@ int WINAPI get_url_into_file(WCHAR *uri, char *path, int *cancelled) return success; } + DWORD sizeof_total_bytes = sizeof(gTotalBytes); + HttpQueryInfo(hdownload, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &gTotalBytes, &sizeof_total_bytes, NULL); + DWORD total_bytes = 0; success = InternetQueryDataAvailable(hdownload, &total_bytes, 0, 0); if (success == FALSE) @@ -187,7 +191,11 @@ int WINAPI get_url_into_file(WCHAR *uri, char *path, int *cancelled) gTotalBytesRead += int(bytes_read); - wsprintf(gProgress, L"Downloaded: %dK", gTotalBytesRead / 1024); + if (gTotalBytes != -1) + wsprintf(gProgress, L"Downloaded: %d%%", 100 * gTotalBytesRead / gTotalBytes); + else + wsprintf(gProgress, L"Downloaded: %dK", gTotalBytesRead / 1024); + } #if _DEBUG |