| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726 | //-----------------------------------------------------------------------------// Copyright (c) 2012 GarageGames, LLC//// Permission is hereby granted, free of charge, to any person obtaining a copy// of this software and associated documentation files (the "Software"), to// deal in the Software without restriction, including without limitation the// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or// sell copies of the Software, and to permit persons to whom the Software is// furnished to do so, subject to the following conditions://// The above copyright notice and this permission notice shall be included in// all copies or substantial portions of the Software.//// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS// IN THE SOFTWARE.//-----------------------------------------------------------------------------#include "util/settings.h"#include "console/engineAPI.h"#include "console/consoleTypes.h"#include "console/SimXMLDocument.h"IMPLEMENT_CONOBJECT(Settings);ConsoleDocClass( Settings,				"@brief Class used for writing out preferences and settings for editors\n\n"				"Not intended for game development, for editors or internal use only.\n\n "				"@internal");Settings::Settings(){   mFile = "";	mSearchPos = 0;}Settings::~Settings(){   }void Settings::initPersistFields(){   addField("file", TypeStringFilename, Offset(mFile, Settings), "The file path and name to be saved to and loaded from.");   Parent::initPersistFields();}void Settings::setDefaultValue(const UTF8 *settingName, const UTF8 *settingValue, const UTF8 *settingType){   String baseName;   buildGroupString(baseName, settingName);   String name = baseName + "_default";   StringTableEntry nameEntry = StringTable->insert(name.c_str());   String type = baseName + "_type";   StringTableEntry typeEntry = StringTable->insert(type.c_str());   setModStaticFields(false);   setDataField(nameEntry, NULL, settingValue);   setDataField(typeEntry, NULL, settingType);   setModStaticFields(true);}void Settings::setValue(const UTF8 *settingName, const UTF8 *settingValue){   String name;   buildGroupString(name, settingName);   StringTableEntry nameEntry = StringTable->insert(name.c_str());   setModStaticFields(false);   setDataField(nameEntry, NULL, settingValue);   setModStaticFields(true);}const UTF8 *Settings::value(const UTF8 *settingName, const UTF8 *defaultValue){   String name;   buildGroupString(name, settingName);   StringTableEntry nameEntry = StringTable->insert(name.c_str());   name += "_default";   StringTableEntry defaultNameEntry = StringTable->insert(name.c_str());   // we do this setModStaticFields call to make sure our get/set calls   // don't grab a regular field, don't want to stomp anything   setModStaticFields(false);     const UTF8 *value = getDataField(nameEntry, NULL);   const UTF8 *storedDefaultValue = getDataField(defaultNameEntry, NULL);   setModStaticFields(true);   if(String::compare(value, "") != 0)      return value;   else if(String::compare(storedDefaultValue, "") != 0)      return storedDefaultValue;   else	  return defaultValue;}void Settings::remove(const UTF8 *settingName, bool includeDefaults){    // Fetch Dynamic-Field Dictionary.    SimFieldDictionary* pFieldDictionary = getFieldDictionary();    // Any Field Dictionary?    if ( pFieldDictionary == NULL )    {        // No, so we're done.        return;    }	 String name;    buildGroupString(name, settingName);    StringTableEntry nameEntry = StringTable->insert(name.c_str());	 StringTableEntry nameEntryDefault = StringTable->insert( String::ToString("%s%s",name.c_str(), "_default") );    // Iterate fields.    for ( SimFieldDictionaryIterator itr(pFieldDictionary); *itr; ++itr )    {        // Fetch Field Entry.        SimFieldDictionary::Entry* fieldEntry = *itr;        // is this a field of our current group        if ( (String::compare(nameEntry, "") == 0) || 					String::compare( nameEntry, fieldEntry->slotName ) == 0 ||					(includeDefaults && String::compare( nameEntryDefault, fieldEntry->slotName ) == 0) )        {            // Yes, so remove it.            pFieldDictionary->setFieldValue( fieldEntry->slotName, "" );        }    }}void Settings::buildGroupString(String &name, const UTF8 *settingName){   // here we want to loop through the stack and build a "/" seperated string   // representing the entire current group stack that gets pre-pended to the   // setting name passed in   if(mGroupStack.size() > 0)   {      for(S32 i=0; i < mGroupStack.size(); i++)      {         S32 pos = 0;         if(name.size() > 0)            pos = name.size()-1;         // tack on the "/" in front if this isn't the first         if(i == 0)         {            name.insert(pos, mGroupStack[i]);         }         else         {            name.insert(pos, "/");            name.insert(pos+1, mGroupStack[i]);         }      }	  // tack on a final "/"	  name.insert(name.size()-1, "/");	  if(dStrlen(settingName) > 0)	     name.insert(name.size()-1, settingName);   }   else   {      name = settingName;   }}void Settings::clearAllFields(){    // Fetch Dynamic-Field Dictionary.    SimFieldDictionary* pFieldDictionary = getFieldDictionary();    // Any Field Dictionary?    if ( pFieldDictionary == NULL )    {        // No, so we're done.        return;    }    // Iterate fields.    for ( SimFieldDictionaryIterator itr(pFieldDictionary); *itr; ++itr )    {        // Fetch Field Entry.        SimFieldDictionary::Entry* fieldEntry = *itr;        // don't remove default field values        if (dStrEndsWith(fieldEntry->slotName, "_default"))           continue;        // remove it.        pFieldDictionary->setFieldValue( fieldEntry->slotName, "" );    }}bool Settings::write(){   // Fetch Dynamic-Field Dictionary.   SimFieldDictionary* pFieldDictionary = getFieldDictionary();   // Any Field Dictionary?   if ( pFieldDictionary == NULL )   {      // No, so we're done.      return false;   }/*   // Iterate fields.   for ( SimFieldDictionaryIterator itr(pFieldDictionary); *itr; ++itr )   {      // Fetch Field Entry.      SimFieldDictionary::Entry* fieldEntry = *itr;	  String check(fieldEntry->slotName);	  String::SizeType pos = check.find("_default");	  if(pos != String::NPos)		 continue;      // let's build our XML doc      document->pushNewElement("dynamicField");	  document->setAttribute("name", fieldEntry->slotName);	  document->addText(fieldEntry->value);      document->popElement();   }*/   SimXMLDocument *document = new SimXMLDocument();   document->registerObject();   document->addHeader();   document->pushNewElement(getName());   SettingSaveNode *node = new SettingSaveNode();   // Iterate fields.   for ( SimFieldDictionaryIterator itr(pFieldDictionary); *itr; ++itr )   {      // Fetch Field Entry.      SimFieldDictionary::Entry* fieldEntry = *itr;      String check(fieldEntry->slotName);	   if(check.find("_default") != String::NPos || check.find("_type") != String::NPos)		  continue;	  node->addValue(fieldEntry->slotName, fieldEntry->value);   }   node->buildDocument(document, true);   node->clear();   delete node;   bool saved = document->saveFile(mFile);   document->deleteObject();   if(saved)      return true;   else	   return false;   }bool Settings::read(){   SimXMLDocument *document = new SimXMLDocument();   document->registerObject();   bool success = true;   if(document->loadFile(mFile))   {      clearAllFields();      // set our base element      if(document->pushFirstChildElement(getName()))      {         setModStaticFields(false);         readLayer(document);         setModStaticFields(true);      }      else         success = false;   }   else      success = false;   document->deleteObject();   return success;}void Settings::readLayer(SimXMLDocument *document, String groupStack){   for(S32 i=0; document->pushChildElement(i); i++)   {	  const UTF8 *type = document->elementValue();	  const UTF8 *name = document->attribute("name");	  const UTF8 *value = document->getText();	  	  if(String::compare(type, "Group") == 0)	  {		 String newStack = groupStack;		 if(!groupStack.isEmpty())			newStack += "/";		 newStack += name;         readLayer(document, newStack);	  } else if(String::compare(type, "Setting") == 0)	  {		 String nameString = groupStack;         		 if(!groupStack.isEmpty())		    nameString += "/";         nameString += name;         setDataField(StringTable->insert(nameString.c_str()), NULL, value);	  }	  	  document->popElement();   }}void Settings::beginGroup(const UTF8 *groupName, bool fromStart){   // check if we want to clear the stack   if(fromStart)      clearGroups();   mGroupStack.push_back(String(groupName));}void Settings::endGroup(){   if(mGroupStack.size() > 0)      mGroupStack.pop_back();}void Settings::clearGroups(){   mGroupStack.clear();}const UTF8 *Settings::getCurrentGroups(){   // we want to return a string with our group setup   String returnString;   for(S32 i=0; i<mGroupStack.size(); i++)   {	  S32 pos = returnString.size() - 1;	  if(pos < 0)		 pos = 0;	  if(i == 0)	  {         returnString.insert(pos, mGroupStack[i]);	  } else	  {		 returnString.insert(pos, "/");         returnString.insert(pos+1, mGroupStack[i]);	  }   }   return StringTable->insert(returnString.c_str());}/*S32 Settings::buildSearchList( const char* pattern, bool deepSearch, bool includeDefaults ){	mSearchResults.clear();	SimFieldDictionary* fieldDictionary = getFieldDictionary();	// Get the dynamic field count	if ( !fieldDictionary )		return -1;   for (SimFieldDictionaryIterator itr(fieldDictionary); *itr; ++itr)	{		// Fetch Field Entry.      SimFieldDictionary::Entry* fieldEntry = *itr;				// Compare strings, store proper results in vector		String extendedPath = String::ToString(fieldEntry->slotName);		String::SizeType start(0);		String::SizeType slashPos = extendedPath.find('/', 0, String::Right);		String shortPath = extendedPath.substr( start, slashPos );		if( deepSearch )		{			if( shortPath.find( pattern ) != -1 )			{				if( !includeDefaults && extendedPath.find("_default") != -1 )					continue;				String listMember = String::ToString(fieldEntry->value);				listMember.insert(start, " " );				listMember.insert(start, String::ToString(fieldEntry->slotName) );				mSearchResults.push_back( listMember );			}		}		else		{			if( shortPath.compare( pattern ) == 0 )			{				if( !includeDefaults && extendedPath.find("_default") != -1 )					continue;				String listMember = String::ToString(fieldEntry->value);				listMember.insert(start, " " );				listMember.insert(start, String::ToString(fieldEntry->slotName) );				mSearchResults.push_back( listMember );			}		}	}	return mSearchResults.size();}*/const char* Settings::findFirstValue( const char* pattern, bool deepSearch, bool includeDefaults ){	mSearchResults.clear();	SimFieldDictionary* fieldDictionary = getFieldDictionary();	// Get the dynamic field count	if ( !fieldDictionary )		return "";   for (SimFieldDictionaryIterator itr(fieldDictionary); *itr; ++itr)	{		// Fetch Field Entry.      SimFieldDictionary::Entry* fieldEntry = *itr;				// Compare strings, store proper results in vector		String extendedPath = String::ToString(fieldEntry->slotName);		String::SizeType start(0);		String::SizeType slashPos = extendedPath.find('/', 0, String::Right);		String shortPath = extendedPath.substr( start, slashPos );		if( deepSearch )		{			if( shortPath.find( pattern ) != -1 )			{				if( !includeDefaults && extendedPath.find("_default") != -1 )					continue;				String listMember = String::ToString(fieldEntry->slotName);				//listMember.insert(start, " " );				//listMember.insert(start, String::ToString(fieldEntry->slotName) );				mSearchResults.push_back( listMember );			}		}		else		{			if( shortPath.compare( pattern ) == 0 )			{				if( !includeDefaults && extendedPath.find("_default") != -1 )					continue;				String listMember = String::ToString(fieldEntry->slotName);				//listMember.insert(start, " " );				//listMember.insert(start, String::ToString(fieldEntry->slotName) );				mSearchResults.push_back( listMember );			}		}	}		if( mSearchResults.size() < 1 )   {		Con::errorf("findFirstValue() : Pattern not found");      return "";   }	mSearchPos = 0;	return mSearchResults[mSearchPos];}const char* Settings::findNextValue(){	if ( mSearchPos + 1 >= mSearchResults.size() )      return "";	mSearchPos++;	return mSearchResults[mSearchPos];}// make sure to replace the stringsDefineEngineMethod(Settings, findFirstValue, const char*, ( const char* pattern, bool deepSearch, bool includeDefaults ), ("", false, false), "settingObj.findFirstValue();"){   return object->findFirstValue( pattern, deepSearch, includeDefaults );}DefineEngineMethod(Settings, findNextValue, const char*, (), , "settingObj.findNextValue();"){	return object->findNextValue();}/*ConsoleMethod(Settings, buildSearchList, void, 2, 2, "settingObj.buildSearchList();"){	object->buildSearchList( "foobar" );}*/void SettingSaveNode::addValue(const UTF8 *name, const UTF8 *value){   String nameString(name);   S32 groupCount = getGroupCount(nameString);   SettingSaveNode *parentNode = this;   // let's check to make sure all these groups exist already   for(S32 i=0; i<groupCount; i++)   {      String groupName = getGroup(nameString, i);	  if(!groupName.isEmpty())	  {		 bool found = false;         // loop through all of our nodes to find if this one exists,		 // if it does we want to use it         for(S32 j=0; j<parentNode->mGroupNodes.size(); j++)		 {            SettingSaveNode *node = parentNode->mGroupNodes[j];            if(!node->mIsGroup)			   continue;            if(node->mName.compare(groupName) == 0)			{			   parentNode = node;			   found = true;               break;			}		 }         		 // not found, so we create it		 if(!found)		 {   			SettingSaveNode *node = new SettingSaveNode(groupName, true);            parentNode->mGroupNodes.push_back(node);		    parentNode = node;		 }	  }   }   // now we can properly set our actual value   String settingNameString = getSettingName(name);   String valueString(value);   SettingSaveNode *node = new SettingSaveNode(settingNameString, valueString);   parentNode->mSettingNodes.push_back(node);}S32 SettingSaveNode::getGroupCount(const String &name){   String::SizeType pos = 0;   S32 count = 0;   // loop through and count our exiting groups   while(pos != String::NPos)   {	  pos = name.find("/", pos + 1);	  if(pos != String::NPos)	     count++;   }   return count;}String SettingSaveNode::getGroup(const String &name, S32 num){   String::SizeType pos = 0;   String::SizeType lastPos = 0;   S32 count = 0;   while(pos != String::NPos)   {	  lastPos = pos;	  pos = name.find("/", pos + 1);      if(count == num)	  {		 String::SizeType startPos = lastPos;         if(count > 0)			startPos++;		 if(pos == String::NPos)		    return name.substr(startPos, name.length() - (startPos));		 else            return name.substr(startPos, pos - startPos);	  }	  count++;   }      return String("");}String SettingSaveNode::getSettingName(const String &name){   String::SizeType pos = name.find("/", 0, String::Right);   if(pos == String::NPos)	  return String(name);   else	  return name.substr(pos+1, name.length() - (pos+1));}void SettingSaveNode::clear(){   for( U32 i = 0, num = mGroupNodes.size(); i < num; ++ i )      delete mGroupNodes[ i ];   for( U32 i = 0, num = mSettingNodes.size(); i < num; ++ i )      delete mSettingNodes[ i ];   mGroupNodes.clear();   mSettingNodes.clear();}void SettingSaveNode::buildDocument(SimXMLDocument *document, bool skipWrite){   // let's build our XML doc   if(mIsGroup && !skipWrite)   {      document->pushNewElement("Group");      document->setAttribute("name", mName);   }   if(!mIsGroup && !skipWrite)   {      document->pushNewElement("Setting");      document->setAttribute("name", mName);      document->addText(mValue);   }   else   {      mSettingNodes.sort(_NodeCompare);      mGroupNodes.sort(_NodeCompare);	  for(S32 i=0; i<mSettingNodes.size(); i++)	  {         SettingSaveNode *node = mSettingNodes[i];		 node->buildDocument(document);	  }      for(S32 i=0; i<mGroupNodes.size(); i++)	  {         SettingSaveNode *node = mGroupNodes[i];		 node->buildDocument(document);	  }   }      if(!skipWrite)      document->popElement();}S32 QSORT_CALLBACK SettingSaveNode::_NodeCompare(SettingSaveNode* const* a, SettingSaveNode* const* b){   S32 result = dStrnatcasecmp((*a)->mName.c_str(), (*b)->mName.c_str());   return result;}DefineEngineMethod(Settings, setValue, void, (const char * settingName, const char * value), (""), "settingObj.setValue(settingName, value);"){   StringTableEntry fieldName = StringTable->insert( settingName );      if (!String::isEmpty(value))      object->setValue( fieldName, value );   else      object->setValue( fieldName );}DefineEngineMethod(Settings, setDefaultValue, void, (const char * settingName, const char * value), , "settingObj.setDefaultValue(settingName, value);"){   StringTableEntry fieldName = StringTable->insert( settingName );   object->setDefaultValue( fieldName, value );}DefineEngineMethod(Settings, value, const char*, (const char * settingName, const char * defaultValue), (""), "settingObj.value(settingName, defaultValue);"){   StringTableEntry fieldName = StringTable->insert( settingName );      if (String::compare(defaultValue, "") != 0)      return object->value( fieldName, defaultValue );   else if (String::compare(settingName, "") != 0)      return object->value( fieldName );   return "";}DefineEngineMethod(Settings, remove, void, (const char * settingName, bool includeDefaults), (false), "settingObj.remove(settingName, includeDefaults = false);"){   // there's a problem with some fields not being removed properly, but works if you run it twice,   // a temporary solution for now is simply to call the remove twice	object->remove( settingName, includeDefaults );	object->remove( settingName, includeDefaults );}DefineEngineMethod(Settings, write, bool, (),, "%success = settingObj.write();"){   return object->write();}DefineEngineMethod(Settings, read, bool, (), , "%success = settingObj.read();"){   return object->read();}DefineEngineMethod(Settings, beginGroup, void, (const char * groupName, bool includeDefaults), (false), "settingObj.beginGroup(groupName, fromStart = false);"){	object->beginGroup( groupName, includeDefaults );}DefineEngineMethod(Settings, endGroup, void, (), , "settingObj.endGroup();"){   object->endGroup();}DefineEngineMethod(Settings, clearGroups, void, (), , "settingObj.clearGroups();"){   object->clearGroups();}DefineEngineMethod(Settings, getCurrentGroups, const char*, (), , "settingObj.getCurrentGroups();"){   return object->getCurrentGroups();}
 |