/** * @file llpreviewscript.cpp * @brief LLPreviewScript class implementation * * $LicenseInfo:firstyear=2002&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llpreviewscript.h" #include "llassetstorage.h" #include "llbutton.h" #include "llcheckboxctrl.h" #include "llcombobox.h" #include "lldir.h" #include "llexternaleditor.h" #include "llfilepicker.h" #include "llfloaterreg.h" #include "llinventorydefines.h" #include "llinventorymodel.h" #include "llkeyboard.h" #include "lllineeditor.h" #include "llmd5.h" #include "llhelp.h" #include "llnotificationsutil.h" #include "llresmgr.h" #include "llscrollbar.h" #include "llscrollcontainer.h" #include "llscrolllistctrl.h" #include "llscrolllistitem.h" #include "llscrolllistcell.h" #include "llsdserialize.h" #include "llslider.h" #include "lltooldraganddrop.h" #include "llfilesystem.h" #include "llagent.h" #include "llmenugl.h" #include "roles_constants.h" #include "llselectmgr.h" #include "llviewerinventory.h" #include "llviewermenu.h" #include "llviewermenufile.h" // LLFilePickerReplyThread #include "llviewerobject.h" #include "llviewerobjectlist.h" #include "llviewerregion.h" #include "llkeyboard.h" #include "llscrollcontainer.h" #include "llcheckboxctrl.h" #include "llscripteditor.h" #include "llselectmgr.h" #include "lltooldraganddrop.h" #include "llscrolllistctrl.h" #include "lltextbox.h" #include "llslider.h" #include "lldir.h" #include "llcombobox.h" #include "llviewerstats.h" #include "llviewerwindow.h" #include "lluictrlfactory.h" #include "llmediactrl.h" #include "lluictrlfactory.h" #include "lltrans.h" #include "llviewercontrol.h" #include "llappviewer.h" #include "llfloatergotoline.h" #include "llexperiencecache.h" #include "llfloaterexperienceprofile.h" #include "llviewerassetupload.h" #include "lltoggleablemenu.h" #include "llmenubutton.h" #include "llinventoryfunctions.h" const std::string HELLO_LSL = "default\n" "{\n" " state_entry()\n" " {\n" " llSay(0, \"Hello, Avatar!\");\n" " }\n" "\n" " touch_start(integer total_number)\n" " {\n" " llSay(0, \"Touched.\");\n" " }\n" "}\n"; const std::string HELP_LSL_PORTAL_TOPIC = "LSL_Portal"; const std::string DEFAULT_SCRIPT_NAME = "New Script"; // *TODO:Translate? const std::string DEFAULT_SCRIPT_DESC = "(No Description)"; // *TODO:Translate? // Description and header information const S32 MAX_HISTORY_COUNT = 10; const F32 LIVE_HELP_REFRESH_TIME = 1.f; static bool have_script_upload_cap(LLUUID& object_id) { LLViewerObject* object = gObjectList.findObject(object_id); return object && (! object->getRegion()->getCapability("UpdateScriptTask").empty()); } /// --------------------------------------------------------------------------- /// LLLiveLSLFile /// --------------------------------------------------------------------------- LLLiveLSLFile::LLLiveLSLFile(std::string file_path, change_callback_t change_cb) : mOnChangeCallback(change_cb) , mIgnoreNextUpdate(false) , LLLiveFile(file_path, 1.0) { llassert(mOnChangeCallback); } LLLiveLSLFile::~LLLiveLSLFile() { LLFile::remove(filename()); } bool LLLiveLSLFile::loadFile() { if (mIgnoreNextUpdate) { mIgnoreNextUpdate = false; return true; } return mOnChangeCallback(filename()); } /// --------------------------------------------------------------------------- /// LLFloaterScriptSearch /// --------------------------------------------------------------------------- class LLFloaterScriptSearch : public LLFloater { public: LLFloaterScriptSearch(LLScriptEdCore* editor_core); ~LLFloaterScriptSearch(); /*virtual*/ bool postBuild(); static void show(LLScriptEdCore* editor_core); static void onBtnSearch(void* userdata); void handleBtnSearch(); static void onBtnReplace(void* userdata); void handleBtnReplace(); static void onBtnReplaceAll(void* userdata); void handleBtnReplaceAll(); LLScriptEdCore* getEditorCore() { return mEditorCore; } static LLFloaterScriptSearch* getInstance() { return sInstance; } virtual bool hasAccelerators() const; virtual bool handleKeyHere(KEY key, MASK mask); private: LLScriptEdCore* mEditorCore; static LLFloaterScriptSearch* sInstance; protected: LLLineEditor* mSearchBox; LLLineEditor* mReplaceBox; void onSearchBoxCommit(); }; LLFloaterScriptSearch* LLFloaterScriptSearch::sInstance = NULL; LLFloaterScriptSearch::LLFloaterScriptSearch(LLScriptEdCore* editor_core) : LLFloater(LLSD()), mSearchBox(NULL), mReplaceBox(NULL), mEditorCore(editor_core) { buildFromFile("floater_script_search.xml"); sInstance = this; // find floater in which script panel is embedded LLView* viewp = (LLView*)editor_core; while(viewp) { LLFloater* floaterp = dynamic_cast(viewp); if (floaterp) { floaterp->addDependentFloater(this); break; } viewp = viewp->getParent(); } } bool LLFloaterScriptSearch::postBuild() { mReplaceBox = getChild("replace_text"); mSearchBox = getChild("search_text"); mSearchBox->setCommitCallback(boost::bind(&LLFloaterScriptSearch::onSearchBoxCommit, this)); mSearchBox->setCommitOnFocusLost(false); childSetAction("search_btn", onBtnSearch,this); childSetAction("replace_btn", onBtnReplace,this); childSetAction("replace_all_btn", onBtnReplaceAll,this); setDefaultBtn("search_btn"); return true; } //static void LLFloaterScriptSearch::show(LLScriptEdCore* editor_core) { LLSD::String search_text; LLSD::String replace_text; if (sInstance && sInstance->mEditorCore && sInstance->mEditorCore != editor_core) { search_text=sInstance->mSearchBox->getValue().asString(); replace_text=sInstance->mReplaceBox->getValue().asString(); sInstance->closeFloater(); delete sInstance; } if (!sInstance) { // sInstance will be assigned in the constructor. new LLFloaterScriptSearch(editor_core); sInstance->mSearchBox->setValue(search_text); sInstance->mReplaceBox->setValue(replace_text); } sInstance->openFloater(); } LLFloaterScriptSearch::~LLFloaterScriptSearch() { sInstance = NULL; } // static void LLFloaterScriptSearch::onBtnSearch(void *userdata) { LLFloaterScriptSearch* self = (LLFloaterScriptSearch*)userdata; self->handleBtnSearch(); } void LLFloaterScriptSearch::handleBtnSearch() { LLCheckBoxCtrl* caseChk = getChild("case_text"); mEditorCore->mEditor->selectNext(mSearchBox->getValue().asString(), caseChk->get()); } // static void LLFloaterScriptSearch::onBtnReplace(void *userdata) { LLFloaterScriptSearch* self = (LLFloaterScriptSearch*)userdata; self->handleBtnReplace(); } void LLFloaterScriptSearch::handleBtnReplace() { LLCheckBoxCtrl* caseChk = getChild("case_text"); mEditorCore->mEditor->replaceText(mSearchBox->getValue().asString(), mReplaceBox->getValue().asString(), caseChk->get()); } // static void LLFloaterScriptSearch::onBtnReplaceAll(void *userdata) { LLFloaterScriptSearch* self = (LLFloaterScriptSearch*)userdata; self->handleBtnReplaceAll(); } void LLFloaterScriptSearch::handleBtnReplaceAll() { LLCheckBoxCtrl* caseChk = getChild("case_text"); mEditorCore->mEditor->replaceTextAll(mSearchBox->getValue().asString(), mReplaceBox->getValue().asString(), caseChk->get()); } bool LLFloaterScriptSearch::hasAccelerators() const { if (mEditorCore) { return mEditorCore->hasAccelerators(); } return false; } bool LLFloaterScriptSearch::handleKeyHere(KEY key, MASK mask) { if (mEditorCore) { bool handled = mEditorCore->handleKeyHere(key, mask); if (!handled) { LLFloater::handleKeyHere(key, mask); } } return false; } void LLFloaterScriptSearch::onSearchBoxCommit() { if (mEditorCore && mEditorCore->mEditor) { LLCheckBoxCtrl* caseChk = getChild("case_text"); mEditorCore->mEditor->selectNext(mSearchBox->getValue().asString(), caseChk->get()); } } /// --------------------------------------------------------------------------- class LLScriptMovedObserver : public LLInventoryObserver { public: LLScriptMovedObserver(LLPreviewLSL *floater) : mPreview(floater) { gInventory.addObserver(this); } virtual ~LLScriptMovedObserver() { gInventory.removeObserver(this); } virtual void changed(U32 mask); private: LLPreviewLSL *mPreview; }; void LLScriptMovedObserver::changed(U32 mask) { const std::set &mChangedItemIDs = gInventory.getChangedIDs(); std::set::const_iterator it; const LLUUID &item_id = mPreview->getScriptID(); for (it = mChangedItemIDs.begin(); it != mChangedItemIDs.end(); it++) { if (*it == item_id) { if ((mask & (LLInventoryObserver::STRUCTURE)) != 0) { mPreview->setDirty(); } } } } /// --------------------------------------------------------------------------- /// LLScriptEdCore /// --------------------------------------------------------------------------- struct LLSECKeywordCompare { bool operator()(const std::string& lhs, const std::string& rhs) { return (LLStringUtil::compareDictInsensitive( lhs, rhs ) < 0 ); } }; LLScriptEdCore::LLScriptEdCore( LLScriptEdContainer* container, const std::string& sample, const LLHandle& floater_handle, void (*load_callback)(void*), void (*save_callback)(void*, bool), void (*search_replace_callback) (void* userdata), void* userdata, bool live, S32 bottom_pad) : LLPanel(), mSampleText(sample), mEditor( NULL ), mLoadCallback( load_callback ), mSaveCallback( save_callback ), mSearchReplaceCallback( search_replace_callback ), mUserdata( userdata ), mForceClose( false ), mLastHelpToken(NULL), mLiveHelpHistorySize(0), mEnableSave(false), mLiveFile(NULL), mLive(live), mContainer(container), mHasScriptData(false), mScriptRemoved(false), mSaveDialogShown(false) { setFollowsAll(); setBorderVisible(false); setXMLFilename("panel_script_ed.xml"); llassert_always(mContainer != NULL); } LLScriptEdCore::~LLScriptEdCore() { deleteBridges(); // If the search window is up for this editor, close it. LLFloaterScriptSearch* script_search = LLFloaterScriptSearch::getInstance(); if (script_search && script_search->getEditorCore() == this) { script_search->closeFloater(); // closeFloater can delete instance since it's not reusable nor single instance // so make sure instance is still there before deleting script_search = LLFloaterScriptSearch::getInstance(); if (script_search) { delete script_search; } } delete mLiveFile; if (mSyntaxIDConnection.connected()) { mSyntaxIDConnection.disconnect(); } } void LLLiveLSLEditor::experienceChanged() { if(mScriptEd->getAssociatedExperience() != mExperiences->getSelectedValue().asUUID()) { mScriptEd->enableSave(getIsModifiable()); //mSaveBtn->setEnabled(true); mScriptEd->setAssociatedExperience(mExperiences->getSelectedValue().asUUID()); updateExperiencePanel(); } } void LLLiveLSLEditor::onViewProfile( LLUICtrl *ui, void* userdata ) { LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata; LLUUID id; if(self->mExperienceEnabled->get()) { id=self->mScriptEd->getAssociatedExperience(); if(id.notNull()) { LLFloaterReg::showInstance("experience_profile", id, true); } } } void LLLiveLSLEditor::onToggleExperience( LLUICtrl *ui, void* userdata ) { LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata; LLUUID id; if(self->mExperienceEnabled->get()) { if(self->mScriptEd->getAssociatedExperience().isNull()) { id=self->mExperienceIds.beginArray()->asUUID(); } } if(id != self->mScriptEd->getAssociatedExperience()) { self->mScriptEd->enableSave(self->getIsModifiable()); } self->mScriptEd->setAssociatedExperience(id); self->updateExperiencePanel(); } bool LLScriptEdCore::postBuild() { mLineCol = getChild("line_col"); mErrorList = getChild("lsl errors"); mFunctions = getChild("Insert..."); childSetCommitCallback("Insert...", &LLScriptEdCore::onBtnInsertFunction, this); mEditor = getChild("Script Editor"); childSetCommitCallback("lsl errors", &LLScriptEdCore::onErrorList, this); mSaveBtn = getChild("Save_btn"); mSaveBtn->setCommitCallback(boost::bind(&LLScriptEdCore::doSave, this, false)); childSetAction("Edit_btn", boost::bind(&LLScriptEdCore::openInExternalEditor, this)); initMenu(); mSyntaxIDConnection = LLSyntaxIdLSL::getInstance()->addSyntaxIDCallback(boost::bind(&LLScriptEdCore::processKeywords, this)); // Intialise keyword highlighting for the current simulator's version of LSL LLSyntaxIdLSL::getInstance()->initialize(); processKeywords(); mCommitCallbackRegistrar.add("FontSize.Set", { boost::bind(&LLScriptEdCore::onChangeFontSize, this, _2) }); mEnableCallbackRegistrar.add("FontSize.Check", boost::bind(&LLScriptEdCore::isFontSizeChecked, this, _2)); LLToggleableMenu *context_menu = LLUICtrlFactory::getInstance()->createFromFile( "menu_lsl_font_size.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); getChild("font_btn")->setMenu(context_menu, LLMenuButton::MP_BOTTOM_LEFT, true); return true; } void LLScriptEdCore::processKeywords() { LL_DEBUGS("SyntaxLSL") << "Processing keywords" << LL_ENDL; mEditor->clearSegments(); mEditor->initKeywords(); mEditor->loadKeywords(); string_vec_t primary_keywords; string_vec_t secondary_keywords; LLKeywordToken *token; LLKeywords::keyword_iterator_t token_it; for (token_it = mEditor->keywordsBegin(); token_it != mEditor->keywordsEnd(); ++token_it) { token = token_it->second; if (token->getType() == LLKeywordToken::TT_FUNCTION) { primary_keywords.push_back( wstring_to_utf8str(token->getToken()) ); } else { secondary_keywords.push_back( wstring_to_utf8str(token->getToken()) ); } } for (string_vec_t::const_iterator iter = primary_keywords.begin(); iter!= primary_keywords.end(); ++iter) { mFunctions->add(*iter); } for (string_vec_t::const_iterator iter = secondary_keywords.begin(); iter!= secondary_keywords.end(); ++iter) { mFunctions->add(*iter); } } void LLScriptEdCore::initMenu() { // *TODO: Skinning - make these callbacks data driven LLMenuItemCallGL* menuItem; menuItem = getChild("Save"); menuItem->setClickCallback(boost::bind(&LLScriptEdCore::doSave, this, false)); menuItem->setEnableCallback(boost::bind(&LLScriptEdCore::hasChanged, this)); menuItem = getChild("Revert All Changes"); menuItem->setClickCallback(boost::bind(&LLScriptEdCore::onBtnUndoChanges, this)); menuItem->setEnableCallback(boost::bind(&LLScriptEdCore::hasChanged, this)); menuItem = getChild("Undo"); menuItem->setClickCallback(boost::bind(&LLTextEditor::undo, mEditor)); menuItem->setEnableCallback(boost::bind(&LLTextEditor::canUndo, mEditor)); menuItem = getChild("Redo"); menuItem->setClickCallback(boost::bind(&LLTextEditor::redo, mEditor)); menuItem->setEnableCallback(boost::bind(&LLTextEditor::canRedo, mEditor)); menuItem = getChild("Cut"); menuItem->setClickCallback(boost::bind(&LLTextEditor::cut, mEditor)); menuItem->setEnableCallback(boost::bind(&LLTextEditor::canCut, mEditor)); menuItem = getChild("Copy"); menuItem->setClickCallback(boost::bind(&LLTextEditor::copy, mEditor)); menuItem->setEnableCallback(boost::bind(&LLTextEditor::canCopy, mEditor)); menuItem = getChild("Paste"); menuItem->setClickCallback(boost::bind(&LLTextEditor::paste, mEditor)); menuItem->setEnableCallback(boost::bind(&LLTextEditor::canPaste, mEditor)); menuItem = getChild("Select All"); menuItem->setClickCallback(boost::bind(&LLTextEditor::selectAll, mEditor)); menuItem->setEnableCallback(boost::bind(&LLTextEditor::canSelectAll, mEditor)); menuItem = getChild("Deselect"); menuItem->setClickCallback(boost::bind(&LLTextEditor::deselect, mEditor)); menuItem->setEnableCallback(boost::bind(&LLTextEditor::canDeselect, mEditor)); menuItem = getChild("Search / Replace..."); menuItem->setClickCallback(boost::bind(&LLFloaterScriptSearch::show, this)); menuItem = getChild("Go to line..."); menuItem->setClickCallback(boost::bind(&LLFloaterGotoLine::show, this)); menuItem = getChild("Keyword Help..."); menuItem->setClickCallback(boost::bind(&LLScriptEdCore::onBtnDynamicHelp, this)); menuItem = getChild("LoadFromFile"); menuItem->setClickCallback(boost::bind(&LLScriptEdCore::onBtnLoadFromFile, this)); menuItem->setEnableCallback(boost::bind(&LLScriptEdCore::enableLoadFromFileMenu, this)); menuItem = getChild("SaveToFile"); menuItem->setClickCallback(boost::bind(&LLScriptEdCore::onBtnSaveToFile, this)); menuItem->setEnableCallback(boost::bind(&LLScriptEdCore::enableSaveToFileMenu, this)); } void LLScriptEdCore::setScriptText(const std::string& text, bool is_valid) { if (mEditor) { mEditor->setText(text); mHasScriptData = is_valid; } } void LLScriptEdCore::makeEditorPristine() { if (mEditor) { mEditor->makePristine(); } } bool LLScriptEdCore::loadScriptText(const std::string& filename) { if (filename.empty()) { LL_WARNS() << "Empty file name" << LL_ENDL; return false; } LLFILE* file = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/ if (!file) { LL_WARNS() << "Error opening " << filename << LL_ENDL; return false; } // read in the whole file fseek(file, 0L, SEEK_END); size_t file_length = (size_t) ftell(file); fseek(file, 0L, SEEK_SET); char* buffer = new char[file_length+1]; size_t nread = fread(buffer, 1, file_length, file); if (nread < file_length) { LL_WARNS() << "Short read" << LL_ENDL; } buffer[nread] = '\0'; fclose(file); std::string text = std::string(buffer); LLStringUtil::replaceTabsWithSpaces(text, LLTextEditor::spacesPerTab()); mEditor->setText(text); delete[] buffer; return true; } bool LLScriptEdCore::writeToFile(const std::string& filename) { LLFILE* fp = LLFile::fopen(filename, "wb"); if (!fp) { LL_WARNS() << "Unable to write to " << filename << LL_ENDL; LLSD row; row["columns"][0]["value"] = "Error writing to local file. Is your hard drive full?"; row["columns"][0]["font"] = "SANSSERIF_SMALL"; mErrorList->addElement(row); return false; } std::string utf8text = mEditor->getText(); // Special case for a completely empty script - stuff in one space so it can store properly. See SL-46889 if (utf8text.size() == 0) { utf8text = " "; } fputs(utf8text.c_str(), fp); fclose(fp); return true; } void LLScriptEdCore::sync() { // Sync with external editor. if (mLiveFile) { std::string tmp_file = mLiveFile->filename(); llstat s; if (LLFile::stat(tmp_file, &s) == 0) // file exists { mLiveFile->ignoreNextUpdate(); writeToFile(tmp_file); } } } bool LLScriptEdCore::hasChanged() const { if (!mEditor) return false; return ((!mEditor->isPristine() || mEnableSave) && mHasScriptData); } void LLScriptEdCore::draw() { bool script_changed = hasChanged(); mSaveBtn->setEnabled(script_changed && !mScriptRemoved); if( mEditor->hasFocus() ) { S32 line = 0; S32 column = 0; mEditor->getCurrentLineAndColumn( &line, &column, false ); // don't include wordwrap LLStringUtil::format_map_t args; std::string cursor_pos; args["[LINE]"] = llformat ("%d", line); args["[COLUMN]"] = llformat ("%d", column); cursor_pos = LLTrans::getString("CursorPos", args); mLineCol->setValue(cursor_pos); } else { mLineCol->setValue(LLStringUtil::null); } updateDynamicHelp(); LLPanel::draw(); } void LLScriptEdCore::updateDynamicHelp(bool immediate) { LLFloater* help_floater = mLiveHelpHandle.get(); if (!help_floater) return; // update back and forward buttons LLButton* fwd_button = help_floater->getChild("fwd_btn"); LLButton* back_button = help_floater->getChild("back_btn"); LLMediaCtrl* browser = help_floater->getChild("lsl_guide_html"); back_button->setEnabled(browser->canNavigateBack()); fwd_button->setEnabled(browser->canNavigateForward()); if (!immediate && !gSavedSettings.getBOOL("ScriptHelpFollowsCursor")) { return; } LLTextSegmentPtr segment = NULL; std::vector selected_segments; mEditor->getSelectedSegments(selected_segments); LLKeywordToken* token; // try segments in selection range first std::vector::iterator segment_iter; for (segment_iter = selected_segments.begin(); segment_iter != selected_segments.end(); ++segment_iter) { token = (*segment_iter)->getToken(); if(token && isKeyword(token)) { segment = *segment_iter; break; } } // then try previous segment in case we just typed it if (!segment) { const LLTextSegmentPtr test_segment = mEditor->getPreviousSegment(); token = test_segment->getToken(); if(token && isKeyword(token)) { segment = test_segment; } } if (segment) { if (segment->getToken() != mLastHelpToken) { mLastHelpToken = segment->getToken(); mLiveHelpTimer.start(); } if (immediate || (mLiveHelpTimer.getStarted() && mLiveHelpTimer.getElapsedTimeF32() > LIVE_HELP_REFRESH_TIME)) { // Use Wtext since segment's start/end are made for wstring and will // result in a shift for case of multi-byte symbols inside std::string. LLWString segment_text = mEditor->getWText().substr(segment->getStart(), segment->getEnd() - segment->getStart()); std::string help_string = wstring_to_utf8str(segment_text); setHelpPage(help_string); mLiveHelpTimer.stop(); } } else { if (immediate) { setHelpPage(LLStringUtil::null); } } } bool LLScriptEdCore::isKeyword(LLKeywordToken* token) { switch(token->getType()) { case LLKeywordToken::TT_CONSTANT: case LLKeywordToken::TT_CONTROL: case LLKeywordToken::TT_EVENT: case LLKeywordToken::TT_FUNCTION: case LLKeywordToken::TT_SECTION: case LLKeywordToken::TT_TYPE: case LLKeywordToken::TT_WORD: return true; default: return false; } } void LLScriptEdCore::setHelpPage(const std::string& help_string) { LLFloater* help_floater = mLiveHelpHandle.get(); if (!help_floater) return; LLMediaCtrl* web_browser = help_floater->getChild("lsl_guide_html"); if (!web_browser) return; LLComboBox* history_combo = help_floater->getChild("history_combo"); if (!history_combo) return; LLUIString url_string = gSavedSettings.getString("LSLHelpURL"); url_string.setArg("[LSL_STRING]", help_string.empty() ? HELP_LSL_PORTAL_TOPIC : help_string); addHelpItemToHistory(help_string); web_browser->navigateTo(url_string); } void LLScriptEdCore::addHelpItemToHistory(const std::string& help_string) { if (help_string.empty()) return; LLFloater* help_floater = mLiveHelpHandle.get(); if (!help_floater) return; LLComboBox* history_combo = help_floater->getChild("history_combo"); if (!history_combo) return; // separate history items from full item list if (mLiveHelpHistorySize == 0) { history_combo->addSeparator(ADD_TOP); } // delete all history items over history limit while(mLiveHelpHistorySize > MAX_HISTORY_COUNT - 1) { history_combo->remove(mLiveHelpHistorySize - 1); mLiveHelpHistorySize--; } history_combo->setSimple(help_string); S32 index = history_combo->getCurrentIndex(); // if help string exists in the combo box if (index >= 0) { S32 cur_index = history_combo->getCurrentIndex(); if (cur_index < mLiveHelpHistorySize) { // item found in history, bubble up to top history_combo->remove(history_combo->getCurrentIndex()); mLiveHelpHistorySize--; } } history_combo->add(help_string, LLSD(help_string), ADD_TOP); history_combo->selectFirstItem(); mLiveHelpHistorySize++; } bool LLScriptEdCore::canClose() { if(mForceClose || !hasChanged() || mScriptRemoved) { return true; } else { if(!mSaveDialogShown) { mSaveDialogShown = true; // Bring up view-modal dialog: Save changes? Yes, No, Cancel LLNotificationsUtil::add("SaveChanges", LLSD(), LLSD(), boost::bind(&LLScriptEdCore::handleSaveChangesDialog, this, _1, _2)); } return false; } } void LLScriptEdCore::setEnableEditing(bool enable) { mEditor->setEnabled(enable); getChildView("Edit_btn")->setEnabled(enable); } bool LLScriptEdCore::handleSaveChangesDialog(const LLSD& notification, const LLSD& response ) { mSaveDialogShown = false; S32 option = LLNotificationsUtil::getSelectedOption(notification, response); switch( option ) { case 0: // "Yes" // close after saving doSave( true ); break; case 1: // "No" mForceClose = true; // This will close immediately because mForceClose is true, so we won't // infinite loop with these dialogs. JC ((LLFloater*) getParent())->closeFloater(); break; case 2: // "Cancel" default: // If we were quitting, we didn't really mean it. LLAppViewer::instance()->abortQuit(); break; } return false; } void LLScriptEdCore::onBtnDynamicHelp() { LLFloater* live_help_floater = mLiveHelpHandle.get(); if (!live_help_floater) { live_help_floater = new LLFloater(LLSD()); live_help_floater->buildFromFile("floater_lsl_guide.xml"); LLFloater* parent = dynamic_cast(getParent()); llassert(parent); if (parent) parent->addDependentFloater(live_help_floater, true); live_help_floater->childSetCommitCallback("lock_check", onCheckLock, this); live_help_floater->getChild("lock_check")->setValue(gSavedSettings.getBOOL("ScriptHelpFollowsCursor")); live_help_floater->childSetCommitCallback("history_combo", onHelpComboCommit, this); live_help_floater->childSetAction("back_btn", onClickBack, this); live_help_floater->childSetAction("fwd_btn", onClickForward, this); LLMediaCtrl* browser = live_help_floater->getChild("lsl_guide_html"); browser->setAlwaysRefresh(true); LLComboBox* help_combo = live_help_floater->getChild("history_combo"); LLKeywordToken *token; LLKeywords::keyword_iterator_t token_it; for (token_it = mEditor->keywordsBegin(); token_it != mEditor->keywordsEnd(); ++token_it) { token = token_it->second; help_combo->add(wstring_to_utf8str(token->getToken())); } help_combo->sortByName(); // re-initialize help variables mLastHelpToken = NULL; mLiveHelpHandle = live_help_floater->getHandle(); mLiveHelpHistorySize = 0; } bool visible = true; bool take_focus = true; live_help_floater->setVisible(visible); live_help_floater->setFrontmost(take_focus); updateDynamicHelp(true); } //static void LLScriptEdCore::onClickBack(void* userdata) { LLScriptEdCore* corep = (LLScriptEdCore*)userdata; LLFloater* live_help_floater = corep->mLiveHelpHandle.get(); if (live_help_floater) { LLMediaCtrl* browserp = live_help_floater->getChild("lsl_guide_html"); if (browserp) { browserp->navigateBack(); } } } //static void LLScriptEdCore::onClickForward(void* userdata) { LLScriptEdCore* corep = (LLScriptEdCore*)userdata; LLFloater* live_help_floater = corep->mLiveHelpHandle.get(); if (live_help_floater) { LLMediaCtrl* browserp = live_help_floater->getChild("lsl_guide_html"); if (browserp) { browserp->navigateForward(); } } } // static void LLScriptEdCore::onCheckLock(LLUICtrl* ctrl, void* userdata) { LLScriptEdCore* corep = (LLScriptEdCore*)userdata; // clear out token any time we lock the frame, so we will refresh web page immediately when unlocked gSavedSettings.setBOOL("ScriptHelpFollowsCursor", ctrl->getValue().asBoolean()); corep->mLastHelpToken = NULL; } // static void LLScriptEdCore::onBtnInsertSample(void* userdata) { LLScriptEdCore* self = (LLScriptEdCore*) userdata; // Insert sample code self->mEditor->selectAll(); self->mEditor->cut(); self->mEditor->insertText(self->mSampleText); } // static void LLScriptEdCore::onHelpComboCommit(LLUICtrl* ctrl, void* userdata) { LLScriptEdCore* corep = (LLScriptEdCore*)userdata; LLFloater* live_help_floater = corep->mLiveHelpHandle.get(); if (live_help_floater) { std::string help_string = ctrl->getValue().asString(); corep->addHelpItemToHistory(help_string); LLMediaCtrl* web_browser = live_help_floater->getChild("lsl_guide_html"); LLUIString url_string = gSavedSettings.getString("LSLHelpURL"); url_string.setArg("[LSL_STRING]", help_string); web_browser->navigateTo(url_string); } } // static void LLScriptEdCore::onBtnInsertFunction(LLUICtrl *ui, void* userdata) { LLScriptEdCore* self = (LLScriptEdCore*) userdata; // Insert sample code if(self->mEditor->getEnabled()) { self->mEditor->insertText(self->mFunctions->getSimple()); } self->mEditor->setFocus(true); self->setHelpPage(self->mFunctions->getSimple()); } void LLScriptEdCore::doSave( bool close_after_save ) { add(LLStatViewer::LSL_SAVES, 1); if( mSaveCallback ) { mSaveCallback( mUserdata, close_after_save ); } } void LLScriptEdCore::openInExternalEditor() { delete mLiveFile; // deletes file // Generate a suitable filename std::string script_name = mScriptName; std::string forbidden_chars = "<>:\"\\/|?*"; for (std::string::iterator c = forbidden_chars.begin(); c != forbidden_chars.end(); c++) { script_name.erase(std::remove(script_name.begin(), script_name.end(), *c), script_name.end()); } std::string filename = mContainer->getTmpFileName(script_name); // Save the script to a temporary file. if (!writeToFile(filename)) { // In case some characters from script name are forbidden // and not accounted for, name is too long or some other issue, // try file that doesn't include script name script_name.clear(); filename = mContainer->getTmpFileName(script_name); writeToFile(filename); } // Start watching file changes. mLiveFile = new LLLiveLSLFile(filename, boost::bind(&LLScriptEdContainer::onExternalChange, mContainer, _1)); mLiveFile->addToEventTimer(); // Open it in external editor. { LLExternalEditor ed; LLExternalEditor::EErrorCode status; std::string msg; status = ed.setCommand("LL_SCRIPT_EDITOR"); if (status != LLExternalEditor::EC_SUCCESS) { if (status == LLExternalEditor::EC_NOT_SPECIFIED) // Use custom message for this error. { msg = LLTrans::getString("ExternalEditorNotSet"); } else { msg = LLExternalEditor::getErrorMessage(status); } LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", msg)); return; } status = ed.run(filename); if (status != LLExternalEditor::EC_SUCCESS) { msg = LLExternalEditor::getErrorMessage(status); LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", msg)); } } } void LLScriptEdCore::onBtnUndoChanges() { if( !mEditor->tryToRevertToPristineState() ) { LLNotificationsUtil::add("ScriptCannotUndo", LLSD(), LLSD(), boost::bind(&LLScriptEdCore::handleReloadFromServerDialog, this, _1, _2)); } } // static void LLScriptEdCore::onErrorList(LLUICtrl*, void* user_data) { LLScriptEdCore* self = (LLScriptEdCore*)user_data; LLScrollListItem* item = self->mErrorList->getFirstSelected(); if(item) { // *FIX: replace with boost grep S32 row = 0; S32 column = 0; const LLScrollListCell* cell = item->getColumn(0); std::string line(cell->getValue().asString()); line.erase(0, 1); LLStringUtil::replaceChar(line, ',',' '); LLStringUtil::replaceChar(line, ')',' '); sscanf(line.c_str(), "%d %d", &row, &column); //LL_INFOS() << "LLScriptEdCore::onErrorList() - " << row << ", " //<< column << LL_ENDL; self->mEditor->setCursor(row, column); self->mEditor->setFocus(true); } } bool LLScriptEdCore::handleReloadFromServerDialog(const LLSD& notification, const LLSD& response ) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); switch( option ) { case 0: // "Yes" if( mLoadCallback ) { setScriptText(getString("loading"), false); mLoadCallback(mUserdata); } break; case 1: // "No" break; default: llassert(0); break; } return false; } void LLScriptEdCore::selectFirstError() { // Select the first item; mErrorList->selectFirstItem(); onErrorList(mErrorList, this); } struct LLEntryAndEdCore { LLScriptEdCore* mCore; LLEntryAndEdCore(LLScriptEdCore* core) : mCore(core) {} }; void LLScriptEdCore::deleteBridges() { auto count = mBridges.size(); LLEntryAndEdCore* eandc; for(size_t i = 0; i < count; i++) { eandc = mBridges.at(i); delete eandc; mBridges[i] = NULL; } mBridges.clear(); } // virtual bool LLScriptEdCore::handleKeyHere(KEY key, MASK mask) { bool just_control = MASK_CONTROL == (mask & MASK_MODIFIERS); if(('S' == key) && just_control) { if(mSaveCallback) { // don't close after saving mSaveCallback(mUserdata, false); } return true; } if(('F' == key) && just_control) { if(mSearchReplaceCallback) { mSearchReplaceCallback(mUserdata); } return true; } return false; } void LLScriptEdCore::onBtnLoadFromFile( void* data ) { LLFilePickerReplyThread::startPicker(boost::bind(&LLScriptEdCore::loadScriptFromFile, _1, data), LLFilePicker::FFLOAD_SCRIPT, false); } void LLScriptEdCore::loadScriptFromFile(const std::vector& filenames, void* data) { std::string filename = filenames[0]; llifstream fin(filename.c_str()); std::string line; std::string text; std::string linetotal; while (!fin.eof()) { getline(fin, line); text += line; if (!fin.eof()) { text += "\n"; } } fin.close(); // Only replace the script if there is something to replace with. LLScriptEdCore* self = (LLScriptEdCore*)data; if (self && (text.length() > 0)) { self->mEditor->selectAll(); LLWString script(utf8str_to_wstring(text)); self->mEditor->insertText(script); } } void LLScriptEdCore::onBtnSaveToFile( void* userdata ) { add(LLStatViewer::LSL_SAVES, 1); LLScriptEdCore* self = (LLScriptEdCore*) userdata; if( self->mSaveCallback ) { LLFilePickerReplyThread::startPicker(boost::bind(&LLScriptEdCore::saveScriptToFile, _1, userdata), LLFilePicker::FFSAVE_SCRIPT, self->mScriptName); } } void LLScriptEdCore::saveScriptToFile(const std::vector& filenames, void* data) { LLScriptEdCore* self = (LLScriptEdCore*)data; if (self) { std::string filename = filenames[0]; std::string scriptText = self->mEditor->getText(); llofstream fout(filename.c_str()); fout << (scriptText); fout.close(); self->mSaveCallback(self->mUserdata, false); } } bool LLScriptEdCore::canLoadOrSaveToFile( void* userdata ) { LLScriptEdCore* self = (LLScriptEdCore*) userdata; return self->mEditor->canLoadOrSaveToFile(); } // static bool LLScriptEdCore::enableSaveToFileMenu(void* userdata) { LLScriptEdCore* self = (LLScriptEdCore*)userdata; if (!self || !self->mEditor) return false; return self->mEditor->canLoadOrSaveToFile(); } // static bool LLScriptEdCore::enableLoadFromFileMenu(void* userdata) { LLScriptEdCore* self = (LLScriptEdCore*)userdata; return (self && self->mEditor) ? self->mEditor->canLoadOrSaveToFile() : false; } LLUUID LLScriptEdCore::getAssociatedExperience()const { return mAssociatedExperience; } void LLScriptEdCore::onChangeFontSize(const LLSD &userdata) { const std::string font_name = userdata.asString(); gSavedSettings.setString("LSLFontSizeName", font_name); } bool LLScriptEdCore::isFontSizeChecked(const LLSD &userdata) { const std::string current_size_name = LLScriptEditor::getScriptFontSize(); const std::string size_name = userdata.asString(); return (size_name == current_size_name); } void LLLiveLSLEditor::setExperienceIds( const LLSD& experience_ids ) { mExperienceIds=experience_ids; updateExperiencePanel(); } void LLLiveLSLEditor::updateExperiencePanel() { if(mScriptEd->getAssociatedExperience().isNull()) { mExperienceEnabled->set(false); mExperiences->setVisible(false); if(mExperienceIds.size()>0) { mExperienceEnabled->setEnabled(true); mExperienceEnabled->setToolTip(getString("add_experiences")); } else { mExperienceEnabled->setEnabled(false); mExperienceEnabled->setToolTip(getString("no_experiences")); } getChild("view_profile")->setVisible(false); } else { mExperienceEnabled->setToolTip(getString("experience_enabled")); mExperienceEnabled->setEnabled(getIsModifiable()); mExperiences->setVisible(true); mExperienceEnabled->set(true); getChild("view_profile")->setToolTip(getString("show_experience_profile")); buildExperienceList(); } } void LLLiveLSLEditor::buildExperienceList() { mExperiences->clearRows(); bool foundAssociated=false; const LLUUID& associated = mScriptEd->getAssociatedExperience(); LLUUID last; LLScrollListItem* item; for(LLSD::array_const_iterator it = mExperienceIds.beginArray(); it != mExperienceIds.endArray(); ++it) { LLUUID id = it->asUUID(); EAddPosition position = ADD_BOTTOM; if(id == associated) { foundAssociated = true; position = ADD_TOP; } const LLSD& experience = LLExperienceCache::instance().get(id); if(experience.isUndefined()) { mExperiences->add(getString("loading"), id, position); last = id; } else { std::string experience_name_string = experience[LLExperienceCache::NAME].asString(); if (experience_name_string.empty()) { experience_name_string = LLTrans::getString("ExperienceNameUntitled"); } mExperiences->add(experience_name_string, id, position); } } if(!foundAssociated ) { const LLSD& experience = LLExperienceCache::instance().get(associated); if(experience.isDefined()) { std::string experience_name_string = experience[LLExperienceCache::NAME].asString(); if (experience_name_string.empty()) { experience_name_string = LLTrans::getString("ExperienceNameUntitled"); } item=mExperiences->add(experience_name_string, associated, ADD_TOP); } else { item=mExperiences->add(getString("loading"), associated, ADD_TOP); last = associated; } item->setEnabled(false); } if(last.notNull()) { mExperiences->setEnabled(false); LLExperienceCache::instance().get(last, boost::bind(&LLLiveLSLEditor::buildExperienceList, this)); } else { mExperiences->setEnabled(true); mExperiences->sortByName(true); mExperiences->setCurrentByIndex(mExperiences->getCurrentIndex()); getChild("view_profile")->setVisible(true); } } void LLScriptEdCore::setAssociatedExperience( const LLUUID& experience_id ) { mAssociatedExperience = experience_id; } void LLLiveLSLEditor::requestExperiences() { if (!getIsModifiable()) { return; } LLViewerRegion* region = gAgent.getRegion(); if (region) { std::string lookup_url=region->getCapability("GetCreatorExperiences"); if(!lookup_url.empty()) { LLCoreHttpUtil::HttpCoroutineAdapter::completionCallback_t success = boost::bind(&LLLiveLSLEditor::receiveExperienceIds, _1, getDerivedHandle()); LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpGet(lookup_url, success); } } } /*static*/ void LLLiveLSLEditor::receiveExperienceIds(LLSD result, LLHandle hparent) { LLLiveLSLEditor* parent = hparent.get(); if (!parent) return; parent->setExperienceIds(result["experience_ids"]); } /// --------------------------------------------------------------------------- /// LLScriptEdContainer /// --------------------------------------------------------------------------- LLScriptEdContainer::LLScriptEdContainer(const LLSD& key) : LLPreview(key) , mScriptEd(NULL) { } std::string LLScriptEdContainer::getTmpFileName(const std::string& script_name) { // Take script inventory item id (within the object inventory) // to consideration so that it's possible to edit multiple scripts // in the same object inventory simultaneously (STORM-781). std::string script_id = mObjectUUID.asString() + "_" + mItemUUID.asString(); // Use MD5 sum to make the file name shorter and not exceed maximum path length. char script_id_hash_str[33]; /* Flawfinder: ignore */ LLMD5 script_id_hash((const U8 *)script_id.c_str()); script_id_hash.hex_digest(script_id_hash_str); if (script_name.empty()) { return std::string(LLFile::tmpdir()) + "sl_script_" + script_id_hash_str + ".lsl"; } else { return std::string(LLFile::tmpdir()) + "sl_script_" + script_name + "_" + script_id_hash_str + ".lsl"; } } bool LLScriptEdContainer::onExternalChange(const std::string& filename) { if (!mScriptEd->loadScriptText(filename)) { return false; } // Disable sync to avoid recursive load->save->load calls. saveIfNeeded(false); return true; } bool LLScriptEdContainer::handleKeyHere(KEY key, MASK mask) { if (('A' == key) && (MASK_CONTROL == (mask & MASK_MODIFIERS))) { mScriptEd->selectAll(); return true; } if (!LLPreview::handleKeyHere(key, mask)) { return mScriptEd->handleKeyHere(key, mask); } return true; } /// --------------------------------------------------------------------------- /// LLPreviewLSL /// --------------------------------------------------------------------------- struct LLScriptSaveInfo { LLUUID mItemUUID; std::string mDescription; LLTransactionID mTransactionID; LLScriptSaveInfo(const LLUUID& uuid, const std::string& desc, LLTransactionID tid) : mItemUUID(uuid), mDescription(desc), mTransactionID(tid) {} }; //static void* LLPreviewLSL::createScriptEdPanel(void* userdata) { LLPreviewLSL *self = (LLPreviewLSL*)userdata; self->mScriptEd = new LLScriptEdCore( self, HELLO_LSL, self->getHandle(), LLPreviewLSL::onLoad, LLPreviewLSL::onSave, LLPreviewLSL::onSearchReplace, self, false, 0); return self->mScriptEd; } LLPreviewLSL::LLPreviewLSL(const LLSD& key ) : LLScriptEdContainer(key), mPendingUploads(0) { mFactoryMap["script panel"] = LLCallbackMap(LLPreviewLSL::createScriptEdPanel, this); mItemObserver = new LLScriptMovedObserver(this); } LLPreviewLSL::~LLPreviewLSL() { delete mItemObserver; mItemObserver = NULL; } // virtual bool LLPreviewLSL::postBuild() { const LLInventoryItem* item = getItem(); llassert(item); if (item) { getChild("desc")->setValue(item->getDescription()); std::string item_path = get_category_path(item->getParentUUID()); getChild("path_txt")->setValue(item_path); getChild("path_txt")->setToolTip(item_path); } childSetCommitCallback("desc", LLPreview::onText, this); getChild("desc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); return LLPreview::postBuild(); } void LLPreviewLSL::draw() { const LLInventoryItem* item = getItem(); if(!item) { setTitle(LLTrans::getString("ScriptWasDeleted")); mScriptEd->setItemRemoved(true); } else if (mDirty) { std::string item_path = get_category_path(item->getParentUUID()); getChild("path_txt")->setValue(item_path); getChild("path_txt")->setToolTip(item_path); } LLPreview::draw(); } // virtual void LLPreviewLSL::callbackLSLCompileSucceeded() { LL_INFOS() << "LSL Bytecode saved" << LL_ENDL; mScriptEd->mErrorList->setCommentText(LLTrans::getString("CompileSuccessful")); mScriptEd->mErrorList->setCommentText(LLTrans::getString("SaveComplete")); closeIfNeeded(); } // virtual void LLPreviewLSL::callbackLSLCompileFailed(const LLSD& compile_errors) { LL_INFOS() << "Compile failed!" << LL_ENDL; for(LLSD::array_const_iterator line = compile_errors.beginArray(); line < compile_errors.endArray(); line++) { LLSD row; std::string error_message = line->asString(); LLStringUtil::stripNonprintable(error_message); row["columns"][0]["value"] = error_message; row["columns"][0]["font"] = "OCRA"; mScriptEd->mErrorList->addElement(row); } mScriptEd->selectFirstError(); closeIfNeeded(); } void LLPreviewLSL::loadAsset() { // *HACK: we poke into inventory to see if it's there, and if so, // then it might be part of the inventory library. If it's in the // library, then you can see the script, but not modify it. const LLInventoryItem* item = gInventory.getItem(mItemUUID); bool is_library = item && !gInventory.isObjectDescendentOf(mItemUUID, gInventory.getRootFolderID()); if(!item) { // do the more generic search. getItem(); } if(item) { bool is_copyable = gAgent.allowOperation(PERM_COPY, item->getPermissions(), GP_OBJECT_MANIPULATE); bool is_modifiable = gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE); if (gAgent.isGodlike() || (is_copyable && (is_modifiable || is_library))) { LLUUID* new_uuid = new LLUUID(mItemUUID); gAssetStorage->getInvItemAsset(LLHost(), gAgent.getID(), gAgent.getSessionID(), item->getPermissions().getOwner(), LLUUID::null, item->getUUID(), item->getAssetUUID(), item->getType(), &LLPreviewLSL::onLoadComplete, (void*)new_uuid, true); mAssetStatus = PREVIEW_ASSET_LOADING; } else { mScriptEd->setScriptText(mScriptEd->getString("can_not_view"), false); mScriptEd->mEditor->makePristine(); mScriptEd->mFunctions->setEnabled(false); mAssetStatus = PREVIEW_ASSET_LOADED; } getChildView("lock")->setVisible( !is_modifiable); mScriptEd->getChildView("Insert...")->setEnabled(is_modifiable); } else { mScriptEd->setScriptText(std::string(HELLO_LSL), true); mScriptEd->setEnableEditing(true); mAssetStatus = PREVIEW_ASSET_LOADED; } } bool LLPreviewLSL::canClose() { return mScriptEd->canClose(); } void LLPreviewLSL::closeIfNeeded() { // Find our window and close it if requested. getWindow()->decBusyCount(); mPendingUploads--; if (mPendingUploads <= 0 && mCloseAfterSave) { closeFloater(); } } void LLPreviewLSL::onSearchReplace(void* userdata) { LLPreviewLSL* self = (LLPreviewLSL*)userdata; LLScriptEdCore* sec = self->mScriptEd; LLFloaterScriptSearch::show(sec); } // static void LLPreviewLSL::onLoad(void* userdata) { LLPreviewLSL* self = (LLPreviewLSL*)userdata; self->loadAsset(); } // static void LLPreviewLSL::onSave(void* userdata, bool close_after_save) { LLPreviewLSL* self = (LLPreviewLSL*)userdata; self->mCloseAfterSave = close_after_save; self->saveIfNeeded(); } /*static*/ void LLPreviewLSL::finishedLSLUpload(LLUUID itemId, LLSD response) { // Find our window and close it if requested. LLPreviewLSL* preview = LLFloaterReg::findTypedInstance("preview_script", LLSD(itemId)); if (preview) { // Bytecode save completed if (response["compiled"]) { preview->callbackLSLCompileSucceeded(); } else { preview->callbackLSLCompileFailed(response["errors"]); } } } bool LLPreviewLSL::failedLSLUpload(LLUUID itemId, LLUUID taskId, LLSD response, std::string reason) { LLSD floater_key; if (taskId.notNull()) { floater_key["taskid"] = taskId; floater_key["itemid"] = itemId; } else { floater_key = LLSD(itemId); } LLPreviewLSL* preview = LLFloaterReg::findTypedInstance("preview_script", floater_key); if (preview) { // unfreeze floater LLSD errors; errors.append(LLTrans::getString("UploadFailed") + reason); preview->callbackLSLCompileFailed(errors); return true; } return false; } // Save needs to compile the text in the buffer. If the compile // succeeds, then save both assets out to the database. If the compile // fails, go ahead and save the text anyway. void LLPreviewLSL::saveIfNeeded(bool sync /*= true*/) { if (!mScriptEd->hasChanged()) { return; } mPendingUploads = 0; mScriptEd->mErrorList->deleteAllItems(); mScriptEd->mEditor->makePristine(); if (sync) { mScriptEd->sync(); } if (!gAgent.getRegion()) return; const LLInventoryItem *inv_item = getItem(); // save it out to asset server std::string url = gAgent.getRegion()->getCapability("UpdateScriptAgent"); if(inv_item) { getWindow()->incBusyCount(); mPendingUploads++; if (!url.empty()) { std::string buffer(mScriptEd->mEditor->getText()); LLUUID old_asset_id = inv_item->getAssetUUID().isNull() ? mScriptEd->getAssetID() : inv_item->getAssetUUID(); LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared(mItemUUID, buffer, [old_asset_id](LLUUID itemId, LLUUID, LLUUID, LLSD response) { LLFileSystem::removeFile(old_asset_id, LLAssetType::AT_LSL_TEXT); LLPreviewLSL::finishedLSLUpload(itemId, response); }, LLPreviewLSL::failedLSLUpload)); LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); } } } // static void LLPreviewLSL::onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status) { LL_DEBUGS() << "LLPreviewLSL::onLoadComplete: got uuid " << asset_uuid << LL_ENDL; LLUUID* item_uuid = (LLUUID*)user_data; LLPreviewLSL* preview = LLFloaterReg::findTypedInstance("preview_script", *item_uuid); if( preview ) { if(0 == status) { LLFileSystem file(asset_uuid, type); S32 file_length = file.getSize(); std::vector buffer(file_length+1); file.read((U8*)&buffer[0], file_length); // put a EOS at the end buffer[file_length] = 0; preview->mScriptEd->setScriptText(LLStringExplicit(&buffer[0]), true); preview->mScriptEd->mEditor->makePristine(); std::string script_name = DEFAULT_SCRIPT_NAME; LLInventoryItem* item = gInventory.getItem(*item_uuid); bool is_modifiable = false; if (item) { if (!item->getName().empty()) { script_name = item->getName(); } if (gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE)) { is_modifiable = true; } } preview->mScriptEd->setScriptName(script_name); preview->mScriptEd->setEnableEditing(is_modifiable); preview->mScriptEd->setAssetID(asset_uuid); preview->mAssetStatus = PREVIEW_ASSET_LOADED; } else { if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || LL_ERR_FILE_EMPTY == status) { LLNotificationsUtil::add("ScriptMissing"); } else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status) { LLNotificationsUtil::add("ScriptNoPermissions"); } else { LLNotificationsUtil::add("UnableToLoadScript"); } preview->mAssetStatus = PREVIEW_ASSET_ERROR; LL_WARNS() << "Problem loading script: " << status << LL_ENDL; } } delete item_uuid; } /// --------------------------------------------------------------------------- /// LLLiveLSLEditor /// --------------------------------------------------------------------------- //static void* LLLiveLSLEditor::createScriptEdPanel(void* userdata) { LLLiveLSLEditor *self = (LLLiveLSLEditor*)userdata; self->mScriptEd = new LLScriptEdCore( self, HELLO_LSL, self->getHandle(), &LLLiveLSLEditor::onLoad, &LLLiveLSLEditor::onSave, &LLLiveLSLEditor::onSearchReplace, self, true, 0); return self->mScriptEd; } LLLiveLSLEditor::LLLiveLSLEditor(const LLSD& key) : LLScriptEdContainer(key), mAskedForRunningInfo(false), mHaveRunningInfo(false), mCloseAfterSave(false), mPendingUploads(0), mIsModifiable(false), mIsNew(false), mIsSaving(false), mObjectName("") { mFactoryMap["script ed panel"] = LLCallbackMap(LLLiveLSLEditor::createScriptEdPanel, this); } bool LLLiveLSLEditor::postBuild() { childSetCommitCallback("running", LLLiveLSLEditor::onRunningCheckboxClicked, this); getChildView("running")->setEnabled(false); childSetAction("Reset",&LLLiveLSLEditor::onReset,this); getChildView("Reset")->setEnabled(true); mMonoCheckbox = getChild("mono"); childSetCommitCallback("mono", &LLLiveLSLEditor::onMonoCheckboxClicked, this); getChildView("mono")->setEnabled(true); mScriptEd->mEditor->makePristine(); mScriptEd->mEditor->setFocus(true); mExperiences = getChild("Experiences..."); mExperiences->setCommitCallback(boost::bind(&LLLiveLSLEditor::experienceChanged, this)); mExperienceEnabled = getChild("enable_xp"); childSetCommitCallback("enable_xp", onToggleExperience, this); childSetCommitCallback("view_profile", onViewProfile, this); return LLPreview::postBuild(); } // virtual void LLLiveLSLEditor::callbackLSLCompileSucceeded(const LLUUID& task_id, const LLUUID& item_id, bool is_script_running) { LL_DEBUGS() << "LSL Bytecode saved" << LL_ENDL; mScriptEd->mErrorList->setCommentText(LLTrans::getString("CompileSuccessful")); mScriptEd->mErrorList->setCommentText(LLTrans::getString("SaveComplete")); getChild("running")->set(is_script_running); mIsSaving = false; closeIfNeeded(); } // virtual void LLLiveLSLEditor::callbackLSLCompileFailed(const LLSD& compile_errors) { LL_DEBUGS() << "Compile failed!" << LL_ENDL; for(LLSD::array_const_iterator line = compile_errors.beginArray(); line < compile_errors.endArray(); line++) { LLSD row; std::string error_message = line->asString(); LLStringUtil::stripNonprintable(error_message); row["columns"][0]["value"] = error_message; // *TODO: change to "MONOSPACE" and change llfontgl.cpp? row["columns"][0]["font"] = "OCRA"; mScriptEd->mErrorList->addElement(row); } mScriptEd->selectFirstError(); mIsSaving = false; closeIfNeeded(); } void LLLiveLSLEditor::loadAsset() { //LL_INFOS() << "LLLiveLSLEditor::loadAsset()" << LL_ENDL; if(!mIsNew) { LLViewerObject* object = gObjectList.findObject(mObjectUUID); if(object) { LLViewerInventoryItem* item = dynamic_cast(object->getInventoryObject(mItemUUID)); if(item) { LLViewerRegion* region = object->getRegion(); std::string url = std::string(); if(region) { url = region->getCapability("GetMetadata"); } LLExperienceCache::instance().fetchAssociatedExperience(item->getParentUUID(), item->getUUID(), url, boost::bind(&LLLiveLSLEditor::setAssociatedExperience, getDerivedHandle(), _1)); bool isGodlike = gAgent.isGodlike(); bool copyManipulate = gAgent.allowOperation(PERM_COPY, item->getPermissions(), GP_OBJECT_MANIPULATE); mIsModifiable = gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE); if(!isGodlike && (!copyManipulate || !mIsModifiable)) { mItem = new LLViewerInventoryItem(item); mScriptEd->setScriptText(getString("not_allowed"), false); mScriptEd->mEditor->makePristine(); mScriptEd->enableSave(false); mAssetStatus = PREVIEW_ASSET_LOADED; } else if(copyManipulate || isGodlike) { mItem = new LLViewerInventoryItem(item); // request the text from the object LLSD* user_data = new LLSD(); user_data->with("taskid", mObjectUUID).with("itemid", mItemUUID); gAssetStorage->getInvItemAsset(object->getRegion()->getHost(), gAgent.getID(), gAgent.getSessionID(), item->getPermissions().getOwner(), object->getID(), item->getUUID(), item->getAssetUUID(), item->getType(), &LLLiveLSLEditor::onLoadComplete, (void*)user_data, true); LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_GetScriptRunning); msg->nextBlockFast(_PREHASH_Script); msg->addUUIDFast(_PREHASH_ObjectID, mObjectUUID); msg->addUUIDFast(_PREHASH_ItemID, mItemUUID); msg->sendReliable(object->getRegion()->getHost()); mAskedForRunningInfo = true; mAssetStatus = PREVIEW_ASSET_LOADING; } } if(mItem.isNull()) { mScriptEd->setScriptText(LLStringUtil::null, false); mScriptEd->mEditor->makePristine(); mAssetStatus = PREVIEW_ASSET_LOADED; mIsModifiable = false; } refreshFromItem(); getChild("obj_name")->setValue(mObjectName); // This is commented out, because we don't completely // handle script exports yet. /* // request the exports from the object gMessageSystem->newMessage("GetScriptExports"); gMessageSystem->nextBlock("ScriptBlock"); gMessageSystem->addUUID("AgentID", gAgent.getID()); U32 local_id = object->getLocalID(); gMessageSystem->addData("LocalID", &local_id); gMessageSystem->addUUID("ItemID", mItemUUID); LLHost host(object->getRegion()->getIP(), object->getRegion()->getPort()); gMessageSystem->sendReliable(host); */ } } else { mScriptEd->setScriptText(std::string(HELLO_LSL), true); mScriptEd->enableSave(false); LLPermissions perm; perm.init(gAgent.getID(), gAgent.getID(), LLUUID::null, gAgent.getGroupID()); perm.initMasks(PERM_ALL, PERM_ALL, PERM_NONE, PERM_NONE, PERM_MOVE | PERM_TRANSFER); mItem = new LLViewerInventoryItem(mItemUUID, mObjectUUID, perm, LLUUID::null, LLAssetType::AT_LSL_TEXT, LLInventoryType::IT_LSL, DEFAULT_SCRIPT_NAME, DEFAULT_SCRIPT_DESC, LLSaleInfo::DEFAULT, LLInventoryItemFlags::II_FLAGS_NONE, time_corrected()); mAssetStatus = PREVIEW_ASSET_LOADED; } requestExperiences(); } // static void LLLiveLSLEditor::onLoadComplete(const LLUUID& asset_id, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status) { LL_DEBUGS() << "LLLiveLSLEditor::onLoadComplete: got uuid " << asset_id << LL_ENDL; LLSD* floater_key = (LLSD*)user_data; LLLiveLSLEditor* instance = LLFloaterReg::findTypedInstance("preview_scriptedit", *floater_key); if(instance ) { if( LL_ERR_NOERR == status ) { instance->loadScriptText(asset_id, type); instance->mScriptEd->setEnableEditing(true); instance->mAssetStatus = PREVIEW_ASSET_LOADED; instance->mScriptEd->setAssetID(asset_id); } else { if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || LL_ERR_FILE_EMPTY == status) { LLNotificationsUtil::add("ScriptMissing"); } else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status) { LLNotificationsUtil::add("ScriptNoPermissions"); } else { LLNotificationsUtil::add("UnableToLoadScript"); } instance->mAssetStatus = PREVIEW_ASSET_ERROR; } } delete floater_key; } void LLLiveLSLEditor::loadScriptText(const LLUUID &uuid, LLAssetType::EType type) { LLFileSystem file(uuid, type); S32 file_length = file.getSize(); std::vector buffer(file_length + 1); file.read((U8*)&buffer[0], file_length); if (file.getLastBytesRead() != file_length || file_length <= 0) { LL_WARNS() << "Error reading " << uuid << ":" << type << LL_ENDL; } buffer[file_length] = '\0'; mScriptEd->setScriptText(LLStringExplicit(&buffer[0]), true); mScriptEd->makeEditorPristine(); std::string script_name = DEFAULT_SCRIPT_NAME; const LLInventoryItem* inv_item = getItem(); if(inv_item) { script_name = inv_item->getName(); } mScriptEd->setScriptName(script_name); } void LLLiveLSLEditor::onRunningCheckboxClicked( LLUICtrl*, void* userdata ) { LLLiveLSLEditor* self = (LLLiveLSLEditor*) userdata; LLViewerObject* object = gObjectList.findObject( self->mObjectUUID ); LLCheckBoxCtrl* runningCheckbox = self->getChild("running"); bool running = runningCheckbox->get(); //self->mRunningCheckbox->get(); if( object ) { LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_SetScriptRunning); msg->nextBlockFast(_PREHASH_AgentData); msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); msg->nextBlockFast(_PREHASH_Script); msg->addUUIDFast(_PREHASH_ObjectID, self->mObjectUUID); msg->addUUIDFast(_PREHASH_ItemID, self->mItemUUID); msg->addBOOLFast(_PREHASH_Running, running); msg->sendReliable(object->getRegion()->getHost()); } else { runningCheckbox->set(!running); LLNotificationsUtil::add("CouldNotStartStopScript"); } } void LLLiveLSLEditor::onReset(void *userdata) { LLLiveLSLEditor* self = (LLLiveLSLEditor*) userdata; LLViewerObject* object = gObjectList.findObject( self->mObjectUUID ); if(object) { LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_ScriptReset); msg->nextBlockFast(_PREHASH_AgentData); msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); msg->nextBlockFast(_PREHASH_Script); msg->addUUIDFast(_PREHASH_ObjectID, self->mObjectUUID); msg->addUUIDFast(_PREHASH_ItemID, self->mItemUUID); msg->sendReliable(object->getRegion()->getHost()); } else { LLNotificationsUtil::add("CouldNotStartStopScript"); } } void LLLiveLSLEditor::draw() { LLViewerObject* object = gObjectList.findObject(mObjectUUID); LLCheckBoxCtrl* runningCheckbox = getChild( "running"); if(object && mAskedForRunningInfo && mHaveRunningInfo) { if(object->permAnyOwner()) { runningCheckbox->setLabel(getString("script_running")); runningCheckbox->setEnabled(!mIsSaving); } else { runningCheckbox->setLabel(getString("public_objects_can_not_run")); runningCheckbox->setEnabled(false); // *FIX: Set it to false so that the ui is correct for // a box that is released to public. It could be // incorrect after a release/claim cycle, but will be // correct after clicking on it. runningCheckbox->set(false); mMonoCheckbox->set(false); } } else if(!object) { // HACK: Display this information in the title bar. // Really ought to put in main window. setTitle(LLTrans::getString("ObjectOutOfRange")); runningCheckbox->setEnabled(false); mMonoCheckbox->setEnabled(false); // object may have fallen out of range. mHaveRunningInfo = false; } LLPreview::draw(); } void LLLiveLSLEditor::onSearchReplace(void* userdata) { LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata; LLScriptEdCore* sec = self->mScriptEd; LLFloaterScriptSearch::show(sec); } struct LLLiveLSLSaveData { LLLiveLSLSaveData(const LLUUID& id, const LLViewerInventoryItem* item, bool active); LLUUID mSaveObjectID; LLPointer mItem; bool mActive; }; LLLiveLSLSaveData::LLLiveLSLSaveData(const LLUUID& id, const LLViewerInventoryItem* item, bool active) : mSaveObjectID(id), mActive(active) { llassert(item); mItem = new LLViewerInventoryItem(item); } /*static*/ void LLLiveLSLEditor::finishLSLUpload(LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response, bool isRunning) { LLSD floater_key; floater_key["taskid"] = taskId; floater_key["itemid"] = itemId; LLLiveLSLEditor* preview = LLFloaterReg::findTypedInstance("preview_scriptedit", floater_key); if (preview) { preview->mItem->setAssetUUID(newAssetId); preview->mScriptEd->setAssetID(newAssetId); // Bytecode save completed if (response["compiled"]) { preview->callbackLSLCompileSucceeded(taskId, itemId, isRunning); } else { preview->callbackLSLCompileFailed(response["errors"]); } } } // virtual void LLLiveLSLEditor::saveIfNeeded(bool sync /*= true*/) { LLViewerObject* object = gObjectList.findObject(mObjectUUID); if (!object) { LLNotificationsUtil::add("SaveScriptFailObjectNotFound"); return; } if (mItem.isNull() || !mItem->isFinished()) { // $NOTE: While the error message may not be exactly correct, // it's pretty close. LLNotificationsUtil::add("SaveScriptFailObjectNotFound"); return; } // get the latest info about it. We used to be losing the script // name on save, because the viewer object version of the item, // and the editor version would get out of synch. Here's a good // place to synch them back up. LLInventoryItem* inv_item = dynamic_cast(object->getInventoryObject(mItemUUID)); if (inv_item) { mItem->copyItem(inv_item); } // Don't need to save if we're pristine if (!mScriptEd->hasChanged()) { return; } mPendingUploads = 0; // save the script mScriptEd->enableSave(false); mScriptEd->mEditor->makePristine(); mScriptEd->mErrorList->deleteAllItems(); mScriptEd->mEditor->makePristine(); if (sync) { mScriptEd->sync(); } bool isRunning = getChild("running")->get(); getWindow()->incBusyCount(); mPendingUploads++; std::string url = object->getRegion()->getCapability("UpdateScriptTask"); if (!url.empty()) { std::string buffer(mScriptEd->mEditor->getText()); LLUUID old_asset_id = mScriptEd->getAssetID(); LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared(mObjectUUID, mItemUUID, monoChecked() ? LLScriptAssetUpload::MONO : LLScriptAssetUpload::LSL2, isRunning, mScriptEd->getAssociatedExperience(), buffer, [isRunning, old_asset_id](LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response) { LLFileSystem::removeFile(old_asset_id, LLAssetType::AT_LSL_TEXT); LLLiveLSLEditor::finishLSLUpload(itemId, taskId, newAssetId, response, isRunning); }, nullptr)); // needs failure handling? LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); } } bool LLLiveLSLEditor::canClose() { return mScriptEd->canClose(); } void LLLiveLSLEditor::closeIfNeeded() { getWindow()->decBusyCount(); mPendingUploads--; if ((mPendingUploads <= 0) && mCloseAfterSave) { closeFloater(); } } // static void LLLiveLSLEditor::onLoad(void* userdata) { LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata; self->loadAsset(); } // static void LLLiveLSLEditor::onSave(void* userdata, bool close_after_save) { if (LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata) { self->mCloseAfterSave = close_after_save; self->mScriptEd->mErrorList->setCommentText(""); self->saveIfNeeded(); } } // static void LLLiveLSLEditor::processScriptRunningReply(LLMessageSystem* msg, void**) { LLUUID item_id; LLUUID object_id; msg->getUUIDFast(_PREHASH_Script, _PREHASH_ObjectID, object_id); msg->getUUIDFast(_PREHASH_Script, _PREHASH_ItemID, item_id); LLSD floater_key; floater_key["taskid"] = object_id; floater_key["itemid"] = item_id; if (LLLiveLSLEditor* instance = LLFloaterReg::findTypedInstance("preview_scriptedit", floater_key)) { instance->mHaveRunningInfo = true; bool running; msg->getBOOLFast(_PREHASH_Script, _PREHASH_Running, running); LLCheckBoxCtrl* runningCheckbox = instance->getChild("running"); runningCheckbox->set(running); bool mono; msg->getBOOLFast(_PREHASH_Script, "Mono", mono); LLCheckBoxCtrl* monoCheckbox = instance->getChild("mono"); monoCheckbox->setEnabled(instance->getIsModifiable() && have_script_upload_cap(object_id)); monoCheckbox->set(mono); } } void LLLiveLSLEditor::onMonoCheckboxClicked(LLUICtrl*, void* userdata) { LLLiveLSLEditor* self = static_cast(userdata); self->mMonoCheckbox->setEnabled(have_script_upload_cap(self->mObjectUUID)); self->mScriptEd->enableSave(self->getIsModifiable()); } bool LLLiveLSLEditor::monoChecked() const { return mMonoCheckbox && mMonoCheckbox->getValue(); } void LLLiveLSLEditor::setAssociatedExperience( LLHandle editor, const LLSD& experience ) { if (LLLiveLSLEditor* scriptEd = editor.get()) { LLUUID id; if (experience.has(LLExperienceCache::EXPERIENCE_ID)) { id=experience[LLExperienceCache::EXPERIENCE_ID].asUUID(); } scriptEd->mScriptEd->setAssociatedExperience(id); scriptEd->updateExperiencePanel(); } }