/**
 * @file llpreviewgesture.cpp
 * @brief Editing UI for inventory-based gestures.
 *
 * $LicenseInfo:firstyear=2004&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 "llpreviewgesture.h"

#include "llagent.h"
#include "llanimstatelabels.h"
#include "llanimationstates.h"
#include "llappviewer.h"
#include "llcheckboxctrl.h"
#include "llcombobox.h"
#include "lldatapacker.h"
#include "lldelayedgestureerror.h"
#include "llfloaterreg.h"
#include "llgesturemgr.h"
#include "llinventorydefines.h"
#include "llinventoryfunctions.h"
#include "llinventorymodel.h"
#include "llinventorymodelbackgroundfetch.h"
#include "llkeyboard.h"
#include "llmultigesture.h"
#include "llnotificationsutil.h"
#include "llradiogroup.h"
#include "llresmgr.h"
#include "lltrans.h"
#include "llfilesystem.h"
#include "llviewerobjectlist.h"
#include "llviewerregion.h"
#include "llviewerstats.h"
#include "llviewerassetupload.h"

std::string NONE_LABEL;
std::string SHIFT_LABEL;
std::string CTRL_LABEL;

void dialog_refresh_all();

// used for getting

class LLInventoryGestureAvailable : public LLInventoryCompletionObserver
{
public:
    LLInventoryGestureAvailable() {}

protected:
    virtual void done();
};

void LLInventoryGestureAvailable::done()
{
    for(uuid_vec_t::iterator it = mComplete.begin(); it != mComplete.end(); ++it)
    {
        LLPreviewGesture* preview = LLFloaterReg::findTypedInstance<LLPreviewGesture>("preview_gesture", *it);
        if(preview)
        {
            preview->refresh();
        }
    }
    gInventory.removeObserver(this);
    delete this;
}

// Used for sorting
struct SortItemPtrsByName
{
    bool operator()(const LLInventoryItem* i1, const LLInventoryItem* i2)
    {
        return (LLStringUtil::compareDict(i1->getName(), i2->getName()) < 0);
    }
};

// static
LLPreviewGesture* LLPreviewGesture::show(const LLUUID& item_id, const LLUUID& object_id)
{
    LLPreviewGesture* preview = LLFloaterReg::showTypedInstance<LLPreviewGesture>("preview_gesture", LLSD(item_id), TAKE_FOCUS_YES);
    if (!preview)
    {
        return NULL;
    }

    preview->setObjectID(object_id);

    // Start speculative download of sounds and animations
    const LLUUID animation_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_ANIMATION);
    LLInventoryModelBackgroundFetch::instance().start(animation_folder_id);

    const LLUUID sound_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_SOUND);
    LLInventoryModelBackgroundFetch::instance().start(sound_folder_id);

    // this will call refresh when we have everything.
    LLViewerInventoryItem* item = (LLViewerInventoryItem*)preview->getItem();
    if (item && !item->isFinished())
    {
        LLInventoryGestureAvailable* observer;
        observer = new LLInventoryGestureAvailable();
        observer->watchItem(item_id);
        gInventory.addObserver(observer);
        item->fetchFromServer();
    }
    else
    {
        // not sure this is necessary.
        preview->refresh();
    }

    return preview;
}

void LLPreviewGesture::draw()
{
    // Skip LLPreview::draw() to avoid description update
    LLFloater::draw();
}

// virtual
bool LLPreviewGesture::handleKeyHere(KEY key, MASK mask)
{
    if(('S' == key) && (MASK_CONTROL == (mask & MASK_CONTROL)))
    {
        saveIfNeeded();
        return true;
    }

    return LLPreview::handleKeyHere(key, mask);
}


// virtual
bool LLPreviewGesture::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop,
                                         EDragAndDropType cargo_type,
                                         void* cargo_data,
                                         EAcceptance* accept,
                                         std::string& tooltip_msg)
{
    bool handled = true;
    switch(cargo_type)
    {
    case DAD_ANIMATION:
    case DAD_SOUND:
        {
            // TODO: Don't allow this if you can't transfer the sound/animation

            // make a script step
            LLInventoryItem* item = (LLInventoryItem*)cargo_data;
            if (item
                && gInventory.getItem(item->getUUID()))
            {
                LLPermissions perm = item->getPermissions();
                if (!((perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED))
                {
                    *accept = ACCEPT_NO;
                    if (tooltip_msg.empty())
                    {
                        tooltip_msg.assign("Only animations and sounds\n"
                                            "with unrestricted permissions\n"
                                            "can be added to a gesture.");
                    }
                    break;
                }
                else if (drop)
                {
                    LLScrollListItem* line = NULL;
                    if (cargo_type == DAD_ANIMATION)
                    {
                        line = addStep( STEP_ANIMATION );
                        LLGestureStepAnimation* anim = (LLGestureStepAnimation*)line->getUserdata();
                        anim->mAnimAssetID = item->getAssetUUID();
                        anim->mAnimName = item->getName();
                    }
                    else if (cargo_type == DAD_SOUND)
                    {
                        line = addStep( STEP_SOUND );
                        LLGestureStepSound* sound = (LLGestureStepSound*)line->getUserdata();
                        sound->mSoundAssetID = item->getAssetUUID();
                        sound->mSoundName = item->getName();
                    }
                    updateLabel(line);
                    mDirty = true;
                    refresh();
                }
                *accept = ACCEPT_YES_COPY_MULTI;
            }
            else
            {
                // Not in user's inventory means it was in object inventory
                *accept = ACCEPT_NO;
            }
            break;
        }
    default:
        *accept = ACCEPT_NO;
        if (tooltip_msg.empty())
        {
            tooltip_msg.assign("Only animations and sounds\n"
                                "can be added to a gesture.");
        }
        break;
    }
    return handled;
}


// virtual
bool LLPreviewGesture::canClose()
{

    if(!mDirty || mForceClose)
    {
        return true;
    }
    else
    {
        if(!mSaveDialogShown)
        {
            mSaveDialogShown = true;
            // Bring up view-modal dialog: Save changes? Yes, No, Cancel
            LLNotificationsUtil::add("SaveChanges", LLSD(), LLSD(),
                    boost::bind(&LLPreviewGesture::handleSaveChangesDialog, this, _1, _2) );
        }
        return false;
    }
}

// virtual
void LLPreviewGesture::onClose(bool app_quitting)
{
    LLGestureMgr::instance().stopGesture(mPreviewGesture);
}

// virtual
void LLPreviewGesture::onUpdateSucceeded()
{
    refresh();
}

void LLPreviewGesture::onVisibilityChanged ( const LLSD& new_visibility )
{
    if (new_visibility.asBoolean())
    {
        refresh();
    }
}


bool LLPreviewGesture::handleSaveChangesDialog(const LLSD& notification, const LLSD& response)
{
    mSaveDialogShown = false;
    S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
    switch(option)
    {
    case 0:  // "Yes"
        LLGestureMgr::instance().stopGesture(mPreviewGesture);
        mCloseAfterSave = true;
        onClickSave(this);
        break;

    case 1:  // "No"
        LLGestureMgr::instance().stopGesture(mPreviewGesture);
        mDirty = false; // Force the dirty flag because user has clicked NO on confirm save dialog...
        closeFloater();
        break;

    case 2: // "Cancel"
    default:
        // If we were quitting, we didn't really mean it.
        LLAppViewer::instance()->abortQuit();
        break;
    }
    return false;
}


LLPreviewGesture::LLPreviewGesture(const LLSD& key)
:   LLPreview(key),
    mTriggerEditor(NULL),
    mModifierCombo(NULL),
    mKeyCombo(NULL),
    mLibraryList(NULL),
    mAddBtn(NULL),
    mUpBtn(NULL),
    mDownBtn(NULL),
    mDeleteBtn(NULL),
    mStepList(NULL),
    mOptionsText(NULL),
    mAnimationRadio(NULL),
    mAnimationCombo(NULL),
    mSoundCombo(NULL),
    mChatEditor(NULL),
    mSaveBtn(NULL),
    mPreviewBtn(NULL),
    mPreviewGesture(NULL),
    mDirty(false)
{
    NONE_LABEL =  LLTrans::getString("---");
    SHIFT_LABEL = LLTrans::getString("KBShift");
    CTRL_LABEL = LLTrans::getString("KBCtrl");
}


LLPreviewGesture::~LLPreviewGesture()
{
    // Userdata for all steps is a LLGestureStep we need to clean up
    std::vector<LLScrollListItem*> data_list = mStepList->getAllData();
    std::vector<LLScrollListItem*>::iterator data_itor;
    for (data_itor = data_list.begin(); data_itor != data_list.end(); ++data_itor)
    {
        LLScrollListItem* item = *data_itor;
        LLGestureStep* step = (LLGestureStep*)item->getUserdata();
        delete step;
        step = NULL;
    }
}


bool LLPreviewGesture::postBuild()
{
    setVisibleCallback(boost::bind(&LLPreviewGesture::onVisibilityChanged, this, _2));

    LLLineEditor* edit;
    LLComboBox* combo;
    LLButton* btn;
    LLScrollListCtrl* list;
    LLTextBox* text;
    LLCheckBoxCtrl* check;

    edit = getChild<LLLineEditor>("desc");
    edit->setKeystrokeCallback(onKeystrokeCommit, this);

    edit = getChild<LLLineEditor>("trigger_editor");
    edit->setKeystrokeCallback(onKeystrokeCommit, this);
    edit->setCommitCallback(onCommitSetDirty, this);
    edit->setCommitOnFocusLost(true);
    edit->setIgnoreTab(true);
    mTriggerEditor = edit;

    text = getChild<LLTextBox>("replace_text");
    text->setEnabled(false);
    mReplaceText = text;

    edit = getChild<LLLineEditor>("replace_editor");
    edit->setEnabled(false);
    edit->setKeystrokeCallback(onKeystrokeCommit, this);
    edit->setCommitCallback(onCommitSetDirty, this);
    edit->setCommitOnFocusLost(true);
    edit->setIgnoreTab(true);
    mReplaceEditor = edit;

    combo = getChild<LLComboBox>( "modifier_combo");
    combo->setCommitCallback(boost::bind(&LLPreviewGesture::onCommitKeyorModifier, this));
    mModifierCombo = combo;

    combo = getChild<LLComboBox>( "key_combo");
    combo->setCommitCallback(boost::bind(&LLPreviewGesture::onCommitKeyorModifier, this));
    mKeyCombo = combo;

    list = getChild<LLScrollListCtrl>("library_list");
    list->setCommitCallback(onCommitLibrary, this);
    list->setDoubleClickCallback(onClickAdd, this);
    mLibraryList = list;

    btn = getChild<LLButton>( "add_btn");
    btn->setClickedCallback(onClickAdd, this);
    btn->setEnabled(false);
    mAddBtn = btn;

    btn = getChild<LLButton>( "up_btn");
    btn->setClickedCallback(onClickUp, this);
    btn->setEnabled(false);
    mUpBtn = btn;

    btn = getChild<LLButton>( "down_btn");
    btn->setClickedCallback(onClickDown, this);
    btn->setEnabled(false);
    mDownBtn = btn;

    btn = getChild<LLButton>( "delete_btn");
    btn->setClickedCallback(onClickDelete, this);
    btn->setEnabled(false);
    mDeleteBtn = btn;

    list = getChild<LLScrollListCtrl>("step_list");
    list->setCommitCallback(onCommitStep, this);
    mStepList = list;

    // Options
    mOptionsText = getChild<LLTextBox>("options_text");

    combo = getChild<LLComboBox>( "animation_list");
    combo->setVisible(false);
    combo->setCommitCallback(onCommitAnimation, this);
    mAnimationCombo = combo;

    LLRadioGroup* group;
    group = getChild<LLRadioGroup>("animation_trigger_type");
    group->setVisible(false);
    group->setCommitCallback(onCommitAnimationTrigger, this);
    mAnimationRadio = group;

    combo = getChild<LLComboBox>( "sound_list");
    combo->setVisible(false);
    combo->setCommitCallback(onCommitSound, this);
    mSoundCombo = combo;

    edit = getChild<LLLineEditor>("chat_editor");
    edit->setVisible(false);
    edit->setCommitCallback(onCommitChat, this);
    //edit->setKeystrokeCallback(onKeystrokeCommit, this);
    edit->setCommitOnFocusLost(true);
    edit->setIgnoreTab(true);
    mChatEditor = edit;

    check = getChild<LLCheckBoxCtrl>( "wait_key_release_check");
    check->setVisible(false);
    check->setCommitCallback(onCommitWait, this);
    mWaitKeyReleaseCheck = check;

    check = getChild<LLCheckBoxCtrl>( "wait_anim_check");
    check->setVisible(false);
    check->setCommitCallback(onCommitWait, this);
    mWaitAnimCheck = check;

    check = getChild<LLCheckBoxCtrl>( "wait_time_check");
    check->setVisible(false);
    check->setCommitCallback(onCommitWait, this);
    mWaitTimeCheck = check;

    edit = getChild<LLLineEditor>("wait_time_editor");
    edit->setEnabled(false);
    edit->setVisible(false);
    edit->setPrevalidate(LLTextValidate::validateFloat);
//  edit->setKeystrokeCallback(onKeystrokeCommit, this);
    edit->setCommitOnFocusLost(true);
    edit->setCommitCallback(onCommitWaitTime, this);
    edit->setIgnoreTab(true);
    mWaitTimeEditor = edit;

    // Buttons at the bottom
    check = getChild<LLCheckBoxCtrl>( "active_check");
    check->setCommitCallback(onCommitActive, this);
    mActiveCheck = check;

    btn = getChild<LLButton>( "save_btn");
    btn->setClickedCallback(onClickSave, this);
    mSaveBtn = btn;

    btn = getChild<LLButton>( "preview_btn");
    btn->setClickedCallback(onClickPreview, this);
    mPreviewBtn = btn;


    // Populate the comboboxes
    addModifiers();
    addKeys();
    addAnimations();
    addSounds();

    const LLInventoryItem* item = getItem();

    if (item)
    {
        getChild<LLUICtrl>("desc")->setValue(item->getDescription());
        getChild<LLLineEditor>("desc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe);
    }

    return LLPreview::postBuild();
}


void LLPreviewGesture::addModifiers()
{
    LLComboBox* combo = mModifierCombo;

    combo->add( NONE_LABEL,  ADD_BOTTOM );
    combo->add( SHIFT_LABEL, ADD_BOTTOM );
    combo->add( CTRL_LABEL,  ADD_BOTTOM );
    combo->setCurrentByIndex(0);
}

void LLPreviewGesture::addKeys()
{
    LLComboBox* combo = mKeyCombo;

    combo->add( NONE_LABEL );
    for (KEY key = KEY_F2; key <= KEY_F12; key++)
    {
        combo->add( LLKeyboard::stringFromKey(key), ADD_BOTTOM );
    }
    combo->setCurrentByIndex(0);
}


// TODO: Sort the legacy and non-legacy together?
void LLPreviewGesture::addAnimations()
{
    LLComboBox* combo = mAnimationCombo;

    combo->removeall();

    std::string none_text = getString("none_text");

    combo->add(none_text, LLUUID::null);

    // Add all the default (legacy) animations
    S32 i;
    for (i = 0; i < gUserAnimStatesCount; ++i)
    {
        // Use the user-readable name
        std::string label = LLAnimStateLabels::getStateLabel( gUserAnimStates[i].mName );
        const LLUUID& id = gUserAnimStates[i].mID;
        combo->add(label, id);
    }

    // Get all inventory items that are animations
    LLViewerInventoryCategory::cat_array_t cats;
    LLViewerInventoryItem::item_array_t items;
    LLIsTypeWithPermissions is_copyable_animation(LLAssetType::AT_ANIMATION,
                                                    PERM_ITEM_UNRESTRICTED,
                                                    gAgent.getID(),
                                                    gAgent.getGroupID());
    gInventory.collectDescendentsIf(gInventory.getRootFolderID(),
                                    cats,
                                    items,
                                    LLInventoryModel::EXCLUDE_TRASH,
                                    is_copyable_animation);

    // Copy into something we can sort
    std::vector<LLInventoryItem*> animations;
    for (auto& item : items)
    {
        animations.emplace_back(item);
    }

    // Do the sort
    std::sort(animations.begin(), animations.end(), SortItemPtrsByName());

    // And load up the combobox
    std::vector<LLInventoryItem*>::iterator it;
    for (it = animations.begin(); it != animations.end(); ++it)
    {
        LLInventoryItem* item = *it;

        combo->add(item->getName(), item->getAssetUUID(), ADD_BOTTOM);
    }
}


void LLPreviewGesture::addSounds()
{
    LLComboBox* combo = mSoundCombo;
    combo->removeall();

    std::string none_text = getString("none_text");

    combo->add(none_text, LLUUID::null);

    // Get all inventory items that are sounds
    LLViewerInventoryCategory::cat_array_t cats;
    LLViewerInventoryItem::item_array_t items;
    LLIsTypeWithPermissions is_copyable_sound(LLAssetType::AT_SOUND,
                                                    PERM_ITEM_UNRESTRICTED,
                                                    gAgent.getID(),
                                                    gAgent.getGroupID());
    gInventory.collectDescendentsIf(gInventory.getRootFolderID(),
                                    cats,
                                    items,
                                    LLInventoryModel::EXCLUDE_TRASH,
                                    is_copyable_sound);

    // Copy sounds into something we can sort
    std::vector<LLInventoryItem*> sounds;
    for (auto& item : items)
    {
        sounds.emplace_back(item);
    }

    // Do the sort
    std::sort(sounds.begin(), sounds.end(), SortItemPtrsByName());

    // And load up the combobox
    std::vector<LLInventoryItem*>::iterator it;
    for (it = sounds.begin(); it != sounds.end(); ++it)
    {
        LLInventoryItem* item = *it;

        combo->add(item->getName(), item->getAssetUUID(), ADD_BOTTOM);
    }
}


void LLPreviewGesture::refresh()
{
    LLPreview::refresh();
    // If previewing or item is incomplete, all controls are disabled
    LLViewerInventoryItem* item = (LLViewerInventoryItem*)getItem();
    bool is_complete = item && item->isFinished();
    if (mPreviewGesture || !is_complete)
    {

        getChildView("desc")->setEnabled(false);
        //mDescEditor->setEnabled(false);
        mTriggerEditor->setEnabled(false);
        mReplaceText->setEnabled(false);
        mReplaceEditor->setEnabled(false);
        mModifierCombo->setEnabled(false);
        mKeyCombo->setEnabled(false);
        mLibraryList->setEnabled(false);
        mAddBtn->setEnabled(false);
        mUpBtn->setEnabled(false);
        mDownBtn->setEnabled(false);
        mDeleteBtn->setEnabled(false);
        mStepList->setEnabled(false);
        mOptionsText->setEnabled(false);
        mAnimationCombo->setEnabled(false);
        mAnimationRadio->setEnabled(false);
        mSoundCombo->setEnabled(false);
        mChatEditor->setEnabled(false);
        mWaitKeyReleaseCheck->setEnabled(false);
        mWaitAnimCheck->setEnabled(false);
        mWaitTimeCheck->setEnabled(false);
        mWaitTimeEditor->setEnabled(false);
        mActiveCheck->setEnabled(false);
        mSaveBtn->setEnabled(false);

        // Make sure preview button is enabled, so we can stop it
        mPreviewBtn->setEnabled(true);
        return;
    }

    bool modifiable = item->getPermissions().allowModifyBy(gAgent.getID());

    getChildView("desc")->setEnabled(modifiable);
    mTriggerEditor->setEnabled(true);
    mLibraryList->setEnabled(modifiable);
    mStepList->setEnabled(modifiable);
    mOptionsText->setEnabled(modifiable);
    mAnimationCombo->setEnabled(modifiable);
    mAnimationRadio->setEnabled(modifiable);
    mSoundCombo->setEnabled(modifiable);
    mChatEditor->setEnabled(modifiable);
    mWaitKeyReleaseCheck->setEnabled(modifiable);
    mWaitAnimCheck->setEnabled(modifiable);
    mWaitTimeCheck->setEnabled(modifiable);
    mWaitTimeEditor->setEnabled(modifiable);
    mActiveCheck->setEnabled(true);

    const std::string& trigger = mTriggerEditor->getText();
    bool have_trigger = !trigger.empty();

    const std::string& replace = mReplaceEditor->getText();
    bool have_replace = !replace.empty();

    LLScrollListItem* library_item = mLibraryList->getFirstSelected();
    bool have_library = (library_item != NULL);

    LLScrollListItem* step_item = mStepList->getFirstSelected();
    S32 step_index = mStepList->getFirstSelectedIndex();
    S32 step_count = mStepList->getItemCount();
    bool have_step = (step_item != NULL);

    mReplaceText->setEnabled(have_trigger || have_replace);
    mReplaceEditor->setEnabled(have_trigger || have_replace);

    mModifierCombo->setEnabled(true);
    mKeyCombo->setEnabled(true);

    mAddBtn->setEnabled(modifiable && have_library);
    mUpBtn->setEnabled(modifiable && have_step && step_index > 0);
    mDownBtn->setEnabled(modifiable && have_step && step_index < step_count-1);
    mDeleteBtn->setEnabled(modifiable && have_step);

    // Assume all not visible
    mAnimationCombo->setVisible(false);
    mAnimationRadio->setVisible(false);
    mSoundCombo->setVisible(false);
    mChatEditor->setVisible(false);
    mWaitKeyReleaseCheck->setVisible(false);
    mWaitAnimCheck->setVisible(false);
    mWaitTimeCheck->setVisible(false);
    mWaitTimeEditor->setVisible(false);

    std::string optionstext;

    if (have_step)
    {
        // figure out the type, show proper options, update text
        LLGestureStep* step = (LLGestureStep*)step_item->getUserdata();
        EStepType type = step->getType();

        switch(type)
        {
        case STEP_ANIMATION:
            {
                LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step;
                optionstext = getString("step_anim");
                mAnimationCombo->setVisible(true);
                mAnimationRadio->setVisible(true);
                mAnimationRadio->setSelectedIndex((anim_step->mFlags & ANIM_FLAG_STOP) ? 1 : 0);
                mAnimationCombo->setCurrentByID(anim_step->mAnimAssetID);
                break;
            }
        case STEP_SOUND:
            {
                LLGestureStepSound* sound_step = (LLGestureStepSound*)step;
                optionstext = getString("step_sound");
                mSoundCombo->setVisible(true);
                mSoundCombo->setCurrentByID(sound_step->mSoundAssetID);
                break;
            }
        case STEP_CHAT:
            {
                LLGestureStepChat* chat_step = (LLGestureStepChat*)step;
                optionstext = getString("step_chat");
                mChatEditor->setVisible(true);
                mChatEditor->setText(chat_step->mChatText);
                break;
            }
        case STEP_WAIT:
            {
                LLGestureStepWait* wait_step = (LLGestureStepWait*)step;
                optionstext = getString("step_wait");
                mWaitKeyReleaseCheck->setVisible(true);
                mWaitKeyReleaseCheck->set(wait_step->mFlags & WAIT_FLAG_KEY_RELEASE);
                mWaitAnimCheck->setVisible(true);
                mWaitAnimCheck->set(wait_step->mFlags & WAIT_FLAG_ALL_ANIM);
                mWaitTimeCheck->setVisible(true);
                mWaitTimeCheck->set(wait_step->mFlags & WAIT_FLAG_TIME);
                mWaitTimeEditor->setVisible(true);
                std::string buffer = llformat("%.1f", (double)wait_step->mWaitSeconds);
                mWaitTimeEditor->setText(buffer);
                break;
            }
        default:
            break;
        }
    }

    mOptionsText->setText(optionstext);

    bool active = LLGestureMgr::instance().isGestureActive(mItemUUID);
    mActiveCheck->set(active);

    // Can only preview if there are steps
    mPreviewBtn->setEnabled(step_count > 0);

    // And can only save if changes have been made
    mSaveBtn->setEnabled(mDirty);
    addAnimations();
    addSounds();
}


void LLPreviewGesture::initDefaultGesture()
{
    LLScrollListItem* item;
    item = addStep( STEP_ANIMATION );
    LLGestureStepAnimation* anim = (LLGestureStepAnimation*)item->getUserdata();
    anim->mAnimAssetID = ANIM_AGENT_HELLO;
    anim->mAnimName = LLTrans::getString("Wave");
    updateLabel(item);

    item = addStep( STEP_WAIT );
    LLGestureStepWait* wait = (LLGestureStepWait*)item->getUserdata();
    wait->mFlags = WAIT_FLAG_ALL_ANIM;
    updateLabel(item);

    item = addStep( STEP_CHAT );
    LLGestureStepChat* chat_step = (LLGestureStepChat*)item->getUserdata();
    chat_step->mChatText =  LLTrans::getString("HelloAvatar");
    updateLabel(item);

    // Start with item list selected
    mStepList->selectFirstItem();

    // this is *new* content, so we are dirty
    mDirty = true;
}


void LLPreviewGesture::loadAsset()
{
    const LLInventoryItem* item = getItem();
    if (!item)
    {
        // Don't set asset status here; we may not have set the item id yet
        // (e.g. when this gets called initially)
        //mAssetStatus = PREVIEW_ASSET_ERROR;
        return;
    }

    LLUUID asset_id = item->getAssetUUID();
    if (asset_id.isNull())
    {
        // Freshly created gesture, don't need to load asset.
        // Blank gesture will be fine.
        initDefaultGesture();
        refresh();
        mAssetStatus = PREVIEW_ASSET_LOADED;
        return;
    }

    // TODO: Based on item->getPermissions().allow*
    // could enable/disable UI.

    // Copy the UUID, because the user might close the preview
    // window if the download gets stalled.
    LLUUID* item_idp = new LLUUID(mItemUUID);

    const bool high_priority = true;
    gAssetStorage->getAssetData(asset_id,
                                LLAssetType::AT_GESTURE,
                                onLoadComplete,
                                (void**)item_idp,
                                high_priority);
    mAssetStatus = PREVIEW_ASSET_LOADING;
}


// static
void LLPreviewGesture::onLoadComplete(const LLUUID& asset_uuid,
                                      LLAssetType::EType type,
                                      void* user_data, S32 status, LLExtStat ext_status)
{
    LLUUID* item_idp = (LLUUID*)user_data;

    LLPreviewGesture* self = LLFloaterReg::findTypedInstance<LLPreviewGesture>("preview_gesture", *item_idp);
    if (self)
    {
        if (0 == status)
        {
            LLFileSystem file(asset_uuid, type, LLFileSystem::READ);
            S32 size = file.getSize();

            std::vector<char> buffer(size+1);
            file.read((U8*)&buffer[0], size);
            buffer[size] = '\0';

            LLMultiGesture* gesture = new LLMultiGesture();

            LLDataPackerAsciiBuffer dp(&buffer[0], size+1);
            bool ok = gesture->deserialize(dp);

            if (ok)
            {
                // Everything has been successful.  Load up the UI.
                self->loadUIFromGesture(gesture);

                self->mStepList->selectFirstItem();

                self->mDirty = false;
                self->refresh();
                self->refreshFromItem(); // to update description and title
            }
            else
            {
                LL_WARNS() << "Unable to load gesture" << LL_ENDL;
            }

            delete gesture;
            gesture = NULL;

            self->mAssetStatus = PREVIEW_ASSET_LOADED;
        }
        else
        {
            if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status ||
                LL_ERR_FILE_EMPTY == status)
            {
                LLDelayedGestureError::gestureMissing( *item_idp );
            }
            else
            {
                LLDelayedGestureError::gestureFailedToLoad( *item_idp );
            }

            LL_WARNS() << "Problem loading gesture: " << status << LL_ENDL;
            self->mAssetStatus = PREVIEW_ASSET_ERROR;
        }
    }
    delete item_idp;
    item_idp = NULL;
}


void LLPreviewGesture::loadUIFromGesture(LLMultiGesture* gesture)
{
    /*LLInventoryItem* item = getItem();



    if (item)
    {
        LLLineEditor* descEditor = getChild<LLLineEditor>("desc");
        descEditor->setText(item->getDescription());
    }*/

    mTriggerEditor->setText(gesture->mTrigger);

    mReplaceEditor->setText(gesture->mReplaceText);

    switch (gesture->mMask)
    {
    default:
      case MASK_NONE:
        mModifierCombo->setSimple( NONE_LABEL );
        break;
      case MASK_SHIFT:
        mModifierCombo->setSimple( SHIFT_LABEL );
        break;
      case MASK_CONTROL:
        mModifierCombo->setSimple( CTRL_LABEL );
        break;
    }

    mModifierCombo->setEnabledByValue(CTRL_LABEL, gesture->mKey != KEY_F10);

    mKeyCombo->setCurrentByIndex(0);
    if (gesture->mKey != KEY_NONE)
    {
        mKeyCombo->setSimple(LLKeyboard::stringFromKey(gesture->mKey));
    }

    mKeyCombo->setEnabledByValue(LLKeyboard::stringFromKey(KEY_F10), gesture->mMask != MASK_CONTROL);

    // Make UI steps for each gesture step
    size_t i;
    size_t count = gesture->mSteps.size();
    for (i = 0; i < count; ++i)
    {
        LLGestureStep* step = gesture->mSteps[i];

        LLGestureStep* new_step = NULL;

        switch(step->getType())
        {
        case STEP_ANIMATION:
            {
                LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step;
                LLGestureStepAnimation* new_anim_step =
                    new LLGestureStepAnimation(*anim_step);
                new_step = new_anim_step;
                break;
            }
        case STEP_SOUND:
            {
                LLGestureStepSound* sound_step = (LLGestureStepSound*)step;
                LLGestureStepSound* new_sound_step =
                    new LLGestureStepSound(*sound_step);
                new_step = new_sound_step;
                break;
            }
        case STEP_CHAT:
            {
                LLGestureStepChat* chat_step = (LLGestureStepChat*)step;
                LLGestureStepChat* new_chat_step =
                    new LLGestureStepChat(*chat_step);
                new_step = new_chat_step;
                break;
            }
        case STEP_WAIT:
            {
                LLGestureStepWait* wait_step = (LLGestureStepWait*)step;
                LLGestureStepWait* new_wait_step =
                    new LLGestureStepWait(*wait_step);
                new_step = new_wait_step;
                break;
            }
        default:
            {
                break;
            }
        }

        if (!new_step) continue;

        // Create an enabled item with this step
        LLSD row;
        row["columns"][0]["value"] = getLabel( new_step->getLabel());
        row["columns"][0]["font"] = "SANSSERIF_SMALL";
        LLScrollListItem* item = mStepList->addElement(row);
        item->setUserdata(new_step);
    }
}

