/**
 * @file llviewertexteditor.cpp
 * @brief Text editor widget to let users enter a multi-line document.
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "llviewerprecompiledheaders.h"

#include "llviewertexteditor.h"

#include "llagent.h"
#include "llaudioengine.h"
#include "llavataractions.h"
#include "llenvironment.h"
#include "llfloaterreg.h"
#include "llfloatersidepanelcontainer.h"
#include "llfloaterworldmap.h"
#include "llfocusmgr.h"
#include "llinspecttexture.h"
#include "llinventorybridge.h"
#include "llinventorydefines.h"
#include "llinventorymodel.h"
#include "lllandmark.h"
#include "lllandmarkactions.h"
#include "lllandmarklist.h"
#include "llmaterialeditor.h"
#include "llmemorystream.h"
#include "llmenugl.h"
#include "llnotecard.h"
#include "llnotificationsutil.h"
#include "llpanelplaces.h"
#include "llpreview.h"
#include "llpreviewnotecard.h"
#include "llpreviewtexture.h"
#include "llscrollbar.h"
#include "llscrollcontainer.h"
#include "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;
        LLFloaterSidePanelContainer::showPanel("places", key);
    }
    static void processForeignLandmark(LLLandmark* landmark,
            const LLUUID& object_id, const LLUUID& notecard_inventory_id,
            LLPointer<LLInventoryItem> item_ptr)
    {
        LLVector3d global_pos;
        landmark->getGlobalPos(global_pos);
        LLViewerInventoryItem* agent_landmark =
                LLLandmarkActions::findLandmarkForGlobalPos(global_pos);

        if (agent_landmark)
        {
            showInfo(agent_landmark->getUUID());
        }
        else
        {
            if (item_ptr.isNull())
            {
                // check to prevent a crash. See EXT-8459.
                LL_WARNS() << "Passed handle contains a dead inventory item. Most likely notecard has been closed and embedded item was destroyed." << LL_ENDL;
            }
            else
            {
                LLInventoryItem* item = item_ptr.get();
                LLPointer<LLEmbeddedLandmarkCopied> cb = new LLEmbeddedLandmarkCopied();
                copy_inventory_from_notecard(get_folder_by_itemtype(item),
                                             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)
        {
            LL_WARNS() << "Item add reported, but not found in inventory!: " << inv_item << LL_ENDL;
        }
        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)
    {

        mStyle = new LLStyle(LLStyle::Params().font(LLFontGL::getFontSansSerif()));
        mToolTip = inv_item->getName() + '\n' + inv_item->getDescription();
    }

    /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const
    {
        if (num_chars == 0)
        {
            width = 0;
            height = 0;
        }
        else
        {
            width = EMBEDDED_ITEM_LABEL_PADDING + mImage->getWidth() + mStyle->getFont()->getWidthF32(mLabel.c_str());
            height = llmax(mImage->getHeight(), mStyle->getFont()->getLineHeight());
        }
        return false;
    }

    /*virtual*/ S32             getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) 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 LLRectf& draw_rect)
    {
        LLRectf image_rect = draw_rect;
        image_rect.mRight = image_rect.mLeft + mImage->getWidth();
        image_rect.mTop = image_rect.mBottom + mImage->getHeight();
        mImage->draw(LLRect((S32)image_rect.mLeft, (S32)image_rect.mTop, (S32)image_rect.mRight, (S32)image_rect.mBottom));

        static const LLUIColor embedded_item_readonly_col = LLUIColorTable::instance().getColor("TextEmbeddedItemReadOnlyColor");
        static const LLUIColor embedded_item_col = LLUIColorTable::instance().getColor("TextEmbeddedItemColor");
        const LLColor4& color = mEditor.getReadOnly() ? embedded_item_readonly_col : embedded_item_col;

        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, static_cast<S32>(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::getInstance()->getWindow()->setCursor(UI_CURSOR_HAND);
        return true;
    }
    virtual bool                handleToolTip(S32 x, S32 y, MASK mask )
    {
        if (mItem->getThumbnailUUID().notNull())
        {
            LLSD params;
            params["inv_type"] = mItem->getInventoryType();
            params["thumbnail_id"] = mItem->getThumbnailUUID();
            params["asset_id"] = mItem->getAssetUUID();

            LLToolTipMgr::instance().show(LLToolTip::Params()
                    .message(mToolTip)
                    .create_callback(boost::bind(&LLInspectTextureUtil::createInventoryToolTip, _1))
                    .create_params(params));

            return true;
        }

        if (!mToolTip.empty())
        {
            LLToolTipMgr::instance().show(mToolTip);
            return true;
        }
        return false;
    }

    /*virtual*/ LLStyleConstSP      getStyle() const { return mStyle; }

private:
    LLUIImagePtr    mImage;
    LLWString       mLabel;
    LLStyleSP       mStyle;
    std::string     mToolTip;
    LLPointer<LLInventoryItem> mItem;
    LLTextEditor&   mEditor;
};



