/** * @file llviewertexteditor.cpp * @brief Text editor widget to let users enter a multi-line document. * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llviewertexteditor.h" #include "llagent.h" #include "llaudioengine.h" #include "llavataractions.h" #include "llfloaterreg.h" #include "llfloatersidepanelcontainer.h" #include "llfloaterworldmap.h" #include "llfocusmgr.h" #include "llinventorybridge.h" #include "llinventorydefines.h" #include "llinventorymodel.h" #include "lllandmark.h" #include "lllandmarkactions.h" #include "lllandmarklist.h" #include "llmemorystream.h" #include "llmenugl.h" #include "llnotecard.h" #include "llnotificationsutil.h" #include "llpanelplaces.h" #include "llpreview.h" #include "llpreviewnotecard.h" #include "llpreviewtexture.h" #include "llscrollbar.h" #include "llscrollcontainer.h" #include "llsidetray.h" #include "lltooldraganddrop.h" #include "lltooltip.h" #include "lltrans.h" #include "lluictrlfactory.h" #include "llviewerassettype.h" #include "llviewercontrol.h" #include "llviewerinventory.h" #include "llviewertexturelist.h" #include "llviewerwindow.h" static LLDefaultChildRegistry::Register r("text_editor"); ///----------------------------------------------------------------------- /// Class LLEmbeddedLandmarkCopied ///----------------------------------------------------------------------- class LLEmbeddedLandmarkCopied: public LLInventoryCallback { public: LLEmbeddedLandmarkCopied(){} void fire(const LLUUID& inv_item) { showInfo(inv_item); } static void showInfo(const LLUUID& landmark_inv_id) { LLSD key; key["type"] = "landmark"; key["id"] = landmark_inv_id; LLFloaterSidePanelContainer::showPanel("places", key); } static void processForeignLandmark(LLLandmark* landmark, const LLUUID& object_id, const LLUUID& notecard_inventory_id, LLPointer item_ptr) { LLVector3d global_pos; landmark->getGlobalPos(global_pos); LLViewerInventoryItem* agent_lanmark = LLLandmarkActions::findLandmarkForGlobalPos(global_pos); if (agent_lanmark) { showInfo(agent_lanmark->getUUID()); } else { if (item_ptr.isNull()) { // check to prevent a crash. See EXT-8459. llwarns << "Passed handle contains a dead inventory item. Most likely notecard has been closed and embedded item was destroyed." << llendl; } else { LLPointer cb = new LLEmbeddedLandmarkCopied(); copy_inventory_from_notecard(object_id, notecard_inventory_id, item_ptr.get(), gInventoryCallbacks.registerCB(cb)); } } } }; ///---------------------------------------------------------------------------- /// Class LLEmbeddedNotecardOpener ///---------------------------------------------------------------------------- class LLEmbeddedNotecardOpener : public LLInventoryCallback { LLViewerTextEditor* mTextEditor; public: LLEmbeddedNotecardOpener() : mTextEditor(NULL) { } void setEditor(LLViewerTextEditor* e) {mTextEditor = e;} // override void fire(const LLUUID& inv_item) { if(!mTextEditor) { // The parent text editor may have vanished by now. // In that case just quit. return; } LLInventoryItem* item = gInventory.getItem(inv_item); if(!item) { llwarns << "Item add reported, but not found in inventory!: " << inv_item << llendl; } else { if(!gSavedSettings.getBOOL("ShowNewInventory")) { LLFloaterReg::showInstance("preview_notecard", LLSD(item->getUUID()), TAKE_FOCUS_YES); } } } }; // // class LLEmbeddedItemSegment // const S32 EMBEDDED_ITEM_LABEL_PADDING = 2; class LLEmbeddedItemSegment : public LLTextSegment { public: LLEmbeddedItemSegment(S32 pos, LLUIImagePtr image, LLPointer inv_item, LLTextEditor& editor) : LLTextSegment(pos, pos + 1), mImage(image), mLabel(utf8str_to_wstring(inv_item->getName())), mItem(inv_item), mEditor(editor), mHasMouseHover(false) { mStyle = new LLStyle(LLStyle::Params().font(LLFontGL::getFontSansSerif())); mToolTip = inv_item->getName() + '\n' + inv_item->getDescription(); } /*virtual*/ bool getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const { if (num_chars == 0) { width = 0; height = 0; } else { width = EMBEDDED_ITEM_LABEL_PADDING + mImage->getWidth() + mStyle->getFont()->getWidth(mLabel.c_str()); height = llmax(mImage->getHeight(), llceil(mStyle->getFont()->getLineHeight())); } return false; } /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const { // always draw at beginning of line if (line_offset == 0) { return 1; } else { S32 width, height; getDimensions(mStart, 1, width, height); if (width > num_pixels) { return 0; } else { return 1; } } } /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect) { LLRect image_rect = draw_rect; image_rect.mRight = image_rect.mLeft + mImage->getWidth(); image_rect.mTop = image_rect.mBottom + mImage->getHeight(); mImage->draw(image_rect); LLColor4 color; if (mEditor.getReadOnly()) { color = LLUIColorTable::instance().getColor("TextEmbeddedItemReadOnlyColor"); } else { color = LLUIColorTable::instance().getColor("TextEmbeddedItemColor"); } F32 right_x; mStyle->getFont()->render(mLabel, 0, image_rect.mRight + EMBEDDED_ITEM_LABEL_PADDING, draw_rect.mTop, color, LLFontGL::LEFT, LLFontGL::TOP, LLFontGL::UNDERLINE, LLFontGL::NO_SHADOW, mLabel.length(), S32_MAX, &right_x); return right_x; } /*virtual*/ bool canEdit() const { return false; } /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask) { LLUI::getWindow()->setCursor(UI_CURSOR_HAND); return TRUE; } virtual BOOL handleToolTip(S32 x, S32 y, MASK mask ) { if (!mToolTip.empty()) { LLToolTipMgr::instance().show(mToolTip); return TRUE; } return FALSE; } /*virtual*/ LLStyleConstSP getStyle() const { return mStyle; } private: LLUIImagePtr mImage; LLWString mLabel; LLStyleSP mStyle; std::string mToolTip; LLPointer mItem; LLTextEditor& mEditor; bool mHasMouseHover; }; //////////////////////////////////////////////////////////// // LLEmbeddedItems // // Embedded items are stored as: // * A global map of llwchar to LLInventoryItem // ** This is unique for each item embedded in any notecard // to support copy/paste across notecards // * A per-notecard set of embeded llwchars for easy removal // from the global list // * A per-notecard vector of embedded lwchars for mapping from // old style 0x80 + item format notechards class LLEmbeddedItems { public: LLEmbeddedItems(const LLViewerTextEditor* editor); ~LLEmbeddedItems(); void clear(); // return true if there are no embedded items. bool empty(); BOOL insertEmbeddedItem(LLInventoryItem* item, llwchar* value, bool is_new); BOOL removeEmbeddedItem( llwchar ext_char ); BOOL hasEmbeddedItem(llwchar ext_char); // returns TRUE if /this/ editor has an entry for this item LLUIImagePtr getItemImage(llwchar ext_char) const; void getEmbeddedItemList( std::vector >& items ); void addItems(const std::vector >& items); llwchar getEmbeddedCharFromIndex(S32 index); void removeUnusedChars(); void copyUsedCharsToIndexed(); S32 getIndexFromEmbeddedChar(llwchar wch); void markSaved(); static LLPointer getEmbeddedItemPtr(llwchar ext_char); // returns pointer to item from static list static BOOL getEmbeddedItemSaved(llwchar ext_char); // returns whether item from static list is saved private: struct embedded_info_t { LLPointer mItemPtr; BOOL mSaved; }; typedef std::map item_map_t; static item_map_t sEntries; static std::stack sFreeEntries; std::set mEmbeddedUsedChars; // list of used llwchars std::vector mEmbeddedIndexedChars; // index -> wchar for 0x80 + index format const LLViewerTextEditor* mEditor; }; //statics LLEmbeddedItems::item_map_t LLEmbeddedItems::sEntries; std::stack LLEmbeddedItems::sFreeEntries; LLEmbeddedItems::LLEmbeddedItems(const LLViewerTextEditor* editor) : mEditor(editor) { } LLEmbeddedItems::~LLEmbeddedItems() { clear(); } void LLEmbeddedItems::clear() { // Remove entries for this editor from static list for (std::set::iterator iter = mEmbeddedUsedChars.begin(); iter != mEmbeddedUsedChars.end();) { std::set::iterator nextiter = iter++; removeEmbeddedItem(*nextiter); } mEmbeddedUsedChars.clear(); mEmbeddedIndexedChars.clear(); } bool LLEmbeddedItems::empty() { removeUnusedChars(); return mEmbeddedUsedChars.empty(); } // Inserts a new unique entry BOOL LLEmbeddedItems::insertEmbeddedItem( LLInventoryItem* item, llwchar* ext_char, bool is_new) { // Now insert a new one llwchar wc_emb; if (!sFreeEntries.empty()) { wc_emb = sFreeEntries.top(); sFreeEntries.pop(); } else if (sEntries.empty()) { wc_emb = LLTextEditor::FIRST_EMBEDDED_CHAR; } else { item_map_t::iterator last = sEntries.end(); --last; wc_emb = last->first; if (wc_emb >= LLTextEditor::LAST_EMBEDDED_CHAR) { return FALSE; } ++wc_emb; } sEntries[wc_emb].mItemPtr = item; sEntries[wc_emb].mSaved = is_new ? FALSE : TRUE; *ext_char = wc_emb; mEmbeddedUsedChars.insert(wc_emb); return TRUE; } // Removes an entry (all entries are unique) BOOL LLEmbeddedItems::removeEmbeddedItem( llwchar ext_char ) { mEmbeddedUsedChars.erase(ext_char); item_map_t::iterator iter = sEntries.find(ext_char); if (iter != sEntries.end()) { sEntries.erase(ext_char); sFreeEntries.push(ext_char); return TRUE; } return FALSE; } // static LLPointer LLEmbeddedItems::getEmbeddedItemPtr(llwchar ext_char) { if( ext_char >= LLTextEditor::FIRST_EMBEDDED_CHAR && ext_char <= LLTextEditor::LAST_EMBEDDED_CHAR ) { item_map_t::iterator iter = sEntries.find(ext_char); if (iter != sEntries.end()) { return iter->second.mItemPtr; } } return NULL; } // static BOOL LLEmbeddedItems::getEmbeddedItemSaved(llwchar ext_char) { if( ext_char >= LLTextEditor::FIRST_EMBEDDED_CHAR && ext_char <= LLTextEditor::LAST_EMBEDDED_CHAR ) { item_map_t::iterator iter = sEntries.find(ext_char); if (iter != sEntries.end()) { return iter->second.mSaved; } } return FALSE; } llwchar LLEmbeddedItems::getEmbeddedCharFromIndex(S32 index) { if (index >= (S32)mEmbeddedIndexedChars.size()) { llwarns << "No item for embedded char " << index << " using LL_UNKNOWN_CHAR" << llendl; return LL_UNKNOWN_CHAR; } return mEmbeddedIndexedChars[index]; } void LLEmbeddedItems::removeUnusedChars() { std::set used = mEmbeddedUsedChars; const LLWString& wtext = mEditor->getWText(); for (S32 i=0; i<(S32)wtext.size(); i++) { llwchar wc = wtext[i]; if( wc >= LLTextEditor::FIRST_EMBEDDED_CHAR && wc <= LLTextEditor::LAST_EMBEDDED_CHAR ) { used.erase(wc); } } // Remove chars not actually used for (std::set::iterator iter = used.begin(); iter != used.end(); ++iter) { removeEmbeddedItem(*iter); } } void LLEmbeddedItems::copyUsedCharsToIndexed() { // Prune unused items removeUnusedChars(); // Copy all used llwchars to mEmbeddedIndexedChars mEmbeddedIndexedChars.clear(); for (std::set::iterator iter = mEmbeddedUsedChars.begin(); iter != mEmbeddedUsedChars.end(); ++iter) { mEmbeddedIndexedChars.push_back(*iter); } } S32 LLEmbeddedItems::getIndexFromEmbeddedChar(llwchar wch) { S32 idx = 0; for (std::vector::iterator iter = mEmbeddedIndexedChars.begin(); iter != mEmbeddedIndexedChars.end(); ++iter) { if (wch == *iter) break; ++idx; } if (idx < (S32)mEmbeddedIndexedChars.size()) { return idx; } else { llwarns << "Embedded char " << wch << " not found, using 0" << llendl; return 0; } } BOOL LLEmbeddedItems::hasEmbeddedItem(llwchar ext_char) { std::set::iterator iter = mEmbeddedUsedChars.find(ext_char); if (iter != mEmbeddedUsedChars.end()) { return TRUE; } return FALSE; } LLUIImagePtr LLEmbeddedItems::getItemImage(llwchar ext_char) const { LLInventoryItem* item = getEmbeddedItemPtr(ext_char); if (item) { const char* img_name = ""; switch( item->getType() ) { case LLAssetType::AT_TEXTURE: if(item->getInventoryType() == LLInventoryType::IT_SNAPSHOT) { img_name = "Inv_Snapshot"; } else { img_name = "Inv_Texture"; } break; case LLAssetType::AT_SOUND: img_name = "Inv_Sound"; break; case LLAssetType::AT_CLOTHING: img_name = "Inv_Clothing"; break; case LLAssetType::AT_OBJECT: img_name = LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS & item->getFlags() ? "Inv_Object_Multi" : "Inv_Object"; break; case LLAssetType::AT_CALLINGCARD: img_name = "Inv_CallingCard"; break; case LLAssetType::AT_LANDMARK: img_name = "Inv_Landmark"; break; case LLAssetType::AT_NOTECARD: img_name = "Inv_Notecard"; break; case LLAssetType::AT_LSL_TEXT: img_name = "Inv_Script"; break; case LLAssetType::AT_BODYPART: img_name = "Inv_Skin"; break; case LLAssetType::AT_ANIMATION: img_name = "Inv_Animation"; break; case LLAssetType::AT_GESTURE: img_name = "Inv_Gesture"; break; case LLAssetType::AT_MESH: img_name = "Inv_Mesh"; break; default: llassert(0); } return LLUI::getUIImage(img_name); } return LLUIImagePtr(); } void LLEmbeddedItems::addItems(const std::vector >& items) { for (std::vector >::const_iterator iter = items.begin(); iter != items.end(); ++iter) { LLInventoryItem* item = *iter; if (item) { llwchar wc; if (!insertEmbeddedItem( item, &wc, false )) { break; } mEmbeddedIndexedChars.push_back(wc); } } } void LLEmbeddedItems::getEmbeddedItemList( std::vector >& items ) { for (std::set::iterator iter = mEmbeddedUsedChars.begin(); iter != mEmbeddedUsedChars.end(); ++iter) { llwchar wc = *iter; LLPointer item = getEmbeddedItemPtr(wc); if (item) { items.push_back(item); } } } void LLEmbeddedItems::markSaved() { for (std::set::iterator iter = mEmbeddedUsedChars.begin(); iter != mEmbeddedUsedChars.end(); ++iter) { llwchar wc = *iter; sEntries[wc].mSaved = TRUE; } } /////////////////////////////////////////////////////////////////// class LLViewerTextEditor::TextCmdInsertEmbeddedItem : public LLTextBase::TextCmd { public: TextCmdInsertEmbeddedItem( S32 pos, LLInventoryItem* item ) : TextCmd(pos, FALSE), mExtCharValue(0) { mItem = item; } virtual BOOL execute( LLTextBase* editor, S32* delta ) { LLViewerTextEditor* viewer_editor = (LLViewerTextEditor*)editor; // Take this opportunity to remove any unused embedded items from this editor viewer_editor->mEmbeddedItemList->removeUnusedChars(); if(viewer_editor->mEmbeddedItemList->insertEmbeddedItem( mItem, &mExtCharValue, true ) ) { LLWString ws; ws.assign(1, mExtCharValue); *delta = insert(editor, getPosition(), ws ); return (*delta != 0); } return FALSE; } virtual S32 undo( LLTextBase* editor ) { remove(editor, getPosition(), 1); return getPosition(); } virtual S32 redo( LLTextBase* editor ) { LLWString ws; ws += mExtCharValue; insert(editor, getPosition(), ws ); return getPosition() + 1; } virtual BOOL hasExtCharValue( llwchar value ) const { return (value == mExtCharValue); } private: LLPointer mItem; llwchar mExtCharValue; }; struct LLNotecardCopyInfo { LLNotecardCopyInfo(LLViewerTextEditor *ed, LLInventoryItem *item) : mTextEd(ed) { mItem = item; } LLViewerTextEditor* mTextEd; // need to make this be a copy (not a * here) because it isn't stable. // I wish we had passed LLPointers all the way down, but we didn't LLPointer mItem; }; //---------------------------------------------------------------------------- // // Member functions // LLViewerTextEditor::LLViewerTextEditor(const LLViewerTextEditor::Params& p) : LLTextEditor(p), mDragItemChar(0), mDragItemSaved(FALSE), mInventoryCallback(new LLEmbeddedNotecardOpener) { mEmbeddedItemList = new LLEmbeddedItems(this); mInventoryCallback->setEditor(this); } LLViewerTextEditor::~LLViewerTextEditor() { delete mEmbeddedItemList; // The inventory callback may still be in use by gInventoryCallbackManager... // so set its reference to this to null. mInventoryCallback->setEditor(NULL); } /////////////////////////////////////////////////////////////////// // virtual void LLViewerTextEditor::makePristine() { mEmbeddedItemList->markSaved(); LLTextEditor::makePristine(); } BOOL LLViewerTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) { BOOL handled = FALSE; // Let scrollbar have first dibs handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL; if( !handled) { if( allowsEmbeddedItems() ) { setCursorAtLocalPos( x, y, FALSE ); llwchar wc = 0; if (mCursorPos < getLength()) { wc = getWText()[mCursorPos]; } LLPointer item_at_pos = LLEmbeddedItems::getEmbeddedItemPtr(wc); if (item_at_pos) { mDragItem = item_at_pos; mDragItemChar = wc; mDragItemSaved = LLEmbeddedItems::getEmbeddedItemSaved(wc); gFocusMgr.setMouseCapture( this ); mMouseDownX = x; mMouseDownY = y; S32 screen_x; S32 screen_y; localPointToScreen(x, y, &screen_x, &screen_y ); LLToolDragAndDrop::getInstance()->setDragStart( screen_x, screen_y ); if (hasTabStop()) { setFocus( TRUE ); } handled = TRUE; } else { mDragItem = NULL; } } if (!handled) { handled = LLTextEditor::handleMouseDown(x, y, mask); } } return handled; } BOOL LLViewerTextEditor::handleHover(S32 x, S32 y, MASK mask) { BOOL handled = LLTextEditor::handleHover(x, y, mask); if(hasMouseCapture() && mDragItem) { S32 screen_x; S32 screen_y; localPointToScreen(x, y, &screen_x, &screen_y ); mScroller->autoScroll(x, y); if( LLToolDragAndDrop::getInstance()->isOverThreshold( screen_x, screen_y ) ) { LLToolDragAndDrop::getInstance()->beginDrag( LLViewerAssetType::lookupDragAndDropType( mDragItem->getType() ), mDragItem->getUUID(), LLToolDragAndDrop::SOURCE_NOTECARD, mPreviewID, mObjectID); return LLToolDragAndDrop::getInstance()->handleHover( x, y, mask ); } getWindow()->setCursor(UI_CURSOR_HAND); handled = TRUE; } return handled; } BOOL LLViewerTextEditor::handleMouseUp(S32 x, S32 y, MASK mask) { BOOL handled = FALSE; if( hasMouseCapture() ) { if (mDragItem) { // mouse down was on an item S32 dx = x - mMouseDownX; S32 dy = y - mMouseDownY; if (-2 < dx && dx < 2 && -2 < dy && dy < 2) { if(mDragItemSaved) { openEmbeddedItem(mDragItem, mDragItemChar); } else { showUnsavedAlertDialog(mDragItem); } } } mDragItem = NULL; } handled = LLTextEditor::handleMouseUp(x,y,mask); return handled; } BOOL LLViewerTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) { BOOL handled = FALSE; // let scrollbar have first dibs handled = LLView::childrenHandleDoubleClick(x, y, mask) != NULL; if( !handled) { if( allowsEmbeddedItems() ) { S32 doc_index = getDocIndexFromLocalCoord(x, y, FALSE); llwchar doc_char = getWText()[doc_index]; if (mEmbeddedItemList->hasEmbeddedItem(doc_char)) { if( openEmbeddedItemAtPos( doc_index )) { deselect(); setFocus( FALSE ); return TRUE; } } } handled = LLTextEditor::handleDoubleClick(x, y, mask); } return handled; } // virtual BOOL LLViewerTextEditor::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void *cargo_data, EAcceptance *accept, std::string& tooltip_msg) { BOOL handled = FALSE; LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource(); if (LLToolDragAndDrop::SOURCE_NOTECARD == source) { // We currently do not handle dragging items from one notecard to another // since items in a notecard must be in Inventory to be verified. See DEV-2891. return FALSE; } if (getEnabled() && acceptsTextInput()) { switch( cargo_type ) { case DAD_CALLINGCARD: case DAD_TEXTURE: case DAD_SOUND: case DAD_LANDMARK: case DAD_SCRIPT: case DAD_CLOTHING: case DAD_OBJECT: case DAD_NOTECARD: case DAD_BODYPART: case DAD_ANIMATION: case DAD_GESTURE: case DAD_MESH: { LLInventoryItem *item = (LLInventoryItem *)cargo_data; if( item && allowsEmbeddedItems() ) { U32 mask_next = item->getPermissions().getMaskNextOwner(); if((mask_next & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED) { if( drop ) { deselect(); S32 old_cursor = mCursorPos; setCursorAtLocalPos( x, y, TRUE ); S32 insert_pos = mCursorPos; setCursorPos(old_cursor); BOOL inserted = insertEmbeddedItem( insert_pos, item ); if( inserted && (old_cursor > mCursorPos) ) { setCursorPos(mCursorPos + 1); } needsReflow(); } *accept = ACCEPT_YES_COPY_MULTI; } else { *accept = ACCEPT_NO; if (tooltip_msg.empty()) { // *TODO: Translate tooltip_msg.assign("Only items with unrestricted\n" "'next owner' permissions \n" "can be attached to notecards."); } } } else { *accept = ACCEPT_NO; } break; } default: *accept = ACCEPT_NO; break; } } else { // Not enabled *accept = ACCEPT_NO; } handled = TRUE; lldebugst(LLERR_USER_INPUT) << "dragAndDrop handled by LLViewerTextEditor " << getName() << llendl; return handled; } void LLViewerTextEditor::setASCIIEmbeddedText(const std::string& instr) { LLWString wtext; const U8* buffer = (U8*)(instr.c_str()); while (*buffer) { llwchar wch; U8 c = *buffer++; if (c >= 0x80) { S32 index = (S32)(c - 0x80); wch = mEmbeddedItemList->getEmbeddedCharFromIndex(index); } else { wch = (llwchar)c; } wtext.push_back(wch); } setWText(wtext); } void LLViewerTextEditor::setEmbeddedText(const std::string& instr) { LLWString wtext = utf8str_to_wstring(instr); for (S32 i=0; i<(S32)wtext.size(); i++) { llwchar wch = wtext[i]; if( wch >= FIRST_EMBEDDED_CHAR && wch <= LAST_EMBEDDED_CHAR ) { S32 index = wch - FIRST_EMBEDDED_CHAR; wtext[i] = mEmbeddedItemList->getEmbeddedCharFromIndex(index); } } setWText(wtext); } std::string LLViewerTextEditor::getEmbeddedText() { #if 1 // New version (Version 2) mEmbeddedItemList->copyUsedCharsToIndexed(); LLWString outtextw; for (S32 i=0; i<(S32)getWText().size(); i++) { llwchar wch = getWText()[i]; if( wch >= FIRST_EMBEDDED_CHAR && wch <= LAST_EMBEDDED_CHAR ) { S32 index = mEmbeddedItemList->getIndexFromEmbeddedChar(wch); wch = FIRST_EMBEDDED_CHAR + index; } outtextw.push_back(wch); } std::string outtext = wstring_to_utf8str(outtextw); return outtext; #else // Old version (Version 1) mEmbeddedItemList->copyUsedCharsToIndexed(); std::string outtext; for (S32 i=0; i<(S32)mWText.size(); i++) { llwchar wch = mWText[i]; if( wch >= FIRST_EMBEDDED_CHAR && wch <= LAST_EMBEDDED_CHAR ) { S32 index = mEmbeddedItemList->getIndexFromEmbeddedChar(wch); wch = 0x80 | index % 128; } else if (wch >= 0x80) { wch = LL_UNKNOWN_CHAR; } outtext.push_back((U8)wch); } return outtext; #endif } std::string LLViewerTextEditor::appendTime(bool prepend_newline) { time_t utc_time; utc_time = time_corrected(); std::string timeStr ="[["+ LLTrans::getString("TimeHour")+"]:[" +LLTrans::getString("TimeMin")+"]] "; LLSD substitution; substitution["datetime"] = (S32) utc_time; LLStringUtil::format (timeStr, substitution); appendText(timeStr, prepend_newline, LLStyle::Params().color(LLColor4::grey)); blockUndo(); return timeStr; } //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- llwchar LLViewerTextEditor::pasteEmbeddedItem(llwchar ext_char) { if (mEmbeddedItemList->hasEmbeddedItem(ext_char)) { return ext_char; // already exists in my list } LLInventoryItem* item = LLEmbeddedItems::getEmbeddedItemPtr(ext_char); if (item) { // Add item to my list and return new llwchar associated with it llwchar new_wc; if (mEmbeddedItemList->insertEmbeddedItem( item, &new_wc, true )) { return new_wc; } } return LL_UNKNOWN_CHAR; // item not found or list full } void LLViewerTextEditor::onValueChange(S32 start, S32 end) { updateSegments(); updateLinkSegments(); findEmbeddedItemSegments(start, end); } void LLViewerTextEditor::findEmbeddedItemSegments(S32 start, S32 end) { LLWString text = getWText(); LLColor4 text_color = ( mReadOnly ? mReadOnlyFgColor.get() : mFgColor.get() ); // Start with i just after the first embedded item for(S32 idx = start; idx < end; idx++ ) { llwchar embedded_char = text[idx]; if( embedded_char >= FIRST_EMBEDDED_CHAR && embedded_char <= LAST_EMBEDDED_CHAR && mEmbeddedItemList->hasEmbeddedItem(embedded_char) ) { LLInventoryItem* itemp = mEmbeddedItemList->getEmbeddedItemPtr(embedded_char); LLUIImagePtr image = mEmbeddedItemList->getItemImage(embedded_char); insertSegment(new LLEmbeddedItemSegment(idx, image, itemp, *this)); } } } BOOL LLViewerTextEditor::openEmbeddedItemAtPos(S32 pos) { if( pos < getLength()) { llwchar wc = getWText()[pos]; LLPointer item = LLEmbeddedItems::getEmbeddedItemPtr( wc ); if( item ) { BOOL saved = LLEmbeddedItems::getEmbeddedItemSaved( wc ); if (saved) { return openEmbeddedItem(item, wc); } else { showUnsavedAlertDialog(item); } } } return FALSE; } BOOL LLViewerTextEditor::openEmbeddedItem(LLPointer item, llwchar wc) { switch( item->getType() ) { case LLAssetType::AT_TEXTURE: openEmbeddedTexture( item, wc ); return TRUE; case LLAssetType::AT_SOUND: openEmbeddedSound( item, wc ); return TRUE; case LLAssetType::AT_NOTECARD: openEmbeddedNotecard( item, wc ); return TRUE; case LLAssetType::AT_LANDMARK: openEmbeddedLandmark( item, wc ); return TRUE; case LLAssetType::AT_CALLINGCARD: openEmbeddedCallingcard( item, wc ); return TRUE; case LLAssetType::AT_LSL_TEXT: case LLAssetType::AT_CLOTHING: case LLAssetType::AT_OBJECT: case LLAssetType::AT_BODYPART: case LLAssetType::AT_ANIMATION: case LLAssetType::AT_GESTURE: showCopyToInvDialog( item, wc ); return TRUE; default: return FALSE; } } void LLViewerTextEditor::openEmbeddedTexture( LLInventoryItem* item, llwchar wc ) { // *NOTE: Just for embedded Texture , we should use getAssetUUID(), // not getUUID(), because LLPreviewTexture pass in AssetUUID into // LLPreview constructor ItemUUID parameter. if (!item) return; LLPreviewTexture* preview = LLFloaterReg::showTypedInstance("preview_texture", LLSD(item->getAssetUUID()), TAKE_FOCUS_YES); if (preview) { preview->setAuxItem( item ); preview->setNotecardInfo(mNotecardInventoryID, mObjectID); } } void LLViewerTextEditor::openEmbeddedSound( LLInventoryItem* item, llwchar wc ) { // Play sound locally LLVector3d lpos_global = gAgent.getPositionGlobal(); const F32 SOUND_GAIN = 1.0f; if(gAudiop) { gAudiop->triggerSound(item->getAssetUUID(), gAgentID, SOUND_GAIN, LLAudioEngine::AUDIO_TYPE_UI, lpos_global); } showCopyToInvDialog( item, wc ); } void LLViewerTextEditor::openEmbeddedLandmark( LLPointer item_ptr, llwchar wc ) { if (item_ptr.isNull()) return; LLLandmark* landmark = gLandmarkList.getAsset(item_ptr->getAssetUUID(), boost::bind(&LLEmbeddedLandmarkCopied::processForeignLandmark, _1, mObjectID, mNotecardInventoryID, item_ptr)); if (landmark) { LLEmbeddedLandmarkCopied::processForeignLandmark(landmark, mObjectID, mNotecardInventoryID, item_ptr); } } void LLViewerTextEditor::openEmbeddedNotecard( LLInventoryItem* item, llwchar wc ) { copyInventory(item, gInventoryCallbacks.registerCB(mInventoryCallback)); } void LLViewerTextEditor::openEmbeddedCallingcard( LLInventoryItem* item, llwchar wc ) { if(item && !item->getCreatorUUID().isNull()) { LLAvatarActions::showProfile(item->getCreatorUUID()); } } void LLViewerTextEditor::showUnsavedAlertDialog( LLInventoryItem* item ) { LLSD payload; payload["item_id"] = item->getUUID(); payload["notecard_id"] = mNotecardInventoryID; LLNotificationsUtil::add( "ConfirmNotecardSave", LLSD(), payload, LLViewerTextEditor::onNotecardDialog); } // static bool LLViewerTextEditor::onNotecardDialog(const LLSD& notification, const LLSD& response ) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if( option == 0 ) { LLPreviewNotecard* preview = LLFloaterReg::findTypedInstance("preview_notecard", notification["payload"]["notecard_id"]);; if (preview) { preview->saveItem(); } } return false; } void LLViewerTextEditor::showCopyToInvDialog( LLInventoryItem* item, llwchar wc ) { LLSD payload; LLUUID item_id = item->getUUID(); payload["item_id"] = item_id; payload["item_wc"] = LLSD::Integer(wc); LLNotificationsUtil::add( "ConfirmItemCopy", LLSD(), payload, boost::bind(&LLViewerTextEditor::onCopyToInvDialog, this, _1, _2)); } bool LLViewerTextEditor::onCopyToInvDialog(const LLSD& notification, const LLSD& response) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if( 0 == option ) { LLUUID item_id = notification["payload"]["item_id"].asUUID(); llwchar wc = llwchar(notification["payload"]["item_wc"].asInteger()); LLInventoryItem* itemp = LLEmbeddedItems::getEmbeddedItemPtr(wc); if (itemp) copyInventory(itemp); } return false; } // Returns change in number of characters in mWText S32 LLViewerTextEditor::insertEmbeddedItem( S32 pos, LLInventoryItem* item ) { return execute( new TextCmdInsertEmbeddedItem( pos, item ) ); } bool LLViewerTextEditor::importStream(std::istream& str) { LLNotecard nc(LLNotecard::MAX_SIZE); bool success = nc.importStream(str); if (success) { mEmbeddedItemList->clear(); const std::vector >& items = nc.getItems(); mEmbeddedItemList->addItems(items); // Actually set the text if (allowsEmbeddedItems()) { if (nc.getVersion() == 1) setASCIIEmbeddedText( nc.getText() ); else setEmbeddedText( nc.getText() ); } else { setText( nc.getText() ); } } return success; } void LLViewerTextEditor::copyInventory(const LLInventoryItem* item, U32 callback_id) { copy_inventory_from_notecard(mObjectID, mNotecardInventoryID, item, callback_id); } bool LLViewerTextEditor::hasEmbeddedInventory() { return ! mEmbeddedItemList->empty(); } //////////////////////////////////////////////////////////////////////////// BOOL LLViewerTextEditor::importBuffer( const char* buffer, S32 length ) { LLMemoryStream str((U8*)buffer, length); return importStream(str); } BOOL LLViewerTextEditor::exportBuffer( std::string& buffer ) { LLNotecard nc(LLNotecard::MAX_SIZE); // Get the embedded text and update the item list to just be the used items nc.setText(getEmbeddedText()); // Now get the used items and copy the list to the notecard std::vector > embedded_items; mEmbeddedItemList->getEmbeddedItemList(embedded_items); nc.setItems(embedded_items); std::stringstream out_stream; nc.exportStream(out_stream); buffer = out_stream.str(); return TRUE; }