guiInspector.cc 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2013 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 "gui/editor/guiInspector.h"
  23. #include "gui/buttons/guiIconButtonCtrl.h"
  24. #include "memory/frameAllocator.h"
  25. //////////////////////////////////////////////////////////////////////////
  26. // GuiInspector
  27. //////////////////////////////////////////////////////////////////////////
  28. // The GuiInspector Control houses the body of the inspector.
  29. // It is not exposed as a conobject because it merely does the grunt work
  30. // and is only meant to be used when housed by a scroll control. Therefore
  31. // the GuiInspector control is a scroll control that creates it's own
  32. // content. That content being of course, the GuiInspector control.
  33. IMPLEMENT_CONOBJECT(GuiInspector);
  34. GuiInspector::GuiInspector()
  35. {
  36. mGroups.clear();
  37. mTarget = NULL;
  38. mPadding = 1;
  39. }
  40. GuiInspector::~GuiInspector()
  41. {
  42. clearGroups();
  43. }
  44. bool GuiInspector::onAdd()
  45. {
  46. if( !Parent::onAdd() )
  47. return false;
  48. return true;
  49. }
  50. //////////////////////////////////////////////////////////////////////////
  51. // Handle Parent Sizing (We constrain ourself to our parents width)
  52. //////////////////////////////////////////////////////////////////////////
  53. void GuiInspector::parentResized(const Point2I &oldParentExtent, const Point2I &newParentExtent)
  54. {
  55. GuiControl *parent = getParent();
  56. if( parent && dynamic_cast<GuiScrollCtrl*>(parent) != NULL )
  57. {
  58. GuiScrollCtrl *scroll = dynamic_cast<GuiScrollCtrl*>(parent);
  59. setWidth( ( newParentExtent.x - ( scroll->scrollBarThickness() + 4 ) ) );
  60. }
  61. else
  62. Parent::parentResized(oldParentExtent,newParentExtent);
  63. }
  64. bool GuiInspector::findExistentGroup( StringTableEntry groupName )
  65. {
  66. // If we have no groups, it couldn't possibly exist
  67. if( mGroups.empty() )
  68. return false;
  69. // Attempt to find it in the group list
  70. Vector<GuiInspectorGroup*>::iterator i = mGroups.begin();
  71. for( ; i != mGroups.end(); i++ )
  72. {
  73. if( dStricmp( (*i)->getGroupName(), groupName ) == 0 )
  74. return true;
  75. }
  76. return false;
  77. }
  78. void GuiInspector::clearGroups()
  79. {
  80. // If we're clearing the groups, we want to clear our target too.
  81. mTarget = NULL;
  82. // If we have no groups, there's nothing to clear!
  83. if( mGroups.empty() )
  84. return;
  85. // Attempt to find it in the group list
  86. Vector<GuiInspectorGroup*>::iterator i = mGroups.begin();
  87. for( ; i != mGroups.end(); i++ )
  88. if( (*i)->isProperlyAdded() )
  89. (*i)->deleteObject();
  90. mGroups.clear();
  91. }
  92. void GuiInspector::inspectObject( SimObject *object )
  93. {
  94. GuiCanvas *guiCanvas = getRoot();
  95. if( !guiCanvas )
  96. return;
  97. SimObjectPtr<GuiControl> currResponder = guiCanvas->getFirstResponder();
  98. // If our target is the same as our current target, just update the groups.
  99. if( mTarget == object )
  100. {
  101. Vector<GuiInspectorGroup*>::iterator i = mGroups.begin();
  102. for ( ; i != mGroups.end(); i++ )
  103. (*i)->inspectGroup();
  104. // Don't steal first responder
  105. if( !currResponder.isNull() )
  106. guiCanvas->setFirstResponder( currResponder );
  107. return;
  108. }
  109. // Clear our current groups
  110. clearGroups();
  111. // Set Target
  112. mTarget = object;
  113. // Always create the 'general' group (for un-grouped fields)
  114. GuiInspectorGroup* general = new GuiInspectorGroup( mTarget, "General", this );
  115. if( general != NULL )
  116. {
  117. general->registerObject();
  118. mGroups.push_back( general );
  119. addObject( general );
  120. }
  121. // Grab this objects field list
  122. AbstractClassRep::FieldList &fieldList = mTarget->getModifiableFieldList();
  123. AbstractClassRep::FieldList::iterator itr;
  124. // Iterate through, identifying the groups and create necessary GuiInspectorGroups
  125. for(itr = fieldList.begin(); itr != fieldList.end(); itr++)
  126. {
  127. if(itr->type == AbstractClassRep::StartGroupFieldType && !findExistentGroup( itr->pGroupname ) )
  128. {
  129. GuiInspectorGroup *group = new GuiInspectorGroup( mTarget, itr->pGroupname, this );
  130. if( group != NULL )
  131. {
  132. group->registerObject();
  133. mGroups.push_back( group );
  134. addObject( group );
  135. }
  136. }
  137. }
  138. // Deal with dynamic fields
  139. GuiInspectorGroup *dynGroup = new GuiInspectorDynamicGroup( mTarget, "Dynamic Fields", this);
  140. if( dynGroup != NULL )
  141. {
  142. dynGroup->registerObject();
  143. mGroups.push_back( dynGroup );
  144. addObject( dynGroup );
  145. }
  146. // If the general group is still empty at this point, kill it.
  147. for(S32 i=0; i<mGroups.size(); i++)
  148. {
  149. if(mGroups[i] == general && general->mStack->size() == 0)
  150. {
  151. mGroups.erase(i);
  152. general->deleteObject();
  153. updatePanes();
  154. }
  155. }
  156. // Don't steal first responder
  157. if( !currResponder.isNull() )
  158. guiCanvas->setFirstResponder( currResponder );
  159. }
  160. ConsoleMethod( GuiInspector, inspect, void, 3, 3, "(obj) Goes through the object's fields and autogenerates editor boxes\n"
  161. "@return No return value.")
  162. {
  163. SimObject * target = Sim::findObject(argv[2]);
  164. if(!target)
  165. {
  166. if(dAtoi(argv[2]) > 0)
  167. Con::warnf("%s::inspect(): invalid object: %s", argv[0], argv[2]);
  168. object->clearGroups();
  169. return;
  170. }
  171. object->inspectObject(target);
  172. }
  173. ConsoleMethod( GuiInspector, getInspectObject, const char*, 2, 2, "() - Returns currently inspected object\n"
  174. "@return The Object's ID as a string.")
  175. {
  176. SimObject *pSimObject = object->getInspectObject();
  177. if( pSimObject != NULL )
  178. return pSimObject->getIdString();
  179. return "";
  180. }
  181. void GuiInspector::setName( const char* newName )
  182. {
  183. if( mTarget == NULL )
  184. return;
  185. // Only assign a new name if we provide one
  186. mTarget->assignName(newName);
  187. }
  188. ConsoleMethod( GuiInspector, setName, void, 3, 3, "(NewObjectName) Set object name.\n"
  189. "@return No return value.")
  190. {
  191. object->setName(argv[2]);
  192. }
  193. //////////////////////////////////////////////////////////////////////////
  194. // GuiInspectorField
  195. //////////////////////////////////////////////////////////////////////////
  196. // The GuiInspectorField control is a representation of a single abstract
  197. // field for a given ConsoleObject derived object. It handles creation
  198. // getting and setting of it's fields data and editing control.
  199. //
  200. // Creation of custom edit controls is done through this class and is
  201. // dependent upon the dynamic console type, which may be defined to be
  202. // custom for different types.
  203. //
  204. // Note : GuiInspectorField controls must have a GuiInspectorGroup as their
  205. // parent.
  206. IMPLEMENT_CONOBJECT(GuiInspectorField);
  207. // Caption width is in percentage of total width
  208. S32 GuiInspectorField::smCaptionWidth = 50;
  209. GuiInspectorField::GuiInspectorField( GuiInspectorGroup* parent, SimObjectPtr<SimObject> target, AbstractClassRep::Field* field )
  210. {
  211. if( field != NULL )
  212. mCaption = StringTable->insert( field->pFieldname );
  213. else
  214. mCaption = StringTable->EmptyString;
  215. mParent = parent;
  216. mTarget = target;
  217. mField = field;
  218. mCanSave = false;
  219. mFieldArrayIndex = NULL;
  220. mBounds.set(0,0,100,18);
  221. }
  222. GuiInspectorField::GuiInspectorField()
  223. {
  224. mCaption = StringTable->EmptyString;
  225. mParent = NULL;
  226. mTarget = NULL;
  227. mField = NULL;
  228. mFieldArrayIndex = NULL;
  229. mBounds.set(0,0,100,18);
  230. mCanSave = false;
  231. }
  232. GuiInspectorField::~GuiInspectorField()
  233. {
  234. }
  235. //////////////////////////////////////////////////////////////////////////
  236. // Get/Set Data Functions
  237. //////////////////////////////////////////////////////////////////////////
  238. void GuiInspectorField::setData( const char* data )
  239. {
  240. if( mField == NULL || mTarget == NULL )
  241. return;
  242. mTarget->inspectPreApply();
  243. mTarget->setDataField( mField->pFieldname, mFieldArrayIndex, data );
  244. // Force our edit to update
  245. updateValue( data );
  246. mTarget->inspectPostApply();
  247. }
  248. const char* GuiInspectorField::getData()
  249. {
  250. if( mField == NULL || mTarget == NULL )
  251. return "";
  252. return mTarget->getDataField( mField->pFieldname, mFieldArrayIndex );
  253. }
  254. void GuiInspectorField::setInspectorField( AbstractClassRep::Field *field, const char*arrayIndex )
  255. {
  256. mField = field;
  257. if( arrayIndex != NULL )
  258. {
  259. mFieldArrayIndex = StringTable->insert( arrayIndex );
  260. S32 frameTempSize = dStrlen( field->pFieldname ) + 32;
  261. FrameTemp<char> valCopy( frameTempSize );
  262. dSprintf( (char *)valCopy, frameTempSize, "%s%s", field->pFieldname, arrayIndex );
  263. mCaption = StringTable->insert( valCopy );
  264. }
  265. else
  266. mCaption = StringTable->insert( field->pFieldname );
  267. }
  268. StringTableEntry GuiInspectorField::getFieldName()
  269. {
  270. // Sanity
  271. if ( mField == NULL )
  272. return StringTable->EmptyString;
  273. // Array element?
  274. if( mFieldArrayIndex != NULL )
  275. {
  276. S32 frameTempSize = dStrlen( mField->pFieldname ) + 32;
  277. FrameTemp<char> valCopy( frameTempSize );
  278. dSprintf( (char *)valCopy, frameTempSize, "%s%s", mField->pFieldname, mFieldArrayIndex );
  279. // Return formatted element
  280. return StringTable->insert( valCopy );
  281. }
  282. // Plain ole field name.
  283. return mField->pFieldname;
  284. };
  285. //////////////////////////////////////////////////////////////////////////
  286. // Overrideables for custom edit fields
  287. //////////////////////////////////////////////////////////////////////////
  288. GuiControl* GuiInspectorField::constructEditControl()
  289. {
  290. GuiControl* retCtrl = new GuiTextEditCtrl();
  291. // If we couldn't construct the control, bail!
  292. if( retCtrl == NULL )
  293. return retCtrl;
  294. // Let's make it look pretty.
  295. retCtrl->setField( "profile", "GuiInspectorTextEditProfile" );
  296. // Don't forget to register ourselves
  297. registerEditControl( retCtrl );
  298. char szBuffer[512];
  299. dSprintf( szBuffer, 512, "%d.apply(%d.getText());",getId(), retCtrl->getId() );
  300. retCtrl->setField("AltCommand", szBuffer );
  301. retCtrl->setField("Validate", szBuffer );
  302. return retCtrl;
  303. }
  304. void GuiInspectorField::registerEditControl( GuiControl *ctrl )
  305. {
  306. if(!mTarget)
  307. return;
  308. char szName[512];
  309. dSprintf( szName, 512, "IE_%s_%d_%s_Field", ctrl->getClassName(), mTarget->getId(),mCaption);
  310. // Register the object
  311. ctrl->registerObject( szName );
  312. }
  313. void GuiInspectorField::onRender(Point2I offset, const RectI &updateRect)
  314. {
  315. if(mCaption && mCaption[0])
  316. {
  317. // Calculate Caption Rect
  318. RectI captionRect( offset , Point2I((S32) mFloor( mBounds.extent.x * (F32)( (F32)GuiInspectorField::smCaptionWidth / 100.0f ) ), (S32)mBounds.extent.y ) );
  319. // Calculate Y Offset to center vertically the caption
  320. U32 captionYOffset = (U32)mFloor( (F32)( captionRect.extent.y - mProfile->mFont->getHeight() ) / 2 );
  321. RectI clipRect = dglGetClipRect();
  322. if( clipRect.intersect( captionRect ) )
  323. {
  324. // Backup Bitmap Modulation
  325. ColorI currColor;
  326. dglGetBitmapModulation( &currColor );
  327. dglSetBitmapModulation( mProfile->mFontColor );
  328. dglSetClipRect( RectI( clipRect.point, Point2I( captionRect.extent.x, clipRect.extent.y ) ));
  329. // Draw Caption ( Vertically Centered )
  330. U32 textY = captionRect.point.y + captionYOffset;
  331. U32 textX = captionRect.point.x + captionRect.extent.x - mProfile->mFont->getStrWidth(mCaption) - 6;
  332. Point2I textPT(textX, textY);
  333. dglDrawText( mProfile->mFont, textPT, mCaption, &mProfile->mFontColor );
  334. dglSetBitmapModulation( currColor );
  335. dglSetClipRect( clipRect );
  336. }
  337. }
  338. Parent::onRender( offset, updateRect );
  339. }
  340. bool GuiInspectorField::onAdd()
  341. {
  342. if( !Parent::onAdd() )
  343. return false;
  344. if( !mTarget )
  345. return false;
  346. mEdit = constructEditControl();
  347. if( mEdit == NULL )
  348. return false;
  349. // Add our edit as a child
  350. addObject( mEdit );
  351. // Calculate Caption Rect
  352. RectI captionRect( mBounds.point , Point2I( (S32)mFloor( mBounds.extent.x * (F32)( (F32)GuiInspectorField::smCaptionWidth / 100.0 ) ), (S32)mBounds.extent.y ) );
  353. // Calculate Edit Field Rect
  354. RectI editFieldRect( Point2I( captionRect.extent.x + 1, 1 ) , Point2I( mBounds.extent.x - ( captionRect.extent.x + 5 ) , mBounds.extent.y - 1) );
  355. // Resize to fit properly in allotted space
  356. mEdit->resize( editFieldRect.point, editFieldRect.extent );
  357. // Prefer GuiInspectorFieldProfile
  358. setField( "profile", "GuiInspectorFieldProfile" );
  359. // Force our editField to set it's value
  360. updateValue( getData() );
  361. return true;
  362. }
  363. void GuiInspectorField::updateValue( const char* newValue )
  364. {
  365. GuiTextEditCtrl *ctrl = dynamic_cast<GuiTextEditCtrl*>( mEdit );
  366. if( ctrl != NULL )
  367. ctrl->setText( newValue );
  368. }
  369. ConsoleMethod( GuiInspectorField, apply, void, 3,3, "(newValue) Applies the given value to the field\n"
  370. "@return No return value." )
  371. {
  372. object->setData( argv[2] );
  373. }
  374. void GuiInspectorField::resize( const Point2I &newPosition, const Point2I &newExtent )
  375. {
  376. Parent::resize( newPosition, newExtent );
  377. if( mEdit != NULL )
  378. {
  379. // Calculate Caption Rect
  380. RectI captionRect( mBounds.point , Point2I( (S32)mFloor( mBounds.extent.x * (F32)( (F32)GuiInspectorField::smCaptionWidth / 100.0f ) ), (S32)mBounds.extent.y ) );
  381. // Calculate Edit Field Rect
  382. RectI editFieldRect( Point2I( captionRect.extent.x + 1, 1 ) , Point2I( mBounds.extent.x - ( captionRect.extent.x + 5 ) , mBounds.extent.y - 1) );
  383. mEdit->resize( editFieldRect.point, editFieldRect.extent );
  384. }
  385. }
  386. //////////////////////////////////////////////////////////////////////////
  387. // GuiInspectorGroup
  388. //////////////////////////////////////////////////////////////////////////
  389. //
  390. // The GuiInspectorGroup control is a helper control that the inspector
  391. // makes use of which houses a collapsible pane type control for separating
  392. // inspected objects fields into groups. The content of the inspector is
  393. // made up of zero or more GuiInspectorGroup controls inside of a GuiStackControl
  394. //
  395. //
  396. //
  397. IMPLEMENT_CONOBJECT(GuiInspectorGroup);
  398. GuiInspectorGroup::GuiInspectorGroup()
  399. {
  400. mBounds.set(0,0,200,20);
  401. mChildren.clear();
  402. mTarget = NULL;
  403. mParent = NULL;
  404. mCanSave = false;
  405. // Make sure we receive our ticks.
  406. setProcessTicks();
  407. }
  408. GuiInspectorGroup::GuiInspectorGroup( SimObjectPtr<SimObject> target, StringTableEntry groupName, SimObjectPtr<GuiInspector> parent )
  409. {
  410. mBounds.set(0,0,200,20);
  411. mChildren.clear();
  412. mCaption = StringTable->insert(groupName);
  413. mTarget = target;
  414. mParent = parent;
  415. mCanSave = false;
  416. }
  417. GuiInspectorGroup::~GuiInspectorGroup()
  418. {
  419. if( !mChildren.empty() )
  420. {
  421. Vector<GuiInspectorField*>::iterator i = mChildren.begin();
  422. for( ; i != mChildren.end(); i++ );
  423. }
  424. }
  425. //////////////////////////////////////////////////////////////////////////
  426. // Scene Events
  427. //////////////////////////////////////////////////////////////////////////
  428. bool GuiInspectorGroup::onAdd()
  429. {
  430. setField( "profile", "GuiInspectorGroupProfile" );
  431. if( !Parent::onAdd() )
  432. return false;
  433. // Create our inner controls. Allow subclasses to provide other content.
  434. if(!createContent())
  435. return false;
  436. inspectGroup();
  437. return true;
  438. }
  439. bool GuiInspectorGroup::createContent()
  440. {
  441. // Create our field stack control
  442. mStack = new GuiStackControl();
  443. if( !mStack )
  444. return false;
  445. // Prefer GuiTransperantProfile for the stack.
  446. mStack->setField( "profile", "GuiTransparentProfile" );
  447. mStack->registerObject();
  448. addObject( mStack );
  449. mStack->setField( "padding", "0" );
  450. return true;
  451. }
  452. //////////////////////////////////////////////////////////////////////////
  453. // Control Sizing Animation Functions
  454. //////////////////////////////////////////////////////////////////////////
  455. void GuiInspectorGroup::animateToContents()
  456. {
  457. calculateHeights();
  458. if(size() > 0)
  459. animateTo( mExpanded.extent.y );
  460. else
  461. animateTo( mHeader.extent.y );
  462. }
  463. GuiInspectorField* GuiInspectorGroup::constructField( S32 fieldType )
  464. {
  465. ConsoleBaseType *cbt = ConsoleBaseType::getType(fieldType);
  466. AssertFatal(cbt, "GuiInspectorGroup::constructField - could not resolve field type!");
  467. // Alright, is it a datablock?
  468. if(cbt->isDatablock())
  469. {
  470. // This is fairly straightforward to deal with.
  471. GuiInspectorDatablockField *dbFieldClass = new GuiInspectorDatablockField( cbt->getTypeClassName() );
  472. if( dbFieldClass != NULL )
  473. {
  474. // return our new datablock field with correct datablock type enumeration info
  475. return dbFieldClass;
  476. }
  477. }
  478. // Nope, not a datablock. So maybe it has a valid inspector field override we can use?
  479. if(!cbt->getInspectorFieldType())
  480. // Nothing, so bail.
  481. return NULL;
  482. // Otherwise try to make it!
  483. ConsoleObject *co = create(cbt->getInspectorFieldType());
  484. GuiInspectorField *gif = dynamic_cast<GuiInspectorField*>(co);
  485. if(!gif)
  486. {
  487. // Wasn't appropriate type, bail.
  488. delete co;
  489. return NULL;
  490. }
  491. return gif;
  492. }
  493. GuiInspectorField *GuiInspectorGroup::findField( StringTableEntry fieldName )
  494. {
  495. // If we don't have any field children we can't very well find one then can we?
  496. if( mChildren.empty() )
  497. return NULL;
  498. Vector<GuiInspectorField*>::iterator i = mChildren.begin();
  499. for( ; i != mChildren.end(); i++ )
  500. {
  501. if( (*i)->getFieldName() != NULL && dStricmp( (*i)->getFieldName(), fieldName ) == 0 )
  502. return (*i);
  503. }
  504. return NULL;
  505. }
  506. bool GuiInspectorGroup::inspectGroup()
  507. {
  508. // We can't inspect a group without a target!
  509. if( !mTarget )
  510. return false;
  511. // to prevent crazy resizing, we'll just freeze our stack for a sec..
  512. mStack->freeze(true);
  513. bool bNoGroup = false;
  514. // Un-grouped fields are all sorted into the 'general' group
  515. if ( dStricmp( mCaption, "General" ) == 0 )
  516. bNoGroup = true;
  517. AbstractClassRep::FieldList &fieldList = mTarget->getModifiableFieldList();
  518. AbstractClassRep::FieldList::iterator itr;
  519. bool bGrabItems = false;
  520. bool bNewItems = false;
  521. for(itr = fieldList.begin(); itr != fieldList.end(); itr++)
  522. {
  523. if( itr->type == AbstractClassRep::StartGroupFieldType )
  524. {
  525. // If we're dealing with general fields, always set grabItems to true (to skip them)
  526. if( bNoGroup == true )
  527. bGrabItems = true;
  528. else if( itr->pGroupname != NULL && dStricmp( itr->pGroupname, mCaption ) == 0 )
  529. bGrabItems = true;
  530. continue;
  531. }
  532. else if ( itr->type == AbstractClassRep::EndGroupFieldType )
  533. {
  534. // If we're dealing with general fields, always set grabItems to false (to grab them)
  535. if( bNoGroup == true )
  536. bGrabItems = false;
  537. else if( itr->pGroupname != NULL && dStricmp( itr->pGroupname, mCaption ) == 0 )
  538. bGrabItems = false;
  539. continue;
  540. }
  541. if( ( bGrabItems == true || ( bNoGroup == true && bGrabItems == false ) ) && itr->type != AbstractClassRep::DepricatedFieldType )
  542. {
  543. if( bNoGroup == true && bGrabItems == true )
  544. continue;
  545. // This is weird, but it should work for now. - JDD
  546. // We are going to check to see if this item is an array
  547. // if so, we're going to construct a field for each array element
  548. if( itr->elementCount > 1 )
  549. {
  550. for(S32 nI = 0; nI < itr->elementCount; nI++)
  551. {
  552. FrameTemp<char> intToStr( 64 );
  553. dSprintf( intToStr, 64, "%d", nI );
  554. const char *val = mTarget->getDataField( itr->pFieldname, intToStr );
  555. if (!val)
  556. val = StringTable->EmptyString;
  557. // Copy Val and construct proper ValueName[nI] format
  558. // which is "ValueName0" for index 0, etc.
  559. S32 frameTempSize = dStrlen( val ) + 32;
  560. FrameTemp<char> valCopy( frameTempSize );
  561. dSprintf( (char *)valCopy, frameTempSize, "%s%d", itr->pFieldname, nI );
  562. // If the field already exists, just update it
  563. GuiInspectorField *field = findField( valCopy );
  564. if( field != NULL )
  565. {
  566. field->updateValue( field->getData() );
  567. continue;
  568. }
  569. bNewItems = true;
  570. field = constructField( itr->type );
  571. if( field == NULL )
  572. {
  573. field = new GuiInspectorField( this, mTarget, itr );
  574. field->setInspectorField( itr, intToStr );
  575. }
  576. else
  577. {
  578. field->setTarget( mTarget );
  579. field->setParent( this );
  580. field->setInspectorField( itr, intToStr );
  581. }
  582. field->registerObject();
  583. mChildren.push_back( field );
  584. mStack->addObject( field );
  585. }
  586. }
  587. else
  588. {
  589. // If the field already exists, just update it
  590. GuiInspectorField *field = findField( itr->pFieldname );
  591. if( field != NULL )
  592. {
  593. field->updateValue( field->getData() );
  594. continue;
  595. }
  596. bNewItems = true;
  597. field = constructField( itr->type );
  598. if( field == NULL )
  599. field = new GuiInspectorField( this, mTarget, itr );
  600. else
  601. {
  602. field->setTarget( mTarget );
  603. field->setParent( this );
  604. field->setInspectorField( itr );
  605. }
  606. field->registerObject();
  607. mChildren.push_back( field );
  608. mStack->addObject( field );
  609. }
  610. }
  611. }
  612. mStack->freeze(false);
  613. mStack->updatePanes();
  614. // If we've no new items, there's no need to resize anything!
  615. if( bNewItems == false && !mChildren.empty() )
  616. return true;
  617. sizeToContents();
  618. setUpdate();
  619. return true;
  620. }
  621. IMPLEMENT_CONOBJECT(GuiInspectorDynamicGroup);
  622. //////////////////////////////////////////////////////////////////////////
  623. // GuiInspectorDynamicGroup - add custom controls
  624. //////////////////////////////////////////////////////////////////////////
  625. bool GuiInspectorDynamicGroup::createContent()
  626. {
  627. if(!Parent::createContent())
  628. return false;
  629. // add a button that lets us add new dynamic fields.
  630. GuiIconButtonCtrl* addFieldBtn = new GuiIconButtonCtrl();
  631. {
  632. addFieldBtn->setBitmap("tools/gui/images/iconAdd");
  633. SimObject* profilePtr = Sim::findObject("EditorButton");
  634. if( profilePtr != NULL )
  635. addFieldBtn->setControlProfile( dynamic_cast<GuiControlProfile*>(profilePtr) );
  636. char commandBuf[64];
  637. dSprintf(commandBuf, 64, "%d.addDynamicField();", this->getId());
  638. addFieldBtn->setField("command", commandBuf);
  639. addFieldBtn->setSizing(horizResizeLeft,vertResizeCenter);
  640. //addFieldBtn->setField("buttonMargin", "2 2");
  641. addFieldBtn->resize(Point2I(mBounds.extent.x - 20,2), Point2I(16, 16));
  642. addFieldBtn->registerObject("zAddButton");
  643. }
  644. // encapsulate the button in a dummy control.
  645. GuiControl* shell = new GuiControl();
  646. shell->setField( "profile", "GuiTransparentProfile" );
  647. shell->registerObject();
  648. shell->resize(Point2I(0,0), Point2I(mBounds.extent.x, 28));
  649. shell->addObject(addFieldBtn);
  650. // save off the shell control, so we can push it to the bottom of the stack in inspectGroup()
  651. mAddCtrl = shell;
  652. mStack->addObject(shell);
  653. return true;
  654. }
  655. static S32 QSORT_CALLBACK compareEntries(const void* a,const void* b)
  656. {
  657. SimFieldDictionary::Entry *fa = *((SimFieldDictionary::Entry **)a);
  658. SimFieldDictionary::Entry *fb = *((SimFieldDictionary::Entry **)b);
  659. return dStricmp(fa->slotName, fb->slotName);
  660. }
  661. //////////////////////////////////////////////////////////////////////////
  662. // GuiInspectorDynamicGroup - inspectGroup override
  663. //////////////////////////////////////////////////////////////////////////
  664. bool GuiInspectorDynamicGroup::inspectGroup()
  665. {
  666. // We can't inspect a group without a target!
  667. if( !mTarget )
  668. return false;
  669. // Clearing the fields and recreating them will more than likely be more
  670. // efficient than looking up existent fields, updating them, and then iterating
  671. // over existent fields and making sure they still exist, if not, deleting them.
  672. clearFields();
  673. // Create a vector of the fields
  674. Vector<SimFieldDictionary::Entry *> flist;
  675. // Then populate with fields
  676. SimFieldDictionary * fieldDictionary = mTarget->getFieldDictionary();
  677. for(SimFieldDictionaryIterator ditr(fieldDictionary); *ditr; ++ditr)
  678. {
  679. flist.push_back(*ditr);
  680. }
  681. dQsort(flist.address(),flist.size(),sizeof(SimFieldDictionary::Entry *),compareEntries);
  682. for(U32 i = 0; i < (U32)flist.size(); i++)
  683. {
  684. SimFieldDictionary::Entry * entry = flist[i];
  685. GuiInspectorField *field = new GuiInspectorDynamicField( this, mTarget, entry );
  686. if( field != NULL )
  687. {
  688. field->registerObject();
  689. mChildren.push_back( field );
  690. mStack->addObject( field );
  691. }
  692. }
  693. mStack->pushObjectToBack(mAddCtrl);
  694. setUpdate();
  695. return true;
  696. }
  697. ConsoleMethod(GuiInspectorDynamicGroup, inspectGroup, bool, 2, 2, "() Refreshes the dynamic fields in the inspector.\n"
  698. "@return Returns true on success.")
  699. {
  700. return object->inspectGroup();
  701. }
  702. void GuiInspectorDynamicGroup::clearFields()
  703. {
  704. // save mAddCtrl
  705. Sim::getGuiGroup()->addObject(mAddCtrl);
  706. // delete everything else
  707. mStack->clear();
  708. // clear the mChildren list.
  709. mChildren.clear();
  710. // and restore.
  711. mStack->addObject(mAddCtrl);
  712. }
  713. SimFieldDictionary::Entry* GuiInspectorDynamicGroup::findDynamicFieldInDictionary( StringTableEntry fieldName )
  714. {
  715. if( !mTarget )
  716. return NULL;
  717. SimFieldDictionary * fieldDictionary = mTarget->getFieldDictionary();
  718. for(SimFieldDictionaryIterator ditr(fieldDictionary); *ditr; ++ditr)
  719. {
  720. SimFieldDictionary::Entry * entry = (*ditr);
  721. if( dStricmp( entry->slotName, fieldName ) == 0 )
  722. return entry;
  723. }
  724. return NULL;
  725. }
  726. void GuiInspectorDynamicGroup::addDynamicField()
  727. {
  728. // We can't add a field without a target
  729. if( !mTarget || !mStack )
  730. {
  731. Con::warnf("GuiInspectorDynamicGroup::addDynamicField - no target SimObject to add a dynamic field to.");
  732. return;
  733. }
  734. // find a field name that is not in use.
  735. // But we wont try more than 100 times to find an available field.
  736. U32 uid = 1;
  737. char buf[64] = "dynamicField";
  738. SimFieldDictionary::Entry* entry = findDynamicFieldInDictionary(buf);
  739. while(entry != NULL && uid < 100)
  740. {
  741. dSprintf(buf, sizeof(buf), "dynamicField%03d", uid++);
  742. entry = findDynamicFieldInDictionary(buf);
  743. }
  744. //Con::evaluatef( "%d.%s = \"defaultValue\";", mTarget->getId(), buf );
  745. mTarget->setDataField(StringTable->insert(buf), NULL, "defaultValue");
  746. // now we simply re-inspect the object, to see the new field.
  747. this->inspectGroup();
  748. animateToContents();
  749. }
  750. ConsoleMethod( GuiInspectorDynamicGroup, addDynamicField, void, 2, 2, "obj.addDynamicField();" )
  751. {
  752. object->addDynamicField();
  753. }
  754. //////////////////////////////////////////////////////////////////////////
  755. // GuiInspectorDynamicField - Child class of GuiInspectorField
  756. //////////////////////////////////////////////////////////////////////////
  757. IMPLEMENT_CONOBJECT(GuiInspectorDynamicField);
  758. GuiInspectorDynamicField::GuiInspectorDynamicField( GuiInspectorGroup* parent, SimObjectPtr<SimObject> target, SimFieldDictionary::Entry* field )
  759. {
  760. mCaption = NULL;
  761. mParent = parent;
  762. mTarget = target;
  763. mDynField = field;
  764. mBounds.set(0,0,100,20);
  765. mRenameCtrl = NULL;
  766. }
  767. void GuiInspectorDynamicField::setData( const char* data )
  768. {
  769. if( mTarget == NULL || mDynField == NULL )
  770. return;
  771. char buf[1024];
  772. const char * newValue = mEdit->getScriptValue();
  773. dStrcpy( buf, newValue ? newValue : "" );
  774. collapseEscape(buf);
  775. mTarget->getFieldDictionary()->setFieldValue(mDynField->slotName, buf);
  776. // Force our edit to update
  777. updateValue( data );
  778. }
  779. const char* GuiInspectorDynamicField::getData()
  780. {
  781. if( mTarget == NULL || mDynField == NULL )
  782. return "";
  783. return mTarget->getFieldDictionary()->getFieldValue( mDynField->slotName );
  784. }
  785. void GuiInspectorDynamicField::renameField( StringTableEntry newFieldName )
  786. {
  787. if( mTarget == NULL || mDynField == NULL || mParent == NULL || mEdit == NULL )
  788. {
  789. Con::warnf("GuiInspectorDynamicField::renameField - No target object or dynamic field data found!" );
  790. return;
  791. }
  792. if( !newFieldName )
  793. {
  794. Con::warnf("GuiInspectorDynamicField::renameField - Invalid field name specified!" );
  795. return;
  796. }
  797. // Only proceed if the name has changed
  798. if( dStricmp( newFieldName, getFieldName() ) == 0 )
  799. return;
  800. // Grab a pointer to our parent and cast it to GuiInspectorDynamicGroup
  801. GuiInspectorDynamicGroup *group = dynamic_cast<GuiInspectorDynamicGroup*>(mParent);
  802. if( group == NULL )
  803. {
  804. Con::warnf("GuiInspectorDynamicField::renameField - Unable to locate GuiInspectorDynamicGroup parent!" );
  805. return;
  806. }
  807. // Grab our current dynamic field value
  808. const char* currentValue = getData();
  809. // Create our new field with the value of our old field and the new fields name!
  810. mTarget->setDataField( newFieldName, NULL, currentValue );
  811. // Configure our field to grab data from the new dynamic field
  812. SimFieldDictionary::Entry *newEntry = group->findDynamicFieldInDictionary( newFieldName );
  813. if( newEntry == NULL )
  814. {
  815. Con::warnf("GuiInspectorDynamicField::renameField - Unable to find new field!" );
  816. return;
  817. }
  818. // Set our old fields data to "" (which will effectively erase the field)
  819. mTarget->setDataField( getFieldName(), NULL, "" );
  820. // Assign our dynamic field pointer (where we retrieve field information from) to our new field pointer
  821. mDynField = newEntry;
  822. // Lastly we need to reassign our Command and AltCommand fields for our value edit control
  823. char szBuffer[512];
  824. dSprintf( szBuffer, 512, "%d.%s = %d.getText();",mTarget->getId(), getFieldName(), mEdit->getId() );
  825. mEdit->setField("AltCommand", szBuffer );
  826. mEdit->setField("Validate", szBuffer );
  827. }
  828. ConsoleMethod( GuiInspectorDynamicField, renameField, void, 3,3, "field.renameField(newDynamicFieldName);" )
  829. {
  830. object->renameField( StringTable->insert(argv[2]) );
  831. }
  832. bool GuiInspectorDynamicField::onAdd()
  833. {
  834. if( !Parent::onAdd() )
  835. return false;
  836. mRenameCtrl = constructRenameControl();
  837. pushObjectToBack(mEdit);
  838. return true;
  839. }
  840. GuiControl* GuiInspectorDynamicField::constructRenameControl()
  841. {
  842. // Create our renaming field
  843. GuiControl* retCtrl = new GuiTextEditCtrl();
  844. // If we couldn't construct the control, bail!
  845. if( retCtrl == NULL )
  846. return retCtrl;
  847. // Let's make it look pretty.
  848. retCtrl->setField( "profile", "GuiInspectorTextEditRightProfile" );
  849. // Don't forget to register ourselves
  850. char szName[512];
  851. dSprintf( szName, 512, "IE_%s_%d_%s_Rename", retCtrl->getClassName(), mTarget->getId(), getFieldName() );
  852. retCtrl->registerObject( szName );
  853. // Our command will evaluate to :
  854. //
  855. // if( (editCtrl).getText() !$= "" )
  856. // (field).renameField((editCtrl).getText());
  857. //
  858. char szBuffer[512];
  859. dSprintf( szBuffer, 512, "if( %d.getText() !$= \"\" ) %d.renameField(%d.getText());",retCtrl->getId(), getId(), retCtrl->getId() );
  860. dynamic_cast<GuiTextEditCtrl*>(retCtrl)->setText( getFieldName() );
  861. retCtrl->setField("AltCommand", szBuffer );
  862. retCtrl->setField("Validate", szBuffer );
  863. // Calculate Caption Rect (Adjust for 16 pixel wide delete button)
  864. RectI captionRect( Point2I(mBounds.point.x,0) , Point2I( (S32)mFloor( mBounds.extent.x * (F32)( (F32)GuiInspectorField::smCaptionWidth / 100.0f ) ), (S32)mBounds.extent.y ) );
  865. RectI valueRect(mEdit->mBounds.point, mEdit->mBounds.extent - Point2I(20, 0));
  866. RectI deleteRect( Point2I( mBounds.point.x + mBounds.extent.x - 20,2), Point2I( 16, mBounds.extent.y - 4));
  867. addObject( retCtrl );
  868. // Resize the name control to fit in our caption rect (tricksy!)
  869. retCtrl->resize( captionRect.point, captionRect.extent );
  870. // resize the value control to leave space for the delete button
  871. mEdit->resize(valueRect.point, valueRect.extent);
  872. // Finally, add a delete button for this field
  873. GuiIconButtonCtrl * delButt = new GuiIconButtonCtrl();
  874. if( delButt != NULL )
  875. {
  876. dSprintf(szBuffer, 512, "%d.%s = \"\";%d.inspectGroup();", mTarget->getId(), getFieldName(), mParent->getId());
  877. delButt->setField("Bitmap", "^modules/gui/images/iconDelete");
  878. delButt->setField("Text", "X");
  879. delButt->setField("Command", szBuffer);
  880. delButt->setSizing(horizResizeLeft,vertResizeCenter);
  881. delButt->registerObject();
  882. delButt->resize( deleteRect.point,deleteRect.extent);
  883. addObject(delButt);
  884. }
  885. return retCtrl;
  886. }
  887. void GuiInspectorDynamicField::resize( const Point2I &newPosition, const Point2I &newExtent )
  888. {
  889. Parent::resize( newPosition, newExtent );
  890. // If we don't have a field rename control, bail!
  891. if( mRenameCtrl == NULL )
  892. return;
  893. // Calculate Caption Rect
  894. RectI captionRect( Point2I(mBounds.point.x,0) , Point2I( (S32)mFloor( mBounds.extent.x * (F32)( (F32)GuiInspectorField::smCaptionWidth / 100.0f ) ), (S32)mBounds.extent.y ) );
  895. RectI valueRect(mEdit->mBounds.point, mEdit->mBounds.extent - Point2I(20, 0));
  896. // Resize the edit control to fit in our caption rect (tricksy!)
  897. mRenameCtrl->resize( captionRect.point, captionRect.extent );
  898. mEdit->resize( valueRect.point, valueRect.extent);
  899. }
  900. //////////////////////////////////////////////////////////////////////////
  901. // GuiInspectorDatablockField
  902. // Field construction for datablock types
  903. //////////////////////////////////////////////////////////////////////////
  904. IMPLEMENT_CONOBJECT(GuiInspectorDatablockField);
  905. static S32 QSORT_CALLBACK stringCompare(const void *a,const void *b)
  906. {
  907. StringTableEntry sa = *(StringTableEntry*)a;
  908. StringTableEntry sb = *(StringTableEntry*)b;
  909. return(dStricmp(sb, sa));
  910. }
  911. GuiInspectorDatablockField::GuiInspectorDatablockField( StringTableEntry className )
  912. {
  913. setClassName(className);
  914. };
  915. void GuiInspectorDatablockField::setClassName( StringTableEntry className )
  916. {
  917. // Walk the ACR list and find a matching class if any.
  918. AbstractClassRep *walk = AbstractClassRep::getClassList();
  919. while(walk)
  920. {
  921. if(!dStricmp(walk->getClassName(), className))
  922. {
  923. // Match!
  924. mDesiredClass = walk;
  925. return;
  926. }
  927. walk = walk->getNextClass();
  928. }
  929. // No dice.
  930. Con::warnf("GuiInspectorDatablockField::setClassName - no class '%s' found!", className);
  931. return;
  932. }
  933. GuiControl* GuiInspectorDatablockField::constructEditControl()
  934. {
  935. GuiControl* retCtrl = new GuiPopUpMenuCtrl();
  936. // If we couldn't construct the control, bail!
  937. if( retCtrl == NULL )
  938. return retCtrl;
  939. GuiPopUpMenuCtrl *menu = dynamic_cast<GuiPopUpMenuCtrl*>(retCtrl);
  940. // Let's make it look pretty.
  941. retCtrl->setField( "profile", "InspectorTypeEnumProfile" );
  942. menu->setField("text", getData());
  943. registerEditControl( retCtrl );
  944. // Configure it to update our value when the popup is closed
  945. char szBuffer[512];
  946. dSprintf( szBuffer, 512, "%d.%s = %d.getText();%d.inspect(%d);",mTarget->getId(), mField->pFieldname, menu->getId(), mParent->mParent->getId(), mTarget->getId() );
  947. menu->setField("Command", szBuffer );
  948. Vector<StringTableEntry> entries;
  949. SimDataBlockGroup * grp = Sim::getDataBlockGroup();
  950. for(SimDataBlockGroup::iterator i = grp->begin(); i != grp->end(); i++)
  951. {
  952. SimDataBlock * datablock = dynamic_cast<SimDataBlock*>(*i);
  953. // Skip non-datablocks if we somehow encounter them.
  954. if(!datablock)
  955. continue;
  956. // Ok, now we have to figure inheritance info.
  957. if( datablock && datablock->getClassRep()->isClass(mDesiredClass) )
  958. entries.push_back(datablock->getName());
  959. }
  960. // sort the entries
  961. dQsort(entries.address(), entries.size(), sizeof(StringTableEntry), stringCompare);
  962. // add them to our enum
  963. for(U32 j = 0; j < (U32)entries.size(); j++)
  964. menu->addEntry(entries[j], 0);
  965. return retCtrl;
  966. }