////////////////////////////////////////////////////////////
// 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 LLPointer<LLInventoryItem> getEmbeddedItemPtr(llwchar ext_char); // returns pointer to item from static list
    static bool getEmbeddedItemSaved(llwchar ext_char); // returns whether item from static list is saved

private:

    struct embedded_info_t
    {
        LLPointer<LLInventoryItem> mItemPtr;
        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].mItemPtr = item;
    sEntries[wc_emb].mSaved = !is_new;
    *ext_char = wc_emb;
    mEmbeddedUsedChars.insert(wc_emb);
    return true;
}

// Removes an entry (all entries are unique)
bool LLEmbeddedItems::removeEmbeddedItem( llwchar ext_char )
{
    mEmbeddedUsedChars.erase(ext_char);
    item_map_t::iterator iter = sEntries.find(ext_char);
    if (iter != sEntries.end())
    {
        sEntries.erase(ext_char);
        sFreeEntries.push(ext_char);
        return true;
    }
    return false;
}

// static
LLPointer<LLInventoryItem> LLEmbeddedItems::getEmbeddedItemPtr(llwchar ext_char)
{
    if( ext_char >= LLTextEditor::FIRST_EMBEDDED_CHAR && ext_char <= LLTextEditor::LAST_EMBEDDED_CHAR )
    {
        item_map_t::iterator iter = sEntries.find(ext_char);
        if (iter != sEntries.end())
        {
            return iter->second.mItemPtr;
        }
    }
    return NULL;
}

// static
bool LLEmbeddedItems::getEmbeddedItemSaved(llwchar ext_char)
{
    if( ext_char >= LLTextEditor::FIRST_EMBEDDED_CHAR && ext_char <= LLTextEditor::LAST_EMBEDDED_CHAR )
    {
        item_map_t::iterator iter = sEntries.find(ext_char);
        if (iter != sEntries.end())
        {
            return iter->second.mSaved;
        }
    }
    return false;
}

