diff options
36 files changed, 5609 insertions, 1106 deletions
| diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index 1010b199a1..a5ad24815c 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -65,10 +65,12 @@ add_custom_target(viewer)  if (VIEWER)    add_subdirectory(${LIBS_OPEN_PREFIX}llcrashlogger)    add_subdirectory(${LIBS_OPEN_PREFIX}llui) +  add_subdirectory(${LIBS_OPEN_PREFIX}llxuixml)    if (LINUX)      add_subdirectory(${VIEWER_PREFIX}linux_crash_logger) -    add_dependencies(viewer linux-crash-logger-strip-target) +    add_subdirectory(${VIEWER_PREFIX}linux_updater) +    add_dependencies(viewer linux-crash-logger-strip-target linux-updater)    elseif (DARWIN)      add_subdirectory(${VIEWER_PREFIX}mac_crash_logger)      add_subdirectory(${VIEWER_PREFIX}mac_updater) diff --git a/indra/cmake/LLXUIXML.cmake b/indra/cmake/LLXUIXML.cmake new file mode 100644 index 0000000000..b8bfe48c77 --- /dev/null +++ b/indra/cmake/LLXUIXML.cmake @@ -0,0 +1,7 @@ +# -*- cmake -*- + +set(LLXUIXML_INCLUDE_DIRS +    ${LIBS_OPEN_DIR}/llxuixml +    ) + +set(LLXUIXML_LIBRARIES llxuixml) diff --git a/indra/integration_tests/llui_libtest/CMakeLists.txt b/indra/integration_tests/llui_libtest/CMakeLists.txt index 88564c6085..1ccdb0f20b 100644 --- a/indra/integration_tests/llui_libtest/CMakeLists.txt +++ b/indra/integration_tests/llui_libtest/CMakeLists.txt @@ -16,6 +16,7 @@ include(LLWindow)  include(LLUI)  include(LLVFS)        # ugh, needed for LLDir  include(LLXML) +include(LLXUIXML)  include(Linking)  # include(Tut) @@ -29,6 +30,7 @@ include_directories(      ${LLVFS_INCLUDE_DIRS}      ${LLWINDOW_INCLUDE_DIRS}      ${LLXML_INCLUDE_DIRS} +    ${LLXUIXML_INCLUDE_DIRS}      )  set(llui_libtest_SOURCE_FILES diff --git a/indra/integration_tests/llui_libtest/llui_libtest.cpp b/indra/integration_tests/llui_libtest/llui_libtest.cpp index 3d433fdfdc..3631761c93 100644 --- a/indra/integration_tests/llui_libtest/llui_libtest.cpp +++ b/indra/integration_tests/llui_libtest/llui_libtest.cpp @@ -43,7 +43,7 @@  #include "llfloater.h"  #include "llfontfreetype.h"  #include "llfontgl.h" -#include "lltrans.h" +#include "lltransutil.h"  #include "llui.h"  #include "lluictrlfactory.h" @@ -154,8 +154,8 @@ void init_llui()  	// Otherwise we get translation warnings when setting up floaters  	// (tooltips for buttons)  	std::set<std::string> default_args; -	LLTrans::parseStrings("strings.xml", default_args); -    LLTrans::parseLanguageStrings("language_settings.xml"); +	LLTransUtil::parseStrings("strings.xml", default_args); +	LLTransUtil::parseLanguageStrings("language_settings.xml");  	LLFontManager::initClass();  	// Creating widgets apparently requires fonts to be initialized, diff --git a/indra/linux_updater/CMakeLists.txt b/indra/linux_updater/CMakeLists.txt new file mode 100644 index 0000000000..9fe32ecb46 --- /dev/null +++ b/indra/linux_updater/CMakeLists.txt @@ -0,0 +1,58 @@ +# -*- cmake -*- + +project(linux_updater) + +include(00-Common) +include(CURL) +include(CARes) +include(OpenSSL) +include(UI) +include(LLCommon) +include(LLVFS) +include(LLXML) +include(LLXUIXML) +include(Linking) + +include_directories( +    ${LLCOMMON_INCLUDE_DIRS} +    ${LLVFS_INCLUDE_DIRS} +    ${LLXML_INCLUDE_DIRS} +    ${LLXUIXML_INCLUDE_DIRS} +    ${CURL_INCLUDE_DIRS} +    ${CARES_INCLUDE_DIRS} +    ${OPENSSL_INCLUDE_DIRS} +    ${UI_INCLUDE_DIRS} +    ) + +set(linux_updater_SOURCE_FILES linux_updater.cpp) + +set(linux_updater_HEADER_FILES CMakeLists.txt) + +set_source_files_properties(${linux_updater_HEADER_FILES} +                            PROPERTIES HEADER_FILES_ONLY TRUE) + +list(APPEND linux_updater_SOURCE_FILES ${linux_updater_HEADER_FILES}) + +add_executable(linux-updater ${linux_updater_SOURCE_FILES}) + +target_link_libraries(linux-updater +    ${CURL_LIBRARIES} +    ${CARES_LIBRARIES} +    ${OPENSSL_LIBRARIES} +    ${CRYPTO_LIBRARIES} +    ${UI_LIBRARIES} +    ${LLXML_LIBRARIES} +    ${LLXUIXML_LIBRARIES} +    ${LLVFS_LIBRARIES} +    ${LLCOMMON_LIBRARIES} +    ) + +add_custom_command( +    OUTPUT linux-updater-stripped +    COMMAND strip +    ARGS --strip-debug -o linux-updater-stripped linux-updater +    DEPENDS linux-updater +    ) + +add_custom_target(linux-updater-strip-target ALL +                  DEPENDS linux-updater-stripped) diff --git a/indra/linux_updater/linux_updater.cpp b/indra/linux_updater/linux_updater.cpp new file mode 100644 index 0000000000..acc60d42bf --- /dev/null +++ b/indra/linux_updater/linux_updater.cpp @@ -0,0 +1,818 @@ +/**  + * @file linux_updater.cpp + * @author Kyle Ambroff <ambroff@lindenlab.com>, Tofu Linden + * @brief Viewer update program for unix platforms that support GTK+ + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + *  + * Copyright (c) 2008, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include <unistd.h> +#include <signal.h> +#include <errno.h> + +#include "linden_common.h" +#include "llerrorcontrol.h" +#include "llfile.h" +#include "lldir.h" +#include "llxmlnode.h" +#include "lltrans.h" + +#include <curl/curl.h> + +extern "C" { +#include <gtk/gtk.h> +} + +const guint UPDATE_PROGRESS_TIMEOUT = 100; +const guint UPDATE_PROGRESS_TEXT_TIMEOUT = 1000; +const guint ROTATE_IMAGE_TIMEOUT = 8000; + +typedef struct _updater_app_state { +	std::string app_name; +	std::string url; +	std::string image_dir; +	std::string dest_dir; +	std::string strings_dirs; +	std::string strings_file; + +	GtkWidget *window; +	GtkWidget *progress_bar; +	GtkWidget *image; + +	double progress_value; +	bool activity_mode; + +	guint image_rotation_timeout_id; +	guint progress_update_timeout_id; +	guint update_progress_text_timeout_id; + +	bool failure; +} UpdaterAppState; + +// List of entries from strings.xml to always replace +static std::set<std::string> default_trans_args; +void init_default_trans_args() +{ +        default_trans_args.insert("SECOND_LIFE"); // World +        default_trans_args.insert("SECOND_LIFE_VIEWER"); +        default_trans_args.insert("SECOND_LIFE_GRID"); +        default_trans_args.insert("SECOND_LIFE_SUPPORT"); +} + +bool translate_init(std::string comma_delim_path_list, +		    std::string base_xml_name) +{ +	init_default_trans_args(); + +	// extract paths string vector from comma-delimited flat string +	std::vector<std::string> paths; +	LLStringUtil::getTokens(comma_delim_path_list, paths); // split over ',' + +	// suck the translation xml files into memory +	LLXMLNodePtr root; +	bool success = LLXMLNode::getLayeredXMLNode(base_xml_name, root, paths); +	if (!success) +	{ +		// couldn't load string table XML +		return false; +	} +	else +	{ +		// get those strings out of the XML +		LLTrans::parseStrings(root, default_trans_args); +		return true; +	} +} + + +void updater_app_ui_init(void); +void updater_app_quit(UpdaterAppState *app_state); +void parse_args_and_init(int argc, char **argv, UpdaterAppState *app_state); +std::string next_image_filename(std::string& image_path); +void display_error(GtkWidget *parent, std::string title, std::string message); +BOOL install_package(std::string package_file, std::string destination); +BOOL spawn_viewer(UpdaterAppState *app_state); + +extern "C" { +	void on_window_closed(GtkWidget *sender, gpointer state); +	gpointer worker_thread_cb(gpointer *data); +	int download_progress_cb(gpointer data, double t, double d, double utotal, double ulnow); +	gboolean rotate_image_cb(gpointer data); +	gboolean progress_update_timeout(gpointer data); +	gboolean update_progress_text_timeout(gpointer data); +} + +void updater_app_ui_init(UpdaterAppState *app_state) +{ +	GtkWidget *vbox; +	GtkWidget *summary_label; +	GtkWidget *description_label; +	GtkWidget *frame; + +	llassert(app_state != NULL); + +	// set up window and main container +	std::string window_title = LLTrans::getString("UpdaterWindowTitle"); +	app_state->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); +	gtk_window_set_title(GTK_WINDOW(app_state->window), +			     window_title.c_str()); +	gtk_window_set_resizable(GTK_WINDOW(app_state->window), FALSE); +	gtk_window_set_position(GTK_WINDOW(app_state->window), +				GTK_WIN_POS_CENTER_ALWAYS); + +	gtk_container_set_border_width(GTK_CONTAINER(app_state->window), 12); +	g_signal_connect(G_OBJECT(app_state->window), "delete-event",  +			 G_CALLBACK(on_window_closed), app_state); + +	vbox = gtk_vbox_new(FALSE, 6); +	gtk_container_add(GTK_CONTAINER(app_state->window), vbox); + +	// set top label +	std::ostringstream label_ostr; +	label_ostr << "<big><b>" +		   << LLTrans::getString("UpdaterNowUpdating") +		   << "</b></big>"; + +	summary_label = gtk_label_new(NULL); +	gtk_label_set_use_markup(GTK_LABEL(summary_label), TRUE); +	gtk_label_set_markup(GTK_LABEL(summary_label),  +			     label_ostr.str().c_str()); +	gtk_misc_set_alignment(GTK_MISC(summary_label), 0, 0.5); +	gtk_box_pack_start(GTK_BOX(vbox), summary_label, FALSE, FALSE, 0); + +	// create the description label +	description_label = gtk_label_new(LLTrans::getString("UpdaterUpdatingDescriptive").c_str()); +	gtk_label_set_line_wrap(GTK_LABEL(description_label), TRUE); +	gtk_misc_set_alignment(GTK_MISC(description_label), 0, 0.5); +	gtk_box_pack_start(GTK_BOX(vbox), description_label, FALSE, FALSE, 0); + +	// If an image path has been set, load the background images +	if (!app_state->image_dir.empty()) { +		frame = gtk_frame_new(NULL); +		gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN); +		gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); + +		// load the first image +		app_state->image = gtk_image_new_from_file +			(next_image_filename(app_state->image_dir).c_str()); +		gtk_widget_set_size_request(app_state->image, 340, 310); +		gtk_container_add(GTK_CONTAINER(frame), app_state->image); + +		// rotate the images every 5 seconds +		app_state->image_rotation_timeout_id = g_timeout_add +			(ROTATE_IMAGE_TIMEOUT, rotate_image_cb, app_state); +	} + +	// set up progress bar, and update it roughly every 1/10 of a second +	app_state->progress_bar = gtk_progress_bar_new(); +	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app_state->progress_bar),  +				  LLTrans::getString("UpdaterProgressBarTextWithEllipses").c_str()); +	gtk_box_pack_start(GTK_BOX(vbox),  +			   app_state->progress_bar, FALSE, TRUE, 0); +	app_state->progress_update_timeout_id = g_timeout_add +		(UPDATE_PROGRESS_TIMEOUT, progress_update_timeout, app_state); +	app_state->update_progress_text_timeout_id = g_timeout_add +		(UPDATE_PROGRESS_TEXT_TIMEOUT, update_progress_text_timeout, app_state); + +	gtk_widget_show_all(app_state->window); +} + +gboolean rotate_image_cb(gpointer data) +{ +	UpdaterAppState *app_state; +	std::string filename; + +	llassert(data != NULL); +	app_state = (UpdaterAppState *) data; + +	filename = next_image_filename(app_state->image_dir); + +	gdk_threads_enter(); +	gtk_image_set_from_file(GTK_IMAGE(app_state->image), filename.c_str()); +	gdk_threads_leave(); + +	return TRUE; +} + +std::string next_image_filename(std::string& image_path) +{ +	std::string image_filename; +	gDirUtilp->getNextFileInDir(image_path, "/*.jpg", image_filename, true); +	return image_path + "/" + image_filename; +} + +void on_window_closed(GtkWidget *sender, gpointer data) +{ +	UpdaterAppState *app_state; + +	llassert(data != NULL); +	app_state = (UpdaterAppState *) data; + +	updater_app_quit(app_state); +} + +void updater_app_quit(UpdaterAppState *app_state) +{ +	if (app_state != NULL) +	{ +		g_source_remove(app_state->progress_update_timeout_id); + +		if (!app_state->image_dir.empty()) +		{ +			g_source_remove(app_state->image_rotation_timeout_id); +		} +	} + +	gtk_main_quit(); +} + +void display_error(GtkWidget *parent, std::string title, std::string message) +{ +	GtkWidget *dialog; + +	dialog = gtk_message_dialog_new(GTK_WINDOW(parent), +					GTK_DIALOG_DESTROY_WITH_PARENT, +					GTK_MESSAGE_ERROR, +					GTK_BUTTONS_OK, +					message.c_str()); +	gtk_window_set_title(GTK_WINDOW(dialog), title.c_str()); +	gtk_dialog_run(GTK_DIALOG(dialog)); +	gtk_widget_destroy(dialog); +} + +gpointer worker_thread_cb(gpointer data) +{ +	UpdaterAppState *app_state; +	CURL *curl; +	CURLcode result; +	FILE *package_file; +	GError *error = NULL; +	char *tmp_filename = NULL; +	int fd; + +	//g_return_val_if_fail (data != NULL, NULL); +	app_state = (UpdaterAppState *) data; + +	try { +		// create temporary file to store the package. +		fd = g_file_open_tmp +			("secondlife-update-XXXXXX", &tmp_filename, &error); +		if (error != NULL) +		{ +			llerrs << "Unable to create temporary file: " +			       << error->message +			       << llendl; + +			g_error_free(error); +			throw 0; +		} + +		package_file = fdopen(fd, "wb"); +		if (package_file == NULL) +		{ +			llerrs << "Failed to create temporary file: " +			       << tmp_filename +			       << llendl; + +			gdk_threads_enter(); +			display_error(app_state->window, +				      LLTrans::getString("UpdaterFailDownloadTitle"), +				      LLTrans::getString("UpdaterFailUpdateDescriptive")); +			gdk_threads_leave(); +			throw 0; +		} + +		// initialize curl and start downloading the package +		llinfos << "Downloading package: " << app_state->url << llendl; + +		curl = curl_easy_init(); +		if (curl == NULL) +		{ +			llerrs << "Failed to initialize libcurl" << llendl; + +			gdk_threads_enter(); +			display_error(app_state->window, +				      LLTrans::getString("UpdaterFailDownloadTitle"), +				      LLTrans::getString("UpdaterFailUpdateDescriptive")); +			gdk_threads_leave(); +			throw 0; +		} + +		curl_easy_setopt(curl, CURLOPT_URL, app_state->url.c_str()); +		curl_easy_setopt(curl, CURLOPT_NOSIGNAL, TRUE); +		curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, TRUE); +		curl_easy_setopt(curl, CURLOPT_WRITEDATA, package_file); +		curl_easy_setopt(curl, CURLOPT_NOPROGRESS, FALSE); +		curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION,  +				 &download_progress_cb); +		curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, app_state); + +		result = curl_easy_perform(curl); +		fclose(package_file); +		curl_easy_cleanup(curl); + +		if (result) +		{ +			llerrs << "Failed to download update: "  +			       << app_state->url  +			       << llendl; + +			gdk_threads_enter(); +			display_error(app_state->window, +				      LLTrans::getString("UpdaterFailDownloadTitle"), +				      LLTrans::getString("UpdaterFailUpdateDescriptive")); +			gdk_threads_leave(); + +			throw 0; +		} + +		// now pulse the progres bar back and forth while the package is +		// being unpacked +		gdk_threads_enter(); +		std::string installing_msg = LLTrans::getString("UpdaterNowInstalling"); +		gtk_progress_bar_set_text( +			GTK_PROGRESS_BAR(app_state->progress_bar), +			installing_msg.c_str()); +		app_state->activity_mode = TRUE; +		gdk_threads_leave(); + +		// *TODO: if the destination is not writable, terminate this +		// thread and show file chooser? +		if (!install_package(tmp_filename, app_state->dest_dir)) +		{ +			llwarns << "Failed to install package to destination: " +				<< app_state->dest_dir +				<< llendl; + +			gdk_threads_enter(); +			display_error(app_state->window, +				      LLTrans::getString("UpdaterFailInstallTitle"), +				      LLTrans::getString("UpdaterFailUpdateDescriptive")); +			//"Failed to update " + app_state->app_name, +			gdk_threads_leave(); +			throw 0; +		} + +		// try to spawn the new viewer +		if (!spawn_viewer(app_state)) +		{ +			llwarns << "Viewer was not installed properly in : " +				<< app_state->dest_dir +				<< llendl; + +			gdk_threads_enter(); +			display_error(app_state->window, +				      LLTrans::getString("UpdaterFailStartTitle"), +				      LLTrans::getString("UpdaterFailUpdateDescriptive")); +			gdk_threads_leave(); +			throw 0; +		} +	} +	catch (...) +	{ +		app_state->failure = TRUE; +	} + +	// FIXME: delete package file also if delete-event is raised on window +	if (tmp_filename != NULL) +	{ +		if (gDirUtilp->fileExists(tmp_filename)) +		{ +			LLFile::remove(tmp_filename); +		} +	} + +	gdk_threads_enter(); +	updater_app_quit(app_state); +	gdk_threads_leave(); + +	return NULL; +} + + +gboolean less_anal_gspawnsync(gchar **argv, +			      gchar **stderr_output, +			      gint *child_exit_status, +			      GError **spawn_error) +{ +	// store current SIGCHLD handler if there is one, replace with default +	// handler to make glib happy +	struct sigaction sigchld_backup; +	struct sigaction sigchld_appease_glib; +	sigchld_appease_glib.sa_handler = SIG_DFL; +	sigemptyset(&sigchld_appease_glib.sa_mask); +	sigchld_appease_glib.sa_flags = 0; +	sigaction(SIGCHLD, &sigchld_appease_glib, &sigchld_backup); + +	gboolean rtn = g_spawn_sync(NULL, +				    argv, +				    NULL, +				    (GSpawnFlags) (G_SPAWN_STDOUT_TO_DEV_NULL), +				    NULL, +				    NULL, +				    NULL, +				    stderr_output, +				    child_exit_status, +				    spawn_error); + +	// restore SIGCHLD handler +	sigaction(SIGCHLD, &sigchld_backup, NULL); +	 +	return rtn; +} + + +// perform a rename, or perform a (prompted) root rename if that fails +int +rename_with_sudo_fallback(const std::string& filename, const std::string& newname) +{ +	int rtncode = ::rename(filename.c_str(), newname.c_str()); +	lldebugs << "rename result is: " << rtncode << " / " << errno << llendl; +	if (rtncode && (EACCES == errno || EPERM == errno || EXDEV == errno)) +	{ +		llinfos << "Permission problem in rename, or moving between different mount points.  Retrying as a mv under a sudo." << llendl; +		// failed due to permissions, try again as a gksudo or kdesu mv wrapper hack +		char *sudo_cmd = NULL; +		sudo_cmd = g_find_program_in_path("gksudo"); +		if (!sudo_cmd) +		{ +			sudo_cmd = g_find_program_in_path("kdesu"); +		} +		if (sudo_cmd) +		{ +			char *mv_cmd = NULL; +			mv_cmd = g_find_program_in_path("mv"); +			if (mv_cmd) +			{ +				char *src_string_copy = g_strdup(filename.c_str()); +				char *dst_string_copy = g_strdup(newname.c_str()); +				char* argv[] =  +					{ +						sudo_cmd, +						mv_cmd, +						src_string_copy, +						dst_string_copy, +						NULL +					}; + +				gchar *stderr_output = NULL; +				gint child_exit_status = 0; +				GError *spawn_error = NULL; +				if (!less_anal_gspawnsync(argv, &stderr_output, +							  &child_exit_status, &spawn_error)) +				{ +					llwarns << "Failed to spawn child process: "  +						<< spawn_error->message  +						<< llendl; +				} +				else if (child_exit_status) +				{ +					llwarns << "mv command failed: " +						<< (stderr_output ? stderr_output : "(no reason given)") +						<< llendl; +				} +				else +				{ +					// everything looks good, clear the error code +					rtncode = 0; +				}				 + +				g_free(src_string_copy); +				g_free(dst_string_copy); +				if (spawn_error) g_error_free(spawn_error); +			} +		} +	} +	return rtncode; +} + +gboolean install_package(std::string package_file, std::string destination) +{ +	char *tar_cmd = NULL; +	std::ostringstream command; + +	// Find the absolute path to the 'tar' command. +	tar_cmd = g_find_program_in_path("tar"); +	if (!tar_cmd) +	{ +		llerrs << "`tar' was not found in $PATH" << llendl; +		return FALSE; +	} +	llinfos << "Found tar command: " << tar_cmd << llendl; + +	// Unpack the tarball in a temporary place first, then move it to  +	// its final destination +	std::string tmp_dest_dir = gDirUtilp->getTempFilename(); +	if (LLFile::mkdir(tmp_dest_dir, 0744)) +	{ +		llerrs << "Failed to create directory: " +		       << destination +		       << llendl; + +		return FALSE; +	} + +	char *package_file_string_copy = g_strdup(package_file.c_str()); +	char *tmp_dest_dir_string_copy = g_strdup(tmp_dest_dir.c_str()); +	char *argv[8] = { +		tar_cmd, +		"--strip", "1", +		"-xjf", +		package_file_string_copy, +		"-C", tmp_dest_dir_string_copy, +		NULL, +	}; + +	llinfos << "Untarring package: " << package_file << llendl; + +	// store current SIGCHLD handler if there is one, replace with default +	// handler to make glib happy +	struct sigaction sigchld_backup; +	struct sigaction sigchld_appease_glib; +	sigchld_appease_glib.sa_handler = SIG_DFL; +	sigemptyset(&sigchld_appease_glib.sa_mask); +	sigchld_appease_glib.sa_flags = 0; +	sigaction(SIGCHLD, &sigchld_appease_glib, &sigchld_backup); + +	gchar *stderr_output = NULL; +	gint child_exit_status = 0; +	GError *untar_error = NULL; +	if (!less_anal_gspawnsync(argv, &stderr_output, +				  &child_exit_status, &untar_error)) +	{ +		llwarns << "Failed to spawn child process: "  +			<< untar_error->message  +			<< llendl; +		return FALSE; +	} + +	if (child_exit_status) +	{ +	 	llwarns << "Untar command failed: " +			<< (stderr_output ? stderr_output : "(no reason given)") +			<< llendl; +		return FALSE; +	} + +	g_free(tar_cmd); +	g_free(package_file_string_copy); +	g_free(tmp_dest_dir_string_copy); +	g_free(stderr_output); +	if (untar_error) g_error_free(untar_error); + +	// move the existing package out of the way if it exists +	if (gDirUtilp->fileExists(destination)) +	{ +		std::string backup_dir = destination + ".backup"; +		int oldcounter = 1; +		while (gDirUtilp->fileExists(backup_dir)) +		{ +			// find a foo.backup.N folder name that isn't taken yet +			backup_dir = destination + ".backup." + llformat("%d", oldcounter); +			++oldcounter; +		} + +		if (rename_with_sudo_fallback(destination, backup_dir)) +		{ +			llwarns << "Failed to move directory: '"  +				<< destination << "' -> '" << backup_dir  +				<< llendl; +			return FALSE; +		} +	} + +	// The package has been unpacked in a staging directory, now we just +	// need to move it to its destination. +	if (rename_with_sudo_fallback(tmp_dest_dir, destination)) +	{ +		llwarns << "Failed to move installation to the destination: " +			<< destination  +			<< llendl; +		return FALSE; +	} + +	// \0/ Success! +	return TRUE; +} + +gboolean progress_update_timeout(gpointer data) +{ +	UpdaterAppState *app_state; + +	llassert(data != NULL); + +	app_state = (UpdaterAppState *) data; + +	gdk_threads_enter(); +	if (app_state->activity_mode) +	{ +		gtk_progress_bar_pulse +			(GTK_PROGRESS_BAR(app_state->progress_bar)); +	} +	else +	{ +		gtk_progress_set_value(GTK_PROGRESS(app_state->progress_bar), +				       app_state->progress_value); +	} +	gdk_threads_leave(); + +	return TRUE; +} + +gboolean update_progress_text_timeout(gpointer data) +{ +	UpdaterAppState *app_state; + +	llassert(data != NULL); +	app_state = (UpdaterAppState *) data; + +	if (app_state->activity_mode == TRUE) +	{ +		// We no longer need this timeout, it will be removed. +		return FALSE; +	} + +	if (!app_state->progress_value) +	{ +		return TRUE; +	} + +	std::string progress_text = llformat((LLTrans::getString("UpdaterProgressBarText")+" (%.0f%%)").c_str(), app_state->progress_value); + +	gdk_threads_enter(); +	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app_state->progress_bar), +				  progress_text.c_str()); +	gdk_threads_leave(); + +	return TRUE; +} + +int download_progress_cb(gpointer data, +			 double t, +			 double d, +			 double utotal, +			 double ulnow) +{ +	UpdaterAppState *app_state; + +	llassert(data != NULL); +	app_state = (UpdaterAppState *) data; + +	if (t <= 0.0) +	{ +		app_state->progress_value = 0; +	} +	else +	{ +		app_state->progress_value = d * 100.0 / t; +	} +	return 0; +} + +BOOL spawn_viewer(UpdaterAppState *app_state) +{ +	llassert(app_state != NULL); + +	std::string cmd = app_state->dest_dir + "/secondlife"; +	GError *error = NULL; + +	// We want to spawn the Viewer on the same display as the updater app +	gboolean success = gdk_spawn_command_line_on_screen +		(gtk_widget_get_screen(app_state->window), cmd.c_str(), &error); + +	if (!success) +	{ +		llwarns << "Failed to launch viewer: " << error->message  +			<< llendl; +	} + +	if (error) g_error_free(error); + +	return success; +} + +void show_usage_and_exit() +{ +	std::cout << "Usage: linux-updater --url URL --name NAME --dest PATH --stringsdir PATH1,PATH2 --stringsfile FILE" +		  << "[--image-dir PATH]" +		  << std::endl; +	exit(1); +} + +void parse_args_and_init(int argc, char **argv, UpdaterAppState *app_state) +{ +	int i; + +	for (i = 1; i < argc; i++) +	{ +		if ((!strcmp(argv[i], "--url")) && (++i < argc)) +		{ +			app_state->url = argv[i]; +		} +		else if ((!strcmp(argv[i], "--name")) && (++i < argc)) +		{ +			app_state->app_name = argv[i]; +		} +		else if ((!strcmp(argv[i], "--image-dir")) && (++i < argc)) +		{ +			app_state->image_dir = argv[i]; +		} +		else if ((!strcmp(argv[i], "--dest")) && (++i < argc)) +		{ +			app_state->dest_dir = argv[i]; +		} +		else if ((!strcmp(argv[i], "--stringsdir")) && (++i < argc)) +		{ +			app_state->strings_dirs = argv[i]; +		} +		else if ((!strcmp(argv[i], "--stringsfile")) && (++i < argc)) +		{ +			app_state->strings_file = argv[i]; +		} +		else +		{ +			// show usage, an invalid option was given. +			show_usage_and_exit(); +		} +	} + +	if (app_state->app_name.empty()  +	    || app_state->url.empty()  +	    || app_state->dest_dir.empty()) +	{ +		show_usage_and_exit(); +	} + +	app_state->progress_value = 0.0; +	app_state->activity_mode = FALSE; +	app_state->failure = FALSE; + +	translate_init(app_state->strings_dirs, app_state->strings_file); +} + +int main(int argc, char **argv) +{ +	UpdaterAppState app_state; +	GThread *worker_thread; + +	parse_args_and_init(argc, argv, &app_state); + +	// Initialize logger, and rename old log file +	gDirUtilp->initAppDirs("SecondLife"); +	LLError::initForApplication +		(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "")); +	std::string old_log_file = gDirUtilp->getExpandedFilename +		(LL_PATH_LOGS, "updater.log.old"); +	std::string log_file =  +		gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "updater.log"); +	LLFile::rename(log_file, old_log_file); +	LLError::logToFile(log_file); + +	// initialize gthreads and gtk+ +	if (!g_thread_supported()) +	{ +		g_thread_init(NULL); +		gdk_threads_init(); +	} + +	gtk_init(&argc, &argv); + +	// create UI +	updater_app_ui_init(&app_state); + +	//llinfos << "SAMPLE TRANSLATION IS: " << LLTrans::getString("LoginInProgress") << llendl; + +	// create download thread +	worker_thread = g_thread_create +		(GThreadFunc(worker_thread_cb), &app_state, FALSE, NULL); + +	gdk_threads_enter(); +	gtk_main(); +	gdk_threads_leave(); + +	return (app_state.failure == FALSE) ? 0 : 1; +} diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index e8d95b44a5..7f71ff6a53 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -12,6 +12,7 @@ include(LLRender)  include(LLWindow)  include(LLVFS)  include(LLXML) +include(LLXUIXML)  include_directories(      ${LLAUDIO_INCLUDE_DIRS} @@ -23,6 +24,7 @@ include_directories(      ${LLWINDOW_INCLUDE_DIRS}      ${LLVFS_INCLUDE_DIRS}      ${LLXML_INCLUDE_DIRS} +    ${LLXUIXML_INCLUDE_DIRS}      )  set(llui_SOURCE_FILES @@ -44,7 +46,6 @@ set(llui_SOURCE_FILES      llfocusmgr.cpp      llfunctorregistry.cpp      lliconctrl.cpp -    llinitparam.cpp      llkeywords.cpp      lllayoutstack.cpp      lllineeditor.cpp @@ -82,9 +83,8 @@ set(llui_SOURCE_FILES      lltextbox.cpp      lltexteditor.cpp      lltextparser.cpp -    lltrans.cpp +    lltransutil.cpp      llui.cpp -    lluicolor.cpp      lluicolortable.cpp      lluictrl.cpp      lluictrlfactory.cpp @@ -121,7 +121,6 @@ set(llui_HEADER_FILES      llhandle.h      llhtmlhelp.h      lliconctrl.h -    llinitparam.h      llkeywords.h      lllayoutstack.h      lllazyvalue.h @@ -136,7 +135,6 @@ set(llui_HEADER_FILES      llpanel.h      llprogressbar.h      llradiogroup.h -    llregistry.h      llresizebar.h      llresizehandle.h      llresmgr.h @@ -161,8 +159,7 @@ set(llui_HEADER_FILES      lltextbox.h      lltexteditor.h      lltextparser.h -    lltrans.h -    lluicolor.h +    lltransutil.h      lluicolortable.h      lluiconstants.h      lluictrlfactory.h @@ -191,6 +188,7 @@ target_link_libraries(llui      llwindow      llimage      llvfs       # ugh, just for LLDir +    llxuixml      llxml      llcommon    # must be after llimage, llwindow, llrender      llmath diff --git a/indra/llui/lltransutil.cpp b/indra/llui/lltransutil.cpp new file mode 100644 index 0000000000..eaee260c7a --- /dev/null +++ b/indra/llui/lltransutil.cpp @@ -0,0 +1,67 @@ +/** + * @file lltrans.cpp + * @brief LLTrans implementation + * + * $LicenseInfo:firstyear=2000&license=viewergpl$ + *  + * Copyright (c) 2000-2009, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "lltrans.h" +#include "lluictrlfactory.h" + +#include "lltransutil.h" + + +bool LLTransUtil::parseStrings(const std::string& xml_filename, const std::set<std::string>& default_args) +{ +	LLXMLNodePtr root; +	BOOL success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root); +	if (!success) +	{ +		llerrs << "Couldn't load string table" << llendl; +		return false; +	} + +	return LLTrans::parseStrings(root, default_args); +} + + +bool LLTransUtil::parseLanguageStrings(const std::string& xml_filename) +{ +	LLXMLNodePtr root; +	BOOL success  = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root); +	 +	if (!success) +	{ +		llerrs << "Couldn't load string table " << xml_filename << llendl; +		return false; +	} +	 +	return LLTrans::parseLanguageStrings(root); +} diff --git a/indra/llui/lltransutil.h b/indra/llui/lltransutil.h new file mode 100644 index 0000000000..2ddfd81361 --- /dev/null +++ b/indra/llui/lltransutil.h @@ -0,0 +1,51 @@ +/** + * @file lltransutil.h + * @brief LLTrans helper + * + * $LicenseInfo:firstyear=2000&license=viewergpl$ + *  + * Copyright (c) 2000-2009, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_TRANSUTIL_H +#define LL_TRANSUTIL_H + +#include "lltrans.h" + +namespace LLTransUtil +{ +	/** +	 * @brief Parses the xml file that holds the strings. Used once on startup +	 * @param xml_filename Filename to parse +	 * @param default_args Set of strings (expected to be in the file) to use as default replacement args, e.g. "SECOND_LIFE" +	 * @returns true if the file was parsed successfully, true if something went wrong +	 */ +	bool parseStrings(const std::string& xml_filename, const std::set<std::string>& default_args); + +	bool parseLanguageStrings(const std::string& xml_filename); +}; + +#endif diff --git a/indra/llui/lluictrlfactory.cpp b/indra/llui/lluictrlfactory.cpp index a4c9728402..2bbede8c13 100644 --- a/indra/llui/lluictrlfactory.cpp +++ b/indra/llui/lluictrlfactory.cpp @@ -71,14 +71,6 @@  #include "llui.h"  #include "llviewborder.h" -const char XML_HEADER[] = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n"; - -const S32 HPAD = 4; -const S32 VPAD = 4; -const S32 FLOATER_H_MARGIN = 15; -const S32 MIN_WIDGET_HEIGHT = 10; -const S32 MAX_STRING_ATTRIBUTE_SIZE = 40; -  LLFastTimer::DeclareTimer FTM_WIDGET_CONSTRUCTION("Widget Construction");  LLFastTimer::DeclareTimer FTM_INIT_FROM_PARAMS("Widget InitFromParams");  LLFastTimer::DeclareTimer FTM_WIDGET_SETUP("Widget Setup"); @@ -436,929 +428,3 @@ void LLUICtrlFactory::popFactoryFunctions()  		mFactoryStack.pop_back();  	}  } - -// -// LLXSDWriter -// -LLXSDWriter::LLXSDWriter() -{ -	registerInspectFunc<bool>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:boolean", _1, _2, _3, _4)); -	registerInspectFunc<std::string>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); -	registerInspectFunc<U8>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedByte", _1, _2, _3, _4)); -	registerInspectFunc<S8>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:signedByte", _1, _2, _3, _4)); -	registerInspectFunc<U16>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedShort", _1, _2, _3, _4)); -	registerInspectFunc<S16>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:signedShort", _1, _2, _3, _4)); -	registerInspectFunc<U32>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedInt", _1, _2, _3, _4)); -	registerInspectFunc<S32>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:integer", _1, _2, _3, _4)); -	registerInspectFunc<F32>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:float", _1, _2, _3, _4)); -	registerInspectFunc<F64>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:double", _1, _2, _3, _4)); -	registerInspectFunc<LLColor4>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); -	registerInspectFunc<LLUIColor>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); -	registerInspectFunc<LLUUID>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); -	registerInspectFunc<LLSD>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); -} - -void LLXSDWriter::writeXSD(const std::string& type_name, LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const std::string& xml_namespace) -{ -	mSchemaNode = node; -	node->setName("xs:schema"); -	node->createChild("attributeFormDefault", true)->setStringValue("unqualified"); -	node->createChild("elementFormDefault", true)->setStringValue("qualified"); -	node->createChild("targetNamespace", true)->setStringValue(xml_namespace); -	node->createChild("xmlns:xs", true)->setStringValue("http://www.w3.org/2001/XMLSchema"); -	node->createChild("xmlns", true)->setStringValue(xml_namespace); - -	node = node->createChild("xs:complexType", false); -	node->createChild("name", true)->setStringValue(type_name); -	node->createChild("mixed", true)->setStringValue("true"); - -	mAttributeNode = node; -	mElementNode = node->createChild("xs:choice", false); -	mElementNode->createChild("minOccurs", true)->setStringValue("0"); -	mElementNode->createChild("maxOccurs", true)->setStringValue("unbounded"); -	block.inspectBlock(*this); - -	// duplicate element choices -	LLXMLNodeList children; -	mElementNode->getChildren("xs:element", children, FALSE); -	for (LLXMLNodeList::iterator child_it = children.begin(); child_it != children.end(); ++child_it) -	{ -		LLXMLNodePtr child_copy = child_it->second->deepCopy(); -		std::string child_name; -		child_copy->getAttributeString("name", child_name); -		child_copy->setAttributeString("name", type_name + "." + child_name); -		mElementNode->addChild(child_copy); -	} - -	LLXMLNodePtr element_declaration_node = mSchemaNode->createChild("xs:element", false); -	element_declaration_node->createChild("name", true)->setStringValue(type_name); -	element_declaration_node->createChild("type", true)->setStringValue(type_name); -} - -void LLXSDWriter::writeAttribute(const std::string& type, const Parser::name_stack_t& stack, S32 min_count, S32 max_count, const std::vector<std::string>* possible_values) -{ -	name_stack_t non_empty_names; -	std::string attribute_name; -	for (name_stack_t::const_iterator it = stack.begin(); -		it != stack.end(); -		++it) -	{ -		const std::string& name = it->first; -		if (!name.empty()) -		{ -			non_empty_names.push_back(*it); -		} -	} - -	for (name_stack_t::const_iterator it = non_empty_names.begin(); -		it != non_empty_names.end(); -		++it) -	{ -		if (!attribute_name.empty()) -		{ -			attribute_name += "."; -		} -		attribute_name += it->first; -	} - -	// only flag non-nested attributes as mandatory, nested attributes have variant syntax -	// that can't be properly constrained in XSD -	// e.g. <foo mandatory.value="bar"/> vs <foo><mandatory value="bar"/></foo> -	bool attribute_mandatory = min_count == 1 && max_count == 1 && non_empty_names.size() == 1; - -	// don't bother supporting "Multiple" params as xml attributes -	if (max_count <= 1) -	{ -		// add compound attribute to root node -		addAttributeToSchema(mAttributeNode, attribute_name, type, attribute_mandatory, possible_values); -	} - -	// now generated nested elements for compound attributes -	if (non_empty_names.size() > 1 && !attribute_mandatory) -	{ -		std::string element_name; - -		// traverse all but last element, leaving that as an attribute name -		name_stack_t::const_iterator end_it = non_empty_names.end(); -		end_it--; - -		for (name_stack_t::const_iterator it = non_empty_names.begin(); -			it != end_it; -			++it) -		{ -			if (it != non_empty_names.begin()) -			{ -				element_name += "."; -			} -			element_name += it->first; -		} - -		std::string short_attribute_name = non_empty_names.back().first; - -		LLXMLNodePtr complex_type_node; - -		// find existing element node here, starting at tail of child list -		if (mElementNode->mChildren.notNull()) -		{ -			for(LLXMLNodePtr element = mElementNode->mChildren->tail; -				element.notNull();  -				element = element->mPrev) -			{ -				std::string name; -				if(element->getAttributeString("name", name) && name == element_name) -				{ -					complex_type_node = element->mChildren->head; -					break; -				} -			} -		} -		//create complex_type node -		// -		//<xs:element -        //    maxOccurs="1" -        //    minOccurs="0" -        //    name="name"> -        //       <xs:complexType> -        //       </xs:complexType> -        //</xs:element> -		if(complex_type_node.isNull()) -		{ -			complex_type_node = mElementNode->createChild("xs:element", false); - -			complex_type_node->createChild("minOccurs", true)->setIntValue(min_count); -			complex_type_node->createChild("maxOccurs", true)->setIntValue(max_count); -			complex_type_node->createChild("name",		true)->setStringValue(element_name); -			complex_type_node = complex_type_node->createChild("xs:complexType", false); -		} - -		addAttributeToSchema(complex_type_node, short_attribute_name, type, false, possible_values); -	} -} - -void LLXSDWriter::addAttributeToSchema(LLXMLNodePtr type_declaration_node, const std::string& attribute_name, const std::string& type, bool mandatory, const std::vector<std::string>* possible_values) -{ -	if (!attribute_name.empty()) -	{ -		LLXMLNodePtr new_enum_type_node; -		if (possible_values != NULL) -		{ -			// custom attribute type, for example -			//<xs:simpleType> -			 // <xs:restriction -			 //    base="xs:string"> -			 //     <xs:enumeration -			 //      value="a" /> -			 //     <xs:enumeration -			 //      value="b" /> -			 //   </xs:restriction> -			 // </xs:simpleType> -			new_enum_type_node = new LLXMLNode("xs:simpleType", false); - -			LLXMLNodePtr restriction_node = new_enum_type_node->createChild("xs:restriction", false); -			restriction_node->createChild("base", true)->setStringValue("xs:string"); - -			for (std::vector<std::string>::const_iterator it = possible_values->begin(); -				it != possible_values->end(); -				++it) -			{ -				LLXMLNodePtr enum_node = restriction_node->createChild("xs:enumeration", false); -				enum_node->createChild("value", true)->setStringValue(*it); -			} -		} - -		string_set_t& attributes_written = mAttributesWritten[type_declaration_node]; - -		string_set_t::iterator found_it = attributes_written.lower_bound(attribute_name); - -		// attribute not yet declared -		if (found_it == attributes_written.end() || attributes_written.key_comp()(attribute_name, *found_it)) -		{ -			attributes_written.insert(found_it, attribute_name); - -			LLXMLNodePtr attribute_node = type_declaration_node->createChild("xs:attribute", false); - -			// attribute name -			attribute_node->createChild("name", true)->setStringValue(attribute_name); - -			if (new_enum_type_node.notNull()) -			{ -				attribute_node->addChild(new_enum_type_node); -			} -			else -			{ -				// simple attribute type -				attribute_node->createChild("type", true)->setStringValue(type); -			} - -			// required or optional -			attribute_node->createChild("use", true)->setStringValue(mandatory ? "required" : "optional"); -		} -		 // attribute exists...handle collision of same name attributes with potentially different types -		else -		{ -			LLXMLNodePtr attribute_declaration; -			if (type_declaration_node.notNull()) -			{ -				for(LLXMLNodePtr node = type_declaration_node->mChildren->tail;  -					node.notNull();  -					node = node->mPrev) -				{ -					std::string name; -					if (node->getAttributeString("name", name) && name == attribute_name) -					{ -						attribute_declaration = node; -						break; -					} -				} -			} - -			bool new_type_is_enum = new_enum_type_node.notNull(); -			bool existing_type_is_enum = !attribute_declaration->hasAttribute("type"); - -			// either type is enum, revert to string in collision -			// don't bother to check for enum equivalence -			if (new_type_is_enum || existing_type_is_enum) -			{ -				if (attribute_declaration->hasAttribute("type")) -				{ -					attribute_declaration->setAttributeString("type", "xs:string"); -				} -				else -				{ -					attribute_declaration->createChild("type", true)->setStringValue("xs:string"); -				} -				attribute_declaration->deleteChildren("xs:simpleType"); -			} -			else  -			{ -				// check for collision of different standard types -				std::string existing_type; -				attribute_declaration->getAttributeString("type", existing_type); -				// if current type is not the same as the new type, revert to strnig -				if (existing_type != type) -				{ -					// ...than use most general type, string -					attribute_declaration->setAttributeString("type", "string"); -				} -			} -		} -	} -} - -// -// LLXUIXSDWriter -// -void LLXUIXSDWriter::writeXSD(const std::string& type_name, const std::string& path, const LLInitParam::BaseBlock& block) -{ -	std::string file_name(path); -	file_name += type_name + ".xsd"; -	LLXMLNodePtr root_nodep = new LLXMLNode(); - -	LLXSDWriter::writeXSD(type_name, root_nodep, block, "http://www.lindenlab.com/xui"); - -	// add includes for all possible children -	const std::type_info* type = *LLWidgetTypeRegistry::instance().getValue(type_name); -	const widget_registry_t* widget_registryp = LLChildRegistryRegistry::instance().getValue(type); -	 -	// add include declarations for all valid children -	for (widget_registry_t::Registrar::registry_map_t::const_iterator it = widget_registryp->currentRegistrar().beginItems(); -		it != widget_registryp->currentRegistrar().endItems(); -		++it) -	{ -		std::string widget_name = it->first; -		if (widget_name == type_name) -		{ -			continue; -		} -		LLXMLNodePtr nodep = new LLXMLNode("xs:include", false); -		nodep->createChild("schemaLocation", true)->setStringValue(widget_name + ".xsd"); - -		// add to front of schema -		mSchemaNode->addChild(nodep, mSchemaNode); -	} - -	// add choices for valid children -	if (widget_registryp) -	{ -		for (widget_registry_t::Registrar::registry_map_t::const_iterator it = widget_registryp->currentRegistrar().beginItems(); -			it != widget_registryp->currentRegistrar().endItems(); -			++it) -		{ -			std::string widget_name = it->first; -            //<xs:element name="widget_name" type="widget_name"> -			LLXMLNodePtr widget_node = mElementNode->createChild("xs:element", false); -			widget_node->createChild("name", true)->setStringValue(widget_name); -			widget_node->createChild("type", true)->setStringValue(widget_name); -		} -	} - -	LLFILE* xsd_file = LLFile::fopen(file_name.c_str(), "w"); -	LLXMLNode::writeHeaderToFile(xsd_file); -	root_nodep->writeToFile(xsd_file); -	fclose(xsd_file); -} - -// -// LLXUIParser -// -LLXUIParser::LLXUIParser() -:	mLastWriteGeneration(-1), -	mCurReadDepth(0) -{ -	registerParserFuncs<bool>(boost::bind(&LLXUIParser::readBoolValue, this, _1), -								boost::bind(&LLXUIParser::writeBoolValue, this, _1, _2)); -	registerParserFuncs<std::string>(boost::bind(&LLXUIParser::readStringValue, this, _1), -								boost::bind(&LLXUIParser::writeStringValue, this, _1, _2)); -	registerParserFuncs<U8>(boost::bind(&LLXUIParser::readU8Value, this, _1), -								boost::bind(&LLXUIParser::writeU8Value, this, _1, _2)); -	registerParserFuncs<S8>(boost::bind(&LLXUIParser::readS8Value, this, _1), -								boost::bind(&LLXUIParser::writeS8Value, this, _1, _2)); -	registerParserFuncs<U16>(boost::bind(&LLXUIParser::readU16Value, this, _1), -								boost::bind(&LLXUIParser::writeU16Value, this, _1, _2)); -	registerParserFuncs<S16>(boost::bind(&LLXUIParser::readS16Value, this, _1), -								boost::bind(&LLXUIParser::writeS16Value, this, _1, _2)); -	registerParserFuncs<U32>(boost::bind(&LLXUIParser::readU32Value, this, _1), -								boost::bind(&LLXUIParser::writeU32Value, this, _1, _2)); -	registerParserFuncs<S32>(boost::bind(&LLXUIParser::readS32Value, this, _1), -								boost::bind(&LLXUIParser::writeS32Value, this, _1, _2)); -	registerParserFuncs<F32>(boost::bind(&LLXUIParser::readF32Value, this, _1), -								boost::bind(&LLXUIParser::writeF32Value, this, _1, _2)); -	registerParserFuncs<F64>(boost::bind(&LLXUIParser::readF64Value, this, _1), -								boost::bind(&LLXUIParser::writeF64Value, this, _1, _2)); -	registerParserFuncs<LLColor4>(boost::bind(&LLXUIParser::readColor4Value, this, _1), -								boost::bind(&LLXUIParser::writeColor4Value, this, _1, _2)); -	registerParserFuncs<LLUIColor>(boost::bind(&LLXUIParser::readUIColorValue, this, _1), -								boost::bind(&LLXUIParser::writeUIColorValue, this, _1, _2)); -	registerParserFuncs<LLUUID>(boost::bind(&LLXUIParser::readUUIDValue, this, _1), -								boost::bind(&LLXUIParser::writeUUIDValue, this, _1, _2)); -	registerParserFuncs<LLSD>(boost::bind(&LLXUIParser::readSDValue, this, _1), -								boost::bind(&LLXUIParser::writeSDValue, this, _1, _2)); -} - -static LLFastTimer::DeclareTimer PARSE_XUI("XUI Parsing"); - -void LLXUIParser::readXUI(LLXMLNodePtr node, LLInitParam::BaseBlock& block, bool silent) -{ -	LLFastTimer timer(PARSE_XUI); -	mNameStack.clear(); -	mCurReadDepth = 0; -	setParseSilently(silent); - -	if (node.isNull()) -	{ -		parserWarning("Invalid node"); -	} -	else -	{ -		readXUIImpl(node, std::string(node->getName()->mString), block); -	} -} - -bool LLXUIParser::readXUIImpl(LLXMLNodePtr nodep, const std::string& scope, LLInitParam::BaseBlock& block) -{ -	typedef boost::tokenizer<boost::char_separator<char> > tokenizer; -	boost::char_separator<char> sep("."); - -	bool values_parsed = false; - -	// submit attributes for current node -	values_parsed |= readAttributes(nodep, block); - -	// treat text contents of xml node as "value" parameter -	std::string text_contents = nodep->getSanitizedValue(); -	if (!text_contents.empty()) -	{ -		mCurReadNode = nodep; -		mNameStack.push_back(std::make_pair(std::string("value"), newParseGeneration())); -		// child nodes are not necessarily valid parameters (could be a child widget) -		// so don't complain once we've recursed -		bool silent = mCurReadDepth > 0; -		if (!block.submitValue(mNameStack, *this, true)) -		{ -			mNameStack.pop_back(); -			block.submitValue(mNameStack, *this, silent); -		} -		else -		{ -			mNameStack.pop_back(); -		} -	} - -	// then traverse children -	// child node must start with last name of parent node (our "scope") -	// for example: "<button><button.param nested_param1="foo"><param.nested_param2 nested_param3="bar"/></button.param></button>" -	// which equates to the following nesting: -	// button -	//     param -	//         nested_param1 -	//         nested_param2 -	//             nested_param3	 -	mCurReadDepth++; -	for(LLXMLNodePtr childp = nodep->getFirstChild(); childp.notNull();) -	{ -		std::string child_name(childp->getName()->mString); -		S32 num_tokens_pushed = 0; - -		// for non "dotted" child nodes	check to see if child node maps to another widget type -		// and if not, treat as a child element of the current node -		// e.g. <button><rect left="10"/></button> will interpret <rect> as "button.rect" -		// since there is no widget named "rect" -		if (child_name.find(".") == std::string::npos)  -		{ -			mNameStack.push_back(std::make_pair(child_name, newParseGeneration())); -			num_tokens_pushed++; -		} -		else -		{ -			// parse out "dotted" name into individual tokens -			tokenizer name_tokens(child_name, sep); - -			tokenizer::iterator name_token_it = name_tokens.begin(); -			if(name_token_it == name_tokens.end())  -			{ -				childp = childp->getNextSibling(); -				continue; -			} - -			// check for proper nesting -			if(!scope.empty() && *name_token_it != scope) -			{ -				childp = childp->getNextSibling(); -				continue; -			} - -			// now ignore first token -			++name_token_it;  - -			// copy remaining tokens on to our running token list -			for(tokenizer::iterator token_to_push = name_token_it; token_to_push != name_tokens.end(); ++token_to_push) -			{ -				mNameStack.push_back(std::make_pair(*token_to_push, newParseGeneration())); -				num_tokens_pushed++; -			} -		} - -		// recurse and visit children XML nodes -		if(readXUIImpl(childp, mNameStack.empty() ? scope : mNameStack.back().first, block)) -		{ -			// child node successfully parsed, remove from DOM - -			values_parsed = true; -			LLXMLNodePtr node_to_remove = childp; -			childp = childp->getNextSibling(); - -			nodep->deleteChild(node_to_remove); -		} -		else -		{ -			childp = childp->getNextSibling(); -		} - -		while(num_tokens_pushed-- > 0) -		{ -			mNameStack.pop_back(); -		} -	} -	mCurReadDepth--; -	return values_parsed; -} - -bool LLXUIParser::readAttributes(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block) -{ -	typedef boost::tokenizer<boost::char_separator<char> > tokenizer; -	boost::char_separator<char> sep("."); - -	bool any_parsed = false; - -	for(LLXMLAttribList::const_iterator attribute_it = nodep->mAttributes.begin();  -		attribute_it != nodep->mAttributes.end();  -		++attribute_it) -	{ -		S32 num_tokens_pushed = 0; -		std::string attribute_name(attribute_it->first->mString); -		mCurReadNode = attribute_it->second; - -		tokenizer name_tokens(attribute_name, sep); -		// copy remaining tokens on to our running token list -		for(tokenizer::iterator token_to_push = name_tokens.begin(); token_to_push != name_tokens.end(); ++token_to_push) -		{ -			mNameStack.push_back(std::make_pair(*token_to_push, newParseGeneration())); -			num_tokens_pushed++; -		} - -		// child nodes are not necessarily valid attributes, so don't complain once we've recursed -		bool silent = mCurReadDepth > 0; -		any_parsed |= block.submitValue(mNameStack, *this, silent); -		 -		while(num_tokens_pushed-- > 0) -		{ -			mNameStack.pop_back(); -		} -	} - -	return any_parsed; -} - -void LLXUIParser::writeXUI(LLXMLNodePtr node, const LLInitParam::BaseBlock &block, const LLInitParam::BaseBlock* diff_block) -{ -	mWriteRootNode = node; -	block.serializeBlock(*this, Parser::name_stack_t(), diff_block); -	mOutNodes.clear(); -} - -// go from a stack of names to a specific XML node -LLXMLNodePtr LLXUIParser::getNode(const name_stack_t& stack) -{ -	name_stack_t name_stack; -	for (name_stack_t::const_iterator it = stack.begin(); -		it != stack.end(); -		++it) -	{ -		if (!it->first.empty()) -		{ -			name_stack.push_back(*it); -		} -	} - -	LLXMLNodePtr out_node = mWriteRootNode; - -	name_stack_t::const_iterator next_it = name_stack.begin(); -	for (name_stack_t::const_iterator it = name_stack.begin(); -		it != name_stack.end(); -		it = next_it) -	{ -		++next_it; -		if (it->first.empty()) -		{ -			continue; -		} - -		out_nodes_t::iterator found_it = mOutNodes.lower_bound(it->second); - -		// node with this name not yet written -		if (found_it == mOutNodes.end() || mOutNodes.key_comp()(found_it->first, it->second)) -		{ -			// make an attribute if we are the last element on the name stack -			bool is_attribute = next_it == name_stack.end(); -			LLXMLNodePtr new_node = new LLXMLNode(it->first.c_str(), is_attribute); -			out_node->addChild(new_node); -			mOutNodes.insert(found_it, std::make_pair(it->second, new_node)); -			out_node = new_node; -		} -		else -		{ -			out_node = found_it->second; -		} -	} - -	return (out_node == mWriteRootNode ? LLXMLNodePtr(NULL) : out_node); -} - - -bool LLXUIParser::readBoolValue(void* val_ptr) -{ -	S32 value; -	bool success = mCurReadNode->getBoolValue(1, &value); -	*((bool*)val_ptr) = (value != FALSE); -	return success; -} - -bool LLXUIParser::writeBoolValue(const void* val_ptr, const name_stack_t& stack) -{ -	LLXMLNodePtr node = getNode(stack); -	if (node.notNull()) -	{ -		node->setBoolValue(*((bool*)val_ptr)); -		return true; -	} -	return false; -} - -bool LLXUIParser::readStringValue(void* val_ptr) -{ -	*((std::string*)val_ptr) = mCurReadNode->getSanitizedValue(); -	return true; -} - -bool LLXUIParser::writeStringValue(const void* val_ptr, const name_stack_t& stack) -{ -	LLXMLNodePtr node = getNode(stack); -	if (node.notNull()) -	{ -		const std::string* string_val = reinterpret_cast<const std::string*>(val_ptr); -		if (string_val->find('\n') != std::string::npos  -			|| string_val->size() > MAX_STRING_ATTRIBUTE_SIZE) -		{ -			// don't write strings with newlines into attributes -			std::string attribute_name = node->getName()->mString; -			LLXMLNodePtr parent_node = node->mParent; -			parent_node->deleteChild(node); -			// write results in text contents of node -			if (attribute_name == "value") -			{ -				// "value" is implicit, just write to parent -				node = parent_node; -			} -			else -			{ -				// create a child that is not an attribute, but with same name -				node = parent_node->createChild(attribute_name.c_str(), false); -			} -		} -		node->setStringValue(*string_val); -		return true; -	} -	return false; -} - -bool LLXUIParser::readU8Value(void* val_ptr) -{ -	return mCurReadNode->getByteValue(1, (U8*)val_ptr); -} - -bool LLXUIParser::writeU8Value(const void* val_ptr, const name_stack_t& stack) -{ -	LLXMLNodePtr node = getNode(stack); -	if (node.notNull()) -	{ -		node->setUnsignedValue(*((U8*)val_ptr)); -		return true; -	} -	return false; -} - -bool LLXUIParser::readS8Value(void* val_ptr) -{ -	S32 value; -	if(mCurReadNode->getIntValue(1, &value)) -	{ -		*((S8*)val_ptr) = value; -		return true; -	} -	return false; -} - -bool LLXUIParser::writeS8Value(const void* val_ptr, const name_stack_t& stack) -{ -	LLXMLNodePtr node = getNode(stack); -	if (node.notNull()) -	{ -		node->setIntValue(*((S8*)val_ptr)); -		return true; -	} -	return false; -} - -bool LLXUIParser::readU16Value(void* val_ptr) -{ -	U32 value; -	if(mCurReadNode->getUnsignedValue(1, &value)) -	{ -		*((U16*)val_ptr) = value; -		return true; -	} -	return false; -} - -bool LLXUIParser::writeU16Value(const void* val_ptr, const name_stack_t& stack) -{ -	LLXMLNodePtr node = getNode(stack); -	if (node.notNull()) -	{ -		node->setUnsignedValue(*((U16*)val_ptr)); -		return true; -	} -	return false; -} - -bool LLXUIParser::readS16Value(void* val_ptr) -{ -	S32 value; -	if(mCurReadNode->getIntValue(1, &value)) -	{ -		*((S16*)val_ptr) = value; -		return true; -	} -	return false; -} - -bool LLXUIParser::writeS16Value(const void* val_ptr, const name_stack_t& stack) -{ -	LLXMLNodePtr node = getNode(stack); -	if (node.notNull()) -	{ -		node->setIntValue(*((S16*)val_ptr)); -		return true; -	} -	return false; -} - -bool LLXUIParser::readU32Value(void* val_ptr) -{ -	return mCurReadNode->getUnsignedValue(1, (U32*)val_ptr); -} - -bool LLXUIParser::writeU32Value(const void* val_ptr, const name_stack_t& stack) -{ -	LLXMLNodePtr node = getNode(stack); -	if (node.notNull()) -	{ -		node->setUnsignedValue(*((U32*)val_ptr)); -		return true; -	} -	return false; -} - -bool LLXUIParser::readS32Value(void* val_ptr) -{ -	return mCurReadNode->getIntValue(1, (S32*)val_ptr); -} - -bool LLXUIParser::writeS32Value(const void* val_ptr, const name_stack_t& stack) -{ -	LLXMLNodePtr node = getNode(stack); -	if (node.notNull()) -	{ -		node->setIntValue(*((S32*)val_ptr)); -		return true; -	} -	return false; -} - -bool LLXUIParser::readF32Value(void* val_ptr) -{ -	return mCurReadNode->getFloatValue(1, (F32*)val_ptr); -} - -bool LLXUIParser::writeF32Value(const void* val_ptr, const name_stack_t& stack) -{ -	LLXMLNodePtr node = getNode(stack); -	if (node.notNull()) -	{ -		node->setFloatValue(*((F32*)val_ptr)); -		return true; -	} -	return false; -} - -bool LLXUIParser::readF64Value(void* val_ptr) -{ -	return mCurReadNode->getDoubleValue(1, (F64*)val_ptr); -} - -bool LLXUIParser::writeF64Value(const void* val_ptr, const name_stack_t& stack) -{ -	LLXMLNodePtr node = getNode(stack); -	if (node.notNull()) -	{ -		node->setDoubleValue(*((F64*)val_ptr)); -		return true; -	} -	return false; -} - -bool LLXUIParser::readColor4Value(void* val_ptr) -{ -	LLColor4* colorp = (LLColor4*)val_ptr; -	if(mCurReadNode->getFloatValue(4, colorp->mV) >= 3) -	{ -		return true; -	} - -	return false; -} - -bool LLXUIParser::writeColor4Value(const void* val_ptr, const name_stack_t& stack) -{ -	LLXMLNodePtr node = getNode(stack); -	if (node.notNull()) -	{ -		LLColor4 color = *((LLColor4*)val_ptr); -		node->setFloatValue(4, color.mV); -		return true; -	} -	return false; -} - -bool LLXUIParser::readUIColorValue(void* val_ptr) -{ -	LLUIColor* param = (LLUIColor*)val_ptr; -	LLColor4 color; -	bool success =  mCurReadNode->getFloatValue(4, color.mV) >= 3; -	if (success) -	{ -		param->set(color); -		return true; -	} -	return false; -} - -bool LLXUIParser::writeUIColorValue(const void* val_ptr, const name_stack_t& stack) -{ -	LLXMLNodePtr node = getNode(stack); -	if (node.notNull()) -	{ -		LLUIColor color = *((LLUIColor*)val_ptr); -		//RN: don't write out the color that is represented by a function -		// rely on param block exporting to get the reference to the color settings -		if (color.isReference()) return false; -		node->setFloatValue(4, color.get().mV); -		return true; -	} -	return false; -} - -bool LLXUIParser::readUUIDValue(void* val_ptr) -{ -	LLUUID temp_id; -	// LLUUID::set is destructive, so use temporary value -	if (temp_id.set(mCurReadNode->getSanitizedValue())) -	{ -		*(LLUUID*)(val_ptr) = temp_id; -		return true; -	} -	return false; -} - -bool LLXUIParser::writeUUIDValue(const void* val_ptr, const name_stack_t& stack) -{ -	LLXMLNodePtr node = getNode(stack); -	if (node.notNull()) -	{ -		node->setStringValue(((LLUUID*)val_ptr)->asString()); -		return true; -	} -	return false; -} - -bool LLXUIParser::readSDValue(void* val_ptr) -{ -	*((LLSD*)val_ptr) = LLSD(mCurReadNode->getSanitizedValue()); -	return true; -} - -bool LLXUIParser::writeSDValue(const void* val_ptr, const name_stack_t& stack) -{ -	LLXMLNodePtr node = getNode(stack); -	if (node.notNull()) -	{ -		std::string string_val = ((LLSD*)val_ptr)->asString(); -		if (string_val.find('\n') != std::string::npos || string_val.size() > MAX_STRING_ATTRIBUTE_SIZE) -		{ -			// don't write strings with newlines into attributes -			std::string attribute_name = node->getName()->mString; -			LLXMLNodePtr parent_node = node->mParent; -			parent_node->deleteChild(node); -			// write results in text contents of node -			if (attribute_name == "value") -			{ -				// "value" is implicit, just write to parent -				node = parent_node; -			} -			else -			{ -				node = parent_node->createChild(attribute_name.c_str(), false); -			} -		} - -		node->setStringValue(string_val); -		return true; -	} -	return false; -} - -/*virtual*/ std::string LLXUIParser::getCurrentElementName() -{ -	std::string full_name; -	for (name_stack_t::iterator it = mNameStack.begin();	 -		it != mNameStack.end(); -		++it) -	{ -		full_name += it->first + "."; // build up dotted names: "button.param.nestedparam." -	} - -	return full_name; -} - -void LLXUIParser::parserWarning(const std::string& message) -{ -#ifdef LL_WINDOWS -	// use Visual Studo friendly formatting of output message for easy access to originating xml -	llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", LLUICtrlFactory::getInstance()->getCurFileName().c_str(), mCurReadNode->getLineNumber(), message.c_str()).c_str()); -	utf16str += '\n'; -	OutputDebugString(utf16str.c_str()); -#else -	Parser::parserWarning(message); -#endif -} - -void LLXUIParser::parserError(const std::string& message) -{ -#ifdef LL_WINDOWS -	llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", LLUICtrlFactory::getInstance()->getCurFileName().c_str(), mCurReadNode->getLineNumber(), message.c_str()).c_str()); -	utf16str += '\n'; -	OutputDebugString(utf16str.c_str()); -#else -	Parser::parserError(message); -#endif -} diff --git a/indra/llui/lluictrlfactory.h b/indra/llui/lluictrlfactory.h index b82feb3f58..e47010c316 100644 --- a/indra/llui/lluictrlfactory.h +++ b/indra/llui/lluictrlfactory.h @@ -35,9 +35,12 @@  #include "llcallbackmap.h"  #include "llinitparam.h" +#include "llregistry.h"  #include "llxmlnode.h"  #include "llfasttimer.h" +#include "llxuiparser.h" +  #include <boost/function.hpp>  #include <iosfwd>  #include <stack> @@ -47,110 +50,6 @@ class LLPanel;  class LLFloater;  class LLView; -class LLXSDWriter : public LLInitParam::Parser -{ -	LOG_CLASS(LLXSDWriter); -public: -	void writeXSD(const std::string& name, LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const std::string& xml_namespace); - -	/*virtual*/ std::string getCurrentElementName() { return LLStringUtil::null; } - -	LLXSDWriter(); - -protected: -	void writeAttribute(const std::string& type, const Parser::name_stack_t&, S32 min_count, S32 max_count, const std::vector<std::string>* possible_values); -	void addAttributeToSchema(LLXMLNodePtr nodep, const std::string& attribute_name, const std::string& type, bool mandatory, const std::vector<std::string>* possible_values); -	LLXMLNodePtr mAttributeNode; -	LLXMLNodePtr mElementNode; -	LLXMLNodePtr mSchemaNode; - -	typedef std::set<std::string> string_set_t; -	typedef std::map<LLXMLNodePtr, string_set_t> attributes_map_t; -	attributes_map_t	mAttributesWritten; -}; - -// NOTE: DOES NOT WORK YET -// should support child widgets for XUI -class LLXUIXSDWriter : public LLXSDWriter -{ -public: -	void writeXSD(const std::string& name, const std::string& path, const LLInitParam::BaseBlock& block); -}; - -class LLXUIParser : public LLInitParam::Parser, public LLSingleton<LLXUIParser> -{ -LOG_CLASS(LLXUIParser); - -protected: -	LLXUIParser(); -	friend class LLSingleton<LLXUIParser>; -public: -	typedef LLInitParam::Parser::name_stack_t name_stack_t; - -	/*virtual*/ std::string getCurrentElementName(); -	/*virtual*/ void parserWarning(const std::string& message); -	/*virtual*/ void parserError(const std::string& message); - -	void readXUI(LLXMLNodePtr node, LLInitParam::BaseBlock& block, bool silent=false); -	void writeXUI(LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const LLInitParam::BaseBlock* diff_block = NULL); - -private: -	typedef std::list<std::pair<std::string, bool> >	token_list_t; - -	bool readXUIImpl(LLXMLNodePtr node, const std::string& scope, LLInitParam::BaseBlock& block); -	bool readAttributes(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block); - -	//reader helper functions -	bool readBoolValue(void* val_ptr); -	bool readStringValue(void* val_ptr); -	bool readU8Value(void* val_ptr); -	bool readS8Value(void* val_ptr); -	bool readU16Value(void* val_ptr); -	bool readS16Value(void* val_ptr); -	bool readU32Value(void* val_ptr); -	bool readS32Value(void* val_ptr); -	bool readF32Value(void* val_ptr); -	bool readF64Value(void* val_ptr); -	bool readColor4Value(void* val_ptr); -	bool readUIColorValue(void* val_ptr); -	bool readUUIDValue(void* val_ptr); -	bool readSDValue(void* val_ptr); - -	//writer helper functions -	bool writeBoolValue(const void* val_ptr, const name_stack_t&); -	bool writeStringValue(const void* val_ptr, const name_stack_t&); -	bool writeU8Value(const void* val_ptr, const name_stack_t&); -	bool writeS8Value(const void* val_ptr, const name_stack_t&); -	bool writeU16Value(const void* val_ptr, const name_stack_t&); -	bool writeS16Value(const void* val_ptr, const name_stack_t&); -	bool writeU32Value(const void* val_ptr, const name_stack_t&); -	bool writeS32Value(const void* val_ptr, const name_stack_t&); -	bool writeF32Value(const void* val_ptr, const name_stack_t&); -	bool writeF64Value(const void* val_ptr, const name_stack_t&); -	bool writeColor4Value(const void* val_ptr, const name_stack_t&); -	bool writeUIColorValue(const void* val_ptr, const name_stack_t&); -	bool writeUUIDValue(const void* val_ptr, const name_stack_t&); -	bool writeSDValue(const void* val_ptr, const name_stack_t&); - -	LLXMLNodePtr getNode(const name_stack_t& stack); - -private: -	Parser::name_stack_t			mNameStack; -	LLXMLNodePtr					mCurReadNode; -	// Root of the widget XML sub-tree, for example, "line_editor" -	LLXMLNodePtr					mWriteRootNode; -	 -	typedef std::map<S32, LLXMLNodePtr>	out_nodes_t; -	out_nodes_t						mOutNodes; -	S32								mLastWriteGeneration; -	LLXMLNodePtr					mLastWrittenChild; -	S32								mCurReadDepth; -}; - -// global static instance for registering all widget types -typedef boost::function<LLView* (LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node)> LLWidgetCreatorFunc; - -typedef LLRegistry<std::string, LLWidgetCreatorFunc> widget_registry_t;  // sort functor for typeid maps  struct LLCompareTypeID @@ -192,11 +91,6 @@ class LLWidgetNameRegistry  :	public LLRegistrySingleton<const std::type_info*, std::string, LLWidgetNameRegistry , LLCompareTypeID>  {}; -// lookup widget type by name -class LLWidgetTypeRegistry -:	public LLRegistrySingleton<std::string, const std::type_info*, LLWidgetTypeRegistry> -{}; -  // lookup factory functions for default widget instances by widget type  typedef LLView* (*dummy_widget_creator_func_t)(const std::string&);  class LLDefaultWidgetRegistry @@ -209,10 +103,6 @@ class LLDefaultParamBlockRegistry  :	public LLRegistrySingleton<const std::type_info*, empty_param_block_func_t, LLDefaultParamBlockRegistry, LLCompareTypeID>  {}; -class LLChildRegistryRegistry -: public LLRegistrySingleton<const std::type_info*, widget_registry_t, LLChildRegistryRegistry> -{}; -  extern LLFastTimer::DeclareTimer FTM_WIDGET_SETUP;  extern LLFastTimer::DeclareTimer FTM_WIDGET_CONSTRUCTION;  extern LLFastTimer::DeclareTimer FTM_INIT_FROM_PARAMS; diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index bf40353410..ecda880c1f 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -2362,8 +2362,10 @@ void LLWindowSDL::spawnWebBrowser(const std::string& escaped_url)  # endif // LL_X11  	std::string cmd, arg; -	cmd  = gDirUtilp->getAppRODataDir().c_str(); -	cmd += gDirUtilp->getDirDelimiter().c_str(); +	cmd  = gDirUtilp->getAppRODataDir(); +	cmd += gDirUtilp->getDirDelimiter(); +	cmd += "etc"; +	cmd += gDirUtilp->getDirDelimiter();  	cmd += "launch_url.sh";  	arg = escaped_url;  	exec_cmd(cmd, arg); diff --git a/indra/llxuixml/CMakeLists.txt b/indra/llxuixml/CMakeLists.txt new file mode 100644 index 0000000000..daed4de6ce --- /dev/null +++ b/indra/llxuixml/CMakeLists.txt @@ -0,0 +1,45 @@ +# -*- cmake -*- + +project(llxuixml) + +include(00-Common) +include(LLCommon) +include(LLMath) +include(LLXML) + +include_directories( +    ${LLCOMMON_INCLUDE_DIRS} +    ${LLMATH_INCLUDE_DIRS} +    ${LLXML_INCLUDE_DIRS} +    ) + +set(llxuixml_SOURCE_FILES +    llinitparam.cpp +    lltrans.cpp +    lluicolor.cpp +    llxuiparser.cpp +    ) +     +set(llxuixml_HEADER_FILES +    CMakeLists.txt + +    llinitparam.h +    lltrans.h +    llregistry.h +    lluicolor.h +    llxuiparser.h +    ) + +set_source_files_properties(${llxuixml_HEADER_FILES} +                            PROPERTIES HEADER_FILE_ONLY TRUE) + +list(APPEND llxuixml_SOURCE_FILES ${llxuixml_HEADER_FILES}) + +add_library (llxuixml ${llxuixml_SOURCE_FILES}) +# Libraries on which this library depends, needed for Linux builds +# Sort by high-level to low-level +target_link_libraries(llxuixml +    llxml +    llcommon +    llmath +    ) diff --git a/indra/llxuixml/llinitparam.cpp b/indra/llxuixml/llinitparam.cpp new file mode 100644 index 0000000000..d35e7b40f8 --- /dev/null +++ b/indra/llxuixml/llinitparam.cpp @@ -0,0 +1,524 @@ +/**  + * @file llinitparam.cpp + * @brief parameter block abstraction for creating complex objects and  + * parsing construction parameters from xml and LLSD + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + *  + * Copyright (c) 2008-2009, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llinitparam.h" + + +namespace LLInitParam +{ +	BlockDescriptor BaseBlock::sBlockDescriptor; + +	// +	// Param +	// +	Param::Param(BaseBlock* enclosing_block) +	:	mIsProvided(false) +	{ +		const U8* my_addr = reinterpret_cast<const U8*>(this); +		const U8* block_addr = reinterpret_cast<const U8*>(enclosing_block); +		mEnclosingBlockOffset = (S16)(block_addr - my_addr); +	} + +	// +	// Parser +	// +	Parser::~Parser() +	{} + +	void Parser::parserWarning(const std::string& message) +	{ +		if (mParseSilently) return; +		llwarns << message << llendl; +	} +	 +	void Parser::parserError(const std::string& message) +	{ +		if (mParseSilently) return; +		llerrs << message << llendl; +	} + + +	// +	// BlockDescriptor +	// +	void BlockDescriptor::aggregateBlockData(BlockDescriptor& src_block_data)  +	{ +		mNamedParams.insert(src_block_data.mNamedParams.begin(), src_block_data.mNamedParams.end()); +		mSynonyms.insert(src_block_data.mSynonyms.begin(), src_block_data.mSynonyms.end()); +		std::copy(src_block_data.mUnnamedParams.begin(), src_block_data.mUnnamedParams.end(), std::back_inserter(mUnnamedParams)); +		std::copy(src_block_data.mValidationList.begin(), src_block_data.mValidationList.end(), std::back_inserter(mValidationList)); +		std::copy(src_block_data.mAllParams.begin(), src_block_data.mAllParams.end(), std::back_inserter(mAllParams)); +	} + +	// +	// BaseBlock +	// +	BaseBlock::BaseBlock() +	:	mLastChangedParam(0), +		mChangeVersion(0) +	{} + +	BaseBlock::~BaseBlock() +	{} + +	// called by each derived class in least to most derived order +	void BaseBlock::init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size) +	{ +		mBlockDescriptor = &descriptor; + +		descriptor.mCurrentBlockPtr = this; +		descriptor.mMaxParamOffset = block_size; + +		switch(descriptor.mInitializationState) +		{ +		case BlockDescriptor::UNINITIALIZED: +			// copy params from base class here +			descriptor.aggregateBlockData(base_descriptor); + +			descriptor.mInitializationState = BlockDescriptor::INITIALIZING; +			break; +		case BlockDescriptor::INITIALIZING: +			descriptor.mInitializationState = BlockDescriptor::INITIALIZED; +			break; +		case BlockDescriptor::INITIALIZED: +			// nothing to do +			break; +		} +	} + +	param_handle_t BaseBlock::getHandleFromParam(const Param* param) const +	{ +		const U8* param_address = reinterpret_cast<const U8*>(param); +		const U8* baseblock_address = reinterpret_cast<const U8*>(this); +		return (param_address - baseblock_address); +	} + +	bool BaseBlock::submitValue(const Parser::name_stack_t& name_stack, Parser& p, bool silent) +	{ +		if (!deserializeBlock(p, boost::make_iterator_range(name_stack.begin(), name_stack.end()))) +		{ +			if (!silent) +			{ +				p.parserWarning(llformat("Failed to parse parameter \"%s\"", p.getCurrentElementName().c_str())); +			} +			return false; +		} +		return true; +	} + + +	bool BaseBlock::validateBlock(bool silent) const +	{ +		const BlockDescriptor& block_data = getBlockDescriptor(); +		for (BlockDescriptor::param_validation_list_t::const_iterator it = block_data.mValidationList.begin(); it != block_data.mValidationList.end(); ++it) +		{ +			const Param* param = getParamFromHandle(it->first); +			if (!it->second(param)) +			{ +				if (!silent) +				{ +					llwarns << "Invalid param \"" << getParamName(block_data, param) << "\"" << llendl; +				} +				return false; +			} +		} +		return true; +	} + +	bool BaseBlock::serializeBlock(Parser& parser, Parser::name_stack_t name_stack, const LLInitParam::BaseBlock* diff_block) const +	{ +		// named param is one like LLView::Params::follows +		// unnamed param is like LLView::Params::rect - implicit +		const BlockDescriptor& block_data = getBlockDescriptor(); + +		for (BlockDescriptor::param_list_t::const_iterator it = block_data.mUnnamedParams.begin();  +			it != block_data.mUnnamedParams.end();  +			++it) +		{ +			param_handle_t param_handle = (*it)->mParamHandle; +			const Param* param = getParamFromHandle(param_handle); +			ParamDescriptor::serialize_func_t serialize_func = (*it)->mSerializeFunc; +			if (serialize_func) +			{ +				const Param* diff_param = diff_block ? diff_block->getParamFromHandle(param_handle) : NULL; +				// each param descriptor remembers its serial number +				// so we can inspect the same param under different names +				// and see that it has the same number +				(*it)->mGeneration = parser.newParseGeneration(); +				name_stack.push_back(std::make_pair("", (*it)->mGeneration)); +				serialize_func(*param, parser, name_stack, diff_param); +				name_stack.pop_back(); +			} +		} + +		for(BlockDescriptor::param_map_t::const_iterator it = block_data.mNamedParams.begin(); +			it != block_data.mNamedParams.end(); +			++it) +		{ +			param_handle_t param_handle = it->second->mParamHandle; +			const Param* param = getParamFromHandle(param_handle); +			ParamDescriptor::serialize_func_t serialize_func = it->second->mSerializeFunc; +			if (serialize_func) +			{ +				// Ensure this param has not already been serialized +				// Prevents <rect> from being serialized as its own tag. +				bool duplicate = false; +				for (BlockDescriptor::param_list_t::const_iterator it2 = block_data.mUnnamedParams.begin();  +					it2 != block_data.mUnnamedParams.end();  +					++it2) +				{ +					if (param_handle == (*it2)->mParamHandle) +					{ +						duplicate = true; +						break; +					} +				} + +				//FIXME: for now, don't attempt to serialize values under synonyms, as current parsers +				// don't know how to detect them +				if (duplicate)  +				{ +					continue; +				} + +				if (!duplicate) +				{ +					it->second->mGeneration = parser.newParseGeneration(); +				} + +				name_stack.push_back(std::make_pair(it->first, it->second->mGeneration)); +				const Param* diff_param = diff_block ? diff_block->getParamFromHandle(param_handle) : NULL; +				serialize_func(*param, parser, name_stack, diff_param); +				name_stack.pop_back(); +			} +		} + +		return true; +	} + +	bool BaseBlock::inspectBlock(Parser& parser, Parser::name_stack_t name_stack) const +	{ +		// named param is one like LLView::Params::follows +		// unnamed param is like LLView::Params::rect - implicit +		const BlockDescriptor& block_data = getBlockDescriptor(); + +		for (BlockDescriptor::param_list_t::const_iterator it = block_data.mUnnamedParams.begin();  +			it != block_data.mUnnamedParams.end();  +			++it) +		{ +			param_handle_t param_handle = (*it)->mParamHandle; +			const Param* param = getParamFromHandle(param_handle); +			ParamDescriptor::inspect_func_t inspect_func = (*it)->mInspectFunc; +			if (inspect_func) +			{ +				(*it)->mGeneration = parser.newParseGeneration(); +				name_stack.push_back(std::make_pair("", (*it)->mGeneration)); +				inspect_func(*param, parser, name_stack, (*it)->mMinCount, (*it)->mMaxCount); +				name_stack.pop_back(); +			} +		} + +		for(BlockDescriptor::param_map_t::const_iterator it = block_data.mNamedParams.begin(); +			it != block_data.mNamedParams.end(); +			++it) +		{ +			param_handle_t param_handle = it->second->mParamHandle; +			const Param* param = getParamFromHandle(param_handle); +			ParamDescriptor::inspect_func_t inspect_func = it->second->mInspectFunc; +			if (inspect_func) +			{ +				// Ensure this param has not already been inspected +				bool duplicate = false; +				for (BlockDescriptor::param_list_t::const_iterator it2 = block_data.mUnnamedParams.begin();  +					it2 != block_data.mUnnamedParams.end();  +					++it2) +				{ +					if (param_handle == (*it2)->mParamHandle) +					{ +						duplicate = true; +						break; +					} +				} + +				if (!duplicate) +				{ +					it->second->mGeneration = parser.newParseGeneration(); +				} +				name_stack.push_back(std::make_pair(it->first, it->second->mGeneration)); +				inspect_func(*param, parser, name_stack, it->second->mMinCount, it->second->mMaxCount); +				name_stack.pop_back(); +			} +		} + +		for(BlockDescriptor::param_map_t::const_iterator it = block_data.mSynonyms.begin(); +			it != block_data.mSynonyms.end(); +			++it) +		{ +			param_handle_t param_handle = it->second->mParamHandle; +			const Param* param = getParamFromHandle(param_handle); +			ParamDescriptor::inspect_func_t inspect_func = it->second->mInspectFunc; +			if (inspect_func) +			{ +				// use existing serial number for param +				name_stack.push_back(std::make_pair(it->first, it->second->mGeneration)); +				inspect_func(*param, parser, name_stack, it->second->mMinCount, it->second->mMaxCount); +				name_stack.pop_back(); +			} +		} + +		return true; +	} + +	bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack) +	{ +		BlockDescriptor& block_data = getBlockDescriptor(); +		bool names_left = !name_stack.empty(); + +		if (names_left) +		{ +			const std::string& top_name = name_stack.front().first; + +			ParamDescriptor::deserialize_func_t deserialize_func = NULL; +			Param* paramp = NULL; + +			BlockDescriptor::param_map_t::iterator found_it = block_data.mNamedParams.find(top_name); +			if (found_it != block_data.mNamedParams.end()) +			{ +				// find pointer to member parameter from offset table +				paramp = getParamFromHandle(found_it->second->mParamHandle); +				deserialize_func = found_it->second->mDeserializeFunc; +			} +			else +			{ +				BlockDescriptor::param_map_t::iterator found_it = block_data.mSynonyms.find(top_name); +				if (found_it != block_data.mSynonyms.end()) +				{ +					// find pointer to member parameter from offset table +					paramp = getParamFromHandle(found_it->second->mParamHandle); +					deserialize_func = found_it->second->mDeserializeFunc; +				} +			} +					 +			Parser::name_stack_range_t new_name_stack(++name_stack.begin(), name_stack.end()); +			if (deserialize_func) +			{ +				return deserialize_func(*paramp, p, new_name_stack, name_stack.empty() ? -1 : name_stack.front().second); +			} +		} + +		// try to parse unnamed parameters, in declaration order +		for ( BlockDescriptor::param_list_t::iterator it = block_data.mUnnamedParams.begin();  +			it != block_data.mUnnamedParams.end();  +			++it) +		{ +			Param* paramp = getParamFromHandle((*it)->mParamHandle); +			ParamDescriptor::deserialize_func_t deserialize_func = (*it)->mDeserializeFunc; + +			if (deserialize_func && deserialize_func(*paramp, p, name_stack, name_stack.empty() ? -1 : name_stack.front().second)) +			{ +				mLastChangedParam = (*it)->mParamHandle; +				return true; +			} +		} + +		return false; +	} + +	//static  +	void BaseBlock::addParam(BlockDescriptor& block_data, const ParamDescriptor& in_param, const char* char_name) +	{ +		// create a copy of the paramdescriptor in allparams +		// so other data structures can store a pointer to it +		block_data.mAllParams.push_back(in_param); +		ParamDescriptor& param(block_data.mAllParams.back()); + +		std::string name(char_name); +		if ((size_t)param.mParamHandle > block_data.mMaxParamOffset) +		{ +			llerrs << "Attempted to register param with block defined for parent class, make sure to derive from LLInitParam::Block<YOUR_CLASS, PARAM_BLOCK_BASE_CLASS>" << llendl; +		} + +		if (name.empty()) +		{ +			block_data.mUnnamedParams.push_back(¶m); +		} +		else +		{ +			// don't use insert, since we want to overwrite existing entries +			block_data.mNamedParams[name] = ¶m; +		} + +		if (param.mValidationFunc) +		{ +			block_data.mValidationList.push_back(std::make_pair(param.mParamHandle, param.mValidationFunc)); +		} +	} + +	void BaseBlock::addSynonym(Param& param, const std::string& synonym) +	{ +		BlockDescriptor& block_data = getBlockDescriptor(); +		if (block_data.mInitializationState == BlockDescriptor::INITIALIZING) +		{ +			param_handle_t handle = getHandleFromParam(¶m); +			 +			// check for invalid derivation from a paramblock (i.e. without using +			// Block<T, Base_Class> +			if ((size_t)handle > block_data.mMaxParamOffset) +			{ +				llerrs << "Attempted to register param with block defined for parent class, make sure to derive from LLInitParam::Block<YOUR_CLASS, PARAM_BLOCK_BASE_CLASS>" << llendl; +			} + +			ParamDescriptor* param_descriptor = findParamDescriptor(handle); +			if (param_descriptor) +			{ +				if (synonym.empty()) +				{ +					block_data.mUnnamedParams.push_back(param_descriptor); +				} +				else +				{ +					block_data.mSynonyms[synonym] = param_descriptor; +				} +			} +		} +	} + +	void BaseBlock::setLastChangedParam(const Param& last_param, bool user_provided) +	{  +		mLastChangedParam = getHandleFromParam(&last_param);  +		mChangeVersion++; +	} + +	const std::string& BaseBlock::getParamName(const BlockDescriptor& block_data, const Param* paramp) const +	{ +		param_handle_t handle = getHandleFromParam(paramp); +		for (BlockDescriptor::param_map_t::const_iterator it = block_data.mNamedParams.begin(); it != block_data.mNamedParams.end(); ++it) +		{ +			if (it->second->mParamHandle == handle) +			{ +				return it->first; +			} +		} + +		for (BlockDescriptor::param_map_t::const_iterator it = block_data.mSynonyms.begin(); it != block_data.mSynonyms.end(); ++it) +		{ +			if (it->second->mParamHandle == handle) +			{ +				return it->first; +			} +		} + +		return LLStringUtil::null; +	} + +	ParamDescriptor* BaseBlock::findParamDescriptor(param_handle_t handle) +	{ +		BlockDescriptor& descriptor = getBlockDescriptor(); +		BlockDescriptor::all_params_list_t::iterator end_it = descriptor.mAllParams.end(); +		for (BlockDescriptor::all_params_list_t::iterator it = descriptor.mAllParams.begin(); +			it != end_it; +			++it) +		{ +			if (it->mParamHandle == handle) return &(*it); +		} +		return NULL; +	} + +	// take all provided params from other and apply to self +	// NOTE: this requires that "other" is of the same derived type as this +	bool BaseBlock::overwriteFromImpl(BlockDescriptor& block_data, const BaseBlock& other) +	{ +		bool param_changed = false; +		BlockDescriptor::all_params_list_t::const_iterator end_it = block_data.mAllParams.end(); +		for (BlockDescriptor::all_params_list_t::const_iterator it = block_data.mAllParams.begin(); +			it != end_it; +			++it) +		{ +			const Param* other_paramp = other.getParamFromHandle(it->mParamHandle); +			ParamDescriptor::merge_func_t merge_func = it->mMergeFunc; +			if (merge_func) +			{ +				Param* paramp = getParamFromHandle(it->mParamHandle); +				param_changed |= merge_func(*paramp, *other_paramp, true); +				mLastChangedParam = it->mParamHandle; +			} +		} +		return param_changed; +	} + +	// take all provided params that are not already provided, and apply to self +	bool BaseBlock::fillFromImpl(BlockDescriptor& block_data, const BaseBlock& other) +	{ +		bool param_changed = false; +		BlockDescriptor::all_params_list_t::const_iterator end_it = block_data.mAllParams.end(); +		for (BlockDescriptor::all_params_list_t::const_iterator it = block_data.mAllParams.begin(); +			it != end_it; +			++it) +		{ +			const Param* other_paramp = other.getParamFromHandle(it->mParamHandle); +			ParamDescriptor::merge_func_t merge_func = it->mMergeFunc; +			if (merge_func) +			{ +				Param* paramp = getParamFromHandle(it->mParamHandle); +				param_changed |= merge_func(*paramp, *other_paramp, false); +				mLastChangedParam = it->mParamHandle; +			} +		} +		return param_changed; +	} + + +	template<> +	bool ParamCompare<boost::function<void (const std::string &,void *)> >::equals( +	   const boost::function<void (const std::string &,void *)> &a, +	   const boost::function<void (const std::string &,void *)> &b) +	{ +		return false; +	} + +	template<> +	bool ParamCompare<boost::function<void (const LLSD &,const LLSD &)> >::equals( +	   const boost::function<void (const LLSD &,const LLSD &)> &a, +	   const boost::function<void (const LLSD &,const LLSD &)> &b) +	{ +		return false; +	} + +	template<> +	bool ParamCompare<LLSD>::equals(const LLSD &a, const LLSD &b) +	{ +		return false; +	} +} diff --git a/indra/llxuixml/llinitparam.h b/indra/llxuixml/llinitparam.h new file mode 100644 index 0000000000..cb56049ae2 --- /dev/null +++ b/indra/llxuixml/llinitparam.h @@ -0,0 +1,1822 @@ +/**  + * @file llinitparam.h + * @brief parameter block abstraction for creating complex objects and  + * parsing construction parameters from xml and LLSD + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + *  + * Copyright (c) 2008-2009, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPARAM_H +#define LL_LLPARAM_H + +#include <vector> + +#include <stddef.h> +#include <boost/function.hpp> +#include <boost/bind.hpp> +#include <boost/range/iterator_range.hpp> +#include "llregistry.h" +#include "llmemory.h" + + +namespace LLInitParam +{ +    template <typename T>  +    class ParamCompare { +    public: +    	static bool equals(const T &a, const T &b); +    }; +     +    template<class T> +    bool ParamCompare<T>::equals(const T &a, const T&b) +    { +    	return a == b; +    } + + +	// default constructor adaptor for InitParam Values +	// constructs default instances of the given type, returned by const reference +	template <typename T> +	struct DefaultInitializer +	{ +		typedef const T&			T_const_ref; +		// return reference to a single default instance of T +		// built-in types will be initialized to zero, default constructor otherwise +		static T_const_ref get() { static T t = T(); return t; }  +	}; + +	// helper functions and classes +	typedef ptrdiff_t param_handle_t; + +	template <typename T> +	class TypeValues +	{ +	public: +		// empty default implemenation of key cache +		class KeyCache +		{ +		public: +			void setKey(const std::string& key) {} +			std::string getKey() const { return ""; } +			void clearKey(){} +		}; + +		static bool get(const std::string& name, T& value) +		{ +			return false; +		} + +		static bool empty() +		{ +			return true; +		} + +		static std::vector<std::string>* getPossibleValues() { return NULL; } +	}; + +	template <typename T, typename DERIVED_TYPE = TypeValues<T> > +	class TypeValuesHelper +	:	public LLRegistrySingleton<std::string, T, DERIVED_TYPE > +	{ +		typedef LLRegistrySingleton<std::string, T, DERIVED_TYPE>	super_t; +		typedef LLSingleton<DERIVED_TYPE>							singleton_t; +	public: + +		//TODO: cache key by index to save on param block size +		class KeyCache +		{ +		public: +			void setKey(const std::string& key)  +			{ +				mKey = key;  +			} + +			void clearKey() +			{ +				mKey = ""; +			} + +			std::string getKey() const +			{  +				return mKey;  +			} + +		private: +			std::string mKey; +		}; + +		static bool get(const std::string& name, T& value) +		{ +			if (!singleton_t::instance().exists(name)) return false; + +			value = *singleton_t::instance().getValue(name); +			return true; +		} + +		static bool empty() +		{ +			return singleton_t::instance().LLRegistry<std::string, T>::empty(); +		} +	 +		//override this to add name value pairs +		static void declareValues() {} +	 +		void initSingleton() +		{ +			DERIVED_TYPE::declareValues(); +		} + +		static const std::vector<std::string>* getPossibleValues()  +		{  +			// in order to return a pointer to a member, we lazily +			// evaluate the result and store it in mValues here +			if (singleton_t::instance().mValues.empty()) +			{ +				typename super_t::Registrar::registry_map_t::const_iterator it; +				for (it = super_t::defaultRegistrar().beginItems(); it != super_t::defaultRegistrar().endItems(); ++it) +				{ +					singleton_t::instance().mValues.push_back(it->first); +				} +			} +			return &singleton_t::instance().mValues;  +		} + + +	protected: +		static void declare(const std::string& name, const T& value) +		{ +			super_t::defaultRegistrar().add(name, value); +		} + +	private: +		std::vector<std::string> mValues; +	}; + +	class Parser +	{ +		LOG_CLASS(Parser); + +	public: +		 +		struct CompareTypeID +		{ +			bool operator()(const std::type_info* lhs, const std::type_info* rhs) const +			{ +				return lhs->before(*rhs); +			} +		}; + +		typedef std::vector<std::pair<std::string, S32> >			name_stack_t; +		typedef boost::iterator_range<name_stack_t::const_iterator>	name_stack_range_t; +		typedef std::vector<std::string>							possible_values_t; + +		typedef boost::function<bool (void*)>															parser_read_func_t; +		typedef boost::function<bool (const void*, const name_stack_t&)>								parser_write_func_t; +		typedef boost::function<void (const name_stack_t&, S32, S32, const possible_values_t*)>	parser_inspect_func_t; + +		typedef std::map<const std::type_info*, parser_read_func_t, CompareTypeID>		parser_read_func_map_t; +		typedef std::map<const std::type_info*, parser_write_func_t, CompareTypeID>		parser_write_func_map_t; +		typedef std::map<const std::type_info*, parser_inspect_func_t, CompareTypeID>	parser_inspect_func_map_t; + +		Parser() +		:	mParseSilently(false), +			mParseGeneration(0) +		{} +		virtual ~Parser(); + +		template <typename T> bool readValue(T& param) +	    { +		    parser_read_func_map_t::iterator found_it = mParserReadFuncs.find(&typeid(T)); +		    if (found_it != mParserReadFuncs.end()) +		    { +			    return found_it->second((void*)¶m); +		    } +		    return false; +	    } + +		template <typename T> bool writeValue(const T& param, const name_stack_t& name_stack) +		{ +		    parser_write_func_map_t::iterator found_it = mParserWriteFuncs.find(&typeid(T)); +		    if (found_it != mParserWriteFuncs.end()) +		    { +			    return found_it->second((const void*)¶m, name_stack); +		    } +		    return false; +		} + +		// dispatch inspection to registered inspection functions, for each parameter in a param block +		template <typename T> bool inspectValue(const name_stack_t& name_stack, S32 min_count, S32 max_count, const possible_values_t* possible_values) +		{ +		    parser_inspect_func_map_t::iterator found_it = mParserInspectFuncs.find(&typeid(T)); +		    if (found_it != mParserInspectFuncs.end()) +		    { +			    found_it->second(name_stack, min_count, max_count, possible_values); +				return true; +		    } +			return false; +		} + +		virtual std::string getCurrentElementName() = 0; +		virtual void parserWarning(const std::string& message); +		virtual void parserError(const std::string& message); +		void setParseSilently(bool silent) { mParseSilently = silent; } +		bool getParseSilently() { return mParseSilently; } + +		S32 getParseGeneration() { return mParseGeneration; } +		S32 newParseGeneration() { return ++mParseGeneration; } + + +	protected: +		template <typename T> +		void registerParserFuncs(parser_read_func_t read_func, parser_write_func_t write_func) +		{ +			mParserReadFuncs.insert(std::make_pair(&typeid(T), read_func)); +			mParserWriteFuncs.insert(std::make_pair(&typeid(T), write_func)); +		} + +		template <typename T> +		void registerInspectFunc(parser_inspect_func_t inspect_func) +		{ +			mParserInspectFuncs.insert(std::make_pair(&typeid(T), inspect_func)); +		} + +		bool				mParseSilently; + +	private: +		parser_read_func_map_t		mParserReadFuncs; +		parser_write_func_map_t		mParserWriteFuncs; +		parser_inspect_func_map_t	mParserInspectFuncs; +		S32	mParseGeneration; +	}; + +	class BaseBlock; + +	class Param +	{ +	public: +		// public to allow choice blocks to clear provided flag on stale choices +		void setProvided(bool is_provided) { mIsProvided = is_provided; } + +	protected: +		bool getProvided() const { return mIsProvided; } + +		Param(class BaseBlock* enclosing_block); + +		// store pointer to enclosing block as offset to reduce space and allow for quick copying +		BaseBlock& enclosingBlock() const +		{  +			const U8* my_addr = reinterpret_cast<const U8*>(this); +			// get address of enclosing BLOCK class using stored offset to enclosing BaseBlock class +			return *const_cast<BaseBlock*>( +							reinterpret_cast<const BaseBlock*>(my_addr + (ptrdiff_t)mEnclosingBlockOffset)); +		} + +	private: +		friend class BaseBlock; + +		bool		mIsProvided; +		S16			mEnclosingBlockOffset; +	}; + +	// various callbacks and constraints associated with an individual param +	struct ParamDescriptor +	{ +	public: +		typedef bool(*merge_func_t)(Param&, const Param&, bool); +		typedef bool(*deserialize_func_t)(Param&, Parser&, const Parser::name_stack_range_t&, S32); +		typedef void(*serialize_func_t)(const Param&, Parser&, Parser::name_stack_t&, const Param* diff_param); +		typedef void(*inspect_func_t)(const Param&, Parser&, Parser::name_stack_t&, S32 min_count, S32 max_count); +		typedef bool(*validation_func_t)(const Param*); + +		ParamDescriptor(param_handle_t p,  +						merge_func_t merge_func,  +						deserialize_func_t deserialize_func,  +						serialize_func_t serialize_func, +						validation_func_t validation_func, +						inspect_func_t inspect_func, +						S32 min_count, +						S32 max_count) +		:	mParamHandle(p), +			mMergeFunc(merge_func), +			mDeserializeFunc(deserialize_func), +			mSerializeFunc(serialize_func), +			mValidationFunc(validation_func), +			mInspectFunc(inspect_func), +			mMinCount(min_count), +			mMaxCount(max_count), +			mNumRefs(0) +		{} + +		ParamDescriptor() +		:	mParamHandle(0), +			mMergeFunc(NULL), +			mDeserializeFunc(NULL), +			mSerializeFunc(NULL), +			mValidationFunc(NULL), +			mInspectFunc(NULL), +			mMinCount(0), +			mMaxCount(0), +			mGeneration(0), +			mNumRefs(0) +		{} + +		param_handle_t		mParamHandle; +	 +		merge_func_t		mMergeFunc; +		deserialize_func_t	mDeserializeFunc; +		serialize_func_t	mSerializeFunc; +		inspect_func_t		mInspectFunc; +		validation_func_t	mValidationFunc; +		S32					mMinCount; +		S32					mMaxCount; +		S32					mGeneration; +		S32					mNumRefs; +	}; + +	// each derived Block class keeps a static data structure maintaining offsets to various params +	class BlockDescriptor +	{ +	public: +		BlockDescriptor() +		:	mMaxParamOffset(0), +			mInitializationState(UNINITIALIZED) +		{} + +		typedef enum e_initialization_state +		{ +			UNINITIALIZED, +			INITIALIZING, +			INITIALIZED +		} EInitializationState; + +		void aggregateBlockData(BlockDescriptor& src_block_data); + +	public: +		typedef std::map<const std::string, ParamDescriptor*> param_map_t; // references param descriptors stored in mAllParams +		typedef std::vector<ParamDescriptor*> param_list_t;  + +		typedef std::list<ParamDescriptor> all_params_list_t;// references param descriptors stored in mAllParams +		typedef std::vector<std::pair<param_handle_t, ParamDescriptor::validation_func_t> > param_validation_list_t; + +		param_map_t						mNamedParams;			// parameters with associated names +		param_map_t						mSynonyms;				// parameters with alternate names +		param_list_t					mUnnamedParams;			// parameters with_out_ associated names +		param_validation_list_t			mValidationList;		// parameters that must be validated +		all_params_list_t				mAllParams;				// all parameters, owns descriptors + +		size_t					mMaxParamOffset; + +		EInitializationState	mInitializationState;	// whether or not static block data has been initialized +		class BaseBlock*		mCurrentBlockPtr;		// pointer to block currently being constructed +	}; + +	class BaseBlock +	{ +	public: +		// this typedef identifies derived classes as being blocks +		typedef void baseblock_base_class_t; +		LOG_CLASS(BaseBlock); +		friend class Param; + +		BaseBlock(); +		virtual ~BaseBlock(); +		bool submitValue(const Parser::name_stack_t& name_stack, Parser& p, bool silent=false); + +		param_handle_t getHandleFromParam(const Param* param) const; +		bool validateBlock(bool silent = false) const; + +		Param* getParamFromHandle(const param_handle_t param_handle) +		{ +			if (param_handle == 0) return NULL; +			U8* baseblock_address = reinterpret_cast<U8*>(this); +			return reinterpret_cast<Param*>(baseblock_address + param_handle); +		} + +		const Param* getParamFromHandle(const param_handle_t param_handle) const +		{ +			const U8* baseblock_address = reinterpret_cast<const U8*>(this); +			return reinterpret_cast<const Param*>(baseblock_address + param_handle); +		} + +		void addSynonym(Param& param, const std::string& synonym); + +		// Blocks can override this to do custom tracking of changes +		virtual void setLastChangedParam(const Param& last_param, bool user_provided); + +		const Param* getLastChangedParam() const { return mLastChangedParam ? getParamFromHandle(mLastChangedParam) : NULL; } +		S32 getLastChangeVersion() const { return mChangeVersion; } + +		bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack); +		bool serializeBlock(Parser& p, Parser::name_stack_t name_stack = Parser::name_stack_t(), const BaseBlock* diff_block = NULL) const; +		virtual bool inspectBlock(Parser& p, Parser::name_stack_t name_stack = Parser::name_stack_t()) const; + +		const BlockDescriptor& getBlockDescriptor() const { return *mBlockDescriptor; } +		BlockDescriptor& getBlockDescriptor() { return *mBlockDescriptor; } + +		// take all provided params from other and apply to self +		bool overwriteFrom(const BaseBlock& other) +		{ +			return false; +		} + +		// take all provided params that are not already provided, and apply to self +		bool fillFrom(const BaseBlock& other) +		{ +			return false; +		} + +		static void addParam(BlockDescriptor& block_data, const ParamDescriptor& param, const char* name); +	protected: +		void init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size); + + +		// take all provided params from other and apply to self +		bool overwriteFromImpl(BlockDescriptor& block_data, const BaseBlock& other); + +		// take all provided params that are not already provided, and apply to self +		bool fillFromImpl(BlockDescriptor& block_data, const BaseBlock& other); + +		// can be updated in getters +		mutable param_handle_t	mLastChangedParam; +		mutable S32				mChangeVersion; + +		BlockDescriptor*		mBlockDescriptor;	// most derived block descriptor + +		static BlockDescriptor sBlockDescriptor; + +	private: +		const std::string& getParamName(const BlockDescriptor& block_data, const Param* paramp) const; +		ParamDescriptor* findParamDescriptor(param_handle_t handle); +	}; + + +	template<typename T> +	struct ParamIterator +	{ +		typedef typename std::vector<T>::const_iterator		const_iterator; +		typedef typename std::vector<T>::iterator			iterator; +	}; + +	// these templates allow us to distinguish between template parameters +	// that derive from BaseBlock and those that don't +	// this is supposedly faster than boost::is_convertible and the ilk +	template<typename T, typename Void = void> +	struct is_BaseBlock +	: boost::false_type +	{}; + +	template<typename T> +	struct is_BaseBlock<T, typename T::baseblock_base_class_t> +	:	boost::true_type +	{}; + +	// specialize for custom parsing/decomposition of specific classes +	// e.g. TypedParam<LLRect> has left, top, right, bottom, etc... +	template<typename	T, +			typename	NAME_VALUE_LOOKUP = TypeValues<T>, +			bool		HAS_MULTIPLE_VALUES = false, +			typename	VALUE_IS_BLOCK = typename is_BaseBlock<T>::type> +	class TypedParam  +	:	public Param +	{ +	public: +		typedef const T&																	value_const_ref_t; +		typedef value_const_ref_t															value_assignment_t; +		typedef typename NAME_VALUE_LOOKUP::KeyCache										key_cache_t; +		typedef	TypedParam<T, NAME_VALUE_LOOKUP, HAS_MULTIPLE_VALUES, VALUE_IS_BLOCK>		self_t; + +		TypedParam(BlockDescriptor& block_descriptor, const char* name, value_assignment_t value, ParamDescriptor::validation_func_t validate_func, S32 min_count, S32 max_count)  +		:	Param(block_descriptor.mCurrentBlockPtr) +		{ +			if (block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING) +			{ +				ParamDescriptor param_descriptor(block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), +												&mergeWith, +												&deserializeParam, +												&serializeParam, +												validate_func, +												&inspectParam, +												min_count, max_count); +				BaseBlock::addParam(block_descriptor, param_descriptor, name); +			} + +			mData.mValue = value; +		}  + +		bool isProvided() const { return Param::getProvided(); } + +		static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack, S32 generation)  +		{  +			self_t& typed_param = static_cast<self_t&>(param); +			// no further names in stack, attempt to parse value now +			if (name_stack.empty()) +			{ +				if (parser.readValue<T>(typed_param.mData.mValue)) +				{ +					typed_param.setProvided(true); +					typed_param.enclosingBlock().setLastChangedParam(param, true); +					return true; +				} +				 +				// try to parse a known named value +				if(!NAME_VALUE_LOOKUP::empty()) +				{ +					// try to parse a known named value +					std::string name; +					if (parser.readValue<std::string>(name)) +					{ +						// try to parse a per type named value +						if (NAME_VALUE_LOOKUP::get(name, typed_param.mData.mValue)) +						{ +							typed_param.mData.setKey(name); +							typed_param.setProvided(true); +							typed_param.enclosingBlock().setLastChangedParam(param, true); +							return true; +						} + +					} +				} +			} +			return false; +		} + +		static void serializeParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, const Param* diff_param) +		{ +			const self_t& typed_param = static_cast<const self_t&>(param); +			if (!typed_param.isProvided()) return; + +			if (!name_stack.empty()) +			{ +				name_stack.back().second = parser.newParseGeneration(); +			} + +			std::string key = typed_param.mData.getKey(); + +			// first try to write out name of name/value pair + +			if (!key.empty()) +			{ +				if (!diff_param || !ParamCompare<std::string>::equals(static_cast<const self_t*>(diff_param)->mData.getKey(), key)) +				{ +					if (!parser.writeValue<std::string>(key, name_stack)) +					{ +						return; +					} +				} +			} +			// then try to serialize value directly +			else if (!diff_param || !ParamCompare<T>::equals(typed_param.get(), static_cast<const self_t*>(diff_param)->get()))					{ +				if (!parser.writeValue<T>(typed_param.mData.mValue, name_stack))  +				{ +					return; +				} +			} +		} + +		static void inspectParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, S32 min_count, S32 max_count) +		{ +			// tell parser about our actual type +			parser.inspectValue<T>(name_stack, min_count, max_count, NULL); +			// then tell it about string-based alternatives ("red", "blue", etc. for LLColor4) +			if (NAME_VALUE_LOOKUP::getPossibleValues()) +			{ +				parser.inspectValue<std::string>(name_stack, min_count, max_count, NAME_VALUE_LOOKUP::getPossibleValues()); +			} +		} + +		void set(value_assignment_t val, bool flag_as_provided = true) +		{ +			mData.mValue = val; +			mData.clearKey(); +			setProvided(flag_as_provided); +			Param::enclosingBlock().setLastChangedParam(*this, flag_as_provided); +		} + +		void setIfNotProvided(value_assignment_t val, bool flag_as_provided = true) +		{ +			if (!isProvided()) +			{ +				set(val, flag_as_provided); +			} +		} + +		// implicit conversion +		operator value_assignment_t() const { return get(); }  +		// explicit conversion +		value_assignment_t operator()() const { return get(); }  + +	protected: +		value_assignment_t get() const +		{ +			return mData.mValue; +		} + +		static bool mergeWith(Param& dst, const Param& src, bool overwrite) +		{ +			const self_t& src_typed_param = static_cast<const self_t&>(src); +			self_t& dst_typed_param = static_cast<self_t&>(dst); +			if (src_typed_param.isProvided() +				&& (overwrite || !dst_typed_param.isProvided())) +			{ +				dst_typed_param.mData.clearKey(); +				dst_typed_param = src_typed_param; +				return true; +			} +			return false; +		} + +		struct Data : public key_cache_t +		{ +			T mValue; +		}; + +		Data		mData; +	}; + +	// parameter that is a block +	template <typename T, typename NAME_VALUE_LOOKUP> +	class TypedParam<T, NAME_VALUE_LOOKUP, false, boost::true_type>  +	:	public T, +		public Param +	{ +	public: +		typedef const T																			value_const_t; +		typedef T																				value_t; +		typedef value_const_t&																	value_const_ref_t; +		typedef value_const_ref_t																value_assignment_t; +		typedef typename NAME_VALUE_LOOKUP::KeyCache											key_cache_t; +		typedef TypedParam<T, NAME_VALUE_LOOKUP, false, boost::true_type>						self_t; + +		TypedParam(BlockDescriptor& block_descriptor, const char* name, value_assignment_t value, ParamDescriptor::validation_func_t validate_func, S32 min_count, S32 max_count) +		:	Param(block_descriptor.mCurrentBlockPtr), +			T(value) +		{ +			if (block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING) +			{ +				ParamDescriptor param_descriptor(block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), +												&mergeWith, +												&deserializeParam, +												&serializeParam, +												validate_func,  +												&inspectParam, +												min_count, max_count); +				BaseBlock::addParam(block_descriptor, param_descriptor, name); +			} +		} + +		static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack, S32 generation)  +		{  +			self_t& typed_param = static_cast<self_t&>(param); +			// attempt to parse block... +			if(typed_param.deserializeBlock(parser, name_stack)) +			{ +				typed_param.enclosingBlock().setLastChangedParam(param, true); +				return true; +			} + +			if(!NAME_VALUE_LOOKUP::empty()) +			{ +				// try to parse a known named value +				std::string name; +				if (parser.readValue<std::string>(name)) +				{ +					// try to parse a per type named value +					if (NAME_VALUE_LOOKUP::get(name, typed_param)) +					{ +						typed_param.enclosingBlock().setLastChangedParam(param, true); +						typed_param.mData.setKey(name); +						typed_param.mData.mKeyVersion = typed_param.getLastChangeVersion(); +						return true; +					} + +				} +			} +			return false; +		} + +		static void serializeParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, const Param* diff_param) +		{ +			const self_t& typed_param = static_cast<const self_t&>(param); +			if (!name_stack.empty()) +			{ +				name_stack.back().second = parser.newParseGeneration(); +			} + +			std::string key = typed_param.mData.getKey(); +			if (!key.empty() && typed_param.mData.mKeyVersion == typed_param.getLastChangeVersion()) +			{ +				if (!parser.writeValue<std::string>(key, name_stack)) +				{ +					return; +				} +			} +			else +			{ +				typed_param.serializeBlock(parser, name_stack, static_cast<const self_t*>(diff_param)); +			} +		} + +		static void inspectParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, S32 min_count, S32 max_count) +		{ +			// I am a param that is also a block, so just recurse into my contents +			const self_t& typed_param = static_cast<const self_t&>(param); +			typed_param.inspectBlock(parser, name_stack); +		} + +		// a param-that-is-a-block is provided when the user has set one of its child params +		// *and* the block as a whole validates +		bool isProvided() const  +		{  +			// only validate block when it hasn't already passed validation and user has supplied *some* value +			if (Param::getProvided() && mData.mValidatedVersion < T::getLastChangeVersion()) +			{ +				// a sub-block is "provided" when it has been filled in enough to be valid +				mData.mValidated = T::validateBlock(true); +				mData.mValidatedVersion = T::getLastChangeVersion(); +			} +			return Param::getProvided() && mData.mValidated; +		} + +		// assign block contents to this param-that-is-a-block +		void set(value_assignment_t val, bool flag_as_provided = true) +		{ +			value_t::operator=(val); +			mData.clearKey(); +			// force revalidation of block by clearing known provided version +			// next call to isProvided() will update provision status based on validity +			mData.mValidatedVersion = 0; +			setProvided(flag_as_provided); +			Param::enclosingBlock().setLastChangedParam(*this, flag_as_provided); +		} + +		void setIfNotProvided(value_assignment_t val, bool flag_as_provided = true) +		{ +			if (!isProvided()) +			{ +				set(val, flag_as_provided); +			} +		} + +		// propagate changed status up to enclosing block +		/*virtual*/ void setLastChangedParam(const Param& last_param, bool user_provided) +		{  +			T::setLastChangedParam(last_param, user_provided); +			Param::enclosingBlock().setLastChangedParam(*this, user_provided); +			if (user_provided) +			{ +				// a child param has been explicitly changed +				// so *some* aspect of this block is now provided +				setProvided(true); +			} +		} + +		// implicit conversion +		operator value_assignment_t() const { return get(); }  +		// explicit conversion +		value_assignment_t operator()() const { return get(); }  + +	protected: +		value_assignment_t get() const +		{ +			return *this; +		} + +		static bool mergeWith(Param& dst, const Param& src, bool overwrite) +		{ +			const self_t& src_typed_param = static_cast<const self_t&>(src); +			self_t& dst_typed_param = static_cast<self_t&>(dst); +			if (overwrite) +			{ +				if (dst_typed_param.T::overwriteFrom(src_typed_param)) +				{ +					dst_typed_param.mData.clearKey(); +					return true; +				} +			} +			else +			{ +				if (dst_typed_param.T::fillFrom(src_typed_param)) +				{			 +					dst_typed_param.mData.clearKey(); +					return true; +				} +			} +			return false; +		} + +		struct Data : public key_cache_t +		{ +			S32 			mKeyVersion; +			mutable S32 	mValidatedVersion; +			mutable bool 	mValidated; // lazy validation flag + +			Data()  +			:	mKeyVersion(0), +				mValidatedVersion(0), +				mValidated(false) +			{} +		}; +		Data	mData; +	}; + +	// container of non-block parameters +	template <typename VALUE_TYPE, typename NAME_VALUE_LOOKUP> +	class TypedParam<VALUE_TYPE, NAME_VALUE_LOOKUP, true, boost::false_type>  +	:	public Param +	{ +	public: +		typedef TypedParam<VALUE_TYPE, NAME_VALUE_LOOKUP, true, boost::false_type>				self_t; +		typedef typename std::vector<VALUE_TYPE>												container_t; +		typedef const container_t&																value_assignment_t; + +		typedef VALUE_TYPE																		value_t; +		typedef value_t&																		value_ref_t; +		typedef const value_t&																	value_const_ref_t; +		 +		typedef typename NAME_VALUE_LOOKUP::KeyCache											key_cache_t; + +		TypedParam(BlockDescriptor& block_descriptor, const char* name, value_assignment_t value, ParamDescriptor::validation_func_t validate_func, S32 min_count, S32 max_count)  +		:	Param(block_descriptor.mCurrentBlockPtr), +			mValues(value) +		{ +			mCachedKeys.resize(mValues.size()); +			if (block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING) +			{ +				ParamDescriptor param_descriptor(block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), +												&mergeWith, +												&deserializeParam, +												&serializeParam, +												validate_func, +												&inspectParam, +												min_count, max_count); +				BaseBlock::addParam(block_descriptor, param_descriptor, name); +			} +		}  + +		bool isProvided() const { return Param::getProvided(); } + +		static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack, S32 generation)  +		{  +			self_t& typed_param = static_cast<self_t&>(param); +			value_t value; +			// no further names in stack, attempt to parse value now +			if (name_stack.empty()) +			{ +				// attempt to read value directly +				if (parser.readValue<value_t>(value)) +				{ +					typed_param.mValues.push_back(value); +					// save an empty name/value key as a placeholder +					typed_param.mCachedKeys.push_back(key_cache_t()); +					typed_param.enclosingBlock().setLastChangedParam(param, true); +					typed_param.setProvided(true); +					return true; +				} +				 +				// try to parse a known named value +				if(!NAME_VALUE_LOOKUP::empty()) +				{ +					// try to parse a known named value +					std::string name; +					if (parser.readValue<std::string>(name)) +					{ +						// try to parse a per type named value +						if (NAME_VALUE_LOOKUP::get(name, typed_param.mValues)) +						{ +							typed_param.mValues.push_back(value); +							typed_param.mCachedKeys.push_back(key_cache_t()); +							typed_param.mCachedKeys.back().setKey(name); +							typed_param.enclosingBlock().setLastChangedParam(param, true); +							typed_param.setProvided(true); +							return true; +						} + +					} +				} +			} +			return false; +		} + +		static void serializeParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, const Param* diff_param) +		{ +			const self_t& typed_param = static_cast<const self_t&>(param); +			if (!typed_param.isProvided() || name_stack.empty()) return; + +			typename container_t::const_iterator it = typed_param.mValues.begin(); +			for (typename std::vector<key_cache_t>::const_iterator key_it = typed_param.mCachedKeys.begin(); +				it != typed_param.mValues.end(); +				++key_it, ++it) +			{ +				std::string key = key_it->get(); +				name_stack.back().second = parser.newParseGeneration(); + +				if(!key.empty()) +				{ +					if(!parser.writeValue<std::string>(key, name_stack)) +					{ +						return; +					} +				} +				// not parse via name values, write out value directly +				else if (!parser.writeValue<VALUE_TYPE>(*it, name_stack)) +				{ +					return; +				} +			} +		} + +		static void inspectParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, S32 min_count, S32 max_count) +		{ +			parser.inspectValue<VALUE_TYPE>(name_stack, min_count, max_count, NULL); +			if (NAME_VALUE_LOOKUP::getPossibleValues()) +			{ +				parser.inspectValue<std::string>(name_stack, min_count, max_count, NAME_VALUE_LOOKUP::getPossibleValues()); +			} +		} + +		void set(value_assignment_t val, bool flag_as_provided = true) +		{ +			mValues = val; +			mCachedKeys.clear(); +			mCachedKeys.resize(mValues.size()); +			setProvided(flag_as_provided); +			Param::enclosingBlock().setLastChangedParam(*this, flag_as_provided); +		} + + +		void setIfNotProvided(value_assignment_t val, bool flag_as_provided = true) +		{ +			if (!isProvided()) +			{ +				set(val, flag_as_provided); +			} +		} + +		value_ref_t add() +		{ +			mValues.push_back(value_t()); +			mCachedKeys.push_back(key_cache_t()); +			setProvided(true); +			return mValues.back(); +		} + +		void add(value_const_ref_t item) +		{ +			mValues.push_back(item); +			mCachedKeys.push_back(key_cache_t()); +			setProvided(true); +		} + +		// implicit conversion +		operator value_assignment_t() const { return self_t::get(); }  +		// explicit conversion +		value_assignment_t operator()() const { return get(); }  + +		bool hasNValidElements(S32 n) const +		{ +			return mValues.size() >= n; +		} + +	protected: +		value_assignment_t get() const +		{ +			return mValues; +		} + +		static bool mergeWith(Param& dst, const Param& src, bool overwrite) +		{ +			const self_t& src_typed_param = static_cast<const self_t&>(src); +			self_t& dst_typed_param = static_cast<self_t&>(dst); + +			if (src_typed_param.isProvided() +				&& (overwrite || !isProvided())) +			{ +				dst_typed_param = src_typed_param; +				return true; +			} +			return false; +		} + +		container_t		mValues; +		std::vector<key_cache_t>	mCachedKeys; +	}; + +	// container of block parameters +	template <typename VALUE_TYPE, typename NAME_VALUE_LOOKUP> +	class TypedParam<VALUE_TYPE, NAME_VALUE_LOOKUP, true, boost::true_type>  +	:	public Param +	{ +	public: +		typedef TypedParam<VALUE_TYPE, NAME_VALUE_LOOKUP, true, boost::true_type>				self_t; +		typedef typename std::vector<VALUE_TYPE>												container_t; +		typedef const container_t&																value_assignment_t; + +		typedef VALUE_TYPE																		value_t; +		typedef value_t&																		value_ref_t; +		typedef const value_t&																	value_const_ref_t; + +		typedef typename NAME_VALUE_LOOKUP::KeyCache											key_cache_t; + +		TypedParam(BlockDescriptor& block_descriptor, const char* name, value_assignment_t value, ParamDescriptor::validation_func_t validate_func, S32 min_count, S32 max_count)  +		:	Param(block_descriptor.mCurrentBlockPtr), +			mValues(value), +			mLastParamGeneration(0) +		{ +			mCachedKeys.resize(mValues.size()); +			if (block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING) +			{ +				ParamDescriptor param_descriptor(block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), +												&mergeWith, +												&deserializeParam, +												&serializeParam, +												validate_func, +												&inspectParam, +												min_count, max_count); +				BaseBlock::addParam(block_descriptor, param_descriptor, name); +			} +		}  + +		bool isProvided() const { return Param::getProvided(); } + +		value_ref_t operator[](S32 index) { return mValues[index]; } +		value_const_ref_t operator[](S32 index) const { return mValues[index]; } + +		static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack, S32 generation)  +		{  +			self_t& typed_param = static_cast<self_t&>(param); +			if (generation != typed_param.mLastParamGeneration || typed_param.mValues.empty()) +			{ +				typed_param.mValues.push_back(value_t()); +				typed_param.mCachedKeys.push_back(Data()); +				typed_param.enclosingBlock().setLastChangedParam(param, true); +				typed_param.mLastParamGeneration = generation; +			} + +			value_t& value = typed_param.mValues.back(); + +			// attempt to parse block... +			if(value.deserializeBlock(parser, name_stack)) +			{ +				typed_param.setProvided(true); +				return true; +			} + +			if(!NAME_VALUE_LOOKUP::empty()) +			{ +				// try to parse a known named value +				std::string name; +				if (parser.readValue<std::string>(name)) +				{ +					// try to parse a per type named value +					if (NAME_VALUE_LOOKUP::get(name, value)) +					{ +						typed_param.mCachedKeys.back().setKey(name); +						typed_param.mCachedKeys.back().mKeyVersion = value.getLastChangeVersion(); +						typed_param.enclosingBlock().setLastChangedParam(param, true); +						typed_param.setProvided(true); +						return true; +					} + +				} +			} + +			return false; +		} + +		static void serializeParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, const Param* diff_param) +		{ +			const self_t& typed_param = static_cast<const self_t&>(param); +			if (!typed_param.isProvided() || name_stack.empty()) return; + +			typename container_t::const_iterator it = typed_param.mValues.begin(); +			for (typename std::vector<Data>::const_iterator key_it = typed_param.mCachedKeys.begin(); +				it != typed_param.mValues.end(); +				++key_it, ++it) +			{ +				name_stack.back().second = parser.newParseGeneration(); + +				std::string key = key_it->getKey(); +				if (!key.empty() && key_it->mKeyVersion == it->getLastChangeVersion()) +				{ +					if(!parser.writeValue<std::string>(key, name_stack)) +					{ +						return; +					} +				} +				// Not parsed via named values, write out value directly +				// NOTE: currently we don't worry about removing default values in Multiple +				else if (!it->serializeBlock(parser, name_stack, NULL)) +				{ +					return; +				} +			} +		} + +		static void inspectParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, S32 min_count, S32 max_count) +		{ +			// I am a vector of blocks, so describe my contents recursively +			value_t().inspectBlock(parser, name_stack); +		} + +		void set(value_assignment_t val, bool flag_as_provided = true) +		{ +			mValues = val; +			mCachedKeys.clear(); +			mCachedKeys.resize(mValues.size()); +			setProvided(flag_as_provided); +			Param::enclosingBlock().setLastChangedParam(*this, flag_as_provided); +		} + +		void setIfNotProvided(value_assignment_t val, bool flag_as_provided = true) +		{ +			if (!isProvided()) +			{ +				set(val, flag_as_provided); +			} +		} + +		value_ref_t add() +		{ +			mValues.push_back(value_t()); +			mCachedKeys.push_back(Data()); +			setProvided(true); +			return mValues.back(); +		} + +		void add(value_const_ref_t item) +		{ +			mValues.push_back(item); +			mCachedKeys.push_back(Data()); +			setProvided(true); +		} + +		// implicit conversion +		operator value_assignment_t() const { return self_t::get(); }  +		// explicit conversion +		value_assignment_t operator()() const { return get(); }  + +		U32 numValidElements() const +		{ +			U32 count = 0; +			for (typename container_t::const_iterator it = mValues.begin(); +				it != mValues.end(); +				++it) +			{ +				if(it->validateBlock(true)) count++; +			} +			return count; +		} + +	protected: +		value_assignment_t get() const +		{ +			return mValues; +		} + +		static bool mergeWith(Param& dst, const Param& src, bool overwrite) +		{ +			const self_t& src_typed_param = static_cast<const self_t&>(src); +			self_t& dst_typed_param = static_cast<self_t&>(dst); + +			if (src_typed_param.isProvided() +				&& (overwrite || !dst_typed_param.isProvided())) +			{ +				dst_typed_param = src_typed_param; +				return true; +			} +			return false; +		} + +		struct Data : public key_cache_t +		{ +			S32 mKeyVersion;	// version of block for which key was last valid + +			Data() : mKeyVersion(0) {} +		}; + +		container_t			mValues; +		std::vector<Data>	mCachedKeys; + +		S32			mLastParamGeneration; +	}; + +	template <typename DERIVED_BLOCK> +	class Choice : public BaseBlock +	{ +		typedef Choice<DERIVED_BLOCK> self_t; +		typedef Choice<DERIVED_BLOCK> enclosing_block_t; +		 +		LOG_CLASS(self_t); +	public: +		// take all provided params from other and apply to self +		bool overwriteFrom(const self_t& other) +		{ +			mCurChoice = other.mCurChoice; +			return BaseBlock::overwriteFromImpl(sBlockDescriptor, other); +		} + +		// take all provided params that are not already provided, and apply to self +		bool fillFrom(const self_t& other) +		{ +			return false; +		} + +		// clear out old choice when param has changed +		/*virtual*/ void setLastChangedParam(const Param& last_param, bool user_provided) +		{  +			param_handle_t changed_param_handle = BaseBlock::getHandleFromParam(&last_param); +			// if we have a new choice... +			if (changed_param_handle != mCurChoice) +			{ +				// clear provided flag on previous choice +				Param* previous_choice = BaseBlock::getParamFromHandle(mCurChoice); +				if (previous_choice)  +				{ +					previous_choice->setProvided(false); +				} +				mCurChoice = changed_param_handle; +			} +			BaseBlock::setLastChangedParam(last_param, user_provided); +		} + +	protected: +		Choice() +		:	mCurChoice(0) +		{ +			BaseBlock::init(sBlockDescriptor, BaseBlock::sBlockDescriptor, sizeof(DERIVED_BLOCK)); +		} + +		// Alternatives are mutually exclusive wrt other Alternatives in the same block.   +		// One alternative in a block will always have isChosen() == true. +		// At most one alternative in a block will have isProvided() == true. +		template <typename T, typename NAME_VALUE_LOOKUP = TypeValues<T> > +		class Alternative : public TypedParam<T, NAME_VALUE_LOOKUP, false> +		{ +		public: +			friend class Choice<DERIVED_BLOCK>; + +			typedef Alternative<T, NAME_VALUE_LOOKUP>						self_t; +			typedef TypedParam<T, NAME_VALUE_LOOKUP, false>				super_t; +			typedef typename super_t::value_assignment_t				value_assignment_t; + +			explicit Alternative(const char* name, value_assignment_t val = DefaultInitializer<T>::get()) +			:	super_t(DERIVED_BLOCK::sBlockDescriptor, name, val, NULL, 0, 1), +				mOriginalValue(val) +			{ +				// assign initial choice to first declared option +				DERIVED_BLOCK* blockp = ((DERIVED_BLOCK*)DERIVED_BLOCK::sBlockDescriptor.mCurrentBlockPtr); +				if (DERIVED_BLOCK::sBlockDescriptor.mInitializationState == BlockDescriptor::INITIALIZING +					&& blockp->mCurChoice == 0) +				{ +					blockp->mCurChoice = Param::enclosingBlock().getHandleFromParam(this); +				} +			} + +			Alternative& operator=(value_assignment_t val) +			{ +				super_t::set(val); +				return *this; +			} + +			void operator()(typename super_t::value_assignment_t val)  +			{  +				super_t::set(val); +			} + +			operator value_assignment_t() const  +			{  +				if (static_cast<enclosing_block_t&>(Param::enclosingBlock()).getCurrentChoice() == this) +				{ +					return super_t::get();  +				} +				return mOriginalValue; +			}  + +			value_assignment_t operator()() const  +			{  +				if (static_cast<enclosing_block_t&>(Param::enclosingBlock()).getCurrentChoice() == this) +				{ +					return super_t::get();  +				} +				return mOriginalValue; +			}  + +			bool isChosen() const +			{ +				return static_cast<enclosing_block_t&>(Param::enclosingBlock()).getCurrentChoice() == this; +			} +		 +		private: +			T			mOriginalValue; +		}; + +	protected: +		static BlockDescriptor sBlockDescriptor; + +	private: +		param_handle_t	mCurChoice; + +		const Param* getCurrentChoice() const +		{ +			return BaseBlock::getParamFromHandle(mCurChoice); +		} +	}; + +	template<typename DERIVED_BLOCK>  +		BlockDescriptor +			Choice<DERIVED_BLOCK>::sBlockDescriptor; + +	//struct CardinalityConstraint +	//{ +	//	virtual std::pair<S32, S32> getRange() = 0; +	//}; + +	struct AnyAmount +	{ +		static U32 minCount() { return 0; } +		static U32 maxCount() { return U32_MAX; } +	}; + +	template<U32 MIN_AMOUNT> +	struct AtLeast +	{ +		static U32 minCount() { return MIN_AMOUNT; } +		static U32 maxCount() { return U32_MAX; } +	}; + +	template<U32 MAX_AMOUNT> +	struct AtMost +	{ +		static U32 minCount() { return 0; } +		static U32 maxCount() { return MAX_AMOUNT; } +	}; + +	template<U32 MIN_AMOUNT, U32 MAX_AMOUNT> +	struct Between +	{ +		static U32 minCount() { return MIN_AMOUNT; } +		static U32 maxCount() { return MAX_AMOUNT; } +	}; + +	template<U32 EXACT_COUNT> +	struct Exactly +	{ +		static U32 minCount() { return EXACT_COUNT; } +		static U32 maxCount() { return EXACT_COUNT; } +	}; + +	template <typename DERIVED_BLOCK, typename BASE_BLOCK = BaseBlock> +	class Block  +	:	public BASE_BLOCK +	{ +		typedef Block<DERIVED_BLOCK, BASE_BLOCK> self_t; +		typedef Block<DERIVED_BLOCK, BASE_BLOCK> block_t; + +	public: +		typedef BASE_BLOCK base_block_t; + +		// take all provided params from other and apply to self +		bool overwriteFrom(const self_t& other) +		{ +			return BaseBlock::overwriteFromImpl(sBlockDescriptor, other); +		} + +		// take all provided params that are not already provided, and apply to self +		bool fillFrom(const self_t& other) +		{ +			return BaseBlock::fillFromImpl(sBlockDescriptor, other); +		} +	protected: +		Block() +		{ +			//#pragma message("Parsing LLInitParam::Block") +			BaseBlock::init(sBlockDescriptor, BASE_BLOCK::sBlockDescriptor, sizeof(DERIVED_BLOCK)); +		} + +		// +		// Nested classes for declaring parameters +		// +		template <typename T, typename NAME_VALUE_LOOKUP = TypeValues<T> > +		class Optional : public TypedParam<T, NAME_VALUE_LOOKUP, false> +		{ +		public: +			typedef TypedParam<T, NAME_VALUE_LOOKUP, false>				super_t; +			typedef typename super_t::value_assignment_t				value_assignment_t; + +			explicit Optional(const char* name = "", value_assignment_t val = DefaultInitializer<T>::get()) +			:	super_t(DERIVED_BLOCK::sBlockDescriptor, name, val, NULL, 0, 1) +			{ +				//#pragma message("Parsing LLInitParam::Block::Optional") +			} + +			Optional& operator=(value_assignment_t val) +			{ +				set(val); +				return *this; +			} + +			DERIVED_BLOCK& operator()(typename super_t::value_assignment_t val) +			{ +				super_t::set(val); +				return static_cast<DERIVED_BLOCK&>(Param::enclosingBlock()); +			} +			using super_t::operator(); +		}; + +		template <typename T, typename NAME_VALUE_LOOKUP = TypeValues<T> > +		class Mandatory : public TypedParam<T, NAME_VALUE_LOOKUP, false> +		{ +		public: +			typedef TypedParam<T, NAME_VALUE_LOOKUP, false>		super_t; +			typedef Mandatory<T, NAME_VALUE_LOOKUP>				self_t; +			typedef typename super_t::value_assignment_t		value_assignment_t; + +			// mandatory parameters require a name to be parseable +			explicit Mandatory(const char* name = "", value_assignment_t val = DefaultInitializer<T>::get()) +			:	super_t(DERIVED_BLOCK::sBlockDescriptor, name, val, &validate, 1, 1) +			{} + +			Mandatory& operator=(value_assignment_t val) +			{ +				set(val); +				return *this; +			} + +			DERIVED_BLOCK& operator()(typename super_t::value_assignment_t val) +			{ +				super_t::set(val); +				return static_cast<DERIVED_BLOCK&>(Param::enclosingBlock()); +			} +			using super_t::operator(); + +			static bool validate(const Param* p) +			{ +				// valid only if provided +				return static_cast<const self_t*>(p)->isProvided(); +			} + +		}; + +		template <typename T, typename RANGE = AnyAmount, typename NAME_VALUE_LOOKUP = TypeValues<T> > +		class Multiple : public TypedParam<T, NAME_VALUE_LOOKUP, true> +		{ +		public: +			typedef TypedParam<T, NAME_VALUE_LOOKUP, true>	super_t; +			typedef Multiple<T, RANGE, NAME_VALUE_LOOKUP>	self_t; +			typedef typename super_t::container_t			container_t; +			typedef typename super_t::value_assignment_t	value_assignment_t; +			typedef typename container_t::iterator			iterator; +			typedef typename container_t::const_iterator	const_iterator; + +			explicit Multiple(const char* name = "", value_assignment_t val = DefaultInitializer<container_t>::get()) +			:	super_t(DERIVED_BLOCK::sBlockDescriptor, name, val, &validate, RANGE::minCount(), RANGE::maxCount()) +			{} + +			using super_t::operator(); + +			Multiple& operator=(value_assignment_t val) +			{ +				set(val); +				return *this; +			} +			 +			DERIVED_BLOCK& operator()(typename super_t::value_assignment_t val) +			{ +				super_t::set(val); +				return static_cast<DERIVED_BLOCK&>(Param::enclosingBlock()); +			} + +			static bool validate(const Param* paramp)  +			{ +				U32 num_valid = ((super_t*)paramp)->numValidElements(); +				return RANGE::minCount() <= num_valid && num_valid <= RANGE::maxCount(); +			} +		}; + +		class Deprecated : public Param +		{ +		public: +			explicit Deprecated(const char* name) +			:	Param(DERIVED_BLOCK::sBlockDescriptor.mCurrentBlockPtr) +			{ +				BlockDescriptor& block_descriptor = DERIVED_BLOCK::sBlockDescriptor; +				if (block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING) +				{ +					ParamDescriptor param_descriptor(block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), +													NULL, +													&deserializeParam, +													NULL, +													NULL, +													NULL,  +													0, S32_MAX); +					BaseBlock::addParam(block_descriptor, param_descriptor, name); +				} +			} +			 +			static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack, S32 generation) +			{ +				if (name_stack.empty()) +				{ +					//std::string message = llformat("Deprecated value %s ignored", getName().c_str()); +					//parser.parserWarning(message); +					return true; +				} + +				return false; +			} +		}; + +		typedef Deprecated Ignored; + +	protected: +		static BlockDescriptor sBlockDescriptor; +	}; +	 +	template<typename DERIVED_BLOCK, typename BASE_BLOCK>  +		BlockDescriptor +			Block<DERIVED_BLOCK, BASE_BLOCK>::sBlockDescriptor; + +	template<typename T, typename DERIVED = TypedParam<T> > +	class BlockValue +	:	public Block<TypedParam<T, TypeValues<T>, false> >, +		public Param +	{ +	public: +		typedef BlockValue<T>																self_t; +		typedef Block<TypedParam<T, TypeValues<T>, false> >									block_t; +		typedef const T&															value_const_ref_t; +		typedef value_const_ref_t															value_assignment_t; +		typedef typename TypeValues<T>::KeyCache											key_cache_t; + +		BlockValue(BlockDescriptor& block_descriptor, const char* name, value_assignment_t value, ParamDescriptor::validation_func_t validate_func, S32 min_count, S32 max_count) +		:	Param(block_descriptor.mCurrentBlockPtr), +			mData(value) +		{ +			if (block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING) +			{ +				ParamDescriptor param_descriptor(block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), +												&mergeWith, +												&deserializeParam, +												&serializeParam, +												validate_func, +												&inspectParam, +												min_count, max_count); +				BaseBlock::addParam(block_descriptor, param_descriptor, name); +			} +		} + +		// implicit conversion +		operator value_assignment_t() const { return get(); }  +		// explicit conversion +		value_assignment_t operator()() const { return get(); }  + +		static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack, S32 generation) +		{ +			self_t& typed_param = static_cast<self_t&>(param); +			// type to apply parse direct value T +			if (name_stack.empty()) +			{ +				if(parser.readValue<T>(typed_param.mData.mValue)) +				{ +					typed_param.enclosingBlock().setLastChangedParam(param, true); +					typed_param.setProvided(true); +					typed_param.mData.mLastParamVersion = typed_param.BaseBlock::getLastChangeVersion(); +					return true; +				} + +				if(!TypeValues<T>::empty()) +				{ +					// try to parse a known named value +					std::string name; +					if (parser.readValue<std::string>(name)) +					{ +						// try to parse a per type named value +						if (TypeValues<T>::get(name, typed_param.mData.mValue)) +						{ +							typed_param.mData.setKey(name); +							typed_param.enclosingBlock().setLastChangedParam(param, true); +							typed_param.setProvided(true); +							typed_param.mData.mLastParamVersion = typed_param.BaseBlock::getLastChangeVersion(); +							return true; +						} +					} +				} +			} + +			// fall back on parsing block components for T +			// if we deserialized at least one component... +			if (typed_param.BaseBlock::deserializeBlock(parser, name_stack)) +			{ +				// ...our block is provided, and considered changed +				typed_param.enclosingBlock().setLastChangedParam(param, true); +				typed_param.setProvided(true); +				return true; +			} +			return false; +		} + +		static void serializeParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, const Param* diff_param) +		{ +			const self_t& typed_param = static_cast<const self_t&>(param); +			 +			if (!typed_param.isProvided()) return; +			 +			std::string key = typed_param.mData.getKey(); + +			// first try to write out name of name/value pair +			if (!key.empty()) +			{ +				if (!diff_param || !ParamCompare<std::string>::equals(static_cast<const self_t*>(diff_param)->mData.getKey(), key)) +				{ +					if (!parser.writeValue<std::string>(key, name_stack)) +					{ +						return; +					} +				} +			} +			// then try to serialize value directly +			else if (!diff_param || !ParamCompare<T>::equals(typed_param.get(), (static_cast<const self_t*>(diff_param))->get()))	 +            { +				 +				if (parser.writeValue<T>(typed_param.mData.mValue, name_stack))  +				{ +					return; +				} + +				//RN: *always* serialize provided components of BlockValue (don't pass diff_param on), +				// since these tend to be viewed as the constructor arguments for the value T.  It seems +				// cleaner to treat the uniqueness of a BlockValue according to the generated value, and +				// not the individual components.  This way <color red="0" green="1" blue="0"/> will not +				// be exported as <color green="1"/>, since it was probably the intent of the user to  +				// be specific about the RGB color values.  This also fixes an issue where we distinguish +				// between rect.left not being provided and rect.left being explicitly set to 0 (same as default) +				typed_param.BaseBlock::serializeBlock(parser, name_stack, NULL); +			} +		} + +		static void inspectParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, S32 min_count, S32 max_count) +		{ +			// first, inspect with actual type... +			parser.inspectValue<T>(name_stack, min_count, max_count, NULL); +			if (TypeValues<T>::getPossibleValues()) +			{ +				//...then inspect with possible string values... +				parser.inspectValue<std::string>(name_stack, min_count, max_count, TypeValues<T>::getPossibleValues()); +			} +			// then recursively inspect contents... +			const self_t& typed_param = static_cast<const self_t&>(param); +			typed_param.inspectBlock(parser, name_stack); +		} + + +		bool isProvided() const  +		{ +			// either param value provided directly or block is sufficiently filled in +			// if cached value is stale, regenerate from params +			if (Param::getProvided() && mData.mLastParamVersion < BaseBlock::getLastChangeVersion()) +			{ +				if (block_t::validateBlock(true)) +				{ +					mData.mValue = static_cast<const DERIVED*>(this)->getValueFromBlock(); +					// clear stale keyword associated with old value +					mData.clearKey(); +					mData.mLastParamVersion = BaseBlock::getLastChangeVersion(); +					return true; +				} +				else +				{ +					//block value incomplete, so not considered provided +					// will attempt to revalidate on next call to isProvided() +					return false;   +				} +			} +			// either no data provided, or we have a valid value in hand +			return Param::getProvided(); +		} + +		void set(value_assignment_t val, bool flag_as_provided = true) +		{ +			Param::enclosingBlock().setLastChangedParam(*this, flag_as_provided); +			// set param version number to be up to date, so we ignore block contents +			mData.mLastParamVersion = BaseBlock::getLastChangeVersion(); + +			mData.mValue = val; +			mData.clearKey(); +			setProvided(flag_as_provided); +		} + +		void setIfNotProvided(value_assignment_t val, bool flag_as_provided = true) +		{ +			// don't override any user provided value +			if (!isProvided()) +			{ +				set(val, flag_as_provided); +			} +		} + + 		// propagate change status up to enclosing block +		/*virtual*/ void setLastChangedParam(const Param& last_param, bool user_provided) +		{  +			BaseBlock::setLastChangedParam(last_param, user_provided); +			Param::enclosingBlock().setLastChangedParam(*this, user_provided); +			if (user_provided) +			{ +				setProvided(true);  // some component provided +			} +		} + +	protected: +		value_assignment_t get() const +		{ +			if (mData.mLastParamVersion < BaseBlock::getLastChangeVersion() && block_t::validateBlock(true)) +			{ +				mData.mValue = static_cast<const DERIVED*>(this)->getValueFromBlock(); +				mData.clearKey(); +				mData.mLastParamVersion = BaseBlock::getLastChangeVersion(); +			} + +			return mData.mValue; +		} + +		// mutable to allow lazy updates on get +		struct Data : public key_cache_t +		{ +			Data(const T& value)  +			:	mValue(value), +				mLastParamVersion(0) +			{} + +			T		mValue; +			S32		mLastParamVersion; +		}; + +		mutable Data		mData; + +	private: +		static bool mergeWith(Param& dst, const Param& src, bool overwrite) +		{ +			const self_t& src_param = static_cast<const self_t&>(src); +			self_t& dst_typed_param = static_cast<self_t&>(dst); + +			if (src_param.isProvided() +				&& (overwrite || !dst_typed_param.isProvided())) +			{ +				// assign individual parameters +				if (overwrite) +				{ +					dst_typed_param.BaseBlock::overwriteFromImpl(block_t::sBlockDescriptor, src_param); +				} +				else +				{ +					dst_typed_param.BaseBlock::fillFromImpl(block_t::sBlockDescriptor, src_param); +				} +				// then copy actual value +				dst_typed_param.mData.mValue = src_param.get(); +				dst_typed_param.mData.clearKey(); +				dst_typed_param.setProvided(true); +				return true; +			} +			return false; +		} +	}; + +    template<> +	bool ParamCompare<boost::function<void (const std::string &,void *)> >::equals( +		const boost::function<void (const std::string &,void *)> &a, +		const boost::function<void (const std::string &,void *)> &b); +	 +	template<> +	bool ParamCompare<boost::function<void (const LLSD &,const LLSD &)> >::equals( +		const boost::function<void (const LLSD &,const LLSD &)> &a, +		const boost::function<void (const LLSD &,const LLSD &)> &b); +	 +	template<> +	bool ParamCompare<LLSD>::equals(const LLSD &a, const LLSD &b); +} + +#endif // LL_LLPARAM_H diff --git a/indra/llxuixml/llregistry.h b/indra/llxuixml/llregistry.h new file mode 100644 index 0000000000..2c04d8c419 --- /dev/null +++ b/indra/llxuixml/llregistry.h @@ -0,0 +1,347 @@ +/**  + * @file llregistry.h + * @brief template classes for registering name, value pairs in nested scopes, statically, etc. + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + *  + * Copyright (c) 2001-2009, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLREGISTRY_H +#define LL_LLREGISTRY_H + +#include <list> + +#include <boost/type_traits.hpp> +#include "llsingleton.h" + +template <typename T> +class LLRegistryDefaultComparator +{ +	bool operator()(const T& lhs, const T& rhs) { return lhs < rhs; } +}; + +template <typename KEY, typename VALUE, typename COMPARATOR = LLRegistryDefaultComparator<KEY> > +class LLRegistry +{ +public: +	typedef LLRegistry<KEY, VALUE, COMPARATOR>											registry_t; +	typedef typename boost::add_reference<typename boost::add_const<KEY>::type>::type	ref_const_key_t; +	typedef typename boost::add_reference<typename boost::add_const<VALUE>::type>::type	ref_const_value_t; +	typedef typename boost::add_reference<VALUE>::type									ref_value_t; +	typedef typename boost::add_pointer<typename boost::add_const<VALUE>::type>::type	ptr_const_value_t; +	typedef typename boost::add_pointer<VALUE>::type									ptr_value_t; + +	class Registrar +	{ +		friend class LLRegistry<KEY, VALUE, COMPARATOR>; +	public: +		typedef typename std::map<KEY, VALUE> registry_map_t; + +		bool add(ref_const_key_t key, ref_const_value_t value) +		{ +			if (mMap.insert(std::make_pair(key, value)).second == false) +			{ +				llwarns << "Tried to register " << key << " but it was already registered!" << llendl; +				return false; +			} +			return true; +		} + +		void remove(ref_const_key_t key) +		{ +			mMap.erase(key); +		} + +		typename registry_map_t::const_iterator beginItems() const +		{ +			return mMap.begin(); +		} + +		typename registry_map_t::const_iterator endItems() const +		{ +			return mMap.end(); +		} + +	protected: +		ptr_value_t getValue(ref_const_key_t key) +		{ +			typename registry_map_t::iterator found_it = mMap.find(key); +			if (found_it != mMap.end()) +			{ +				return &(found_it->second); +			} +			return NULL; +		} + +		ptr_const_value_t getValue(ref_const_key_t key) const +		{ +			typename registry_map_t::const_iterator found_it = mMap.find(key); +			if (found_it != mMap.end()) +			{ +				return &(found_it->second); +			} +			return NULL; +		} + +		// if the registry is used to store pointers, and null values are valid entries +		// then use this function to check the existence of an entry +		bool exists(ref_const_key_t key) const +		{ +			return mMap.find(key) != mMap.end(); +		} + +		bool empty() const +		{ +			return mMap.empty(); +		} + +	protected: +		// use currentRegistrar() or defaultRegistrar() +		Registrar() {} +		~Registrar() {} + +	private: +		registry_map_t											mMap; +	}; +	 +	typedef typename std::list<Registrar*> scope_list_t; +	typedef typename std::list<Registrar*>::iterator scope_list_iterator_t; +	typedef typename std::list<Registrar*>::const_iterator scope_list_const_iterator_t; +	 +	LLRegistry()  +	{} + +	~LLRegistry() {} + +	ptr_value_t getValue(ref_const_key_t key) +	{ +		for(scope_list_iterator_t it = mActiveScopes.begin(); +			it != mActiveScopes.end(); +			++it) +		{ +			ptr_value_t valuep = (*it)->getValue(key); +			if (valuep != NULL) return valuep; +		} +		return mDefaultRegistrar.getValue(key); +	} + +	ptr_const_value_t getValue(ref_const_key_t key) const +	{ +		for(scope_list_const_iterator_t it = mActiveScopes.begin(); +			it != mActiveScopes.end(); +			++it) +		{ +			ptr_value_t valuep = (*it)->getValue(key); +			if (valuep != NULL) return valuep; +		} +		return mDefaultRegistrar.getValue(key); +	} + +	bool exists(ref_const_key_t key) const +	{ +		for(scope_list_const_iterator_t it = mActiveScopes.begin(); +			it != mActiveScopes.end(); +			++it) +		{ +			if ((*it)->exists(key)) return true; +		} + +		return mDefaultRegistrar.exists(key); +	} + +	bool empty() const +	{ +		for(scope_list_const_iterator_t it = mActiveScopes.begin(); +			it != mActiveScopes.end(); +			++it) +		{ +			if (!(*it)->empty()) return false; +		} + +		return mDefaultRegistrar.empty(); +	} + + +	Registrar& defaultRegistrar() +	{ +		return mDefaultRegistrar; +	} + +	const Registrar& defaultRegistrar() const +	{ +		return mDefaultRegistrar; +	} + + +	Registrar& currentRegistrar() +	{ +		if (!mActiveScopes.empty())  +		{ +			return *mActiveScopes.front(); +		} + +		return mDefaultRegistrar; +	} + +	const Registrar& currentRegistrar() const +	{ +		if (!mActiveScopes.empty())  +		{ +			return *mActiveScopes.front(); +		} + +		return mDefaultRegistrar; +	} + + +protected: +	void addScope(Registrar* scope) +	{ +		// newer scopes go up front +		mActiveScopes.insert(mActiveScopes.begin(), scope); +	} + +	void removeScope(Registrar* scope) +	{ +		// O(N) but should be near the beggining and N should be small and this is safer than storing iterators +		scope_list_iterator_t iter = std::find(mActiveScopes.begin(), mActiveScopes.end(), scope); +		if (iter != mActiveScopes.end()) +		{ +			mActiveScopes.erase(iter); +		} +	} + +private: +	scope_list_t	mActiveScopes; +	Registrar		mDefaultRegistrar; +}; + +template <typename KEY, typename VALUE, typename DERIVED_TYPE, typename COMPARATOR = LLRegistryDefaultComparator<KEY> > +class LLRegistrySingleton +	:	public LLRegistry<KEY, VALUE, COMPARATOR>, +		public LLSingleton<DERIVED_TYPE> +{ +	friend class LLSingleton<DERIVED_TYPE>; +public: +	typedef LLRegistry<KEY, VALUE, COMPARATOR>		registry_t; +	typedef const KEY&								ref_const_key_t; +	typedef const VALUE&							ref_const_value_t; +	typedef VALUE*									ptr_value_t; +	typedef const VALUE*							ptr_const_value_t; +	typedef LLSingleton<DERIVED_TYPE>				singleton_t; + +	class ScopedRegistrar : public registry_t::Registrar +	{ +	public: +		ScopedRegistrar(bool push_scope = true)  +		{ +			if (push_scope) +			{ +				pushScope(); +			} +		} + +		~ScopedRegistrar() +		{ +			if (!singleton_t::destroyed()) +			{ +				popScope(); +			} +		} + +		void pushScope() +		{ +			singleton_t::instance().addScope(this); +		} +		 +		void popScope() +		{ +			singleton_t::instance().removeScope(this); +		} +		 +		ptr_value_t getValueFromScope(ref_const_key_t key) +		{ +			return getValue(key); +		} + +		ptr_const_value_t getValueFromScope(ref_const_key_t key) const +		{ +			return getValue(key); +		} + +	private: +		typename std::list<typename registry_t::Registrar*>::iterator	mListIt; +	}; + +	class StaticRegistrar : public registry_t::Registrar +	{ +	public: +		virtual ~StaticRegistrar() {} +		StaticRegistrar(ref_const_key_t key, ref_const_value_t value) +		{ +			singleton_t::instance().mStaticScope->add(key, value); +		} +	}; + +	// convenience functions +	typedef typename LLRegistry<KEY, VALUE, COMPARATOR>::Registrar& ref_registrar_t; +	static ref_registrar_t currentRegistrar() +	{ +		return singleton_t::instance().registry_t::currentRegistrar(); +	} + +	static ref_registrar_t defaultRegistrar() +	{ +		return singleton_t::instance().registry_t::defaultRegistrar(); +	} +	 +	static ptr_value_t getValue(ref_const_key_t key) +	{ +		return singleton_t::instance().registry_t::getValue(key); +	} + +protected: +	// DERIVED_TYPE needs to derive from LLRegistrySingleton +	LLRegistrySingleton() +		: mStaticScope(NULL) +	{} + +	virtual void initSingleton() +	{ +		mStaticScope = new ScopedRegistrar(); +	} + +	virtual ~LLRegistrySingleton()  +	{ +		delete mStaticScope; +	} + +private: +	ScopedRegistrar*	mStaticScope; +}; + +#endif diff --git a/indra/llxuixml/lltrans.cpp b/indra/llxuixml/lltrans.cpp new file mode 100644 index 0000000000..db7421575c --- /dev/null +++ b/indra/llxuixml/lltrans.cpp @@ -0,0 +1,171 @@ +/** + * @file lltrans.cpp + * @brief LLTrans implementation + * + * $LicenseInfo:firstyear=2000&license=viewergpl$ + *  + * Copyright (c) 2000-2009, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "lltrans.h" + +#include "llfasttimer.h"	// for call count statistics +#include "llxuiparser.h" + +#include <map> + +LLTrans::template_map_t LLTrans::sStringTemplates; +LLStringUtil::format_map_t LLTrans::sDefaultArgs; + +struct StringDef : public LLInitParam::Block<StringDef> +{ +	Mandatory<std::string> name; +	Mandatory<std::string> value; + +	StringDef() +	:	name("name"), +		value("value") +	{} +}; + +struct StringTable : public LLInitParam::Block<StringTable> +{ +	Multiple<StringDef> strings; +	StringTable() +	:	strings("string") +	{} +}; + +//static  +bool LLTrans::parseStrings(LLXMLNodePtr &root, const std::set<std::string>& default_args) +{ +	std::string xml_filename = "(strings file)"; +	if (!root->hasName("strings")) +	{ +		llerrs << "Invalid root node name in " << xml_filename  +			<< ": was " << root->getName() << ", expected \"strings\"" << llendl; +	} + +	StringTable string_table; +	LLXUIParser::instance().readXUI(root, string_table); + +	if (!string_table.validateBlock()) +	{ +		llerrs << "Problem reading strings: " << xml_filename << llendl; +		return false; +	} +	 +	sStringTemplates.clear(); +	sDefaultArgs.clear(); +	 +	for(LLInitParam::ParamIterator<StringDef>::const_iterator it = string_table.strings().begin(); +		it != string_table.strings().end(); +		++it) +	{ +		LLTransTemplate xml_template(it->name, it->value); +		sStringTemplates[xml_template.mName] = xml_template; +		 +		std::set<std::string>::const_iterator iter = default_args.find(xml_template.mName); +		if (iter != default_args.end()) +		{ +			std::string name = *iter; +			if (name[0] != '[') +				name = llformat("[%s]",name.c_str()); +			sDefaultArgs[name] = xml_template.mText; +		} +	} + +	return true; +} + + +//static +bool LLTrans::parseLanguageStrings(LLXMLNodePtr &root) +{ +	std::string xml_filename = "(language strings file)"; +	if (!root->hasName("strings")) +	{ +		llerrs << "Invalid root node name in " << xml_filename  +		<< ": was " << root->getName() << ", expected \"strings\"" << llendl; +	} +	 +	StringTable string_table; +	LLXUIParser::instance().readXUI(root, string_table); +	 +	if (!string_table.validateBlock()) +	{ +		llerrs << "Problem reading strings: " << xml_filename << llendl; +		return false; +	} +		 +	for(LLInitParam::ParamIterator<StringDef>::const_iterator it = string_table.strings().begin(); +		it != string_table.strings().end(); +		++it) +	{ +		// share the same map with parseStrings() so we can search the strings using the same getString() function.- angela +		LLTransTemplate xml_template(it->name, it->value); +		sStringTemplates[xml_template.mName] = xml_template; +	} +	 +	return true; +} + + + +static LLFastTimer::DeclareTimer FTM_GET_TRANS("Translate string"); + +//static  +std::string LLTrans::getString(const std::string &xml_desc, const LLStringUtil::format_map_t& msg_args) +{ +	// Don't care about time as much as call count.  Make sure we're not +	// calling LLTrans::getString() in an inner loop. JC +	LLFastTimer timer(FTM_GET_TRANS); +	 +	template_map_t::iterator iter = sStringTemplates.find(xml_desc); +	if (iter != sStringTemplates.end()) +	{ +		std::string text = iter->second.mText; +		LLStringUtil::format_map_t args = sDefaultArgs; +		args.insert(msg_args.begin(), msg_args.end()); +		LLStringUtil::format(text, args); +		 +		return text; +	} +	else +	{ +		LLSD args; +		args["STRING_NAME"] = xml_desc; +		LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; + +		//LLNotifications::instance().add("MissingString", args); // *TODO: resurrect +		//return xml_desc; + +		return "MissingString("+xml_desc+")"; +	} +} + diff --git a/indra/llxuixml/lltrans.h b/indra/llxuixml/lltrans.h new file mode 100644 index 0000000000..6423c88245 --- /dev/null +++ b/indra/llxuixml/lltrans.h @@ -0,0 +1,111 @@ +/** + * @file lltrans.h + * @brief LLTrans definition + * + * $LicenseInfo:firstyear=2000&license=viewergpl$ + *  + * Copyright (c) 2000-2009, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_TRANS_H +#define LL_TRANS_H + +#include <map> + +#include "llstring.h" +#include "llxmlnode.h" + +/** + * @brief String template loaded from strings.xml + */ +class LLTransTemplate +{ +public: +	LLTransTemplate(const std::string& name = LLStringUtil::null, const std::string& text = LLStringUtil::null) : mName(name), mText(text) {} + +	std::string mName; +	std::string mText; +}; + +/** + * @brief Localized strings class + * This class is used to retrieve translations of strings used to build larger ones, as well as + * strings with a general usage that don't belong to any specific floater. For example, + * "Owner:", "Retrieving..." used in the place of a not yet known name, etc. + */ +class LLTrans +{ +public: +	LLTrans(); + +	/** +	 * @brief Parses the xml root that holds the strings. Used once on startup +// *FIXME	 * @param xml_filename Filename to parse +	 * @param default_args Set of strings (expected to be in the file) to use as default replacement args, e.g. "SECOND_LIFE" +	 * @returns true if the file was parsed successfully, true if something went wrong +	 */ +	static bool parseStrings(LLXMLNodePtr& root, const std::set<std::string>& default_args); + +	static bool parseLanguageStrings(LLXMLNodePtr &root); + +	/** +	 * @brief Returns a translated string +	 * @param xml_desc String's description +	 * @param args A list of substrings to replace in the string +	 * @returns Translated string +	 */ +	static std::string getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args); + +	/** +	 * @brief Returns a translated string +	 * @param xml_desc String's description +	 * @returns Translated string +	 */ +	static std::string getString(const std::string &xml_desc) +	{ +		LLStringUtil::format_map_t empty; +		return getString(xml_desc, empty); +	} +	 +	// get the default args +	static const LLStringUtil::format_map_t& getDefaultArgs() +	{ +		return sDefaultArgs; +	} + +	// insert default args into an arg list +	static void getArgs(LLStringUtil::format_map_t& args) +	{ +		args.insert(sDefaultArgs.begin(), sDefaultArgs.end()); +	} +	 +private: +	typedef std::map<std::string, LLTransTemplate > template_map_t; +	static template_map_t sStringTemplates; +	static LLStringUtil::format_map_t sDefaultArgs; +}; + +#endif diff --git a/indra/llxuixml/lluicolor.cpp b/indra/llxuixml/lluicolor.cpp new file mode 100644 index 0000000000..ef0fa5d634 --- /dev/null +++ b/indra/llxuixml/lluicolor.cpp @@ -0,0 +1,71 @@ +/**  + * @file lluicolor.cpp + * @brief brief LLUIColor class implementation file + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#include "lluicolor.h" + +LLUIColor::LLUIColor() +	:mColorPtr(NULL) +{ +} + +LLUIColor::LLUIColor(const LLColor4* color) +	:mColorPtr(color) +{ +} + +LLUIColor::LLUIColor(const LLColor4& color) +	:mColor(color), mColorPtr(NULL) +{ +} + +void LLUIColor::set(const LLColor4& color) +{ +	mColor = color; +	mColorPtr = NULL; +} + +void LLUIColor::set(const LLColor4* color) +{ +	mColorPtr = color; +} + +const LLColor4& LLUIColor::get() const +{ +	return (mColorPtr == NULL ? mColor : *mColorPtr); +} + +LLUIColor::operator const LLColor4& () const +{ +	return get(); +} + +const LLColor4& LLUIColor::operator()() const +{ +	return get(); +} + +bool LLUIColor::isReference() const +{ +	return mColorPtr != NULL; +} + +namespace LLInitParam +{ +	// used to detect equivalence with default values on export +	template<> +	class ParamCompare<LLUIColor> +	{ +	public: +		static bool equals(const LLUIColor &a, const LLUIColor &b) +		{ +			// do not detect value equivalence, treat pointers to colors as distinct from color values +			return (a.mColorPtr == NULL && b.mColorPtr == NULL ? a.mColor == b.mColor : a.mColorPtr == b.mColorPtr); +		} +	}; +} diff --git a/indra/llxuixml/lluicolor.h b/indra/llxuixml/lluicolor.h new file mode 100644 index 0000000000..365f61003b --- /dev/null +++ b/indra/llxuixml/lluicolor.h @@ -0,0 +1,45 @@ +/**  + * @file lluicolor.h + * @brief brief LLUIColor class header file + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#ifndef LL_LLUICOLOR_H_ +#define LL_LLUICOLOR_H_ + +#include "v4color.h" + +namespace LLInitParam +{ +	template<typename T> +	class ParamCompare; +} + +class LLUIColor +{ +public: +	LLUIColor(); +	LLUIColor(const LLColor4* color); +	LLUIColor(const LLColor4& color); + +	void set(const LLColor4& color); +	void set(const LLColor4* color); + +	const LLColor4& get() const; + +	operator const LLColor4& () const; +	const LLColor4& operator()() const; + +	bool isReference() const; + +private: +	friend class LLInitParam::ParamCompare<LLUIColor>; + +	const LLColor4* mColorPtr; +	LLColor4 mColor; +}; + +#endif diff --git a/indra/llxuixml/llxuiparser.cpp b/indra/llxuixml/llxuiparser.cpp new file mode 100644 index 0000000000..e1f61906e2 --- /dev/null +++ b/indra/llxuixml/llxuiparser.cpp @@ -0,0 +1,968 @@ +/**  + * @file llxuiparser.cpp + * @brief Utility functions for handling XUI structures in XML + * + * $LicenseInfo:firstyear=2003&license=viewergpl$ + *  + * Copyright (c) 2003-2009, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llxuiparser.h" + +#include <fstream> +#include <boost/tokenizer.hpp> + +#include "lluicolor.h" + +const S32 MAX_STRING_ATTRIBUTE_SIZE = 40; + +// +// LLXSDWriter +// +LLXSDWriter::LLXSDWriter() +{ +	registerInspectFunc<bool>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:boolean", _1, _2, _3, _4)); +	registerInspectFunc<std::string>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); +	registerInspectFunc<U8>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedByte", _1, _2, _3, _4)); +	registerInspectFunc<S8>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:signedByte", _1, _2, _3, _4)); +	registerInspectFunc<U16>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedShort", _1, _2, _3, _4)); +	registerInspectFunc<S16>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:signedShort", _1, _2, _3, _4)); +	registerInspectFunc<U32>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedInt", _1, _2, _3, _4)); +	registerInspectFunc<S32>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:integer", _1, _2, _3, _4)); +	registerInspectFunc<F32>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:float", _1, _2, _3, _4)); +	registerInspectFunc<F64>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:double", _1, _2, _3, _4)); +	registerInspectFunc<LLColor4>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); +	registerInspectFunc<LLUIColor>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); +	registerInspectFunc<LLUUID>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); +	registerInspectFunc<LLSD>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); +} + +void LLXSDWriter::writeXSD(const std::string& type_name, LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const std::string& xml_namespace) +{ +	mSchemaNode = node; +	node->setName("xs:schema"); +	node->createChild("attributeFormDefault", true)->setStringValue("unqualified"); +	node->createChild("elementFormDefault", true)->setStringValue("qualified"); +	node->createChild("targetNamespace", true)->setStringValue(xml_namespace); +	node->createChild("xmlns:xs", true)->setStringValue("http://www.w3.org/2001/XMLSchema"); +	node->createChild("xmlns", true)->setStringValue(xml_namespace); + +	node = node->createChild("xs:complexType", false); +	node->createChild("name", true)->setStringValue(type_name); +	node->createChild("mixed", true)->setStringValue("true"); + +	mAttributeNode = node; +	mElementNode = node->createChild("xs:choice", false); +	mElementNode->createChild("minOccurs", true)->setStringValue("0"); +	mElementNode->createChild("maxOccurs", true)->setStringValue("unbounded"); +	block.inspectBlock(*this); + +	// duplicate element choices +	LLXMLNodeList children; +	mElementNode->getChildren("xs:element", children, FALSE); +	for (LLXMLNodeList::iterator child_it = children.begin(); child_it != children.end(); ++child_it) +	{ +		LLXMLNodePtr child_copy = child_it->second->deepCopy(); +		std::string child_name; +		child_copy->getAttributeString("name", child_name); +		child_copy->setAttributeString("name", type_name + "." + child_name); +		mElementNode->addChild(child_copy); +	} + +	LLXMLNodePtr element_declaration_node = mSchemaNode->createChild("xs:element", false); +	element_declaration_node->createChild("name", true)->setStringValue(type_name); +	element_declaration_node->createChild("type", true)->setStringValue(type_name); +} + +void LLXSDWriter::writeAttribute(const std::string& type, const Parser::name_stack_t& stack, S32 min_count, S32 max_count, const std::vector<std::string>* possible_values) +{ +	name_stack_t non_empty_names; +	std::string attribute_name; +	for (name_stack_t::const_iterator it = stack.begin(); +		it != stack.end(); +		++it) +	{ +		const std::string& name = it->first; +		if (!name.empty()) +		{ +			non_empty_names.push_back(*it); +		} +	} + +	for (name_stack_t::const_iterator it = non_empty_names.begin(); +		it != non_empty_names.end(); +		++it) +	{ +		if (!attribute_name.empty()) +		{ +			attribute_name += "."; +		} +		attribute_name += it->first; +	} + +	// only flag non-nested attributes as mandatory, nested attributes have variant syntax +	// that can't be properly constrained in XSD +	// e.g. <foo mandatory.value="bar"/> vs <foo><mandatory value="bar"/></foo> +	bool attribute_mandatory = min_count == 1 && max_count == 1 && non_empty_names.size() == 1; + +	// don't bother supporting "Multiple" params as xml attributes +	if (max_count <= 1) +	{ +		// add compound attribute to root node +		addAttributeToSchema(mAttributeNode, attribute_name, type, attribute_mandatory, possible_values); +	} + +	// now generated nested elements for compound attributes +	if (non_empty_names.size() > 1 && !attribute_mandatory) +	{ +		std::string element_name; + +		// traverse all but last element, leaving that as an attribute name +		name_stack_t::const_iterator end_it = non_empty_names.end(); +		end_it--; + +		for (name_stack_t::const_iterator it = non_empty_names.begin(); +			it != end_it; +			++it) +		{ +			if (it != non_empty_names.begin()) +			{ +				element_name += "."; +			} +			element_name += it->first; +		} + +		std::string short_attribute_name = non_empty_names.back().first; + +		LLXMLNodePtr complex_type_node; + +		// find existing element node here, starting at tail of child list +		if (mElementNode->mChildren.notNull()) +		{ +			for(LLXMLNodePtr element = mElementNode->mChildren->tail; +				element.notNull();  +				element = element->mPrev) +			{ +				std::string name; +				if(element->getAttributeString("name", name) && name == element_name) +				{ +					complex_type_node = element->mChildren->head; +					break; +				} +			} +		} +		//create complex_type node +		// +		//<xs:element +        //    maxOccurs="1" +        //    minOccurs="0" +        //    name="name"> +        //       <xs:complexType> +        //       </xs:complexType> +        //</xs:element> +		if(complex_type_node.isNull()) +		{ +			complex_type_node = mElementNode->createChild("xs:element", false); + +			complex_type_node->createChild("minOccurs", true)->setIntValue(min_count); +			complex_type_node->createChild("maxOccurs", true)->setIntValue(max_count); +			complex_type_node->createChild("name",		true)->setStringValue(element_name); +			complex_type_node = complex_type_node->createChild("xs:complexType", false); +		} + +		addAttributeToSchema(complex_type_node, short_attribute_name, type, false, possible_values); +	} +} + +void LLXSDWriter::addAttributeToSchema(LLXMLNodePtr type_declaration_node, const std::string& attribute_name, const std::string& type, bool mandatory, const std::vector<std::string>* possible_values) +{ +	if (!attribute_name.empty()) +	{ +		LLXMLNodePtr new_enum_type_node; +		if (possible_values != NULL) +		{ +			// custom attribute type, for example +			//<xs:simpleType> +			 // <xs:restriction +			 //    base="xs:string"> +			 //     <xs:enumeration +			 //      value="a" /> +			 //     <xs:enumeration +			 //      value="b" /> +			 //   </xs:restriction> +			 // </xs:simpleType> +			new_enum_type_node = new LLXMLNode("xs:simpleType", false); + +			LLXMLNodePtr restriction_node = new_enum_type_node->createChild("xs:restriction", false); +			restriction_node->createChild("base", true)->setStringValue("xs:string"); + +			for (std::vector<std::string>::const_iterator it = possible_values->begin(); +				it != possible_values->end(); +				++it) +			{ +				LLXMLNodePtr enum_node = restriction_node->createChild("xs:enumeration", false); +				enum_node->createChild("value", true)->setStringValue(*it); +			} +		} + +		string_set_t& attributes_written = mAttributesWritten[type_declaration_node]; + +		string_set_t::iterator found_it = attributes_written.lower_bound(attribute_name); + +		// attribute not yet declared +		if (found_it == attributes_written.end() || attributes_written.key_comp()(attribute_name, *found_it)) +		{ +			attributes_written.insert(found_it, attribute_name); + +			LLXMLNodePtr attribute_node = type_declaration_node->createChild("xs:attribute", false); + +			// attribute name +			attribute_node->createChild("name", true)->setStringValue(attribute_name); + +			if (new_enum_type_node.notNull()) +			{ +				attribute_node->addChild(new_enum_type_node); +			} +			else +			{ +				// simple attribute type +				attribute_node->createChild("type", true)->setStringValue(type); +			} + +			// required or optional +			attribute_node->createChild("use", true)->setStringValue(mandatory ? "required" : "optional"); +		} +		 // attribute exists...handle collision of same name attributes with potentially different types +		else +		{ +			LLXMLNodePtr attribute_declaration; +			if (type_declaration_node.notNull()) +			{ +				for(LLXMLNodePtr node = type_declaration_node->mChildren->tail;  +					node.notNull();  +					node = node->mPrev) +				{ +					std::string name; +					if (node->getAttributeString("name", name) && name == attribute_name) +					{ +						attribute_declaration = node; +						break; +					} +				} +			} + +			bool new_type_is_enum = new_enum_type_node.notNull(); +			bool existing_type_is_enum = !attribute_declaration->hasAttribute("type"); + +			// either type is enum, revert to string in collision +			// don't bother to check for enum equivalence +			if (new_type_is_enum || existing_type_is_enum) +			{ +				if (attribute_declaration->hasAttribute("type")) +				{ +					attribute_declaration->setAttributeString("type", "xs:string"); +				} +				else +				{ +					attribute_declaration->createChild("type", true)->setStringValue("xs:string"); +				} +				attribute_declaration->deleteChildren("xs:simpleType"); +			} +			else  +			{ +				// check for collision of different standard types +				std::string existing_type; +				attribute_declaration->getAttributeString("type", existing_type); +				// if current type is not the same as the new type, revert to strnig +				if (existing_type != type) +				{ +					// ...than use most general type, string +					attribute_declaration->setAttributeString("type", "string"); +				} +			} +		} +	} +} + +// +// LLXUIXSDWriter +// +void LLXUIXSDWriter::writeXSD(const std::string& type_name, const std::string& path, const LLInitParam::BaseBlock& block) +{ +	std::string file_name(path); +	file_name += type_name + ".xsd"; +	LLXMLNodePtr root_nodep = new LLXMLNode(); + +	LLXSDWriter::writeXSD(type_name, root_nodep, block, "http://www.lindenlab.com/xui"); + +	// add includes for all possible children +	const std::type_info* type = *LLWidgetTypeRegistry::instance().getValue(type_name); +	const widget_registry_t* widget_registryp = LLChildRegistryRegistry::instance().getValue(type); +	 +	// add include declarations for all valid children +	for (widget_registry_t::Registrar::registry_map_t::const_iterator it = widget_registryp->currentRegistrar().beginItems(); +		it != widget_registryp->currentRegistrar().endItems(); +		++it) +	{ +		std::string widget_name = it->first; +		if (widget_name == type_name) +		{ +			continue; +		} +		LLXMLNodePtr nodep = new LLXMLNode("xs:include", false); +		nodep->createChild("schemaLocation", true)->setStringValue(widget_name + ".xsd"); + +		// add to front of schema +		mSchemaNode->addChild(nodep, mSchemaNode); +	} + +	// add choices for valid children +	if (widget_registryp) +	{ +		for (widget_registry_t::Registrar::registry_map_t::const_iterator it = widget_registryp->currentRegistrar().beginItems(); +			it != widget_registryp->currentRegistrar().endItems(); +			++it) +		{ +			std::string widget_name = it->first; +            //<xs:element name="widget_name" type="widget_name"> +			LLXMLNodePtr widget_node = mElementNode->createChild("xs:element", false); +			widget_node->createChild("name", true)->setStringValue(widget_name); +			widget_node->createChild("type", true)->setStringValue(widget_name); +		} +	} + +	LLFILE* xsd_file = LLFile::fopen(file_name.c_str(), "w"); +	LLXMLNode::writeHeaderToFile(xsd_file); +	root_nodep->writeToFile(xsd_file); +	fclose(xsd_file); +} + +// +// LLXUIParser +// +LLXUIParser::LLXUIParser() +:	mLastWriteGeneration(-1), +	mCurReadDepth(0) +{ +	registerParserFuncs<bool>(boost::bind(&LLXUIParser::readBoolValue, this, _1), +								boost::bind(&LLXUIParser::writeBoolValue, this, _1, _2)); +	registerParserFuncs<std::string>(boost::bind(&LLXUIParser::readStringValue, this, _1), +								boost::bind(&LLXUIParser::writeStringValue, this, _1, _2)); +	registerParserFuncs<U8>(boost::bind(&LLXUIParser::readU8Value, this, _1), +								boost::bind(&LLXUIParser::writeU8Value, this, _1, _2)); +	registerParserFuncs<S8>(boost::bind(&LLXUIParser::readS8Value, this, _1), +								boost::bind(&LLXUIParser::writeS8Value, this, _1, _2)); +	registerParserFuncs<U16>(boost::bind(&LLXUIParser::readU16Value, this, _1), +								boost::bind(&LLXUIParser::writeU16Value, this, _1, _2)); +	registerParserFuncs<S16>(boost::bind(&LLXUIParser::readS16Value, this, _1), +								boost::bind(&LLXUIParser::writeS16Value, this, _1, _2)); +	registerParserFuncs<U32>(boost::bind(&LLXUIParser::readU32Value, this, _1), +								boost::bind(&LLXUIParser::writeU32Value, this, _1, _2)); +	registerParserFuncs<S32>(boost::bind(&LLXUIParser::readS32Value, this, _1), +								boost::bind(&LLXUIParser::writeS32Value, this, _1, _2)); +	registerParserFuncs<F32>(boost::bind(&LLXUIParser::readF32Value, this, _1), +								boost::bind(&LLXUIParser::writeF32Value, this, _1, _2)); +	registerParserFuncs<F64>(boost::bind(&LLXUIParser::readF64Value, this, _1), +								boost::bind(&LLXUIParser::writeF64Value, this, _1, _2)); +	registerParserFuncs<LLColor4>(boost::bind(&LLXUIParser::readColor4Value, this, _1), +								boost::bind(&LLXUIParser::writeColor4Value, this, _1, _2)); +	registerParserFuncs<LLUIColor>(boost::bind(&LLXUIParser::readUIColorValue, this, _1), +								boost::bind(&LLXUIParser::writeUIColorValue, this, _1, _2)); +	registerParserFuncs<LLUUID>(boost::bind(&LLXUIParser::readUUIDValue, this, _1), +								boost::bind(&LLXUIParser::writeUUIDValue, this, _1, _2)); +	registerParserFuncs<LLSD>(boost::bind(&LLXUIParser::readSDValue, this, _1), +								boost::bind(&LLXUIParser::writeSDValue, this, _1, _2)); +} + +static LLFastTimer::DeclareTimer PARSE_XUI("XUI Parsing"); + +void LLXUIParser::readXUI(LLXMLNodePtr node, LLInitParam::BaseBlock& block, bool silent) +{ +	LLFastTimer timer(PARSE_XUI); +	mNameStack.clear(); +	mCurReadDepth = 0; +	setParseSilently(silent); + +	if (node.isNull()) +	{ +		parserWarning("Invalid node"); +	} +	else +	{ +		readXUIImpl(node, std::string(node->getName()->mString), block); +	} +} + +bool LLXUIParser::readXUIImpl(LLXMLNodePtr nodep, const std::string& scope, LLInitParam::BaseBlock& block) +{ +	typedef boost::tokenizer<boost::char_separator<char> > tokenizer; +	boost::char_separator<char> sep("."); + +	bool values_parsed = false; + +	// submit attributes for current node +	values_parsed |= readAttributes(nodep, block); + +	// treat text contents of xml node as "value" parameter +	std::string text_contents = nodep->getSanitizedValue(); +	if (!text_contents.empty()) +	{ +		mCurReadNode = nodep; +		mNameStack.push_back(std::make_pair(std::string("value"), newParseGeneration())); +		// child nodes are not necessarily valid parameters (could be a child widget) +		// so don't complain once we've recursed +		bool silent = mCurReadDepth > 0; +		if (!block.submitValue(mNameStack, *this, true)) +		{ +			mNameStack.pop_back(); +			block.submitValue(mNameStack, *this, silent); +		} +		else +		{ +			mNameStack.pop_back(); +		} +	} + +	// then traverse children +	// child node must start with last name of parent node (our "scope") +	// for example: "<button><button.param nested_param1="foo"><param.nested_param2 nested_param3="bar"/></button.param></button>" +	// which equates to the following nesting: +	// button +	//     param +	//         nested_param1 +	//         nested_param2 +	//             nested_param3	 +	mCurReadDepth++; +	for(LLXMLNodePtr childp = nodep->getFirstChild(); childp.notNull();) +	{ +		std::string child_name(childp->getName()->mString); +		S32 num_tokens_pushed = 0; + +		// for non "dotted" child nodes	check to see if child node maps to another widget type +		// and if not, treat as a child element of the current node +		// e.g. <button><rect left="10"/></button> will interpret <rect> as "button.rect" +		// since there is no widget named "rect" +		if (child_name.find(".") == std::string::npos)  +		{ +			mNameStack.push_back(std::make_pair(child_name, newParseGeneration())); +			num_tokens_pushed++; +		} +		else +		{ +			// parse out "dotted" name into individual tokens +			tokenizer name_tokens(child_name, sep); + +			tokenizer::iterator name_token_it = name_tokens.begin(); +			if(name_token_it == name_tokens.end())  +			{ +				childp = childp->getNextSibling(); +				continue; +			} + +			// check for proper nesting +			if(!scope.empty() && *name_token_it != scope) +			{ +				childp = childp->getNextSibling(); +				continue; +			} + +			// now ignore first token +			++name_token_it;  + +			// copy remaining tokens on to our running token list +			for(tokenizer::iterator token_to_push = name_token_it; token_to_push != name_tokens.end(); ++token_to_push) +			{ +				mNameStack.push_back(std::make_pair(*token_to_push, newParseGeneration())); +				num_tokens_pushed++; +			} +		} + +		// recurse and visit children XML nodes +		if(readXUIImpl(childp, mNameStack.empty() ? scope : mNameStack.back().first, block)) +		{ +			// child node successfully parsed, remove from DOM + +			values_parsed = true; +			LLXMLNodePtr node_to_remove = childp; +			childp = childp->getNextSibling(); + +			nodep->deleteChild(node_to_remove); +		} +		else +		{ +			childp = childp->getNextSibling(); +		} + +		while(num_tokens_pushed-- > 0) +		{ +			mNameStack.pop_back(); +		} +	} +	mCurReadDepth--; +	return values_parsed; +} + +bool LLXUIParser::readAttributes(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block) +{ +	typedef boost::tokenizer<boost::char_separator<char> > tokenizer; +	boost::char_separator<char> sep("."); + +	bool any_parsed = false; + +	for(LLXMLAttribList::const_iterator attribute_it = nodep->mAttributes.begin();  +		attribute_it != nodep->mAttributes.end();  +		++attribute_it) +	{ +		S32 num_tokens_pushed = 0; +		std::string attribute_name(attribute_it->first->mString); +		mCurReadNode = attribute_it->second; + +		tokenizer name_tokens(attribute_name, sep); +		// copy remaining tokens on to our running token list +		for(tokenizer::iterator token_to_push = name_tokens.begin(); token_to_push != name_tokens.end(); ++token_to_push) +		{ +			mNameStack.push_back(std::make_pair(*token_to_push, newParseGeneration())); +			num_tokens_pushed++; +		} + +		// child nodes are not necessarily valid attributes, so don't complain once we've recursed +		bool silent = mCurReadDepth > 0; +		any_parsed |= block.submitValue(mNameStack, *this, silent); +		 +		while(num_tokens_pushed-- > 0) +		{ +			mNameStack.pop_back(); +		} +	} + +	return any_parsed; +} + +void LLXUIParser::writeXUI(LLXMLNodePtr node, const LLInitParam::BaseBlock &block, const LLInitParam::BaseBlock* diff_block) +{ +	mWriteRootNode = node; +	block.serializeBlock(*this, Parser::name_stack_t(), diff_block); +	mOutNodes.clear(); +} + +// go from a stack of names to a specific XML node +LLXMLNodePtr LLXUIParser::getNode(const name_stack_t& stack) +{ +	name_stack_t name_stack; +	for (name_stack_t::const_iterator it = stack.begin(); +		it != stack.end(); +		++it) +	{ +		if (!it->first.empty()) +		{ +			name_stack.push_back(*it); +		} +	} + +	LLXMLNodePtr out_node = mWriteRootNode; + +	name_stack_t::const_iterator next_it = name_stack.begin(); +	for (name_stack_t::const_iterator it = name_stack.begin(); +		it != name_stack.end(); +		it = next_it) +	{ +		++next_it; +		if (it->first.empty()) +		{ +			continue; +		} + +		out_nodes_t::iterator found_it = mOutNodes.lower_bound(it->second); + +		// node with this name not yet written +		if (found_it == mOutNodes.end() || mOutNodes.key_comp()(found_it->first, it->second)) +		{ +			// make an attribute if we are the last element on the name stack +			bool is_attribute = next_it == name_stack.end(); +			LLXMLNodePtr new_node = new LLXMLNode(it->first.c_str(), is_attribute); +			out_node->addChild(new_node); +			mOutNodes.insert(found_it, std::make_pair(it->second, new_node)); +			out_node = new_node; +		} +		else +		{ +			out_node = found_it->second; +		} +	} + +	return (out_node == mWriteRootNode ? LLXMLNodePtr(NULL) : out_node); +} + + +bool LLXUIParser::readBoolValue(void* val_ptr) +{ +	S32 value; +	bool success = mCurReadNode->getBoolValue(1, &value); +	*((bool*)val_ptr) = (value != FALSE); +	return success; +} + +bool LLXUIParser::writeBoolValue(const void* val_ptr, const name_stack_t& stack) +{ +	LLXMLNodePtr node = getNode(stack); +	if (node.notNull()) +	{ +		node->setBoolValue(*((bool*)val_ptr)); +		return true; +	} +	return false; +} + +bool LLXUIParser::readStringValue(void* val_ptr) +{ +	*((std::string*)val_ptr) = mCurReadNode->getSanitizedValue(); +	return true; +} + +bool LLXUIParser::writeStringValue(const void* val_ptr, const name_stack_t& stack) +{ +	LLXMLNodePtr node = getNode(stack); +	if (node.notNull()) +	{ +		const std::string* string_val = reinterpret_cast<const std::string*>(val_ptr); +		if (string_val->find('\n') != std::string::npos  +			|| string_val->size() > MAX_STRING_ATTRIBUTE_SIZE) +		{ +			// don't write strings with newlines into attributes +			std::string attribute_name = node->getName()->mString; +			LLXMLNodePtr parent_node = node->mParent; +			parent_node->deleteChild(node); +			// write results in text contents of node +			if (attribute_name == "value") +			{ +				// "value" is implicit, just write to parent +				node = parent_node; +			} +			else +			{ +				// create a child that is not an attribute, but with same name +				node = parent_node->createChild(attribute_name.c_str(), false); +			} +		} +		node->setStringValue(*string_val); +		return true; +	} +	return false; +} + +bool LLXUIParser::readU8Value(void* val_ptr) +{ +	return mCurReadNode->getByteValue(1, (U8*)val_ptr); +} + +bool LLXUIParser::writeU8Value(const void* val_ptr, const name_stack_t& stack) +{ +	LLXMLNodePtr node = getNode(stack); +	if (node.notNull()) +	{ +		node->setUnsignedValue(*((U8*)val_ptr)); +		return true; +	} +	return false; +} + +bool LLXUIParser::readS8Value(void* val_ptr) +{ +	S32 value; +	if(mCurReadNode->getIntValue(1, &value)) +	{ +		*((S8*)val_ptr) = value; +		return true; +	} +	return false; +} + +bool LLXUIParser::writeS8Value(const void* val_ptr, const name_stack_t& stack) +{ +	LLXMLNodePtr node = getNode(stack); +	if (node.notNull()) +	{ +		node->setIntValue(*((S8*)val_ptr)); +		return true; +	} +	return false; +} + +bool LLXUIParser::readU16Value(void* val_ptr) +{ +	U32 value; +	if(mCurReadNode->getUnsignedValue(1, &value)) +	{ +		*((U16*)val_ptr) = value; +		return true; +	} +	return false; +} + +bool LLXUIParser::writeU16Value(const void* val_ptr, const name_stack_t& stack) +{ +	LLXMLNodePtr node = getNode(stack); +	if (node.notNull()) +	{ +		node->setUnsignedValue(*((U16*)val_ptr)); +		return true; +	} +	return false; +} + +bool LLXUIParser::readS16Value(void* val_ptr) +{ +	S32 value; +	if(mCurReadNode->getIntValue(1, &value)) +	{ +		*((S16*)val_ptr) = value; +		return true; +	} +	return false; +} + +bool LLXUIParser::writeS16Value(const void* val_ptr, const name_stack_t& stack) +{ +	LLXMLNodePtr node = getNode(stack); +	if (node.notNull()) +	{ +		node->setIntValue(*((S16*)val_ptr)); +		return true; +	} +	return false; +} + +bool LLXUIParser::readU32Value(void* val_ptr) +{ +	return mCurReadNode->getUnsignedValue(1, (U32*)val_ptr); +} + +bool LLXUIParser::writeU32Value(const void* val_ptr, const name_stack_t& stack) +{ +	LLXMLNodePtr node = getNode(stack); +	if (node.notNull()) +	{ +		node->setUnsignedValue(*((U32*)val_ptr)); +		return true; +	} +	return false; +} + +bool LLXUIParser::readS32Value(void* val_ptr) +{ +	return mCurReadNode->getIntValue(1, (S32*)val_ptr); +} + +bool LLXUIParser::writeS32Value(const void* val_ptr, const name_stack_t& stack) +{ +	LLXMLNodePtr node = getNode(stack); +	if (node.notNull()) +	{ +		node->setIntValue(*((S32*)val_ptr)); +		return true; +	} +	return false; +} + +bool LLXUIParser::readF32Value(void* val_ptr) +{ +	return mCurReadNode->getFloatValue(1, (F32*)val_ptr); +} + +bool LLXUIParser::writeF32Value(const void* val_ptr, const name_stack_t& stack) +{ +	LLXMLNodePtr node = getNode(stack); +	if (node.notNull()) +	{ +		node->setFloatValue(*((F32*)val_ptr)); +		return true; +	} +	return false; +} + +bool LLXUIParser::readF64Value(void* val_ptr) +{ +	return mCurReadNode->getDoubleValue(1, (F64*)val_ptr); +} + +bool LLXUIParser::writeF64Value(const void* val_ptr, const name_stack_t& stack) +{ +	LLXMLNodePtr node = getNode(stack); +	if (node.notNull()) +	{ +		node->setDoubleValue(*((F64*)val_ptr)); +		return true; +	} +	return false; +} + +bool LLXUIParser::readColor4Value(void* val_ptr) +{ +	LLColor4* colorp = (LLColor4*)val_ptr; +	if(mCurReadNode->getFloatValue(4, colorp->mV) >= 3) +	{ +		return true; +	} + +	return false; +} + +bool LLXUIParser::writeColor4Value(const void* val_ptr, const name_stack_t& stack) +{ +	LLXMLNodePtr node = getNode(stack); +	if (node.notNull()) +	{ +		LLColor4 color = *((LLColor4*)val_ptr); +		node->setFloatValue(4, color.mV); +		return true; +	} +	return false; +} + +bool LLXUIParser::readUIColorValue(void* val_ptr) +{ +	LLUIColor* param = (LLUIColor*)val_ptr; +	LLColor4 color; +	bool success =  mCurReadNode->getFloatValue(4, color.mV) >= 3; +	if (success) +	{ +		param->set(color); +		return true; +	} +	return false; +} + +bool LLXUIParser::writeUIColorValue(const void* val_ptr, const name_stack_t& stack) +{ +	LLXMLNodePtr node = getNode(stack); +	if (node.notNull()) +	{ +		LLUIColor color = *((LLUIColor*)val_ptr); +		//RN: don't write out the color that is represented by a function +		// rely on param block exporting to get the reference to the color settings +		if (color.isReference()) return false; +		node->setFloatValue(4, color.get().mV); +		return true; +	} +	return false; +} + +bool LLXUIParser::readUUIDValue(void* val_ptr) +{ +	LLUUID temp_id; +	// LLUUID::set is destructive, so use temporary value +	if (temp_id.set(mCurReadNode->getSanitizedValue())) +	{ +		*(LLUUID*)(val_ptr) = temp_id; +		return true; +	} +	return false; +} + +bool LLXUIParser::writeUUIDValue(const void* val_ptr, const name_stack_t& stack) +{ +	LLXMLNodePtr node = getNode(stack); +	if (node.notNull()) +	{ +		node->setStringValue(((LLUUID*)val_ptr)->asString()); +		return true; +	} +	return false; +} + +bool LLXUIParser::readSDValue(void* val_ptr) +{ +	*((LLSD*)val_ptr) = LLSD(mCurReadNode->getSanitizedValue()); +	return true; +} + +bool LLXUIParser::writeSDValue(const void* val_ptr, const name_stack_t& stack) +{ +	LLXMLNodePtr node = getNode(stack); +	if (node.notNull()) +	{ +		std::string string_val = ((LLSD*)val_ptr)->asString(); +		if (string_val.find('\n') != std::string::npos || string_val.size() > MAX_STRING_ATTRIBUTE_SIZE) +		{ +			// don't write strings with newlines into attributes +			std::string attribute_name = node->getName()->mString; +			LLXMLNodePtr parent_node = node->mParent; +			parent_node->deleteChild(node); +			// write results in text contents of node +			if (attribute_name == "value") +			{ +				// "value" is implicit, just write to parent +				node = parent_node; +			} +			else +			{ +				node = parent_node->createChild(attribute_name.c_str(), false); +			} +		} + +		node->setStringValue(string_val); +		return true; +	} +	return false; +} + +/*virtual*/ std::string LLXUIParser::getCurrentElementName() +{ +	std::string full_name; +	for (name_stack_t::iterator it = mNameStack.begin();	 +		it != mNameStack.end(); +		++it) +	{ +		full_name += it->first + "."; // build up dotted names: "button.param.nestedparam." +	} + +	return full_name; +} + +void LLXUIParser::parserWarning(const std::string& message) +{ +#if 0 //#ifdef LL_WINDOWS +	// use Visual Studo friendly formatting of output message for easy access to originating xml +	llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", LLUICtrlFactory::getInstance()->getCurFileName().c_str(), mCurReadNode->getLineNumber(), message.c_str()).c_str()); +	utf16str += '\n'; +	OutputDebugString(utf16str.c_str()); +#else +	Parser::parserWarning(message); +#endif +} + +void LLXUIParser::parserError(const std::string& message) +{ +#if 0 //#ifdef LL_WINDOWS +	llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", LLUICtrlFactory::getInstance()->getCurFileName().c_str(), mCurReadNode->getLineNumber(), message.c_str()).c_str()); +	utf16str += '\n'; +	OutputDebugString(utf16str.c_str()); +#else +	Parser::parserError(message); +#endif +} diff --git a/indra/llxuixml/llxuiparser.h b/indra/llxuixml/llxuiparser.h new file mode 100644 index 0000000000..6f000f2422 --- /dev/null +++ b/indra/llxuixml/llxuiparser.h @@ -0,0 +1,174 @@ +/**  + * @file llxuiparser.h + * @brief Utility functions for handling XUI structures in XML + * + * $LicenseInfo:firstyear=2003&license=viewergpl$ + *  + * Copyright (c) 2003-2009, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LLXUIPARSER_H +#define LLXUIPARSER_H + +#include "llinitparam.h" +#include "llxmlnode.h" +#include "llfasttimer.h" +#include "llregistry.h" + +#include <boost/function.hpp> +#include <iosfwd> +#include <stack> +#include <set> + + + +class LLView; + + + +// lookup widget type by name +class LLWidgetTypeRegistry +:	public LLRegistrySingleton<std::string, const std::type_info*, LLWidgetTypeRegistry> +{}; + + +// global static instance for registering all widget types +typedef boost::function<LLView* (LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node)> LLWidgetCreatorFunc; + +typedef LLRegistry<std::string, LLWidgetCreatorFunc> widget_registry_t; + +class LLChildRegistryRegistry +: public LLRegistrySingleton<const std::type_info*, widget_registry_t, LLChildRegistryRegistry> +{}; + + + +class LLXSDWriter : public LLInitParam::Parser +{ +	LOG_CLASS(LLXSDWriter); +public: +	void writeXSD(const std::string& name, LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const std::string& xml_namespace); + +	/*virtual*/ std::string getCurrentElementName() { return LLStringUtil::null; } + +	LLXSDWriter(); + +protected: +	void writeAttribute(const std::string& type, const Parser::name_stack_t&, S32 min_count, S32 max_count, const std::vector<std::string>* possible_values); +	void addAttributeToSchema(LLXMLNodePtr nodep, const std::string& attribute_name, const std::string& type, bool mandatory, const std::vector<std::string>* possible_values); +	LLXMLNodePtr mAttributeNode; +	LLXMLNodePtr mElementNode; +	LLXMLNodePtr mSchemaNode; + +	typedef std::set<std::string> string_set_t; +	typedef std::map<LLXMLNodePtr, string_set_t> attributes_map_t; +	attributes_map_t	mAttributesWritten; +}; + + + +// NOTE: DOES NOT WORK YET +// should support child widgets for XUI +class LLXUIXSDWriter : public LLXSDWriter +{ +public: +	void writeXSD(const std::string& name, const std::string& path, const LLInitParam::BaseBlock& block); +}; + + + +class LLXUIParser : public LLInitParam::Parser, public LLSingleton<LLXUIParser> +{ +LOG_CLASS(LLXUIParser); + +protected: +	LLXUIParser(); +	friend class LLSingleton<LLXUIParser>; +public: +	typedef LLInitParam::Parser::name_stack_t name_stack_t; + +	/*virtual*/ std::string getCurrentElementName(); +	/*virtual*/ void parserWarning(const std::string& message); +	/*virtual*/ void parserError(const std::string& message); + +	void readXUI(LLXMLNodePtr node, LLInitParam::BaseBlock& block, bool silent=false); +	void writeXUI(LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const LLInitParam::BaseBlock* diff_block = NULL); + +private: +	typedef std::list<std::pair<std::string, bool> >	token_list_t; + +	bool readXUIImpl(LLXMLNodePtr node, const std::string& scope, LLInitParam::BaseBlock& block); +	bool readAttributes(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block); + +	//reader helper functions +	bool readBoolValue(void* val_ptr); +	bool readStringValue(void* val_ptr); +	bool readU8Value(void* val_ptr); +	bool readS8Value(void* val_ptr); +	bool readU16Value(void* val_ptr); +	bool readS16Value(void* val_ptr); +	bool readU32Value(void* val_ptr); +	bool readS32Value(void* val_ptr); +	bool readF32Value(void* val_ptr); +	bool readF64Value(void* val_ptr); +	bool readColor4Value(void* val_ptr); +	bool readUIColorValue(void* val_ptr); +	bool readUUIDValue(void* val_ptr); +	bool readSDValue(void* val_ptr); + +	//writer helper functions +	bool writeBoolValue(const void* val_ptr, const name_stack_t&); +	bool writeStringValue(const void* val_ptr, const name_stack_t&); +	bool writeU8Value(const void* val_ptr, const name_stack_t&); +	bool writeS8Value(const void* val_ptr, const name_stack_t&); +	bool writeU16Value(const void* val_ptr, const name_stack_t&); +	bool writeS16Value(const void* val_ptr, const name_stack_t&); +	bool writeU32Value(const void* val_ptr, const name_stack_t&); +	bool writeS32Value(const void* val_ptr, const name_stack_t&); +	bool writeF32Value(const void* val_ptr, const name_stack_t&); +	bool writeF64Value(const void* val_ptr, const name_stack_t&); +	bool writeColor4Value(const void* val_ptr, const name_stack_t&); +	bool writeUIColorValue(const void* val_ptr, const name_stack_t&); +	bool writeUUIDValue(const void* val_ptr, const name_stack_t&); +	bool writeSDValue(const void* val_ptr, const name_stack_t&); + +	LLXMLNodePtr getNode(const name_stack_t& stack); + +private: +	Parser::name_stack_t			mNameStack; +	LLXMLNodePtr					mCurReadNode; +	// Root of the widget XML sub-tree, for example, "line_editor" +	LLXMLNodePtr					mWriteRootNode; +	 +	typedef std::map<S32, LLXMLNodePtr>	out_nodes_t; +	out_nodes_t						mOutNodes; +	S32								mLastWriteGeneration; +	LLXMLNodePtr					mLastWrittenChild; +	S32								mCurReadDepth; +}; + + +#endif //LLXUIPARSER_H diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index ffdacee22a..d7eabf33c4 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -26,6 +26,7 @@ include(LLUI)  include(LLVFS)  include(LLWindow)  include(LLXML) +include(LLXUIXML)  include(LScript)  include(Linking)  include(Mozlib) @@ -58,6 +59,7 @@ include_directories(      ${LLVFS_INCLUDE_DIRS}      ${LLWINDOW_INCLUDE_DIRS}      ${LLXML_INCLUDE_DIRS} +    ${LLXUIXML_INCLUDE_DIRS}      ${LSCRIPT_INCLUDE_DIRS}      ${LSCRIPT_INCLUDE_DIRS}/lscript_compile      ) @@ -1343,6 +1345,7 @@ target_link_libraries(${VIEWER_BINARY_NAME}      ${LLVFS_LIBRARIES}      ${LLWINDOW_LIBRARIES}      ${LLXML_LIBRARIES} +    ${LLXUIXML_LIBRARIES}      ${LSCRIPT_LIBRARIES}      ${LLMATH_LIBRARIES}      ${LLCOMMON_LIBRARIES} @@ -1402,6 +1405,7 @@ if (LINUX)    if (NOT INSTALL)      add_custom_target(package ALL DEPENDS ${product}.tar.bz2)      add_dependencies(package linux-crash-logger-strip-target) +    add_dependencies(package linux-updater-strip-target)    endif (NOT INSTALL)  endif (LINUX) diff --git a/indra/newview/linux_tools/client-readme.txt b/indra/newview/linux_tools/client-readme.txt index 99c973f7ea..07a8f951ee 100644 --- a/indra/newview/linux_tools/client-readme.txt +++ b/indra/newview/linux_tools/client-readme.txt @@ -75,8 +75,9 @@ Life Linux client is very similar to that for Windows, as detailed at:  3. INSTALLING & RUNNING  -=-=-=-=-=-=-=-=-=-=-=- -The Second Life Linux client entirely runs out of the directory you have -unpacked it into - no installation step is required. +The Second Life Linux client can entirely run from the directory you have +unpacked it into - no installation step is required.  If you wish to +perform a separate installation step anyway, you may run './install.sh'  Run ./secondlife from the installation directory to start Second Life. @@ -96,10 +97,7 @@ you wish.  4. KNOWN ISSUES  -=-=-=-=-=-=-=- -* UPDATING - when the client detects that a new version of Second Life -  is available, it will ask you if you wish to download the new version. -  This option is not implemented; to upgrade, you should manually download a -  new version from the Second Life web site, <http://www.secondlife.com/>. +* No significant known issues at this time.  5. TROUBLESHOOTING diff --git a/indra/newview/linux_tools/handle_secondlifeprotocol.sh b/indra/newview/linux_tools/handle_secondlifeprotocol.sh index 7ff86d1b93..203012132e 100755 --- a/indra/newview/linux_tools/handle_secondlifeprotocol.sh +++ b/indra/newview/linux_tools/handle_secondlifeprotocol.sh @@ -11,7 +11,7 @@ if [ -z "$URL" ]; then  fi  RUN_PATH=`dirname "$0" || echo .` -cd "${RUN_PATH}" +cd "${RUN_PATH}/.."  exec ./secondlife -url \'"${URL}"\' diff --git a/indra/newview/linux_tools/install.sh b/indra/newview/linux_tools/install.sh new file mode 100755 index 0000000000..c94510267a --- /dev/null +++ b/indra/newview/linux_tools/install.sh @@ -0,0 +1,106 @@ +#!/bin/bash + +# Install the Second Life Viewer. This script can install the viewer both +# system-wide and for an individual user. + +VT102_STYLE_NORMAL='\E[0m' +VT102_COLOR_RED='\E[31m' + +SCRIPTSRC=`readlink -f "$0" || echo "$0"` +RUN_PATH=`dirname "${SCRIPTSRC}" || echo .` +tarball_path=${RUN_PATH} + +function prompt() +{ +    local prompt=$1 +    local input + +    echo -n "$prompt" + +    while read input; do +        case $input in +            [Yy]* ) +                return 1 +                ;; +            [Nn]* ) +                return 0 +                ;; +            * ) +                echo "Please enter yes or no." +                echo -n "$prompt" +        esac +    done +} + +function die() +{ +    warn $1 +    exit 1 +} + +function warn() +{ +    echo -n -e $VT102_COLOR_RED +    echo $1 +    echo -n -e $VT102_STYLE_NORMAL +} + +function homedir_install() +{ +    warn "You are not running as a privileged user, so you will only be able" +    warn "to install the Second Life Viewer in your home directory. If you" +    warn "would like to install the Second Life Viewer system-wide, please run" +    warn "this script as the root user, or with the 'sudo' command." +    echo + +    prompt "Proceed with the installation? [Y/N]: " +    if [[ $? == 0 ]]; then +	exit 0 +    fi + +    install_to_prefix "$HOME/.secondlife-install" +    $HOME/.secondlife-install/etc/refresh_desktop_app_entry.sh +} + +function root_install() +{ +    local default_prefix="/opt/secondlife-install" + +    echo -n "Enter the desired installation directory [${default_prefix}]: "; +    read +    if [[ "$REPLY" = "" ]] ; then +	local install_prefix=$default_prefix +    else +	local install_prefix=$REPLY +    fi + +    install_to_prefix "$install_prefix" + +    mkdir -p /usr/local/share/applications +    ${install_prefix}/etc/refresh_desktop_app_entry.sh +} + +function install_to_prefix() +{ +    test -e "$1" && backup_previous_installation "$1" +    mkdir -p "$1" || die "Failed to create installation directory!" + +    echo " - Installing to $1" + +    cp -a "${tarball_path}"/* "$1/" || die "Failed to complete the installation!" +} + +function backup_previous_installation() +{ +    local backup_dir="$1".backup-$(date -I) +    echo " - Backing up previous installation to $backup_dir" + +    mv "$1" "$backup_dir" || die "Failed to create backup of existing installation!" +} + + +if [ "$UID" == "0" ]; then +    root_install +else +    homedir_install +fi diff --git a/indra/newview/linux_tools/refresh_desktop_app_entry.sh b/indra/newview/linux_tools/refresh_desktop_app_entry.sh new file mode 100755 index 0000000000..d2b2a732d5 --- /dev/null +++ b/indra/newview/linux_tools/refresh_desktop_app_entry.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +SCRIPTSRC=`readlink -f "$0" || echo "$0"` +RUN_PATH=`dirname "${SCRIPTSRC}" || echo .` + +install_prefix=${RUN_PATH}/.. + +function install_desktop_entry() +{ +    local installation_prefix="$1" +    local desktop_entries_dir="$2" + +    local desktop_entry="\ +[Desktop Entry]\n\ +Name=Second Life\n\ +Comment=Client for the On-line Virtual World, Second Life\n\ +Exec=${installation_prefix}/secondlife\n\ +Icon=${installation_prefix}/secondlife_icon.png\n\ +Terminal=false\n\ +Type=Application\n\ +Categories=Application;Network;\n\ +StartupNotify=true\n\ +X-Desktop-File-Install-Version=3.0" + +    echo " - Installing menu entries in ${desktop_entries_dir}" +    mkdir -vp "${desktop_entries_dir}" +    echo -e $desktop_entry > "${desktop_entries_dir}/secondlife-viewer.desktop" || "Failed to install application menu!" +} + +if [ "$UID" == "0" ]; then +    # system-wide +    install_desktop_entry "$install_prefix" /usr/local/share/applications +else +    # user-specific +    install_desktop_entry "$install_prefix" "$HOME/.local/share/applications" +fi diff --git a/indra/newview/linux_tools/register_secondlifeprotocol.sh b/indra/newview/linux_tools/register_secondlifeprotocol.sh index 4ab96f97d6..c7b4d55461 100755 --- a/indra/newview/linux_tools/register_secondlifeprotocol.sh +++ b/indra/newview/linux_tools/register_secondlifeprotocol.sh @@ -7,10 +7,10 @@  HANDLER="$1"  RUN_PATH=`dirname "$0" || echo .` -cd "${RUN_PATH}" +cd "${RUN_PATH}/.."  if [ -z "$HANDLER" ]; then -    HANDLER=`pwd`/handle_secondlifeprotocol.sh +    HANDLER=`pwd`/etc/handle_secondlifeprotocol.sh  fi  # Register handler for GNOME-aware apps diff --git a/indra/newview/linux_tools/wrapper.sh b/indra/newview/linux_tools/wrapper.sh index e188abe5d2..3209654498 100755 --- a/indra/newview/linux_tools/wrapper.sh +++ b/indra/newview/linux_tools/wrapper.sh @@ -91,12 +91,14 @@ echo "Running from ${RUN_PATH}"  cd "${RUN_PATH}"  # Re-register the secondlife:// protocol handler every launch, for now. -./register_secondlifeprotocol.sh +./etc/register_secondlifeprotocol.sh + +# Re-register the application with the desktop system every launch, for now. +./etc/refresh_desktop_app_entry.sh +  ## Before we mess with LD_LIBRARY_PATH, save the old one to restore for  ##  subprocesses that care. -if [ "${LD_LIBRARY_PATH+isset}" = "isset" ]; then -    export SAVED_LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" -fi +export SAVED_LD_LIBRARY_PATH="${LD_LIBRARY_PATH}"  if [ -n "$LL_TCMALLOC" ]; then      tcmalloc_libs='/usr/lib/libtcmalloc.so.0 /usr/lib/libstacktrace.so.0 /lib/libpthread.so.0' @@ -118,7 +120,7 @@ fi  export SL_ENV='LD_LIBRARY_PATH="`pwd`"/lib:"`pwd`"/app_settings/mozilla-runtime-linux-i686:"${LD_LIBRARY_PATH}"'  export SL_CMD='$LL_WRAPPER bin/do-not-directly-run-secondlife-bin' -export SL_OPT="`cat gridargs.dat` $@" +export SL_OPT="`cat etc/gridargs.dat` $@"  # Run the program  eval ${SL_ENV} ${SL_CMD} ${SL_OPT} || LL_RUN_ERR=runerr diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index a1484b3c52..5045f18784 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -113,6 +113,7 @@  #include "llviewermenu.h"  #include "llselectmgr.h"  #include "lltrans.h" +#include "lltransutil.h"  #include "lltracker.h"  #include "llviewerparcelmgr.h"  #include "llworldmapview.h" @@ -677,8 +678,8 @@ bool LLAppViewer::init()  	// Setup paths and LLTrans after LLUI::initClass has been called  	LLUI::setupPaths(); -	LLTrans::parseStrings("strings.xml", default_trans_args);		 -	LLTrans::parseLanguageStrings("language_settings.xml"); +	LLTransUtil::parseStrings("strings.xml", default_trans_args);		 +	LLTransUtil::parseLanguageStrings("language_settings.xml");  	LLWeb::initClass();			  // do this after LLUI  	LLTextEditor::setURLCallbacks(&LLWeb::loadURL, @@ -1740,8 +1741,8 @@ bool LLAppViewer::initConfiguration()  	}  	LLUI::setupPaths(); // setup paths for LLTrans based on settings files only -	LLTrans::parseStrings("strings.xml", default_trans_args); -	LLTrans::parseLanguageStrings("language_settings.xml"); +	LLTransUtil::parseStrings("strings.xml", default_trans_args); +	LLTransUtil::parseLanguageStrings("language_settings.xml");  	// - set procedural settings  	// Note: can't use LL_PATH_PER_SL_ACCOUNT for any of these since we haven't logged in yet  	gSavedSettings.setString("ClientSettingsFile",  diff --git a/indra/newview/llappviewerlinux.cpp b/indra/newview/llappviewerlinux.cpp index d02e86a557..cd35c28aa7 100644 --- a/indra/newview/llappviewerlinux.cpp +++ b/indra/newview/llappviewerlinux.cpp @@ -553,7 +553,7 @@ void LLAppViewerLinux::handleSyncCrashTrace()  void LLAppViewerLinux::handleCrashReporting(bool reportFreeze)  { -	std::string cmd =gDirUtilp->getAppRODataDir(); +	std::string cmd =gDirUtilp->getExecutableDir();  	cmd += gDirUtilp->getDirDelimiter();  #if LL_LINUX  	cmd += "linux-crash-logger.bin"; diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 1fc2245a8f..04a3b52e98 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -40,6 +40,12 @@  #	include <sys/stat.h>		// mkdir()  #endif +#if LL_LINUX && LL_GTK +extern "C" { +#	include "glib.h"		// g_spawn_command_line_async() +} +#endif +  #include "audioengine.h"  #ifdef LL_FMOD @@ -2872,10 +2878,12 @@ void update_app(BOOL mandatory, const std::string& auth_msg)  #if LL_WINDOWS  	notification_name += "Windows"; -#else +#elif LL_DARWIN  	notification_name += "Mac"; +#else +	notification_name += "Linux";	  #endif -	 +  	if (mandatory)  	{  		notification_name += "Mandatory"; @@ -2888,7 +2896,6 @@ void update_app(BOOL mandatory, const std::string& auth_msg)  	}  	LLNotifications::instance().add(notification_name, args, payload, update_dialog_callback); -	  }  bool update_dialog_callback(const LLSD& notification, const LLSD& response) @@ -2920,6 +2927,13 @@ bool update_dialog_callback(const LLSD& notification, const LLSD& response)  		}  		return false;  	} + +	// if a sim name was passed in via command line parameter (typically through a SLURL) +	if ( LLURLSimString::sInstance.mSimString.length() ) +	{ +		// record the location to start at next time +		gSavedSettings.setString("NextLoginLocation", LLURLSimString::sInstance.mSimString); +	}  	LLSD query_map = LLSD::emptyMap();  	// *TODO place os string in a global constant @@ -2980,13 +2994,6 @@ bool update_dialog_callback(const LLSD& notification, const LLSD& response)  		return false;  	} -	// if a sim name was passed in via command line parameter (typically through a SLURL) -	if ( LLURLSimString::sInstance.mSimString.length() ) -	{ -		// record the location to start at next time -		gSavedSettings.setString( "NextLoginLocation", LLURLSimString::sInstance.mSimString );  -	}; -  	LLAppViewer::sUpdaterInfo->mParams << "-url \"" << update_url.asString() << "\"";  	LL_DEBUGS("AppInit") << "Calling updater: " << LLAppViewer::sUpdaterInfo->mUpdateExePath << " " << LLAppViewer::sUpdaterInfo->mParams.str() << LL_ENDL; @@ -2995,13 +3002,6 @@ bool update_dialog_callback(const LLSD& notification, const LLSD& response)  	LLAppViewer::instance()->removeMarkerFile(); // In case updater fails  #elif LL_DARWIN -	// if a sim name was passed in via command line parameter (typically through a SLURL) -	if ( LLURLSimString::sInstance.mSimString.length() ) -	{ -		// record the location to start at next time -		gSavedSettings.setString( "NextLoginLocation", LLURLSimString::sInstance.mSimString );  -	}; -	  	LLAppViewer::sUpdaterInfo->mUpdateExePath = "'";  	LLAppViewer::sUpdaterInfo->mUpdateExePath += gDirUtilp->getAppRODataDir();  	LLAppViewer::sUpdaterInfo->mUpdateExePath += "/mac-updater.app/Contents/MacOS/mac-updater' -url \""; @@ -3015,7 +3015,49 @@ bool update_dialog_callback(const LLSD& notification, const LLSD& response)  	// Run the auto-updater.  	system(LLAppViewer::sUpdaterInfo->mUpdateExePath.c_str()); /* Flawfinder: ignore */ -#elif LL_LINUX || LL_SOLARIS +#elif (LL_LINUX || LL_SOLARIS) && LL_GTK +	// we tell the updater where to find the xml containing string +	// translations which it can use for its own UI +	std::string xml_strings_file = "strings.xml"; +	std::vector<std::string> xui_path_vec = LLUI::getXUIPaths(); +	std::string xml_search_paths; +	std::vector<std::string>::const_iterator iter; +	// build comma-delimited list of xml paths to pass to updater +	for (iter = xui_path_vec.begin(); iter != xui_path_vec.end(); ) +	{ +		std::string this_skin_dir = gDirUtilp->getDefaultSkinDir() +			+ gDirUtilp->getDirDelimiter() +			+ (*iter); +		llinfos << "Got a XUI path: " << this_skin_dir << llendl; +		xml_search_paths.append(this_skin_dir); +		++iter; +		if (iter != xui_path_vec.end()) +			xml_search_paths.append(","); // comma-delimit +	} +	// build the overall command-line to run the updater correctly +	update_exe_path =  +		gDirUtilp->getExecutableDir() + "/" + "linux-updater.bin" +  +		" --url \"" + update_url.asString() + "\"" + +		" --name \"" + LLAppViewer::instance()->getSecondLifeTitle() + "\"" + +		" --dest \"" + gDirUtilp->getAppRODataDir() + "\"" + +		" --stringsdir \"" + xml_search_paths + "\"" + +		" --stringsfile \"" + xml_strings_file + "\""; + +	LL_INFOS("AppInit") << "Calling updater: "  +			    << update_exe_path << LL_ENDL; + +	// *TODO: we could use the gdk equivilant to ensure the updater +	// gets started on the same screen. +	GError *error = NULL; +	if (!g_spawn_command_line_async(update_exe_path.c_str(), &error)) +	{ +		llerrs << "Failed to launch updater: " +		       << error->message +		       << llendl; +	} +	if (error) +		g_error_free(error); +#else  	OSMessageBox(LLTrans::getString("MBNoAutoUpdate"), LLStringUtil::null, OSMB_OK);  #endif  	LLAppViewer::instance()->forceQuit(); diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index ca9e89723c..7b01fe4280 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -32,6 +32,8 @@  #include "llviewerprecompiledheaders.h" +#include <sys/stat.h> +  #include "llviewertexturelist.h"  #include "imageids.h" @@ -61,8 +63,7 @@  #include "llviewerstats.h"  #include "pipeline.h"  #include "llappviewer.h" -#include "lluictrlfactory.h" // for LLXUIParser -#include <sys/stat.h> +#include "llxuiparser.h"  //////////////////////////////////////////////////////////////////////////// diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 8460e98fa3..3083a7f689 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -2628,6 +2628,45 @@ This update is not required, but we suggest you install it to improve performanc    <notification     icon="alertmodal.tga" +   name="DownloadLinuxMandatory" +   type="alertmodal"> +A new version of [SECOND_LIFE] is available. +[MESSAGE] +You must download this update to use [SECOND_LIFE]. +    <usetemplate +     name="okcancelbuttons" +     notext="Quit" +     yestext="Download"/> +  </notification> + +  <notification +   icon="alertmodal.tga" +   name="DownloadLinux" +   type="alertmodal"> +An updated version of [SECOND_LIFE] is available. +[MESSAGE] +This update is not required, but we suggest you install it to improve performance and stability. +    <usetemplate +     name="okcancelbuttons" +     notext="Continue" +     yestext="Download"/> +  </notification> + +  <notification +   icon="alertmodal.tga" +   name="DownloadLinuxReleaseForDownload" +   type="alertmodal"> +An updated version of [SECOND_LIFE] is available. +[MESSAGE] +This update is not required, but we suggest you install it to improve performance and stability. +    <usetemplate +     name="okcancelbuttons" +     notext="Continue" +     yestext="Download"/> +  </notification> + +  <notification +   icon="alertmodal.tga"     name="DownloadMacMandatory"     type="alertmodal">  A new version of [SECOND_LIFE] is available. diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index f89625c535..3d4ac94044 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -666,7 +666,7 @@ Expected .wav, .tga, .bmp, .jpg, .jpeg, or .bvh  	<string name="Any">Any</string>	  	<string name="You">You</string>							 -	<!-- puncutations --> +	<!-- punctuations -->  	<string name=":">:</string>			  	<string name=",">,</string>					  	<string name="...">...</string>					 @@ -936,4 +936,37 @@ If you continue to receive this message, contact customer support.      <string name="poofy skirt">poofy skirt</string>      <string name="tight skirt">tight skirt</string>	  	<string name="wrinkles">wrinkles</string>	 +	 +	<!-- Strings used by the (currently Linux) auto-updater app --> +	<string name="UpdaterWindowTitle"> +	  [SECOND_LIFE_VIEWER] Update +	</string> +	<string name="UpdaterNowUpdating"> +	  Now updating [SECOND_LIFE_VIEWER]... +	</string> +	<string name="UpdaterNowInstalling"> +	  Installing [SECOND_LIFE_VIEWER]... +	</string> +	<string name="UpdaterUpdatingDescriptive"> +	  Your [SECOND_LIFE_VIEWER] Viewer is being updated to the latest release.  This may take some time, so please be patient. +	</string> +	<string name="UpdaterProgressBarTextWithEllipses"> +	  Downloading update... +	</string> +	<string name="UpdaterProgressBarText"> +	  Downloading update +	</string> +	<string name="UpdaterFailDownloadTitle"> +	  Failed to download update +	</string> +	<string name="UpdaterFailUpdateDescriptive"> +	  An error occurred while updating Second Life. Please download the latest version from www.secondlife.com. +	</string> +	<string name="UpdaterFailInstallTitle"> +	  Failed to install update +	</string> +	<string name="UpdaterFailStartTitle"> +	  Failed to start viewer +	</string> +	  </strings> diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 54095f866f..9c122deba0 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -593,12 +593,15 @@ class LinuxManifest(ViewerManifest):              self.path("client-readme-voice.txt","README-linux-voice.txt")              self.path("client-readme-joystick.txt","README-linux-joystick.txt")              self.path("wrapper.sh","secondlife") -            self.path("handle_secondlifeprotocol.sh") -            self.path("register_secondlifeprotocol.sh") +            self.path("handle_secondlifeprotocol.sh", "etc/handle_secondlifeprotocol.sh") +            self.path("register_secondlifeprotocol.sh", "etc/register_secondlifeprotocol.sh") +            self.path("refresh_desktop_app_entry.sh", "etc/refresh_desktop_app_entry.sh") +            self.path("launch_url.sh","etc/launch_url.sh") +            self.path("install.sh")              self.end_prefix("linux_tools")          # Create an appropriate gridargs.dat for this package, denoting required grid. -        self.put_in_file(self.flags_list(), 'gridargs.dat') +        self.put_in_file(self.flags_list(), 'etc/gridargs.dat')      def package_finish(self): @@ -659,8 +662,8 @@ class Linux_i686Manifest(LinuxManifest):              pass          self.path("secondlife-stripped","bin/do-not-directly-run-secondlife-bin") -        self.path("../linux_crash_logger/linux-crash-logger-stripped","linux-crash-logger.bin") -        self.path("linux_tools/launch_url.sh","launch_url.sh") +        self.path("../linux_crash_logger/linux-crash-logger-stripped","bin/linux-crash-logger.bin") +        self.path("../linux_updater/linux-updater-stripped", "bin/linux-updater.bin")          if self.prefix("res-sdl"):              self.path("*")              # recurse @@ -702,7 +705,6 @@ class Linux_x86_64Manifest(LinuxManifest):          super(Linux_x86_64Manifest, self).construct()          self.path("secondlife-stripped","bin/do-not-directly-run-secondlife-bin")          self.path("../linux_crash_logger/linux-crash-logger-stripped","linux-crash-logger.bin") -        self.path("linux_tools/launch_url.sh","launch_url.sh")          if self.prefix("res-sdl"):              self.path("*")              # recurse | 