// Helpful structure so we can look up the inventory item
// after the save finishes.
struct LLSaveInfo
{
    LLSaveInfo(const LLUUID& item_id, const LLUUID& object_id, const std::string& desc,
                const LLTransactionID tid)
        : mItemUUID(item_id), mObjectUUID(object_id), mDesc(desc), mTransactionID(tid)
    {
    }

    LLUUID mItemUUID;
    LLUUID mObjectUUID;
    std::string mDesc;
    LLTransactionID mTransactionID;
};


void LLPreviewGesture::finishInventoryUpload(LLUUID itemId, LLUUID newAssetId)
{
    // If this gesture is active, then we need to update the in-memory
    // active map with the new pointer.
    if (LLGestureMgr::instance().isGestureActive(itemId))
    {
        // Active gesture edited from menu.
        LLGestureMgr::instance().replaceGesture(itemId, newAssetId);
        gInventory.notifyObservers();
    }

    //gesture will have a new asset_id
    LLPreviewGesture* previewp = LLFloaterReg::findTypedInstance<LLPreviewGesture>("preview_gesture", LLSD(itemId));
    if (previewp)
    {
        previewp->onUpdateSucceeded();
    }
}


void LLPreviewGesture::saveIfNeeded()
{
    if (!gAssetStorage)
    {
        LL_WARNS() << "Can't save gesture, no asset storage system." << LL_ENDL;
        return;
    }

    if (!mDirty)
    {
        return;
    }

    // Copy the UI into a gesture
    LLMultiGesture* gesture = createGesture();

    // Serialize the gesture
    S32 maxSize = gesture->getMaxSerialSize();
    char* buffer = new char[maxSize];

    LLDataPackerAsciiBuffer dp(buffer, maxSize);

    bool ok = gesture->serialize(dp);

    if (dp.getCurrentSize() > 1000)
    {
        LLNotificationsUtil::add("GestureSaveFailedTooManySteps");

        delete gesture;
        gesture = NULL;
        return;
    }
    else if (!ok)
    {
        LLNotificationsUtil::add("GestureSaveFailedTryAgain");
        delete gesture;
        gesture = NULL;
        return;
    }

    LLAssetID assetId;
    LLPreview::onCommit();
    bool delayedUpload(false);

    LLViewerInventoryItem* item = (LLViewerInventoryItem*) getItem();
    if (item)
    {
        const LLViewerRegion* region = gAgent.getRegion();
        if (!region)
        {
            LL_WARNS() << "Not connected to a region, cannot save gesture." << LL_ENDL;
            return;
        }
        std::string agent_url = region->getCapability("UpdateGestureAgentInventory");
        std::string task_url = region->getCapability("UpdateGestureTaskInventory");

        if (!agent_url.empty() && !task_url.empty())
        {
            std::string url;
            LLResourceUploadInfo::ptr_t uploadInfo;

            if (mObjectUUID.isNull() && !agent_url.empty())
            {
                //need to disable the preview floater so item
                //isn't re-saved before new asset arrives
                //fake out refresh.
                item->setComplete(false);
                refresh();
                item->setComplete(true);

                uploadInfo = std::make_shared<LLBufferedAssetUploadInfo>(mItemUUID, LLAssetType::AT_GESTURE, buffer,
                    [](LLUUID itemId, LLUUID newAssetId, LLUUID, LLSD)
                    {
                        LLPreviewGesture::finishInventoryUpload(itemId, newAssetId);
                    },
                    nullptr);
                url = agent_url;
            }
            else if (!mObjectUUID.isNull() && !task_url.empty())
            {
                uploadInfo = std::make_shared<LLBufferedAssetUploadInfo>(mObjectUUID, mItemUUID, LLAssetType::AT_GESTURE, buffer, nullptr, nullptr);
                url = task_url;
            }

            if (!url.empty() && uploadInfo)
            {
                delayedUpload = true;

                LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo);
            }

        }
        else if (gAssetStorage)
        {
            // Every save gets a new UUID.  Yup.
            LLTransactionID tid;
            tid.generate();
            assetId = tid.makeAssetID(gAgent.getSecureSessionID());

            LLFileSystem file(assetId, LLAssetType::AT_GESTURE, LLFileSystem::APPEND);

            S32 size = dp.getCurrentSize();
            file.write((U8*)buffer, size);

            LLLineEditor* descEditor = getChild<LLLineEditor>("desc");
            LLSaveInfo* info = new LLSaveInfo(mItemUUID, mObjectUUID, descEditor->getText(), tid);
            gAssetStorage->storeAssetData(tid, LLAssetType::AT_GESTURE, onSaveComplete, info, false);
        }

    }

    // If this gesture is active, then we need to update the in-memory
    // active map with the new pointer.
    if (!delayedUpload && LLGestureMgr::instance().isGestureActive(mItemUUID))
    {
        // gesture manager now owns the pointer
        LLGestureMgr::instance().replaceGesture(mItemUUID, gesture, assetId);

        // replaceGesture may deactivate other gestures so let the
        // inventory know.
        gInventory.notifyObservers();
    }
    else
    {
        // we're done with this gesture
        delete gesture;
        gesture = NULL;
    }

    mDirty = false;
    // refresh will be called when callback
    // if triggered when delayedUpload
    if(!delayedUpload)
    {
        refresh();
    }

}


// TODO: This is very similar to LLPreviewNotecard::onSaveComplete.
// Could merge code.
// static
void LLPreviewGesture::onSaveComplete(const LLUUID& asset_uuid, void* user_data, S32 status, LLExtStat ext_status) // StoreAssetData callback (fixed)
{
    LLSaveInfo* info = (LLSaveInfo*)user_data;
    if (info && (status == 0))
    {
        if(info->mObjectUUID.isNull())
        {
            // Saving into user inventory
            LLViewerInventoryItem* item;
            item = (LLViewerInventoryItem*)gInventory.getItem(info->mItemUUID);
            if(item)
            {
                LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item);
                new_item->setDescription(info->mDesc);
                new_item->setTransactionID(info->mTransactionID);
                new_item->setAssetUUID(asset_uuid);
                new_item->updateServer(false);
                gInventory.updateItem(new_item);
                gInventory.notifyObservers();
            }
            else
            {
                LL_WARNS() << "Inventory item for gesture " << info->mItemUUID
                        << " is no longer in agent inventory." << LL_ENDL;
            }
        }
        else
        {
            // Saving into in-world object inventory
            LLViewerObject* object = gObjectList.findObject(info->mObjectUUID);
            LLViewerInventoryItem* item = NULL;
            if(object)
            {
                item = (LLViewerInventoryItem*)object->getInventoryObject(info->mItemUUID);
            }
            if(object && item)
            {
                item->setDescription(info->mDesc);
                item->setAssetUUID(asset_uuid);
                item->setTransactionID(info->mTransactionID);
                object->updateInventory(item, TASK_INVENTORY_ITEM_KEY, false);
                dialog_refresh_all();
            }
            else
            {
                LLNotificationsUtil::add("GestureSaveFailedObjectNotFound");
            }
        }

        // Find our window and close it if requested.
        LLPreviewGesture* previewp = LLFloaterReg::findTypedInstance<LLPreviewGesture>("preview_gesture", info->mItemUUID);
        if (previewp && previewp->mCloseAfterSave)
        {
            previewp->closeFloater();
        }
    }
    else
    {
        LL_WARNS() << "Problem saving gesture: " << status << LL_ENDL;
        LLSD args;
        args["REASON"] = std::string(LLAssetStorage::getErrorString(status));
        LLNotificationsUtil::add("GestureSaveFailedReason", args);
    }
    delete info;
    info = NULL;
}


