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
parent
5b86f60a13
commit
b2fcba25c8
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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**)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue