PageRenderTime 189ms CodeModel.GetById 23ms app.highlight 144ms RepoModel.GetById 1ms app.codeStats 1ms

/indra/newview/llfloateruipreview.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 1766 lines | 1354 code | 206 blank | 206 comment | 217 complexity | 8da1e9bb2a25d4f9e245b4cebca0ee44 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/**
   2 * @file llfloateruipreview.cpp
   3 * @brief Tool for previewing and editing floaters, plus localization tool integration
   4 *
   5 * $LicenseInfo:firstyear=2008&license=viewerlgpl$
   6 * Second Life Viewer Source Code
   7 * Copyright (C) 2010, Linden Research, Inc.
   8 * 
   9 * This library is free software; you can redistribute it and/or
  10 * modify it under the terms of the GNU Lesser General Public
  11 * License as published by the Free Software Foundation;
  12 * version 2.1 of the License only.
  13 * 
  14 * This library is distributed in the hope that it will be useful,
  15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  17 * Lesser General Public License for more details.
  18 * 
  19 * You should have received a copy of the GNU Lesser General Public
  20 * License along with this library; if not, write to the Free Software
  21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  22 * 
  23 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
  24 * $/LicenseInfo$
  25 */
  26
  27// Tool for previewing floaters and panels for localization and UI design purposes.
  28// See: https://wiki.lindenlab.com/wiki/GUI_Preview_And_Localization_Tools
  29// See: https://jira.lindenlab.com/browse/DEV-16869
  30
  31// *TODO: Translate error messgaes using notifications/alerts.xml
  32
  33#include "llviewerprecompiledheaders.h"	// Precompiled headers
  34
  35#include "llfloateruipreview.h"			// Own header
  36
  37// Internal utility
  38#include "lldiriterator.h"
  39#include "lleventtimer.h"
  40#include "llexternaleditor.h"
  41#include "llrender.h"
  42#include "llsdutil.h"
  43#include "llxmltree.h"
  44#include "llviewerwindow.h"
  45
  46// XUI
  47#include "lluictrlfactory.h"
  48#include "llcombobox.h"
  49#include "llnotificationsutil.h"
  50#include "llresizebar.h"
  51#include "llscrolllistitem.h"
  52#include "llscrolllistctrl.h"
  53#include "llfilepicker.h"
  54#include "lldraghandle.h"
  55#include "lllayoutstack.h"
  56#include "lltooltip.h"
  57#include "llviewermenu.h"
  58#include "llrngwriter.h"
  59#include "llfloater.h"			// superclass
  60#include "llfloaterreg.h"
  61#include "llscrollcontainer.h"	// scroll container for overlapping elements
  62#include "lllivefile.h"					// live file poll/stat/reload
  63
  64// Boost (for linux/unix command-line execv)
  65#include <boost/tokenizer.hpp>
  66#include <boost/shared_ptr.hpp>
  67
  68// External utility
  69#include <string>
  70#include <list>
  71#include <map>
  72
  73#if LL_DARWIN
  74#include <CoreFoundation/CFURL.h>
  75#endif
  76
  77// Static initialization
  78static const S32 PRIMARY_FLOATER = 1;
  79static const S32 SECONDARY_FLOATER = 2;
  80
  81class LLOverlapPanel;
  82static LLDefaultChildRegistry::Register<LLOverlapPanel> register_overlap_panel("overlap_panel");
  83
  84static std::string get_xui_dir()
  85{
  86	std::string delim = gDirUtilp->getDirDelimiter();
  87	return gDirUtilp->getSkinBaseDir() + delim + "default" + delim + "xui" + delim;
  88}
  89
  90// Forward declarations to avoid header dependencies
  91class LLColor;
  92class LLScrollListCtrl;
  93class LLComboBox;
  94class LLButton;
  95class LLLineEditor;
  96class LLXmlTreeNode;
  97class LLFloaterUIPreview;
  98class LLFadeEventTimer;
  99
 100class LLLocalizationResetForcer;
 101class LLGUIPreviewLiveFile;
 102class LLFadeEventTimer;
 103class LLPreviewedFloater;
 104
 105// Implementation of custom overlapping element display panel
 106class LLOverlapPanel : public LLPanel
 107{
 108public:
 109	struct Params : public LLInitParam::Block<Params, LLPanel::Params>
 110	{
 111		Params() {}
 112	};
 113	LLOverlapPanel(Params p = Params()) : LLPanel(p),
 114		mSpacing(10),
 115		// mClickedElement(NULL),
 116		mLastClickedElement(NULL)
 117	{
 118		mOriginalWidth = getRect().getWidth();
 119		mOriginalHeight = getRect().getHeight();
 120	}
 121	virtual void draw();
 122	
 123	typedef std::map<LLView*, std::list<LLView*> >	OverlapMap;
 124	OverlapMap mOverlapMap;						// map, of XUI element to a list of XUI elements it overlaps
 125	
 126	// LLView *mClickedElement;
 127	LLView *mLastClickedElement;
 128	int mOriginalWidth, mOriginalHeight, mSpacing;
 129};
 130
 131
 132class LLFloaterUIPreview : public LLFloater
 133{
 134public:
 135	// Setup
 136	LLFloaterUIPreview(const LLSD& key);
 137	virtual ~LLFloaterUIPreview();
 138
 139	std::string getLocStr(S32 ID);							// fetches the localization string based on what is selected in the drop-down menu
 140	void displayFloater(BOOL click, S32 ID, bool save = false);			// needs to be public so live file can call it when it finds an update
 141
 142	/*virtual*/ BOOL postBuild();
 143	/*virtual*/ void onClose(bool app_quitting);
 144
 145	void refreshList();										// refresh list (empty it out and fill it up from scratch)
 146	void addFloaterEntry(const std::string& path);			// add a single file's entry to the list of floaters
 147	
 148	static BOOL containerType(LLView* viewp);				// check if the element is a container type and tree traverses need to look at its children
 149	
 150public:	
 151	LLPreviewedFloater*			mDisplayedFloater;			// the floater which is currently being displayed
 152	LLPreviewedFloater*			mDisplayedFloater_2;			// the floater which is currently being displayed
 153	LLGUIPreviewLiveFile*		mLiveFile;					// live file for checking for updates to the currently-displayed XML file
 154	LLOverlapPanel*				mOverlapPanel;				// custom overlapping elements panel
 155	// BOOL						mHighlightingDiffs;			// bool for whether localization diffs are being highlighted or not
 156	BOOL						mHighlightingOverlaps;		// bool for whether overlapping elements are being highlighted
 157
 158	// typedef std::map<std::string,std::pair<std::list<std::string>,std::list<std::string> > > DiffMap; // this version copies the lists etc., and thus is bad memory-wise
 159	typedef std::list<std::string> StringList;
 160	typedef boost::shared_ptr<StringList> StringListPtr;
 161	typedef std::map<std::string, std::pair<StringListPtr,StringListPtr> > DiffMap;
 162	DiffMap mDiffsMap;							// map, of filename to pair of list of changed element paths and list of errors
 163
 164private:
 165	LLExternalEditor mExternalEditor;
 166
 167	// XUI elements for this floater
 168	LLScrollListCtrl*			mFileList;							// scroll list control for file list
 169	LLLineEditor*				mEditorPathTextBox;					// text field for path to editor executable
 170	LLLineEditor*				mEditorArgsTextBox;					// text field for arguments to editor executable
 171	LLLineEditor*				mDiffPathTextBox;					// text field for path to diff file
 172	LLButton*					mDisplayFloaterBtn;					// button to display primary floater
 173	LLButton*					mDisplayFloaterBtn_2;				// button to display secondary floater
 174	LLButton*					mEditFloaterBtn;					// button to edit floater
 175	LLButton*					mExecutableBrowseButton;			// button to browse for executable
 176	LLButton*					mCloseOtherButton;					// button to close primary displayed floater
 177	LLButton*					mCloseOtherButton_2;				// button to close secondary displayed floater
 178	LLButton*					mDiffBrowseButton;					// button to browse for diff file
 179	LLButton*					mToggleHighlightButton;				// button to toggle highlight of files/elements with diffs
 180	LLButton*					mToggleOverlapButton;				// button to togle overlap panel/highlighting
 181	LLComboBox*					mLanguageSelection;					// combo box for primary language selection
 182	LLComboBox*					mLanguageSelection_2;				// combo box for secondary language selection
 183	LLScrollContainer*			mOverlapScrollView;					// overlapping elements scroll container
 184	S32							mLastDisplayedX, mLastDisplayedY;	// stored position of last floater so the new one opens up in the same place
 185	std::string 				mDelim;								// the OS-specific delimiter character (/ or \) (*TODO: this shouldn't be needed, right?)
 186
 187	std::string					mSavedEditorPath;					// stored editor path so closing this floater doesn't reset it
 188	std::string					mSavedEditorArgs;					// stored editor args so closing this floater doesn't reset it
 189	std::string					mSavedDiffPath;						// stored diff file path so closing this floater doesn't reset it
 190
 191	// Internal functionality
 192	static void popupAndPrintWarning(const std::string& warning);	// pop up a warning
 193	std::string getLocalizedDirectory();							// build and return the path to the XUI directory for the currently-selected localization
 194	void scanDiffFile(LLXmlTreeNode* file_node);					// scan a given XML node for diff entries and highlight them in its associated file
 195	void highlightChangedElements();								// look up the list of elements to highlight and highlight them in the current floater
 196	void highlightChangedFiles();									// look up the list of changed files to highlight and highlight them in the scroll list
 197	void findOverlapsInChildren(LLView* parent);					// fill the map below with element overlap information
 198	static BOOL overlapIgnorable(LLView* viewp);					// check it the element can be ignored for overlap/localization purposes
 199
 200	// check if two elements overlap using their rectangles
 201	// used instead of llrect functions because by adding a few pixels of leeway I can cut down drastically on the number of overlaps
 202	BOOL elementOverlap(LLView* view1, LLView* view2);
 203
 204	// Button/drop-down action listeners (self explanatory)
 205	void onClickDisplayFloater(S32 id);
 206	void onClickSaveFloater(S32 id);
 207	void onClickSaveAll(S32 id);
 208	void onClickEditFloater();
 209	void onClickBrowseForEditor();
 210	void onClickBrowseForDiffs();
 211	void onClickToggleDiffHighlighting();
 212	void onClickToggleOverlapping();
 213	void onClickCloseDisplayedFloater(S32 id);
 214	void onLanguageComboSelect(LLUICtrl* ctrl);
 215	void onClickExportSchema();
 216	void onClickShowRectangles(const LLSD& data);
 217};
 218
 219//----------------------------------------------------------------------------
 220// Local class declarations
 221// Reset object to ensure that when we change the current language setting for preview purposes,
 222// it automatically is reset.  Constructed on the stack at the start of the method; the reset
 223// occurs as it falls out of scope at the end of the method.  See llfloateruipreview.cpp for usage.
 224class LLLocalizationResetForcer
 225{
 226public:
 227	LLLocalizationResetForcer(LLFloaterUIPreview* floater, S32 ID);
 228	virtual ~LLLocalizationResetForcer();
 229
 230private:
 231	std::string mSavedLocalization;	// the localization before we change it
 232};
 233
 234// Implementation of live file
 235// When a floater is being previewed, any saved changes to its corresponding
 236// file cause the previewed floater to be reloaded
 237class LLGUIPreviewLiveFile : public LLLiveFile
 238{
 239public:
 240	LLGUIPreviewLiveFile(std::string path, std::string name, LLFloaterUIPreview* parent);
 241	virtual ~LLGUIPreviewLiveFile();
 242	LLFloaterUIPreview* mParent;
 243	LLFadeEventTimer* mFadeTimer;	// timer for fade-to-yellow-and-back effect to warn that file has been reloaded
 244	BOOL mFirstFade;				// setting this avoids showing the fade reload warning on first load
 245	std::string mFileName;
 246protected:
 247	bool loadFile();
 248};
 249
 250// Implementation of graphical fade in/out (on timer) for when XUI files are updated
 251class LLFadeEventTimer : public LLEventTimer
 252{
 253public:
 254	LLFadeEventTimer(F32 refresh, LLGUIPreviewLiveFile* parent);
 255	BOOL tick();
 256	LLGUIPreviewLiveFile* mParent;
 257private:
 258	BOOL mFadingOut;			// fades in then out; this is toggled in between
 259	LLColor4 mOriginalColor;	// original color; color is reset to this after fade is coimplete
 260};
 261
 262// Implementation of previewed floater
 263// Used to override draw and mouse handler
 264class LLPreviewedFloater : public LLFloater
 265{
 266public:
 267	LLPreviewedFloater(LLFloaterUIPreview* floater, const Params& params)
 268		: LLFloater(LLSD(), params),
 269		  mFloaterUIPreview(floater)
 270	{
 271	}
 272
 273	virtual void draw();
 274	BOOL handleRightMouseDown(S32 x, S32 y, MASK mask);
 275	BOOL handleToolTip(S32 x, S32 y, MASK mask);
 276	BOOL selectElement(LLView* parent, int x, int y, int depth);	// select element to display its overlappers
 277
 278	LLFloaterUIPreview* mFloaterUIPreview;
 279
 280	// draw widget outlines
 281	static bool	sShowRectangles;
 282};
 283
 284bool LLPreviewedFloater::sShowRectangles = false;
 285
 286//----------------------------------------------------------------------------
 287
 288// Localization reset forcer -- ensures that when localization is temporarily changed for previewed floater, it is reset
 289// Changes are made here
 290LLLocalizationResetForcer::LLLocalizationResetForcer(LLFloaterUIPreview* floater, S32 ID)
 291{
 292	mSavedLocalization = LLUI::sSettingGroups["config"]->getString("Language");				// save current localization setting
 293	LLUI::sSettingGroups["config"]->setString("Language", floater->getLocStr(ID));// hack language to be the one we want to preview floaters in
 294	LLUI::setupPaths();														// forcibly reset XUI paths with this new language
 295}
 296
 297// Actually reset in destructor
 298// Changes are reversed here
 299LLLocalizationResetForcer::~LLLocalizationResetForcer()
 300{
 301	LLUI::sSettingGroups["config"]->setString("Language", mSavedLocalization);	// reset language to what it was before we changed it
 302	LLUI::setupPaths();														// forcibly reset XUI paths with this new language
 303}
 304
 305// Live file constructor
 306// Needs full path for LLLiveFile but needs just file name for this code, hence the reduntant arguments; easier than separating later
 307LLGUIPreviewLiveFile::LLGUIPreviewLiveFile(std::string path, std::string name, LLFloaterUIPreview* parent)
 308        : mFileName(name),
 309		mParent(parent),
 310		mFirstFade(TRUE),
 311		mFadeTimer(NULL),
 312		LLLiveFile(path, 1.0)
 313{}
 314
 315LLGUIPreviewLiveFile::~LLGUIPreviewLiveFile()
 316{
 317	mParent->mLiveFile = NULL;
 318	if(mFadeTimer)
 319	{
 320		mFadeTimer->mParent = NULL;
 321		// deletes itself; see lltimer.cpp
 322	}
 323}
 324
 325// Live file load
 326bool LLGUIPreviewLiveFile::loadFile()
 327{
 328	mParent->displayFloater(FALSE,1);	// redisplay the floater
 329	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
 330	{
 331		mFirstFade = FALSE;
 332	}
 333	else
 334	{
 335		if(mFadeTimer)
 336		{
 337			mFadeTimer->mParent = NULL;
 338		}
 339		mFadeTimer = new LLFadeEventTimer(0.05f,this);
 340	}
 341	return true;
 342}
 343
 344// Initialize fade event timer
 345LLFadeEventTimer::LLFadeEventTimer(F32 refresh, LLGUIPreviewLiveFile* parent)
 346	: mParent(parent),
 347	mFadingOut(TRUE),
 348	LLEventTimer(refresh)
 349{
 350	mOriginalColor = mParent->mParent->mDisplayedFloater->getBackgroundColor();
 351}
 352
 353// Single tick of fade event timer: increment the color
 354BOOL LLFadeEventTimer::tick()
 355{
 356	float diff = 0.04f;
 357	if(TRUE == mFadingOut)	// set fade for in/out color direction
 358	{
 359		diff = -diff;
 360	}
 361
 362	if(NULL == mParent)	// no more need to tick, so suicide
 363	{
 364		return TRUE;
 365	}
 366
 367	// Set up colors
 368	LLColor4 bg_color = mParent->mParent->mDisplayedFloater->getBackgroundColor();
 369	LLSD colors = bg_color.getValue();
 370	LLSD colors_old = colors;
 371
 372	// Tick colors
 373	colors[0] = colors[0].asReal() - diff; if(colors[0].asReal() < mOriginalColor.getValue()[0].asReal()) { colors[0] = colors_old[0]; }
 374	colors[1] = colors[1].asReal() - diff; if(colors[1].asReal() < mOriginalColor.getValue()[1].asReal()) { colors[1] = colors_old[1]; }
 375	colors[2] = colors[2].asReal() + diff; if(colors[2].asReal() > mOriginalColor.getValue()[2].asReal()) { colors[2] = colors_old[2]; }
 376
 377	// Clamp and set colors
 378	bg_color.setValue(colors);
 379	bg_color.clamp();	// make sure we didn't exceed [0,1]
 380	mParent->mParent->mDisplayedFloater->setBackgroundColor(bg_color);
 381
 382	if(bg_color[2] <= 0.0f)	// end of fade out, start fading in
 383	{
 384		mFadingOut = FALSE;
 385	}
 386
 387	return FALSE;
 388}
 389
 390// Constructor
 391LLFloaterUIPreview::LLFloaterUIPreview(const LLSD& key)
 392  : LLFloater(key),
 393	mDisplayedFloater(NULL),
 394	mDisplayedFloater_2(NULL),
 395	mLiveFile(NULL),
 396	// sHighlightingDiffs(FALSE),
 397	mHighlightingOverlaps(FALSE),
 398	mLastDisplayedX(0),
 399	mLastDisplayedY(0)
 400{
 401}
 402
 403// Destructor
 404LLFloaterUIPreview::~LLFloaterUIPreview()
 405{
 406	// spawned floaters are deleted automatically, so we don't need to delete them here
 407
 408	// save contents of textfields so it can be restored later if the floater is created again this session
 409	mSavedEditorPath = mEditorPathTextBox->getText();
 410	mSavedEditorArgs = mEditorArgsTextBox->getText();
 411	mSavedDiffPath   = mDiffPathTextBox->getText();
 412
 413	// delete live file if it exists
 414	if(mLiveFile)
 415	{
 416		delete mLiveFile;
 417		mLiveFile = NULL;
 418	}
 419}
 420
 421// Perform post-build setup (defined in superclass)
 422BOOL LLFloaterUIPreview::postBuild()
 423{
 424	LLPanel* main_panel_tmp = getChild<LLPanel>("main_panel");				// get a pointer to the main panel in order to...
 425	mFileList = main_panel_tmp->getChild<LLScrollListCtrl>("name_list");	// save pointer to file list
 426	// Double-click opens the floater, for convenience
 427	mFileList->setDoubleClickCallback(boost::bind(&LLFloaterUIPreview::onClickDisplayFloater, this, PRIMARY_FLOATER));
 428
 429	setDefaultBtn("display_floater");
 430	// get pointers to buttons and link to callbacks
 431	mLanguageSelection = main_panel_tmp->getChild<LLComboBox>("language_select_combo");
 432	mLanguageSelection->setCommitCallback(boost::bind(&LLFloaterUIPreview::onLanguageComboSelect, this, mLanguageSelection));
 433	mLanguageSelection_2 = main_panel_tmp->getChild<LLComboBox>("language_select_combo_2");
 434	mLanguageSelection_2->setCommitCallback(boost::bind(&LLFloaterUIPreview::onLanguageComboSelect, this, mLanguageSelection));
 435	LLPanel* editor_panel_tmp = main_panel_tmp->getChild<LLPanel>("editor_panel");
 436	mDisplayFloaterBtn = main_panel_tmp->getChild<LLButton>("display_floater");
 437	mDisplayFloaterBtn->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickDisplayFloater, this, PRIMARY_FLOATER));
 438	mDisplayFloaterBtn_2 = main_panel_tmp->getChild<LLButton>("display_floater_2");
 439	mDisplayFloaterBtn_2->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickDisplayFloater, this, SECONDARY_FLOATER));
 440	mToggleOverlapButton = main_panel_tmp->getChild<LLButton>("toggle_overlap_panel");
 441	mToggleOverlapButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickToggleOverlapping, this));
 442	mCloseOtherButton = main_panel_tmp->getChild<LLButton>("close_displayed_floater");
 443	mCloseOtherButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickCloseDisplayedFloater, this, PRIMARY_FLOATER));
 444	mCloseOtherButton_2 = main_panel_tmp->getChild<LLButton>("close_displayed_floater_2");
 445	mCloseOtherButton_2->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickCloseDisplayedFloater, this, SECONDARY_FLOATER));
 446	mEditFloaterBtn = main_panel_tmp->getChild<LLButton>("edit_floater");
 447	mEditFloaterBtn->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickEditFloater, this));
 448	mExecutableBrowseButton = editor_panel_tmp->getChild<LLButton>("browse_for_executable");
 449	LLPanel* vlt_panel_tmp = main_panel_tmp->getChild<LLPanel>("vlt_panel");
 450	mExecutableBrowseButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickBrowseForEditor, this));
 451	mDiffBrowseButton = vlt_panel_tmp->getChild<LLButton>("browse_for_vlt_diffs");
 452	mDiffBrowseButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickBrowseForDiffs, this));
 453	mToggleHighlightButton = vlt_panel_tmp->getChild<LLButton>("toggle_vlt_diff_highlight");
 454	mToggleHighlightButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickToggleDiffHighlighting, this));
 455	main_panel_tmp->getChild<LLButton>("save_floater")->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickSaveFloater, this, PRIMARY_FLOATER));
 456	main_panel_tmp->getChild<LLButton>("save_all_floaters")->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickSaveAll, this, PRIMARY_FLOATER));
 457
 458	getChild<LLButton>("export_schema")->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickExportSchema, this));
 459	getChild<LLUICtrl>("show_rectangles")->setCommitCallback(
 460		boost::bind(&LLFloaterUIPreview::onClickShowRectangles, this, _2));
 461
 462	// get pointers to text fields
 463	mEditorPathTextBox = editor_panel_tmp->getChild<LLLineEditor>("executable_path_field");
 464	mEditorArgsTextBox = editor_panel_tmp->getChild<LLLineEditor>("executable_args_field");
 465	mDiffPathTextBox = vlt_panel_tmp->getChild<LLLineEditor>("vlt_diff_path_field");
 466
 467	// *HACK: restored saved editor path and args to textfields
 468	mEditorPathTextBox->setText(mSavedEditorPath);
 469	mEditorArgsTextBox->setText(mSavedEditorArgs);
 470	mDiffPathTextBox->setText(mSavedDiffPath);
 471
 472	// Set up overlap panel
 473	mOverlapPanel = getChild<LLOverlapPanel>("overlap_panel");
 474
 475	getChildView("overlap_scroll")->setVisible( mHighlightingOverlaps);
 476	
 477	mDelim = gDirUtilp->getDirDelimiter();	// initialize delimiter to dir sep slash
 478
 479	// refresh list of available languages (EN will still be default)
 480	BOOL found = TRUE;
 481	BOOL found_en_us = FALSE;
 482	std::string language_directory;
 483	std::string xui_dir = get_xui_dir();	// directory containing localizations -- don't forget trailing delim
 484	mLanguageSelection->removeall();																				// clear out anything temporarily in list from XML
 485
 486	LLDirIterator iter(xui_dir, "*");
 487	while(found)																									// for every directory
 488	{
 489		if((found = iter.next(language_directory)))							// get next directory
 490		{
 491			std::string full_path = xui_dir + language_directory;
 492			if(LLFile::isfile(full_path.c_str()))																	// if it's not a directory, skip it
 493			{
 494				continue;
 495			}
 496
 497			if(strncmp("template",language_directory.c_str(),8) && -1 == language_directory.find("."))				// if it's not the template directory or a hidden directory
 498			{
 499				if(!strncmp("en",language_directory.c_str(),5))													// remember if we've seen en, so we can make it default
 500				{
 501					found_en_us = TRUE;
 502				}
 503				else
 504				{
 505					mLanguageSelection->add(std::string(language_directory));											// add it to the language selection dropdown menu
 506					mLanguageSelection_2->add(std::string(language_directory));
 507				}
 508			}
 509		}
 510	}
 511	if(found_en_us)
 512	{
 513		mLanguageSelection->add(std::string("en"),ADD_TOP);															// make en first item if we found it
 514		mLanguageSelection_2->add(std::string("en"),ADD_TOP);	
 515	}
 516	else
 517	{
 518		std::string warning = std::string("No EN localization found; check your XUI directories!");
 519		popupAndPrintWarning(warning);
 520	}
 521	mLanguageSelection->selectFirstItem();																			// select the first item
 522	mLanguageSelection_2->selectFirstItem();
 523
 524	refreshList();																									// refresh the list of available floaters
 525
 526	return TRUE;
 527}
 528
 529// Callback for language combo box selection: refresh current floater when you change languages
 530void LLFloaterUIPreview::onLanguageComboSelect(LLUICtrl* ctrl)
 531{
 532	LLComboBox* caller = dynamic_cast<LLComboBox*>(ctrl);
 533	if (!caller)
 534		return;
 535	if(caller->getName() == std::string("language_select_combo"))
 536	{
 537		if(mDisplayedFloater)
 538		{
 539			onClickCloseDisplayedFloater(PRIMARY_FLOATER);
 540			displayFloater(TRUE,1);
 541		}
 542	}
 543	else
 544	{
 545		if(mDisplayedFloater_2)
 546		{
 547			onClickCloseDisplayedFloater(PRIMARY_FLOATER);
 548			displayFloater(TRUE,2);	// *TODO: make take an arg
 549		}
 550	}
 551
 552}
 553
 554void LLFloaterUIPreview::onClickExportSchema()
 555{
 556	//NOTE: schema generation not complete
 557	//gViewerWindow->setCursor(UI_CURSOR_WAIT);
 558	//std::string template_path = gDirUtilp->getExpandedFilename(LL_PATH_DEFAULT_SKIN, "xui", "schema");
 559
 560	//typedef LLWidgetTypeRegistry::Registrar::registry_map_t::const_iterator registry_it;
 561	//registry_it end_it = LLWidgetTypeRegistry::defaultRegistrar().endItems();
 562	//for(registry_it it = LLWidgetTypeRegistry::defaultRegistrar().beginItems();
 563	//	it != end_it;
 564	//	++it)
 565	//{
 566	//	std::string widget_name = it->first;
 567	//	const LLInitParam::BaseBlock& block = 
 568	//		(*LLDefaultParamBlockRegistry::instance().getValue(*LLWidgetTypeRegistry::instance().getValue(widget_name)))();
 569	//	LLXMLNodePtr root_nodep = new LLXMLNode();
 570	//	LLRNGWriter().writeRNG(widget_name, root_nodep, block, "http://www.lindenlab.com/xui");
 571
 572	//	std::string file_name(template_path + gDirUtilp->getDirDelimiter() + widget_name + ".rng");
 573
 574	//	LLFILE* rng_file = LLFile::fopen(file_name.c_str(), "w");
 575	//	{
 576	//		LLXMLNode::writeHeaderToFile(rng_file);
 577	//		const bool use_type_decorations = false;
 578	//		root_nodep->writeToFile(rng_file, std::string(), use_type_decorations);
 579	//	}
 580	//	fclose(rng_file);
 581	//}
 582	//gViewerWindow->setCursor(UI_CURSOR_ARROW);
 583}
 584
 585void LLFloaterUIPreview::onClickShowRectangles(const LLSD& data)
 586{
 587	LLPreviewedFloater::sShowRectangles = data.asBoolean();
 588}
 589
 590// Close click handler -- delete my displayed floater if it exists
 591void LLFloaterUIPreview::onClose(bool app_quitting)
 592{
 593	if(!app_quitting && mDisplayedFloater)
 594	{
 595		onClickCloseDisplayedFloater(PRIMARY_FLOATER);
 596		onClickCloseDisplayedFloater(SECONDARY_FLOATER);
 597		delete mDisplayedFloater;
 598		mDisplayedFloater = NULL;
 599		delete mDisplayedFloater_2;
 600		mDisplayedFloater_2 = NULL;
 601	}
 602}
 603
 604// Error handling (to avoid code repetition)
 605// *TODO: this is currently unlocalized.  Add to alerts/notifications.xml, someday, maybe.
 606void LLFloaterUIPreview::popupAndPrintWarning(const std::string& warning)
 607{
 608	llwarns << warning << llendl;
 609	LLSD args;
 610	args["MESSAGE"] = warning;
 611	LLNotificationsUtil::add("GenericAlert", args);
 612}
 613
 614// Get localization string from drop-down menu
 615std::string LLFloaterUIPreview::getLocStr(S32 ID)
 616{
 617	if(ID == 1)
 618	{
 619		return mLanguageSelection->getSelectedItemLabel(0);
 620	}
 621	else
 622	{
 623		return mLanguageSelection_2->getSelectedItemLabel(0);
 624	}
 625}
 626
 627// Get localized directory (build path from data directory to XUI files, substituting localization string in for language)
 628std::string LLFloaterUIPreview::getLocalizedDirectory()
 629{
 630	return get_xui_dir() + (getLocStr(1)) + mDelim; // e.g. "C:/Code/guipreview/indra/newview/skins/xui/en/";
 631}
 632
 633// Refresh the list of floaters by doing a directory traverse for XML XUI floater files
 634// Could be used to grab any specific language's list of compatible floaters, but currently it's just used to get all of them
 635void LLFloaterUIPreview::refreshList()
 636{
 637	// Note: the mask doesn't seem to accept regular expressions, so there need to be two directory searches here
 638	mFileList->clearRows();		// empty list
 639	std::string name;
 640	BOOL found = TRUE;
 641
 642	LLDirIterator floater_iter(getLocalizedDirectory(), "floater_*.xml");
 643	while(found)				// for every floater file that matches the pattern
 644	{
 645		if((found = floater_iter.next(name)))	// get next file matching pattern
 646		{
 647			addFloaterEntry(name.c_str());	// and add it to the list (file name only; localization code takes care of rest of path)
 648		}
 649	}
 650	found = TRUE;
 651
 652	LLDirIterator inspect_iter(getLocalizedDirectory(), "inspect_*.xml");
 653	while(found)				// for every inspector file that matches the pattern
 654	{
 655		if((found = inspect_iter.next(name)))	// get next file matching pattern
 656		{
 657			addFloaterEntry(name.c_str());	// and add it to the list (file name only; localization code takes care of rest of path)
 658		}
 659	}
 660	found = TRUE;
 661
 662	LLDirIterator menu_iter(getLocalizedDirectory(), "menu_*.xml");
 663	while(found)				// for every menu file that matches the pattern
 664	{
 665		if((found = menu_iter.next(name)))	// get next file matching pattern
 666		{
 667			addFloaterEntry(name.c_str());	// and add it to the list (file name only; localization code takes care of rest of path)
 668		}
 669	}
 670	found = TRUE;
 671
 672	LLDirIterator panel_iter(getLocalizedDirectory(), "panel_*.xml");
 673	while(found)				// for every panel file that matches the pattern
 674	{
 675		if((found = panel_iter.next(name)))	// get next file matching pattern
 676		{
 677			addFloaterEntry(name.c_str());	// and add it to the list (file name only; localization code takes care of rest of path)
 678		}
 679	}
 680	found = TRUE;
 681
 682	LLDirIterator sidepanel_iter(getLocalizedDirectory(), "sidepanel_*.xml");
 683	while(found)				// for every sidepanel file that matches the pattern
 684	{
 685		if((found = sidepanel_iter.next(name)))	// get next file matching pattern
 686		{
 687			addFloaterEntry(name.c_str());	// and add it to the list (file name only; localization code takes care of rest of path)
 688		}
 689	}
 690
 691	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)
 692	{
 693		mFileList->selectFirstItem();
 694	}
 695}
 696
 697// Add a single entry to the list of available floaters
 698// Note: no deduplification (shouldn't be necessary)
 699void LLFloaterUIPreview::addFloaterEntry(const std::string& path)
 700{
 701	LLUUID* entry_id = new LLUUID();				// create a new UUID
 702	entry_id->generate(path);
 703	const LLUUID& entry_id_ref = *entry_id;			// get a reference to the UUID for the LLSD block
 704
 705	// fill LLSD column entry: initialize row/col structure
 706	LLSD row;
 707	row["id"] = entry_id_ref;
 708	LLSD& columns = row["columns"];
 709
 710	// Get name of floater:
 711	LLXmlTree xml_tree;
 712	std::string full_path = getLocalizedDirectory() + path;			// get full path
 713	BOOL success = xml_tree.parseFile(full_path.c_str(), TRUE);		// parse xml
 714	std::string entry_name;
 715	std::string entry_title;
 716	if(success)
 717	{
 718		// get root (or error handle)
 719		LLXmlTreeNode* root_floater = xml_tree.getRoot();
 720		if (!root_floater)
 721		{
 722			std::string warning = std::string("No root node found in XUI file: ") + path;
 723			popupAndPrintWarning(warning);
 724			return;
 725		}
 726
 727		// get name
 728		root_floater->getAttributeString("name",entry_name);
 729		if(std::string("") == entry_name)
 730		{
 731			entry_name = "Error: unable to load " + std::string(path);	// set to error state if load fails
 732		}
 733
 734		// get title
 735		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
 736	}
 737	else
 738	{
 739		std::string warning = std::string("Unable to parse XUI file: ") + path;	// error handling
 740		popupAndPrintWarning(warning);
 741		if(mLiveFile)
 742		{
 743			delete mLiveFile;
 744			mLiveFile = NULL;
 745		}
 746		return;
 747	}
 748
 749	// Fill floater title column
 750	columns[0]["column"] = "title_column";
 751	columns[0]["type"] = "text";
 752	columns[0]["value"] = entry_title;
 753
 754	// Fill floater path column
 755	columns[1]["column"] = "file_column";
 756	columns[1]["type"] = "text";
 757	columns[1]["value"] = std::string(path);
 758
 759	// Fill floater name column
 760	columns[2]["column"] = "top_level_node_column";
 761	columns[2]["type"] = "text";
 762	columns[2]["value"] = entry_name;
 763
 764	mFileList->addElement(row);		// actually add to list
 765}
 766
 767// Respond to button click to display/refresh currently-selected floater
 768void LLFloaterUIPreview::onClickDisplayFloater(S32 caller_id)
 769{
 770	displayFloater(TRUE, caller_id);
 771}
 772
 773// Saves the current floater/panel
 774void LLFloaterUIPreview::onClickSaveFloater(S32 caller_id)
 775{
 776	displayFloater(TRUE, caller_id, true);
 777}
 778
 779// Saves all floater/panels
 780void LLFloaterUIPreview::onClickSaveAll(S32 caller_id)
 781{
 782	int listSize = mFileList->getItemCount();
 783
 784	for (int index = 0; index < listSize; index++)
 785	{
 786		mFileList->selectNthItem(index);
 787		displayFloater(TRUE, caller_id, true);
 788	}
 789}
 790
 791// Given path to floater or panel XML file "filename.xml",
 792// returns "filename_new.xml"
 793static std::string append_new_to_xml_filename(const std::string& path)
 794{
 795	std::string full_filename = gDirUtilp->findSkinnedFilename(LLUI::getLocalizedSkinPath(), path);
 796	std::string::size_type extension_pos = full_filename.rfind(".xml");
 797	full_filename.resize(extension_pos);
 798	full_filename += "_new.xml";
 799	return full_filename;
 800}
 801
 802// Actually display the floater
 803// 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;
 804// otherwise, we get an infinite loop as the live file keeps recreating itself.  That means this function is generally called twice.
 805void LLFloaterUIPreview::displayFloater(BOOL click, S32 ID, bool save)
 806{
 807	// Convince UI that we're in a different language (the one selected on the drop-down menu)
 808	LLLocalizationResetForcer reset_forcer(this, ID);						// save old language in reset forcer object (to be reset upon destruction when it falls out of scope)
 809
 810	LLPreviewedFloater** floaterp = (ID == 1 ? &(mDisplayedFloater) : &(mDisplayedFloater_2));
 811	if(ID == 1)
 812	{
 813		BOOL floater_already_open = mDisplayedFloater != NULL;
 814		if(floater_already_open)											// if we are already displaying a floater
 815		{
 816			mLastDisplayedX = mDisplayedFloater->calcScreenRect().mLeft;	// save floater's last known position to put the new one there
 817			mLastDisplayedY = mDisplayedFloater->calcScreenRect().mBottom;
 818			delete mDisplayedFloater;							// delete it (this closes it too)
 819			mDisplayedFloater = NULL;							// and reset the pointer
 820		}
 821	}
 822	else
 823	{
 824		if(mDisplayedFloater_2 != NULL)
 825		{
 826			delete mDisplayedFloater_2;
 827			mDisplayedFloater_2 = NULL;
 828		}
 829	}
 830
 831	std::string path = mFileList->getSelectedItemLabel(1);		// get the path of the currently-selected floater
 832	if(std::string("") == path)											// if no item is selected
 833	{
 834		return;															// ignore click (this can only happen with empty list; otherwise an item is always selected)
 835	}
 836
 837	LLFloater::Params p(LLFloater::getDefaultParams());
 838	p.min_height=p.header_height;
 839	p.min_width=10;
 840
 841	*floaterp = new LLPreviewedFloater(this, p);
 842
 843	if(!strncmp(path.c_str(),"floater_",8)
 844		|| !strncmp(path.c_str(), "inspect_", 8))		// if it's a floater
 845	{
 846		if (save)
 847		{
 848			LLXMLNodePtr floater_write = new LLXMLNode();			
 849			(*floaterp)->buildFromFile(path, floater_write);	// just build it
 850
 851			if (!floater_write->isNull())
 852			{
 853				std::string full_filename = append_new_to_xml_filename(path);
 854				LLFILE* floater_temp = LLFile::fopen(full_filename.c_str(), "w");
 855				LLXMLNode::writeHeaderToFile(floater_temp);
 856				const bool use_type_decorations = false;
 857				floater_write->writeToFile(floater_temp, std::string(), use_type_decorations);
 858				fclose(floater_temp);
 859			}
 860		}
 861		else
 862		{
 863			(*floaterp)->buildFromFile(path);	// just build it
 864			(*floaterp)->openFloater((*floaterp)->getKey());
 865			(*floaterp)->setCanResize((*floaterp)->isResizable());
 866		}
 867
 868	}
 869	else if (!strncmp(path.c_str(),"menu_",5))								// if it's a menu
 870	{
 871		if (save)
 872		{	
 873			LLXMLNodePtr menu_write = new LLXMLNode();	
 874			LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>(path, gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance(), menu_write);
 875
 876			if (!menu_write->isNull())
 877			{
 878				std::string full_filename = append_new_to_xml_filename(path);
 879				LLFILE* menu_temp = LLFile::fopen(full_filename.c_str(), "w");
 880				LLXMLNode::writeHeaderToFile(menu_temp);
 881				const bool use_type_decorations = false;
 882				menu_write->writeToFile(menu_temp, std::string(), use_type_decorations);
 883				fclose(menu_temp);
 884			}
 885
 886			delete menu;
 887		}
 888	}
 889	else																// if it is a panel...
 890	{
 891		(*floaterp)->setCanResize(true);
 892
 893		const LLFloater::Params& floater_params = LLFloater::getDefaultParams();
 894		S32 floater_header_size = floater_params.header_height;
 895
 896		LLPanel::Params panel_params;
 897		LLPanel* panel = LLUICtrlFactory::create<LLPanel>(panel_params);	// create a new panel
 898
 899		if (save)
 900		{
 901			LLXMLNodePtr panel_write = new LLXMLNode();
 902			panel->buildFromFile(path, panel_write);		// build it
 903			
 904			if (!panel_write->isNull())
 905			{
 906				std::string full_filename = append_new_to_xml_filename(path);
 907				LLFILE* panel_temp = LLFile::fopen(full_filename.c_str(), "w");
 908				LLXMLNode::writeHeaderToFile(panel_temp);
 909				const bool use_type_decorations = false;
 910				panel_write->writeToFile(panel_temp, std::string(), use_type_decorations);
 911				fclose(panel_temp);
 912			}
 913		}
 914		else
 915		{
 916			panel->buildFromFile(path);										// build it
 917			LLRect new_size = panel->getRect();								// get its rectangle
 918			panel->setOrigin(2,2);											// reset its origin point so it's not offset by -left or other XUI attributes
 919			(*floaterp)->setTitle(path);									// use the file name as its title, since panels have no guaranteed meaningful name attribute
 920			panel->setUseBoundingRect(TRUE);								// enable the use of its outer bounding rect (normally disabled because it's O(n) on the number of sub-elements)
 921			panel->updateBoundingRect();									// update bounding rect
 922			LLRect bounding_rect = panel->getBoundingRect();				// get the bounding rect
 923			LLRect new_rect = panel->getRect();								// get the panel's rect
 924			new_rect.unionWith(bounding_rect);								// union them to make sure we get the biggest one possible
 925			LLRect floater_rect = new_rect;
 926			floater_rect.stretch(4, 4);
 927			(*floaterp)->reshape(floater_rect.getWidth(), floater_rect.getHeight() + floater_header_size);	// reshape floater to match the union rect's dimensions
 928			panel->reshape(new_rect.getWidth(), new_rect.getHeight());		// reshape panel to match the union rect's dimensions as well (both are needed)
 929			(*floaterp)->addChild(panel);					// add panel as child
 930			(*floaterp)->openFloater();						// open floater (needed?)
 931		}
 932	}
 933
 934	if(ID == 1)
 935	{
 936		(*floaterp)->setOrigin(mLastDisplayedX, mLastDisplayedY);
 937	}
 938
 939	// *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,
 940	// resulting in a double free
 941	(*floaterp)->setCanClose(FALSE);
 942	
 943	if(ID == 1)
 944	{
 945		mCloseOtherButton->setEnabled(TRUE);	// enable my floater's close button
 946	}
 947	else
 948	{
 949		mCloseOtherButton_2->setEnabled(TRUE);
 950	}
 951
 952	// Add localization to title so user knows whether it's localized or defaulted to en
 953	std::string full_path = getLocalizedDirectory() + path;
 954	std::string floater_lang = "EN";
 955	llstat dummy;
 956	if(!LLFile::stat(full_path.c_str(), &dummy))	// if the file does not exist
 957	{
 958		floater_lang = getLocStr(ID);
 959	}
 960	std::string new_title = (*floaterp)->getTitle() + std::string(" [") + floater_lang +
 961						(ID == 1 ? " - Primary" : " - Secondary") + std::string("]");
 962	(*floaterp)->setTitle(new_title);
 963
 964	(*floaterp)->center();
 965	addDependentFloater(*floaterp);
 966
 967	if(click && ID == 1 && !save)
 968	{
 969		// set up live file to track it
 970		if(mLiveFile)
 971		{
 972			delete mLiveFile;
 973			mLiveFile = NULL;
 974		}
 975		mLiveFile = new LLGUIPreviewLiveFile(std::string(full_path.c_str()),std::string(path.c_str()),this);
 976		mLiveFile->checkAndReload();
 977		mLiveFile->addToEventTimer();
 978	}
 979
 980	if(ID == 1)
 981	{
 982		mToggleOverlapButton->setEnabled(TRUE);
 983	}
 984
 985	if(LLView::sHighlightingDiffs && click && ID == 1)
 986	{
 987		highlightChangedElements();
 988	}
 989
 990	if(ID == 1)
 991	{
 992		mOverlapPanel->mOverlapMap.clear();
 993		LLView::sPreviewClickedElement = NULL;	// stop overlapping elements from drawing
 994		mOverlapPanel->mLastClickedElement = NULL;
 995		findOverlapsInChildren((LLView*)mDisplayedFloater);
 996
 997		// highlight and enable them
 998		if(mHighlightingOverlaps)
 999		{
1000			for(LLOverlapPanel::OverlapMap::iterator iter = mOverlapPanel->mOverlapMap.begin(); iter != mOverlapPanel->mOverlapMap.end(); ++iter)
1001			{
1002				LLView* viewp = iter->first;
1003				LLView::sPreviewHighlightedElements.insert(viewp);
1004			}
1005		}
1006		else if(LLView::sHighlightingDiffs)
1007		{
1008			highlightChangedElements();
1009		}
1010	}
1011
1012	// NOTE: language is reset here automatically when the reset forcer object falls out of scope (see header for details)
1013}
1014
1015// Respond to button click to edit currently-selected floater
1016void LLFloaterUIPreview::onClickEditFloater()
1017{
1018	// Determine file to edit.
1019	std::string file_path;
1020	{
1021		std::string file_name = mFileList->getSelectedItemLabel(1);	// get the file name of the currently-selected floater
1022		if (file_name.empty())					// if no item is selected
1023		{
1024			llwarns << "No file selected" << llendl;
1025			return;															// ignore click
1026		}
1027		file_path = getLocalizedDirectory() + file_name;
1028
1029		// 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)
1030		llstat dummy;
1031		if(LLFile::stat(file_path.c_str(), &dummy))								// if the file does not exist
1032		{
1033			popupAndPrintWarning("No file for this floater exists in the selected localization.  Opening the EN version instead.");
1034			file_path = get_xui_dir() + mDelim + "en" + mDelim + file_name; // open the en version instead, by default
1035		}
1036	}
1037
1038	// Set the editor command.
1039	std::string cmd_override;
1040	{
1041		std::string bin = mEditorPathTextBox->getText();
1042		if (!bin.empty())
1043		{
1044			// surround command with double quotes for the case if the path contains spaces
1045			if (bin.find("\"") == std::string::npos)
1046			{
1047				bin = "\"" + bin + "\"";
1048			}
1049
1050			std::string args = mEditorArgsTextBox->getText();
1051			cmd_override = bin + " " + args;
1052		}
1053	}
1054
1055	LLExternalEditor::EErrorCode status = mExternalEditor.setCommand("LL_XUI_EDITOR", cmd_override);
1056	if (status != LLExternalEditor::EC_SUCCESS)
1057	{
1058		std::string warning;
1059
1060		if (status == LLExternalEditor::EC_NOT_SPECIFIED) // Use custom message for this error.
1061		{
1062			warning = getString("ExternalEditorNotSet");
1063		}
1064		else
1065		{
1066			warning = LLExternalEditor::getErrorMessage(status);
1067		}
1068
1069		popupAndPrintWarning(warning);
1070		return;
1071	}
1072
1073	// Run the editor.
1074	if (mExternalEditor.run(file_path) != LLExternalEditor::EC_SUCCESS)
1075	{
1076		popupAndPrintWarning(LLExternalEditor::getErrorMessage(status));
1077		return;
1078	}
1079}
1080
1081// Respond to button click to browse for an executable with which to edit XML files
1082void LLFloaterUIPreview::onClickBrowseForEditor()
1083{
1084	// create load dialog box
1085	LLFilePicker::ELoadFilter type = (LLFilePicker::ELoadFilter)((intptr_t)((void*)LLFilePicker::FFLOAD_ALL));	// nothing for *.exe so just use all
1086	LLFilePicker& picker = LLFilePicker::instance();
1087	if (!picker.getOpenFile(type))	// user cancelled -- do nothing
1088	{
1089		return;
1090	}
1091
1092	// put the selected path into text field
1093	const std::string chosen_path = picker.getFirstFile();
1094	std::string executable_path = chosen_path;
1095#if LL_DARWIN
1096	// on Mac, if it's an application bundle, figure out the actual path from the Info.plist file
1097	CFStringRef path_cfstr = CFStringCreateWithCString(kCFAllocatorDefault, chosen_path.c_str(), kCFStringEncodingMacRoman);		// get path as a CFStringRef
1098	CFURLRef path_url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path_cfstr, kCFURLPOSIXPathStyle, TRUE);			// turn it into a CFURLRef
1099	CFBundleRef chosen_bundle = CFBundleCreate(kCFAllocatorDefault, path_url);												// get a handle for the bundle
1100	if(NULL != chosen_bundle)
1101	{
1102		CFDictionaryRef bundleInfoDict = CFBundleGetInfoDictionary(chosen_bundle);												// get the bundle's dictionary
1103		if(NULL != bundleInfoDict)
1104		{
1105			CFStringRef executable_cfstr = (CFStringRef)CFDictionaryGetValue(bundleInfoDict, CFSTR("CFBundleExecutable"));	// get the name of the actual executable (e.g. TextEdit or firefox-bin)
1106			int max_file_length = 256;																						// (max file name length is 255 in OSX)
1107			char executable_buf[max_file_length];
1108			if(CFStringGetCString(executable_cfstr, executable_buf, max_file_length, kCFStringEncodingMacRoman))			// convert CFStringRef to char*
1109			{
1110				executable_path += std::string("/Contents/MacOS/") + std::string(executable_buf);							// append path to executable directory and then executable name to exec path
1111			}
1112			else
1113			{
1114				std::string warning = "Unable to get CString from CFString for executable path";
1115				popupAndPrintWarning(warning);
1116			}
1117		}
1118		else
1119		{
1120			std::string warning = "Unable to get bundle info dictionary from application bundle";
1121			popupAndPrintWarning(warning);
1122		}
1123	}
1124	else
1125	{
1126		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
1127		{
1128			std::string warning = std::string("Unable to get bundle from path \"") + chosen_path + std::string("\"");
1129			popupAndPrintWarning(warning);
1130		}
1131	}
1132
1133#endif
1134	mEditorPathTextBox->setText(std::string(executable_path));	// copy the path to the executable to the textfield for display and later fetching
1135}
1136
1137// Respond to button click to browse for a VLT-generated diffs file
1138void LLFloaterUIPreview::onClickBrowseForDiffs()
1139{
1140	// create load dialog box
1141	LLFilePicker::ELoadFilter type = (LLFilePicker::ELoadFilter)((intptr_t)((void*)LLFilePicker::FFLOAD_XML));	// nothing for *.exe so just use all
1142	LLFilePicker& picker = LLFilePicker::instance();
1143	if (!picker.getOpenFile(type))	// user cancelled -- do nothing
1144	{
1145		return;
1146	}
1147
1148	// put the selected path into text field
1149	const std::string chosen_path = picker.getFirstFile();
1150	mDiffPathTextBox->setText(std::string(chosen_path));	// copy the path to the executable to the textfield for display and later fetching
1151	if(LLView::sHighlightingDiffs)								// if we're already highlighting, toggle off and then on so we get the data from the new file
1152	{
1153		onClickToggleDiffHighlighting();
1154		onClickToggleDiffHighlighting();
1155	}
1156}
1157
1158void LLFloaterUIPreview::onClickToggleDiffHighlighting()
1159{
1160	if(mHighlightingOverlaps)
1161	{
1162		onClickToggleOverlapping();
1163		mToggleOverlapButton->toggleState();
1164	}
1165
1166	LLView::sPreviewHighlightedElements.clear();	// clear lists first
1167	mDiffsMap.clear();
1168	mFileList->clearHighlightedItems();
1169
1170	if(LLView::sHighlightingDiffs)				// Turning highlighting off
1171	{
1172		LLView::sHighlightingDiffs = !sHighlightingDiffs;
1173		return;
1174	}
1175	else											// Turning highlighting on
1176	{
1177		// Get the file and make sure it exists
1178		std::string path_in_textfield = mDiffPathTextBox->getText();	// get file path
1179		BOOL error = FALSE;
1180
1181		if(std::string("") == path_in_textfield)									// check for blank file
1182		{
1183			std::string warning = "Unable to highlight differences because no file was provided; fill in the relevant text field";
1184			popupAndPrintWarning(warning);
1185			error = TRUE;
1186		}
1187
1188		llstat dummy;
1189		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)
1190		{
1191			std::string warning = std::string("Unable to highlight differences because an invalid path to a difference file was provided:\"") + path_in_textfield + "\"";
1192			popupAndPrintWarning(warning);
1193			error = TRUE;
1194		}
1195
1196		// Build a list of changed elements as given by the XML
1197		std::list<std::string> changed_element_names;
1198		LLXmlTree xml_tree;
1199		BOOL success = xml_tree.parseFile(path_in_textfield.c_str(), TRUE);
1200
1201		if(success && !error)
1202		{
1203			LLXmlTreeNode* root_floater = xml_tree.getRoot();
1204			if(!strncmp("XuiDelta",root_floater->getName().c_str(),9))
1205			{
1206				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?)
1207					 child != NULL;
1208 					 child = root_floater->getNextChild())	// get child for next iteration
1209				{
1210					if(!strncmp("file",child->getName().c_str(),5))
1211					{
1212						scanDiffFile(child);
1213					}
1214					else if(!strncmp("error",child->getName().c_str(),6))
1215					{
1216						std::string error_file, error_message;
1217						child->getAttributeString("filename",error_file);
1218						child->getAttributeString("message",error_message);
1219						if(mDiffsMap.find(error_file) != mDiffsMap.end())
1220						{
1221							mDiffsMap.insert(std::make_pair(error_file,std::make_pair(StringListPtr(new StringList), StringListPtr(new StringList))));
1222						}
1223						mDiffsMap[error_file].second->push_back(error_message);
1224					}
1225					else
1226					{
1227						std::string warning = std::string("Child was neither a file or an error, but rather the following:\"") + std::string(child->getName()) + "\"";
1228						popupAndPrintWarning(warning);
1229						error = TRUE;
1230						break;
1231					}
1232				}
1233			}
1234			else
1235			{
1236				std::string warning = std::string("Root node not named XuiDelta:\"") + path_in_textfield + "\"";
1237				popupAndPrintWarning(warning);
1238				error = TRUE;
1239			}
1240		}
1241		else if(!error)
1242		{
1243			std::string warning = std::string("Unable to create tree from XML:\"") + path_in_textfield + "\"";
1244			popupAndPrintWarning(warning);
1245			error = TRUE;
1246		}
1247
1248		if(error)	// if we encountered an error, reset the button to off
1249		{
1250			mToggleHighlightButton->setToggleState(FALSE);		
1251		}
1252		else		// only toggle if we didn't encounter an error
1253		{
1254			LLView::sHighlightingDiffs = !sHighlightingDiffs;
1255			highlightChangedElements();		// *TODO: this is extraneous, right?
1256			highlightChangedFiles();			// *TODO: this is extraneous, right?
1257		}
1258	}
1259}
1260
1261void LLFloaterUIPreview::scanDiffFile(LLXmlTreeNode* file_node)
1262{
1263	// Get file name
1264	std::string file_name;
1265	file_node->getAttributeString("name",file_name);
1266	if(std::string("") == file_name)
1267	{
1268		std::string warning = std::string("Empty file name encountered in differences:\"") + file_name + "\"";
1269		popupAndPrintWarning(warning);
1270		return;
1271	}
1272
1273	// Get a list of changed elements
1274	// Get the first child first, then below get the next one; otherwise the iterator is invalid (bug or feature in XML code?)
1275	for (LLXmlTreeNode* child = file_node->getFirstChild(); child != NULL; child = file_node->getNextChild())
1276	{
1277		if(!strncmp("delta",child->getName().c_str(),6))
1278		{
1279			std::string id;
1280			child->getAttributeString("id",id);
1281			if(mDiffsMap.find(file_name) == mDiffsMap.end())
1282			{
1283				mDiffsMap.insert(std::make_pair(file_name,std::make_pair(StringListPtr(new StringList), StringListPtr(new StringList))));
1284			}
1285			mDiffsMap[file_name].first->push_back(std::string(id.c_str()));
1286		}
1287		else
1288		{
1289			std::string warning = std::string("Child of file was not a delta, but rather the following:\"") + std::string(child->getName()) + "\"";
1290			popupAndPrintWarning(warning);
1291			return;
1292		}
1293	}
1294}
1295
1296void LLFloaterUIPreview::highlightChangedElements()
1297{
1298	if(NULL == mLiveFile)
1299	{
1300		return;
1301	}
1302
1303	// Process differences first (we want their warnings to be shown underneath other warnings)
1304	StringListPtr changed_element_paths;
1305	DiffMap::iterator iterExists = mDiffsMap.find(mLiveFile->mFileName);
1306	if(iterExists != mDiffsMap.end())
1307	{
1308		changed_element_paths = mDiffsMap[mLiveFile->mFileName].first;		// retrieve list of changed element paths from map
1309	}
1310
1311	for(std::list<std::string>::iterator iter = changed_element_paths->begin(); iter != changed_element_paths->end(); ++iter)	// for every changed element path
1312	{
1313		LLView* element = mDisplayedFloater;
1314		if(!strncmp(iter->c_str(),".",1))	// if it's the root floater itself
1315		{
1316			continue;
1317		}
1318
1319		// 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.)
1320		typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
1321		boost::char_separator<char> sep(".");
1322		tokenizer tokens(*iter, sep);
1323		tokenizer::iterator token_iter;
1324		BOOL failed = FALSE;
1325		for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter)
1326		{
1327			element = element->findChild<LLView>(*token_iter,FALSE);	// try to find element: don't recur, and don't create if missing
1328
1329			// if we still didn't find it...
1330			if(NULL == element)												
1331			{
1332				llinfos << "Unable to find element in XuiDelta file named \"" << *iter << "\" in file \"" << mLiveFile->mFileName <<
1333							"\". The element may no longer exist, the path may be incorrect, or it may not be a non-displayable element …

Large files files are truncated, but you can click here to view the full file