LLMultiGesture* LLPreviewGesture::createGesture()
{
    LLMultiGesture* gesture = new LLMultiGesture();

    gesture->mTrigger = mTriggerEditor->getText();
    gesture->mReplaceText = mReplaceEditor->getText();

    const std::string& modifier = mModifierCombo->getSimple();
    if (modifier == CTRL_LABEL)
    {
        gesture->mMask = MASK_CONTROL;
    }
    else if (modifier == SHIFT_LABEL)
    {
        gesture->mMask = MASK_SHIFT;
    }
    else
    {
        gesture->mMask = MASK_NONE;
    }

    if (mKeyCombo->getCurrentIndex() == 0)
    {
        gesture->mKey = KEY_NONE;
    }
    else
    {
        const std::string& key_string = mKeyCombo->getSimple();
        LLKeyboard::keyFromString(key_string, &(gesture->mKey));
    }

    std::vector<LLScrollListItem*> data_list = mStepList->getAllData();
    std::vector<LLScrollListItem*>::iterator data_itor;
    for (data_itor = data_list.begin(); data_itor != data_list.end(); ++data_itor)
    {
        LLScrollListItem* item = *data_itor;
        LLGestureStep* step = (LLGestureStep*)item->getUserdata();

        switch(step->getType())
        {
        case STEP_ANIMATION:
            {
                // Copy UI-generated step into actual gesture step
                LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step;
                LLGestureStepAnimation* new_anim_step =
                    new LLGestureStepAnimation(*anim_step);
                gesture->mSteps.push_back(new_anim_step);
                break;
            }
        case STEP_SOUND:
            {
                // Copy UI-generated step into actual gesture step
                LLGestureStepSound* sound_step = (LLGestureStepSound*)step;
                LLGestureStepSound* new_sound_step =
                    new LLGestureStepSound(*sound_step);
                gesture->mSteps.push_back(new_sound_step);
                break;
            }
        case STEP_CHAT:
            {
                // Copy UI-generated step into actual gesture step
                LLGestureStepChat* chat_step = (LLGestureStepChat*)step;
                LLGestureStepChat* new_chat_step =
                    new LLGestureStepChat(*chat_step);
                gesture->mSteps.push_back(new_chat_step);
                break;
            }
        case STEP_WAIT:
            {
                // Copy UI-generated step into actual gesture step
                LLGestureStepWait* wait_step = (LLGestureStepWait*)step;
                LLGestureStepWait* new_wait_step =
                    new LLGestureStepWait(*wait_step);
                gesture->mSteps.push_back(new_wait_step);
                break;
            }
        default:
            {
                break;
            }
        }
    }

    return gesture;
}


