STORM-52 FIXED Made it possible to use an external script editor.

The editor can be specified:
* via "ExternalEditor" setting in settings.xml
* via LL_SCRIPT_EDITOR variable

Removed obsolete XUIEditor setting in favor of the new one.
master
Vadim ProductEngine 2010-11-13 00:53:29 +02:00
parent 5b86f60a13
commit b2fcba25c8
10 changed files with 542 additions and 233 deletions

View File

@ -58,6 +58,11 @@ void LLProcessLauncher::setWorkingDirectory(const std::string &dir)
mWorkingDir = dir;
}
const std::string& LLProcessLauncher::getExecutable() const
{
return mExecutable;
}
void LLProcessLauncher::clearArguments()
{
mLaunchArguments.clear();

View File

@ -47,6 +47,8 @@ public:
void setExecutable(const std::string &executable);
void setWorkingDirectory(const std::string &dir);
const std::string& getExecutable() const;
void clearArguments();
void addArgument(const std::string &arg);
void addArgument(const char *arg);

View File

@ -143,6 +143,7 @@ set(viewer_SOURCE_FILES
lleventnotifier.cpp
lleventpoll.cpp
llexpandabletextbox.cpp
llexternaleditor.cpp
llface.cpp
llfasttimerview.cpp
llfavoritesbar.cpp
@ -674,6 +675,7 @@ set(viewer_HEADER_FILES
lleventnotifier.h
lleventpoll.h
llexpandabletextbox.h
llexternaleditor.h
llface.h
llfasttimerview.h
llfavoritesbar.h

View File

@ -11883,10 +11883,10 @@
<key>Value</key>
<real>150000.0</real>
</map>
<key>XUIEditor</key>
<key>ExternalEditor</key>
<map>
<key>Comment</key>
<string>Path to program used to edit XUI files</string>
<string>Path to program used to edit LSL scripts and XUI files, e.g.: /usr/bin/gedit --new-window "%s"</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>

View File

@ -0,0 +1,192 @@
/**
* @file llexternaleditor.cpp
* @brief A convenient class to run external editor.
*
* $LicenseInfo:firstyear=2010&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 "llexternaleditor.h"
#include "llui.h"
// static
const std::string LLExternalEditor::sFilenameMarker = "%s";
// static
const std::string LLExternalEditor::sSetting = "ExternalEditor";
bool LLExternalEditor::setCommand(const std::string& env_var, const std::string& override)
{
std::string cmd = findCommand(env_var, override);
if (cmd.empty())
{
llwarns << "Empty editor command" << llendl;
return false;
}
// Add the filename marker if missing.
if (cmd.find(sFilenameMarker) == std::string::npos)
{
cmd += " \"" + sFilenameMarker + "\"";
llinfos << "Adding the filename marker (" << sFilenameMarker << ")" << llendl;
}
string_vec_t tokens;
if (tokenize(tokens, cmd) < 2) // 2 = bin + at least one arg (%s)
{
llwarns << "Error parsing editor command" << llendl;
return false;
}
// Check executable for existence.
std::string bin_path = tokens[0];
if (!LLFile::isfile(bin_path))
{
llwarns << "Editor binary [" << bin_path << "] not found" << llendl;
return false;
}
// Save command.
mProcess.setExecutable(bin_path);
mArgs.clear();
for (size_t i = 1; i < tokens.size(); ++i)
{
if (i > 1) mArgs += " ";
mArgs += "\"" + tokens[i] + "\"";
}
llinfos << "Setting command [" << bin_path << " " << mArgs << "]" << llendl;
return true;
}
bool LLExternalEditor::run(const std::string& file_path)
{
std::string args = mArgs;
if (mProcess.getExecutable().empty() || args.empty())
{
llwarns << "Editor command not set" << llendl;
return false;
}
// Substitute the filename marker in the command with the actual passed file name.
LLStringUtil::replaceString(args, sFilenameMarker, file_path);
// Split command into separate tokens.
string_vec_t tokens;
tokenize(tokens, args);
// Set process arguments taken from the command.
mProcess.clearArguments();
for (string_vec_t::const_iterator arg_it = tokens.begin(); arg_it != tokens.end(); ++arg_it)
{
mProcess.addArgument(*arg_it);
}
// Run the editor.
llinfos << "Running editor command [" << mProcess.getExecutable() + " " + args << "]" << llendl;
int result = mProcess.launch();
if (result == 0)
{
// Prevent killing the process in destructor (will add it to the zombies list).
mProcess.orphan();
}
return result == 0;
}
// static
size_t LLExternalEditor::tokenize(string_vec_t& tokens, const std::string& str)
{
tokens.clear();
// Split the argument string into separate strings for each argument
typedef boost::tokenizer< boost::char_separator<char> > tokenizer;
boost::char_separator<char> sep("", "\" ", boost::drop_empty_tokens);
tokenizer tokens_list(str, sep);
tokenizer::iterator token_iter;
BOOL inside_quotes = FALSE;
BOOL last_was_space = FALSE;
for (token_iter = tokens_list.begin(); token_iter != tokens_list.end(); ++token_iter)
{
if (!strncmp("\"",(*token_iter).c_str(),2))
{
inside_quotes = !inside_quotes;
}
else if (!strncmp(" ",(*token_iter).c_str(),2))
{
if(inside_quotes)
{
tokens.back().append(std::string(" "));
last_was_space = TRUE;
}
}
else
{
std::string to_push = *token_iter;
if (last_was_space)
{
tokens.back().append(to_push);
last_was_space = FALSE;
}
else
{
tokens.push_back(to_push);
}
}
}
return tokens.size();
}
// static
std::string LLExternalEditor::findCommand(
const std::string& env_var,
const std::string& override)
{
std::string cmd;
// Get executable path.
if (!override.empty()) // try the supplied override first
{
cmd = override;
llinfos << "Using override" << llendl;
}
else if (!LLUI::sSettingGroups["config"]->getString(sSetting).empty())
{
cmd = LLUI::sSettingGroups["config"]->getString(sSetting);
llinfos << "Using setting" << llendl;
}
else // otherwise use the path specified by the environment variable
{
char* env_var_val = getenv(env_var.c_str());
if (env_var_val)
{
cmd = env_var_val;
llinfos << "Using env var " << env_var << llendl;
}
}
llinfos << "Found command [" << cmd << "]" << llendl;
return cmd;
}

View File

@ -0,0 +1,91 @@
/**
* @file llexternaleditor.h
* @brief A convenient class to run external editor.
*
* $LicenseInfo:firstyear=2010&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$
*/
#ifndef LL_LLEXTERNALEDITOR_H
#define LL_LLEXTERNALEDITOR_H
#include <llprocesslauncher.h>
/**
* Usage:
* LLExternalEditor ed;
* ed.setCommand("MY_EXTERNAL_EDITOR_VAR");
* ed.run("/path/to/file1");
* ed.run("/other/path/to/file2");
*/
class LLExternalEditor
{
typedef std::vector<std::string> string_vec_t;
public:
/**
* Set editor command.
*
* @param env_var Environment variable of the same purpose.
* @param override Optional override.
*
* First tries the override, then a predefined setting (sSetting),
* then the environment variable.
*
* @return Command if found, empty string otherwise.
*
* @see sSetting
*/
bool setCommand(const std::string& env_var, const std::string& override = LLStringUtil::null);
/**
* Run the editor with the given file.
*
* @param file_path File to edit.
* @return true on success, false on error.
*/
bool run(const std::string& file_path);
private:
static std::string findCommand(
const std::string& env_var,
const std::string& override);
static size_t tokenize(string_vec_t& tokens, const std::string& str);
/**
* Filename placeholder that gets replaced with an actual file name.
*/
static const std::string sFilenameMarker;
/**
* Setting that can specify the editor command.
*/
static const std::string sSetting;
std::string mArgs;
LLProcessLauncher mProcess;
};
#endif // LL_LLEXTERNALEDITOR_H

View File

@ -36,6 +36,7 @@
// Internal utility
#include "lleventtimer.h"
#include "llexternaleditor.h"
#include "llrender.h"
#include "llsdutil.h"
#include "llxmltree.h"
@ -160,6 +161,8 @@ public:
DiffMap mDiffsMap; // map, of filename to pair of list of changed element paths and list of errors
private:
LLExternalEditor mExternalEditor;
// XUI elements for this floater
LLScrollListCtrl* mFileList; // scroll list control for file list
LLLineEditor* mEditorPathTextBox; // text field for path to editor executable
@ -185,7 +188,7 @@ private:
std::string mSavedDiffPath; // stored diff file path so closing this floater doesn't reset it
// Internal functionality
static void popupAndPrintWarning(std::string& warning); // pop up a warning
static void popupAndPrintWarning(const std::string& warning); // pop up a warning
std::string getLocalizedDirectory(); // build and return the path to the XUI directory for the currently-selected localization
void scanDiffFile(LLXmlTreeNode* file_node); // scan a given XML node for diff entries and highlight them in its associated file
void highlightChangedElements(); // look up the list of elements to highlight and highlight them in the current floater
@ -597,7 +600,7 @@ void LLFloaterUIPreview::onClose(bool app_quitting)
// Error handling (to avoid code repetition)
// *TODO: this is currently unlocalized. Add to alerts/notifications.xml, someday, maybe.
void LLFloaterUIPreview::popupAndPrintWarning(std::string& warning)
void LLFloaterUIPreview::popupAndPrintWarning(const std::string& warning)
{
llwarns << warning << llendl;
LLSD args;
@ -998,190 +1001,55 @@ void LLFloaterUIPreview::displayFloater(BOOL click, S32 ID, bool save)
// Respond to button click to edit currently-selected floater
void LLFloaterUIPreview::onClickEditFloater()
{
std::string file_name = mFileList->getSelectedItemLabel(1); // get the file name of the currently-selected floater
if(std::string("") == file_name) // if no item is selected
// Determine file to edit.
std::string file_path;
{
return; // ignore click
}
std::string path = getLocalizedDirectory() + file_name;
std::string file_name = mFileList->getSelectedItemLabel(1); // get the file name of the currently-selected floater
if (file_name.empty()) // if no item is selected
{
llwarns << "No file selected" << llendl;
return; // ignore click
}
file_path = getLocalizedDirectory() + file_name;
// stat file to see if it exists (some localized versions may not have it there are no diffs, and then we try to open an nonexistent file)
llstat dummy;
if(LLFile::stat(path.c_str(), &dummy)) // if the file does not exist
{
std::string warning = "No file for this floater exists in the selected localization. Opening the EN version instead.";
popupAndPrintWarning(warning);
path = get_xui_dir() + mDelim + "en" + mDelim + file_name; // open the en version instead, by default
// stat file to see if it exists (some localized versions may not have it there are no diffs, and then we try to open an nonexistent file)
llstat dummy;
if(LLFile::stat(file_path.c_str(), &dummy)) // if the file does not exist
{
popupAndPrintWarning("No file for this floater exists in the selected localization. Opening the EN version instead.");
file_path = get_xui_dir() + mDelim + "en" + mDelim + file_name; // open the en version instead, by default
}
}
// get executable path
const char* exe_path_char;
std::string path_in_textfield = mEditorPathTextBox->getText();
if(std::string("") != path_in_textfield) // if the text field is not emtpy, use its path
// Set the editor command.
std::string cmd_override;
{
exe_path_char = path_in_textfield.c_str();
}
else if (!LLUI::sSettingGroups["config"]->getString("XUIEditor").empty())
{
exe_path_char = LLUI::sSettingGroups["config"]->getString("XUIEditor").c_str();
}
else // otherwise use the path specified by the environment variable
{
exe_path_char = getenv("LL_XUI_EDITOR");
}
std::string bin = mEditorPathTextBox->getText();
if (!bin.empty())
{
// surround command with double quotes for the case if the path contains spaces
if (bin.find("\"") == std::string::npos)
{
bin = "\"" + bin + "\"";
}
// error check executable path
if(NULL == exe_path_char)
std::string args = mEditorArgsTextBox->getText();
cmd_override = bin + " " + args;
}
}
if (!mExternalEditor.setCommand("LL_XUI_EDITOR", cmd_override))
{
std::string warning = "Select an editor by setting the environment variable LL_XUI_EDITOR or specifying its path in the \"Editor Path\" field.";
std::string warning = "Select an editor by setting the environment variable LL_XUI_EDITOR "
"or the ExternalEditor setting or specifying its path in the \"Editor Path\" field.";
popupAndPrintWarning(warning);
return;
}
std::string exe_path = exe_path_char; // do this after error check, otherwise internal strlen call fails on bad char*
// remove any quotes; they're added back in later where necessary
int found_at;
while((found_at = exe_path.find("\"")) != -1 || (found_at = exe_path.find("'")) != -1)
// Run the editor.
if (!mExternalEditor.run(file_path))
{
exe_path.erase(found_at,1);
}
llstat s;
if(!LLFile::stat(exe_path.c_str(), &s)) // If the executable exists
{
// build paths and arguments
std::string quote = std::string("\"");
std::string args;
std::string custom_args = mEditorArgsTextBox->getText();
int position_of_file = custom_args.find(std::string("%FILE%"), 0); // prepare to replace %FILE% with actual file path
std::string first_part_of_args = "";
std::string second_part_of_args = "";
if(-1 == position_of_file) // default: Executable.exe File.xml
{
args = quote + path + quote; // execute the command Program.exe "File.xml"
}
else // use advanced command-line arguments, e.g. "Program.exe -safe File.xml" -windowed for "-safe %FILE% -windowed"
{
first_part_of_args = custom_args.substr(0,position_of_file); // get part of args before file name
second_part_of_args = custom_args.substr(position_of_file+6,custom_args.length()); // get part of args after file name
custom_args = first_part_of_args + std::string("\"") + path + std::string("\"") + second_part_of_args; // replace %FILE% with "<file path>" and put back together
args = custom_args; // and save in the variable that is actually used
}
// find directory in which executable resides by taking everything after last slash
int last_slash_position = exe_path.find_last_of(mDelim);
if(-1 == last_slash_position)
{
std::string warning = std::string("Unable to find a valid path to the specified executable for XUI XML editing: ") + exe_path;
popupAndPrintWarning(warning);
return;
}
std::string exe_dir = exe_path.substr(0,last_slash_position); // strip executable off, e.g. get "C:\Program Files\TextPad 5" (with or without trailing slash)
#if LL_WINDOWS
PROCESS_INFORMATION pinfo;
STARTUPINFOA sinfo;
memset(&sinfo, 0, sizeof(sinfo));
memset(&pinfo, 0, sizeof(pinfo));
std::string exe_name = exe_path.substr(last_slash_position+1);
args = quote + exe_name + quote + std::string(" ") + args; // and prepend the executable name, so we get 'Program.exe "Arg1"'
char *args2 = new char[args.size() + 1]; // Windows requires that the second parameter to CreateProcessA be a writable (non-const) string...
strcpy(args2, args.c_str());
// we don't want the current directory to be the executable directory, since the file path is now relative. By using
// NULL for the current directory instead of exe_dir.c_str(), the path to the target file will work.
if(!CreateProcessA(exe_path.c_str(), args2, NULL, NULL, FALSE, 0, NULL, NULL, &sinfo, &pinfo))
{
// DWORD dwErr = GetLastError();
std::string warning = "Creating editor process failed!";
popupAndPrintWarning(warning);
}
else
{
// foo = pinfo.dwProcessId; // get your pid here if you want to use it later on
// sGatewayHandle = pinfo.hProcess;
CloseHandle(pinfo.hThread); // stops leaks - nothing else
}
delete[] args2;
#else // if !LL_WINDOWS
// This code was copied from the code to run SLVoice, with some modification; should work in UNIX (Mac/Darwin or Linux)
{
std::vector<std::string> arglist;
arglist.push_back(exe_path.c_str());
// Split the argument string into separate strings for each argument
typedef boost::tokenizer< boost::char_separator<char> > tokenizer;
boost::char_separator<char> sep("","\" ", boost::drop_empty_tokens);
tokenizer tokens(args, sep);
tokenizer::iterator token_iter;
BOOL inside_quotes = FALSE;
BOOL last_was_space = FALSE;
for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter)
{
if(!strncmp("\"",(*token_iter).c_str(),2))
{
inside_quotes = !inside_quotes;
}
else if(!strncmp(" ",(*token_iter).c_str(),2))
{
if(inside_quotes)
{
arglist.back().append(std::string(" "));
last_was_space = TRUE;
}
}
else
{
std::string to_push = *token_iter;
if(last_was_space)
{
arglist.back().append(to_push);
last_was_space = FALSE;
}
else
{
arglist.push_back(to_push);
}
}
}
// create an argv vector for the child process
char **fakeargv = new char*[arglist.size() + 1];
int i;
for(i=0; i < arglist.size(); i++)
fakeargv[i] = const_cast<char*>(arglist[i].c_str());
fakeargv[i] = NULL;
fflush(NULL); // flush all buffers before the child inherits them
pid_t id = vfork();
if(id == 0)
{
// child
execv(exe_path.c_str(), fakeargv);
// If we reach this point, the exec failed.
// Use _exit() instead of exit() per the vfork man page.
std::string warning = "Creating editor process failed (vfork/execv)!";
popupAndPrintWarning(warning);
_exit(0);
}
// parent
delete[] fakeargv;
// sGatewayPID = id;
}
#endif // LL_WINDOWS
}
else
{
std::string warning = "Unable to find path to external XML editor for XUI preview tool";
popupAndPrintWarning(warning);
popupAndPrintWarning("Failed to run editor");
return;
}
}

View File

@ -34,11 +34,13 @@
#include "llcheckboxctrl.h"
#include "llcombobox.h"
#include "lldir.h"
#include "llexternaleditor.h"
#include "llfloaterreg.h"
#include "llinventorydefines.h"
#include "llinventorymodel.h"
#include "llkeyboard.h"
#include "lllineeditor.h"
#include "lllivefile.h"
#include "llhelp.h"
#include "llnotificationsutil.h"
#include "llresmgr.h"
@ -115,6 +117,54 @@ static bool have_script_upload_cap(LLUUID& object_id)
return object && (! object->getRegion()->getCapability("UpdateScriptTask").empty());
}
/// ---------------------------------------------------------------------------
/// LLLiveLSLFile
/// ---------------------------------------------------------------------------
class LLLiveLSLFile : public LLLiveFile
{
public:
LLLiveLSLFile(std::string file_path, LLLiveLSLEditor* parent);
~LLLiveLSLFile();
void ignoreNextUpdate() { mIgnoreNextUpdate = true; }
protected:
/*virtual*/ bool loadFile();
LLLiveLSLEditor* mParent;
bool mIgnoreNextUpdate;
};
LLLiveLSLFile::LLLiveLSLFile(std::string file_path, LLLiveLSLEditor* parent)
: mParent(parent)
, mIgnoreNextUpdate(false)
, LLLiveFile(file_path, 1.0)
{
}
LLLiveLSLFile::~LLLiveLSLFile()
{
LLFile::remove(filename());
}
bool LLLiveLSLFile::loadFile()
{
if (mIgnoreNextUpdate)
{
mIgnoreNextUpdate = false;
return true;
}
if (!mParent->loadScriptText(filename()))
{
return false;
}
// Disable sync to avoid recursive load->save->load calls.
mParent->saveIfNeeded(false);
return true;
}
/// ---------------------------------------------------------------------------
/// LLFloaterScriptSearch
/// ---------------------------------------------------------------------------
@ -281,6 +331,7 @@ LLScriptEdCore::LLScriptEdCore(
const LLHandle<LLFloater>& floater_handle,
void (*load_callback)(void*),
void (*save_callback)(void*, BOOL),
void (*edit_callback)(void*),
void (*search_replace_callback) (void* userdata),
void* userdata,
S32 bottom_pad)
@ -290,6 +341,7 @@ LLScriptEdCore::LLScriptEdCore(
mEditor( NULL ),
mLoadCallback( load_callback ),
mSaveCallback( save_callback ),
mEditCallback( edit_callback ),
mSearchReplaceCallback( search_replace_callback ),
mUserdata( userdata ),
mForceClose( FALSE ),
@ -329,6 +381,7 @@ BOOL LLScriptEdCore::postBuild()
childSetCommitCallback("lsl errors", &LLScriptEdCore::onErrorList, this);
childSetAction("Save_btn", boost::bind(&LLScriptEdCore::doSave,this,FALSE));
childSetAction("Edit_btn", boost::bind(&LLScriptEdCore::onEditButtonClick, this));
initMenu();
@ -809,6 +862,13 @@ void LLScriptEdCore::doSave( BOOL close_after_save )
}
}
void LLScriptEdCore::onEditButtonClick()
{
if (mEditCallback)
{
mEditCallback(mUserdata);
}
}
void LLScriptEdCore::onBtnUndoChanges()
{
@ -949,6 +1009,7 @@ void* LLPreviewLSL::createScriptEdPanel(void* userdata)
self->getHandle(),
LLPreviewLSL::onLoad,
LLPreviewLSL::onSave,
NULL, // no edit callback
LLPreviewLSL::onSearchReplace,
self,
0);
@ -1417,6 +1478,7 @@ void* LLLiveLSLEditor::createScriptEdPanel(void* userdata)
self->getHandle(),
&LLLiveLSLEditor::onLoad,
&LLLiveLSLEditor::onSave,
&LLLiveLSLEditor::onEdit,
&LLLiveLSLEditor::onSearchReplace,
self,
0);
@ -1433,6 +1495,7 @@ LLLiveLSLEditor::LLLiveLSLEditor(const LLSD& key) :
mCloseAfterSave(FALSE),
mPendingUploads(0),
mIsModifiable(FALSE),
mLiveFile(NULL),
mIsNew(false)
{
mFactoryMap["script ed panel"] = LLCallbackMap(LLLiveLSLEditor::createScriptEdPanel, this);
@ -1458,6 +1521,7 @@ BOOL LLLiveLSLEditor::postBuild()
LLLiveLSLEditor::~LLLiveLSLEditor()
{
delete mLiveFile;
}
// virtual
@ -1639,38 +1703,39 @@ void LLLiveLSLEditor::onLoadComplete(LLVFS *vfs, const LLUUID& asset_id,
delete xored_id;
}
// unused
// void LLLiveLSLEditor::loadScriptText(const std::string& filename)
// {
// if(!filename)
// {
// llerrs << "Filename is Empty!" << llendl;
// return;
// }
// LLFILE* file = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/
// if(file)
// {
// // read in the whole file
// fseek(file, 0L, SEEK_END);
// long file_length = 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 < (size_t) file_length)
// {
// llwarns << "Short read" << llendl;
// }
// buffer[nread] = '\0';
// fclose(file);
// mScriptEd->mEditor->setText(LLStringExplicit(buffer));
// mScriptEd->mEditor->makePristine();
// delete[] buffer;
// }
// else
// {
// llwarns << "Error opening " << filename << llendl;
// }
// }
bool LLLiveLSLEditor::loadScriptText(const std::string& filename)
{
if (filename.empty())
{
llwarns << "Empty file name" << llendl;
return false;
}
LLFILE* file = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/
if (!file)
{
llwarns << "Error opening " << filename << llendl;
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)
{
llwarns << "Short read" << llendl;
}
buffer[nread] = '\0';
fclose(file);
mScriptEd->mEditor->setText(LLStringExplicit(buffer));
//mScriptEd->mEditor->makePristine();
delete[] buffer;
return true;
}
void LLLiveLSLEditor::loadScriptText(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type)
{
@ -1825,9 +1890,8 @@ LLLiveLSLSaveData::LLLiveLSLSaveData(const LLUUID& id,
mItem = new LLViewerInventoryItem(item);
}
void LLLiveLSLEditor::saveIfNeeded()
void LLLiveLSLEditor::saveIfNeeded(bool sync)
{
llinfos << "LLLiveLSLEditor::saveIfNeeded()" << llendl;
LLViewerObject* object = gObjectList.findObject(mObjectUUID);
if(!object)
{
@ -1877,29 +1941,19 @@ void LLLiveLSLEditor::saveIfNeeded()
mItem->setAssetUUID(asset_id);
mItem->setTransactionID(tid);
// write out the data, and store it in the asset database
LLFILE* fp = LLFile::fopen(filename, "wb");
if(!fp)
writeToFile(filename);
if (sync)
{
llwarns << "Unable to write to " << filename << llendl;
LLSD row;
row["columns"][0]["value"] = "Error writing to local file. Is your hard drive full?";
row["columns"][0]["font"] = "SANSSERIF_SMALL";
mScriptEd->mErrorList->addElement(row);
return;
// Sync with external ed2itor.
std::string tmp_file = getTmpFileName();
llstat s;
if (LLFile::stat(tmp_file, &s) == 0) // file exists
{
if (mLiveFile) mLiveFile->ignoreNextUpdate();
writeToFile(tmp_file);
}
}
std::string utf8text = mScriptEd->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);
fp = NULL;
// save it out to asset server
std::string url = object->getRegion()->getCapability("UpdateScriptTask");
@ -1916,6 +1970,73 @@ void LLLiveLSLEditor::saveIfNeeded()
}
}
void LLLiveLSLEditor::openExternalEditor()
{
LLViewerObject* object = gObjectList.findObject(mObjectUUID);
if(!object)
{
LLNotificationsUtil::add("SaveScriptFailObjectNotFound");
return;
}
delete mLiveFile; // deletes file
// Save the script to a temporary file.
std::string filename = getTmpFileName();
writeToFile(filename);
// Start watching file changes.
mLiveFile = new LLLiveLSLFile(filename, this);
mLiveFile->addToEventTimer();
// Open it in external editor.
{
LLExternalEditor ed;
if (!ed.setCommand("LL_SCRIPT_EDITOR"))
{
std::string msg = "Select an editor by setting the environment variable LL_SCRIPT_EDITOR "
"or the ExternalEditor setting"; // *TODO: localize
LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", msg));
return;
}
ed.run(filename);
}
}
bool LLLiveLSLEditor::writeToFile(const std::string& filename)
{
LLFILE* fp = LLFile::fopen(filename, "wb");
if (!fp)
{
llwarns << "Unable to write to " << filename << llendl;
LLSD row;
row["columns"][0]["value"] = "Error writing to local file. Is your hard drive full?";
row["columns"][0]["font"] = "SANSSERIF_SMALL";
mScriptEd->mErrorList->addElement(row);
return false;
}
std::string utf8text = mScriptEd->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;
}
std::string LLLiveLSLEditor::getTmpFileName()
{
return std::string(LLFile::tmpdir()) + "sl_script_" + mObjectUUID.asString() + ".lsl";
}
void LLLiveLSLEditor::uploadAssetViaCaps(const std::string& url,
const std::string& filename,
const LLUUID& task_id,
@ -2138,6 +2259,14 @@ void LLLiveLSLEditor::onSave(void* userdata, BOOL close_after_save)
self->saveIfNeeded();
}
// static
void LLLiveLSLEditor::onEdit(void* userdata)
{
LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata;
self->openExternalEditor();
}
// static
void LLLiveLSLEditor::processScriptRunningReply(LLMessageSystem* msg, void**)
{

View File

@ -35,6 +35,7 @@
#include "lliconctrl.h"
#include "llframetimer.h"
class LLLiveLSLFile;
class LLMessageSystem;
class LLTextEditor;
class LLButton;
@ -62,6 +63,7 @@ public:
const LLHandle<LLFloater>& floater_handle,
void (*load_callback)(void* userdata),
void (*save_callback)(void* userdata, BOOL close_after_save),
void (*edit_callback)(void*),
void (*search_replace_callback)(void* userdata),
void* userdata,
S32 bottom_pad = 0); // pad below bottom row of buttons
@ -80,6 +82,8 @@ public:
bool handleSaveChangesDialog(const LLSD& notification, const LLSD& response);
bool handleReloadFromServerDialog(const LLSD& notification, const LLSD& response);
void onEditButtonClick();
static void onCheckLock(LLUICtrl*, void*);
static void onHelpComboCommit(LLUICtrl* ctrl, void* userdata);
static void onClickBack(void* userdata);
@ -114,6 +118,7 @@ private:
LLTextEditor* mEditor;
void (*mLoadCallback)(void* userdata);
void (*mSaveCallback)(void* userdata, BOOL close_after_save);
void (*mEditCallback)(void* userdata);
void (*mSearchReplaceCallback) (void* userdata);
void* mUserdata;
LLComboBox *mFunctions;
@ -179,6 +184,7 @@ protected:
// Used to view and edit an LSL that is attached to an object.
class LLLiveLSLEditor : public LLPreview
{
friend class LLLiveLSLFile;
public:
LLLiveLSLEditor(const LLSD& key);
~LLLiveLSLEditor();
@ -202,7 +208,10 @@ private:
virtual void loadAsset();
void loadAsset(BOOL is_new);
void saveIfNeeded();
void saveIfNeeded(bool sync = true);
void openExternalEditor();
std::string getTmpFileName();
bool writeToFile(const std::string& filename);
void uploadAssetViaCaps(const std::string& url,
const std::string& filename,
const LLUUID& task_id,
@ -218,6 +227,7 @@ private:
static void onSearchReplace(void* userdata);
static void onLoad(void* userdata);
static void onSave(void* userdata, BOOL close_after_save);
static void onEdit(void* userdata);
static void onLoadComplete(LLVFS *vfs, const LLUUID& asset_uuid,
LLAssetType::EType type,
@ -227,7 +237,7 @@ private:
static void onRunningCheckboxClicked(LLUICtrl*, void* userdata);
static void onReset(void* userdata);
// void loadScriptText(const std::string& filename); // unused
bool loadScriptText(const std::string& filename);
void loadScriptText(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type);
static void onErrorList(LLUICtrl*, void* user_data);
@ -253,6 +263,7 @@ private:
LLCheckBoxCtrl* mMonoCheckbox;
BOOL mIsModifiable;
LLLiveLSLFile* mLiveFile;
};
#endif // LL_LLPREVIEWSCRIPT_H

View File

@ -179,4 +179,13 @@
right="487"
name="Save_btn"
width="81" />
<button
follows="right|bottom"
height="23"
label="Edit..."
layout="topleft"
top_pad="-23"
right="400"
name="Edit_btn"
width="81" />
</panel>