settings.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "util/settings.h"
  23. #include "console/consoleTypes.h"
  24. #include "console/SimXMLDocument.h"
  25. IMPLEMENT_CONOBJECT(Settings);
  26. ConsoleDocClass( Settings,
  27. "@brief Class used for writing out preferences and settings for editors\n\n"
  28. "Not intended for game development, for editors or internal use only.\n\n "
  29. "@internal");
  30. Settings::Settings()
  31. {
  32. mFile = "";
  33. mSearchPos = 0;
  34. }
  35. Settings::~Settings()
  36. {
  37. }
  38. void Settings::initPersistFields()
  39. {
  40. addField("file", TypeStringFilename, Offset(mFile, Settings), "The file path and name to be saved to and loaded from.");
  41. Parent::initPersistFields();
  42. }
  43. void Settings::setDefaultValue(const UTF8 *settingName, const UTF8 *settingValue, const UTF8 *settingType)
  44. {
  45. String baseName;
  46. buildGroupString(baseName, settingName);
  47. String name = baseName + "_default";
  48. StringTableEntry nameEntry = StringTable->insert(name.c_str());
  49. String type = baseName + "_type";
  50. StringTableEntry typeEntry = StringTable->insert(type.c_str());
  51. setModStaticFields(false);
  52. setDataField(nameEntry, NULL, settingValue);
  53. setDataField(typeEntry, NULL, settingType);
  54. setModStaticFields(true);
  55. }
  56. void Settings::setValue(const UTF8 *settingName, const UTF8 *settingValue)
  57. {
  58. String name;
  59. buildGroupString(name, settingName);
  60. StringTableEntry nameEntry = StringTable->insert(name.c_str());
  61. setModStaticFields(false);
  62. setDataField(nameEntry, NULL, settingValue);
  63. setModStaticFields(true);
  64. }
  65. const UTF8 *Settings::value(const UTF8 *settingName, const UTF8 *defaultValue)
  66. {
  67. String name;
  68. buildGroupString(name, settingName);
  69. StringTableEntry nameEntry = StringTable->insert(name.c_str());
  70. name += "_default";
  71. StringTableEntry defaultNameEntry = StringTable->insert(name.c_str());
  72. // we do this setModStaticFields call to make sure our get/set calls
  73. // don't grab a regular field, don't want to stomp anything
  74. setModStaticFields(false);
  75. const UTF8 *value = getDataField(nameEntry, NULL);
  76. const UTF8 *storedDefaultValue = getDataField(defaultNameEntry, NULL);
  77. setModStaticFields(true);
  78. if(dStrcmp(value, "") != 0)
  79. return value;
  80. else if(dStrcmp(storedDefaultValue, "") != 0)
  81. return storedDefaultValue;
  82. else
  83. return defaultValue;
  84. }
  85. void Settings::remove(const UTF8 *settingName, bool includeDefaults)
  86. {
  87. // Fetch Dynamic-Field Dictionary.
  88. SimFieldDictionary* pFieldDictionary = getFieldDictionary();
  89. // Any Field Dictionary?
  90. if ( pFieldDictionary == NULL )
  91. {
  92. // No, so we're done.
  93. return;
  94. }
  95. String name;
  96. buildGroupString(name, settingName);
  97. StringTableEntry nameEntry = StringTable->insert(name.c_str());
  98. StringTableEntry nameEntryDefault = StringTable->insert( String::ToString("%s%s",name.c_str(), "_default") );
  99. // Iterate fields.
  100. for ( SimFieldDictionaryIterator itr(pFieldDictionary); *itr; ++itr )
  101. {
  102. // Fetch Field Entry.
  103. SimFieldDictionary::Entry* fieldEntry = *itr;
  104. // is this a field of our current group
  105. if ( (dStrcmp(nameEntry, "") == 0) ||
  106. dStrcmp( nameEntry, fieldEntry->slotName ) == 0 ||
  107. (includeDefaults && dStrcmp( nameEntryDefault, fieldEntry->slotName ) == 0) )
  108. {
  109. // Yes, so remove it.
  110. pFieldDictionary->setFieldValue( fieldEntry->slotName, "" );
  111. }
  112. }
  113. }
  114. void Settings::buildGroupString(String &name, const UTF8 *settingName)
  115. {
  116. // here we want to loop through the stack and build a "/" seperated string
  117. // representing the entire current group stack that gets pre-pended to the
  118. // setting name passed in
  119. if(mGroupStack.size() > 0)
  120. {
  121. for(S32 i=0; i < mGroupStack.size(); i++)
  122. {
  123. S32 pos = 0;
  124. if(name.size() > 0)
  125. pos = name.size()-1;
  126. // tack on the "/" in front if this isn't the first
  127. if(i == 0)
  128. {
  129. name.insert(pos, mGroupStack[i]);
  130. } else
  131. {
  132. name.insert(pos, "/");
  133. name.insert(pos+1, mGroupStack[i]);
  134. }
  135. }
  136. // tack on a final "/"
  137. name.insert(name.size()-1, "/");
  138. if(dStrlen(settingName) > 0)
  139. name.insert(name.size()-1, settingName);
  140. } else
  141. name = settingName;
  142. }
  143. void Settings::clearAllFields()
  144. {
  145. // Fetch Dynamic-Field Dictionary.
  146. SimFieldDictionary* pFieldDictionary = getFieldDictionary();
  147. // Any Field Dictionary?
  148. if ( pFieldDictionary == NULL )
  149. {
  150. // No, so we're done.
  151. return;
  152. }
  153. // Iterate fields.
  154. for ( SimFieldDictionaryIterator itr(pFieldDictionary); *itr; ++itr )
  155. {
  156. // Fetch Field Entry.
  157. SimFieldDictionary::Entry* fieldEntry = *itr;
  158. // don't remove default field values
  159. if (dStrEndsWith(fieldEntry->slotName, "_default"))
  160. continue;
  161. // remove it.
  162. pFieldDictionary->setFieldValue( fieldEntry->slotName, "" );
  163. }
  164. }
  165. bool Settings::write()
  166. {
  167. // Fetch Dynamic-Field Dictionary.
  168. SimFieldDictionary* pFieldDictionary = getFieldDictionary();
  169. // Any Field Dictionary?
  170. if ( pFieldDictionary == NULL )
  171. {
  172. // No, so we're done.
  173. return false;
  174. }
  175. /*
  176. // Iterate fields.
  177. for ( SimFieldDictionaryIterator itr(pFieldDictionary); *itr; ++itr )
  178. {
  179. // Fetch Field Entry.
  180. SimFieldDictionary::Entry* fieldEntry = *itr;
  181. String check(fieldEntry->slotName);
  182. String::SizeType pos = check.find("_default");
  183. if(pos != String::NPos)
  184. continue;
  185. // let's build our XML doc
  186. document->pushNewElement("dynamicField");
  187. document->setAttribute("name", fieldEntry->slotName);
  188. document->addText(fieldEntry->value);
  189. document->popElement();
  190. }
  191. */
  192. SimXMLDocument *document = new SimXMLDocument();
  193. document->registerObject();
  194. document->addHeader();
  195. document->pushNewElement(getName());
  196. SettingSaveNode *node = new SettingSaveNode();
  197. // Iterate fields.
  198. for ( SimFieldDictionaryIterator itr(pFieldDictionary); *itr; ++itr )
  199. {
  200. // Fetch Field Entry.
  201. SimFieldDictionary::Entry* fieldEntry = *itr;
  202. String check(fieldEntry->slotName);
  203. if(check.find("_default") != String::NPos || check.find("_type") != String::NPos)
  204. continue;
  205. node->addValue(fieldEntry->slotName, fieldEntry->value);
  206. }
  207. node->buildDocument(document, true);
  208. node->clear();
  209. delete node;
  210. bool saved = document->saveFile(mFile.c_str());
  211. document->deleteObject();
  212. if(saved)
  213. return true;
  214. else
  215. return false;
  216. }
  217. bool Settings::read()
  218. {
  219. SimXMLDocument *document = new SimXMLDocument();
  220. document->registerObject();
  221. bool success = true;
  222. if(document->loadFile(mFile.c_str()))
  223. {
  224. clearAllFields();
  225. // set our base element
  226. if(document->pushFirstChildElement(getName()))
  227. {
  228. setModStaticFields(false);
  229. readLayer(document);
  230. setModStaticFields(true);
  231. }
  232. else
  233. success = false;
  234. }
  235. else
  236. success = false;
  237. document->deleteObject();
  238. return success;
  239. }
  240. void Settings::readLayer(SimXMLDocument *document, String groupStack)
  241. {
  242. for(S32 i=0; document->pushChildElement(i); i++)
  243. {
  244. const UTF8 *type = document->elementValue();
  245. const UTF8 *name = document->attribute("name");
  246. const UTF8 *value = document->getText();
  247. if(dStrcmp(type, "Group") == 0)
  248. {
  249. String newStack = groupStack;
  250. if(!groupStack.isEmpty())
  251. newStack += "/";
  252. newStack += name;
  253. readLayer(document, newStack);
  254. } else if(dStrcmp(type, "Setting") == 0)
  255. {
  256. String nameString = groupStack;
  257. if(!groupStack.isEmpty())
  258. nameString += "/";
  259. nameString += name;
  260. setDataField(StringTable->insert(nameString.c_str()), NULL, value);
  261. }
  262. document->popElement();
  263. }
  264. }
  265. void Settings::beginGroup(const UTF8 *groupName, bool fromStart)
  266. {
  267. // check if we want to clear the stack
  268. if(fromStart)
  269. clearGroups();
  270. mGroupStack.push_back(String(groupName));
  271. }
  272. void Settings::endGroup()
  273. {
  274. if(mGroupStack.size() > 0)
  275. mGroupStack.pop_back();
  276. }
  277. void Settings::clearGroups()
  278. {
  279. mGroupStack.clear();
  280. }
  281. const UTF8 *Settings::getCurrentGroups()
  282. {
  283. // we want to return a string with our group setup
  284. String returnString;
  285. for(S32 i=0; i<mGroupStack.size(); i++)
  286. {
  287. S32 pos = returnString.size() - 1;
  288. if(pos < 0)
  289. pos = 0;
  290. if(i == 0)
  291. {
  292. returnString.insert(pos, mGroupStack[i]);
  293. } else
  294. {
  295. returnString.insert(pos, "/");
  296. returnString.insert(pos+1, mGroupStack[i]);
  297. }
  298. }
  299. return StringTable->insert(returnString.c_str());
  300. }
  301. /*
  302. S32 Settings::buildSearchList( const char* pattern, bool deepSearch, bool includeDefaults )
  303. {
  304. mSearchResults.clear();
  305. SimFieldDictionary* fieldDictionary = getFieldDictionary();
  306. // Get the dynamic field count
  307. if ( !fieldDictionary )
  308. return -1;
  309. for (SimFieldDictionaryIterator itr(fieldDictionary); *itr; ++itr)
  310. {
  311. // Fetch Field Entry.
  312. SimFieldDictionary::Entry* fieldEntry = *itr;
  313. // Compare strings, store proper results in vector
  314. String extendedPath = String::ToString(fieldEntry->slotName);
  315. String::SizeType start(0);
  316. String::SizeType slashPos = extendedPath.find('/', 0, String::Right);
  317. String shortPath = extendedPath.substr( start, slashPos );
  318. if( deepSearch )
  319. {
  320. if( shortPath.find( pattern ) != -1 )
  321. {
  322. if( !includeDefaults && extendedPath.find("_default") != -1 )
  323. continue;
  324. String listMember = String::ToString(fieldEntry->value);
  325. listMember.insert(start, " " );
  326. listMember.insert(start, String::ToString(fieldEntry->slotName) );
  327. mSearchResults.push_back( listMember );
  328. }
  329. }
  330. else
  331. {
  332. if( shortPath.compare( pattern ) == 0 )
  333. {
  334. if( !includeDefaults && extendedPath.find("_default") != -1 )
  335. continue;
  336. String listMember = String::ToString(fieldEntry->value);
  337. listMember.insert(start, " " );
  338. listMember.insert(start, String::ToString(fieldEntry->slotName) );
  339. mSearchResults.push_back( listMember );
  340. }
  341. }
  342. }
  343. return mSearchResults.size();
  344. }
  345. */
  346. const char* Settings::findFirstValue( const char* pattern, bool deepSearch, bool includeDefaults )
  347. {
  348. mSearchResults.clear();
  349. SimFieldDictionary* fieldDictionary = getFieldDictionary();
  350. // Get the dynamic field count
  351. if ( !fieldDictionary )
  352. return "";
  353. for (SimFieldDictionaryIterator itr(fieldDictionary); *itr; ++itr)
  354. {
  355. // Fetch Field Entry.
  356. SimFieldDictionary::Entry* fieldEntry = *itr;
  357. // Compare strings, store proper results in vector
  358. String extendedPath = String::ToString(fieldEntry->slotName);
  359. String::SizeType start(0);
  360. String::SizeType slashPos = extendedPath.find('/', 0, String::Right);
  361. String shortPath = extendedPath.substr( start, slashPos );
  362. if( deepSearch )
  363. {
  364. if( shortPath.find( pattern ) != -1 )
  365. {
  366. if( !includeDefaults && extendedPath.find("_default") != -1 )
  367. continue;
  368. String listMember = String::ToString(fieldEntry->slotName);
  369. //listMember.insert(start, " " );
  370. //listMember.insert(start, String::ToString(fieldEntry->slotName) );
  371. mSearchResults.push_back( listMember );
  372. }
  373. }
  374. else
  375. {
  376. if( shortPath.compare( pattern ) == 0 )
  377. {
  378. if( !includeDefaults && extendedPath.find("_default") != -1 )
  379. continue;
  380. String listMember = String::ToString(fieldEntry->slotName);
  381. //listMember.insert(start, " " );
  382. //listMember.insert(start, String::ToString(fieldEntry->slotName) );
  383. mSearchResults.push_back( listMember );
  384. }
  385. }
  386. }
  387. if( mSearchResults.size() < 1 )
  388. {
  389. Con::errorf("findFirstValue() : Pattern not found");
  390. return "";
  391. }
  392. mSearchPos = 0;
  393. return mSearchResults[mSearchPos];
  394. }
  395. const char* Settings::findNextValue()
  396. {
  397. if ( mSearchPos + 1 >= mSearchResults.size() )
  398. return "";
  399. mSearchPos++;
  400. return mSearchResults[mSearchPos];
  401. }
  402. // make sure to replace the strings
  403. ConsoleMethod(Settings, findFirstValue, const char*, 2, 5, "settingObj.findFirstValue();")
  404. {
  405. if( argc == 3 )
  406. return object->findFirstValue( argv[2] );
  407. else if( argc == 4 )
  408. return object->findFirstValue( argv[2], argv[3] );
  409. else if( argc == 5 )
  410. return object->findFirstValue( argv[2], argv[3], argv[4] );
  411. else
  412. return "";
  413. }
  414. ConsoleMethod(Settings, findNextValue, const char*, 2, 2, "settingObj.findNextValue();")
  415. {
  416. return object->findNextValue();
  417. }
  418. /*
  419. ConsoleMethod(Settings, buildSearchList, void, 2, 2, "settingObj.buildSearchList();")
  420. {
  421. object->buildSearchList( "foobar" );
  422. }
  423. */
  424. void SettingSaveNode::addValue(const UTF8 *name, const UTF8 *value)
  425. {
  426. String nameString(name);
  427. S32 groupCount = getGroupCount(nameString);
  428. SettingSaveNode *parentNode = this;
  429. // let's check to make sure all these groups exist already
  430. for(S32 i=0; i<groupCount; i++)
  431. {
  432. String groupName = getGroup(nameString, i);
  433. if(!groupName.isEmpty())
  434. {
  435. bool found = false;
  436. // loop through all of our nodes to find if this one exists,
  437. // if it does we want to use it
  438. for(S32 j=0; j<parentNode->mGroupNodes.size(); j++)
  439. {
  440. SettingSaveNode *node = parentNode->mGroupNodes[j];
  441. if(!node->mIsGroup)
  442. continue;
  443. if(node->mName.compare(groupName) == 0)
  444. {
  445. parentNode = node;
  446. found = true;
  447. break;
  448. }
  449. }
  450. // not found, so we create it
  451. if(!found)
  452. {
  453. SettingSaveNode *node = new SettingSaveNode(groupName, true);
  454. parentNode->mGroupNodes.push_back(node);
  455. parentNode = node;
  456. }
  457. }
  458. }
  459. // now we can properly set our actual value
  460. String settingNameString = getSettingName(name);
  461. String valueString(value);
  462. SettingSaveNode *node = new SettingSaveNode(settingNameString, valueString);
  463. parentNode->mSettingNodes.push_back(node);
  464. }
  465. S32 SettingSaveNode::getGroupCount(const String &name)
  466. {
  467. String::SizeType pos = 0;
  468. S32 count = 0;
  469. // loop through and count our exiting groups
  470. while(pos != String::NPos)
  471. {
  472. pos = name.find("/", pos + 1);
  473. if(pos != String::NPos)
  474. count++;
  475. }
  476. return count;
  477. }
  478. String SettingSaveNode::getGroup(const String &name, S32 num)
  479. {
  480. String::SizeType pos = 0;
  481. String::SizeType lastPos = 0;
  482. S32 count = 0;
  483. while(pos != String::NPos)
  484. {
  485. lastPos = pos;
  486. pos = name.find("/", pos + 1);
  487. if(count == num)
  488. {
  489. String::SizeType startPos = lastPos;
  490. if(count > 0)
  491. startPos++;
  492. if(pos == String::NPos)
  493. return name.substr(startPos, name.length() - (startPos));
  494. else
  495. return name.substr(startPos, pos - startPos);
  496. }
  497. count++;
  498. }
  499. return String("");
  500. }
  501. String SettingSaveNode::getSettingName(const String &name)
  502. {
  503. String::SizeType pos = name.find("/", 0, String::Right);
  504. if(pos == String::NPos)
  505. return String(name);
  506. else
  507. return name.substr(pos+1, name.length() - (pos+1));
  508. }
  509. void SettingSaveNode::clear()
  510. {
  511. for( U32 i = 0, num = mGroupNodes.size(); i < num; ++ i )
  512. delete mGroupNodes[ i ];
  513. for( U32 i = 0, num = mSettingNodes.size(); i < num; ++ i )
  514. delete mSettingNodes[ i ];
  515. mGroupNodes.clear();
  516. mSettingNodes.clear();
  517. }
  518. void SettingSaveNode::buildDocument(SimXMLDocument *document, bool skipWrite)
  519. {
  520. // let's build our XML doc
  521. if(mIsGroup && !skipWrite)
  522. {
  523. document->pushNewElement("Group");
  524. document->setAttribute("name", mName);
  525. }
  526. if(!mIsGroup && !skipWrite)
  527. {
  528. document->pushNewElement("Setting");
  529. document->setAttribute("name", mName);
  530. document->addText(mValue);
  531. } else
  532. {
  533. for(S32 i=0; i<mSettingNodes.size(); i++)
  534. {
  535. SettingSaveNode *node = mSettingNodes[i];
  536. node->buildDocument(document);
  537. }
  538. for(S32 i=0; i<mGroupNodes.size(); i++)
  539. {
  540. SettingSaveNode *node = mGroupNodes[i];
  541. node->buildDocument(document);
  542. }
  543. }
  544. if(!skipWrite)
  545. document->popElement();
  546. }
  547. ConsoleMethod(Settings, setValue, void, 3, 4, "settingObj.setValue(settingName, value);")
  548. {
  549. const char *fieldName = StringTable->insert( argv[2] );
  550. if(argc == 3)
  551. object->setValue( fieldName);
  552. else if(argc == 4)
  553. object->setValue( fieldName, argv[3] );
  554. }
  555. ConsoleMethod(Settings, setDefaultValue, void, 4, 4, "settingObj.setDefaultValue(settingName, value);")
  556. {
  557. const char *fieldName = StringTable->insert( argv[2] );
  558. object->setDefaultValue( fieldName, argv[3] );
  559. }
  560. ConsoleMethod(Settings, value, const char*, 3, 4, "settingObj.value(settingName, defaultValue);")
  561. {
  562. const char *fieldName = StringTable->insert( argv[2] );
  563. if(argc == 3)
  564. return object->value( fieldName );
  565. if(argc == 4)
  566. return object->value( fieldName, argv[3] );
  567. return "";
  568. }
  569. ConsoleMethod(Settings, remove, void, 3, 4, "settingObj.remove(settingName, includeDefaults = false);")
  570. {
  571. // there's a problem with some fields not being removed properly, but works if you run it twice,
  572. // a temporary solution for now is simply to call the remove twice
  573. if(argc == 3)
  574. {
  575. object->remove( argv[2] );
  576. object->remove( argv[2] );
  577. }
  578. else if(argc == 4)
  579. {
  580. object->remove( argv[2], argv[3] );
  581. object->remove( argv[2], argv[3] );
  582. }
  583. }
  584. ConsoleMethod(Settings, write, bool, 2, 2, "%success = settingObj.write();")
  585. {
  586. TORQUE_UNUSED(argc); TORQUE_UNUSED(argv);
  587. return object->write();
  588. }
  589. ConsoleMethod(Settings, read, bool, 2, 2, "%success = settingObj.read();")
  590. {
  591. TORQUE_UNUSED(argc); TORQUE_UNUSED(argv);
  592. return object->read();
  593. }
  594. ConsoleMethod(Settings, beginGroup, void, 3, 4, "settingObj.beginGroup(groupName, fromStart = false);")
  595. {
  596. if(argc == 3)
  597. object->beginGroup( argv[2] );
  598. if(argc == 4)
  599. object->beginGroup( argv[2], dAtob(argv[3]) );
  600. }
  601. ConsoleMethod(Settings, endGroup, void, 2, 2, "settingObj.endGroup();")
  602. {
  603. TORQUE_UNUSED(argc); TORQUE_UNUSED(argv);
  604. object->endGroup();
  605. }
  606. ConsoleMethod(Settings, clearGroups, void, 2, 2, "settingObj.clearGroups();")
  607. {
  608. TORQUE_UNUSED(argc); TORQUE_UNUSED(argv);
  609. object->clearGroups();
  610. }
  611. ConsoleMethod(Settings, getCurrentGroups, const char*, 2, 2, "settingObj.getCurrentGroups();")
  612. {
  613. return object->getCurrentGroups();
  614. }