void LLPreviewGesture::onCommitKeyorModifier()
{
    // SL-14139: ctrl-F10 is currently used to access top menu,
    // so don't allow to bound gestures to this combination.

    mKeyCombo->setEnabledByValue(LLKeyboard::stringFromKey(KEY_F10), mModifierCombo->getSimple() != CTRL_LABEL);
    mModifierCombo->setEnabledByValue(CTRL_LABEL, mKeyCombo->getSimple() != LLKeyboard::stringFromKey(KEY_F10));
    mDirty = true;
    refresh();
}

// static
void LLPreviewGesture::updateLabel(LLScrollListItem* item)
{
    LLGestureStep* step = (LLGestureStep*)item->getUserdata();

    LLScrollListCell* cell = item->getColumn(0);
    LLScrollListText* text_cell = (LLScrollListText*)cell;
    std::string label = getLabel( step->getLabel());
    text_cell->setText(label);
}

// static
void LLPreviewGesture::onCommitSetDirty(LLUICtrl* ctrl, void* data)
{
    LLPreviewGesture* self = (LLPreviewGesture*)data;
    self->mDirty = true;
    self->refresh();
}

// static
void LLPreviewGesture::onCommitLibrary(LLUICtrl* ctrl, void* data)
{
    LLPreviewGesture* self = (LLPreviewGesture*)data;

    LLScrollListItem* library_item = self->mLibraryList->getFirstSelected();
    if (library_item)
    {
        self->mStepList->deselectAllItems();
        self->refresh();
    }
}


