Commits

Stinson Linden committed 4c3460c Merge

Comments (0)

Files changed (87)

 710785535362b3cb801b6a3dc4703be3373bd0cd 3.4.2-beta3
 2aa72e3372a83dece4df9cf72fb1e7c34f90b5e3 DRTVWR-209
 f7bedce18ad52283e6072814db23318907261487 DRTVWR-238
+7b64c96fbcadf360bd2feaae19d330166b70877c DRTVWR-210
+e9a5886052433d5db9e504ffaca10890f9932979 DRTVWR-243
+73b84b9864dc650fe7c8fc9f52361450f0849004 3.4.2-beta4
+5e4e4128b256525bafc07a62e35ae8527aaa9c9d DRTVWR-241

doc/contributions.txt

 	VWR-2682
 	VWR-2684
 Nick Rhodes
+Nicky Dasmijn
+	VWR-29228
 Nicky Perian
 	OPEN-1
 	STORM-1087
 	STORM-1602
 	STORM-1868
     VWR-26622
+	VWR-29224
 Talamasca
 Tali Rosca
 Tayra Dagostino
 Winter Ventura
 Wilton Lundquist
 	VWR-7682
+Wolf Loonie
+	STORM-1868
 WolfPup Lowenhar
 	OPEN-1
 	OPEN-37

indra/integration_tests/llimage_libtest/llimage_libtest.cpp

 		LLDirIterator iter(dir, name);
 		while (iter.next(next_name))
 		{
-			std::string file_name = dir + gDirUtilp->getDirDelimiter() + next_name;
+			std::string file_name = gDirUtilp->add(dir, next_name);
 			input_filenames.push_back(file_name);
 		}
 	}

indra/integration_tests/llui_libtest/llui_libtest.cpp

 };
 TestImageProvider gTestImageProvider;
 
-static std::string get_xui_dir()
-{
-	std::string delim = gDirUtilp->getDirDelimiter();
-	return gDirUtilp->getSkinBaseDir() + delim + "default" + delim + "xui" + delim;
-}
-
 void init_llui()
 {
 	// Font lookup needs directory support
 	const char* newview_path = "../../../newview";
 #endif
 	gDirUtilp->initAppDirs("SecondLife", newview_path);
-	gDirUtilp->setSkinFolder("default");
+	gDirUtilp->setSkinFolder("default", "en");
 	
 	// colors are no longer stored in a LLControlGroup file
 	LLUIColorTable::instance().loadFromSettings();
 
-	std::string config_filename = gDirUtilp->getExpandedFilename(
-																 LL_PATH_APP_SETTINGS, "settings.xml");
+	std::string config_filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "settings.xml");
 	gSavedSettings.loadFromFile(config_filename);
 	
 	// See LLAppViewer::init()
 	
 	const bool no_register_widgets = false;
 	LLWidgetReg::initClass( no_register_widgets );
-	
-	// Unclear if this is needed
-	LLUI::setupPaths();
+
 	// Otherwise we get translation warnings when setting up floaters
 	// (tooltips for buttons)
 	std::set<std::string> default_args;
 	// otherwise it crashes.
 	LLFontGL::initClass(96.f, 1.f, 1.f,
 						gDirUtilp->getAppRODataDir(),
-						LLUI::getXUIPaths(),
 						false );	// don't create gl textures
 	
 	LLFloaterView::Params fvparams;
 	gFloaterView = LLUICtrlFactory::create<LLFloaterView> (fvparams);
 }
 
+/*==========================================================================*|
+static std::string get_xui_dir()
+{
+	std::string delim = gDirUtilp->getDirDelimiter();
+	return gDirUtilp->getSkinBaseDir() + delim + "default" + delim + "xui" + delim;
+}
+
+// buildFromFile() no longer supports generate-output-LLXMLNode
 void export_test_floaters()
 {
 	// Convert all test floaters to new XML format
 		floater->buildFromFile(	filename,
 								//	 FALSE,	// don't open floater
 								output_node);
-		std::string out_filename = xui_dir + filename;
+		std::string out_filename = gDirUtilp->add(xui_dir, filename);
 		std::string::size_type extension_pos = out_filename.rfind(".xml");
 		out_filename.resize(extension_pos);
 		out_filename += "_new.xml";
 		fclose(floater_file);
 	}
 }
+|*==========================================================================*/
 
 int main(int argc, char** argv)
 {
 
 	init_llui();
 	
-	export_test_floaters();
+//	export_test_floaters();
 	
 	return 0;
 }

indra/linux_updater/linux_updater.cpp

 {
 	std::string image_filename;
 	iter.next(image_filename);
-	return image_path + "/" + image_filename;
+	return gDirUtilp->add(image_path, image_filename);
 }
 
 void on_window_closed(GtkWidget *sender, GdkEvent* event, gpointer data)

indra/llaudio/llaudioengine.cpp

 	mBufferp->mAudioDatap = this;
 	return true;
 }
-
-

indra/llcommon/lluri.cpp

 
 // system includes
 #include <boost/tokenizer.hpp>
+#include <boost/algorithm/string/find_iterator.hpp>
+#include <boost/algorithm/string/finder.hpp>
 
 void encode_character(std::ostream& ostr, std::string::value_type val)
 {
 					   const LLSD& path)
 {
 	LLURI result;
-	
+
 	// TODO: deal with '/' '?' '#' in host_port
 	if (prefix.find("://") != prefix.npos)
 	{
 			result.mEscapedPath += "/" + escapePathComponent(it->asString());
 		}
 	}
-	else if(path.isString())
+	else if (path.isString())
 	{
-		result.mEscapedPath += "/" + escapePathComponent(path.asString());
+		std::string pathstr(path);
+		// Trailing slash is significant in HTTP land. If caller specified,
+		// make a point of preserving.
+		std::string last_slash;
+		std::string::size_type len(pathstr.length());
+		if (len && pathstr[len-1] == '/')
+		{
+			last_slash = "/";
+		}
+
+		// Escape every individual path component, recombining with slashes.
+		for (boost::split_iterator<std::string::const_iterator>
+				 ti(pathstr, boost::first_finder("/")), tend;
+			 ti != tend; ++ti)
+		{
+			// Eliminate a leading slash or duplicate slashes anywhere. (Extra
+			// slashes show up here as empty components.) This test also
+			// eliminates a trailing slash, hence last_slash above.
+			if (! ti->empty())
+			{
+				result.mEscapedPath
+					+= "/" + escapePathComponent(std::string(ti->begin(), ti->end()));
+			}
+		}
+
+		// Reinstate trailing slash, if any.
+		result.mEscapedPath += last_slash;
 	} 
 	else if(path.isUndefined())
 	{
 	  // do nothing
 	}
-    else
+	else
 	{
 	  llwarns << "Valid path arguments to buildHTTP are array, string, or undef, you passed type" 
 			  << path.type() << llendl;

indra/llcommon/tests/lluri_test.cpp

 			ensure_equals("escape/unescape escaped", uri_esc_2, uri_esc_1);
 		}
 	};
-	
+
 	typedef test_group<URITestData>	URITestGroup;
 	typedef URITestGroup::object	URITestObject;
 
 	URITestGroup uriTestGroup("LLURI");
