diff options
author | Adam Moss <moss@lindenlab.com> | 2009-08-21 21:52:21 +0000 |
---|---|---|
committer | Adam Moss <moss@lindenlab.com> | 2009-08-21 21:52:21 +0000 |
commit | c3cbd049859c058526ae9a07a5cbfa7e51085943 (patch) | |
tree | 80f74a033a2f56f465cd4a67fa3e11a00bc18248 | |
parent | 87be6648d50efc55785e2298a9ed6ec0546da564 (diff) |
svn merge -r130238:130240 svn+ssh://svn.lindenlab.com/svn/linden/branches/linux-updater-6
QAR-1771 Linux Viewer Autoupdater + XUI-parse refactoring
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 |