// static
void LLPreviewGesture::onCommitStep(LLUICtrl* ctrl, void* data)
{
    LLPreviewGesture* self = (LLPreviewGesture*)data;

    LLScrollListItem* step_item = self->mStepList->getFirstSelected();
    if (!step_item) return;

    self->mLibraryList->deselectAllItems();
    self->refresh();
}


// static
void LLPreviewGesture::onCommitAnimation(LLUICtrl* ctrl, void* data)
{
    LLPreviewGesture* self = (LLPreviewGesture*)data;

    LLScrollListItem* step_item = self->mStepList->getFirstSelected();
    if (step_item)
    {
        LLGestureStep* step = (LLGestureStep*)step_item->getUserdata();
        if (step->getType() == STEP_ANIMATION)
        {
            // Assign the animation name
            LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step;
            if (self->mAnimationCombo->getCurrentIndex() == 0)
            {
                anim_step->mAnimName.clear();
                anim_step->mAnimAssetID.setNull();
            }
            else
            {
                anim_step->mAnimName = self->mAnimationCombo->getSimple();
                anim_step->mAnimAssetID = self->mAnimationCombo->getCurrentID();
            }
            //anim_step->mFlags = 0x0;

            // Update the UI label in the list
            updateLabel(step_item);

            self->mDirty = true;
            self->refresh();
        }
    }
}