-	
+
 	template<> template<>
 	void URITestObject::test<1>()
 	{
 	template<> template<>
 	void URITestObject::test<2>()
 	{
-		// empty string
+		set_test_name("empty string");
 		checkParts(LLURI(""), "", "", "", "");
 	}
-	
+
 	template<> template<>
 	void URITestObject::test<3>()
 	{
-		// no scheme
+		set_test_name("no scheme");
 		checkParts(LLURI("foo"), "", "foo", "", "");
 		checkParts(LLURI("foo%3A"), "", "foo:", "", "");
 	}
 	template<> template<>
 	void URITestObject::test<4>()
 	{
-		// scheme w/o paths
+		set_test_name("scheme w/o paths");
 		checkParts(LLURI("mailto:zero@ll.com"),
 			"mailto", "zero@ll.com", "", "");
 		checkParts(LLURI("silly://abc/def?foo"),
 	template<> template<>
 	void URITestObject::test<5>()
 	{
-		// authority section
+		set_test_name("authority section");
 		checkParts(LLURI("http:///"),
 			"http", "///", "", "/");
-			
+
 		checkParts(LLURI("http://abc"),
 			"http", "//abc", "abc", "");
-			
+
 		checkParts(LLURI("http://a%2Fb/cd"),
 			"http", "//a/b/cd", "a/b", "/cd");
-			
+
 		checkParts(LLURI("http://host?"),
 			"http", "//host?", "host", "");
 	}
 	template<> template<>
 	void URITestObject::test<6>()
 	{		
-		// path section
+		set_test_name("path section");
 		checkParts(LLURI("http://host/a/b/"),
 				"http", "//host/a/b/", "host", "/a/b/");
-				
+
 		checkParts(LLURI("http://host/a%3Fb/"),
 				"http", "//host/a?b/", "host", "/a?b/");
-				
+
 		checkParts(LLURI("http://host/a:b/"),
 				"http", "//host/a:b/", "host", "/a:b/");
 	}
 	template<> template<>
 	void URITestObject::test<7>()
 	{		
-		// query string
+		set_test_name("query string");
 		checkParts(LLURI("http://host/?"),
 				"http", "//host/?", "host", "/", "");
-				
+
 		checkParts(LLURI("http://host/?x"),
 				"http", "//host/?x", "host", "/", "x");
-				
+
 		checkParts(LLURI("http://host/??"),
 				"http", "//host/??", "host", "/", "?");
-				
+
 		checkParts(LLURI("http://host/?%3F"),
 				"http", "//host/??", "host", "/", "?");
 	}
 		path.append("123");
 		checkParts(LLURI::buildHTTP("host", path),
 			"http", "//host/x/123", "host", "/x/123");
-		
+
 		LLSD query;
 		query["123"] = "12";
 		query["abcd"] = "abc";
 		checkParts(LLURI::buildHTTP("host", path, query),
 			"http", "//host/x/123?123=12&abcd=abc",
 			"host", "/x/123", "123=12&abcd=abc");
+
+		ensure_equals(LLURI::buildHTTP("host", "").asString(),
+					  "http://host");
+		ensure_equals(LLURI::buildHTTP("host", "/").asString(),
+					  "http://host/");
+		ensure_equals(LLURI::buildHTTP("host", "//").asString(),
+					  "http://host/");
+		ensure_equals(LLURI::buildHTTP("host", "dir name").asString(),
+					  "http://host/dir%20name");
+		ensure_equals(LLURI::buildHTTP("host", "dir name/").asString(),
+					  "http://host/dir%20name/");
+		ensure_equals(LLURI::buildHTTP("host", "/dir name").asString(),
+					  "http://host/dir%20name");
+		ensure_equals(LLURI::buildHTTP("host", "/dir name/").asString(),
+					  "http://host/dir%20name/");
+		ensure_equals(LLURI::buildHTTP("host", "dir name/subdir name").asString(),
+					  "http://host/dir%20name/subdir%20name");
+		ensure_equals(LLURI::buildHTTP("host", "dir name/subdir name/").asString(),
+					  "http://host/dir%20name/subdir%20name/");
+		ensure_equals(LLURI::buildHTTP("host", "/dir name/subdir name").asString(),
+					  "http://host/dir%20name/subdir%20name");
+		ensure_equals(LLURI::buildHTTP("host", "/dir name/subdir name/").asString(),
+					  "http://host/dir%20name/subdir%20name/");
+		ensure_equals(LLURI::buildHTTP("host", "//dir name//subdir name//").asString(),
+					  "http://host/dir%20name/subdir%20name/");
 	}
 
 	template<> template<>
 	void URITestObject::test<9>()
 	{
-		// test unescaped path components
+		set_test_name("test unescaped path components");
 		LLSD path;
 		path.append("x@*//*$&^");
 		path.append("123");
 	template<> template<>
 	void URITestObject::test<10>()
 	{
-		// test unescaped query components
+		set_test_name("test unescaped query components");
 		LLSD path;
 		path.append("x");
 		path.append("123");
 	template<> template<>
 	void URITestObject::test<11>()
 	{
-		// test unescaped host components
+		set_test_name("test unescaped host components");
 		LLSD path;
 		path.append("x");
 		path.append("123");
 			"http", "//hi123*33--}{:portstuffs/x/123?123=12&abcd=abc",
 			"hi123*33--}{:portstuffs", "/x/123", "123=12&abcd=abc");
 	}
-	
+
 	template<> template<>
 	void URITestObject::test<12>()
 	{
-		// test funky host_port values that are actually prefixes
-		
+		set_test_name("test funky host_port values that are actually prefixes");
+
 		checkParts(LLURI::buildHTTP("http://example.com:8080", LLSD()),
 			"http", "//example.com:8080",
 			"example.com:8080", "");
-			
+
 		checkParts(LLURI::buildHTTP("http://example.com:8080/", LLSD()),
 			"http", "//example.com:8080/",
 			"example.com:8080", "/");
 			"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
 			"0123456789"
 			"-._~";
-		// test escape
+		set_test_name("test escape");
 		ensure_equals("escaping", LLURI::escape("abcdefg", "abcdef"), "abcdef%67");
 		ensure_equals("escaping", LLURI::escape("|/&\\+-_!@", ""), "%7C%2F%26%5C%2B%2D%5F%21%40");
 		ensure_equals("escaping as query variable", 
 		cedilla.push_back( (char)0xA7 );
 		ensure_equals("escape UTF8", LLURI::escape( cedilla, unreserved), "%C3%A7");
 	}
-	
+
 
 	template<> template<>
 	void URITestObject::test<14>()
 	{
-		// make sure escape and unescape of empty strings return empty
-		// strings.
+		set_test_name("make sure escape and unescape of empty strings return empty strings.");
 		std::string uri_esc(LLURI::escape(""));
 		ensure("escape string empty", uri_esc.empty());
 		std::string uri_raw(LLURI::unescape(""));
 	template<> template<>
 	void URITestObject::test<15>()
 	{
-		// do some round-trip tests
+		set_test_name("do some round-trip tests");
 		escapeRoundTrip("http://secondlife.com");
 		escapeRoundTrip("http://secondlife.com/url with spaces");
 		escapeRoundTrip("http://bad[domain]name.com/");
 	template<> template<>
 	void URITestObject::test<16>()
 	{
-		// Test the default escaping
+		set_test_name("Test the default escaping");
 		// yes -- this mangles the url. This is expected behavior
 		std::string simple("http://secondlife.com");
 		ensure_equals(
 	template<> template<>
 	void URITestObject::test<17>()
 	{
-		// do some round-trip tests with very long strings.
+		set_test_name("do some round-trip tests with very long strings.");
 		escapeRoundTrip("Welcome to Second Life.We hope you'll have a richly rewarding experience, filled with creativity, self expression and fun.The goals of the Community Standards are simple: treat each other with respect and without harassment, adhere to local standards as indicated by simulator ratings, and refrain from any hate activity which slurs a real-world individual or real-world community. Behavioral Guidelines - The Big Six");
 		escapeRoundTrip(
 			"'asset_data':b(12100){'task_id':ucc706f2d-0b68-68f8-11a4-f1043ff35ca0}\n{\n\tname\tObject|\n\tpermissions 0\n\t{\n\t\tbase_mask\t7fffffff\n\t\towner_mask\t7fffffff\n\t\tgroup_mask\t00000000\n\t\teveryone_mask\t00000000\n\t\tnext_owner_mask\t7fffffff\n\t\tcreator_id\t13fd9595-a47b-4d64-a5fb-6da645f038e0\n\t\towner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tlast_owner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tgroup_id\t00000000-0000-0000-0000-000000000000\n\t}\n\tlocal_id\t217444921\n\ttotal_crc\t323\n\ttype\t2\n\ttask_valid\t2\n\ttravel_access\t13\n\tdisplayopts\t2\n\tdisplaytype\tv\n\tpos\t-0.368634403\t0.00781063363\t-0.569040775\n\toldpos\t150.117996\t25.8658009\t8.19664001\n\trotation\t-0.06293071806430816650390625\t-0.6995697021484375\t-0.7002241611480712890625\t0.1277817934751510620117188\n\tchildpos\t-0.00499999989\t-0.0359999985\t0.307999998\n\tchildrot\t-0.515492737293243408203125\t-0.46601200103759765625\t0.529055416584014892578125\t0.4870323240756988525390625\n\tscale"
 			"D STRING RW SV 20f36c3a-b44b-9bc7-87f3-018bfdfc8cda\n\tscratchpad\t0\n\t{\n\t\n\t}\n\tsale_info\t0\n\t{\n\t\tsale_type\tnot\n\t\tsale_price\t10\n\t}\n\torig_asset_id\t8747acbc-d391-1e59-69f1-41d06830e6c0\n\torig_item_id\t20f36c3a-b44b-9bc7-87f3-018bfdfc8cda\n\tfrom_task_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\tcorrect_family_id\t00000000-0000-0000-0000-000000000000\n\thas_rezzed\t0\n\tpre_link_base_mask\t7fffffff\n\tlinked \tlinked\n\tdefault_pay_price\t-2\t1\t5\t10\t20\n}\n");
 	}
 
-	 
+
 	template<> template<>
 	void URITestObject::test<18>()
 	{
 		ensure_equals("pathmap",	u.pathArray()[1].asString(),	"login");
 		ensure_equals("query",		u.query(),		"first_name=Testert4&last_name=Tester&web_login_key=test");
 		ensure_equals("query map element", u.queryMap()["last_name"].asString(), "Tester");
-		
+
 		u = LLURI("secondlife://Da Boom/128/128/128");
 		// if secondlife is the scheme, LLURI should parse /128/128/128 as path, with Da Boom as authority
 		ensure_equals("scheme",		u.scheme(),		"secondlife");
 	template<> template<>
 	void URITestObject::test<19>()
 	{
-		// Parse about: schemes
+		set_test_name("Parse about: schemes");
 		LLURI u("about:blank?redirect-http-hack=secondlife%3A%2F%2F%2Fapp%2Flogin%3Ffirst_name%3DCallum%26last_name%3DLinden%26location%3Dspecify%26grid%3Dvaak%26region%3D%2FMorris%2F128%2F128%26web_login_key%3Defaa4795-c2aa-4c58-8966-763c27931e78");
 		ensure_equals("scheme",		u.scheme(),		"about");
 		ensure_equals("authority",	u.authority(),	"");

indra/llmath/v3color.h

 #include "llerror.h"
 #include "llmath.h"
 #include "llsd.h"
+#include <string.h>
 
 //  LLColor3 = |r g b|
 

indra/llrender/llfontgl.cpp

 }
 
 // static
-void LLFontGL::initClass(F32 screen_dpi, F32 x_scale, F32 y_scale, const std::string& app_dir, const std::vector<std::string>& xui_paths, bool create_gl_textures)
+void LLFontGL::initClass(F32 screen_dpi, F32 x_scale, F32 y_scale, const std::string& app_dir, bool create_gl_textures)
 {
 	sVertDPI = (F32)llfloor(screen_dpi * y_scale);
 	sHorizDPI = (F32)llfloor(screen_dpi * x_scale);
 	// Font registry init
 	if (!sFontRegistry)
 	{
-		sFontRegistry = new LLFontRegistry(xui_paths, create_gl_textures);
+		sFontRegistry = new LLFontRegistry(create_gl_textures);
 		sFontRegistry->parseFontInfo("fonts.xml");
 	}
 	else

indra/llrender/llfontgl.h

 	const LLFontDescriptor& getFontDesc() const;
 
 
-	static void initClass(F32 screen_dpi, F32 x_scale, F32 y_scale, const std::string& app_dir, const std::vector<std::string>& xui_paths, bool create_gl_textures = true);
+	static void initClass(F32 screen_dpi, F32 x_scale, F32 y_scale, const std::string& app_dir, bool create_gl_textures = true);
 
 	// Load sans-serif, sans-serif-small, etc.
 	// Slow, requires multiple seconds to load fonts.

indra/llrender/llfontregistry.cpp

 	return LLFontDescriptor(new_name,new_size,new_style,getFileNames());
 }
 
-LLFontRegistry::LLFontRegistry(const string_vec_t& xui_paths,
-							   bool create_gl_textures)
+LLFontRegistry::LLFontRegistry(bool create_gl_textures)
 :	mCreateGLTextures(create_gl_textures)
 {
-	// Propagate this down from LLUICtrlFactory so LLRender doesn't
-	// need an upstream dependency on LLUI.
-	mXUIPaths = xui_paths;
-	
 	// This is potentially a slow directory traversal, so we want to
 	// cache the result.
 	mUltimateFallbackList = LLWindow::getDynamicFallbackFontList();
 
 bool LLFontRegistry::parseFontInfo(const std::string& xml_filename)
 {
-	bool success = false;  // Succeed if we find at least one XUI file
-	const string_vec_t& xml_paths = mXUIPaths;
+	bool success = false;  // Succeed if we find and read at least one XUI file
+	const string_vec_t xml_paths = gDirUtilp->findSkinnedFilenames(LLDir::XUI, xml_filename);
+	if (xml_paths.empty())
+	{
+		// We didn't even find one single XUI file
+		return false;
+	}
+
 	for (string_vec_t::const_iterator path_it = xml_paths.begin();
 		 path_it != xml_paths.end();
 		 ++path_it)
 	{
-	
 		LLXMLNodePtr root;
-		std::string full_filename = gDirUtilp->findSkinnedFilename(*path_it, xml_filename);
-		bool parsed_file = LLXMLNode::parseFile(full_filename, root, NULL);
+		bool parsed_file = LLXMLNode::parseFile(*path_it, root, NULL);
 
 		if (!parsed_file)
 			continue;
-		
+
 		if ( root.isNull() || ! root->hasName( "fonts" ) )
 		{
-			llwarns << "Bad font info file: "
-					<< full_filename << llendl;
+			llwarns << "Bad font info file: " << *path_it << llendl;
 			continue;
 		}
-		
+
 		std::string root_name;
 		root->getAttributeString("name",root_name);
 		if (root->hasName("fonts"))
 	}
 	//if (success)
 	//	dump();
-	
+
 	return success;
 }
 

indra/llrender/llfontregistry.h

 public:
 	// create_gl_textures - set to false for test apps with no OpenGL window,
 	// such as llui_libtest
-	LLFontRegistry(const string_vec_t& xui_paths,
-		bool create_gl_textures);
+	LLFontRegistry(bool create_gl_textures);
 	~LLFontRegistry();
 
 	// Load standard font info from XML file(s).
 	font_size_map_t mFontSizes;
 
 	string_vec_t mUltimateFallbackList;
-	string_vec_t mXUIPaths;
 	bool mCreateGLTextures;
 };
 

indra/llui/llfloater.cpp

 
 static LLFastTimer::DeclareTimer FTM_BUILD_FLOATERS("Build Floaters");
 
-bool LLFloater::buildFromFile(const std::string& filename, LLXMLNodePtr output_node)
+bool LLFloater::buildFromFile(const std::string& filename)
 {
 	LLFastTimer timer(FTM_BUILD_FLOATERS);
 	LLXMLNodePtr root;
 
-	//if exporting, only load the language being exported, 
-	//instead of layering localized version on top of english
-	if (output_node)
+	if (!LLUICtrlFactory::getLayeredXMLNode(filename, root))
 	{
-		if (!LLUICtrlFactory::getLocalizedXMLNode(filename, root))
-		{
-			llwarns << "Couldn't parse floater from: " << LLUI::getLocalizedSkinPath() + gDirUtilp->getDirDelimiter() + filename << llendl;
-			return false;
-		}
-	}
-	else if (!LLUICtrlFactory::getLayeredXMLNode(filename, root))
-	{
-		llwarns << "Couldn't parse floater from: " << LLUI::getSkinPath() + gDirUtilp->getDirDelimiter() + filename << llendl;
+		llwarns << "Couldn't find (or parse) floater from: " << filename << llendl;
 		return false;
 	}
 	
 		getCommitCallbackRegistrar().pushScope();
 		getEnableCallbackRegistrar().pushScope();
 		
-		res = initFloaterXML(root, getParent(), filename, output_node);
+		res = initFloaterXML(root, getParent(), filename, NULL);
 
 		setXMLFilename(filename);
 		

indra/llui/llfloater.h

 
 	// Don't export top/left for rect, only height/width
 	static void setupParamsForExport(Params& p, LLView* parent);
-	bool buildFromFile(const std::string &filename, LLXMLNodePtr output_node = NULL);
+	bool buildFromFile(const std::string &filename);
 
 	boost::signals2::connection setMinimizeCallback( const commit_signal_t::slot_type& cb );
 	boost::signals2::connection setOpenCallback( const commit_signal_t::slot_type& cb );

indra/llui/llfloaterreg.cpp

 					llwarns << "Failed to build floater type: '" << name << "'." << llendl;
 					return NULL;
 				}
-				bool success = res->buildFromFile(xui_file, NULL);
+				bool success = res->buildFromFile(xui_file);
 				if (!success)
 				{
 					llwarns << "Failed to build floater type: '" << name << "'." << llendl;

indra/llui/llnotifications.cpp

 bool LLNotifications::loadTemplates()
 {
 	llinfos << "Reading notifications template" << llendl;
-	std::vector<std::string> search_paths;
-	
-	std::string skin_relative_path = gDirUtilp->getDirDelimiter() + LLUI::getSkinPath() + gDirUtilp->getDirDelimiter() + "notifications.xml";
-	std::string localized_skin_relative_path = gDirUtilp->getDirDelimiter() + LLUI::getLocalizedSkinPath() + gDirUtilp->getDirDelimiter() + "notifications.xml";
-
-	addPathIfExists(gDirUtilp->getDefaultSkinDir() + skin_relative_path, search_paths);
-	addPathIfExists(gDirUtilp->getDefaultSkinDir() + localized_skin_relative_path, search_paths);
-	addPathIfExists(gDirUtilp->getSkinDir() + skin_relative_path, search_paths);
-	addPathIfExists(gDirUtilp->getSkinDir() + localized_skin_relative_path, search_paths);
-	addPathIfExists(gDirUtilp->getUserSkinDir() + skin_relative_path, search_paths);
-	addPathIfExists(gDirUtilp->getUserSkinDir() + localized_skin_relative_path, search_paths);
+	// Passing findSkinnedFilenames(constraint=LLDir::ALL_SKINS) makes it
+	// output all relevant pathnames instead of just the ones from the most
+	// specific skin.
+	std::vector<std::string> search_paths =
+		gDirUtilp->findSkinnedFilenames(LLDir::XUI, "notifications.xml", LLDir::ALL_SKINS);
 
 	std::string base_filename = search_paths.front();
 	LLXMLNodePtr root;
 	BOOL success  = LLXMLNode::getLayeredXMLNode(root, search_paths);
-	
+
 	if (!success || root.isNull() || !root->hasName( "notifications" ))
 	{
-		llerrs << "Problem reading UI Notifications file: " << base_filename << llendl;
+		llerrs << "Problem reading XML from UI Notifications file: " << base_filename << llendl;
 		return false;
 	}
 
 
 	if(!params.validateBlock())
 	{
-		llerrs << "Problem reading UI Notifications file: " << base_filename << llendl;
+		llerrs << "Problem reading XUI from UI Notifications file: " << base_filename << llendl;
 		return false;
 	}
 
 bool LLNotifications::loadVisibilityRules()
 {
 	const std::string xml_filename = "notification_visibility.xml";
-	std::string full_filename = gDirUtilp->findSkinnedFilename(LLUI::getXUIPaths().front(), xml_filename);
+	// Note that here we're looking for the "en" version, the default
+	// language, rather than the most localized version of this file.
+	std::string full_filename = gDirUtilp->findSkinnedFilenameBaseLang(LLDir::XUI, xml_filename);
 
 	LLNotificationVisibilityRule::Rules params;
 	LLSimpleXUIParser parser;

indra/llui/llpanel.cpp

 //-----------------------------------------------------------------------------
 // buildPanel()
 //-----------------------------------------------------------------------------
-BOOL LLPanel::buildFromFile(const std::string& filename, LLXMLNodePtr output_node, const LLPanel::Params& default_params)
+BOOL LLPanel::buildFromFile(const std::string& filename, const LLPanel::Params& default_params)
 {
 	LLFastTimer timer(FTM_BUILD_PANELS);
 	BOOL didPost = FALSE;
 	LLXMLNodePtr root;
 
-	//if exporting, only load the language being exported, 
-	//instead of layering localized version on top of english
-	if (output_node)
-	{	
-		if (!LLUICtrlFactory::getLocalizedXMLNode(filename, root))
-		{
-			llwarns << "Couldn't parse panel from: " << LLUI::getLocalizedSkinPath() + gDirUtilp->getDirDelimiter() + filename  << llendl;
-			return didPost;
-		}
-	}
-	else if (!LLUICtrlFactory::getLayeredXMLNode(filename, root))
+	if (!LLUICtrlFactory::getLayeredXMLNode(filename, root))
 	{
-		llwarns << "Couldn't parse panel from: " << LLUI::getSkinPath() + gDirUtilp->getDirDelimiter() + filename << llendl;
+		llwarns << "Couldn't parse panel from: " << filename << llendl;
 		return didPost;
 	}
 
 		getCommitCallbackRegistrar().pushScope();
 		getEnableCallbackRegistrar().pushScope();
 		
-		didPost = initPanelXML(root, NULL, output_node, default_params);
+		didPost = initPanelXML(root, NULL, NULL, default_params);
 
 		getCommitCallbackRegistrar().popScope();
 		getEnableCallbackRegistrar().popScope();

indra/llui/llpanel.h

 	LLPanel(const LLPanel::Params& params = getDefaultParams());
 	
 public:
-	BOOL buildFromFile(const std::string &filename, LLXMLNodePtr output_node = NULL, const LLPanel::Params&default_params = getDefaultParams());
+	BOOL buildFromFile(const std::string &filename, const LLPanel::Params& default_params = getDefaultParams());
 
 	static LLPanel* createFactoryPanel(const std::string& name);
 

indra/llui/lltransutil.cpp

 #include "lltrans.h"
 #include "lluictrlfactory.h"
 #include "llxmlnode.h"
-
+#include "lldir.h"
 
 bool LLTransUtil::parseStrings(const std::string& xml_filename, const std::set<std::string>& default_args)
 {
 	LLXMLNodePtr root;
-	BOOL success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root);
+	// Pass LLDir::ALL_SKINS to load a composite of all the individual string
+	// definitions in the default skin and the current skin. This means an
+	// individual skin can provide an xml_filename that overrides only a
+	// subset of the available string definitions; any string definition not
+	// overridden by that skin will be sought in the default skin.
+	bool success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root, LLDir::ALL_SKINS);
 	if (!success)
 	{
-		llerrs << "Couldn't load string table" << llendl;
+		llerrs << "Couldn't load string table " << xml_filename << llendl;
 		return false;
 	}
 
 	
 	if (!success)
 	{
-		llerrs << "Couldn't load string table " << xml_filename << llendl;
+		llerrs << "Couldn't load localization table " << xml_filename << llendl;
 		return false;
 	}
 	

indra/llui/llui.cpp

 	{}
 };
 
-//static
-void LLUI::setupPaths()
-{
-	std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, "paths.xml");
-
-	LLXMLNodePtr root;
-	BOOL success  = LLXMLNode::parseFile(filename, root, NULL);
-	Paths paths;
-
-	if(success)
-	{
-		LLXUIParser parser;
-		parser.readXUI(root, paths, filename);
-	}
-	sXUIPaths.clear();
-	
-	if (success && paths.validateBlock())
-	{
-		LLStringUtil::format_map_t path_args;
-		path_args["[LANGUAGE]"] = LLUI::getLanguage();
-		
-		for (LLInitParam::ParamIterator<Directory>::const_iterator it = paths.directories.begin(), 
-				end_it = paths.directories.end();
-			it != end_it;
-			++it)
-		{
-			std::string path_val_ui;
-			for (LLInitParam::ParamIterator<SubDir>::const_iterator subdir_it = it->subdirs.begin(),
-					subdir_end_it = it->subdirs.end();
-				subdir_it != subdir_end_it;)
-			{
-				path_val_ui += subdir_it->value();
-				if (++subdir_it != subdir_end_it)
-					path_val_ui += gDirUtilp->getDirDelimiter();
-			}
-			LLStringUtil::format(path_val_ui, path_args);
-			if (std::find(sXUIPaths.begin(), sXUIPaths.end(), path_val_ui) == sXUIPaths.end())
-			{
-				sXUIPaths.push_back(path_val_ui);
-			}
-
-		}
-	}
-	else // parsing failed
-	{
-		std::string slash = gDirUtilp->getDirDelimiter();
-		std::string dir = "xui" + slash + "en";
-		llwarns << "XUI::config file unable to open: " << filename << llendl;
-		sXUIPaths.push_back(dir);
-	}
-}
-
 
 //static
 std::string LLUI::locateSkin(const std::string& filename)
 {
-	std::string slash = gDirUtilp->getDirDelimiter();
 	std::string found_file = filename;
-	if (!gDirUtilp->fileExists(found_file))
+	if (gDirUtilp->fileExists(found_file))
 	{
-		found_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename); // Should be CUSTOM_SKINS?
+		return found_file;
 	}
-	if (sSettingGroups["config"] && sSettingGroups["config"]->controlExists("Language"))
+
+	found_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename); // Should be CUSTOM_SKINS?
+	if (gDirUtilp->fileExists(found_file))
 	{
-		if (!gDirUtilp->fileExists(found_file))
-		{
-			std::string localization = getLanguage();
-			std::string local_skin = "xui" + slash + localization + slash + filename;
-			found_file = gDirUtilp->findSkinnedFilename(local_skin);
-		}
+		return found_file;
 	}
-	if (!gDirUtilp->fileExists(found_file))
+
+	found_file = gDirUtilp->findSkinnedFilename(LLDir::XUI, filename);
+	if (! found_file.empty())
 	{
-		std::string local_skin = "xui" + slash + "en" + slash + filename;
-		found_file = gDirUtilp->findSkinnedFilename(local_skin);
+		return found_file;
 	}
-	if (!gDirUtilp->fileExists(found_file))
+
+	found_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, filename);
+	if (gDirUtilp->fileExists(found_file))
 	{
-		found_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, filename);
+		return found_file;
 	}
-	return found_file;
-}	
+	LL_WARNS("LLUI") << "Can't find '" << filename
+					 << "' in user settings, any skin directory or app_settings" << LL_ENDL;
+	return "";
+}
 
 //static
 LLVector2 LLUI::getWindowSize()

indra/llui/llui.h

 	// Return the ISO639 language name ("en", "ko", etc.) for the viewer UI.
 	// http://www.loc.gov/standards/iso639-2/php/code_list.php
 	static std::string getLanguage();
-	
-	static void setupPaths();
-	static const std::vector<std::string>& getXUIPaths() { return sXUIPaths; }
-	static std::string getSkinPath() { return sXUIPaths.front(); }
-	static std::string getLocalizedSkinPath() { return sXUIPaths.back(); }  //all files may not exist at the localized path
 
 	//helper functions (should probably move free standing rendering helper functions here)
 	static LLView* getRootView() { return sRootView; }

indra/llui/lluicolortable.cpp

 #include "llui.h"
 #include "lluicolortable.h"
 #include "lluictrlfactory.h"
+#include <boost/foreach.hpp>
 
 LLUIColorTable::ColorParams::ColorParams()
 :	value("value"),
 {
 	bool result = false;
 
-	std::string default_filename = gDirUtilp->getExpandedFilename(LL_PATH_DEFAULT_SKIN, "colors.xml");
-	result |= loadFromFilename(default_filename, mLoadedColors);
-
-	std::string current_filename = gDirUtilp->getExpandedFilename(LL_PATH_TOP_SKIN, "colors.xml");
-	if(current_filename != default_filename)
+	// pass constraint=LLDir::ALL_SKINS because we want colors.xml from every
+	// skin dir
+	BOOST_FOREACH(std::string colors_path,
+				  gDirUtilp->findSkinnedFilenames(LLDir::SKINBASE, "colors.xml", LLDir::ALL_SKINS))
 	{
-		result |= loadFromFilename(current_filename, mLoadedColors);
-	}
-
-	current_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SKIN, "colors.xml");
-	if(current_filename != default_filename)
-	{
-		result |= loadFromFilename(current_filename, mLoadedColors);
+		result |= loadFromFilename(colors_path, mLoadedColors);
 	}
 
 	std::string user_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "colors.xml");

indra/llui/lluictrlfactory.cpp

 
 void LLUICtrlFactory::loadWidgetTemplate(const std::string& widget_tag, LLInitParam::BaseBlock& block)
 {
-	std::string filename = std::string("widgets") + gDirUtilp->getDirDelimiter() + widget_tag + ".xml";
+	std::string filename = gDirUtilp->add("widgets", widget_tag + ".xml");
 	LLXMLNodePtr root_node;
 
-	std::string full_filename = gDirUtilp->findSkinnedFilename(LLUI::getXUIPaths().front(), filename);
+	// Here we're looking for the "en" version, the default-language version
+	// of the file, rather than the localized version.
+	std::string full_filename = gDirUtilp->findSkinnedFilenameBaseLang(LLDir::XUI, filename);
 	if (!full_filename.empty())
 	{
 		LLUICtrlFactory::instance().pushFileName(full_filename);
 //-----------------------------------------------------------------------------
 // getLayeredXMLNode()
 //-----------------------------------------------------------------------------
-bool LLUICtrlFactory::getLayeredXMLNode(const std::string &xui_filename, LLXMLNodePtr& root)
+bool LLUICtrlFactory::getLayeredXMLNode(const std::string &xui_filename, LLXMLNodePtr& root,
+                                        LLDir::ESkinConstraint constraint)
 {
 	LLFastTimer timer(FTM_XML_PARSE);
-	
-	std::vector<std::string> paths;
-	std::string path = gDirUtilp->findSkinnedFilename(LLUI::getSkinPath(), xui_filename);
-	if (!path.empty())
-	{
-		paths.push_back(path);
-	}
-
-	std::string localize_path = gDirUtilp->findSkinnedFilename(LLUI::getLocalizedSkinPath(), xui_filename);
-	if (!localize_path.empty() && localize_path != path)
-	{
-		paths.push_back(localize_path);
-	}
+	std::vector<std::string> paths =
+		gDirUtilp->findSkinnedFilenames(LLDir::XUI, xui_filename, constraint);
 
 	if (paths.empty())
 	{
 
 
 //-----------------------------------------------------------------------------
-// getLocalizedXMLNode()
-//-----------------------------------------------------------------------------
-bool LLUICtrlFactory::getLocalizedXMLNode(const std::string &xui_filename, LLXMLNodePtr& root)
-{
-	LLFastTimer timer(FTM_XML_PARSE);
-	std::string full_filename = gDirUtilp->findSkinnedFilename(LLUI::getLocalizedSkinPath(), xui_filename);
-	if (!LLXMLNode::parseFile(full_filename, root, NULL))
-	{
-		return false;
-	}
-	else
-	{
-		return true;
-	}
-}
-
-//-----------------------------------------------------------------------------
 // saveToXML()
 //-----------------------------------------------------------------------------
 S32 LLUICtrlFactory::saveToXML(LLView* viewp, const std::string& filename)
 
 
 void LLUICtrlFactory::pushFileName(const std::string& name) 
-{ 
-	mFileNames.push_back(gDirUtilp->findSkinnedFilename(LLUI::getSkinPath(), name)); 
+{
+	// Here we seem to be looking for the default language file ("en") rather
+	// than the localized one, if any.
+	mFileNames.push_back(gDirUtilp->findSkinnedFilenameBaseLang(LLDir::XUI, name));
 }
 
 void LLUICtrlFactory::popFileName() 
 	parent->addChild(view, tab_group);
 }
 
-
-// Avoid directly using LLUI and LLDir in the template code
-//static
-std::string LLUICtrlFactory::findSkinnedFilename(const std::string& filename)
-{
-	return gDirUtilp->findSkinnedFilename(LLUI::getSkinPath(), filename);
-}
-
 //static 
 void LLUICtrlFactory::copyName(LLXMLNodePtr src, LLXMLNodePtr dest)
 {

indra/llui/lluictrlfactory.h

 #include "llregistry.h"
 #include "llxuiparser.h"
 #include "llstl.h"
+#include "lldir.h"
 
 class LLView;
 
 	LLView* createFromXML(LLXMLNodePtr node, LLView* parent, const std::string& filename, const widget_registry_t&, LLXMLNodePtr output_node );
 
 	template<typename T>
-	static T* createFromFile(const std::string &filename, LLView *parent, const widget_registry_t& registry, LLXMLNodePtr output_node = NULL)
+	static T* createFromFile(const std::string &filename, LLView *parent, const widget_registry_t& registry)
 	{
 		T* widget = NULL;
-		
-		std::string skinned_filename = findSkinnedFilename(filename);
+
 		instance().pushFileName(filename);
 		{
 			LLXMLNodePtr root_node;
 
-			//if exporting, only load the language being exported, 			
-			//instead of layering localized version on top of english			
-			if (output_node)			
-			{					
-				if (!LLUICtrlFactory::getLocalizedXMLNode(filename, root_node))				
-				{							
-					llwarns << "Couldn't parse XUI file: " <<  filename  << llendl;					
-					goto fail;				
-				}
-			}
-			else if (!LLUICtrlFactory::getLayeredXMLNode(filename, root_node))
+			if (!LLUICtrlFactory::getLayeredXMLNode(filename, root_node))
 			{
-				llwarns << "Couldn't parse XUI file: " << skinned_filename << llendl;
+				llwarns << "Couldn't parse XUI file: " << instance().getCurFileName() << llendl;
 				goto fail;
 			}
-			
-			LLView* view = getInstance()->createFromXML(root_node, parent, filename, registry, output_node);
+
+			LLView* view = getInstance()->createFromXML(root_node, parent, filename, registry, NULL);
 			if (view)
 			{
 				widget = dynamic_cast<T*>(view);
 
 	static void createChildren(LLView* viewp, LLXMLNodePtr node, const widget_registry_t&, LLXMLNodePtr output_node = NULL);
 
-	static bool getLayeredXMLNode(const std::string &filename, LLXMLNodePtr& root);
-	static bool getLocalizedXMLNode(const std::string &xui_filename, LLXMLNodePtr& root);
+	static bool getLayeredXMLNode(const std::string &filename, LLXMLNodePtr& root,
+								  LLDir::ESkinConstraint constraint=LLDir::CURRENT_SKIN);
 
 private:
 	//NOTE: both friend declarations are necessary to keep both gcc and msvc happy
 	// this exists to get around dependency on llview
 	static void setCtrlParent(LLView* view, LLView* parent, S32 tab_group);
 
-	// Avoid directly using LLUI and LLDir in the template code
-	static std::string findSkinnedFilename(const std::string& filename);
-
 	class LLPanel*		mDummyPanel;
 	std::vector<std::string>	mFileNames;
 };

indra/llvfs/lldir.cpp

 #include "lluuid.h"
 
 #include "lldiriterator.h"
+#include "stringize.h"
+#include <boost/foreach.hpp>
+#include <boost/range/begin.hpp>
+#include <boost/range/end.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/bind.hpp>
+#include <boost/ref.hpp>
+#include <algorithm>
+
+using boost::assign::list_of;
+using boost::assign::map_list_of;
 
 #if LL_WINDOWS
 #include "lldir_win32.h"
 
 LLDir *gDirUtilp = (LLDir *)&gDirUtil;
 
+/// Values for findSkinnedFilenames(subdir) parameter
+const char
+	*LLDir::XUI      = "xui",
+	*LLDir::TEXTURES = "textures",
+	*LLDir::SKINBASE = "";
+
+static const char* const empty = "";
+
 LLDir::LLDir()
 :	mAppName(""),
 	mExecutablePathAndName(""),
 	mOSCacheDir(""),
 	mCAFile(""),
 	mTempDir(""),
-	mDirDelimiter("/") // fallback to forward slash if not overridden
+	mDirDelimiter("/"), // fallback to forward slash if not overridden
+	mLanguage("en")
 {
 }
 
 	LLDirIterator iter(dirname, mask);
 	while (iter.next(filename))
 	{
-		fullpath = dirname;
-		fullpath += getDirDelimiter();
-		fullpath += filename;
+		fullpath = add(dirname, filename);
 
 		if(LLFile::isdir(fullpath))
 		{
 		}
 		else
 		{
-			res = getOSUserAppDir() + mDirDelimiter + "cache";
+			res = add(getOSUserAppDir(), "cache");
 		}
 	}
 	else
 	{
-		res = getOSCacheDir() + mDirDelimiter + "SecondLife";
+		res = add(getOSCacheDir(), "SecondLife");
 	}
 	return res;
 }
 	return mDirDelimiter;
 }
 
+const std::string& LLDir::getDefaultSkinDir() const
+{
+	return mDefaultSkinDir;
+}
+
 const std::string &LLDir::getSkinDir() const
 {
 	return mSkinDir;
 }
 
+const std::string &LLDir::getUserDefaultSkinDir() const
+{
+    return mUserDefaultSkinDir;
+}
+
 const std::string &LLDir::getUserSkinDir() const
 {
 	return mUserSkinDir;
 }
 
-const std::string& LLDir::getDefaultSkinDir() const
-{
-	return mDefaultSkinDir;
-}
-
 const std::string LLDir::getSkinBaseDir() const
 {
 	return mSkinBaseDir;
 	return mLLPluginDir;
 }
 
+static std::string ELLPathToString(ELLPath location)
+{
+	typedef std::map<ELLPath, const char*> ELLPathMap;
+#define ENT(symbol) (symbol, #symbol)
+	static const ELLPathMap sMap = map_list_of
+		ENT(LL_PATH_NONE)
+		ENT(LL_PATH_USER_SETTINGS)
+		ENT(LL_PATH_APP_SETTINGS)
+		ENT(LL_PATH_PER_SL_ACCOUNT) // returns/expands to blank string if we don't know the account name yet
+		ENT(LL_PATH_CACHE)
+		ENT(LL_PATH_CHARACTER)
+		ENT(LL_PATH_HELP)
+		ENT(LL_PATH_LOGS)
+		ENT(LL_PATH_TEMP)
+		ENT(LL_PATH_SKINS)
+		ENT(LL_PATH_TOP_SKIN)
+		ENT(LL_PATH_CHAT_LOGS)
+		ENT(LL_PATH_PER_ACCOUNT_CHAT_LOGS)
+		ENT(LL_PATH_USER_SKIN)
+		ENT(LL_PATH_LOCAL_ASSETS)
+		ENT(LL_PATH_EXECUTABLE)
+		ENT(LL_PATH_DEFAULT_SKIN)
+		ENT(LL_PATH_FONTS)
+		ENT(LL_PATH_LAST)
+	;
+#undef ENT
+
+	ELLPathMap::const_iterator found = sMap.find(location);
+	if (found != sMap.end())
+		return found->second;
+	return STRINGIZE("Invalid ELLPath value " << location);
+}
+
 std::string LLDir::getExpandedFilename(ELLPath location, const std::string& filename) const
 {
 	return getExpandedFilename(location, "", filename);
 		break;
 
 	case LL_PATH_APP_SETTINGS:
-		prefix = getAppRODataDir();
-		prefix += mDirDelimiter;
-		prefix += "app_settings";
+		prefix = add(getAppRODataDir(), "app_settings");
 		break;
 	
 	case LL_PATH_CHARACTER:
-		prefix = getAppRODataDir();
-		prefix += mDirDelimiter;
-		prefix += "character";
+		prefix = add(getAppRODataDir(), "character");
 		break;
 		
 	case LL_PATH_HELP:
 		break;
 		
 	case LL_PATH_USER_SETTINGS:
-		prefix = getOSUserAppDir();
-		prefix += mDirDelimiter;
-		prefix += "user_settings";
+		prefix = add(getOSUserAppDir(), "user_settings");
 		break;
 
 	case LL_PATH_PER_SL_ACCOUNT:
 		prefix = getLindenUserDir();
 		if (prefix.empty())
 		{
-			// if we're asking for the per-SL-account directory but we haven't logged in yet (or otherwise don't know the account name from which to build this string), then intentionally return a blank string to the caller and skip the below warning about a blank prefix.
+			// if we're asking for the per-SL-account directory but we haven't
+			// logged in yet (or otherwise don't know the account name from
+			// which to build this string), then intentionally return a blank
+			// string to the caller and skip the below warning about a blank
+			// prefix.
+			LL_DEBUGS("LLDir") << "getLindenUserDir() not yet set: "
+							   << ELLPathToString(location)
+							   << ", '" << subdir1 << "', '" << subdir2 << "', '" << in_filename
+							   << "' => ''" << LL_ENDL;
 			return std::string();
 		}
 		break;
 		break;
 
 	case LL_PATH_LOGS:
-		prefix = getOSUserAppDir();
-		prefix += mDirDelimiter;
-		prefix += "logs";
+		prefix = add(getOSUserAppDir(), "logs");
 		break;
 
 	case LL_PATH_TEMP:
 		break;
 
 	case LL_PATH_LOCAL_ASSETS:
-		prefix = getAppRODataDir();
-		prefix += mDirDelimiter;
-		prefix += "local_assets";
+		prefix = add(getAppRODataDir(), "local_assets");
 		break;
 
 	case LL_PATH_EXECUTABLE:
 		break;
 		
 	case LL_PATH_FONTS:
-		prefix = getAppRODataDir();
-		prefix += mDirDelimiter;
-		prefix += "fonts";
+		prefix = add(getAppRODataDir(), "fonts");
 		break;
 		
 	default:
 		llassert(0);
 	}
 
-	std::string filename = in_filename;
-	if (!subdir2.empty())
+	if (prefix.empty())
 	{
-		filename = subdir2 + mDirDelimiter + filename;
+		llwarns << ELLPathToString(location)
+				<< ", '" << subdir1 << "', '" << subdir2 << "', '" << in_filename
+				<< "': prefix is empty, possible bad filename" << llendl;
 	}
 
-	if (!subdir1.empty())
+	std::string expanded_filename = add(add(prefix, subdir1), subdir2);
+	if (expanded_filename.empty() && in_filename.empty())
 	{
-		filename = subdir1 + mDirDelimiter + filename;
+		return "";
 	}
+	// Use explicit concatenation here instead of another add() call. Callers
+	// passing in_filename as "" expect to obtain a pathname ending with
+	// mDirSeparator so they can later directly concatenate with a specific
+	// filename. A caller using add() doesn't care, but there's still code
+	// loose in the system that uses std::string::operator+().
+	expanded_filename += mDirDelimiter;
+	expanded_filename += in_filename;
 
-	if (prefix.empty())
-	{
-		llwarns << "prefix is empty, possible bad filename" << llendl;
-	}
-	
-	std::string expanded_filename;
-	if (!filename.empty())
-	{
-		if (!prefix.empty())
-		{
-			expanded_filename += prefix;
-			expanded_filename += mDirDelimiter;
-			expanded_filename += filename;
-		}
-		else
-		{
-			expanded_filename = filename;
-		}
-	}
-	else if (!prefix.empty())
-	{
-		// Directory only, no file name.
-		expanded_filename = prefix;
-	}
-	else
-	{
-		expanded_filename.assign("");
-	}
-
-	//llinfos << "*** EXPANDED FILENAME: <" << expanded_filename << ">" << llendl;
+	LL_DEBUGS("LLDir") << ELLPathToString(location)
+					   << ", '" << subdir1 << "', '" << subdir2 << "', '" << in_filename
+					   << "' => '" << expanded_filename << "'" << LL_ENDL;
 	return expanded_filename;
 }
 
 	return exten;
 }
 
-std::string LLDir::findSkinnedFilename(const std::string &filename) const
+std::string LLDir::findSkinnedFilenameBaseLang(const std::string &subdir,
+											   const std::string &filename,
+											   ESkinConstraint constraint) const
 {
-	return findSkinnedFilename("", "", filename);
+	// This implementation is basically just as described in the declaration comments.
+	std::vector<std::string> found(findSkinnedFilenames(subdir, filename, constraint));
+	if (found.empty())
+	{
+		return "";
+	}
+	return found.front();
 }
 
-std::string LLDir::findSkinnedFilename(const std::string &subdir, const std::string &filename) const
+std::string LLDir::findSkinnedFilename(const std::string &subdir,
+									   const std::string &filename,
+									   ESkinConstraint constraint) const
 {
-	return findSkinnedFilename("", subdir, filename);
+	// This implementation is basically just as described in the declaration comments.
+	std::vector<std::string> found(findSkinnedFilenames(subdir, filename, constraint));
+	if (found.empty())
+	{
+		return "";
+	}
+	return found.back();
 }
 
-std::string LLDir::findSkinnedFilename(const std::string &subdir1, const std::string &subdir2, const std::string &filename) const
+// This method exists because the two code paths for
+// findSkinnedFilenames(ALL_SKINS) and findSkinnedFilenames(CURRENT_SKIN) must
+// generate the list of candidate pathnames in identical ways. The only
+// difference is in the body of the inner loop.
+template <typename FUNCTION>
+void LLDir::walkSearchSkinDirs(const std::string& subdir,
+							   const std::vector<std::string>& subsubdirs,
+							   const std::string& filename,
+							   const FUNCTION& function) const
 {
-	// generate subdirectory path fragment, e.g. "/foo/bar", "/foo", ""
-	std::string subdirs = ((subdir1.empty() ? "" : mDirDelimiter) + subdir1)
-						 + ((subdir2.empty() ? "" : mDirDelimiter) + subdir2);
+	BOOST_FOREACH(std::string skindir, mSearchSkinDirs)
+	{
+		std::string subdir_path(add(skindir, subdir));
+		BOOST_FOREACH(std::string subsubdir, subsubdirs)
+		{
+			std::string full_path(add(add(subdir_path, subsubdir), filename));
+			if (fileExists(full_path))
+			{
+				function(subsubdir, full_path);
+			}
+		}
+	}
+}
 
-	std::vector<std::string> search_paths;
-	
-	search_paths.push_back(getUserSkinDir() + subdirs);		// first look in user skin override
-	search_paths.push_back(getSkinDir() + subdirs);			// then in current skin
-	search_paths.push_back(getDefaultSkinDir() + subdirs);  // then default skin
-	search_paths.push_back(getCacheDir() + subdirs);		// and last in preload directory
+// ridiculous little helper function that should go away when we can use lambda
+inline void push_back(std::vector<std::string>& vector, const std::string& value)
+{
+	vector.push_back(value);
+}
 
-	std::string found_file = findFile(filename, search_paths);
-	return found_file;
+typedef std::map<std::string, std::string> StringMap;
+// ridiculous little helper function that should go away when we can use lambda
+inline void store_in_map(StringMap& map, const std::string& key, const std::string& value)
+{
+	map[key] = value;
+}
+
+std::vector<std::string> LLDir::findSkinnedFilenames(const std::string& subdir,
+													 const std::string& filename,
+													 ESkinConstraint constraint) const
+{
+	// Recognize subdirs that have no localization.
+	static const std::set<std::string> sUnlocalized = list_of
+		("")                        // top-level directory not localized
+		("textures")                // textures not localized
+	;
+
+	LL_DEBUGS("LLDir") << "subdir '" << subdir << "', filename '" << filename
+					   << "', constraint "
+					   << ((constraint == CURRENT_SKIN)? "CURRENT_SKIN" : "ALL_SKINS")
+					   << LL_ENDL;
+
+	// Cache the default language directory for each subdir we've encountered.
+	// A cache entry whose value is the empty string means "not localized,
+	// don't bother checking again."
+	static StringMap sLocalized;
+
+	// Check whether we've already discovered if this subdir is localized.
+	StringMap::const_iterator found = sLocalized.find(subdir);
+	if (found == sLocalized.end())
+	{
+		// We have not yet determined that. Is it one of the subdirs "known"
+		// to be unlocalized?
+		if (sUnlocalized.find(subdir) != sUnlocalized.end())
+		{
+			// This subdir is known to be unlocalized. Remember that.
+			found = sLocalized.insert(StringMap::value_type(subdir, "")).first;
+		}
+		else
+		{
+			// We do not recognize this subdir. Investigate.
+			std::string subdir_path(add(getDefaultSkinDir(), subdir));
+			if (fileExists(add(subdir_path, "en")))
+			{
+				// defaultSkinDir/subdir contains subdir "en". That's our
+				// default language; this subdir is localized.
+				found = sLocalized.insert(StringMap::value_type(subdir, "en")).first;
+			}
+			else if (fileExists(add(subdir_path, "en-us")))
+			{
+				// defaultSkinDir/subdir contains subdir "en-us" but not "en".
+				// Set as default language; this subdir is localized.
+				found = sLocalized.insert(StringMap::value_type(subdir, "en-us")).first;
+			}
+			else
+			{
+				// defaultSkinDir/subdir contains neither "en" nor "en-us".
+				// Assume it's not localized. Remember that assumption.
+				found = sLocalized.insert(StringMap::value_type(subdir, "")).first;
+			}
+		}
+	}
+	// Every code path above should have resulted in 'found' becoming a valid
+	// iterator to an entry in sLocalized.
+	llassert(found != sLocalized.end());
+
+	// Now -- is this subdir localized, or not? The answer determines what
+	// subdirectories we check (under subdir) for the requested filename.
+	std::vector<std::string> subsubdirs;
+	if (found->second.empty())
+	{
+		// subdir is not localized. filename should be located directly within it.
+		subsubdirs.push_back("");
+	}
+	else
+	{
+		// subdir is localized, and found->second is the default language
+		// directory within it. Check both the default language and the
+		// current language -- if it differs from the default, of course.
+		subsubdirs.push_back(found->second);
+		if (mLanguage != found->second)
+		{
+			subsubdirs.push_back(mLanguage);
+		}
+	}
+
+	// Build results vector.
+	std::vector<std::string> results;
+	// The process we use depends on 'constraint'.
+	if (constraint != CURRENT_SKIN) // meaning ALL_SKINS
+	{
+		// ALL_SKINS is simpler: just return every pathname generated by
+		// walkSearchSkinDirs(). Tricky bit: walkSearchSkinDirs() passes its
+		// FUNCTION the subsubdir as well as the full pathname. We just want
+		// the full pathname.
+		walkSearchSkinDirs(subdir, subsubdirs, filename,
+						   boost::bind(push_back, boost::ref(results), _2));
+	}
+	else                            // CURRENT_SKIN
+	{
+		// CURRENT_SKIN turns out to be a bit of a misnomer because we might
+		// still return files from two different skins. In any case, this
+		// value of 'constraint' means we will return at most two paths: one
+		// for the default language, one for the current language (supposing
+		// those differ).
+		// It is important to allow a user to override only the localization
+		// for a particular file, for all viewer installs, without also
+		// overriding the default-language file.
+		// It is important to allow a user to override only the default-
+		// language file, for all viewer installs, without also overriding the
+		// applicable localization of that file.
+		// Therefore, treat the default language and the current language as
+		// two separate cases. For each, capture the most-specialized file
+		// that exists.
+		// Use a map keyed by subsubdir (i.e. language code). This allows us
+		// to handle the case of a single subsubdirs entry with the same logic
+		// that handles two. For every real file path generated by
+		// walkSearchSkinDirs(), update the map entry for its subsubdir.
+		StringMap path_for;
+		walkSearchSkinDirs(subdir, subsubdirs, filename,
+						   boost::bind(store_in_map, boost::ref(path_for), _1, _2));
+		// Now that we have a path for each of the default language and the
+		// current language, copy them -- in proper order -- into results.
+		// Don't drive this by walking the map itself: it matters that we
+		// generate results in the same order as subsubdirs.
+		BOOST_FOREACH(std::string subsubdir, subsubdirs)
+		{
+			StringMap::const_iterator found(path_for.find(subsubdir));
+			if (found != path_for.end())
+			{
+				results.push_back(found->second);
+			}
+		}
+	}
+
+	LL_DEBUGS("LLDir") << empty;
+	const char* comma = "";
+	BOOST_FOREACH(std::string path, results)
+	{
+		LL_CONT << comma << "'" << path << "'";
+		comma = ", ";
+	}
+	LL_CONT << LL_ENDL;
+
+	return results;
 }
 
 std::string LLDir::getTempFilename() const
 	random_uuid.generate();
 	random_uuid.toString(uuid_str);
 
-	std::string temp_filename = getTempDir();
-	temp_filename += mDirDelimiter;
-	temp_filename += uuid_str;
-	temp_filename += ".tmp";
-
-	return temp_filename;
+	return add(getTempDir(), uuid_str + ".tmp");
 }
 
 // static
 		std::string userlower(username);
 		LLStringUtil::toLower(userlower);
 		LLStringUtil::replaceChar(userlower, ' ', '_');
-		mLindenUserDir = getOSUserAppDir();
-		mLindenUserDir += mDirDelimiter;
-		mLindenUserDir += userlower;
+		mLindenUserDir = add(getOSUserAppDir(), userlower);
 	}
 	else
 	{
 		std::string userlower(username);
 		LLStringUtil::toLower(userlower);
 		LLStringUtil::replaceChar(userlower, ' ', '_');
-		mPerAccountChatLogsDir = getChatLogsDir();
-		mPerAccountChatLogsDir += mDirDelimiter;
-		mPerAccountChatLogsDir += userlower;
+		mPerAccountChatLogsDir = add(getChatLogsDir(), userlower);
 	}
 	else
 	{
 	
 }
 
-void LLDir::setSkinFolder(const std::string &skin_folder)
+void LLDir::setSkinFolder(const std::string &skin_folder, const std::string& language)
 {
-	mSkinDir = getSkinBaseDir();
-	mSkinDir += mDirDelimiter;
-	mSkinDir += skin_folder;
+	LL_DEBUGS("LLDir") << "Setting skin '" << skin_folder << "', language '" << language << "'"
+					   << LL_ENDL;
+	mSkinName = skin_folder;
+	mLanguage = language;
 
-	// user modifications to current skin
-	// e.g. c:\documents and settings\users\username\application data\second life\skins\dazzle
-	mUserSkinDir = getOSUserAppDir();
-	mUserSkinDir += mDirDelimiter;
-	mUserSkinDir += "skins";
-	mUserSkinDir += mDirDelimiter;	
-	mUserSkinDir += skin_folder;
+	// This method is called multiple times during viewer initialization. Each
+	// time it's called, reset mSearchSkinDirs.
+	mSearchSkinDirs.clear();
 
 	// base skin which is used as fallback for all skinned files
 	// e.g. c:\program files\secondlife\skins\default
 	mDefaultSkinDir = getSkinBaseDir();
-	mDefaultSkinDir += mDirDelimiter;	
-	mDefaultSkinDir += "default";
+	append(mDefaultSkinDir, "default");
+	// This is always the most general of the search skin directories.
+	addSearchSkinDir(mDefaultSkinDir);
+
+	mSkinDir = getSkinBaseDir();
+	append(mSkinDir, skin_folder);
+	// Next level of generality is a skin installed with the viewer.
+	addSearchSkinDir(mSkinDir);
+
+	// user modifications to skins, current and default
+	// e.g. c:\documents and settings\users\username\application data\second life\skins\dazzle
+	mUserSkinDir = getOSUserAppDir();
+	append(mUserSkinDir, "skins");
+	mUserDefaultSkinDir = mUserSkinDir;
+	append(mUserDefaultSkinDir, "default");
+	append(mUserSkinDir, skin_folder);
+	// Next level of generality is user modifications to default skin...
+	addSearchSkinDir(mUserDefaultSkinDir);
+	// then user-defined skins.
+	addSearchSkinDir(mUserSkinDir);
+}
+
+void LLDir::addSearchSkinDir(const std::string& skindir)
+{
+	if (std::find(mSearchSkinDirs.begin(), mSearchSkinDirs.end(), skindir) == mSearchSkinDirs.end())
+	{
+		LL_DEBUGS("LLDir") << "search skin: '" << skindir << "'" << LL_ENDL;
+		mSearchSkinDirs.push_back(skindir);
+	}
+}
+
+std::string LLDir::getSkinFolder() const
+{
+	return mSkinName;
+}
+
+std::string LLDir::getLanguage() const
+{
+	return mLanguage;
 }
 
 bool LLDir::setCacheDir(const std::string &path)
 	else
 	{
 		LLFile::mkdir(path);
-		std::string tempname = path + mDirDelimiter + "temp";
+		std::string tempname = add(path, "temp");
 		LLFILE* file = LLFile::fopen(tempname,"wt");
 		if (file)
 		{
 	LL_DEBUGS2("AppInit","Directories") << "  SkinDir:               " << getSkinDir() << LL_ENDL;
 }
 
+std::string LLDir::add(const std::string& path, const std::string& name) const
+{
+	std::string destpath(path);
+	append(destpath, name);
+	return destpath;
+}
+
+void LLDir::append(std::string& destpath, const std::string& name) const
+{
+	// Delegate question of whether we need a separator to helper method.
+	SepOff sepoff(needSep(destpath, name));
+	if (sepoff.first)               // do we need a separator?
+	{
+		destpath += mDirDelimiter;
+	}
+	// If destpath ends with a separator, AND name starts with one, skip
+	// name's leading separator.
+	destpath += name.substr(sepoff.second);
+}
+
+LLDir::SepOff LLDir::needSep(const std::string& path, const std::string& name) const
+{
+	if (path.empty() || name.empty())
+	{
+		// If either path or name are empty, we do not need a separator
+		// between them.
+		return SepOff(false, 0);
+	}
+	// Here we know path and name are both non-empty. But if path already ends
+	// with a separator, or if name already starts with a separator, we need
+	// not add one.
+	std::string::size_type seplen(mDirDelimiter.length());
+	bool path_ends_sep(path.substr(path.length() - seplen) == mDirDelimiter);
+	bool name_starts_sep(name.substr(0, seplen) == mDirDelimiter);
+	if ((! path_ends_sep) && (! name_starts_sep))
+	{
+		// If neither path nor name brings a separator to the junction, then
+		// we need one.
+		return SepOff(true, 0);
+	}
+	if (path_ends_sep && name_starts_sep)
+	{
+		// But if BOTH path and name bring a separator, we need not add one.
+		// Moreover, we should actually skip the leading separator of 'name'.
+		return SepOff(false, seplen);
+	}
+	// Here we know that either path_ends_sep or name_starts_sep is true --
+	// but not both. So don't add a separator, and don't skip any characters:
+	// simple concatenation will do the trick.
+	return SepOff(false, 0);
+}
 
 void dir_exists_or_crash(const std::string &dir_name)
 {

indra/llvfs/lldir.h

 	LL_PATH_LAST
 } ELLPath;
 
-
+/// Directory operations
 class LLDir
 {
  public:
 	const std::string &getOSCacheDir() const;		// location of OS-specific cache folder (may be empty string)
 	const std::string &getCAFile() const;			// File containing TLS certificate authorities
 	const std::string &getDirDelimiter() const;	// directory separator for platform (ie. '\' or '/' or ':')
+	const std::string &getDefaultSkinDir() const;	// folder for default skin. e.g. c:\program files\second life\skins\default
 	const std::string &getSkinDir() const;		// User-specified skin folder.
+	const std::string &getUserDefaultSkinDir() const; // dir with user modifications to default skin
 	const std::string &getUserSkinDir() const;		// User-specified skin folder with user modifications. e.g. c:\documents and settings\username\application data\second life\skins\curskin
-	const std::string &getDefaultSkinDir() const;	// folder for default skin. e.g. c:\program files\second life\skins\default
 	const std::string getSkinBaseDir() const;		// folder that contains all installed skins (not user modifications). e.g. c:\program files\second life\skins
 	const std::string &getLLPluginDir() const;		// Directory containing plugins and plugin shell
 
 	std::string getExtension(const std::string& filepath) const; // Excludes '.', e.g getExtension("foo.wav") == "wav"
 
 	// these methods search the various skin paths for the specified file in the following order:
-	// getUserSkinDir(), getSkinDir(), getDefaultSkinDir()
-	std::string findSkinnedFilename(const std::string &filename) const;
-	std::string findSkinnedFilename(const std::string &subdir, const std::string &filename) const;
-	std::string findSkinnedFilename(const std::string &subdir1, const std::string &subdir2, const std::string &filename) const;
+	// getUserSkinDir(), getUserDefaultSkinDir(), getSkinDir(), getDefaultSkinDir()
+	/// param value for findSkinnedFilenames(), explained below
+	enum ESkinConstraint { CURRENT_SKIN, ALL_SKINS };
+	/**
+	 * Given a filename within skin, return an ordered sequence of paths to
+	 * search. Nonexistent files will be filtered out -- which means that the
+	 * vector might be empty.
+	 *
+	 * @param subdir Identify top-level skin subdirectory by passing one of
+	 * LLDir::XUI (file lives under "xui" subtree), LLDir::TEXTURES (file
+	 * lives under "textures" subtree), LLDir::SKINBASE (file lives at top
+	 * level of skin subdirectory).
+	 * @param filename Desired filename within subdir within skin, e.g.
+	 * "panel_login.xml". DO NOT prepend (e.g.) "xui" or the desired language.
+	 * @param constraint Callers perform two different kinds of processing.
+	 * When fetching a XUI file, for instance, the existence of @a filename in
+	 * the specified skin completely supercedes any @a filename in the default
+	 * skin. For that case, leave the default @a constraint=CURRENT_SKIN. The
+	 * returned vector will contain only
+	 * ".../<i>current_skin</i>/xui/en/<i>filename</i>",
+	 * ".../<i>current_skin</i>/xui/<i>current_language</i>/<i>filename</i>".
+	 * But for (e.g.) "strings.xml", we want a given skin to be able to
+	 * override only specific entries from the default skin. Any string not
+	 * defined in the specified skin will be sought in the default skin. For
+	 * that case, pass @a constraint=ALL_SKINS. The returned vector will
+	 * contain at least ".../default/xui/en/strings.xml",
+	 * ".../default/xui/<i>current_language</i>/strings.xml",
+	 * ".../<i>current_skin</i>/xui/en/strings.xml",
+	 * ".../<i>current_skin</i>/xui/<i>current_language</i>/strings.xml".
+	 */
+	std::vector<std::string> findSkinnedFilenames(const std::string& subdir,
+												  const std::string& filename,
+												  ESkinConstraint constraint=CURRENT_SKIN) const;
+	/// Values for findSkinnedFilenames(subdir) parameter
+	static const char *XUI, *TEXTURES, *SKINBASE;
+	/**
+	 * Return the base-language pathname from findSkinnedFilenames(), or
+	 * the empty string if no such file exists. Parameters are identical to
+	 * findSkinnedFilenames(). This is shorthand for capturing the vector
+	 * returned by findSkinnedFilenames(), checking for empty() and then
+	 * returning front().
+	 */
+	std::string findSkinnedFilenameBaseLang(const std::string &subdir,
+											const std::string &filename,
+											ESkinConstraint constraint=CURRENT_SKIN) const;
+	/**
+	 * Return the "most localized" pathname from findSkinnedFilenames(), or
+	 * the empty string if no such file exists. Parameters are identical to
+	 * findSkinnedFilenames(). This is shorthand for capturing the vector
+	 * returned by findSkinnedFilenames(), checking for empty() and then
+	 * returning back().
+	 */
+	std::string findSkinnedFilename(const std::string &subdir,
+									const std::string &filename,
+									ESkinConstraint constraint=CURRENT_SKIN) const;
 
 	// random filename in common temporary directory
 	std::string getTempFilename() const;
 	virtual void setChatLogsDir(const std::string &path);		// Set the chat logs dir to this user's dir
 	virtual void setPerAccountChatLogsDir(const std::string &username);		// Set the per user chat log directory.
 	virtual void setLindenUserDir(const std::string &username);		// Set the linden user dir to this user's dir
-	virtual void setSkinFolder(const std::string &skin_folder);
+	virtual void setSkinFolder(const std::string &skin_folder, const std::string& language);
+	virtual std::string getSkinFolder() const;
+	virtual std::string getLanguage() const;
 	virtual bool setCacheDir(const std::string &path);
 
 	virtual void dumpCurrentDirectories();
-	
+