diff options
| -rw-r--r-- | indra/llcommon/llprocesslauncher.cpp | 5 | ||||
| -rw-r--r-- | indra/llcommon/llprocesslauncher.h | 2 | ||||
| -rw-r--r-- | indra/newview/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | indra/newview/app_settings/settings.xml | 4 | ||||
| -rw-r--r-- | indra/newview/llexternaleditor.cpp | 192 | ||||
| -rw-r--r-- | indra/newview/llexternaleditor.h | 91 | ||||
| -rw-r--r-- | indra/newview/llfloateruipreview.cpp | 210 | ||||
| -rw-r--r-- | indra/newview/llpreviewscript.cpp | 235 | ||||
| -rw-r--r-- | indra/newview/llpreviewscript.h | 15 | ||||
| -rw-r--r-- | indra/newview/skins/default/xui/en/panel_script_ed.xml | 9 | 
10 files changed, 537 insertions, 228 deletions
| diff --git a/indra/llcommon/llprocesslauncher.cpp b/indra/llcommon/llprocesslauncher.cpp index 99308c94e7..81e5f8820d 100644 --- a/indra/llcommon/llprocesslauncher.cpp +++ b/indra/llcommon/llprocesslauncher.cpp @@ -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(); diff --git a/indra/llcommon/llprocesslauncher.h b/indra/llcommon/llprocesslauncher.h index 479aeb664a..954c249147 100644 --- a/indra/llcommon/llprocesslauncher.h +++ b/indra/llcommon/llprocesslauncher.h @@ -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); diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 09622d3af5..d44b0ce679 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -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 diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index ebd93b5987..a5d9bd0f4f 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -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> diff --git a/indra/newview/llexternaleditor.cpp b/indra/newview/llexternaleditor.cpp new file mode 100644 index 0000000000..54968841ab --- /dev/null +++ b/indra/newview/llexternaleditor.cpp @@ -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; +} diff --git a/indra/newview/llexternaleditor.h b/indra/newview/llexternaleditor.h new file mode 100644 index 0000000000..6ea210d5e2 --- /dev/null +++ b/indra/newview/llexternaleditor.h @@ -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 diff --git a/indra/newview/llfloateruipreview.cpp b/indra/newview/llfloateruipreview.cpp index 5dc8067648..d3a2f144d9 100644 --- a/indra/newview/llfloateruipreview.cpp +++ b/indra/newview/llfloateruipreview.cpp @@ -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 -	{ -		return;															// ignore click -	} -	std::string 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 -	} - -	// 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 -	{ -		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 +	// Determine file to edit. +	std::string file_path;  	{ -		exe_path_char = getenv("LL_XUI_EDITOR"); -	} - -	// error check executable path -	if(NULL == exe_path_char) -	{ -		std::string warning = "Select an editor by setting the environment variable LL_XUI_EDITOR 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) -	{ -		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" +		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  		{ -			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 +			llwarns << "No file selected" << llendl; +			return;															// ignore click  		} +		file_path = getLocalizedDirectory() + file_name; -		// 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 +		// 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  		{ -			// 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 +			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  		} +	} -		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) +	// Set the editor command. +	std::string cmd_override; +	{ +		std::string bin = mEditorPathTextBox->getText(); +		if (!bin.empty())  		{ -			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) +			// surround command with double quotes for the case if the path contains spaces +			if (bin.find("\"") == std::string::npos)  			{ -				// 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); +				bin = "\"" + bin + "\"";  			} -			// parent -			delete[] fakeargv; -			// sGatewayPID = id; +			std::string args = mEditorArgsTextBox->getText(); +			cmd_override = bin + " " + args;  		} -#endif	// LL_WINDOWS  	} -	else +	if (!mExternalEditor.setCommand("LL_XUI_EDITOR", cmd_override))  	{ -		std::string warning = "Unable to find path to external XML editor for XUI preview tool"; +		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; +	} + +	// Run the editor. +	if (!mExternalEditor.run(file_path)) +	{ +		popupAndPrintWarning("Failed to run editor"); +		return;  	}  } diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp index cf2ea38288..330e809c53 100644 --- a/indra/newview/llpreviewscript.cpp +++ b/indra/newview/llpreviewscript.cpp @@ -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" @@ -116,6 +118,54 @@ static bool have_script_upload_cap(LLUUID& object_id)  }  /// --------------------------------------------------------------------------- +/// 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  /// ---------------------------------------------------------------------------  class LLFloaterScriptSearch : public LLFloater @@ -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,9 +1941,74 @@ void LLLiveLSLEditor::saveIfNeeded()  	mItem->setAssetUUID(asset_id);  	mItem->setTransactionID(tid); -	// write out the data, and store it in the asset database +	writeToFile(filename); + +	if (sync) +	{ +		// 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); +		} +	} +	 +	// save it out to asset server +	std::string url = object->getRegion()->getCapability("UpdateScriptTask"); +	getWindow()->incBusyCount(); +	mPendingUploads++; +	BOOL is_running = getChild<LLCheckBoxCtrl>( "running")->get(); +	if (!url.empty()) +	{ +		uploadAssetViaCaps(url, filename, mObjectUUID, mItemUUID, is_running); +	} +	else if (gAssetStorage) +	{ +		uploadAssetLegacy(filename, object, tid, is_running); +	} +} + +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) +	if (!fp)  	{  		llwarns << "Unable to write to " << filename << llendl; @@ -1887,33 +2016,25 @@ void LLLiveLSLEditor::saveIfNeeded()  		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; +		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 ) +	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"); -	getWindow()->incBusyCount(); -	mPendingUploads++; -	BOOL is_running = getChild<LLCheckBoxCtrl>( "running")->get(); -	if (!url.empty()) -	{ -		uploadAssetViaCaps(url, filename, mObjectUUID, mItemUUID, is_running); -	} -	else if (gAssetStorage) -	{ -		uploadAssetLegacy(filename, object, tid, is_running); -	} +	return true; +} + +std::string LLLiveLSLEditor::getTmpFileName() +{ +	return std::string(LLFile::tmpdir()) + "sl_script_" + mObjectUUID.asString() + ".lsl";  }  void LLLiveLSLEditor::uploadAssetViaCaps(const std::string& url, @@ -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**)  { diff --git a/indra/newview/llpreviewscript.h b/indra/newview/llpreviewscript.h index f4b31e5962..d35c6b8528 100644 --- a/indra/newview/llpreviewscript.h +++ b/indra/newview/llpreviewscript.h @@ -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 diff --git a/indra/newview/skins/default/xui/en/panel_script_ed.xml b/indra/newview/skins/default/xui/en/panel_script_ed.xml index 1e332a40c2..a041c9b229 100644 --- a/indra/newview/skins/default/xui/en/panel_script_ed.xml +++ b/indra/newview/skins/default/xui/en/panel_script_ed.xml @@ -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> | 
