summaryrefslogtreecommitdiff
path: root/indra/newview/llfloateruipreview.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/llfloateruipreview.cpp')
-rw-r--r--indra/newview/llfloateruipreview.cpp1550
1 files changed, 1550 insertions, 0 deletions
diff --git a/indra/newview/llfloateruipreview.cpp b/indra/newview/llfloateruipreview.cpp
new file mode 100644
index 0000000000..34db895b52
--- /dev/null
+++ b/indra/newview/llfloateruipreview.cpp
@@ -0,0 +1,1550 @@
+/**
+ * @file llfloateruipreview.cpp
+ * @brief Tool for previewing and editing floaters, plus localization tool integration
+ *
+ * $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$
+ */
+
+// Tool for previewing floaters and panels for localization and UI design purposes.
+// See: https://wiki.lindenlab.com/wiki/GUI_Preview_And_Localization_Tools
+// See: https://jira.lindenlab.com/browse/DEV-16869
+
+// *TODO: Translate error messgaes using notifications/alerts.xml
+
+#include "llviewerprecompiledheaders.h" // Precompiled headers
+
+#include "llfloateruipreview.h" // Own header
+
+// Internal utility
+#include "llrender.h"
+#include "llsdutil.h"
+#include "llxmltree.h"
+#include "llviewerwindow.h"
+#include "lllivefile.h"
+
+// XUI
+#include "lluictrlfactory.h"
+#include "llcombobox.h"
+#include "llresizebar.h"
+#include "llscrolllistitem.h"
+#include "llscrolllistctrl.h"
+#include "llfilepicker.h"
+#include "lldraghandle.h"
+#include "lllayoutstack.h"
+#include "llviewermenu.h"
+
+// Boost (for linux/unix command-line execv)
+#include <boost/tokenizer.hpp>
+#include <boost/shared_ptr.hpp>
+
+// External utility
+#include <string>
+
+#if LL_DARWIN
+#include <CoreFoundation/CFURL.h>
+#endif
+
+// Static initialization
+LLFloaterUIPreview* LLFloaterUIPreview::sInstance = NULL; // initialization of static instance pointer to NULL
+std::string LLFloaterUIPreview::mSavedEditorPath = std::string("");
+std::string LLFloaterUIPreview::mSavedEditorArgs = std::string("");
+std::string LLFloaterUIPreview::mSavedDiffPath = std::string("");
+static const S32 PRIMARY_FLOATER = 1;
+static const S32 SECONDARY_FLOATER = 2;
+
+static std::string get_xui_dir()
+{
+ std::string delim = gDirUtilp->getDirDelimiter();
+ return gDirUtilp->getAppRODataDir() + delim + std::string("skins") + delim + "default" + delim + "xui" + delim;
+}
+
+// Localization reset forcer -- ensures that when localization is temporarily changed for previewed floater, it is reset
+// Changes are made here
+LLLocalizationResetForcer::LLLocalizationResetForcer(S32 ID)
+{
+ mSavedLocalization = LLUI::sSettingGroups["config"]->getString("Language"); // save current localization setting
+ LLUI::sSettingGroups["config"]->setString("Language", LLFloaterUIPreview::getLocStr(ID));// hack language to be the one we want to preview floaters in
+ LLUI::setupPaths(); // forcibly reset XUI paths with this new language
+}
+
+// Actually reset in destructor
+// Changes are reversed here
+LLLocalizationResetForcer::~LLLocalizationResetForcer()
+{
+ LLUI::sSettingGroups["config"]->setString("Language", mSavedLocalization); // reset language to what it was before we changed it
+ LLUI::setupPaths(); // forcibly reset XUI paths with this new language
+}
+
+// Live file constructor
+// Needs full path for LLLiveFile but needs just file name for this code, hence the reduntant arguments; easier than separating later
+LLGUIPreviewLiveFile::LLGUIPreviewLiveFile(std::string path, std::string name, LLFloaterUIPreview* parent)
+ : mFileName(name),
+ mParent(parent),
+ mFirstFade(TRUE),
+ mFadeTimer(NULL),
+ LLLiveFile(path, 1.0)
+{}
+
+LLGUIPreviewLiveFile::~LLGUIPreviewLiveFile()
+{
+ mParent->mLiveFile = NULL;
+ if(mFadeTimer)
+ {
+ mFadeTimer->mParent = NULL;
+ // deletes itself; see lltimer.cpp
+ }
+}
+
+// Live file load
+bool LLGUIPreviewLiveFile::loadFile()
+{
+ mParent->displayFloater(FALSE,1); // redisplay the floater
+ if(mFirstFade) // only fade if it wasn't just clicked on; can't use "clicked" BOOL below because of an oddity with setting LLLiveFile initial state
+ {
+ mFirstFade = FALSE;
+ }
+ else
+ {
+ if(mFadeTimer)
+ {
+ mFadeTimer->mParent = NULL;
+ }
+ mFadeTimer = new LLFadeEventTimer(0.05f,this);
+ }
+ return true;
+}
+
+// Initialize fade event timer
+LLFadeEventTimer::LLFadeEventTimer(F32 refresh, LLGUIPreviewLiveFile* parent)
+ : mParent(parent),
+ mFadingOut(TRUE),
+ LLEventTimer(refresh)
+{
+ mOriginalColor = mParent->mParent->mDisplayedFloater->getBackgroundColor();
+}
+
+// Single tick of fade event timer: increment the color
+BOOL LLFadeEventTimer::tick()
+{
+ float diff = 0.04f;
+ if(TRUE == mFadingOut) // set fade for in/out color direction
+ {
+ diff = -diff;
+ }
+
+ if(NULL == mParent) // no more need to tick, so suicide
+ {
+ delete this;
+ return FALSE;
+ }
+
+ // Set up colors
+ LLColor4 bg_color = mParent->mParent->mDisplayedFloater->getBackgroundColor();
+ LLSD colors = bg_color.getValue();
+ LLSD colors_old = colors;
+
+ // Tick colors
+ colors[0] = colors[0].asReal() - diff; if(colors[0].asReal() < mOriginalColor.getValue()[0].asReal()) { colors[0] = colors_old[0]; }
+ colors[1] = colors[1].asReal() - diff; if(colors[1].asReal() < mOriginalColor.getValue()[1].asReal()) { colors[1] = colors_old[1]; }
+ colors[2] = colors[2].asReal() + diff; if(colors[2].asReal() > mOriginalColor.getValue()[2].asReal()) { colors[2] = colors_old[2]; }
+
+ // Clamp and set colors
+ bg_color.setValue(colors);
+ bg_color.clamp(); // make sure we didn't exceed [0,1]
+ mParent->mParent->mDisplayedFloater->setBackgroundColor(bg_color);
+
+ if(bg_color[2] <= 0.0f) // end of fade out, start fading in
+ {
+ mFadingOut = FALSE;
+ }
+
+ return FALSE;
+}
+
+void* create_overlap_panel(void* data)
+{
+ return new LLOverlapPanel();
+}
+
+// Constructor
+LLFloaterUIPreview::LLFloaterUIPreview(const LLSD& key)
+ : LLFloater(key),
+ mDisplayedFloater(NULL),
+ mDisplayedFloater_2(NULL),
+ mLiveFile(NULL),
+ // sHighlightingDiffs(FALSE),
+ mHighlightingOverlaps(FALSE),
+ mLastDisplayedX(0),
+ mLastDisplayedY(0)
+
+{
+ sInstance = this;
+ mFactoryMap["overlap_panel"] = LLCallbackMap(create_overlap_panel, NULL);
+ // called from floater reg: LLUICtrlFactory::getInstance()->buildFloater(this, "floater_ui_preview.xml");
+}
+
+// Destructor
+LLFloaterUIPreview::~LLFloaterUIPreview()
+{
+ // spawned floaters are deleted automatically, so we don't need to delete them here
+
+ // save contents of textfields so it can be restored later if the floater is created again this session
+ LLFloaterUIPreview::mSavedEditorPath = mEditorPathTextBox->getText();
+ LLFloaterUIPreview::mSavedEditorArgs = mEditorArgsTextBox->getText();
+ LLFloaterUIPreview::mSavedDiffPath = mDiffPathTextBox->getText();
+
+ // delete live file if it exists
+ if(sInstance->mLiveFile)
+ {
+ delete sInstance->mLiveFile;
+ sInstance->mLiveFile = NULL;
+ }
+
+ sInstance = NULL; // clear static pointer
+}
+
+// Perform post-build setup (defined in superclass)
+BOOL LLFloaterUIPreview::postBuild()
+{
+ LLPanel* main_panel_tmp = getChild<LLPanel>("main_panel"); // get a pointer to the main panel in order to...
+ mFileList = main_panel_tmp->getChild<LLScrollListCtrl>("name_list"); // save pointer to file list
+ // Double-click opens the floater, for convenience
+ mFileList->setDoubleClickCallback(onClickDisplayFloater, (void*)&PRIMARY_FLOATER);
+
+ // get pointers to buttons and link to callbacks
+ mLanguageSelection = main_panel_tmp->getChild<LLComboBox>("language_select_combo");
+ mLanguageSelection->setSelectionCallback(boost::bind(&LLFloaterUIPreview::onLanguageComboSelect, this, mLanguageSelection));
+ mLanguageSelection_2 = main_panel_tmp->getChild<LLComboBox>("language_select_combo_2");
+ mLanguageSelection_2->setSelectionCallback(boost::bind(&LLFloaterUIPreview::onLanguageComboSelect, this, mLanguageSelection));
+ LLPanel* editor_panel_tmp = main_panel_tmp->getChild<LLPanel>("editor_panel");
+ mDisplayFloaterBtn = main_panel_tmp->getChild<LLButton>("display_floater");
+ mDisplayFloaterBtn->setClickedCallback(onClickDisplayFloater, (void*)&PRIMARY_FLOATER);
+ mDisplayFloaterBtn_2 = main_panel_tmp->getChild<LLButton>("display_floater_2");
+ mDisplayFloaterBtn_2->setClickedCallback(onClickDisplayFloater, (void*)&SECONDARY_FLOATER);
+ mToggleOverlapButton = main_panel_tmp->getChild<LLButton>("toggle_overlap_panel");
+ mToggleOverlapButton->setClickedCallback(onClickToggleOverlapping, this);
+ mCloseOtherButton = main_panel_tmp->getChild<LLButton>("close_displayed_floater");
+ mCloseOtherButton->setClickedCallback(onClickCloseDisplayedFloater, (void*)&PRIMARY_FLOATER);
+ mCloseOtherButton_2 = main_panel_tmp->getChild<LLButton>("close_displayed_floater_2");
+ mCloseOtherButton_2->setClickedCallback(onClickCloseDisplayedFloater, (void*)&SECONDARY_FLOATER);
+ mEditFloaterBtn = main_panel_tmp->getChild<LLButton>("edit_floater");
+ mEditFloaterBtn->setClickedCallback(onClickEditFloater, this);
+ mExecutableBrowseButton = editor_panel_tmp->getChild<LLButton>("browse_for_executable");
+ LLPanel* vlt_panel_tmp = main_panel_tmp->getChild<LLPanel>("vlt_panel");
+ mExecutableBrowseButton->setClickedCallback(onClickBrowseForEditor, this);
+ mDiffBrowseButton = vlt_panel_tmp->getChild<LLButton>("browse_for_vlt_diffs");
+ mDiffBrowseButton->setClickedCallback(onClickBrowseForDiffs, NULL);
+ mToggleHighlightButton = vlt_panel_tmp->getChild<LLButton>("toggle_vlt_diff_highlight");
+ mToggleHighlightButton->setClickedCallback(onClickToggleDiffHighlighting, NULL);
+ main_panel_tmp->getChild<LLButton>("save_floater")->setClickedCallback(onClickSaveFloater, (void*)&PRIMARY_FLOATER);
+ main_panel_tmp->getChild<LLButton>("save_all_floaters")->setClickedCallback(onClickSaveAll, (void*)&PRIMARY_FLOATER);
+
+ // get pointers to text fields
+ mEditorPathTextBox = editor_panel_tmp->getChild<LLLineEditor>("executable_path_field");
+ mEditorArgsTextBox = editor_panel_tmp->getChild<LLLineEditor>("executable_args_field");
+ mDiffPathTextBox = vlt_panel_tmp->getChild<LLLineEditor>("vlt_diff_path_field");
+
+ // *HACK: restored saved editor path and args to textfields
+ mEditorPathTextBox->setText(LLFloaterUIPreview::mSavedEditorPath);
+ mEditorArgsTextBox->setText(LLFloaterUIPreview::mSavedEditorArgs);
+ mDiffPathTextBox->setText(LLFloaterUIPreview::mSavedDiffPath);
+
+ // Set up overlap panel
+ mOverlapPanel = getChild<LLOverlapPanel>("overlap_panel");
+
+ sInstance->childSetVisible("overlap_scroll", mHighlightingOverlaps);
+
+ mDelim = gDirUtilp->getDirDelimiter(); // initialize delimiter to dir sep slash
+
+ // refresh list of available languages (EN will still be default)
+ BOOL found = TRUE;
+ BOOL found_en_us = FALSE;
+ std::string language_directory;
+ std::string xui_dir = get_xui_dir(); // directory containing localizations -- don't forget trailing delim
+ mLanguageSelection->removeall(); // clear out anything temporarily in list from XML
+ while(found) // for every directory
+ {
+ if((found = gDirUtilp->getNextFileInDir(xui_dir, "*", language_directory, FALSE))) // get next directory
+ {
+ std::string full_path = xui_dir + language_directory;
+ if(LLFile::isfile(full_path.c_str())) // if it's not a directory, skip it
+ {
+ continue;
+ }
+
+ if(strncmp("template",language_directory.c_str(),8) && -1 == language_directory.find(".")) // if it's not the template directory or a hidden directory
+ {
+ if(!strncmp("en",language_directory.c_str(),5)) // remember if we've seen en, so we can make it default
+ {
+ found_en_us = TRUE;
+ }
+ else
+ {
+ mLanguageSelection->add(std::string(language_directory)); // add it to the language selection dropdown menu
+ mLanguageSelection_2->add(std::string(language_directory));
+ }
+ }
+ }
+ }
+ if(found_en_us)
+ {
+ mLanguageSelection->add(std::string("en"),ADD_TOP); // make en first item if we found it
+ mLanguageSelection_2->add(std::string("en"),ADD_TOP);
+ }
+ else
+ {
+ std::string warning = std::string("No EN localization found; check your XUI directories!");
+ popupAndPrintWarning(warning);
+ }
+ mLanguageSelection->selectFirstItem(); // select the first item
+ mLanguageSelection_2->selectFirstItem();
+
+ refreshList(); // refresh the list of available floaters
+
+ return TRUE;
+}
+
+// Callback for language combo box selection: refresh current floater when you change languages
+void LLFloaterUIPreview::onLanguageComboSelect(LLUICtrl* ctrl)
+{
+ LLComboBox* caller = dynamic_cast<LLComboBox*>(ctrl);
+ if (!caller)
+ return;
+ if(caller->getName() == std::string("language_select_combo"))
+ {
+ if(mDisplayedFloater)
+ {
+ onClickCloseDisplayedFloater((void*)&PRIMARY_FLOATER);
+ displayFloater(TRUE,1);
+ }
+ }
+ else
+ {
+ if(mDisplayedFloater_2)
+ {
+ onClickCloseDisplayedFloater((void*)&PRIMARY_FLOATER);
+ displayFloater(TRUE,2); // *TODO: make take an arg
+ }
+ }
+
+}
+
+// Close click handler -- delete my displayed floater if it exists
+void LLFloaterUIPreview::onClose(bool app_quitting)
+{
+ if(!app_quitting && sInstance && sInstance->mDisplayedFloater)
+ {
+ onClickCloseDisplayedFloater((void*)&PRIMARY_FLOATER);
+ onClickCloseDisplayedFloater((void*)&SECONDARY_FLOATER);
+ delete sInstance->mDisplayedFloater;
+ sInstance->mDisplayedFloater = NULL;
+ }
+ destroy();
+}
+
+// Error handling (to avoid code repetition)
+// *TODO: this is currently unlocalized. Add to alerts/notifications.xml, someday, maybe.
+void LLFloaterUIPreview::popupAndPrintWarning(std::string& warning)
+{
+ llwarns << warning << llendl;
+ LLSD args;
+ args["MESSAGE"] = warning;
+ LLNotifications::instance().add("GenericAlert", args);
+}
+
+// Get localization string from drop-down menu
+std::string LLFloaterUIPreview::getLocStr(S32 ID)
+{
+ if(ID == 1)
+ {
+ return sInstance->mLanguageSelection->getSelectedItemLabel(0);
+ }
+ else
+ {
+ return sInstance->mLanguageSelection_2->getSelectedItemLabel(0);
+ }
+}
+
+// Get localized directory (build path from data directory to XUI files, substituting localization string in for language)
+std::string LLFloaterUIPreview::getLocalizedDirectory()
+{
+ return get_xui_dir() + (sInstance ? getLocStr(1) : "en") + mDelim; // e.g. "C:/Code/guipreview/indra/newview/skins/xui/en/";
+}
+
+// Refresh the list of floaters by doing a directory traverse for XML XUI floater files
+// Could be used to grab any specific language's list of compatible floaters, but currently it's just used to get all of them
+void LLFloaterUIPreview::refreshList()
+{
+ // Note: the mask doesn't seem to accept regular expressions, so there need to be two directory searches here
+ mFileList->clearRows(); // empty list
+ std::string name;
+ BOOL found = TRUE;
+ while(found) // for every floater file that matches the pattern
+ {
+ if((found = gDirUtilp->getNextFileInDir(getLocalizedDirectory(), "floater_*.xml", name, FALSE))) // get next file matching pattern
+ {
+ addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path)
+ }
+ }
+ found = TRUE;
+ while(found) // for every menu file that matches the pattern
+ {
+ if((found = gDirUtilp->getNextFileInDir(getLocalizedDirectory(), "menu_*.xml", name, FALSE))) // get next file matching pattern
+ {
+ addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path)
+ }
+ }
+ found = TRUE;
+ while(found) // for every panel file that matches the pattern
+ {
+ if((found = gDirUtilp->getNextFileInDir(getLocalizedDirectory(), "panel_*.xml", name, FALSE))) // get next file matching pattern
+ {
+ addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path)
+ }
+ }
+
+ if(!mFileList->isEmpty()) // if there were any matching files, just select the first one (so we don't have to worry about disabling buttons when no entry is selected)
+ {
+ mFileList->selectFirstItem();
+ }
+}
+
+// Add a single entry to the list of available floaters
+// Note: no deduplification (shouldn't be necessary)
+void LLFloaterUIPreview::addFloaterEntry(const std::string& path)
+{
+ LLUUID* entry_id = new LLUUID(); // create a new UUID
+ entry_id->generate(path);
+ const LLUUID& entry_id_ref = *entry_id; // get a reference to the UUID for the LLSD block
+
+ // fill LLSD column entry: initialize row/col structure
+ LLSD row;
+ row["id"] = entry_id_ref;
+ LLSD& columns = row["columns"];
+
+ // Get name of floater:
+ LLXmlTree xml_tree;
+ std::string full_path = getLocalizedDirectory() + path; // get full path
+ BOOL success = xml_tree.parseFile(full_path.c_str(), TRUE); // parse xml
+ std::string entry_name;
+ std::string entry_title;
+ if(success)
+ {
+ // get root (or error handle)
+ LLXmlTreeNode* root_floater = xml_tree.getRoot();
+ if (!root_floater)
+ {
+ std::string warning = std::string("No root node found in XUI file: ") + path;
+ popupAndPrintWarning(warning);
+ return;
+ }
+
+ // get name
+ root_floater->getAttributeString("name",entry_name);
+ if(std::string("") == entry_name)
+ {
+ entry_name = "Error: unable to load " + std::string(path); // set to error state if load fails
+ }
+
+ // get title
+ root_floater->getAttributeString("title",entry_title); // some don't have a title, and some have title = "(unknown)", so just leave it blank if it fails
+ }
+ else
+ {
+ std::string warning = std::string("Unable to parse XUI file: ") + path; // error handling
+ popupAndPrintWarning(warning);
+ if(mLiveFile)
+ {
+ delete mLiveFile;
+ mLiveFile = NULL;
+ }
+ return;
+ }
+
+ // Fill floater title column
+ columns[0]["column"] = "title_column";
+ columns[0]["type"] = "text";
+ columns[0]["value"] = entry_title;
+
+ // Fill floater path column
+ columns[1]["column"] = "file_column";
+ columns[1]["type"] = "text";
+ columns[1]["value"] = std::string(path);
+
+ // Fill floater name column
+ columns[2]["column"] = "top_level_node_column";
+ columns[2]["type"] = "text";
+ columns[2]["value"] = entry_name;
+
+ mFileList->addElement(row); // actually add to list
+}
+
+// Respond to button click to display/refresh currently-selected floater
+void LLFloaterUIPreview::onClickDisplayFloater(void* data)
+{
+ S32 caller_id = *((S32*)data);
+ displayFloater(TRUE, caller_id);
+ if(caller_id == 1)
+ {
+ sInstance->mDisplayedFloater->center(); // move displayed floater to the center of the screen
+ }
+}
+
+// Saves the current floater/panel
+void LLFloaterUIPreview::onClickSaveFloater(void* data)
+{
+ S32 caller_id = *((S32*)data);
+ displayFloater(TRUE, caller_id, true);
+ if(caller_id == 1)
+ {
+ sInstance->mDisplayedFloater->center(); // move displayed floater to the center of the screen
+ }
+}
+
+// Saves all floater/panels
+void LLFloaterUIPreview::onClickSaveAll(void* data)
+{
+ S32 caller_id = *((S32*)data);
+ int listSize = sInstance->mFileList->getItemCount();
+
+ for (int index = 0; index < listSize; index++)
+ {
+ sInstance->mFileList->selectNthItem(index);
+ displayFloater(TRUE, caller_id, true);
+ }
+}
+
+// Given path to floater or panel XML file "filename.xml",
+// returns "filename_new.xml"
+static std::string append_new_to_xml_filename(const std::string& path)
+{
+ std::string full_filename = gDirUtilp->findSkinnedFilename(LLUI::getLocalizedSkinPath(), path);
+ std::string::size_type extension_pos = full_filename.rfind(".xml");
+ full_filename.resize(extension_pos);
+ full_filename += "_new.xml";
+ return full_filename;
+}
+
+// Actually display the floater
+// Only set up a new live file if this came from a click (at which point there should be no existing live file), rather than from the live file's update itself;
+// otherwise, we get an infinite loop as the live file keeps recreating itself. That means this function is generally called twice.
+void LLFloaterUIPreview::displayFloater(BOOL click, S32 ID, bool save)
+{
+ // Convince UI that we're in a different language (the one selected on the drop-down menu)
+ LLLocalizationResetForcer reset_forcer(ID); // save old language in reset forcer object (to be reset upon destruction when it falls out of scope)
+
+ LLPreviewedFloater** floaterp = (ID == 1 ? &(sInstance->mDisplayedFloater) : &(sInstance->mDisplayedFloater_2));
+ if(ID == 1)
+ {
+ BOOL floater_already_open = sInstance->mDisplayedFloater != NULL;
+ if(floater_already_open) // if we are already displaying a floater
+ {
+ sInstance->mLastDisplayedX = sInstance->mDisplayedFloater->calcScreenRect().mLeft; // save floater's last known position to put the new one there
+ sInstance->mLastDisplayedY = sInstance->mDisplayedFloater->calcScreenRect().mBottom;
+ delete sInstance->mDisplayedFloater; // delete it (this closes it too)
+ sInstance->mDisplayedFloater = NULL; // and reset the pointer
+ }
+ }
+ else
+ {
+ if(sInstance->mDisplayedFloater_2 != NULL)
+ {
+ delete sInstance->mDisplayedFloater_2;
+ sInstance->mDisplayedFloater_2 = NULL;
+ }
+ }
+
+ std::string path = sInstance->mFileList->getSelectedItemLabel(1); // get the path of the currently-selected floater
+ if(std::string("") == path) // if no item is selected
+ {
+ return; // ignore click (this can only happen with empty list; otherwise an item is always selected)
+ }
+
+ *floaterp = new LLPreviewedFloater();
+
+ if(!strncmp(path.c_str(),"floater_",8)) // if it's a floater
+ {
+ if (save)
+ {
+ LLXMLNodePtr floater_write = new LLXMLNode();
+ LLUICtrlFactory::getInstance()->buildFloater(*floaterp, path, FALSE, floater_write); // just build it
+
+ if (!floater_write->isNull())
+ {
+ std::string full_filename = append_new_to_xml_filename(path);
+ LLFILE* floater_temp = LLFile::fopen(full_filename.c_str(), "w");
+ LLXMLNode::writeHeaderToFile(floater_temp);
+ floater_write->writeToFile(floater_temp);
+ fclose(floater_temp);
+ }
+ }
+ else
+ {
+ LLUICtrlFactory::getInstance()->buildFloater(*floaterp, path, TRUE); // just build it
+ }
+
+ }
+ else if (!strncmp(path.c_str(),"menu_",5)) // if it's a menu
+ {
+ if (save)
+ {
+ LLXMLNodePtr menu_write = new LLXMLNode();
+ LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>(path, gMenuHolder, menu_write);
+
+ if (!menu_write->isNull())
+ {
+ std::string full_filename = append_new_to_xml_filename(path);
+ LLFILE* menu_temp = LLFile::fopen(full_filename.c_str(), "w");
+ LLXMLNode::writeHeaderToFile(menu_temp);
+ menu_write->writeToFile(menu_temp);
+ fclose(menu_temp);
+ }
+
+ delete menu;
+ }
+ }
+ else // if it is a panel...
+ {
+ static LLUICachedControl<S32> floater_header_size ("UIFloaterHeaderSize", 0);
+
+ LLPanel::Params panel_params;
+ LLPanel* panel = LLUICtrlFactory::create<LLPanel>(panel_params); // create a new panel
+
+ if (save)
+ {
+ LLXMLNodePtr panel_write = new LLXMLNode();
+ LLUICtrlFactory::getInstance()->buildPanel(panel, path, panel_write); // build it
+
+ if (!panel_write->isNull())
+ {
+ std::string full_filename = append_new_to_xml_filename(path);
+ LLFILE* panel_temp = LLFile::fopen(full_filename.c_str(), "w");
+ LLXMLNode::writeHeaderToFile(panel_temp);
+ panel_write->writeToFile(panel_temp);
+ fclose(panel_temp);
+ }
+ }
+ else
+ {
+ LLUICtrlFactory::getInstance()->buildPanel(panel, path); // build it
+ LLRect new_size = panel->getRect(); // get its rectangle
+ panel->setOrigin(0,0); // reset its origin point so it's not offset by -left or other XUI attributes
+ (*floaterp)->setTitle(path); // use the file name as its title, since panels have no guaranteed meaningful name attribute
+ panel->setUseBoundingRect(TRUE); // enable the use of its outer bounding rect (normally disabled because it's O(n) on the number of sub-elements)
+ panel->updateBoundingRect(); // update bounding rect
+ LLRect bounding_rect = panel->getBoundingRect(); // get the bounding rect
+ LLRect panel_rect = panel->getRect(); // get the panel's rect
+ LLRect new_rect = panel_rect.unionWith(bounding_rect); // union them to make sure we get the biggest one possible
+ (*floaterp)->reshape(new_rect.getWidth(), new_rect.getHeight() + floater_header_size); // reshape floater to match the union rect's dimensions
+ panel->reshape(new_rect.getWidth(), new_rect.getHeight()); // reshape panel to match the union rect's dimensions as well (both are needed)
+ (*floaterp)->addChild(panel); // add panel as child
+ (*floaterp)->openFloater(); // open floater (needed?)
+ }
+ }
+
+ if(ID == 1)
+ {
+ (*floaterp)->setOrigin(sInstance->mLastDisplayedX, sInstance->mLastDisplayedY);
+ }
+
+ // *HACK: Remove ability to close it; if you close it, its destructor gets called, but we don't know it's null and try to delete it again,
+ // resulting in a double free
+ (*floaterp)->setCanClose(FALSE);
+
+ if(ID == 1)
+ {
+ sInstance->mCloseOtherButton->setEnabled(TRUE); // enable my floater's close button
+ }
+ else
+ {
+ sInstance->mCloseOtherButton_2->setEnabled(TRUE);
+ }
+
+ // *TODO: Make the secondary floater pop up next to the primary one. Doesn't seem to always work if secondary was up first...
+ if((sInstance->mDisplayedFloater && ID == 2) || (sInstance->mDisplayedFloater_2 && ID == 1))
+ {
+ sInstance->mDisplayedFloater_2->setSnapTarget(sInstance->mDisplayedFloater->getHandle());
+ sInstance->mDisplayedFloater->addDependentFloater(sInstance->mDisplayedFloater_2);
+ }
+
+ // Add localization to title so user knows whether it's localized or defaulted to en
+ std::string full_path = sInstance->getLocalizedDirectory() + path;
+ std::string floater_lang = "EN";
+ llstat dummy;
+ if(!LLFile::stat(full_path.c_str(), &dummy)) // if the file does not exist
+ {
+ floater_lang = getLocStr(ID);
+ }
+ std::string new_title = (*floaterp)->getTitle() + std::string(" [") + floater_lang +
+ (ID == 1 ? " - Primary" : " - Secondary") + std::string("]");
+ (*floaterp)->setTitle(new_title);
+
+ if(click && ID == 1 && !save)
+ {
+ // set up live file to track it
+ if(sInstance->mLiveFile)
+ {
+ delete sInstance->mLiveFile;
+ sInstance->mLiveFile = NULL;
+ }
+ sInstance->mLiveFile = new LLGUIPreviewLiveFile(std::string(full_path.c_str()),std::string(path.c_str()),sInstance);
+ sInstance->mLiveFile->checkAndReload();
+ sInstance->mLiveFile->addToEventTimer();
+ }
+
+ if(ID == 1)
+ {
+ sInstance->mToggleOverlapButton->setEnabled(TRUE);
+ }
+
+ if(LLView::sHighlightingDiffs && click && ID == 1)
+ {
+ sInstance->highlightChangedElements();
+ }
+
+ if(ID == 1)
+ {
+ sInstance->mOverlapMap.clear();
+ LLView::sPreviewClickedElement = NULL; // stop overlapping elements from drawing
+ sInstance->mOverlapPanel->mLastClickedElement = NULL;
+ sInstance->findOverlapsInChildren((LLView*)sInstance->mDisplayedFloater);
+
+ // highlight and enable them
+ if(sInstance->mHighlightingOverlaps)
+ {
+ for(OverlapMap::iterator iter = sInstance->mOverlapMap.begin(); iter != sInstance->mOverlapMap.end(); ++iter)
+ {
+ LLView* viewp = iter->first;
+ LLView::sPreviewHighlightedElements.insert(viewp);
+ }
+ }
+ else if(LLView::sHighlightingDiffs)
+ {
+ sInstance->highlightChangedElements();
+ }
+ }
+
+ // NOTE: language is reset here automatically when the reset forcer object falls out of scope (see header for details)
+}
+
+// Respond to button click to edit currently-selected floater
+void LLFloaterUIPreview::onClickEditFloater(void*)
+{
+ std::string file_name = sInstance->mFileList->getSelectedItemLabel(1); // get the file name of the currently-selected floater
+ if(std::string("") == file_name) // if no item is selected
+ {
+ return; // ignore click
+ }
+ std::string path = sInstance->getLocalizedDirectory() + file_name;
+
+ // stat file to see if it exists (some localized versions may not have it there are no diffs, and then we try to open an nonexistent file)
+ llstat dummy;
+ if(LLFile::stat(path.c_str(), &dummy)) // if the file does not exist
+ {
+ std::string warning = "No file for this floater exists in the selected localization. Opening the EN version instead.";
+ popupAndPrintWarning(warning);
+
+ path = get_xui_dir() + sInstance->mDelim + "en" + sInstance->mDelim + file_name; // open the en version instead, by default
+ }
+
+ // get executable path
+ const char* exe_path_char;
+ std::string path_in_textfield = sInstance->mEditorPathTextBox->getText();
+ if(std::string("") != path_in_textfield) // if the text field is not emtpy, use its path
+ {
+ exe_path_char = path_in_textfield.c_str();
+ }
+ else // otherwise use the path specified by the environment variable
+ {
+ exe_path_char = getenv("LL_XUI_EDITOR");
+ }
+
+ // error check executable path
+ if(NULL == exe_path_char)
+ {
+ std::string warning = "Select an editor by setting the environment variable LL_XUI_EDITOR or specifying its path in the \"Editor Path\" field.";
+ popupAndPrintWarning(warning);
+ return;
+ }
+ std::string exe_path = exe_path_char; // do this after error check, otherwise internal strlen call fails on bad char*
+
+ // remove any quotes; they're added back in later where necessary
+ int found_at;
+ while((found_at = exe_path.find("\"")) != -1 || (found_at = exe_path.find("'")) != -1)
+ {
+ exe_path.erase(found_at,1);
+ }
+
+ llstat s;
+ if(!LLFile::stat(exe_path.c_str(), &s)) // If the executable exists
+ {
+ // build paths and arguments
+ std::string args;
+ std::string custom_args = sInstance->mEditorArgsTextBox->getText();
+ int position_of_file = custom_args.find(std::string("%FILE%"), 0); // prepare to replace %FILE% with actual file path
+ std::string first_part_of_args = "";
+ std::string second_part_of_args = "";
+ if(-1 == position_of_file) // default: Executable.exe File.xml
+ {
+ args = std::string("\"") + path + std::string("\""); // execute the command Program.exe "File.xml"
+ }
+ else // use advanced command-line arguments, e.g. "Program.exe -safe File.xml" -windowed for "-safe %FILE% -windowed"
+ {
+ first_part_of_args = custom_args.substr(0,position_of_file); // get part of args before file name
+ second_part_of_args = custom_args.substr(position_of_file+6,custom_args.length()); // get part of args after file name
+ custom_args = first_part_of_args + std::string("\"") + path + std::string("\"") + second_part_of_args; // replace %FILE% with "<file path>" and put back together
+ args = custom_args; // and save in the variable that is actually used
+ }
+
+ // find directory in which executable resides by taking everything after last slash
+ int last_slash_position = exe_path.find_last_of(sInstance->mDelim);
+ if(-1 == last_slash_position)
+ {
+ std::string warning = std::string("Unable to find a valid path to the specified executable for XUI XML editing: ") + exe_path;
+ popupAndPrintWarning(warning);
+ return;
+ }
+ std::string exe_dir = exe_path.substr(0,last_slash_position); // strip executable off, e.g. get "C:\Program Files\TextPad 5" (with or without trailing slash)
+
+#if LL_WINDOWS
+ PROCESS_INFORMATION pinfo;
+ STARTUPINFOA sinfo;
+ memset(&sinfo, 0, sizeof(sinfo));
+ memset(&pinfo, 0, sizeof(pinfo));
+
+ std::string exe_name = exe_path.substr(last_slash_position+1);
+ args = exe_name + std::string(" ") + args; // and prepend the executable name, so we get 'Program.exe "Arg1"'
+
+ char *args2 = new char[args.size() + 1]; // Windows requires that the second parameter to CreateProcessA be a writable (non-const) string...
+ strcpy(args2, args.c_str());
+
+ if(!CreateProcessA(exe_path.c_str(), args2, NULL, NULL, FALSE, 0, NULL, exe_dir.c_str(), &sinfo, &pinfo))
+ {
+ // DWORD dwErr = GetLastError();
+ std::string warning = "Creating editor process failed!";
+ popupAndPrintWarning(warning);
+ }
+ else
+ {
+ // foo = pinfo.dwProcessId; // get your pid here if you want to use it later on
+ // sGatewayHandle = pinfo.hProcess;
+ CloseHandle(pinfo.hThread); // stops leaks - nothing else
+ }
+
+ delete[] args2;
+#else // if !LL_WINDOWS
+ // This code was copied from the code to run SLVoice, with some modification; should work in UNIX (Mac/Darwin or Linux)
+ {
+ std::vector<std::string> arglist;
+ arglist.push_back(exe_path.c_str());
+
+ // Split the argument string into separate strings for each argument
+ typedef boost::tokenizer< boost::char_separator<char> > tokenizer;
+ boost::char_separator<char> sep("","\" ", boost::drop_empty_tokens);
+
+ tokenizer tokens(args, sep);
+ tokenizer::iterator token_iter;
+ BOOL inside_quotes = FALSE;
+ BOOL last_was_space = FALSE;
+ for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter)
+ {
+ if(!strncmp("\"",(*token_iter).c_str(),2))
+ {
+ inside_quotes = !inside_quotes;
+ }
+ else if(!strncmp(" ",(*token_iter).c_str(),2))
+ {
+ if(inside_quotes)
+ {
+ arglist.back().append(std::string(" "));
+ last_was_space = TRUE;
+ }
+ }
+ else
+ {
+ std::string to_push = *token_iter;
+ if(last_was_space)
+ {
+ arglist.back().append(to_push);
+ last_was_space = FALSE;
+ }
+ else
+ {
+ arglist.push_back(to_push);
+ }
+ }
+ }
+
+ // create an argv vector for the child process
+ char **fakeargv = new char*[arglist.size() + 1];
+ int i;
+ for(i=0; i < arglist.size(); i++)
+ fakeargv[i] = const_cast<char*>(arglist[i].c_str());
+
+ fakeargv[i] = NULL;
+
+ fflush(NULL); // flush all buffers before the child inherits them
+ pid_t id = vfork();
+ if(id == 0)
+ {
+ // child
+ execv(exe_path.c_str(), fakeargv);
+
+ // If we reach this point, the exec failed.
+ // Use _exit() instead of exit() per the vfork man page.
+ std::string warning = "Creating editor process failed (vfork/execv)!";
+ popupAndPrintWarning(warning);
+ _exit(0);
+ }
+
+ // parent
+ delete[] fakeargv;
+ // sGatewayPID = id;
+ }
+#endif // LL_WINDOWS
+ }
+ else
+ {
+ std::string warning = "Unable to find path to external XML editor for XUI preview tool";
+ popupAndPrintWarning(warning);
+ }
+}
+
+// Respond to button click to browse for an executable with which to edit XML files
+void LLFloaterUIPreview::onClickBrowseForEditor(void*)
+{
+ // create load dialog box
+ LLFilePicker::ELoadFilter type = (LLFilePicker::ELoadFilter)((intptr_t)((void*)LLFilePicker::FFLOAD_ALL)); // nothing for *.exe so just use all
+ LLFilePicker& picker = LLFilePicker::instance();
+ if (!picker.getOpenFile(type)) // user cancelled -- do nothing
+ {
+ return;
+ }
+
+ // put the selected path into text field
+ const std::string chosen_path = picker.getFirstFile();
+ std::string executable_path = chosen_path;
+#if LL_DARWIN
+ // on Mac, if it's an application bundle, figure out the actual path from the Info.plist file
+ CFStringRef path_cfstr = CFStringCreateWithCString(kCFAllocatorDefault, chosen_path.c_str(), kCFStringEncodingMacRoman); // get path as a CFStringRef
+ CFURLRef path_url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path_cfstr, kCFURLPOSIXPathStyle, TRUE); // turn it into a CFURLRef
+ CFBundleRef chosen_bundle = CFBundleCreate(kCFAllocatorDefault, path_url); // get a handle for the bundle
+ if(NULL != chosen_bundle)
+ {
+ CFDictionaryRef bundleInfoDict = CFBundleGetInfoDictionary(chosen_bundle); // get the bundle's dictionary
+ if(NULL != bundleInfoDict)
+ {
+ CFStringRef executable_cfstr = (CFStringRef)CFDictionaryGetValue(bundleInfoDict, CFSTR("CFBundleExecutable")); // get the name of the actual executable (e.g. TextEdit or firefox-bin)
+ int max_file_length = 256; // (max file name length is 255 in OSX)
+ char executable_buf[max_file_length];
+ if(CFStringGetCString(executable_cfstr, executable_buf, max_file_length, kCFStringEncodingMacRoman)) // convert CFStringRef to char*
+ {
+ executable_path += std::string("/Contents/MacOS/") + std::string(executable_buf); // append path to executable directory and then executable name to exec path
+ }
+ else
+ {
+ std::string warning = "Unable to get CString from CFString for executable path";
+ popupAndPrintWarning(warning);
+ }
+ }
+ else
+ {
+ std::string warning = "Unable to get bundle info dictionary from application bundle";
+ popupAndPrintWarning(warning);
+ }
+ }
+ else
+ {
+ if(-1 != executable_path.find(".app")) // only warn if this path actually had ".app" in it, i.e. it probably just wasn'nt an app bundle and that's okay
+ {
+ std::string warning = std::string("Unable to get bundle from path \"") + chosen_path + std::string("\"");
+ popupAndPrintWarning(warning);
+ }
+ }
+
+#endif
+ sInstance->mEditorPathTextBox->setText(std::string(executable_path)); // copy the path to the executable to the textfield for display and later fetching
+}
+
+// Respond to button click to browse for a VLT-generated diffs file
+void LLFloaterUIPreview::onClickBrowseForDiffs(void*)
+{
+ // create load dialog box
+ LLFilePicker::ELoadFilter type = (LLFilePicker::ELoadFilter)((intptr_t)((void*)LLFilePicker::FFLOAD_XML)); // nothing for *.exe so just use all
+ LLFilePicker& picker = LLFilePicker::instance();
+ if (!picker.getOpenFile(type)) // user cancelled -- do nothing
+ {
+ return;
+ }
+
+ // put the selected path into text field
+ const std::string chosen_path = picker.getFirstFile();
+ sInstance->mDiffPathTextBox->setText(std::string(chosen_path)); // copy the path to the executable to the textfield for display and later fetching
+ if(LLView::sHighlightingDiffs) // if we're already highlighting, toggle off and then on so we get the data from the new file
+ {
+ onClickToggleDiffHighlighting(NULL);
+ onClickToggleDiffHighlighting(NULL);
+ }
+}
+
+void LLFloaterUIPreview::onClickToggleDiffHighlighting(void*)
+{
+ if(sInstance->mHighlightingOverlaps)
+ {
+ onClickToggleOverlapping(NULL);
+ sInstance->mToggleOverlapButton->toggleState();
+ }
+
+ LLView::sPreviewHighlightedElements.clear(); // clear lists first
+ sInstance->mDiffsMap.clear();
+ sInstance->mFileList->clearHighlightedItems();
+
+ if(LLView::sHighlightingDiffs) // Turning highlighting off
+ {
+ LLView::sHighlightingDiffs = !sInstance->sHighlightingDiffs;
+ return;
+ }
+ else // Turning highlighting on
+ {
+ // Get the file and make sure it exists
+ std::string path_in_textfield = sInstance->mDiffPathTextBox->getText(); // get file path
+ BOOL error = FALSE;
+
+ if(std::string("") == path_in_textfield) // check for blank file
+ {
+ std::string warning = "Unable to highlight differences because no file was provided; fill in the relevant text field";
+ popupAndPrintWarning(warning);
+ error = TRUE;
+ }
+
+ llstat dummy;
+ if(LLFile::stat(path_in_textfield.c_str(), &dummy) && !error) // check if the file exists (empty check is reduntant but useful for the informative error message)
+ {
+ std::string warning = std::string("Unable to highlight differences because an invalid path to a difference file was provided:\"") + path_in_textfield + "\"";
+ popupAndPrintWarning(warning);
+ error = TRUE;
+ }
+
+ // Build a list of changed elements as given by the XML
+ std::list<std::string> changed_element_names;
+ LLXmlTree xml_tree;
+ BOOL success = xml_tree.parseFile(path_in_textfield.c_str(), TRUE);
+
+ if(success && !error)
+ {
+ LLXmlTreeNode* root_floater = xml_tree.getRoot();
+ if(!strncmp("XuiDelta",root_floater->getName().c_str(),9))
+ {
+ for (LLXmlTreeNode* child = root_floater->getFirstChild(); // get the first child first, then below get the next one; otherwise the iterator is invalid (bug or feature in XML code?)
+ child != NULL;
+ child = root_floater->getNextChild()) // get child for next iteration
+ {
+ if(!strncmp("file",child->getName().c_str(),5))
+ {
+ sInstance->scanDiffFile(child);
+ }
+ else if(!strncmp("error",child->getName().c_str(),6))
+ {
+ std::string error_file, error_message;
+ child->getAttributeString("filename",error_file);
+ child->getAttributeString("message",error_message);
+ if(sInstance->mDiffsMap.find(error_file) != sInstance->mDiffsMap.end())
+ {
+ sInstance->mDiffsMap.insert(std::make_pair(error_file,std::make_pair(StringListPtr(new StringList), StringListPtr(new StringList))));
+ }
+ sInstance->mDiffsMap[error_file].second->push_back(error_message);
+ }
+ else
+ {
+ std::string warning = std::string("Child was neither a file or an error, but rather the following:\"") + std::string(child->getName()) + "\"";
+ popupAndPrintWarning(warning);
+ error = TRUE;
+ break;
+ }
+ }
+ }
+ else
+ {
+ std::string warning = std::string("Root node not named XuiDelta:\"") + path_in_textfield + "\"";
+ popupAndPrintWarning(warning);
+ error = TRUE;
+ }
+ }
+ else if(!error)
+ {
+ std::string warning = std::string("Unable to create tree from XML:\"") + path_in_textfield + "\"";
+ popupAndPrintWarning(warning);
+ error = TRUE;
+ }
+
+ if(error) // if we encountered an error, reset the button to off
+ {
+ sInstance->mToggleHighlightButton->setToggleState(FALSE);
+ }
+ else // only toggle if we didn't encounter an error
+ {
+ LLView::sHighlightingDiffs = !sInstance->sHighlightingDiffs;
+ sInstance->highlightChangedElements(); // *TODO: this is extraneous, right?
+ sInstance->highlightChangedFiles(); // *TODO: this is extraneous, right?
+ }
+ }
+}
+
+void LLFloaterUIPreview::scanDiffFile(LLXmlTreeNode* file_node)
+{
+ // Get file name
+ std::string file_name;
+ file_node->getAttributeString("name",file_name);
+ if(std::string("") == file_name)
+ {
+ std::string warning = std::string("Empty file name encountered in differences:\"") + file_name + "\"";
+ popupAndPrintWarning(warning);
+ return;
+ }
+
+ // Get a list of changed elements
+ // Get the first child first, then below get the next one; otherwise the iterator is invalid (bug or feature in XML code?)
+ for (LLXmlTreeNode* child = file_node->getFirstChild(); child != NULL; child = file_node->getNextChild())
+ {
+ if(!strncmp("delta",child->getName().c_str(),6))
+ {
+ std::string id;
+ child->getAttributeString("id",id);
+ if(mDiffsMap.find(file_name) == mDiffsMap.end())
+ {
+ mDiffsMap.insert(std::make_pair(file_name,std::make_pair(StringListPtr(new StringList), StringListPtr(new StringList))));
+ }
+ mDiffsMap[file_name].first->push_back(std::string(id.c_str()));
+ }
+ else
+ {
+ std::string warning = std::string("Child of file was not a delta, but rather the following:\"") + std::string(child->getName()) + "\"";
+ popupAndPrintWarning(warning);
+ return;
+ }
+ }
+}
+
+void LLFloaterUIPreview::highlightChangedElements()
+{
+ if(NULL == mLiveFile)
+ {
+ return;
+ }
+
+ // Process differences first (we want their warnings to be shown underneath other warnings)
+ StringListPtr changed_element_paths;
+ DiffMap::iterator iterExists = mDiffsMap.find(mLiveFile->mFileName);
+ if(iterExists != mDiffsMap.end())
+ {
+ changed_element_paths = mDiffsMap[mLiveFile->mFileName].first; // retrieve list of changed element paths from map
+ }
+
+ for(std::list<std::string>::iterator iter = changed_element_paths->begin(); iter != changed_element_paths->end(); ++iter) // for every changed element path
+ {
+ LLView* element = sInstance->mDisplayedFloater;
+ if(!strncmp(iter->c_str(),".",1)) // if it's the root floater itself
+ {
+ continue;
+ }
+
+ // Split element hierarchy path on period (*HACK: it's possible that the element name will have a period in it, in which case this won't work. See https://wiki.lindenlab.com/wiki/Viewer_Localization_Tool_Documentation.)
+ typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
+ boost::char_separator<char> sep(".");
+ tokenizer tokens(*iter, sep);
+ tokenizer::iterator token_iter;
+ BOOL failed = FALSE;
+ for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter)
+ {
+ element = element->getChild<LLView>(*token_iter,FALSE,FALSE); // try to find element: don't recur, and don't create if missing
+
+ // if we still didn't find it...
+ if(NULL == element)
+ {
+ llinfos << "Unable to find element in XuiDelta file named \"" << *iter << "\" in file \"" << mLiveFile->mFileName <<
+ "\". The element may no longer exist, the path may be incorrect, or it may not be a non-displayable element (not an LLView) such as a \"string\" type." << llendl;
+ failed = TRUE;
+ break;
+ }
+ }
+
+ if(!failed)
+ {
+ // Now that we have a pointer to the actual element, add it to the list of elements to be highlighted
+ std::set<LLView*>::iterator iter2 = std::find(LLView::sPreviewHighlightedElements.begin(), LLView::sPreviewHighlightedElements.end(), element);
+ if(iter2 == LLView::sPreviewHighlightedElements.end())
+ {
+ LLView::sPreviewHighlightedElements.insert(element);
+ }
+ }
+ }
+
+ // Process errors second, so their warnings show up on top of other warnings
+ StringListPtr error_list;
+ if(iterExists != mDiffsMap.end())
+ {
+ error_list = mDiffsMap[mLiveFile->mFileName].second;
+ }
+ for(std::list<std::string>::iterator iter = error_list->begin(); iter != error_list->end(); ++iter) // for every changed element path
+ {
+ std::string warning = std::string("Error listed among differences. Filename: \"") + mLiveFile->mFileName + "\". Message: \"" + *iter + "\"";
+ popupAndPrintWarning(warning);
+ }
+}
+
+void LLFloaterUIPreview::highlightChangedFiles()
+{
+ for(DiffMap::iterator iter = mDiffsMap.begin(); iter != mDiffsMap.end(); ++iter) // for every file listed in diffs
+ {
+ LLScrollListItem* item = mFileList->getItemByLabel(std::string(iter->first), FALSE, 1);
+ if(item)
+ {
+ item->setHighlighted(TRUE);
+ }
+ }
+}
+
+// Respond to button click to browse for an executable with which to edit XML files
+void LLFloaterUIPreview::onClickCloseDisplayedFloater(void* data)
+{
+ S32 caller_id = *((S32*)data);
+ if(caller_id == 1)
+ {
+ sInstance->mCloseOtherButton->setEnabled(FALSE);
+ sInstance->mToggleOverlapButton->setEnabled(FALSE);
+
+ if(sInstance->mDisplayedFloater)
+ {
+ sInstance->mLastDisplayedX = sInstance->mDisplayedFloater->calcScreenRect().mLeft;
+ sInstance->mLastDisplayedY = sInstance->mDisplayedFloater->calcScreenRect().mBottom;
+ delete sInstance->mDisplayedFloater;
+ sInstance->mDisplayedFloater = NULL;
+ }
+
+ if(sInstance->mLiveFile)
+ {
+ delete sInstance->mLiveFile;
+ sInstance->mLiveFile = NULL;
+ }
+
+ if(sInstance->mToggleOverlapButton->getToggleState())
+ {
+ sInstance->mToggleOverlapButton->toggleState();
+ onClickToggleOverlapping(NULL);
+ }
+
+ LLView::sPreviewClickedElement = NULL; // stop overlapping elements panel from drawing
+ sInstance->mOverlapPanel->mLastClickedElement = NULL;
+ }
+ else
+ {
+ sInstance->mCloseOtherButton_2->setEnabled(FALSE);
+ delete sInstance->mDisplayedFloater_2;
+ sInstance->mDisplayedFloater_2 = NULL;
+ }
+
+}
+
+BOOL LLPreviewedFloater::handleRightMouseDown(S32 x, S32 y, MASK mask)
+{
+ selectElement(this,x,y,0);
+ return TRUE;
+}
+
+// *NOTE: In order to hide all of the overlapping elements of the selected element so as to see it in context, here is what you would need to do:
+// -This selectElement call fills the overlap panel as normal. The element which is "selected" here is actually just an intermediate selection step;
+// what you've really selected is a list of elements: the one you clicked on and everything that overlaps it.
+// -The user then selects one of the elements from this list the overlap panel (click handling to the overlap panel would have to be added).
+// This becomes the final selection (as opposed to the intermediate selection that was just made).
+// -Everything else that is currently displayed on the overlap panel should be hidden from view in the previewed floater itself (setVisible(FALSE)).
+// -Subsequent clicks on other elements in the overlap panel (they should still be there) should make other elements the final selection.
+// -On close or on the click of a new button, everything should be shown again and all selection state should be cleared.
+// ~Jacob, 8/08
+BOOL LLPreviewedFloater::selectElement(LLView* parent, int x, int y, int depth)
+{
+ if(getVisible())
+ {
+ BOOL handled = FALSE;
+ if(LLFloaterUIPreview::containerType(parent))
+ {
+ for(child_list_const_iter_t child_it = parent->getChildList()->begin(); child_it != parent->getChildList()->end(); ++child_it)
+ {
+ LLView* child = *child_it;
+ S32 local_x = x - child->getRect().mLeft;
+ S32 local_y = y - child->getRect().mBottom;
+ if (child->pointInView(local_x, local_y) &&
+ child->getVisible() &&
+ selectElement(child, x, y, ++depth))
+ {
+ handled = TRUE;
+ break;
+ }
+ }
+ }
+
+ if(!handled)
+ {
+ LLView::sPreviewClickedElement = parent;
+ }
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+void LLPreviewedFloater::draw()
+{
+ if(NULL != LLFloaterUIPreview::sInstance)
+ {
+ // Set and unset sDrawPreviewHighlights flag so as to avoid using two flags
+ if(LLFloaterUIPreview::sInstance->mHighlightingOverlaps)
+ {
+ LLView::sDrawPreviewHighlights = TRUE;
+ }
+ LLFloater::draw();
+ if(LLFloaterUIPreview::sInstance->mHighlightingOverlaps)
+ {
+ LLView::sDrawPreviewHighlights = FALSE;
+ }
+ }
+}
+
+void LLFloaterUIPreview::onClickToggleOverlapping(void*)
+{
+ if(LLView::sHighlightingDiffs)
+ {
+ onClickToggleDiffHighlighting(NULL);
+ sInstance->mToggleHighlightButton->toggleState();
+ }
+ LLView::sPreviewHighlightedElements.clear(); // clear lists first
+
+ S32 width, height;
+ sInstance->getResizeLimits(&width, &height); // illegal call of non-static member function
+ if(sInstance->mHighlightingOverlaps)
+ {
+ sInstance->mHighlightingOverlaps = !sInstance->mHighlightingOverlaps;
+ // reset list of preview highlighted elements
+ sInstance->setRect(LLRect(sInstance->getRect().mLeft,sInstance->getRect().mTop,sInstance->getRect().mRight - sInstance->mOverlapPanel->getRect().getWidth(),sInstance->getRect().mBottom));
+ sInstance->setResizeLimits(width - sInstance->mOverlapPanel->getRect().getWidth(), height);
+ }
+ else
+ {
+ sInstance->mHighlightingOverlaps = !sInstance->mHighlightingOverlaps;
+ displayFloater(FALSE,1);
+ sInstance->setRect(LLRect(sInstance->getRect().mLeft,sInstance->getRect().mTop,sInstance->getRect().mRight + sInstance->mOverlapPanel->getRect().getWidth(),sInstance->getRect().mBottom));
+ sInstance->setResizeLimits(width + sInstance->mOverlapPanel->getRect().getWidth(), height);
+ }
+ sInstance->childSetVisible("overlap_scroll", sInstance->mHighlightingOverlaps);
+}
+
+void LLFloaterUIPreview::findOverlapsInChildren(LLView* parent)
+{
+ if(parent->getChildCount() == 0 || !containerType(parent)) // if it has no children or isn't a container type, skip it
+ {
+ return;
+ }
+
+ // for every child of the parent
+ for(child_list_const_iter_t child_it = parent->getChildList()->begin(); child_it != parent->getChildList()->end(); ++child_it)
+ {
+ LLView* child = *child_it;
+ if(overlapIgnorable(child))
+ {
+ continue;
+ }
+
+ // for every sibling
+ for(child_list_const_iter_t sibling_it = parent->getChildList()->begin(); sibling_it != parent->getChildList()->end(); ++sibling_it) // for each sibling
+ {
+ LLView* sibling = *sibling_it;
+ if(overlapIgnorable(sibling))
+ {
+ continue;
+ }
+
+ // if they overlap... (we don't care if they're visible or enabled -- we want to check those anyway, i.e. hidden tabs that can be later shown)
+ if(sibling != child && elementOverlap(child, sibling))
+ {
+ mOverlapMap[child].push_back(sibling); // add to the map
+ }
+ }
+ findOverlapsInChildren(child); // recur
+ }
+}
+
+// *HACK: don't overlap with the drag handle and various other elements
+// This is using dynamic casts because there is no object-oriented way to tell which elements contain localizable text. These are a few that are ignorable.
+// *NOTE: If a list of elements which have localizable content were created, this function should return false if viewp's class is in that list.
+BOOL LLFloaterUIPreview::overlapIgnorable(LLView* viewp)
+{
+ return NULL != dynamic_cast<LLDragHandle*>(viewp) ||
+ NULL != dynamic_cast<LLViewBorder*>(viewp) ||
+ NULL != dynamic_cast<LLResizeBar*>(viewp);
+}
+
+// *HACK: these are the only two container types as of 8/08, per Richard
+// This is using dynamic casts because there is no object-oriented way to tell which elements are containers.
+BOOL LLFloaterUIPreview::containerType(LLView* viewp)
+{
+ return NULL != dynamic_cast<LLPanel*>(viewp) || NULL != dynamic_cast<LLLayoutStack*>(viewp);
+}
+
+// Check if two llview's rectangles overlap, with some tolerance
+BOOL LLFloaterUIPreview::elementOverlap(LLView* view1, LLView* view2)
+{
+ LLSD rec1 = view1->getRect().getValue();
+ LLSD rec2 = view2->getRect().getValue();
+ int tolerance = 2;
+ return (int)rec1[0] <= (int)rec2[2] - tolerance &&
+ (int)rec2[0] <= (int)rec1[2] - tolerance &&
+ (int)rec1[3] <= (int)rec2[1] - tolerance &&
+ (int)rec2[3] <= (int)rec1[1] - tolerance;
+}
+
+void LLOverlapPanel::draw()
+{
+ static const std::string current_selection_text("Current selection: ");
+ static const std::string overlapper_text("Overlapper: ");
+ LLColor4 text_color = LLColor4::grey;
+ gGL.color4fv(text_color.mV);
+
+ if(!LLView::sPreviewClickedElement)
+ {
+ LLUI::translate(5,getRect().getHeight()-20); // translate to top-5,left-5
+ LLView::sDrawPreviewHighlights = FALSE;
+ LLFontGL::getFontSansSerifSmall()->renderUTF8(current_selection_text, 0, 0, 0, text_color,
+ LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE);
+ }
+ else
+ {
+ LLFloaterUIPreview::OverlapMap::iterator iterExists = LLFloaterUIPreview::sInstance->mOverlapMap.find(LLView::sPreviewClickedElement);
+ if(iterExists == LLFloaterUIPreview::sInstance->mOverlapMap.end())
+ {
+ return;
+ }
+
+ std::list<LLView*> overlappers = LLFloaterUIPreview::sInstance->mOverlapMap[LLView::sPreviewClickedElement];
+ if(overlappers.size() == 0)
+ {
+ LLUI::translate(5,getRect().getHeight()-20); // translate to top-5,left-5
+ LLView::sDrawPreviewHighlights = FALSE;
+ std::string current_selection = std::string(current_selection_text + LLView::sPreviewClickedElement->getName() + " (no elements overlap)");
+ S32 text_width = LLFontGL::getFontSansSerifSmall()->getWidth(current_selection) + 10;
+ LLFontGL::getFontSansSerifSmall()->renderUTF8(current_selection, 0, 0, 0, text_color,
+ LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE);
+ // widen panel enough to fit this text
+ LLRect rect = getRect();
+ setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() < text_width ? rect.mLeft + text_width : rect.mRight,rect.mTop));
+ return;
+ }
+
+ // recalculate required with and height; otherwise use cached
+ BOOL need_to_recalculate_bounds = FALSE;
+ if(mLastClickedElement == NULL)
+ {
+ need_to_recalculate_bounds = TRUE;
+ }
+
+ if(NULL == mLastClickedElement)
+ {
+ mLastClickedElement = LLView::sPreviewClickedElement;
+ }
+
+ // recalculate bounds for scroll panel
+ if(need_to_recalculate_bounds || LLView::sPreviewClickedElement->getName() != mLastClickedElement->getName())
+ {
+ // reset panel's rectangle to its default width and height (300x600)
+ LLRect panel_rect = LLFloaterUIPreview::sInstance->mOverlapPanel->getRect();
+ LLFloaterUIPreview::sInstance->mOverlapPanel->setRect(LLRect(panel_rect.mLeft,panel_rect.mTop,panel_rect.mLeft+LLFloaterUIPreview::sInstance->mOverlapPanel->getRect().getWidth(),panel_rect.mTop-LLFloaterUIPreview::sInstance->mOverlapPanel->getRect().getHeight()));
+
+ LLRect rect;
+
+ // change bounds for selected element
+ int height_sum = mLastClickedElement->getRect().getHeight() + mSpacing + 80;
+ rect = LLFloaterUIPreview::sInstance->mOverlapPanel->getRect();
+ LLFloaterUIPreview::sInstance->mOverlapPanel->setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() > mLastClickedElement->getRect().getWidth() + 5 ? rect.mRight : rect.mLeft + mLastClickedElement->getRect().getWidth() + 5, rect.mBottom));
+
+ // and widen to accomodate text if that's wider
+ std::string display_text = current_selection_text + LLView::sPreviewClickedElement->getName();
+ S32 text_width = LLFontGL::getFontSansSerifSmall()->getWidth(display_text) + 10;
+ rect = getRect();
+ setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() < text_width ? rect.mLeft + text_width : rect.mRight,rect.mTop));
+
+ std::list<LLView*> overlappers = LLFloaterUIPreview::sInstance->mOverlapMap[LLView::sPreviewClickedElement];
+ for(std::list<LLView*>::iterator overlap_it = overlappers.begin(); overlap_it != overlappers.end(); ++overlap_it)
+ {
+ LLView* viewp = *overlap_it;
+ height_sum += viewp->getRect().getHeight() + mSpacing*3;
+
+ // widen panel's rectangle to accommodate widest overlapping element of this floater
+ rect = LLFloaterUIPreview::sInstance->mOverlapPanel->getRect();
+ LLFloaterUIPreview::sInstance->mOverlapPanel->setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() > viewp->getRect().getWidth() + 5 ? rect.mRight : rect.mLeft + viewp->getRect().getWidth() + 5, rect.mBottom));
+
+ // and widen to accomodate text if that's wider
+ std::string display_text = overlapper_text + viewp->getName();
+ S32 text_width = LLFontGL::getFontSansSerifSmall()->getWidth(display_text) + 10;
+ rect = getRect();
+ setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() < text_width ? rect.mLeft + text_width : rect.mRight,rect.mTop));
+ }
+ // change panel's height to accommodate all element heights plus spacing between them
+ rect = LLFloaterUIPreview::sInstance->mOverlapPanel->getRect();
+ LLFloaterUIPreview::sInstance->mOverlapPanel->setRect(LLRect(rect.mLeft,rect.mTop,rect.mRight,rect.mTop-height_sum));
+ }
+
+ LLUI::translate(5,getRect().getHeight()-10); // translate to top left
+ LLView::sDrawPreviewHighlights = FALSE;
+
+ // draw currently-selected element at top of overlappers
+ LLUI::translate(0,-mSpacing);
+ LLFontGL::getFontSansSerifSmall()->renderUTF8(current_selection_text + LLView::sPreviewClickedElement->getName(), 0, 0, 0, text_color,
+ LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE);
+ LLUI::translate(0,-mSpacing-LLView::sPreviewClickedElement->getRect().getHeight()); // skip spacing distance + height
+ LLView::sPreviewClickedElement->draw();
+
+ for(std::list<LLView*>::iterator overlap_it = overlappers.begin(); overlap_it != overlappers.end(); ++overlap_it)
+ {
+ LLView* viewp = *overlap_it;
+
+ // draw separating line
+ LLUI::translate(0,-mSpacing);
+ gl_line_2d(0,0,getRect().getWidth()-10,0,LLColor4(192.0f/255.0f,192.0f/255.0f,192.0f/255.0f));
+
+ // draw name
+ LLUI::translate(0,-mSpacing);
+ LLFontGL::getFontSansSerifSmall()->renderUTF8(overlapper_text + viewp->getName(), 0, 0, 0, text_color,
+ LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE);
+
+ // draw element
+ LLUI::translate(0,-mSpacing-viewp->getRect().getHeight()); // skip spacing distance + height
+ viewp->draw();
+ }
+ mLastClickedElement = LLView::sPreviewClickedElement;
+ }
+}