guiTreeViewCtrl.h 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  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. #ifndef _GUI_TREEVIEWCTRL_H
  23. #define _GUI_TREEVIEWCTRL_H
  24. #include "core/bitSet.h"
  25. #include "math/mRect.h"
  26. #include "gfx/gFont.h"
  27. #include "gui/core/guiControl.h"
  28. #include "gui/core/guiArrayCtrl.h"
  29. class GuiTextEditCtrl;
  30. //------------------------------------------------------------------------------
  31. class GuiTreeViewCtrl : public GuiArrayCtrl
  32. {
  33. private:
  34. typedef GuiArrayCtrl Parent;
  35. public:
  36. /// @section GuiControl_Intro Introduction
  37. /// @nosubgrouping
  38. ///
  39. class Item
  40. {
  41. public:
  42. enum ItemState
  43. {
  44. Selected = BIT( 0 ),
  45. Expanded = BIT( 1 ),
  46. Marked = BIT( 2 ), ///< Marked items are drawn with a border around them. This is
  47. /// different than "Selected" because it can only be set by script.
  48. Filtered = BIT( 3 ), ///< Whether the item is currently filtered out.
  49. MouseOverBmp = BIT( 4 ),
  50. MouseOverText = BIT( 5 ),
  51. MouseOverIcon = BIT( 6 ),
  52. InspectorData = BIT( 7 ), ///< Set if we're representing some inspector
  53. /// info (ie, use mInspectorInfo, not mScriptInfo)
  54. VirtualParent = BIT( 8 ), ///< This indicates that we should be rendered as
  55. /// a parent even though we don't have any children.
  56. /// This is useful for preventing scenarios where
  57. /// we might want to create thousands of
  58. /// Items that might never be shown (for instance
  59. /// if we're browsing the object hierarchy in
  60. /// Torque, which might have thousands of objects).
  61. RebuildVisited = BIT( 9 ), ///< Rebuild traversal for virtual parents has visited and validated this item.
  62. ShowObjectId = BIT( 10 ),
  63. ShowClassName = BIT( 11 ),
  64. ShowObjectName = BIT( 12 ),
  65. ShowInternalName = BIT( 13 ),
  66. ShowClassNameForUnnamed = BIT( 14 )
  67. };
  68. GuiTreeViewCtrl* mParentControl;
  69. BitSet32 mState;
  70. SimObjectPtr< GuiControlProfile > mProfile;
  71. S16 mId;
  72. U16 mTabLevel;
  73. Item* mParent;
  74. Item* mChild;
  75. Item* mNext;
  76. Item* mPrevious;
  77. String mTooltip;
  78. S32 mIcon; //stores the icon that will represent the item in the tree
  79. S32 mDataRenderWidth; /// this stores the pixel width needed
  80. /// to render the item's data in the
  81. /// onRenderCell function to optimize
  82. /// for speed.
  83. Item( GuiTreeViewCtrl* parent, GuiControlProfile *pProfile );
  84. ~Item();
  85. struct ScriptTag
  86. {
  87. S8 mNormalImage;
  88. S8 mExpandedImage;
  89. StringTableEntry mText;
  90. StringTableEntry mValue;
  91. } mScriptInfo;
  92. struct InspectorTag
  93. {
  94. SimObjectPtr<SimObject> mObject;
  95. } mInspectorInfo;
  96. /// @name Get Methods
  97. /// @{
  98. ///
  99. S8 getNormalImage() const;
  100. S8 getExpandedImage() const;
  101. StringTableEntry getText();
  102. StringTableEntry getValue();
  103. inline const S16 getID() const { return mId; };
  104. SimObject *getObject();
  105. U32 getDisplayTextLength();
  106. S32 getDisplayTextWidth(GFont *font);
  107. void getDisplayText(U32 bufLen, char *buf);
  108. bool hasObjectBasedTooltip();
  109. void getTooltipText(U32 bufLen, char *buf);
  110. /// @}
  111. /// @name Set Methods
  112. /// @{
  113. /// Set whether an item is expanded or not (showing children or having them hidden)
  114. void setExpanded( const bool f = true );
  115. /// Set the image to display when an item IS expanded
  116. void setExpandedImage(const S8 id);
  117. /// Set the image to display when an item is NOT expanded
  118. void setNormalImage(const S8 id);
  119. /// Assign a SimObject pointer to an inspector data item
  120. void setObject(SimObject *obj);
  121. /// Set the items displayable text (caption)
  122. void setText(StringTableEntry txt);
  123. /// Set the items script value (data)
  124. void setValue(StringTableEntry val);
  125. /// Set the items virtual parent flag
  126. void setVirtualParent( bool value );
  127. /// Set whether the item is filtered out or not.
  128. void setFiltered( bool value ) { mState.set( Filtered ); }
  129. /// @}
  130. /// @name State Retrieval
  131. /// @{
  132. /// Returns true if this item is expanded. For
  133. /// inspector objects, the expansion is stored
  134. /// on the SimObject, for other things we use our
  135. /// bit vector.
  136. bool isExpanded() const;
  137. /// Return whether the item is current filtered out or not.
  138. /// @note Parent items may be filtered and yet still be visible if they have
  139. /// children that are not filtered.
  140. bool isFiltered() const { return mState.test( Filtered ); }
  141. /// Returns true if an item is inspector data
  142. /// or false if it's just an item.
  143. bool isInspectorData() const { return mState.test(InspectorData); };
  144. /// Returns true if we should show the expand art
  145. /// and make the item interact with the mouse as if
  146. /// it were a parent.
  147. bool isParent() const;
  148. /// Return true if text label for inspector item should include internal name only.
  149. bool showInternalNameOnly() const { return mState.test( ShowInternalName ) && !mState.test( ShowObjectName | ShowClassName | ShowObjectId ); }
  150. /// Return true if text label for inspector item should include object name only.
  151. bool showObjectNameOnly() const { return mState.test( ShowObjectName ) && !mState.test( ShowInternalName | ShowClassName | ShowObjectId ); }
  152. /// @}
  153. /// @name Searching Methods
  154. /// @{
  155. /// Find a regular data item by it's script name.
  156. Item* findChildByName( const char* name );
  157. /// Find an inspector data item by it's SimObject pointer
  158. Item* findChildByValue(const SimObject *obj);
  159. /// Find a regular data item by it's script value
  160. Item* findChildByValue(StringTableEntry Value);
  161. /// @}
  162. /// Sort the childs of the item by their text.
  163. ///
  164. /// @param caseSensitive If true, sorting is case-sensitive.
  165. /// @param traverseHierarchy If true, also triggers a sort() on all child items.
  166. /// @param parentsFirst If true, parents are grouped before children in the resulting sort.
  167. void sort( bool caseSensitive = true, bool traverseHierarchy = false, bool parentsFirst = false );
  168. private:
  169. void _connectMonitors();
  170. void _disconnectMonitors();
  171. };
  172. friend class Item; // _onInspectorSetObjectModified
  173. /// @name Enums
  174. /// @{
  175. ///
  176. enum TreeState
  177. {
  178. RebuildVisible = BIT(0), ///< Temporary flag, we have to rebuild the tree.
  179. IsInspector = BIT(1), ///< We are mapping a SimObject hierarchy.
  180. IsEditable = BIT(2), ///< We allow items to be moved around.
  181. ShowTreeLines = BIT(3), ///< Should we render tree lines or just icons?
  182. BuildingVisTree = BIT(4), ///< We are currently building the visible tree (prevent recursion)
  183. };
  184. protected:
  185. enum
  186. {
  187. MaxIcons = 32,
  188. };
  189. enum Icons
  190. {
  191. Default1 = 0,
  192. SimGroup1,
  193. SimGroup2,
  194. SimGroup3,
  195. SimGroup4,
  196. Hidden,
  197. Lock1,
  198. Lock2,
  199. Default,
  200. Icon31,
  201. Icon32
  202. };
  203. enum mDragMidPointFlags
  204. {
  205. NomDragMidPoint,
  206. AbovemDragMidPoint,
  207. BelowmDragMidPoint
  208. };
  209. ///
  210. enum HitFlags
  211. {
  212. OnIndent = BIT(0),
  213. OnImage = BIT(1),
  214. OnIcon = BIT(2),
  215. OnText = BIT(3),
  216. OnRow = BIT(4),
  217. };
  218. ///
  219. enum BmpIndices
  220. {
  221. BmpFirstChild,
  222. BmpLastChild,
  223. BmpChild,
  224. BmpExp,
  225. BmpExpN,
  226. BmpExpP,
  227. BmpExpPN,
  228. BmpCon,
  229. BmpConN,
  230. BmpConP,
  231. BmpConPN,
  232. BmpLine,
  233. BmpGlow,
  234. };
  235. /// @}
  236. /// @name Callbacks
  237. /// @{
  238. DECLARE_CALLBACK( bool, onDeleteObject, ( SimObject* object ) );
  239. DECLARE_CALLBACK( bool, isValidDragTarget, ( S32 id, const char* value ) );
  240. DECLARE_CALLBACK( void, onDefineIcons, () );
  241. DECLARE_CALLBACK( void, onAddGroupSelected, ( SimGroup* group ) );
  242. DECLARE_CALLBACK( void, onAddSelection, ( S32 itemOrObjectId, bool isLastSelection ) );
  243. DECLARE_CALLBACK( void, onSelect, ( S32 itemOrObjectId ) );
  244. DECLARE_CALLBACK( void, onInspect, ( S32 itemOrObjectId ) );
  245. DECLARE_CALLBACK( void, onRemoveSelection, ( S32 itemOrObjectId ) );
  246. DECLARE_CALLBACK( void, onUnselect, ( S32 itemOrObjectId ) );
  247. DECLARE_CALLBACK( void, onDeleteSelection, () );
  248. DECLARE_CALLBACK( void, onObjectDeleteCompleted, () );
  249. DECLARE_CALLBACK( void, onKeyDown, ( S32 modifier, S32 keyCode ) );
  250. DECLARE_CALLBACK( void, onMouseUp, ( S32 hitItemId, S32 mouseClickCount ) );
  251. DECLARE_CALLBACK( void, onMouseDragged, () );
  252. DECLARE_CALLBACK( void, onRightMouseDown, ( S32 itemId, const Point2I& mousePos, SimObject* object = NULL ) );
  253. DECLARE_CALLBACK( void, onRightMouseUp, ( S32 itemId, const Point2I& mousePos, SimObject* object = NULL ) );
  254. DECLARE_CALLBACK( void, onBeginReparenting, () );
  255. DECLARE_CALLBACK( void, onEndReparenting, () );
  256. DECLARE_CALLBACK( void, onReparent, ( S32 itemOrObjectId, S32 oldParentItemOrObjectId, S32 newParentItemOrObjectId ) );
  257. DECLARE_CALLBACK( void, onDragDropped, () );
  258. DECLARE_CALLBACK( void, onAddMultipleSelectionBegin, () );
  259. DECLARE_CALLBACK( void, onAddMultipleSelectionEnd, () );
  260. DECLARE_CALLBACK( bool, canRenameObject, ( SimObject* object ) );
  261. DECLARE_CALLBACK( bool, handleRenameObject, ( const char* newName, SimObject* object ) );
  262. DECLARE_CALLBACK( void, onClearSelection, () );
  263. /// @}
  264. ///
  265. Vector<Item*> mItems;
  266. Vector<Item*> mVisibleItems;
  267. Vector<Item*> mSelectedItems;
  268. /// Used for tracking stuff that was selected, but may not have been
  269. /// created at time of selection.
  270. Vector<S32> mSelected;
  271. S32 mItemCount;
  272. /// We do our own free list, as we we want to be able to recycle
  273. /// item ids and do some other clever things.
  274. Item* mItemFreeList;
  275. Item* mRoot;
  276. S32 mMaxWidth;
  277. S32 mSelectedItem;
  278. S32 mDraggedToItem;
  279. S32 mStart;
  280. /// A combination of TreeState flags.
  281. BitSet32 mFlags;
  282. Item* mPossibleRenameItem;
  283. Item* mRenamingItem;
  284. Item* mTempItem;
  285. GuiTextEditCtrl* mRenameCtrl;
  286. /// Current filter that determines which items in the tree are displayed and which are hidden.
  287. String mFilterText;
  288. /// If true, a trace of actions taken by the control is logged to the console. Can
  289. /// be turned on with the setDebug() script method.
  290. bool mDebug;
  291. GFXTexHandle mIconTable[MaxIcons];
  292. S32 mTabSize;
  293. S32 mTextOffset;
  294. bool mFullRowSelect;
  295. S32 mItemHeight;
  296. bool mDestroyOnSleep;
  297. bool mSupportMouseDragging;
  298. bool mMultipleSelections;
  299. bool mDeleteObjectAllowed;
  300. bool mDragToItemAllowed;
  301. bool mClearAllOnSingleSelection; ///< When clicking on an already selected item, clear all other selections
  302. bool mCompareToObjectID;
  303. /// Used to hide the root tree element, defaults to true.
  304. bool mShowRoot;
  305. /// If true, object IDs will be included in inspector tree item labels.
  306. bool mShowObjectIds;
  307. /// If true, class names will be included in inspector tree item labels.
  308. bool mShowClassNames;
  309. /// If true, object names will be included in inspector tree item labels.
  310. bool mShowObjectNames;
  311. /// If true, internal names will be included in inspector tree item labels.
  312. bool mShowInternalNames;
  313. /// If true, class names will be used as object names for unnamed objects.
  314. bool mShowClassNameForUnnamedObjects;
  315. /// If true then tooltips will be automatically
  316. /// generated for all Inspector items
  317. bool mUseInspectorTooltips;
  318. /// If true then only render item tooltips if the item
  319. /// extends past the displayable width
  320. bool mTooltipOnWidthOnly;
  321. /// If true clicking on a selected item ( that is an object )
  322. /// will allow you to rename it.
  323. bool mCanRenameObjects;
  324. /// If true then object renaming operates on the internalName rather than
  325. /// the object name.
  326. bool mRenameInternal;
  327. S32 mCurrentDragCell;
  328. S32 mPreviousDragCell;
  329. S32 mDragMidPoint;
  330. bool mMouseDragged;
  331. bool mDragStartInSelection;
  332. Point2I mMouseDownPoint;
  333. StringTableEntry mBitmapBase;
  334. GFXTexHandle mTexRollover;
  335. GFXTexHandle mTexSelected;
  336. ColorI mAltFontColor;
  337. ColorI mAltFontColorHL;
  338. ColorI mAltFontColorSE;
  339. SimObjectPtr<SimObject> mRootObject;
  340. void _destroyChildren( Item* item, Item* parent, bool deleteObjects=true);
  341. void _destroyItem( Item* item, bool deleteObject=true);
  342. void _destroyTree();
  343. void _deleteItem(Item* item);
  344. void _buildItem(Item* item, U32 tabLevel, bool bForceFullUpdate = false);
  345. Item* _findItemByAmbiguousId( S32 itemOrObjectId, bool buildVirtual = true );
  346. void _expandObjectHierarchy( SimGroup* group );
  347. bool _hitTest(const Point2I & pnt, Item* & item, BitSet32 & flags);
  348. S32 getInspectorItemIconsWidth(Item* & item);
  349. virtual bool onVirtualParentBuild(Item *item, bool bForceFullUpdate = false);
  350. virtual bool onVirtualParentExpand(Item *item);
  351. virtual bool onVirtualParentCollapse(Item *item);
  352. virtual void onItemSelected( Item *item );
  353. virtual void onRemoveSelection( Item *item );
  354. virtual void onClearSelection() {};
  355. Item* addInspectorDataItem(Item *parent, SimObject *obj);
  356. virtual bool isValidDragTarget( Item* item );
  357. bool _isRootLevelItem( Item* item ) const
  358. {
  359. return ( item == mRoot && mShowRoot ) || ( item->mParent == mRoot && !mShowRoot );
  360. }
  361. /// For inspector tree views, this is hooked to the SetModificationSignal of sets
  362. /// so that the tree view knows when it needs to refresh.
  363. void _onInspectorSetObjectModified( SetModification modification, SimSet* set, SimObject* object );
  364. public:
  365. GuiTreeViewCtrl();
  366. virtual ~GuiTreeViewCtrl();
  367. /// Used for syncing the mSelected and mSelectedItems lists.
  368. void syncSelection();
  369. void lockSelection(bool lock);
  370. void hideSelection(bool hide);
  371. void toggleLockSelection();
  372. void toggleHideSelection();
  373. virtual bool canAddSelection( Item *item ) { return true; };
  374. void addSelection(S32 itemId, bool update = true, bool isLastSelection = true);
  375. const Vector< Item* >& getSelectedItems() const { return mSelectedItems; }
  376. const Vector< S32 >& getSelected() const { return mSelected; }
  377. bool isSelected(S32 itemId)
  378. {
  379. return isSelected( getItem( itemId ) );
  380. }
  381. bool isSelected(Item *item)
  382. {
  383. if ( !item )
  384. return false;
  385. return mSelectedItems.contains( item );
  386. }
  387. void setDebug( bool value ) { mDebug = value; }
  388. /// Should use addSelection and removeSelection when calling from script
  389. /// instead of setItemSelected. Use setItemSelected when you want to select
  390. /// something in the treeview as it has script call backs.
  391. void removeSelection(S32 itemId);
  392. /// Sets the flag of the item with the matching itemId.
  393. bool setItemSelected(S32 itemId, bool select);
  394. bool setItemExpanded(S32 itemId, bool expand);
  395. bool setItemValue(S32 itemId, StringTableEntry Value);
  396. const char * getItemText(S32 itemId);
  397. const char * getItemValue(S32 itemId);
  398. StringTableEntry getTextToRoot(S32 itemId, const char *delimiter = "");
  399. Item* getRootItem() const { return mRoot; }
  400. Item * getItem(S32 itemId) const;
  401. Item * createItem(S32 icon);
  402. bool editItem( S32 itemId, const char* newText, const char* newValue );
  403. bool markItem( S32 itemId, bool mark );
  404. bool isItemSelected( S32 itemId );
  405. // insertion/removal
  406. void unlinkItem(Item * item);
  407. S32 insertItem(S32 parentId, const char * text, const char * value = "", const char * iconString = "", S16 normalImage = 0, S16 expandedImage = 1);
  408. bool removeItem(S32 itemId, bool deleteObjects=true);
  409. void removeAllChildren(S32 itemId); // Remove all children of the given item
  410. bool buildIconTable(const char * icons);
  411. bool setAddGroup(SimObject * obj);
  412. S32 getIcon(const char * iconString);
  413. // tree items
  414. const S32 getFirstRootItem() const;
  415. S32 getChildItem(S32 itemId);
  416. S32 getParentItem(S32 itemId);
  417. S32 getNextSiblingItem(S32 itemId);
  418. S32 getPrevSiblingItem(S32 itemId);
  419. S32 getItemCount();
  420. S32 getSelectedItem();
  421. S32 getSelectedItem(S32 index); // Given an item's index in the selection list, return its itemId
  422. S32 getSelectedItemsCount() {return mSelectedItems.size();} // Returns the number of selected items
  423. void moveItemUp( S32 itemId );
  424. void moveItemDown( S32 itemId );
  425. // misc.
  426. bool scrollVisible( Item *item );
  427. bool scrollVisible( S32 itemId );
  428. bool scrollVisibleByObjectId( S32 objID );
  429. void deleteSelection();
  430. void clearSelection();
  431. S32 findItemByName(const char *name);
  432. S32 findItemByValue(const char *name);
  433. S32 findItemByObjectId(S32 iObjId);
  434. void sortTree( bool caseSensitive, bool traverseHierarchy, bool parentsFirst );
  435. /// @name Filtering
  436. /// @{
  437. /// Get the current filter expression. Only tree items whose text matches this expression
  438. /// are displayed. By default, the expression is empty and all items are shown.
  439. const String& getFilterText() const { return mFilterText; }
  440. /// Set the pattern by which to filter items in the tree. Only items in the tree whose text
  441. /// matches this pattern are displayed.
  442. void setFilterText( const String& text );
  443. /// Clear the current item filtering pattern.
  444. void clearFilterText() { setFilterText( String::EmptyString ); }
  445. /// @}
  446. // GuiControl
  447. bool onAdd();
  448. bool onWake();
  449. void onSleep();
  450. void onPreRender();
  451. bool onKeyDown( const GuiEvent &event );
  452. void onMouseDown(const GuiEvent &event);
  453. void onMiddleMouseDown(const GuiEvent &event);
  454. void onMouseMove(const GuiEvent &event);
  455. void onMouseEnter(const GuiEvent &event);
  456. void onMouseLeave(const GuiEvent &event);
  457. void onRightMouseDown(const GuiEvent &event);
  458. void onRightMouseUp(const GuiEvent &event);
  459. void onMouseDragged(const GuiEvent &event);
  460. virtual void onMouseUp(const GuiEvent &event);
  461. /// Returns false if the object is a child of one of the inner items.
  462. bool childSearch(Item * item, SimObject *obj, bool yourBaby);
  463. /// Find immediately available inspector items (eg ones that aren't children of other inspector items)
  464. /// and then update their sets
  465. void inspectorSearch(Item * item, Item * parent, SimSet * parentSet, SimSet * newParentSet);
  466. /// Find the Item associated with a sceneObject, returns true if it found one
  467. bool objectSearch( const SimObject *object, Item **item );
  468. // GuiArrayCtrl
  469. void onRenderCell(Point2I offset, Point2I cell, bool, bool);
  470. void onRender(Point2I offset, const RectI &updateRect);
  471. bool renderTooltip( const Point2I &hoverPos, const Point2I& cursorPos, const char* tipText );
  472. static void initPersistFields();
  473. void inspectObject(SimObject * obj, bool okToEdit);
  474. void buildVisibleTree(bool bForceFullUpdate = false);
  475. void cancelRename();
  476. void onRenameValidate();
  477. void showItemRenameCtrl( Item* item );
  478. DECLARE_CONOBJECT(GuiTreeViewCtrl);
  479. DECLARE_CATEGORY( "Gui Lists" );
  480. DECLARE_DESCRIPTION( "Hierarchical list of text items with optional icons.\nCan also be used to inspect SimObject hierarchies." );
  481. };
  482. #endif