/** * @file llviewertexteditor.cpp * @brief Text editor widget to let users enter a multi-line document. * * $LicenseInfo:firstyear=2001&license=viewergpl$ * * Copyright (c) 2001-2009, Linden Research, Inc. * * Second Life Viewer Source Code * 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 * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llfloaterreg.h" #include "llfocusmgr.h" #include "llaudioengine.h" #include "llagent.h" #include "llinventory.h" #include "llinventorybridge.h" #include "llinventorymodel.h" #include "llviewertexteditor.h" #include "llfloaterchat.h" #include "llfloaterworldmap.h" #include "llnotify.h" #include "llpanelplaces.h" #include "llpreview.h" #include "llpreviewtexture.h" #include "llpreviewnotecard.h" #include "llscrollbar.h" #include "llsidetray.h" #include "lltooldraganddrop.h" #include "lltrans.h" #include "llviewercontrol.h" #include "llviewertexturelist.h" #include "llviewerwindow.h" #include "llviewerinventory.h" #include "lluictrlfactory.h" #include "llnotecard.h" #include "llmemorystream.h" #include "llmenugl.h" #include "llscrollcontainer.h" #include "llavataractions.h" #include "llappviewer.h" // for gPacificDaylightTime static LLDefaultChildRegistry::Register r("text_editor"); ///---------------------------------------------------------------------------- /// 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*/ S32 getWidth(S32 first_char, S32 num_chars) const { if (num_chars == 0) { return 0; } else { return EMBEDDED_ITEM_LABEL_PADDING + mImage->getWidth() + mStyle->getFont()->getWidth(mLabel.c_str()); } } //virtual S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const; //virtual void updateLayout(const class LLTextEditor& editor); /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const { 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.mBottom, color, LLFontGL::LEFT, LLFontGL::BOTTOM, mHasMouseHover ? LLFontGL::UNDERLINE : 0, LLFontGL::NO_SHADOW, mLabel.length(), S32_MAX, &right_x); return right_x; } /*virtual*/ S32 getMaxHeight() const { return llmax(mImage->getHeight(), llceil(mStyle->getFont()->getLineHeight())); } /*virtual*/ bool canEdit() const { return false; } //virtual void unlinkFromDocument(class LLTextEditor* editor); //virtual void linkToDocument(class LLTextEditor* editor); virtual void setHasMouseHover(bool hover) { mHasMouseHover = hover; } //virtual const LLColor4& getColor() const; //virtual void setColor(const LLColor4 &color); //virtual void setStyle(const LLStyleSP &style); virtual BOOL getToolTip( std::string& msg ) const { msg = mToolTip; return TRUE; } /*virtual*/ const LLStyleSP 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 LLInventoryItem* getEmbeddedItem(llwchar ext_char); // returns item from static list static BOOL getEmbeddedItemSaved(llwchar ext_char); // returns whether item from static list is saved private: struct embedded_info_t { LLPointer mItem; 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].mItem = 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 LLInventoryItem* LLEmbeddedItems::getEmbeddedItem(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.mItem; } } 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 = getEmbeddedItem(ext_char); if (item) { const char* img_name = ""; switch( item->getType() ) { case LLAssetType::AT_TEXTURE: if(item->getInventoryType() == LLInventoryType::IT_SNAPSHOT) { img_name = "inv_item_snapshot.tga"; } else { img_name = "inv_item_texture.tga"; } break; case LLAssetType::AT_SOUND: img_name = "inv_item_sound.tga"; break; case LLAssetType::AT_LANDMARK: if (item->getFlags() & LLInventoryItem::II_FLAGS_LANDMARK_VISITED) { img_name = "inv_item_landmark_visited.tga"; } else { img_name = "inv_item_landmark.tga"; } break; case LLAssetType::AT_CALLINGCARD: { BOOL online; online = LLAvatarTracker::instance().isBuddyOnline(item->getCreatorUUID()); if (online) { img_name = "inv_item_callingcard_online.tga"; break; } else { img_name = "inv_item_callingcard_offline.tga"; break; } break; } case LLAssetType::AT_CLOTHING: img_name = "inv_item_clothing.tga"; break; case LLAssetType::AT_OBJECT: if (item->getFlags() & LLInventoryItem::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS) { img_name = "inv_item_object_multi.tga"; } else { img_name = "inv_item_object.tga"; } break; case LLAssetType::AT_NOTECARD: img_name = "inv_item_notecard.tga"; break; case LLAssetType::AT_LSL_TEXT: img_name = "inv_item_script.tga"; break; case LLAssetType::AT_BODYPART: img_name = "inv_item_skin.tga"; break; case LLAssetType::AT_ANIMATION: img_name = "inv_item_animation.tga";break; case LLAssetType::AT_GESTURE: img_name = "inv_item_gesture.tga"; break; //TODO need img_name case LLAssetType::AT_FAVORITE: img_name = "inv_item_landmark.tga"; 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 = getEmbeddedItem(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::LLTextCmdInsertEmbeddedItem : public LLTextEditor::LLTextCmd { public: LLTextCmdInsertEmbeddedItem( S32 pos, LLInventoryItem* item ) : LLTextCmd(pos, FALSE), mExtCharValue(0) { mItem = item; } virtual BOOL execute( LLTextEditor* 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( LLTextEditor* editor ) { remove(editor, getPosition(), 1); return getPosition(); } virtual S32 redo( LLTextEditor* 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) { mParseHTML = p.allow_html; 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::handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_rect_screen) { for (child_list_const_iter_t child_iter = getChildList()->begin(); child_iter != getChildList()->end(); ++child_iter) { LLView *viewp = *child_iter; S32 local_x = x - viewp->getRect().mLeft; S32 local_y = y - viewp->getRect().mBottom; if( viewp->pointInView(local_x, local_y) && viewp->getVisible() && viewp->getEnabled() && viewp->handleToolTip(local_x, local_y, msg, sticky_rect_screen ) ) { return TRUE; } } if( mSegments.empty() ) { return TRUE; } const LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y ); if( cur_segment && cur_segment->getToolTip( msg ) ) { // Just use a slop area around the cursor // Convert rect local to screen coordinates S32 SLOP = 8; localPointToScreen( x - SLOP, y - SLOP, &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) ); sticky_rect_screen->mRight = sticky_rect_screen->mLeft + 2 * SLOP; sticky_rect_screen->mTop = sticky_rect_screen->mBottom + 2 * SLOP; } return TRUE; } 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 = getWChar(mCursorPos); } LLInventoryItem* item_at_pos = LLEmbeddedItems::getEmbeddedItem(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( LLAssetType::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::handleRightMouseDown(S32 x, S32 y, MASK mask) { BOOL handled = childrenHandleRightMouseDown(x, y, mask) != NULL; // *TODO: Add right click menus for SLURLs // if(! handled) // { // const LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y ); // if( cur_segment ) // { // if(cur_segment->getStyle()->isLink()) // { // handled = TRUE; // mHTML = cur_segment->getStyle()->getLinkHREF(); // } // } // } // LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); // if(handled && menu && mParseHTML && mHTML.length() > 0) // { // menu->setVisible(TRUE); // menu->arrange(); // menu->updateParent(LLMenuGL::sMenuContainer); // LLMenuGL::showPopup(this, menu, x, y); // mHTML = ""; // } // else // { // if(menu && menu->getVisible()) // { // menu->setVisible(FALSE); // } // } 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: { 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 = getWChar(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); appendColoredText(timeStr, false, prepend_newline, LLColor4::grey); return timeStr; } //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- llwchar LLViewerTextEditor::pasteEmbeddedItem(llwchar ext_char) { if (mEmbeddedItemList->hasEmbeddedItem(ext_char)) { return ext_char; // already exists in my list } LLInventoryItem* item = LLEmbeddedItems::getEmbeddedItem(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(); 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->getEmbeddedItem(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 = getWChar(pos); LLInventoryItem* item = LLEmbeddedItems::getEmbeddedItem( wc ); if( item ) { BOOL saved = LLEmbeddedItems::getEmbeddedItemSaved( wc ); if (saved) { return openEmbeddedItem(item, wc); } else { showUnsavedAlertDialog(item); } } } return FALSE; } BOOL LLViewerTextEditor::openEmbeddedItem(LLInventoryItem* 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( LLInventoryItem* item, llwchar wc ) { if (!item) return; LLSD key; key["type"] = "landmark"; key["id"] = item->getUUID(); LLPanelPlaces *panel = dynamic_cast(LLSideTray::getInstance()->showPanel("panel_places", key)); if (panel) { panel->setItem(item); } } 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; LLNotifications::instance().add( "ConfirmNotecardSave", LLSD(), payload, LLViewerTextEditor::onNotecardDialog); } // static bool LLViewerTextEditor::onNotecardDialog(const LLSD& notification, const LLSD& response ) { S32 option = LLNotification::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); LLNotifications::instance().add( "ConfirmItemCopy", LLSD(), payload, boost::bind(&LLViewerTextEditor::onCopyToInvDialog, this, _1, _2)); } bool LLViewerTextEditor::onCopyToInvDialog(const LLSD& notification, const LLSD& response) { S32 option = LLNotification::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::getEmbeddedItem(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 LLTextCmdInsertEmbeddedItem( 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; }