A few additional constants for the math stuff, mostly just "for fun" items. Minor fix noted from Imprudence chat of possible crashes. Removal of excess items in the gesture menu. Inclusion of files for tattoo tinting. Reordered the client definitions for alphabetical order. Signed-off-by: Beeks <HgDelirium@gmail.com>
1792 lines
44 KiB
C++
1792 lines
44 KiB
C++
/**
|
|
* @file llpreviewgesture.cpp
|
|
* @brief Editing UI for inventory-based gestures.
|
|
*
|
|
* $LicenseInfo:firstyear=2004&license=viewergpl$
|
|
*
|
|
* Copyright (c) 2004-2009, Linden Research, Inc.
|
|
*
|
|
* Second Life Viewer Source Code
|
|
* The source code in this file ("Source Code") is provided by Linden Lab
|
|
* to you under the terms of the GNU General Public License, version 2.0
|
|
* ("GPL"), unless you have obtained a separate licensing agreement
|
|
* ("Other License"), formally executed by you and Linden Lab. Terms of
|
|
* the GPL can be found in doc/GPL-license.txt in this distribution, or
|
|
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
|
|
*
|
|
* There are special exceptions to the terms and conditions of the GPL as
|
|
* it is applied to this Source Code. View the full text of the exception
|
|
* in the file doc/FLOSS-exception.txt in this software distribution, or
|
|
* online at
|
|
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
|
|
*
|
|
* By copying, modifying or distributing this software, you acknowledge
|
|
* that you have read and understood your obligations described above,
|
|
* and agree to abide by those obligations.
|
|
*
|
|
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
|
|
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
|
|
* COMPLETENESS OR PERFORMANCE.
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "llviewerprecompiledheaders.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include "llpreviewgesture.h"
|
|
|
|
// libraries
|
|
#include "lldatapacker.h"
|
|
#include "lldarray.h"
|
|
#include "llstring.h"
|
|
#include "lldir.h"
|
|
#include "llmultigesture.h"
|
|
#include "llvfile.h"
|
|
|
|
// newview
|
|
#include "llagent.h" // todo: remove
|
|
#include "llassetuploadresponders.h"
|
|
#include "llbutton.h"
|
|
#include "llcheckboxctrl.h"
|
|
#include "llcombobox.h"
|
|
#include "lldelayedgestureerror.h"
|
|
#include "llfloatergesture.h" // for some label constants
|
|
#include "llgesturemgr.h"
|
|
#include "llinventorymodel.h"
|
|
#include "llkeyboard.h"
|
|
#include "lllineeditor.h"
|
|
#include "llnotify.h"
|
|
#include "llradiogroup.h"
|
|
#include "llscrolllistctrl.h"
|
|
#include "lltextbox.h"
|
|
#include "lluictrlfactory.h"
|
|
#include "llviewerinventory.h"
|
|
#include "llviewerobject.h"
|
|
#include "llviewerobjectlist.h"
|
|
#include "llviewerregion.h"
|
|
#include "llviewerstats.h"
|
|
#include "llviewerwindow.h" // busycount
|
|
#include "llappviewer.h" // gVFS
|
|
#include "llanimstatelabels.h"
|
|
#include "llresmgr.h"
|
|
|
|
|
|
// *TODO: Translate?
|
|
const std::string NONE_LABEL = "---";
|
|
const std::string SHIFT_LABEL = "Shift";
|
|
const std::string CTRL_LABEL = "Ctrl";
|
|
|
|
void dialog_refresh_all();
|
|
|
|
// used for getting
|
|
|
|
class LLInventoryGestureAvailable : public LLInventoryCompletionObserver
|
|
{
|
|
public:
|
|
LLInventoryGestureAvailable() {}
|
|
|
|
protected:
|
|
virtual void done();
|
|
};
|
|
|
|
void LLInventoryGestureAvailable::done()
|
|
{
|
|
LLPreview* preview = NULL;
|
|
item_ref_t::iterator it = mComplete.begin();
|
|
item_ref_t::iterator end = mComplete.end();
|
|
for(; it < end; ++it)
|
|
{
|
|
preview = LLPreview::find((*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 std::string& title, const LLUUID& item_id, const LLUUID& object_id, BOOL take_focus)
|
|
{
|
|
LLPreviewGesture* previewp = (LLPreviewGesture*)LLPreview::find(item_id);
|
|
if (previewp)
|
|
{
|
|
previewp->open(); /*Flawfinder: ignore*/
|
|
if (take_focus)
|
|
{
|
|
previewp->setFocus(TRUE);
|
|
}
|
|
return previewp;
|
|
}
|
|
|
|
LLPreviewGesture* self = new LLPreviewGesture();
|
|
|
|
// Finish internal construction
|
|
self->init(item_id, object_id);
|
|
|
|
// Builds and adds to gFloaterView
|
|
LLUICtrlFactory::getInstance()->buildFloater(self, "floater_preview_gesture.xml");
|
|
self->setTitle(title);
|
|
|
|
// Move window to top-left of screen
|
|
LLMultiFloater* hostp = self->getHost();
|
|
if (hostp == NULL)
|
|
{
|
|
LLRect r = self->getRect();
|
|
LLRect screen = gFloaterView->getRect();
|
|
r.setLeftTopAndSize(0, screen.getHeight(), r.getWidth(), r.getHeight());
|
|
self->setRect(r);
|
|
}
|
|
else
|
|
{
|
|
// re-add to host to update title
|
|
hostp->addFloater(self, TRUE);
|
|
}
|
|
|
|
// Start speculative download of sounds and animations
|
|
LLUUID animation_folder_id = gInventory.findCategoryUUIDForType(LLAssetType::AT_ANIMATION);
|
|
gInventory.startBackgroundFetch(animation_folder_id);
|
|
|
|
LLUUID sound_folder_id = gInventory.findCategoryUUIDForType(LLAssetType::AT_SOUND);
|
|
gInventory.startBackgroundFetch(sound_folder_id);
|
|
|
|
// this will call refresh when we have everything.
|
|
LLViewerInventoryItem* item = (LLViewerInventoryItem*)self->getItem();
|
|
if(item && !item->isComplete())
|
|
{
|
|
LLInventoryGestureAvailable* observer;
|
|
observer = new LLInventoryGestureAvailable();
|
|
observer->watchItem(item_id);
|
|
gInventory.addObserver(observer);
|
|
item->fetchFromServer();
|
|
}
|
|
else
|
|
{
|
|
// not sure this is necessary.
|
|
self->refresh();
|
|
}
|
|
|
|
if (take_focus)
|
|
{
|
|
self->setFocus(TRUE);
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
|
|
// 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
|
|
{
|
|
// Bring up view-modal dialog: Save changes? Yes, No, Cancel
|
|
LLNotifications::instance().add("SaveChanges", LLSD(), LLSD(),
|
|
boost::bind(&LLPreviewGesture::handleSaveChangesDialog, this, _1, _2) );
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// virtual
|
|
void LLPreviewGesture::onClose(bool app_quitting)
|
|
{
|
|
gGestureManager.stopGesture(mPreviewGesture);
|
|
LLPreview::onClose(app_quitting);
|
|
}
|
|
|
|
// virtual
|
|
void LLPreviewGesture::onUpdateSucceeded()
|
|
{
|
|
refresh();
|
|
}
|
|
|
|
// virtual
|
|
void LLPreviewGesture::setMinimized(BOOL minimize)
|
|
{
|
|
if (minimize != isMinimized())
|
|
{
|
|
LLFloater::setMinimized(minimize);
|
|
|
|
// We're being restored
|
|
if (!minimize)
|
|
{
|
|
refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool LLPreviewGesture::handleSaveChangesDialog(const LLSD& notification, const LLSD& response)
|
|
{
|
|
S32 option = LLNotification::getSelectedOption(notification, response);
|
|
switch(option)
|
|
{
|
|
case 0: // "Yes"
|
|
gGestureManager.stopGesture(mPreviewGesture);
|
|
mCloseAfterSave = TRUE;
|
|
onClickSave(this);
|
|
break;
|
|
|
|
case 1: // "No"
|
|
gGestureManager.stopGesture(mPreviewGesture);
|
|
mDirty = FALSE; // Force the dirty flag because user has clicked NO on confirm save dialog...
|
|
close();
|
|
break;
|
|
|
|
case 2: // "Cancel"
|
|
default:
|
|
// If we were quitting, we didn't really mean it.
|
|
LLAppViewer::instance()->abortQuit();
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
LLPreviewGesture::LLPreviewGesture()
|
|
: LLPreview("Gesture Preview"),
|
|
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)
|
|
{
|
|
}
|
|
|
|
|
|
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()
|
|
{
|
|
LLLineEditor* edit;
|
|
LLComboBox* combo;
|
|
LLButton* btn;
|
|
LLScrollListCtrl* list;
|
|
LLTextBox* text;
|
|
LLCheckBoxCtrl* check;
|
|
|
|
edit = getChild<LLLineEditor>("trigger_editor");
|
|
edit->setKeystrokeCallback(onKeystrokeCommit);
|
|
edit->setCommitCallback(onCommitSetDirty);
|
|
edit->setCommitOnFocusLost(TRUE);
|
|
edit->setCallbackUserData(this);
|
|
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);
|
|
edit->setCommitCallback(onCommitSetDirty);
|
|
edit->setCommitOnFocusLost(TRUE);
|
|
edit->setCallbackUserData(this);
|
|
edit->setIgnoreTab(TRUE);
|
|
mReplaceEditor = edit;
|
|
|
|
combo = getChild<LLComboBox>( "modifier_combo");
|
|
combo->setCommitCallback(onCommitSetDirty);
|
|
combo->setCallbackUserData(this);
|
|
mModifierCombo = combo;
|
|
|
|
combo = getChild<LLComboBox>( "key_combo");
|
|
combo->setCommitCallback(onCommitSetDirty);
|
|
combo->setCallbackUserData(this);
|
|
mKeyCombo = combo;
|
|
|
|
list = getChild<LLScrollListCtrl>("library_list");
|
|
list->setCommitCallback(onCommitLibrary);
|
|
list->setDoubleClickCallback(onClickAdd);
|
|
list->setCallbackUserData(this);
|
|
mLibraryList = list;
|
|
|
|
btn = getChild<LLButton>( "add_btn");
|
|
btn->setClickedCallback(onClickAdd);
|
|
btn->setCallbackUserData(this);
|
|
btn->setEnabled(FALSE);
|
|
mAddBtn = btn;
|
|
|
|
btn = getChild<LLButton>( "up_btn");
|
|
btn->setClickedCallback(onClickUp);
|
|
btn->setCallbackUserData(this);
|
|
btn->setEnabled(FALSE);
|
|
mUpBtn = btn;
|
|
|
|
btn = getChild<LLButton>( "down_btn");
|
|
btn->setClickedCallback(onClickDown);
|
|
btn->setCallbackUserData(this);
|
|
btn->setEnabled(FALSE);
|
|
mDownBtn = btn;
|
|
|
|
btn = getChild<LLButton>( "delete_btn");
|
|
btn->setClickedCallback(onClickDelete);
|
|
btn->setCallbackUserData(this);
|
|
btn->setEnabled(FALSE);
|
|
mDeleteBtn = btn;
|
|
|
|
list = getChild<LLScrollListCtrl>("step_list");
|
|
list->setCommitCallback(onCommitStep);
|
|
list->setCallbackUserData(this);
|
|
mStepList = list;
|
|
|
|
// Options
|
|
text = getChild<LLTextBox>("options_text");
|
|
text->setBorderVisible(TRUE);
|
|
mOptionsText = text;
|
|
|
|
combo = getChild<LLComboBox>( "animation_list");
|
|
combo->setVisible(FALSE);
|
|
combo->setCommitCallback(onCommitAnimation);
|
|
combo->setCallbackUserData(this);
|
|
mAnimationCombo = combo;
|
|
|
|
LLRadioGroup* group;
|
|
group = getChild<LLRadioGroup>("animation_trigger_type");
|
|
group->setVisible(FALSE);
|
|
group->setCommitCallback(onCommitAnimationTrigger);
|
|
group->setCallbackUserData(this);
|
|
mAnimationRadio = group;
|
|
|
|
combo = getChild<LLComboBox>( "sound_list");
|
|
combo->setVisible(FALSE);
|
|
combo->setCommitCallback(onCommitSound);
|
|
combo->setCallbackUserData(this);
|
|
mSoundCombo = combo;
|
|
|
|
edit = getChild<LLLineEditor>("chat_editor");
|
|
edit->setVisible(FALSE);
|
|
edit->setCommitCallback(onCommitChat);
|
|
//edit->setKeystrokeCallback(onKeystrokeCommit);
|
|
edit->setCommitOnFocusLost(TRUE);
|
|
edit->setCallbackUserData(this);
|
|
edit->setIgnoreTab(TRUE);
|
|
mChatEditor = edit;
|
|
|
|
check = getChild<LLCheckBoxCtrl>( "wait_anim_check");
|
|
check->setVisible(FALSE);
|
|
check->setCommitCallback(onCommitWait);
|
|
check->setCallbackUserData(this);
|
|
mWaitAnimCheck = check;
|
|
|
|
check = getChild<LLCheckBoxCtrl>( "wait_time_check");
|
|
check->setVisible(FALSE);
|
|
check->setCommitCallback(onCommitWait);
|
|
check->setCallbackUserData(this);
|
|
mWaitTimeCheck = check;
|
|
|
|
edit = getChild<LLLineEditor>("wait_time_editor");
|
|
edit->setEnabled(FALSE);
|
|
edit->setVisible(FALSE);
|
|
edit->setPrevalidate(LLLineEditor::prevalidateFloat);
|
|
// edit->setKeystrokeCallback(onKeystrokeCommit);
|
|
edit->setCommitOnFocusLost(TRUE);
|
|
edit->setCommitCallback(onCommitWaitTime);
|
|
edit->setCallbackUserData(this);
|
|
edit->setIgnoreTab(TRUE);
|
|
mWaitTimeEditor = edit;
|
|
|
|
// Buttons at the bottom
|
|
check = getChild<LLCheckBoxCtrl>( "active_check");
|
|
check->setCommitCallback(onCommitActive);
|
|
check->setCallbackUserData(this);
|
|
mActiveCheck = check;
|
|
|
|
btn = getChild<LLButton>( "save_btn");
|
|
btn->setClickedCallback(onClickSave);
|
|
btn->setCallbackUserData(this);
|
|
mSaveBtn = btn;
|
|
|
|
btn = getChild<LLButton>( "preview_btn");
|
|
btn->setClickedCallback(onClickPreview);
|
|
btn->setCallbackUserData(this);
|
|
mPreviewBtn = btn;
|
|
|
|
|
|
// Populate the comboboxes
|
|
addModifiers();
|
|
addKeys();
|
|
addAnimations();
|
|
addSounds();
|
|
|
|
|
|
const LLInventoryItem* item = getItem();
|
|
|
|
if (item)
|
|
{
|
|
childSetCommitCallback("desc", LLPreview::onText, this);
|
|
childSetText("desc", item->getDescription());
|
|
childSetPrevalidate("desc", &LLLineEditor::prevalidatePrintableNotPipe);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
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 );
|
|
|
|
// <edit>
|
|
//define for the lulz
|
|
#define addKey(k) combo->add( LLKeyboard::stringFromKey(k), ADD_BOTTOM );
|
|
for (KEY key = KEY_F2; key <= KEY_F12; key++)
|
|
addKey(key)
|
|
for (KEY key = ' '; key <= '~'; key++)
|
|
addKey(key)
|
|
// </edit>
|
|
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(gAgent.getInventoryRootID(),
|
|
cats,
|
|
items,
|
|
LLInventoryModel::EXCLUDE_TRASH,
|
|
is_copyable_animation);
|
|
|
|
// Copy into something we can sort
|
|
std::vector<LLInventoryItem*> animations;
|
|
|
|
S32 count = items.count();
|
|
for(i = 0; i < count; ++i)
|
|
{
|
|
animations.push_back( items.get(i) );
|
|
}
|
|
|
|
// 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(gAgent.getInventoryRootID(),
|
|
cats,
|
|
items,
|
|
LLInventoryModel::EXCLUDE_TRASH,
|
|
is_copyable_sound);
|
|
|
|
// Copy sounds into something we can sort
|
|
std::vector<LLInventoryItem*> sounds;
|
|
|
|
S32 i;
|
|
S32 count = items.count();
|
|
for(i = 0; i < count; ++i)
|
|
{
|
|
sounds.push_back( items.get(i) );
|
|
}
|
|
|
|
// 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::init(const LLUUID& item_id, const LLUUID& object_id)
|
|
{
|
|
// Sets ID and adds to instance list
|
|
setItemID(item_id);
|
|
setObjectID(object_id);
|
|
}
|
|
|
|
|
|
void LLPreviewGesture::refresh()
|
|
{
|
|
// If previewing or item is incomplete, all controls are disabled
|
|
LLViewerInventoryItem* item = (LLViewerInventoryItem*)getItem();
|
|
bool is_complete = (item && item->isComplete()) ? true : false;
|
|
if (mPreviewGesture || !is_complete)
|
|
{
|
|
|
|
childSetEnabled("desc", 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);
|
|
mWaitAnimCheck->setEnabled(FALSE);
|
|
mWaitTimeCheck->setEnabled(FALSE);
|
|
mWaitTimeEditor->setEnabled(FALSE);
|
|
mActiveCheck->setEnabled(FALSE);
|
|
mSaveBtn->setEnabled(FALSE);
|
|
// <edit>
|
|
mStepList->setEnabled(TRUE);
|
|
// </edit>
|
|
|
|
// Make sure preview button is enabled, so we can stop it
|
|
mPreviewBtn->setEnabled(TRUE);
|
|
return;
|
|
}
|
|
|
|
BOOL modifiable = item->getPermissions().allowModifyBy(gAgent.getID());
|
|
|
|
childSetEnabled("desc", 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);
|
|
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);
|
|
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");
|
|
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 = gGestureManager.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 = "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 = "Hello, avatar!";
|
|
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) 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();
|
|
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(LLVFS *vfs,
|
|
const LLUUID& asset_uuid,
|
|
LLAssetType::EType type,
|
|
void* user_data, S32 status, LLExtStat ext_status)
|
|
{
|
|
LLUUID* item_idp = (LLUUID*)user_data;
|
|
LLPreview* preview = LLPreview::find(*item_idp);
|
|
if (preview)
|
|
{
|
|
LLPreviewGesture* self = (LLPreviewGesture*)preview;
|
|
|
|
if (0 == status)
|
|
{
|
|
LLVFile file(vfs, asset_uuid, type, LLVFile::READ);
|
|
S32 size = file.getSize();
|
|
|
|
char* buffer = new char[size+1];
|
|
file.read((U8*)buffer, size); /*Flawfinder: ignore*/
|
|
buffer[size] = '\0';
|
|
|
|
LLMultiGesture* gesture = new LLMultiGesture();
|
|
|
|
LLDataPackerAsciiBuffer dp(buffer, 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();
|
|
}
|
|
else
|
|
{
|
|
llwarns << "Unable to load gesture" << llendl;
|
|
}
|
|
|
|
delete gesture;
|
|
gesture = NULL;
|
|
|
|
delete [] buffer;
|
|
buffer = NULL;
|
|
|
|
self->mAssetStatus = PREVIEW_ASSET_LOADED;
|
|
}
|
|
else
|
|
{
|
|
LLViewerStats::getInstance()->incStat( LLViewerStats::ST_DOWNLOAD_FAILED );
|
|
|
|
if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status ||
|
|
LL_ERR_FILE_EMPTY == status)
|
|
{
|
|
LLDelayedGestureError::gestureMissing( *item_idp );
|
|
}
|
|
else
|
|
{
|
|
LLDelayedGestureError::gestureFailedToLoad( *item_idp );
|
|
}
|
|
|
|
llwarns << "Problem loading gesture: " << status << llendl;
|
|
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;
|
|
}
|
|
|
|
mKeyCombo->setCurrentByIndex(0);
|
|
if (gesture->mKey != KEY_NONE)
|
|
{
|
|
mKeyCombo->setSimple(LLKeyboard::stringFromKey(gesture->mKey));
|
|
}
|
|
|
|
// Make UI steps for each gesture step
|
|
S32 i;
|
|
S32 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"] = 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::saveIfNeeded()
|
|
{
|
|
if (!gAssetStorage)
|
|
{
|
|
llwarns << "Can't save gesture, no asset storage system." << llendl;
|
|
return;
|
|
}
|
|
|
|
if (!mDirty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Copy the UI into a gesture
|
|
LLMultiGesture* gesture = createGesture();
|
|
|
|
// Serialize the gesture
|
|
S32 max_size = gesture->getMaxSerialSize();
|
|
char* buffer = new char[max_size];
|
|
|
|
LLDataPackerAsciiBuffer dp(buffer, max_size);
|
|
|
|
BOOL ok = gesture->serialize(dp);
|
|
|
|
// <edit>
|
|
//if (dp.getCurrentSize() > 1000)
|
|
if(0)
|
|
// </edit>
|
|
{
|
|
LLNotifications::instance().add("GestureSaveFailedTooManySteps");
|
|
|
|
delete gesture;
|
|
gesture = NULL;
|
|
}
|
|
else if (!ok)
|
|
{
|
|
LLNotifications::instance().add("GestureSaveFailedTryAgain");
|
|
delete gesture;
|
|
gesture = NULL;
|
|
}
|
|
else
|
|
{
|
|
// Every save gets a new UUID. Yup.
|
|
LLTransactionID tid;
|
|
LLAssetID asset_id;
|
|
tid.generate();
|
|
asset_id = tid.makeAssetID(gAgent.getSecureSessionID());
|
|
|
|
LLVFile file(gVFS, asset_id, LLAssetType::AT_GESTURE, LLVFile::APPEND);
|
|
|
|
S32 size = dp.getCurrentSize();
|
|
file.setMaxSize(size);
|
|
file.write((U8*)buffer, size);
|
|
|
|
BOOL delayedUpload = FALSE;
|
|
|
|
// Upload that asset to the database
|
|
LLViewerInventoryItem* item = (LLViewerInventoryItem*) getItem();
|
|
if (item)
|
|
{
|
|
std::string agent_url = gAgent.getRegion()->getCapability("UpdateGestureAgentInventory");
|
|
std::string task_url = gAgent.getRegion()->getCapability("UpdateGestureTaskInventory");
|
|
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);
|
|
|
|
// Saving into agent inventory
|
|
LLSD body;
|
|
body["item_id"] = mItemUUID;
|
|
LLHTTPClient::post(agent_url, body,
|
|
new LLUpdateAgentInventoryResponder(body, asset_id, LLAssetType::AT_GESTURE));
|
|
delayedUpload = TRUE;
|
|
}
|
|
else if (!mObjectUUID.isNull() && !task_url.empty())
|
|
{
|
|
// Saving into task inventory
|
|
LLSD body;
|
|
body["task_id"] = mObjectUUID;
|
|
body["item_id"] = mItemUUID;
|
|
LLHTTPClient::post(task_url, body,
|
|
new LLUpdateTaskInventoryResponder(body, asset_id, LLAssetType::AT_GESTURE));
|
|
}
|
|
else if (gAssetStorage)
|
|
{
|
|
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 && gGestureManager.isGestureActive(mItemUUID))
|
|
{
|
|
// gesture manager now owns the pointer
|
|
gGestureManager.replaceGesture(mItemUUID, gesture, asset_id);
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
delete [] buffer;
|
|
buffer = NULL;
|
|
}
|
|
|
|
|
|
// 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
|
|
{
|
|
llwarns << "Inventory item for gesture " << info->mItemUUID
|
|
<< " is no longer in agent inventory." << llendl;
|
|
}
|
|
}
|
|
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
|
|
{
|
|
LLNotifications::instance().add("GestureSaveFailedObjectNotFound");
|
|
}
|
|
}
|
|
|
|
// Find our window and close it if requested.
|
|
LLPreviewGesture* previewp = (LLPreviewGesture*)LLPreview::find(info->mItemUUID);
|
|
if (previewp && previewp->mCloseAfterSave)
|
|
{
|
|
previewp->close();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
llwarns << "Problem saving gesture: " << status << llendl;
|
|
LLSD args;
|
|
args["REASON"] = std::string(LLAssetStorage::getErrorString(status));
|
|
LLNotifications::instance().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;
|
|
}
|
|
|
|
|
|
// static
|
|
void LLPreviewGesture::updateLabel(LLScrollListItem* item)
|
|
{
|
|
LLGestureStep* step = (LLGestureStep*)item->getUserdata();
|
|
|
|
LLScrollListCell* cell = item->getColumn(0);
|
|
LLScrollListText* text_cell = (LLScrollListText*)cell;
|
|
std::string label = 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->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 )
|
|
{
|
|
llerrs << "Unknown step type: " << library_text << llendl;
|
|
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:
|
|
llerrs << "Unknown step type: " << (S32)step_type << llendl;
|
|
return NULL;
|
|
}
|
|
|
|
// Create an enabled item with this step
|
|
LLSD row;
|
|
row["columns"][0]["value"] = 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
|
|
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 (!gGestureManager.isGestureActive(self->mItemUUID))
|
|
{
|
|
gGestureManager.activateGesture(self->mItemUUID);
|
|
}
|
|
else
|
|
{
|
|
gGestureManager.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
|
|
gGestureManager.playGesture(self->mPreviewGesture);
|
|
|
|
self->refresh();
|
|
}
|
|
else
|
|
{
|
|
// Will call onDonePreview() below
|
|
gGestureManager.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();
|
|
} |