Code owners
Assign users and groups as approvers for specific file changes. Learn more.
llviewertexteditor.cpp 31.85 KiB
/**
* @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 "llviewertexteditor.h"
#include "llagent.h"
#include "llaudioengine.h"
#include "llavataractions.h"
#include "llfloaterchat.h"
#include "llfloaterreg.h"
#include "llfloaterworldmap.h"
#include "llfocusmgr.h"
#include "llinventory.h"
#include "llinventorybridge.h"
#include "llinventorymodel.h"
#include "lllandmark.h"
#include "lllandmarkactions.h"
#include "lllandmarklist.h"
#include "llmemorystream.h"
#include "llmenugl.h"
#include "llnotecard.h"
#include "llnotify.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<LLViewerTextEditor> 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;
LLSideTray::getInstance()->showPanel("panel_places", key);
}
static void processForeignLandmark(LLLandmark* landmark,
const LLUUID& object_id, const LLUUID& notecard_inventory_id,
LLInventoryItem* item)
{
LLVector3d global_pos;
landmark->getGlobalPos(global_pos);
LLViewerInventoryItem* agent_lanmark =
LLLandmarkActions::findLandmarkForGlobalPos(global_pos);
if (agent_lanmark)
{
showInfo(agent_lanmark->getUUID());
}
else
{
LLPointer<LLEmbeddedLandmarkCopied> cb = new LLEmbeddedLandmarkCopied();
copy_inventory_from_notecard(object_id, notecard_inventory_id, item, 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<LLInventoryItem> 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*/ const LLStyleSP getStyle() const { return mStyle; }
private:
LLUIImagePtr mImage;
LLWString mLabel;
LLStyleSP mStyle;
std::string mToolTip;
LLPointer<LLInventoryItem> 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<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
private:
struct embedded_info_t
{
LLPointer<LLInventoryItem> mItem;
BOOL mSaved;
};
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();
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<llwchar> 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<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;
}
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_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 = "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;
default: llassert(0);
}
return LLUI::getUIImage(img_name);
}
return LLUIImagePtr();
}
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 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<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 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];
}
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(
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:
{
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::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();
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->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 = getWText()[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<LLPreviewTexture>("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;
LLLandmark* landmark = gLandmarkList.getAsset(item->getAssetUUID(),
boost::bind(&LLEmbeddedLandmarkCopied::processForeignLandmark, _1, mObjectID, mNotecardInventoryID, item));
if (landmark)
{
LLEmbeddedLandmarkCopied::processForeignLandmark(landmark, mObjectID,
mNotecardInventoryID, 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<LLPreviewNotecard>("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 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<LLPointer<LLInventoryItem> >& 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<LLPointer<LLInventoryItem> > embedded_items;
mEmbeddedItemList->getEmbeddedItemList(embedded_items);
nc.setItems(embedded_items);
std::stringstream out_stream;
nc.exportStream(out_stream);
buffer = out_stream.str();
return TRUE;
}