/**
 * @file llfloatergesture.cpp
 * @brief Read-only list of gestures from your inventory.
 *
 * $LicenseInfo:firstyear=2002&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 "llfloatergesture.h"

#include "llinventory.h"
#include "llinventorybridge.h"
#include "llinventoryfunctions.h"
#include "llinventorymodel.h"
#include "llclipboard.h"

#include "llagent.h"
#include "llappearancemgr.h"
#include "llclipboard.h"
#include "llgesturemgr.h"
#include "llkeyboard.h"
#include "llmenugl.h"
#include "llmultigesture.h"
#include "llnotificationsutil.h"
#include "llpreviewgesture.h"
#include "llscrolllistctrl.h"
#include "lltrans.h"
#include "llviewergesture.h"
#include "llviewermenu.h"
#include "llviewerinventory.h"
#include "llviewercontrol.h"
#include "llfloaterperms.h"

bool item_name_precedes( LLInventoryItem* a, LLInventoryItem* b )
{
    return LLStringUtil::precedesDict( a->getName(), b->getName() );
}

class LLFloaterGestureObserver : public LLGestureManagerObserver
{
public:
    LLFloaterGestureObserver(LLFloaterGesture* floater) : mFloater(floater) {}
    virtual ~LLFloaterGestureObserver() {}
    virtual void changed() { mFloater->refreshAll(); }

private:
    LLFloaterGesture* mFloater;
};
//-----------------------------
// GestureCallback
//-----------------------------

class GestureShowCallback : public LLInventoryCallback
{
public:
    void fire(const LLUUID &inv_item)
    {
        LLPreviewGesture::show(inv_item, LLUUID::null);

        LLInventoryItem* item = gInventory.getItem(inv_item);
        if (item)
        {
            LLPermissions perm = item->getPermissions();
            perm.setMaskNext(LLFloaterPerms::getNextOwnerPerms("Gestures"));
            perm.setMaskEveryone(LLFloaterPerms::getEveryonePerms("Gestures"));
            perm.setMaskGroup(LLFloaterPerms::getGroupPerms("Gestures"));
            item->setPermissions(perm);
            item->updateServer(false);
        }
    }
};

class GestureCopiedCallback : public LLInventoryCallback
{
private:
    LLFloaterGesture* mFloater;

public:
    GestureCopiedCallback(LLFloaterGesture* floater): mFloater(floater)
    {}
    void fire(const LLUUID &inv_item)
    {
        if(mFloater)
        {
            mFloater->addGesture(inv_item,NULL,mFloater->getChild<LLScrollListCtrl>("gesture_list"));

            // EXP-1909 (Pasted gesture displayed twice)
            // The problem is that addGesture is called here for the second time for the same item (which is copied)
            // First time addGesture is called from LLFloaterGestureObserver::changed(), which is a callback for inventory
            // change. So we need to refresh the gesture list to avoid duplicates.
            mFloater->refreshAll();
        }
    }
};

//---------------------------------------------------------------------------
// LLFloaterGesture
//---------------------------------------------------------------------------
LLFloaterGesture::LLFloaterGesture(const LLSD& key)
    : LLFloater(key)
{
    mObserver = new LLFloaterGestureObserver(this);
    LLGestureMgr::instance().addObserver(mObserver);

    mCommitCallbackRegistrar.add("Gesture.Action.ToggleActiveState", boost::bind(&LLFloaterGesture::onActivateBtnClick, this));
    mCommitCallbackRegistrar.add("Gesture.Action.ShowPreview", boost::bind(&LLFloaterGesture::onClickEdit, this));
    mCommitCallbackRegistrar.add("Gesture.Action.CopyPaste", boost::bind(&LLFloaterGesture::onCopyPasteAction, this, _2));
    mCommitCallbackRegistrar.add("Gesture.Action.SaveToCOF", boost::bind(&LLFloaterGesture::addToCurrentOutFit, this));
    mCommitCallbackRegistrar.add("Gesture.Action.Rename", boost::bind(&LLFloaterGesture::onRenameSelected, this));

    mEnableCallbackRegistrar.add("Gesture.EnableAction", boost::bind(&LLFloaterGesture::isActionEnabled, this, _2));
}

void LLFloaterGesture::done()
{
    //this method can be called twice: for GestureFolder and once after loading all sudir of GestureFolder
    if (gInventory.isCategoryComplete(mGestureFolderID))
    {
        LL_DEBUGS("Gesture")<< "mGestureFolderID loaded" << LL_ENDL;
        // we load only gesture folder without childred.
        LLInventoryModel::cat_array_t* categories;
        LLInventoryModel::item_array_t* items;
        uuid_vec_t unloaded_folders;
        LL_DEBUGS("Gesture")<< "Get subdirs of Gesture Folder...." << LL_ENDL;
        gInventory.getDirectDescendentsOf(mGestureFolderID, categories, items);
        if (categories->empty())
        {
            gInventory.removeObserver(this);
            LL_INFOS("Gesture")<< "Gesture dos NOT contains sub-directories."<< LL_ENDL;
            return;
        }
        LL_DEBUGS("Gesture")<< "There are " << categories->size() << " Folders "<< LL_ENDL;
        for (LLInventoryModel::cat_array_t::iterator it = categories->begin(); it != categories->end(); it++)
        {
            if (!gInventory.isCategoryComplete(it->get()->getUUID()))
            {
                unloaded_folders.push_back(it->get()->getUUID());
                LL_DEBUGS("Gesture")<< it->get()->getName()<< " Folder added to fetchlist"<< LL_ENDL;
            }

        }
        if (!unloaded_folders.empty())
        {
            LL_DEBUGS("Gesture")<< "Fetching subdirectories....." << LL_ENDL;
            setFetchIDs(unloaded_folders);
            startFetch();
        }
        else
        {
            LL_DEBUGS("Gesture")<< "All Gesture subdirectories have been loaded."<< LL_ENDL;
            gInventory.removeObserver(this);
            buildGestureList();
        }
    }
    else
    {
        LL_WARNS("Gesture")<< "Gesture list was NOT loaded"<< LL_ENDL;
    }
}

// virtual
LLFloaterGesture::~LLFloaterGesture()
{
    LLGestureMgr::instance().removeObserver(mObserver);
    delete mObserver;
    mObserver = NULL;
    gInventory.removeObserver(this);
}

// virtual
bool LLFloaterGesture::postBuild()
{
    std::string label;

    label = getTitle();

    setTitle(label);
    mGestureList = getChild<LLScrollListCtrl>("gesture_list");
    mGestureList->setCommitCallback(boost::bind(&LLFloaterGesture::onCommitList, this));
    mGestureList->setDoubleClickCallback(boost::bind(&LLFloaterGesture::onClickPlay, this));

    getChild<LLUICtrl>("edit_btn")->setCommitCallback(boost::bind(&LLFloaterGesture::onClickEdit, this));

    getChild<LLUICtrl>("play_btn")->setCommitCallback(boost::bind(&LLFloaterGesture::onClickPlay, this));
    getChild<LLUICtrl>("stop_btn")->setCommitCallback(boost::bind(&LLFloaterGesture::onClickPlay, this));
    getChild<LLButton>("activate_btn")->setClickedCallback(boost::bind(&LLFloaterGesture::onActivateBtnClick, this));

    getChild<LLUICtrl>("new_gesture_btn")->setCommitCallback(boost::bind(&LLFloaterGesture::onClickNew, this));
    getChild<LLButton>("del_btn")->setClickedCallback(boost::bind(&LLFloaterGesture::onDeleteSelected, this));

    getChildView("play_btn")->setVisible( true);
    getChildView("stop_btn")->setVisible( false);
    setDefaultBtn("play_btn");
    mGestureFolderID = gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE);

    uuid_vec_t folders;
    folders.push_back(mGestureFolderID);
    //perform loading Gesture directory anyway to make sure that all subdirectory are loaded too. See method done() for details.
    gInventory.addObserver(this);
    setFetchIDs(folders);
    startFetch();

    if (mGestureList)
    {
        buildGestureList();

        mGestureList->setFocus(true);

        constexpr bool ascending = true;
        mGestureList->sortByColumn(std::string("name"), ascending);
        mGestureList->selectFirstItem();
    }

    // Update button labels
    onCommitList();

    return true;
}


void LLFloaterGesture::refreshAll()
{
    if (!mGestureList) return;

    buildGestureList();

    if (mSelectedID.isNull())
    {
        mGestureList->selectFirstItem();
    }
    else
    {
        if (! mGestureList->setCurrentByID(mSelectedID))
        {
            mGestureList->selectFirstItem();
        }
    }

    // Update button labels
    onCommitList();
}

void LLFloaterGesture::buildGestureList()
{
    S32 scroll_pos = mGestureList->getScrollPos();
    uuid_vec_t selected_items;
    getSelectedIds(selected_items);
    LL_DEBUGS("Gesture")<< "Rebuilding gesture list "<< LL_ENDL;
    mGestureList->deleteAllItems();

    LLGestureMgr::item_map_t::const_iterator it;
    const LLGestureMgr::item_map_t& active_gestures = LLGestureMgr::instance().getActiveGestures();
    for (it = active_gestures.begin(); it != active_gestures.end(); ++it)
    {
        addGesture(it->first,it->second, mGestureList);
    }
    if (gInventory.isCategoryComplete(mGestureFolderID))
    {
        LLIsType is_gesture(LLAssetType::AT_GESTURE);
        LLInventoryModel::cat_array_t categories;
        LLInventoryModel::item_array_t items;
        gInventory.collectDescendentsIf(mGestureFolderID, categories, items,
                LLInventoryModel::EXCLUDE_TRASH, is_gesture);

        for (LLInventoryModel::item_array_t::iterator it = items.begin(); it!= items.end(); ++it)
        {
            LLInventoryItem* item = it->get();
            if (active_gestures.find(item->getUUID()) == active_gestures.end())
            {
                // if gesture wasn't loaded yet, we can display only name
                addGesture(item->getUUID(), NULL, mGestureList);
            }
        }
    }

    // attempt to preserve scroll position through re-builds
    // since we do re-build whenever something gets dirty
    for(uuid_vec_t::iterator it = selected_items.begin(); it != selected_items.end(); it++)
    {
        mGestureList->selectByID(*it);
    }
    mGestureList->setScrollPos(scroll_pos);
}

void LLFloaterGesture::addGesture(const LLUUID& item_id , LLMultiGesture* gesture,LLCtrlListInterface * list )
{
    // Note: Can have NULL item if inventory hasn't arrived yet.
    static std::string item_name = getString("loading");
    LLInventoryItem* item = gInventory.getItem(item_id);
    if (item)
    {
        item_name = item->getName();
    }

    static std::string font_style = "NORMAL";
    // If gesture is playing, bold it

    LLSD element;
    element["id"] = item_id;

    if (gesture)
    {
        if (gesture->mPlaying)
        {
            font_style = "BOLD";
        }
        item_name = gesture->mName;
        element["columns"][0]["column"] = "trigger";
        element["columns"][0]["value"] = gesture->mTrigger;
        element["columns"][0]["font"]["name"] = "SANSSERIF";
        element["columns"][0]["font"]["style"] = font_style;

        std::string key_string;
        std::string buffer;

        if (gesture->mKey == KEY_NONE)
        {
            buffer = "---";
            key_string = "~~~"; // alphabetize to end
        }
        else
        {
            key_string = LLKeyboard::stringFromKey(gesture->mKey);
            buffer = LLKeyboard::stringFromAccelerator(gesture->mMask,
                    gesture->mKey);
        }

        element["columns"][1]["column"] = "shortcut";
        element["columns"][1]["value"] = buffer;
        element["columns"][1]["font"]["name"] = "SANSSERIF";
        element["columns"][1]["font"]["style"] = font_style;

        // hidden column for sorting
        element["columns"][2]["column"] = "key";
        element["columns"][2]["value"] = key_string;
        element["columns"][2]["font"]["name"] = "SANSSERIF";
        element["columns"][2]["font"]["style"] = font_style;

        // Only add "playing" if we've got the name, less confusing. JC
        if (item && gesture->mPlaying)
        {
            item_name += " " + getString("playing");
        }
        element["columns"][3]["column"] = "name";
        element["columns"][3]["value"] = item_name;
        element["columns"][3]["font"]["name"] = "SANSSERIF";
        element["columns"][3]["font"]["style"] = font_style;
    }
    else
    {
        element["columns"][0]["column"] = "trigger";
        element["columns"][0]["value"] = "";
        element["columns"][0]["font"]["name"] = "SANSSERIF";
        element["columns"][0]["font"]["style"] = font_style;
        element["columns"][1]["column"] = "shortcut";
        element["columns"][1]["value"] = "---";
        element["columns"][1]["font"]["name"] = "SANSSERIF";
        element["columns"][1]["font"]["style"] = font_style;
        element["columns"][2]["column"] = "key";
        element["columns"][2]["value"] = "~~~";
        element["columns"][2]["font"]["name"] = "SANSSERIF";
        element["columns"][2]["font"]["style"] = font_style;
        element["columns"][3]["column"] = "name";
        element["columns"][3]["value"] = item_name;
        element["columns"][3]["font"]["name"] = "SANSSERIF";
        element["columns"][3]["font"]["style"] = font_style;
    }

    LL_DEBUGS("Gesture") << "Added gesture [" << item_name << "]" << LL_ENDL;

    LLScrollListItem* sl_item = list->addElement(element, ADD_BOTTOM);
    if(sl_item)
    {
        LLFontGL::StyleFlags style = LLGestureMgr::getInstance()->isGestureActive(item_id) ? LLFontGL::BOLD : LLFontGL::NORMAL;
        // *TODO find out why ["font"]["style"] does not affect font style
        ((LLScrollListText*)sl_item->getColumn(0))->setFontStyle(style);
    }
}

void LLFloaterGesture::getSelectedIds(uuid_vec_t& ids)
{
    std::vector<LLScrollListItem*> items = mGestureList->getAllSelected();
    for(std::vector<LLScrollListItem*>::const_iterator it = items.begin(); it != items.end(); it++)
    {
        ids.push_back((*it)->getUUID());
    }
}

bool LLFloaterGesture::isActionEnabled(const LLSD& command)
{
    // paste copy_uuid edit_gesture
    std::string command_name = command.asString();
    if("paste" == command_name)
    {
        if(!LLClipboard::instance().hasContents())
            return false;

        std::vector<LLUUID> ids;
        LLClipboard::instance().pasteFromClipboard(ids);
        for(std::vector<LLUUID>::iterator it = ids.begin(); it != ids.end(); it++)
        {
            LLInventoryItem* item = gInventory.getItem(*it);

            if(item && item->getInventoryType() == LLInventoryType::IT_GESTURE)
            {
                return true;
            }
        }
        return false;
    }
    else if("copy_uuid" == command_name || "edit_gesture" == command_name)
    {
        return  mGestureList->getAllSelected().size() == 1;
    }
    else if ("rename_gesture" == command_name)
    {
        if (mGestureList->getAllSelected().size() == 1)
        {
            LLViewerInventoryItem* item = gInventory.getItem(mGestureList->getCurrentID());

            if (item && item->getPermissions().allowModifyBy(gAgentID))
            {
                return true;
            }
        }
        return false;
    }
    return true;
}

void LLFloaterGesture::onClickPlay()
{
    const LLUUID& item_id = mGestureList->getCurrentID();
    if(item_id.isNull()) return;

    LL_DEBUGS("Gesture")<<" Trying to play gesture id: "<< item_id <<LL_ENDL;
    if(!LLGestureMgr::instance().isGestureActive(item_id))
    {
        // we need to inform server about gesture activating to be consistent with LLPreviewGesture and  LLGestureComboList.
        bool inform_server = true;
        bool deactivate_similar = false;
        LLGestureMgr::instance().setGestureLoadedCallback(item_id, boost::bind(&LLFloaterGesture::playGesture, this, item_id));
        LLViewerInventoryItem *item = gInventory.getItem(item_id);
        llassert(item);
        if (item)
        {
            LLGestureMgr::instance().activateGestureWithAsset(item_id, item->getAssetUUID(), inform_server, deactivate_similar);
            LL_DEBUGS("Gesture")<< "Activating gesture with inventory ID: " << item_id <<LL_ENDL;
        }
    }
    else
    {
        playGesture(item_id);
    }
}

void LLFloaterGesture::onClickNew()
{
    LLPointer<LLInventoryCallback> cb = new GestureShowCallback();
    create_inventory_item(gAgent.getID(),
                          gAgent.getSessionID(),
                          LLUUID::null,
                          LLTransactionID::tnull,
                          "New Gesture",
                          "",
                          LLAssetType::AT_GESTURE,
                          LLInventoryType::IT_GESTURE,
                          NO_INV_SUBTYPE,
                          PERM_MOVE | LLFloaterPerms::getNextOwnerPerms("Gestures"),
                          cb);
}

void LLFloaterGesture::onActivateBtnClick()
{
    uuid_vec_t ids;
    getSelectedIds(ids);
    if(ids.empty())
        return;

    LLGestureMgr* gm = LLGestureMgr::getInstance();
    uuid_vec_t::const_iterator it = ids.begin();
    bool first_gesture_state = gm->isGestureActive(*it);
    bool is_mixed = false;
    while( ++it != ids.end() )
    {
        if(first_gesture_state != gm->isGestureActive(*it))
        {
            is_mixed = true;
            break;
        }
    }
    for(uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); it++)
    {
        if(is_mixed)
        {
            gm->activateGesture(*it);
        }
        else
        {
            if(first_gesture_state)
            {
                gm->deactivateGesture(*it);
            }
            else
            {
                gm->activateGesture(*it);
            }
        }
    }
}

void LLFloaterGesture::onRenameSelected()
{
    LLViewerInventoryItem* gesture = gInventory.getItem(mGestureList->getCurrentID());
    if (!gesture)
    {
        return;
    }

    LLSD args;
    args["NAME"] = gesture->getName();

    LLSD payload;
    payload["gesture_id"] = mGestureList->getCurrentID();

    LLNotificationsUtil::add("RenameGesture", args, payload, boost::bind(onGestureRename, _1, _2));

}

void LLFloaterGesture::onGestureRename(const LLSD& notification, const LLSD& response)
{
    S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
    if (option != 0) return; // canceled

    std::string new_name = response["new_name"].asString();
    LLInventoryObject::correctInventoryName(new_name);
    if (!new_name.empty())
    {
        LLUUID item_id = notification["payload"]["gesture_id"].asUUID();
        LLViewerInventoryItem* gesture = gInventory.getItem(item_id);
        if (gesture && (gesture->getName() != new_name))
        {
            LLSD updates;
            updates["name"] = new_name;
            update_inventory_item(item_id, updates, NULL);
        }
    }
}

void LLFloaterGesture::onCopyPasteAction(const LLSD& command)
{
    std::string command_name  = command.asString();
    // Since we select this command, the inventory items must have already arrived
    if("copy_gesture" == command_name)
    {
        uuid_vec_t ids;
        getSelectedIds(ids);
        // Make sure the clipboard is empty
        LLClipboard::instance().reset();
        for(uuid_vec_t::iterator it = ids.begin(); it != ids.end(); it++)
        {
            LLInventoryItem* item = gInventory.getItem(*it);
            if(item  && item->getInventoryType() == LLInventoryType::IT_GESTURE)
            {
                LLClipboard::instance().addToClipboard(*it);
            }
        }
    }
    else if ("paste" == command_name)
    {
        std::vector<LLUUID> ids;
        LLClipboard::instance().pasteFromClipboard(ids);
        if(ids.empty() || !gInventory.isCategoryComplete(mGestureFolderID))
            return;
        LLInventoryCategory* gesture_dir = gInventory.getCategory(mGestureFolderID);
        llassert(gesture_dir);
        LLPointer<GestureCopiedCallback> cb = new GestureCopiedCallback(this);

        for(std::vector<LLUUID>::iterator it = ids.begin(); it != ids.end(); it++)
        {
            LLInventoryItem* item = gInventory.getItem(*it);
            if(gesture_dir && item && item->getInventoryType() == LLInventoryType::IT_GESTURE)
            {
                LLStringUtil::format_map_t string_args;
                string_args["[COPY_NAME]"] = item->getName();
                LL_DEBUGS("Gesture")<< "Copying gesture " << item->getName() << "  "<< item->getUUID() << " into "
                                        << gesture_dir->getName() << "  "<< gesture_dir->getUUID() << LL_ENDL;
                copy_inventory_item(gAgent.getID(), item->getPermissions().getOwner(), item->getUUID(),
                        gesture_dir->getUUID(), getString("copy_name", string_args), cb);
            }
        }
        LLClipboard::instance().reset();
    }
    else if ("copy_uuid" == command_name)
    {
        LLClipboard::instance().copyToClipboard(mGestureList->getCurrentID(),LLAssetType::AT_GESTURE);
    }
}

void LLFloaterGesture::onClickEdit()
{
    const LLUUID& item_id = mGestureList->getCurrentID();

    LLInventoryItem* item = gInventory.getItem(item_id);
    if (!item) return;

    LLPreviewGesture* previewp = LLPreviewGesture::show(item_id, LLUUID::null);
    if (!previewp->getHost())
    {
        previewp->setRect(gFloaterView->findNeighboringPosition(this, previewp));
    }
}

void LLFloaterGesture::onCommitList()
{
    const LLUUID& item_id = mGestureList->getCurrentID();

    mSelectedID = item_id;
    if (LLGestureMgr::instance().isGesturePlaying(item_id))
    {
        getChildView("play_btn")->setVisible( false);
        getChildView("stop_btn")->setVisible( true);
    }
    else
    {
        getChildView("play_btn")->setVisible( true);
        getChildView("stop_btn")->setVisible( false);
    }
}

void LLFloaterGesture::onDeleteSelected()
{
    uuid_vec_t ids;
    getSelectedIds(ids);
    if(ids.empty())
        return;

    const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH);
    LLGestureMgr* gm = LLGestureMgr::getInstance();
    for(uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); it++)
    {
        const LLUUID& selected_item = *it;
        LLInventoryItem* inv_item = gInventory.getItem(selected_item);
        if (inv_item && inv_item->getInventoryType() == LLInventoryType::IT_GESTURE)
        {
            if(gm->isGestureActive(selected_item))
            {
                gm->deactivateGesture(selected_item);
            }
            LLInventoryModel::update_list_t update;
            LLInventoryModel::LLCategoryUpdate old_folder(inv_item->getParentUUID(), -1);
            update.push_back(old_folder);
            LLInventoryModel::LLCategoryUpdate new_folder(trash_id, 1);
            update.push_back(new_folder);
            gInventory.accountForUpdate(update);

            LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(inv_item);
            new_item->setParent(trash_id);
            // no need to restamp it though it's a move into trash because
            // it's a brand new item already.
            new_item->updateParentOnServer(false);
            gInventory.updateItem(new_item);
        }
    }
    gInventory.notifyObservers();
    buildGestureList();
}

void LLFloaterGesture::addToCurrentOutFit()
{
    uuid_vec_t ids;
    getSelectedIds(ids);
    LLAppearanceMgr* am = LLAppearanceMgr::getInstance();
    LLPointer<LLInventoryCallback> cb = new LLUpdateAppearanceOnDestroy;
    for(uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); it++)
    {
        am->addCOFItemLink(*it, cb);
    }
}

void LLFloaterGesture::playGesture(LLUUID item_id)
{
    LL_DEBUGS("Gesture")<<"Playing gesture "<< item_id<<LL_ENDL;

    if (LLGestureMgr::instance().isGesturePlaying(item_id))
    {
        LLGestureMgr::instance().stopGesture(item_id);
    }
    else
    {
        LLGestureMgr::instance().playGesture(item_id);
    }
}