llwchar LLEmbeddedItems::getEmbeddedCharFromIndex(S32 index)
{
    if (index >= (S32)mEmbeddedIndexedChars.size())
    {
        LL_WARNS() << "No item for embedded char " << index << " using LL_UNKNOWN_CHAR" << LL_ENDL;
        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
    {
        LL_WARNS() << "Embedded char " << (int)wch << " not found, using 0" << LL_ENDL;
        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 = getEmbeddedItemPtr(ext_char);
    if (item)
    {
        const char* img_name = "";
        switch( item->getType() )
        {
            case LLAssetType::AT_TEXTURE:
                if(item->getInventoryType() == LLInventoryType::IT_SNAPSHOT)
                {
                    img_name = "Inv_Snapshot";
                }
                else
                {
                    img_name = "Inv_Texture";
                }

                break;
            case LLAssetType::AT_SOUND:         img_name = "Inv_Sound";     break;
            case LLAssetType::AT_CLOTHING:      img_name = "Inv_Clothing";  break;
            case LLAssetType::AT_OBJECT:
                img_name = LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS & item->getFlags() ?
                    "Inv_Object_Multi" : "Inv_Object";
                break;
            case LLAssetType::AT_CALLINGCARD:   img_name = "Inv_CallingCard"; break;
            case LLAssetType::AT_LANDMARK:      img_name = "Inv_Landmark";  break;
            case LLAssetType::AT_NOTECARD:      img_name = "Inv_Notecard";  break;
            case LLAssetType::AT_LSL_TEXT:      img_name = "Inv_Script";    break;
            case LLAssetType::AT_BODYPART:      img_name = "Inv_Skin";      break;
            case LLAssetType::AT_ANIMATION:     img_name = "Inv_Animation"; break;
            case LLAssetType::AT_GESTURE:       img_name = "Inv_Gesture";   break;
            case LLAssetType::AT_MESH:          img_name = "Inv_Mesh";      break;
            case LLAssetType::AT_SETTINGS:      img_name = "Inv_Settings"; break;
            case LLAssetType::AT_MATERIAL:      img_name = "Inv_Material"; break;
            default:                            img_name = "Inv_Invalid";  break; // use the Inv_Invalid icon for undefined object types (see MAINT-3981)

        }

        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 = getEmbeddedItemPtr(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();
}

void LLViewerTextEditor::onVisibilityChange( bool new_visibility )
{
    LLUICtrl::onVisibilityChange(new_visibility);
}

bool LLViewerTextEditor::handleMouseDown(S32 x, S32 y, MASK mask)
{
    bool    handled = false;

    // Let scrollbar have first dibs
    handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL;

    if( !handled)
    {
        if( allowsEmbeddedItems() )
        {
            setCursorAtLocalPos( x, y, false );
            llwchar wc = 0;
            if (mCursorPos < getLength())
            {
                wc = getWText()[mCursorPos];
            }
            LLPointer<LLInventoryItem> item_at_pos = LLEmbeddedItems::getEmbeddedItemPtr(wc);
            if (item_at_pos)
            {
                mDragItem = item_at_pos;
                mDragItemChar = wc;
                mDragItemSaved = LLEmbeddedItems::getEmbeddedItemSaved(wc);
                gFocusMgr.setMouseCapture( this );
                mMouseDownX = x;
                mMouseDownY = y;
                S32 screen_x;
                S32 screen_y;
                localPointToScreen(x, y, &screen_x, &screen_y );
                LLToolDragAndDrop::getInstance()->setDragStart( screen_x, screen_y );

                if (hasTabStop())
                {
                    setFocus( true );
                }

                handled = true;
            }
            else
            {
                mDragItem = NULL;
            }
        }

        if (!handled)
        {
            handled = LLTextEditor::handleMouseDown(x, y, mask);
        }
    }

    return handled;
}


bool LLViewerTextEditor::handleHover(S32 x, S32 y, MASK mask)
{
    bool handled = LLTextEditor::handleHover(x, y, mask);

    if(hasMouseCapture() && mDragItem)
    {
        S32 screen_x;
        S32 screen_y;
        localPointToScreen(x, y, &screen_x, &screen_y );

        mScroller->autoScroll(x, y);

        if( LLToolDragAndDrop::getInstance()->isOverThreshold( screen_x, screen_y ) )
        {
            LLToolDragAndDrop::getInstance()->beginDrag(
                LLViewerAssetType::lookupDragAndDropType( mDragItem->getType() ),
                mDragItem->getUUID(),
                LLToolDragAndDrop::SOURCE_NOTECARD,
                mPreviewID, mObjectID);
            return LLToolDragAndDrop::getInstance()->handleHover( x, y, mask );
        }
        getWindow()->setCursor(UI_CURSOR_HAND);
        handled = true;
    }

    return handled;
}


bool LLViewerTextEditor::handleMouseUp(S32 x, S32 y, MASK mask)
{
    bool handled = false;

    if( hasMouseCapture() )
    {
        if (mDragItem)
        {
            // mouse down was on an item
            S32 dx = x - mMouseDownX;
            S32 dy = y - mMouseDownY;
            if (-2 < dx && dx < 2 && -2 < dy && dy < 2)
            {
                if(mDragItemSaved)
                {
                    openEmbeddedItem(mDragItem, mDragItemChar);
                }
                else
                {
                    showUnsavedAlertDialog(mDragItem);
                }
            }
        }
        mDragItem = NULL;
    }

    handled = LLTextEditor::handleMouseUp(x,y,mask);

    return handled;
}

bool LLViewerTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask)
{
    bool    handled = false;

    // let scrollbar have first dibs
    handled = LLView::childrenHandleDoubleClick(x, y, mask) != NULL;

    if( !handled)
    {
        if( allowsEmbeddedItems() )
        {
            S32 doc_index = getDocIndexFromLocalCoord(x, y, false);
            llwchar doc_char = getWText()[doc_index];
            if (mEmbeddedItemList->hasEmbeddedItem(doc_char))
            {
                if( openEmbeddedItemAtPos( doc_index ))
                {
                    deselect();
                    setFocus( false );
                    return true;
                }
            }
        }
        handled = LLTextEditor::handleDoubleClick(x, y, mask);
    }
    return handled;
}


// virtual
bool LLViewerTextEditor::handleDragAndDrop(S32 x, S32 y, MASK mask,
                      bool drop, EDragAndDropType cargo_type, void *cargo_data,
                      EAcceptance *accept,
                      std::string& tooltip_msg)
{
    bool handled = false;

    LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource();
    if (LLToolDragAndDrop::SOURCE_NOTECARD == source)
    {
        // We currently do not handle dragging items from one notecard to another
        // since items in a notecard must be in Inventory to be verified. See DEV-2891.
        return false;
    }

    if (getEnabled() && acceptsTextInput())
    {
        bool supported = false;
        switch( cargo_type )
        {
            case DAD_SETTINGS:
            {
                supported = LLEnvironment::instance().isExtendedEnvironmentEnabled();
                if (!supported && tooltip_msg.empty())
                {
                    tooltip_msg.assign(LLTrans::getString("TooltipNotecardNotAllowedTypeDrop"));
                }
                break;
            }
            case DAD_CALLINGCARD:
            case DAD_TEXTURE:
            case DAD_SOUND:
            case DAD_LANDMARK:
            case DAD_SCRIPT:
            case DAD_CLOTHING:
            case DAD_OBJECT:
            case DAD_NOTECARD:
            case DAD_BODYPART:
            case DAD_ANIMATION:
            case DAD_GESTURE:
            case DAD_MESH:
            case DAD_MATERIAL:
            {
                supported = true;
                break;
            }

        default:
            supported = false;
            break;
        }

        LLInventoryItem *item = (LLInventoryItem *)cargo_data;
        if (item && allowsEmbeddedItems() && supported)
        {
            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())
                {
                    tooltip_msg.assign(LLTrans::getString("TooltipNotecardOwnerRestrictedDrop"));
                }
            }
        }
        else
        {
            *accept = ACCEPT_NO;
        }
    }
    else
    {
        // Not enabled
        *accept = ACCEPT_NO;
    }

    handled = true;
    LL_DEBUGS("UserInput") << "dragAndDrop handled by LLViewerTextEditor " << getName() << LL_ENDL;

    return handled;
}

void LLViewerTextEditor::setASCIIEmbeddedText(const std::string& instr)
{
    LLWString wtext;
    const U8* buffer = (U8*)(instr.c_str());
    while (*buffer)
    {
        llwchar wch;
        U8 c = *buffer++;
        if (c >= 0x80)
        {
            S32 index = (S32)(c - 0x80);
            wch = mEmbeddedItemList->getEmbeddedCharFromIndex(index);
        }
        else
        {
            wch = (llwchar)c;
        }
        wtext.push_back(wch);
    }
    setWText(wtext);
}

void LLViewerTextEditor::setEmbeddedText(const std::string& instr)
{
    LLWString wtext = utf8str_to_wstring(instr);
    for (S32 i=0; i<(S32)wtext.size(); i++)
    {
        llwchar wch = wtext[i];
        if( wch >= FIRST_EMBEDDED_CHAR && wch <= LAST_EMBEDDED_CHAR )
        {
            S32 index = wch - FIRST_EMBEDDED_CHAR;
            wtext[i] = mEmbeddedItemList->getEmbeddedCharFromIndex(index);
        }
    }
    setWText(wtext);
}

std::string LLViewerTextEditor::getEmbeddedText()
{
#if 1
    // New version (Version 2)
    mEmbeddedItemList->copyUsedCharsToIndexed();
    LLWString outtextw;
    for (S32 i=0; i<(S32)getWText().size(); i++)
    {
        llwchar wch = getWText()[i];
        if( wch >= FIRST_EMBEDDED_CHAR && wch <= LAST_EMBEDDED_CHAR )
        {
            S32 index = mEmbeddedItemList->getIndexFromEmbeddedChar(wch);
            wch = FIRST_EMBEDDED_CHAR + index;
        }
        outtextw.push_back(wch);
    }
    std::string outtext = wstring_to_utf8str(outtextw);
    return outtext;
#else
    // Old version (Version 1)
    mEmbeddedItemList->copyUsedCharsToIndexed();
    std::string outtext;
    for (S32 i=0; i<(S32)mWText.size(); i++)
    {
        llwchar wch = mWText[i];
        if( wch >= FIRST_EMBEDDED_CHAR && wch <= LAST_EMBEDDED_CHAR )
        {
            S32 index = mEmbeddedItemList->getIndexFromEmbeddedChar(wch);
            wch = 0x80 | index % 128;
        }
        else if (wch >= 0x80)
        {
            wch = LL_UNKNOWN_CHAR;
        }
        outtext.push_back((U8)wch);
    }
    return outtext;
#endif
}

std::string LLViewerTextEditor::appendTime(bool prepend_newline)
{
    time_t utc_time;
    utc_time = time_corrected();
    std::string timeStr ="[["+ LLTrans::getString("TimeHour")+"]:["
        +LLTrans::getString("TimeMin")+"]] ";

    LLSD substitution;

    substitution["datetime"] = (S32) utc_time;
    LLStringUtil::format (timeStr, substitution);
    appendText(timeStr, prepend_newline, LLStyle::Params().color(LLColor4::grey));
    blockUndo();

    return timeStr;
}

//----------------------------------------------------------------------------
//----------------------------------------------------------------------------

llwchar LLViewerTextEditor::pasteEmbeddedItem(llwchar ext_char)
{
    if (mEmbeddedItemList->hasEmbeddedItem(ext_char))
    {
        return ext_char; // already exists in my list
    }
    LLInventoryItem* item = LLEmbeddedItems::getEmbeddedItemPtr(ext_char);
    if (item)
    {
        // Add item to my list and return new llwchar associated with it
        llwchar new_wc;
        if (mEmbeddedItemList->insertEmbeddedItem( item, &new_wc, true ))
        {
            return new_wc;
        }
    }
    return LL_UNKNOWN_CHAR; // item not found or list full
}

void LLViewerTextEditor::onValueChange(S32 start, S32 end)
{
    updateSegments();
    updateLinkSegments();
    findEmbeddedItemSegments(start, end);
}

void LLViewerTextEditor::findEmbeddedItemSegments(S32 start, S32 end)
{
    LLWString text = getWText();

    // Start with i just after the first embedded item
    for(S32 idx = start; idx < end; idx++ )
    {
        llwchar embedded_char = text[idx];
        if( embedded_char >= FIRST_EMBEDDED_CHAR
            && embedded_char <= LAST_EMBEDDED_CHAR
            && mEmbeddedItemList->hasEmbeddedItem(embedded_char) )
        {
            LLInventoryItem* itemp = mEmbeddedItemList->getEmbeddedItemPtr(embedded_char);
            LLUIImagePtr image = mEmbeddedItemList->getItemImage(embedded_char);
            insertSegment(new LLEmbeddedItemSegment(idx, image, itemp, *this));
        }
    }
}

bool LLViewerTextEditor::openEmbeddedItemAtPos(S32 pos)
{
    if( pos < getLength())
    {
        llwchar wc = getWText()[pos];
        LLPointer<LLInventoryItem> item = LLEmbeddedItems::getEmbeddedItemPtr( wc );
        if( item )
        {
            bool saved = LLEmbeddedItems::getEmbeddedItemSaved( wc );
            if (saved)
            {
                return openEmbeddedItem(item, wc);
            }
            else
            {
                showUnsavedAlertDialog(item);
            }
        }
    }
    return false;
}


bool LLViewerTextEditor::openEmbeddedItem(LLPointer<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_LANDMARK:
            openEmbeddedLandmark( item, wc );
            return true;

        case LLAssetType::AT_CALLINGCARD:
            openEmbeddedCallingcard( item, wc );
            return true;
        case LLAssetType::AT_SETTINGS:
            openEmbeddedSetting(item, wc);
            return true;
        case LLAssetType::AT_MATERIAL:
            openEmbeddedGLTFMaterial(item, wc);
            return true;
        case LLAssetType::AT_NOTECARD:
        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);
        if (preview->hasString("Title"))
        {
            LLStringUtil::format_map_t args;
            args["[NAME]"] = item->getName();
            LLUIString title = preview->getString("Title", args);
            preview->setTitle(title.getString());
        }
        preview->getChild<LLUICtrl>("desc")->setValue(item->getDescription());
    }
}