// static
void LLPreviewGesture::onCommitAnimationTrigger(LLUICtrl* ctrl, void *data)
{
    LLPreviewGesture* self = (LLPreviewGesture*)data;

    LLScrollListItem* step_item = self->mStepList->getFirstSelected();
    if (step_item)
    {
        LLGestureStep* step = (LLGestureStep*)step_item->getUserdata();
        if (step->getType() == STEP_ANIMATION)
        {
            LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step;
            if (self->mAnimationRadio->getSelectedIndex() == 0)
            {
                // start
                anim_step->mFlags &= ~ANIM_FLAG_STOP;
            }
            else
            {
                // stop
                anim_step->mFlags |= ANIM_FLAG_STOP;
            }
            // Update the UI label in the list
            updateLabel(step_item);

            self->mDirty = true;
            self->refresh();
        }
    }
}

// static
void LLPreviewGesture::onCommitSound(LLUICtrl* ctrl, void* data)
{
    LLPreviewGesture* self = (LLPreviewGesture*)data;

    LLScrollListItem* step_item = self->mStepList->getFirstSelected();
    if (step_item)
    {
        LLGestureStep* step = (LLGestureStep*)step_item->getUserdata();
        if (step->getType() == STEP_SOUND)
        {
            // Assign the sound name
            LLGestureStepSound* sound_step = (LLGestureStepSound*)step;
            sound_step->mSoundName = self->mSoundCombo->getSimple();
            sound_step->mSoundAssetID = self->mSoundCombo->getCurrentID();
            sound_step->mFlags = 0x0;

            // Update the UI label in the list
            updateLabel(step_item);

            self->mDirty = true;
            self->refresh();
        }
    }
}

// static
void LLPreviewGesture::onCommitChat(LLUICtrl* ctrl, void* data)
{
    LLPreviewGesture* self = (LLPreviewGesture*)data;

    LLScrollListItem* step_item = self->mStepList->getFirstSelected();
    if (!step_item) return;

    LLGestureStep* step = (LLGestureStep*)step_item->getUserdata();
    if (step->getType() != STEP_CHAT) return;

    LLGestureStepChat* chat_step = (LLGestureStepChat*)step;
    chat_step->mChatText = self->mChatEditor->getText();
    chat_step->mFlags = 0x0;

    // Update the UI label in the list
    updateLabel(step_item);

    self->mDirty = true;
    self->refresh();
}

// static
void LLPreviewGesture::onCommitWait(LLUICtrl* ctrl, void* data)
{
    LLPreviewGesture* self = (LLPreviewGesture*)data;

    LLScrollListItem* step_item = self->mStepList->getFirstSelected();
    if (!step_item) return;

    LLGestureStep* step = (LLGestureStep*)step_item->getUserdata();
    if (step->getType() != STEP_WAIT) return;

    LLGestureStepWait* wait_step = (LLGestureStepWait*)step;
    U32 flags = 0x0;
    if (self->mWaitKeyReleaseCheck->get()) flags |= WAIT_FLAG_KEY_RELEASE;
    if (self->mWaitAnimCheck->get()) flags |= WAIT_FLAG_ALL_ANIM;
    if (self->mWaitTimeCheck->get()) flags |= WAIT_FLAG_TIME;
    wait_step->mFlags = flags;

    {
        LLLocale locale(LLLocale::USER_LOCALE);

        F32 wait_seconds = (F32)atof(self->mWaitTimeEditor->getText().c_str());
        if (wait_seconds < 0.f) wait_seconds = 0.f;
        if (wait_seconds > 3600.f) wait_seconds = 3600.f;
        wait_step->mWaitSeconds = wait_seconds;
    }

    // Enable the input area if necessary
    self->mWaitTimeEditor->setEnabled(self->mWaitTimeCheck->get());

    // Update the UI label in the list
    updateLabel(step_item);

    self->mDirty = true;
    self->refresh();
}

