settings.cpp 19 KB

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