void LLViewerTextEditor::openEmbeddedSound( LLInventoryItem* item, llwchar wc )
{
    // Play sound locally
    LLVector3d lpos_global = gAgent.getPositionGlobal();
    const F32 SOUND_GAIN = 1.0f;
    if(gAudiop)
    {
        gAudiop->triggerSound(item->getAssetUUID(), gAgentID, SOUND_GAIN, LLAudioEngine::AUDIO_TYPE_UI, lpos_global);
    }
    showCopyToInvDialog( item, wc );
}


void LLViewerTextEditor::openEmbeddedLandmark( LLPointer<LLInventoryItem> item_ptr, llwchar wc )
{
    if (item_ptr.isNull())
        return;

    LLLandmark* landmark = gLandmarkList.getAsset(item_ptr->getAssetUUID(),
            boost::bind(&LLEmbeddedLandmarkCopied::processForeignLandmark, _1, mObjectID, mNotecardInventoryID, item_ptr));
    if (landmark)
    {
        LLEmbeddedLandmarkCopied::processForeignLandmark(landmark, mObjectID,
                mNotecardInventoryID, item_ptr);
    }
}

void LLViewerTextEditor::openEmbeddedCallingcard( LLInventoryItem* item, llwchar wc )
{
    if (item && !item->getDescription().empty())
    {
        LLAvatarActions::showProfile(LLUUID(item->getDescription()));
    }
    else if (item && !item->getCreatorUUID().isNull())
    {
        LLAvatarActions::showProfile(item->getCreatorUUID());
    }
}

