summaryrefslogtreecommitdiff
path: root/indra/newview/llviewertexteditor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/llviewertexteditor.cpp')
-rw-r--r--indra/newview/llviewertexteditor.cpp1475
1 files changed, 1475 insertions, 0 deletions
diff --git a/indra/newview/llviewertexteditor.cpp b/indra/newview/llviewertexteditor.cpp
new file mode 100644
index 0000000000..ddbf577f0a
--- /dev/null
+++ b/indra/newview/llviewertexteditor.cpp
@@ -0,0 +1,1475 @@
+/**
+ * @file llviewertexteditor.cpp
+ * @brief Text editor widget to let users enter a a multi-line ASCII document.
+ *
+ * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc.
+ * $License$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llfocusmgr.h"
+#include "audioengine.h"
+#include "llagent.h"
+#include "llinventory.h"
+#include "llinventorymodel.h"
+#include "llinventoryview.h"
+
+#include "llviewertexteditor.h"
+
+#include "llfloaterchat.h"
+#include "llfloaterworldmap.h"
+#include "llnotify.h"
+#include "llpreview.h"
+#include "llpreviewtexture.h"
+#include "llpreviewnotecard.h"
+#include "llpreviewlandmark.h"
+#include "llscrollbar.h"
+#include "lltooldraganddrop.h"
+#include "llviewercontrol.h"
+#include "llviewerimagelist.h"
+#include "llviewerwindow.h"
+#include "llviewerinventory.h"
+#include "llnotecard.h"
+#include "llmemorystream.h"
+
+extern BOOL gPacificDaylightTime;
+
+////////////////////////////////////////////////////////////
+// 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();
+
+ void bindEmbeddedChars(const LLFontGL* font);
+ void unbindEmbeddedChars(const LLFontGL* font);
+
+ 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
+
+ void getEmbeddedItemList( std::vector<LLPointer<LLInventoryItem> >& items );
+ void addItems(const std::vector<LLPointer<LLInventoryItem> >& 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
+
+ struct embedded_info_t
+ {
+ LLPointer<LLInventoryItem> mItem;
+ BOOL mSaved;
+ };
+private:
+ typedef std::map<llwchar, embedded_info_t > item_map_t;
+ static item_map_t sEntries;
+ static std::stack<llwchar> sFreeEntries;
+
+ std::set<llwchar> mEmbeddedUsedChars; // list of used llwchars
+ std::vector<llwchar> mEmbeddedIndexedChars; // index -> wchar for 0x80 + index format
+ const LLViewerTextEditor* mEditor;
+};
+
+//statics
+LLEmbeddedItems::item_map_t LLEmbeddedItems::sEntries;
+std::stack<llwchar> 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<llwchar>::iterator iter = mEmbeddedUsedChars.begin();
+ iter != mEmbeddedUsedChars.end();)
+ {
+ std::set<llwchar>::iterator nextiter = iter++;
+ removeEmbeddedItem(*nextiter);
+ }
+ mEmbeddedUsedChars.clear();
+}
+
+// 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 = FIRST_EMBEDDED_CHAR;
+ }
+ else
+ {
+ item_map_t::iterator last = sEntries.end();
+ --last;
+ wc_emb = last->first;
+ if (wc_emb >= 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 >= FIRST_EMBEDDED_CHAR && ext_char <= 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 >= FIRST_EMBEDDED_CHAR && ext_char <= 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<llwchar> used = mEmbeddedUsedChars;
+ const LLWString& wtext = mEditor->getWText();
+ for (S32 i=0; i<(S32)wtext.size(); i++)
+ {
+ llwchar wc = wtext[i];
+ if( wc >= FIRST_EMBEDDED_CHAR && wc <= LAST_EMBEDDED_CHAR )
+ {
+ used.erase(wc);
+ }
+ }
+ // Remove chars not actually used
+ for (std::set<llwchar>::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<llwchar>::iterator iter = mEmbeddedUsedChars.begin();
+ iter != mEmbeddedUsedChars.end(); ++iter)
+ {
+ mEmbeddedIndexedChars.push_back(*iter);
+ }
+}
+
+S32 LLEmbeddedItems::getIndexFromEmbeddedChar(llwchar wch)
+{
+ S32 idx = 0;
+ for (std::vector<llwchar>::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<llwchar>::iterator iter = mEmbeddedUsedChars.find(ext_char);
+ if (iter != mEmbeddedUsedChars.end())
+ {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void LLEmbeddedItems::bindEmbeddedChars( const LLFontGL* font )
+{
+ if( sEntries.empty() )
+ {
+ return;
+ }
+
+ for (std::set<llwchar>::iterator iter1 = mEmbeddedUsedChars.begin(); iter1 != mEmbeddedUsedChars.end(); ++iter1)
+ {
+ llwchar wch = *iter1;
+ item_map_t::iterator iter2 = sEntries.find(wch);
+ if (iter2 == sEntries.end())
+ {
+ continue;
+ }
+ LLInventoryItem* item = iter2->second.mItem;
+ if (!item)
+ {
+ continue;
+ }
+ 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_CLOTHING: img_name = "inv_item_clothing.tga"; break;
+ case LLAssetType::AT_OBJECT: 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_bodypart.tga"; break;
+ case LLAssetType::AT_ANIMATION: img_name = "inv_item_animation.tga";break;
+ case LLAssetType::AT_GESTURE: img_name = "inv_item_gesture.tga"; break;
+ default: llassert(0); continue;
+ }
+
+ LLViewerImage* image = gImageList.getImage(LLUUID(gViewerArt.getString(img_name)), MIPMAP_FALSE, TRUE);
+
+ ((LLFontGL*)font)->addEmbeddedChar( wch, image, item->getName() );
+ }
+}
+
+void LLEmbeddedItems::unbindEmbeddedChars( const LLFontGL* font )
+{
+ if( sEntries.empty() )
+ {
+ return;
+ }
+
+ for (std::set<llwchar>::iterator iter1 = mEmbeddedUsedChars.begin(); iter1 != mEmbeddedUsedChars.end(); ++iter1)
+ {
+ ((LLFontGL*)font)->removeEmbeddedChar(*iter1);
+ }
+}
+
+void LLEmbeddedItems::addItems(const std::vector<LLPointer<LLInventoryItem> >& items)
+{
+ for (std::vector<LLPointer<LLInventoryItem> >::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<LLPointer<LLInventoryItem> >& items )
+{
+ for (std::set<llwchar>::iterator iter = mEmbeddedUsedChars.begin(); iter != mEmbeddedUsedChars.end(); ++iter)
+ {
+ llwchar wc = *iter;
+ LLPointer<LLInventoryItem> item = getEmbeddedItem(wc);
+ if (item)
+ {
+ items.push_back(item);
+ }
+ }
+}
+
+void LLEmbeddedItems::markSaved()
+{
+ for (std::set<llwchar>::iterator iter = mEmbeddedUsedChars.begin(); iter != mEmbeddedUsedChars.end(); ++iter)
+ {
+ llwchar wc = *iter;
+ sEntries[wc].mSaved = TRUE;
+ }
+}
+
+///////////////////////////////////////////////////////////////////
+
+class LLTextCmdInsertEmbeddedItem : public LLTextCmd
+{
+public:
+ LLTextCmdInsertEmbeddedItem::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, mPos, ws );
+ return (*delta != 0);
+ }
+ return FALSE;
+ }
+
+ virtual S32 undo( LLTextEditor* editor )
+ {
+ remove(editor, mPos, 1);
+ return mPos;
+ }
+
+ virtual S32 redo( LLTextEditor* editor )
+ {
+ LLWString ws;
+ ws += mExtCharValue;
+ insert(editor, mPos, ws );
+ return mPos + 1;
+ }
+ virtual BOOL hasExtCharValue( llwchar value )
+ {
+ return (value == mExtCharValue);
+ }
+
+private:
+ LLPointer<LLInventoryItem> 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<LLInventoryItem> mItem;
+};
+
+//----------------------------------------------------------------------------
+
+//
+// Member functions
+//
+
+LLViewerTextEditor::LLViewerTextEditor(const LLString& name,
+ const LLRect& rect,
+ S32 max_length,
+ const LLString& default_text,
+ const LLFontGL* font,
+ BOOL allow_embedded_items)
+ : LLTextEditor(name, rect, max_length, default_text, font, allow_embedded_items),
+ mDragItemSaved(FALSE)
+{
+ mEmbeddedItemList = new LLEmbeddedItems(this);
+}
+
+LLViewerTextEditor::~LLViewerTextEditor()
+{
+ delete mEmbeddedItemList;
+}
+
+///////////////////////////////////////////////////////////////////
+// virtual
+void LLViewerTextEditor::makePristine()
+{
+ mEmbeddedItemList->markSaved();
+ LLTextEditor::makePristine();
+}
+
+///////////////////////////////////////////////////////////////////
+
+BOOL LLViewerTextEditor::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen)
+{
+ if (pointInView(x, y) && getVisible())
+ {
+ 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->handleToolTip(local_x, local_y, msg, sticky_rect_screen ) )
+ {
+ return TRUE;
+ }
+ }
+
+ if( mSegments.empty() )
+ {
+ return TRUE;
+ }
+
+ LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y );
+ if( cur_segment )
+ {
+ BOOL has_tool_tip = FALSE;
+ if( cur_segment->getStyle().getIsEmbeddedItem() )
+ {
+ LLWString wtip;
+ has_tool_tip = getEmbeddedItemToolTipAtPos(cur_segment->getStart(), wtip);
+ msg = wstring_to_utf8str(wtip);
+ }
+ else
+ {
+ has_tool_tip = cur_segment->getToolTip( msg );
+ }
+ if( has_tool_tip )
+ {
+ // 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;
+ }
+ return FALSE;
+}
+
+BOOL LLViewerTextEditor::handleMouseDown(S32 x, S32 y, MASK mask)
+{
+ BOOL handled = FALSE;
+
+ // Let scrollbar have first dibs
+ handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL;
+
+ // enable I Agree checkbox if the user scrolled through entire text
+ BOOL was_scrolled_to_bottom = (mScrollbar->getDocPos() == mScrollbar->getDocPosMax());
+ if (mOnScrollEndCallback && was_scrolled_to_bottom)
+ {
+ mOnScrollEndCallback(mOnScrollEndData);
+ }
+
+ if( !handled && mTakesNonScrollClicks)
+ {
+ if (!(mask & MASK_SHIFT))
+ {
+ deselect();
+ }
+
+ BOOL start_select = TRUE;
+ if( mAllowEmbeddedItems )
+ {
+ setCursorAtLocalPos( x, y, FALSE );
+ llwchar wc = 0;
+ if (mCursorPos < getLength())
+ {
+ wc = mWText[mCursorPos];
+ }
+ LLInventoryItem* item_at_pos = LLEmbeddedItems::getEmbeddedItem(wc);
+ if (item_at_pos)
+ {
+ mDragItem = item_at_pos;
+ mDragItemSaved = LLEmbeddedItems::getEmbeddedItemSaved(wc);
+ gFocusMgr.setMouseCapture( this, NULL );
+ mMouseDownX = x;
+ mMouseDownY = y;
+ S32 screen_x;
+ S32 screen_y;
+ localPointToScreen(x, y, &screen_x, &screen_y );
+ gToolDragAndDrop->setDragStart( screen_x, screen_y );
+
+ start_select = FALSE;
+ }
+ else
+ {
+ mDragItem = NULL;
+ }
+ }
+
+ if( start_select )
+ {
+ // If we're not scrolling (handled by child), then we're selecting
+ if (mask & MASK_SHIFT)
+ {
+ S32 old_cursor_pos = mCursorPos;
+ setCursorAtLocalPos( x, y, TRUE );
+
+ if (hasSelection())
+ {
+ /* Mac-like behavior - extend selection towards the cursor
+ if (mCursorPos < mSelectionStart
+ && mCursorPos < mSelectionEnd)
+ {
+ // ...left of selection
+ mSelectionStart = llmax(mSelectionStart, mSelectionEnd);
+ mSelectionEnd = mCursorPos;
+ }
+ else if (mCursorPos > mSelectionStart
+ && mCursorPos > mSelectionEnd)
+ {
+ // ...right of selection
+ mSelectionStart = llmin(mSelectionStart, mSelectionEnd);
+ mSelectionEnd = mCursorPos;
+ }
+ else
+ {
+ mSelectionEnd = mCursorPos;
+ }
+ */
+ // Windows behavior
+ mSelectionEnd = mCursorPos;
+ }
+ else
+ {
+ mSelectionStart = old_cursor_pos;
+ mSelectionEnd = mCursorPos;
+ }
+ // assume we're starting a drag select
+ mIsSelecting = TRUE;
+ }
+ else
+ {
+ setCursorAtLocalPos( x, y, TRUE );
+ startSelection();
+ }
+ gFocusMgr.setMouseCapture( this, &LLTextEditor::onMouseCaptureLost );
+ //FIXME:
+ //gViewerWindow->requestFastFrame(this);
+ }
+
+ handled = TRUE;
+ }
+
+ if (mTakesFocus)
+ {
+ setFocus( TRUE );
+ handled = TRUE;
+ }
+
+ // Delay cursor flashing
+ mKeystrokeTimer.reset();
+
+ return handled;
+}
+
+
+BOOL LLViewerTextEditor::handleHover(S32 x, S32 y, MASK mask)
+{
+ BOOL handled = FALSE;
+
+ if (!mDragItem)
+ {
+ // leave hover segment active during drag and drop
+ mHoverSegment = NULL;
+ }
+ if( getVisible() )
+ {
+ if(gFocusMgr.getMouseCapture() == this )
+ {
+ if( mIsSelecting )
+ {
+ if (x != mLastSelectionX || y != mLastSelectionY)
+ {
+ mLastSelectionX = x;
+ mLastSelectionY = y;
+ //FIXME:
+ //gViewerWindow->requestFastFrame(this);
+ }
+
+ if( y > mTextRect.mTop )
+ {
+ mScrollbar->setDocPos( mScrollbar->getDocPos() - 1 );
+ }
+ else
+ if( y < mTextRect.mBottom )
+ {
+ mScrollbar->setDocPos( mScrollbar->getDocPos() + 1 );
+ }
+
+ setCursorAtLocalPos( x, y, TRUE );
+ mSelectionEnd = mCursorPos;
+
+ updateScrollFromCursor();
+ getWindow()->setCursor(UI_CURSOR_IBEAM);
+ }
+ else if( mDragItem )
+ {
+ S32 screen_x;
+ S32 screen_y;
+ localPointToScreen(x, y, &screen_x, &screen_y );
+ if( gToolDragAndDrop->isOverThreshold( screen_x, screen_y ) )
+ {
+ gToolDragAndDrop->beginDrag(
+ LLAssetType::lookupDragAndDropType( mDragItem->getType() ),
+ mDragItem->getUUID(),
+ LLToolDragAndDrop::SOURCE_NOTECARD,
+ mSourceID, mObjectID);
+
+ return gToolDragAndDrop->handleHover( x, y, mask );
+ }
+ getWindow()->setCursor(UI_CURSOR_HAND);
+ }
+
+ lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl;
+ handled = TRUE;
+ }
+
+ if( !handled )
+ {
+ // Pass to children
+ handled = LLView::childrenHandleHover(x, y, mask) != NULL;
+ }
+
+ if( handled )
+ {
+ // Delay cursor flashing
+ mKeystrokeTimer.reset();
+ }
+
+ // Opaque
+ if( !handled && mTakesNonScrollClicks)
+ {
+ // Check to see if we're over an HTML-style link
+ if( !mSegments.empty() )
+ {
+ LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y );
+ if( cur_segment )
+ {
+ if(cur_segment->getStyle().isLink())
+ {
+ lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (over link, inactive)" << llendl;
+ getWindow()->setCursor(UI_CURSOR_HAND);
+ handled = TRUE;
+ }
+ else
+ if(cur_segment->getStyle().getIsEmbeddedItem())
+ {
+ lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (over embedded item, inactive)" << llendl;
+ getWindow()->setCursor(UI_CURSOR_HAND);
+ //getWindow()->setCursor(UI_CURSOR_ARROW);
+ handled = TRUE;
+ }
+ mHoverSegment = cur_segment;
+ }
+ }
+
+ if( !handled )
+ {
+ lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl;
+ if (!mScrollbar->getVisible() || x < mRect.getWidth() - SCROLLBAR_SIZE)
+ {
+ getWindow()->setCursor(UI_CURSOR_IBEAM);
+ }
+ else
+ {
+ getWindow()->setCursor(UI_CURSOR_ARROW);
+ }
+ handled = TRUE;
+ }
+ }
+ }
+
+ return handled;
+}
+
+
+BOOL LLViewerTextEditor::handleMouseUp(S32 x, S32 y, MASK mask)
+{
+ BOOL handled = FALSE;
+
+ // let scrollbar have first dibs
+ handled = LLView::childrenHandleMouseUp(x, y, mask) != NULL;
+
+ // enable I Agree checkbox if the user scrolled through entire text
+ BOOL was_scrolled_to_bottom = (mScrollbar->getDocPos() == mScrollbar->getDocPosMax());
+ if (mOnScrollEndCallback && was_scrolled_to_bottom)
+ {
+ mOnScrollEndCallback(mOnScrollEndData);
+ }
+
+ if( !handled && mTakesNonScrollClicks)
+ {
+ if( mIsSelecting )
+ {
+ // Finish selection
+ if( y > mTextRect.mTop )
+ {
+ mScrollbar->setDocPos( mScrollbar->getDocPos() - 1 );
+ }
+ else
+ if( y < mTextRect.mBottom )
+ {
+ mScrollbar->setDocPos( mScrollbar->getDocPos() + 1 );
+ }
+
+ setCursorAtLocalPos( x, y, TRUE );
+ endSelection();
+
+ updateScrollFromCursor();
+ }
+
+ if( !hasSelection() )
+ {
+ handleMouseUpOverSegment( x, y, mask );
+ }
+
+ handled = TRUE;
+ }
+
+ // Delay cursor flashing
+ mKeystrokeTimer.reset();
+
+ if( gFocusMgr.getMouseCapture() == this )
+ {
+ 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)
+ {
+ openEmbeddedItem(mDragItem, mDragItemSaved);
+ }
+ }
+ mDragItem = NULL;
+ gFocusMgr.setMouseCapture( NULL, NULL );
+ handled = TRUE;
+ }
+
+ 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 && mTakesNonScrollClicks)
+ {
+ if( mAllowEmbeddedItems )
+ {
+ LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y );
+ if( cur_segment && cur_segment->getStyle().getIsEmbeddedItem() )
+ {
+ if( openEmbeddedItemAtPos( cur_segment->getStart() ) )
+ {
+ deselect();
+ setFocus( FALSE );
+ return TRUE;
+ }
+ }
+ }
+
+ if (mTakesFocus)
+ {
+ setFocus( TRUE );
+ }
+
+ setCursorAtLocalPos( x, y, FALSE );
+ deselect();
+
+ const LLWString &text = getWText();
+
+ if( isPartOfWord( text[mCursorPos] ) )
+ {
+ // Select word the cursor is over
+ while ((mCursorPos > 0) && isPartOfWord(text[mCursorPos-1]))
+ {
+ mCursorPos--;
+ }
+ startSelection();
+
+ while ((mCursorPos < (S32)text.length()) && isPartOfWord( text[mCursorPos] ) )
+ {
+ mCursorPos++;
+ }
+
+ mSelectionEnd = mCursorPos;
+ }
+ else if ((mCursorPos < (S32)text.length()) && !iswspace( text[mCursorPos]) )
+ {
+ // Select the character the cursor is over
+ startSelection();
+ mCursorPos++;
+ mSelectionEnd = mCursorPos;
+ }
+
+ // We don't want handleMouseUp() to "finish" the selection (and thereby
+ // set mSelectionEnd to where the mouse is), so we finish the selection here.
+ mIsSelecting = FALSE;
+
+ // delay cursor flashing
+ mKeystrokeTimer.reset();
+
+ handled = TRUE;
+ }
+ return handled;
+}
+
+
+// Allow calling cards to be dropped onto text fields. Append the name and
+// a carriage return.
+// virtual
+BOOL LLViewerTextEditor::handleDragAndDrop(S32 x, S32 y, MASK mask,
+ BOOL drop, EDragAndDropType cargo_type, void *cargo_data,
+ EAcceptance *accept,
+ LLString& tooltip_msg)
+{
+ BOOL handled = FALSE;
+
+ if (mTakesNonScrollClicks)
+ {
+ if (getEnabled() && !mReadOnly)
+ {
+ switch( cargo_type )
+ {
+ case DAD_CALLINGCARD:
+ if(mAcceptCallingCardNames)
+ {
+ if (drop)
+ {
+ LLInventoryItem *item = (LLInventoryItem *)cargo_data;
+ LLString name = item->getName();
+ appendText(name, true, true);
+ }
+ *accept = ACCEPT_YES_COPY_SINGLE;
+ }
+ else
+ {
+ *accept = ACCEPT_NO;
+ }
+ break;
+
+ 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( mAllowEmbeddedItems )
+ {
+ 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);
+ }
+
+ updateLineStartList();
+ }
+ *accept = ACCEPT_YES_COPY_MULTI;
+ }
+ else
+ {
+ *accept = ACCEPT_NO;
+ if (tooltip_msg.empty())
+ {
+ 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 LLString& 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 LLString& 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);
+}
+
+LLString LLViewerTextEditor::getEmbeddedText()
+{
+#if 1
+ // New version (Version 2)
+ mEmbeddedItemList->copyUsedCharsToIndexed();
+ LLWString outtextw;
+ 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 = FIRST_EMBEDDED_CHAR + index;
+ }
+ outtextw.push_back(wch);
+ }
+ LLString outtext = wstring_to_utf8str(outtextw);
+ return outtext;
+#else
+ // Old version (Version 1)
+ mEmbeddedItemList->copyUsedCharsToIndexed();
+ LLString 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
+}
+
+LLString LLViewerTextEditor::appendTime(bool prepend_newline)
+{
+ U32 utc_time;
+ utc_time = time_corrected();
+
+ // There's only one internal tm buffer.
+ struct tm* timep;
+
+ // Convert to Pacific, based on server's opinion of whether
+ // it's daylight savings time there.
+ timep = utc_to_pacific_time(utc_time, gPacificDaylightTime);
+
+ LLString text = llformat("[%d:%02d] ", timep->tm_hour, timep->tm_min);
+ appendColoredText(text, false, prepend_newline, LLColor4::grey);
+
+ return text;
+}
+
+//----------------------------------------------------------------------------
+//----------------------------------------------------------------------------
+
+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::bindEmbeddedChars(const LLFontGL* font)
+{
+ mEmbeddedItemList->bindEmbeddedChars( font );
+}
+
+void LLViewerTextEditor::unbindEmbeddedChars(const LLFontGL* font)
+{
+ mEmbeddedItemList->unbindEmbeddedChars( font );
+}
+
+BOOL LLViewerTextEditor::getEmbeddedItemToolTipAtPos(S32 pos, LLWString &msg)
+{
+ if (pos < getLength())
+ {
+ LLInventoryItem* item = LLEmbeddedItems::getEmbeddedItem(mWText[pos]);
+ if( item )
+ {
+ msg = utf8str_to_wstring(item->getName());
+ msg += '\n';
+ msg += utf8str_to_wstring(item->getDescription());
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+
+BOOL LLViewerTextEditor::openEmbeddedItemAtPos(S32 pos)
+{
+ if( pos < getLength())
+ {
+ LLInventoryItem* item = LLEmbeddedItems::getEmbeddedItem( mWText[pos] );
+ if( item )
+ {
+ BOOL saved = LLEmbeddedItems::getEmbeddedItemSaved( mWText[pos] );
+ return openEmbeddedItem(item, saved);
+ }
+ }
+ return FALSE;
+}
+
+
+BOOL LLViewerTextEditor::openEmbeddedItem(LLInventoryItem* item, BOOL saved)
+{
+ switch( item->getType() )
+ {
+ case LLAssetType::AT_TEXTURE:
+ openEmbeddedTexture( item );
+ return TRUE;
+
+ case LLAssetType::AT_SOUND:
+ openEmbeddedSound( item );
+ return TRUE;
+
+ case LLAssetType::AT_NOTECARD:
+ openEmbeddedNotecard( item, saved );
+ return TRUE;
+
+ case LLAssetType::AT_LANDMARK:
+ showLandmarkDialog( item );
+ 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 );
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+
+void LLViewerTextEditor::openEmbeddedTexture( LLInventoryItem* item )
+{
+ // See if we can bring an existing preview to the front
+ if( !LLPreview::show( item->getUUID() ) )
+ {
+ // There isn't one, so make a new preview
+ if(item)
+ {
+ S32 left, top;
+ gFloaterView->getNewFloaterPosition(&left, &top);
+ LLRect rect = gSavedSettings.getRect("PreviewTextureRect");
+ rect.translate( left - rect.mLeft, top - rect.mTop );
+
+ LLPreviewTexture* preview = new LLPreviewTexture("preview texture",
+ rect,
+ item->getName(),
+ item->getAssetUUID(),
+ TRUE);
+ preview->setAuxItem( item );
+ preview->setNotecardInfo(mNotecardInventoryID, mObjectID);
+ }
+ }
+}
+
+void LLViewerTextEditor::openEmbeddedSound( LLInventoryItem* item )
+{
+ // Play sound locally
+ LLVector3d lpos_global = gAgent.getPositionGlobal();
+ const F32 SOUND_GAIN = 1.0f;
+ if(gAudiop)
+ {
+ gAudiop->triggerSound(
+ item->getAssetUUID(), gAgentID, SOUND_GAIN, lpos_global);
+ }
+ showCopyToInvDialog( item );
+}
+
+/*
+void LLViewerTextEditor::openEmbeddedLandmark( LLInventoryItem* item )
+{
+ // See if we can bring an existing preview to the front
+ if( !LLPreview::show( item->getUUID() ) )
+ {
+ // There isn't one, so make a new preview
+ S32 left, top;
+ gFloaterView->getNewFloaterPosition(&left, &top);
+ LLRect rect = gSavedSettings.getRect("PreviewLandmarkRect");
+ rect.translate( left - rect.mLeft, top - rect.mTop );
+
+ LLPreviewLandmark* preview = new LLPreviewLandmark(
+ "preview landmark",
+ rect,
+ item->getName(),
+ item->getUUID());
+ preview->setAuxItem( item );
+ preview->addCopyToInvButton();
+ preview->open();
+ }
+}*/
+
+void LLViewerTextEditor::openEmbeddedNotecard( LLInventoryItem* item, BOOL saved )
+{
+ if (saved)
+ {
+ // Copy to inventory
+ copyInventory(item);
+ }
+ else
+ {
+ LLNotecardCopyInfo *info = new LLNotecardCopyInfo(this, item);
+ gViewerWindow->alertXml("ConfirmNotecardSave",
+ LLViewerTextEditor::onNotecardDialog, (void*)info);
+ }
+}
+
+// static
+void LLViewerTextEditor::onNotecardDialog( S32 option, void* userdata )
+{
+ LLNotecardCopyInfo *info = (LLNotecardCopyInfo *)userdata;
+ if( option == 0 )
+ {
+ // itemptr is deleted by LLPreview::save
+ LLPointer<LLInventoryItem>* itemptr = new LLPointer<LLInventoryItem>(info->mItem);
+ LLPreview::save( info->mTextEd->mNotecardInventoryID, itemptr);
+ }
+}
+
+
+void LLViewerTextEditor::showLandmarkDialog( LLInventoryItem* item )
+{
+ LLNotecardCopyInfo *info = new LLNotecardCopyInfo(this, item);
+ gViewerWindow->alertXml("ConfirmLandmarkCopy",
+ LLViewerTextEditor::onLandmarkDialog, (void*)info);
+}
+
+// static
+void LLViewerTextEditor::onLandmarkDialog( S32 option, void* userdata )
+{
+ LLNotecardCopyInfo *info = (LLNotecardCopyInfo *)userdata;
+ if( option == 0 )
+ {
+ // Copy to inventory
+ info->mTextEd->copyInventory(info->mItem);
+ /*
+ * XXXPAM
+ *
+ * Yes, this is broken. We don't show the map yet.
+ *
+ LLInventoryItem* orig_item = (LLInventoryItem*)userdata;
+
+ // Copy to inventory
+ LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem;
+ cloneInventoryItemToViewer(orig_item, new_item);
+ U32 flags = new_item->getFlags();
+ flags &= ~LLInventoryItem::II_FLAGS_LANDMARK_VISITED;
+ new_item->setFlags(flags);
+ new_item->updateServer(TRUE);
+ gInventory.updateItem(new_item);
+ gInventory.notifyObservers();
+
+ LLInventoryView* view = LLInventoryView::getActiveInventory();
+ if(view)
+ {
+ view->getPanel()->setSelection(new_item->getUUID(), TAKE_FOCUS_NO);
+ }
+
+ if( (0 == option) && gFloaterWorldMap )
+ {
+ // Note: there's a minor race condition here.
+ // If the user immediately tries to teleport to the landmark, the dataserver may
+ // not yet know that the user has the landmark in his inventory and so may
+ // disallow the teleport. However, the user will need to be pretty fast to make
+ // this happen, and, if it does, they haven't lost anything. Once the dataserver
+ // knows about the new item, the user will be able to teleport to it successfully.
+ gFloaterWorldMap->trackLandmark(new_item->getUUID());
+ LLFloaterWorldMap::show(NULL, TRUE);
+ }*/
+ }
+ delete info;
+}
+
+
+void LLViewerTextEditor::showCopyToInvDialog( LLInventoryItem* item )
+{
+ LLNotecardCopyInfo *info = new LLNotecardCopyInfo(this, item);
+ gViewerWindow->alertXml( "ConfirmItemCopy",
+ LLViewerTextEditor::onCopyToInvDialog, (void*)info);
+}
+
+// static
+void LLViewerTextEditor::onCopyToInvDialog( S32 option, void* userdata )
+{
+ LLNotecardCopyInfo *info = (LLNotecardCopyInfo *)userdata;
+ if( 0 == option )
+ {
+ info->mTextEd->copyInventory(info->mItem);
+ }
+ delete info;
+}
+
+
+
+// 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(MAX_NOTECARD_SIZE);
+ bool success = nc.importStream(str);
+ if (success)
+ {
+ const std::vector<LLPointer<LLInventoryItem> >& items = nc.getItems();
+ mEmbeddedItemList->addItems(items);
+ // Actually set the text
+ if (mAllowEmbeddedItems)
+ {
+ if (nc.getVersion() == 1)
+ setASCIIEmbeddedText( nc.getText() );
+ else
+ setEmbeddedText( nc.getText() );
+ }
+ else
+ {
+ setText( nc.getText() );
+ }
+ }
+ return success;
+}
+
+void LLViewerTextEditor::copyInventory(LLInventoryItem* item)
+{
+ copy_inventory_from_notecard(mObjectID,
+ mNotecardInventoryID,
+ item);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+BOOL LLViewerTextEditor::importBuffer( const LLString& buffer )
+{
+ LLMemoryStream str((U8*)buffer.c_str(), buffer.length());
+ return importStream(str);
+}
+
+BOOL LLViewerTextEditor::exportBuffer( LLString& buffer )
+{
+ LLNotecard nc(MAX_NOTECARD_SIZE);
+
+ std::vector<LLPointer<LLInventoryItem> > embedded_items;
+ mEmbeddedItemList->getEmbeddedItemList(embedded_items);
+
+ nc.setItems(embedded_items);
+ nc.setText(getEmbeddedText());
+
+ std::stringstream out_stream;
+ nc.exportStream(out_stream);
+
+ buffer = out_stream.str();
+
+ return TRUE;
+}
+
+LLView* LLViewerTextEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory)
+{
+ LLString name("text_editor");
+ node->getAttributeString("name", name);
+
+ LLRect rect;
+ createRect(node, rect, parent, LLRect());
+
+ U32 max_text_length = 255;
+ node->getAttributeU32("max_length", max_text_length);
+
+ BOOL allow_embedded_items = FALSE;
+ node->getAttributeBOOL("embedded_items", allow_embedded_items);
+
+ LLFontGL* font = LLView::selectFont(node);
+
+ LLString text = node->getValue();
+
+ if (text.size() > max_text_length)
+ {
+ // Erase everything from max_text_length on.
+ text.erase(max_text_length);
+ }
+
+ LLViewerTextEditor* text_editor = new LLViewerTextEditor(name,
+ rect,
+ max_text_length,
+ text,
+ font,
+ allow_embedded_items);
+
+ BOOL ignore_tabs = text_editor->mTabToNextField;
+ node->getAttributeBOOL("ignore_tab", ignore_tabs);
+
+ text_editor->setTabToNextField(ignore_tabs);
+
+
+ text_editor->setTextEditorParameters(node);
+
+ BOOL hide_scrollbar = FALSE;
+ node->getAttributeBOOL("hide_scrollbar",hide_scrollbar);
+ text_editor->setHideScrollbarForShortDocs(hide_scrollbar);
+
+ text_editor->initFromXML(node, parent);
+
+ return text_editor;
+}