// static
void LLPreviewGesture::onCommitWaitTime(LLUICtrl* ctrl, void* data)
{
    LLPreviewGesture* self = (LLPreviewGesture*)data;

    LLScrollListItem* step_item = self->mStepList->getFirstSelected();
    if (!step_item) return;

    LLGestureStep* step = (LLGestureStep*)step_item->getUserdata();
    if (step->getType() != STEP_WAIT) return;

    self->mWaitTimeCheck->set(true);
    onCommitWait(ctrl, data);
}


// static
void LLPreviewGesture::onKeystrokeCommit(LLLineEditor* caller,
                                         void* data)
{
    // Just commit every keystroke
    onCommitSetDirty(caller, data);
}

// static
void LLPreviewGesture::onClickAdd(void* data)
{
    LLPreviewGesture* self = (LLPreviewGesture*)data;

    LLScrollListItem* library_item = self->mLibraryList->getFirstSelected();
    if (!library_item) return;

    S32 library_item_index = self->mLibraryList->getFirstSelectedIndex();

    const LLScrollListCell* library_cell = library_item->getColumn(0);
    const std::string& library_text = library_cell->getValue().asString();

    if( library_item_index >= STEP_EOF )
    {
        LL_ERRS() << "Unknown step type: " << library_text << LL_ENDL;
        return;
    }

    self->addStep( (EStepType)library_item_index );
    self->mDirty = true;
    self->refresh();
}

LLScrollListItem* LLPreviewGesture::addStep( const EStepType step_type )
{
    // Order of enum EStepType MUST match the library_list element in floater_preview_gesture.xml

    LLGestureStep* step = NULL;
    switch( step_type)
    {
        case STEP_ANIMATION:
            step = new LLGestureStepAnimation();

            break;
        case STEP_SOUND:
            step = new LLGestureStepSound();
            break;
        case STEP_CHAT:
            step = new LLGestureStepChat();
            break;
        case STEP_WAIT:
            step = new LLGestureStepWait();
            break;
        default:
            LL_ERRS() << "Unknown step type: " << (S32)step_type << LL_ENDL;
            return NULL;
    }


    // Create an enabled item with this step
    LLSD row;
    row["columns"][0]["value"] = getLabel(step->getLabel());
    row["columns"][0]["font"] = "SANSSERIF_SMALL";
    LLScrollListItem* step_item = mStepList->addElement(row);
    step_item->setUserdata(step);

    // And move selection to the list on the right
    mLibraryList->deselectAllItems();
    mStepList->deselectAllItems();

    step_item->setSelected(true);

    return step_item;
}

// static
std::string LLPreviewGesture::getLabel(std::vector<std::string> labels)
{
    std::vector<std::string> v_labels = labels ;
    std::string result("");

    if( v_labels.size() != 2)
    {
        return result;
    }

    if(v_labels[0]=="Chat")
    {
        result=LLTrans::getString("Chat Message");
    }
    else if(v_labels[0]=="Sound")
    {
        result=LLTrans::getString("Sound");
    }
    else if(v_labels[0]=="Wait")
    {
        result=LLTrans::getString("Wait");
    }
    else if(v_labels[0]=="AnimFlagStop")
    {
        result=LLTrans::getString("AnimFlagStop");
    }
    else if(v_labels[0]=="AnimFlagStart")
    {
        result=LLTrans::getString("AnimFlagStart");
    }

    // lets localize action value
    std::string action = v_labels[1];
    if ("None" == action)
    {
        action = LLTrans::getString("GestureActionNone");
    }
    else if ("until animations are done" == action)
    {
        action = LLFloaterReg::getInstance("preview_gesture")->getChild<LLCheckBoxCtrl>("wait_anim_check")->getLabel();
    }
    result.append(action);
    return result;

}
// static
void LLPreviewGesture::onClickUp(void* data)
{
    LLPreviewGesture* self = (LLPreviewGesture*)data;

    S32 selected_index = self->mStepList->getFirstSelectedIndex();
    if (selected_index > 0)
    {
        self->mStepList->swapWithPrevious(selected_index);
        self->mDirty = true;
        self->refresh();
    }
}

// static
void LLPreviewGesture::onClickDown(void* data)
{
    LLPreviewGesture* self = (LLPreviewGesture*)data;

    S32 selected_index = self->mStepList->getFirstSelectedIndex();
    if (selected_index < 0) return;

    S32 count = self->mStepList->getItemCount();
    if (selected_index < count-1)
    {
        self->mStepList->swapWithNext(selected_index);
        self->mDirty = true;
        self->refresh();
    }
}

// static
void LLPreviewGesture::onClickDelete(void* data)
{
    LLPreviewGesture* self = (LLPreviewGesture*)data;

    LLScrollListItem* item = self->mStepList->getFirstSelected();
    S32 selected_index = self->mStepList->getFirstSelectedIndex();
    if (item && selected_index >= 0)
    {
        LLGestureStep* step = (LLGestureStep*)item->getUserdata();
        delete step;
        step = NULL;

        self->mStepList->deleteSingleItem(selected_index);

        self->mDirty = true;
        self->refresh();
    }
}

// static
void LLPreviewGesture::onCommitActive(LLUICtrl* ctrl, void* data)
{
    LLPreviewGesture* self = (LLPreviewGesture*)data;
    if (!LLGestureMgr::instance().isGestureActive(self->mItemUUID))
    {
        LLGestureMgr::instance().activateGesture(self->mItemUUID);
    }
    else
    {
        LLGestureMgr::instance().deactivateGesture(self->mItemUUID);
    }

    // Make sure the (active) label in the inventory gets updated.
    LLViewerInventoryItem* item = gInventory.getItem(self->mItemUUID);
    if (item)
    {
        gInventory.updateItem(item);
        gInventory.notifyObservers();
    }

    self->refresh();
}

// static
void LLPreviewGesture::onClickSave(void* data)
{
    LLPreviewGesture* self = (LLPreviewGesture*)data;
    self->saveIfNeeded();
}

// static
void LLPreviewGesture::onClickPreview(void* data)
{
    LLPreviewGesture* self = (LLPreviewGesture*)data;

    if (!self->mPreviewGesture)
    {
        // make temporary gesture
        self->mPreviewGesture = self->createGesture();

        // add a callback
        self->mPreviewGesture->mDoneCallback = onDonePreview;
        self->mPreviewGesture->mCallbackData = self;

        // set the button title
        self->mPreviewBtn->setLabel(self->getString("stop_txt"));

        // play it, and delete when done
        LLGestureMgr::instance().playGesture(self->mPreviewGesture);

        self->refresh();
    }
    else
    {
        // Will call onDonePreview() below
        LLGestureMgr::instance().stopGesture(self->mPreviewGesture);

        self->refresh();
    }
}


// static
void LLPreviewGesture::onDonePreview(LLMultiGesture* gesture, void* data)
{
    LLPreviewGesture* self = (LLPreviewGesture*)data;

    self->mPreviewBtn->setLabel(self->getString("preview_txt"));

    delete self->mPreviewGesture;
    self->mPreviewGesture = NULL;

    self->refresh();
}