void LLViewerTextEditor::openEmbeddedSetting(LLInventoryItem* item, llwchar wc)
{
    if (LLEnvironment::instance().isInventoryEnabled())
    {
        showCopyToInvDialog(item, wc);
    }
    else
    {
        LLNotificationsUtil::add("NoEnvironmentSettings");
    }
}

void LLViewerTextEditor::openEmbeddedGLTFMaterial(LLInventoryItem* item, llwchar wc)
{
    if (!item)
    {
        return;
    }

    LLSD floater_key;
    floater_key["objectid"] = mObjectID;
    floater_key["notecardid"] = mNotecardInventoryID;
    LLMaterialEditor* preview = LLFloaterReg::getTypedInstance<LLMaterialEditor>("material_editor", floater_key);
    if (preview)
    {
        preview->setAuxItem(item);
        preview->setNotecardInfo(mNotecardInventoryID, mObjectID);
        preview->openFloater(floater_key);
        preview->setFocus(true);
    }
}

void LLViewerTextEditor::showUnsavedAlertDialog( LLInventoryItem* item )
{
    LLSD payload;
    payload["item_id"] = item->getUUID();
    payload["notecard_id"] = mNotecardInventoryID;
    LLNotificationsUtil::add( "ConfirmNotecardSave", LLSD(), payload, LLViewerTextEditor::onNotecardDialog);
}

