settings.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  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(dStrcmp(value, "") != 0)
  80. return value;
  81. else if(dStrcmp(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 ( (dStrcmp(nameEntry, "") == 0) ||
  107. dStrcmp( nameEntry, fieldEntry->slotName ) == 0 ||
  108. (includeDefaults && dStrcmp( 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. } else
  132. {
  133. name.insert(pos, "/");
  134. name.insert(pos+1, mGroupStack[i]);
  135. }
  136. }
  137. // tack on a final "/"
  138. name.insert(name.size()-1, "/");
  139. if(dStrlen(settingName) > 0)
  140. name.insert(name.size()-1, settingName);
  141. } else
  142. name = settingName;
  143. }
  144. void Settings::clearAllFields()
  145. {
  146. // Fetch Dynamic-Field Dictionary.
  147. SimFieldDictionary* pFieldDictionary = getFieldDictionary();
  148. // Any Field Dictionary?
  149. if ( pFieldDictionary == NULL )
  150. {
  151. // No, so we're done.
  152. return;
  153. }
  154. // Iterate fields.
  155. for ( SimFieldDictionaryIterator itr(pFieldDictionary); *itr; ++itr )
  156. {
  157. // Fetch Field Entry.
  158. SimFieldDictionary::Entry* fieldEntry = *itr;
  159. // don't remove default field values
  160. if (dStrEndsWith(fieldEntry->slotName, "_default"))
  161. continue;
  162. // remove it.
  163. pFieldDictionary->setFieldValue( fieldEntry->slotName, "" );
  164. }
  165. }
  166. bool Settings::write()
  167. {
  168. // Fetch Dynamic-Field Dictionary.
  169. SimFieldDictionary* pFieldDictionary = getFieldDictionary();
  170. // Any Field Dictionary?
  171. if ( pFieldDictionary == NULL )
  172. {
  173. // No, so we're done.
  174. return false;
  175. }
  176. /*
  177. // Iterate fields.
  178. for ( SimFieldDictionaryIterator itr(pFieldDictionary); *itr; ++itr )
  179. {
  180. // Fetch Field Entry.
  181. SimFieldDictionary::Entry* fieldEntry = *itr;
  182. String check(fieldEntry->slotName);
  183. String::SizeType pos = check.find("_default");
  184. if(pos != String::NPos)
  185. continue;
  186. // let's build our XML doc
  187. document->pushNewElement("dynamicField");
  188. document->setAttribute("name", fieldEntry->slotName);
  189. document->addText(fieldEntry->value);
  190. document->popElement();
  191. }
  192. */
  193. SimXMLDocument *document = new SimXMLDocument();
  194. document->registerObject();
  195. document->addHeader();
  196. document->pushNewElement(getName());
  197. SettingSaveNode *node = new SettingSaveNode();
  198. // Iterate fields.
  199. for ( SimFieldDictionaryIterator itr(pFieldDictionary); *itr; ++itr )
  200. {
  201. // Fetch Field Entry.
  202. SimFieldDictionary::Entry* fieldEntry = *itr;
  203. String check(fieldEntry->slotName);
  204. if(check.find("_default") != String::NPos || check.find("_type") != String::NPos)
  205. continue;
  206. node->addValue(fieldEntry->slotName, fieldEntry->value);
  207. }
  208. node->buildDocument(document, true);
  209. node->clear();
  210. delete node;
  211. bool saved = document->saveFile(mFile.c_str());
  212. document->deleteObject();
  213. if(saved)
  214. return true;
  215. else
  216. return false;
  217. }
  218. bool Settings::read()
  219. {
  220. SimXMLDocument *document = new SimXMLDocument();
  221. document->registerObject();
  222. bool success = true;
  223. if(document->loadFile(mFile.c_str()))
  224. {
  225. clearAllFields();
  226. // set our base element
  227. if(document->pushFirstChildElement(getName()))
  228. {
  229. setModStaticFields(false);
  230. readLayer(document);
  231. setModStaticFields(true);
  232. }
  233. else
  234. success = false;
  235. }
  236. else
  237. success = false;
  238. document->deleteObject();
  239. return success;
  240. }
  241. void Settings::readLayer(SimXMLDocument *document, String groupStack)
  242. {
  243. for(S32 i=0; document->pushChildElement(i); i++)
  244. {
  245. const UTF8 *type = document->elementValue();
  246. const UTF8 *name = document->attribute("name");
  247. const UTF8 *value = document->getText();
  248. if(dStrcmp(type, "Group") == 0)
  249. {
  250. String newStack = groupStack;
  251. if(!groupStack.isEmpty())
  252. newStack += "/";
  253. newStack += name;
  254. readLayer(document, newStack);
  255. } else if(dStrcmp(type, "Setting") == 0)
  256. {
  257. String nameString = groupStack;
  258. if(!groupStack.isEmpty())
  259. nameString += "/";
  260. nameString += name;
  261. setDataField(StringTable->insert(nameString.c_str()), NULL, value);
  262. }
  263. document->popElement();
  264. }
  265. }
  266. void Settings::beginGroup(const UTF8 *groupName, bool fromStart)
  267. {
  268. // check if we want to clear the stack
  269. if(fromStart)
  270. clearGroups();
  271. mGroupStack.push_back(String(groupName));
  272. }
  273. void Settings::endGroup()
  274. {
  275. if(mGroupStack.size() > 0)
  276. mGroupStack.pop_back();
  277. }
  278. void Settings::clearGroups()
  279. {
  280. mGroupStack.clear();
  281. }
  282. const UTF8 *Settings::getCurrentGroups()
  283. {
  284. // we want to return a string with our group setup
  285. String returnString;
  286. for(S32 i=0; i<mGroupStack.size(); i++)
  287. {
  288. S32 pos = returnString.size() - 1;
  289. if(pos < 0)
  290. pos = 0;
  291. if(i == 0)
  292. {
  293. returnString.insert(pos, mGroupStack[i]);
  294. } else
  295. {
  296. returnString.insert(pos, "/");
  297. returnString.insert(pos+1, mGroupStack[i]);
  298. }
  299. }
  300. return StringTable->insert(returnString.c_str());
  301. }
  302. /*
  303. S32 Settings::buildSearchList( const char* pattern, bool deepSearch, bool includeDefaults )
  304. {
  305. mSearchResults.clear();
  306. SimFieldDictionary* fieldDictionary = getFieldDictionary();
  307. // Get the dynamic field count
  308. if ( !fieldDictionary )
  309. return -1;
  310. for (SimFieldDictionaryIterator itr(fieldDictionary); *itr; ++itr)
  311. {
  312. // Fetch Field Entry.
  313. SimFieldDictionary::Entry* fieldEntry = *itr;
  314. // Compare strings, store proper results in vector
  315. String extendedPath = String::ToString(fieldEntry->slotName);
  316. String::SizeType start(0);
  317. String::SizeType slashPos = extendedPath.find('/', 0, String::Right);
  318. String shortPath = extendedPath.substr( start, slashPos );
  319. if( deepSearch )
  320. {
  321. if( shortPath.find( pattern ) != -1 )
  322. {
  323. if( !includeDefaults && extendedPath.find("_default") != -1 )
  324. continue;
  325. String listMember = String::ToString(fieldEntry->value);
  326. listMember.insert(start, " " );
  327. listMember.insert(start, String::ToString(fieldEntry->slotName) );
  328. mSearchResults.push_back( listMember );
  329. }
  330. }
  331. else
  332. {
  333. if( shortPath.compare( pattern ) == 0 )
  334. {
  335. if( !includeDefaults && extendedPath.find("_default") != -1 )
  336. continue;
  337. String listMember = String::ToString(fieldEntry->value);
  338. listMember.insert(start, " " );
  339. listMember.insert(start, String::ToString(fieldEntry->slotName) );
  340. mSearchResults.push_back( listMember );
  341. }
  342. }
  343. }
  344. return mSearchResults.size();
  345. }
  346. */
  347. const char* Settings::findFirstValue( const char* pattern, bool deepSearch, bool includeDefaults )
  348. {
  349. mSearchResults.clear();
  350. SimFieldDictionary* fieldDictionary = getFieldDictionary();
  351. // Get the dynamic field count
  352. if ( !fieldDictionary )
  353. return "";
  354. for (SimFieldDictionaryIterator itr(fieldDictionary); *itr; ++itr)
  355. {
  356. // Fetch Field Entry.
  357. SimFieldDictionary::Entry* fieldEntry = *itr;
  358. // Compare strings, store proper results in vector
  359. String extendedPath = String::ToString(fieldEntry->slotName);
  360. String::SizeType start(0);
  361. String::SizeType slashPos = extendedPath.find('/', 0, String::Right);
  362. String shortPath = extendedPath.substr( start, slashPos );
  363. if( deepSearch )
  364. {
  365. if( shortPath.find( pattern ) != -1 )
  366. {
  367. if( !includeDefaults && extendedPath.find("_default") != -1 )
  368. continue;
  369. String listMember = String::ToString(fieldEntry->slotName);
  370. //listMember.insert(start, " " );
  371. //listMember.insert(start, String::ToString(fieldEntry->slotName) );
  372. mSearchResults.push_back( listMember );
  373. }
  374. }
  375. else
  376. {
  377. if( shortPath.compare( pattern ) == 0 )
  378. {
  379. if( !includeDefaults && extendedPath.find("_default") != -1 )
  380. continue;
  381. String listMember = String::ToString(fieldEntry->slotName);
  382. //listMember.insert(start, " " );
  383. //listMember.insert(start, String::ToString(fieldEntry->slotName) );
  384. mSearchResults.push_back( listMember );
  385. }
  386. }
  387. }
  388. if( mSearchResults.size() < 1 )
  389. {
  390. Con::errorf("findFirstValue() : Pattern not found");
  391. return "";
  392. }
  393. mSearchPos = 0;
  394. return mSearchResults[mSearchPos];
  395. }
  396. const char* Settings::findNextValue()
  397. {
  398. if ( mSearchPos + 1 >= mSearchResults.size() )
  399. return "";
  400. mSearchPos++;
  401. return mSearchResults[mSearchPos];
  402. }
  403. // make sure to replace the strings
  404. DefineConsoleMethod(Settings, findFirstValue, const char*, ( const char* pattern, bool deepSearch, bool includeDefaults ), ("", false, false), "settingObj.findFirstValue();")
  405. {
  406. return object->findFirstValue( pattern, deepSearch, includeDefaults );
  407. }
  408. DefineConsoleMethod(Settings, findNextValue, const char*, (), , "settingObj.findNextValue();")
  409. {
  410. return object->findNextValue();
  411. }
  412. /*
  413. ConsoleMethod(Settings, buildSearchList, void, 2, 2, "settingObj.buildSearchList();")
  414. {
  415. object->buildSearchList( "foobar" );
  416. }
  417. */
  418. void SettingSaveNode::addValue(const UTF8 *name, const UTF8 *value)
  419. {
  420. String nameString(name);
  421. S32 groupCount = getGroupCount(nameString);
  422. SettingSaveNode *parentNode = this;
  423. // let's check to make sure all these groups exist already
  424. for(S32 i=0; i<groupCount; i++)
  425. {
  426. String groupName = getGroup(nameString, i);
  427. if(!groupName.isEmpty())
  428. {
  429. bool found = false;
  430. // loop through all of our nodes to find if this one exists,
  431. // if it does we want to use it
  432. for(S32 j=0; j<parentNode->mGroupNodes.size(); j++)
  433. {
  434. SettingSaveNode *node = parentNode->mGroupNodes[j];
  435. if(!node->mIsGroup)
  436. continue;
  437. if(node->mName.compare(groupName) == 0)
  438. {
  439. parentNode = node;
  440. found = true;
  441. break;
  442. }
  443. }
  444. // not found, so we create it
  445. if(!found)
  446. {
  447. SettingSaveNode *node = new SettingSaveNode(groupName, true);
  448. parentNode->mGroupNodes.push_back(node);
  449. parentNode = node;
  450. }
  451. }
  452. }
  453. // now we can properly set our actual value
  454. String settingNameString = getSettingName(name);
  455. String valueString(value);
  456. SettingSaveNode *node = new SettingSaveNode(settingNameString, valueString);
  457. parentNode->mSettingNodes.push_back(node);
  458. }
  459. S32 SettingSaveNode::getGroupCount(const String &name)
  460. {
  461. String::SizeType pos = 0;
  462. S32 count = 0;
  463. // loop through and count our exiting groups
  464. while(pos != String::NPos)
  465. {
  466. pos = name.find("/", pos + 1);
  467. if(pos != String::NPos)
  468. count++;
  469. }
  470. return count;
  471. }
  472. String SettingSaveNode::getGroup(const String &name, S32 num)
  473. {
  474. String::SizeType pos = 0;
  475. String::SizeType lastPos = 0;
  476. S32 count = 0;
  477. while(pos != String::NPos)
  478. {
  479. lastPos = pos;
  480. pos = name.find("/", pos + 1);
  481. if(count == num)
  482. {
  483. String::SizeType startPos = lastPos;
  484. if(count > 0)
  485. startPos++;
  486. if(pos == String::NPos)
  487. return name.substr(startPos, name.length() - (startPos));
  488. else
  489. return name.substr(startPos, pos - startPos);
  490. }
  491. count++;
  492. }
  493. return String("");
  494. }
  495. String SettingSaveNode::getSettingName(const String &name)
  496. {
  497. String::SizeType pos = name.find("/", 0, String::Right);
  498. if(pos == String::NPos)
  499. return String(name);
  500. else
  501. return name.substr(pos+1, name.length() - (pos+1));
  502. }
  503. void SettingSaveNode::clear()
  504. {
  505. for( U32 i = 0, num = mGroupNodes.size(); i < num; ++ i )
  506. delete mGroupNodes[ i ];
  507. for( U32 i = 0, num = mSettingNodes.size(); i < num; ++ i )
  508. delete mSettingNodes[ i ];
  509. mGroupNodes.clear();
  510. mSettingNodes.clear();
  511. }
  512. void SettingSaveNode::buildDocument(SimXMLDocument *document, bool skipWrite)
  513. {
  514. // let's build our XML doc
  515. if(mIsGroup && !skipWrite)
  516. {
  517. document->pushNewElement("Group");
  518. document->setAttribute("name", mName);
  519. }
  520. if(!mIsGroup && !skipWrite)
  521. {
  522. document->pushNewElement("Setting");
  523. document->setAttribute("name", mName);
  524. document->addText(mValue);
  525. } else
  526. {
  527. for(S32 i=0; i<mSettingNodes.size(); i++)
  528. {
  529. SettingSaveNode *node = mSettingNodes[i];
  530. node->buildDocument(document);
  531. }
  532. for(S32 i=0; i<mGroupNodes.size(); i++)
  533. {
  534. SettingSaveNode *node = mGroupNodes[i];
  535. node->buildDocument(document);
  536. }
  537. }
  538. if(!skipWrite)
  539. document->popElement();
  540. }
  541. DefineConsoleMethod(Settings, setValue, void, (const char * settingName, const char * value), (""), "settingObj.setValue(settingName, value);")
  542. {
  543. StringTableEntry fieldName = StringTable->insert( settingName );
  544. if (!String::isEmpty(value))
  545. object->setValue( fieldName, value );
  546. else
  547. object->setValue( fieldName );
  548. }
  549. DefineConsoleMethod(Settings, setDefaultValue, void, (const char * settingName, const char * value), , "settingObj.setDefaultValue(settingName, value);")
  550. {
  551. StringTableEntry fieldName = StringTable->insert( settingName );
  552. object->setDefaultValue( fieldName, value );
  553. }
  554. DefineConsoleMethod(Settings, value, const char*, (const char * settingName, const char * defaultValue), (""), "settingObj.value(settingName, defaultValue);")
  555. {
  556. StringTableEntry fieldName = StringTable->insert( settingName );
  557. if (dStrcmp(defaultValue, "") != 0)
  558. return object->value( fieldName, defaultValue );
  559. else if (dStrcmp(settingName, "") != 0)
  560. return object->value( fieldName );
  561. return "";
  562. }
  563. DefineConsoleMethod(Settings, remove, void, (const char * settingName, bool includeDefaults), (false), "settingObj.remove(settingName, includeDefaults = false);")
  564. {
  565. // there's a problem with some fields not being removed properly, but works if you run it twice,
  566. // a temporary solution for now is simply to call the remove twice
  567. object->remove( settingName, includeDefaults );
  568. object->remove( settingName, includeDefaults );
  569. }
  570. ConsoleMethod(Settings, write, bool, 2, 2, "%success = settingObj.write();")
  571. {
  572. TORQUE_UNUSED(argc); TORQUE_UNUSED(argv);
  573. return object->write();
  574. }
  575. DefineConsoleMethod(Settings, read, bool, (), , "%success = settingObj.read();")
  576. {
  577. return object->read();
  578. }
  579. DefineConsoleMethod(Settings, beginGroup, void, (const char * groupName, bool includeDefaults), (false), "settingObj.beginGroup(groupName, fromStart = false);")
  580. {
  581. object->beginGroup( groupName, includeDefaults );
  582. }
  583. DefineConsoleMethod(Settings, endGroup, void, (), , "settingObj.endGroup();")
  584. {
  585. object->endGroup();
  586. }
  587. DefineConsoleMethod(Settings, clearGroups, void, (), , "settingObj.clearGroups();")
  588. {
  589. object->clearGroups();
  590. }
  591. DefineConsoleMethod(Settings, getCurrentGroups, const char*, (), , "settingObj.getCurrentGroups();")
  592. {
  593. return object->getCurrentGroups();
  594. }