// static
bool LLViewerTextEditor::onNotecardDialog(const LLSD& notification, const LLSD& response )
{
    S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
    if( option == 0 )
    {
        LLPreviewNotecard* preview = LLFloaterReg::findTypedInstance<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);
    LLNotificationsUtil::add( "ConfirmItemCopy", LLSD(), payload,
        boost::bind(&LLViewerTextEditor::onCopyToInvDialog, this, _1, _2));
}

bool LLViewerTextEditor::onCopyToInvDialog(const LLSD& notification, const LLSD& response)
{
    S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
    if( 0 == option )
    {
        llwchar wc = llwchar(notification["payload"]["item_wc"].asInteger());
        LLInventoryItem* itemp = LLEmbeddedItems::getEmbeddedItemPtr(wc);
        if (itemp)
            copyInventory(itemp);
    }
    return false;
}



// Returns change in number of characters in mWText
S32 LLViewerTextEditor::insertEmbeddedItem( S32 pos, LLInventoryItem* item )
{
    return execute( new TextCmdInsertEmbeddedItem( pos, item ) );
}

bool LLViewerTextEditor::importStream(std::istream& str)
{
    LLNotecard nc(LLNotecard::MAX_SIZE);
    bool success = nc.importStream(str);
    if (success)
    {
        mEmbeddedItemList->clear();
        const std::vector<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(LLUUID::null,  // Don't specify a destination -- let the sim do that
                                 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;
}