guiTreeViewCtrl.h
Engine/source/gui/controls/guiTreeViewCtrl.h
Classes:
class
Detailed Description
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#ifndef _GUI_TREEVIEWCTRL_H 25#define _GUI_TREEVIEWCTRL_H 26 27#include "core/bitSet.h" 28#include "math/mRect.h" 29#include "gfx/gFont.h" 30#include "gui/core/guiControl.h" 31#include "gui/core/guiArrayCtrl.h" 32 33 34class GuiTextEditCtrl; 35 36//------------------------------------------------------------------------------ 37 38class GuiTreeViewCtrl : public GuiArrayCtrl 39{ 40 private: 41 typedef GuiArrayCtrl Parent; 42 43 public: 44 /// @section GuiControl_Intro Introduction 45 /// @nosubgrouping 46 47 /// 48 class Item 49 { 50 public: 51 52 enum ItemState 53 { 54 Selected = BIT( 0 ), 55 Expanded = BIT( 1 ), 56 Marked = BIT( 2 ), ///< Marked items are drawn with a border around them. This is 57 /// different than "Selected" because it can only be set by script. 58 Filtered = BIT( 3 ), ///< Whether the item is currently filtered out. 59 MouseOverBmp = BIT( 4 ), 60 MouseOverText = BIT( 5 ), 61 MouseOverIcon = BIT( 6 ), 62 InspectorData = BIT( 7 ), ///< Set if we're representing some inspector 63 /// info (ie, use mInspectorInfo, not mScriptInfo) 64 65 VirtualParent = BIT( 8 ), ///< This indicates that we should be rendered as 66 /// a parent even though we don't have any children. 67 /// This is useful for preventing scenarios where 68 /// we might want to create thousands of 69 /// Items that might never be shown (for instance 70 /// if we're browsing the object hierarchy in 71 /// Torque, which might have thousands of objects). 72 73 RebuildVisited = BIT( 9 ), ///< Rebuild traversal for virtual parents has visited and validated this item. 74 75 ShowObjectId = BIT( 10 ), 76 ShowClassName = BIT( 11 ), 77 ShowObjectName = BIT( 12 ), 78 ShowInternalName = BIT( 13 ), 79 ShowClassNameForUnnamed = BIT( 14 ), 80 ForceItemName = BIT(15), 81 ForceDragTarget = BIT(16), 82 DenyDrag = BIT(17), 83 }; 84 85 GuiTreeViewCtrl* mParentControl; 86 BitSet32 mState; 87 SimObjectPtr< GuiControlProfile> mProfile; 88 S16 mId; 89 U16 mTabLevel; 90 Item* mParent; 91 Item* mChild; 92 Item* mNext; 93 Item* mPrevious; 94 String mTooltip; 95 S32 mIcon; //stores the icon that will represent the item in the tree 96 S32 mDataRenderWidth; /// this stores the pixel width needed 97 /// to render the item's data in the 98 /// onRenderCell function to optimize 99 /// for speed. 100 101 Item( GuiTreeViewCtrl* parent, GuiControlProfile *pProfile ); 102 ~Item(); 103 104 struct ScriptTag 105 { 106 S8 mNormalImage; 107 S8 mExpandedImage; 108 StringTableEntry mText; 109 StringTableEntry mValue; 110 } mScriptInfo; 111 struct InspectorTag 112 { 113 SimObjectPtr<SimObject> mObject; 114 } mInspectorInfo; 115 116 /// @name Get Methods 117 /// @{ 118 119 /// 120 S8 getNormalImage() const; 121 S8 getExpandedImage() const; 122 StringTableEntry getText(); 123 StringTableEntry getValue(); 124 inline const S16 getID() const { return mId; }; 125 SimObject *getObject(); 126 U32 getDisplayTextLength(); 127 S32 getDisplayTextWidth(GFont *font); 128 void getDisplayText(U32 bufLen, char *buf); 129 bool hasObjectBasedTooltip(); 130 void getTooltipText(U32 bufLen, char *buf); 131 132 /// @} 133 134 135 /// @name Set Methods 136 /// @{ 137 138 /// Set whether an item is expanded or not (showing children or having them hidden) 139 void setExpanded( const bool f = true ); 140 /// Set the image to display when an item IS expanded 141 void setExpandedImage(const S8 id); 142 /// Set the image to display when an item is NOT expanded 143 void setNormalImage(const S8 id); 144 /// Assign a SimObject pointer to an inspector data item 145 void setObject(SimObject *obj); 146 /// Set the items displayable text (caption) 147 void setText(StringTableEntry txt); 148 /// Set the items script value (data) 149 void setValue(StringTableEntry val); 150 /// Set the items virtual parent flag 151 void setVirtualParent( bool value ); 152 /// Set whether the item is filtered out or not. 153 void setFiltered( bool value ) { mState.set( Filtered ); } 154 155 /// @} 156 157 158 /// @name State Retrieval 159 /// @{ 160 161 /// Returns true if this item is expanded. For 162 /// inspector objects, the expansion is stored 163 /// on the SimObject, for other things we use our 164 /// bit vector. 165 bool isExpanded() const; 166 167 /// Return whether the item is current filtered out or not. 168 /// @note Parent items may be filtered and yet still be visible if they have 169 /// children that are not filtered. 170 bool isFiltered() const { return mState.test( Filtered ); } 171 172 /// Returns true if an item is inspector data 173 /// or false if it's just an item. 174 bool isInspectorData() const { return mState.test(InspectorData); }; 175 176 /// Returns true if we've been manually set to allow dragging overrides. 177 /// As it's a manually set flag, by default it is false. 178 bool isDragTargetAllowed() const { return mState.test(ForceDragTarget); }; 179 180 /// Returns true if we've been manually set to allow dragging overrides. 181 /// As it's a manually set flag, by default it is false. 182 bool isDragAllowed() const { return !mState.test(DenyDrag); }; 183 184 /// Returns true if we should show the expand art 185 /// and make the item interact with the mouse as if 186 /// it were a parent. 187 bool isParent() const; 188 189 /// Return true if text label for inspector item should include internal name only. 190 bool showInternalNameOnly() const { return mState.test( ShowInternalName ) && !mState.test( ShowObjectName | ShowClassName | ShowObjectId ); } 191 192 /// Return true if text label for inspector item should include object name only. 193 bool showObjectNameOnly() const { return mState.test( ShowObjectName ) && !mState.test( ShowInternalName | ShowClassName | ShowObjectId ); } 194 195 /// @} 196 197 /// @name Searching Methods 198 /// @{ 199 200 /// Find a regular data item by it's script name. 201 Item* findChildByName( const char* name ); 202 203 /// Find an inspector data item by it's SimObject pointer 204 Item* findChildByValue(const SimObject *obj); 205 206 /// Find a regular data item by it's script value 207 Item* findChildByValue(StringTableEntry Value); 208 209 /// @} 210 211 /// Sort the childs of the item by their text. 212 /// 213 /// @param caseSensitive If true, sorting is case-sensitive. 214 /// @param traverseHierarchy If true, also triggers a sort() on all child items. 215 /// @param parentsFirst If true, parents are grouped before children in the resulting sort. 216 void sort( bool caseSensitive = true, bool traverseHierarchy = false, bool parentsFirst = false ); 217 218 private: 219 void _connectMonitors(); 220 void _disconnectMonitors(); 221 }; 222 223 friend class Item; // _onInspectorSetObjectModified 224 225 /// @name Enums 226 /// @{ 227 228 /// 229 enum TreeState 230 { 231 RebuildVisible = BIT(0), ///< Temporary flag, we have to rebuild the tree. 232 IsInspector = BIT(1), ///< We are mapping a SimObject hierarchy. 233 IsEditable = BIT(2), ///< We allow items to be moved around. 234 ShowTreeLines = BIT(3), ///< Should we render tree lines or just icons? 235 BuildingVisTree = BIT(4), ///< We are currently building the visible tree (prevent recursion) 236 }; 237 238 protected: 239 240 enum 241 { 242 MaxIcons = 32, 243 }; 244 245 enum Icons 246 { 247 Default1 = 0, 248 SimGroup1, 249 SimGroup2, 250 SimGroup3, 251 SimGroup4, 252 Hidden, 253 Lock1, 254 Lock2, 255 Default, 256 Icon31, 257 Icon32 258 }; 259 260 enum mDragMidPointFlags 261 { 262 NomDragMidPoint, 263 AbovemDragMidPoint, 264 BelowmDragMidPoint 265 }; 266 267 /// 268 enum HitFlags 269 { 270 OnIndent = BIT(0), 271 OnImage = BIT(1), 272 OnIcon = BIT(2), 273 OnText = BIT(3), 274 OnRow = BIT(4), 275 }; 276 277 /// 278 enum BmpIndices 279 { 280 BmpFirstChild, 281 BmpLastChild, 282 BmpChild, 283 BmpExp, 284 BmpExpN, 285 BmpExpP, 286 BmpExpPN, 287 BmpCon, 288 BmpConN, 289 BmpConP, 290 BmpConPN, 291 BmpLine, 292 BmpGlow, 293 }; 294 295 /// @} 296 297 /// @name Callbacks 298 /// @{ 299 300 DECLARE_CALLBACK( bool, onDeleteObject, ( SimObject* object ) ); 301 DECLARE_CALLBACK( bool, isValidDragTarget, ( S32 id, const char* value ) ); 302 DECLARE_CALLBACK( void, onDefineIcons, () ); 303 DECLARE_CALLBACK( void, onAddGroupSelected, ( SimGroup* group ) ); 304 DECLARE_CALLBACK( void, onAddSelection, ( S32 itemOrObjectId, bool isLastSelection ) ); 305 DECLARE_CALLBACK( void, onSelect, ( S32 itemOrObjectId ) ); 306 DECLARE_CALLBACK( void, onInspect, ( S32 itemOrObjectId ) ); 307 DECLARE_CALLBACK( void, onRemoveSelection, ( S32 itemOrObjectId ) ); 308 DECLARE_CALLBACK( void, onUnselect, ( S32 itemOrObjectId ) ); 309 DECLARE_CALLBACK( void, onDeleteSelection, () ); 310 DECLARE_CALLBACK( void, onObjectDeleteCompleted, () ); 311 DECLARE_CALLBACK( void, onKeyDown, ( S32 modifier, S32 keyCode ) ); 312 DECLARE_CALLBACK( void, onMouseUp, ( S32 hitItemId, S32 mouseClickCount ) ); 313 DECLARE_CALLBACK( void, onMouseDragged, () ); 314 DECLARE_CALLBACK( void, onRightMouseDown, ( S32 itemId, const Point2I& mousePos, SimObject* object = NULL ) ); 315 DECLARE_CALLBACK( void, onRightMouseUp, ( S32 itemId, const Point2I& mousePos, SimObject* object = NULL ) ); 316 DECLARE_CALLBACK( void, onBeginReparenting, () ); 317 DECLARE_CALLBACK( void, onEndReparenting, () ); 318 DECLARE_CALLBACK( void, onReparent, ( S32 itemOrObjectId, S32 oldParentItemOrObjectId, S32 newParentItemOrObjectId ) ); 319 DECLARE_CALLBACK( void, onDragDropped, () ); 320 DECLARE_CALLBACK( void, onAddMultipleSelectionBegin, () ); 321 DECLARE_CALLBACK( void, onAddMultipleSelectionEnd, () ); 322 DECLARE_CALLBACK( bool, canRenameObject, ( SimObject* object ) ); 323 DECLARE_CALLBACK( bool, handleRenameObject, ( const char* newName, SimObject* object ) ); 324 DECLARE_CALLBACK( void, onClearSelection, () ); 325 326 /// @} 327 328 /// 329 Vector<Item*> mItems; 330 Vector<Item*> mVisibleItems; 331 Vector<Item*> mSelectedItems; 332 333 /// Used for tracking stuff that was selected, but may not have been 334 /// created at time of selection. 335 Vector<S32> mSelected; 336 337 S32 mItemCount; 338 339 /// We do our own free list, as we we want to be able to recycle 340 /// item ids and do some other clever things. 341 Item* mItemFreeList; 342 343 Item* mRoot; 344 S32 mMaxWidth; 345 S32 mSelectedItem; 346 S32 mDraggedToItem; 347 S32 mStart; 348 349 /// A combination of TreeState flags. 350 BitSet32 mFlags; 351 352 Item* mPossibleRenameItem; 353 Item* mRenamingItem; 354 Item* mTempItem; 355 GuiTextEditCtrl* mRenameCtrl; 356 357 /// Current filter that determines which items in the tree are displayed and which are hidden. 358 String mFilterText; 359 360 /// If true, all items are filtered. If false, then children of items that successfully pass filter are not filtered 361 bool mDoFilterChildren; 362 363 Vector<U32> mItemFilterExceptionList; 364 Vector<U32> mHiddenItemsList; 365 366 /// If true, a trace of actions taken by the control is logged to the console. Can 367 /// be turned on with the setDebug() script method. 368 bool mDebug; 369 370 GFXTexHandle mIconTable[MaxIcons]; 371 372 S32 mTabSize; 373 S32 mTextOffset; 374 bool mFullRowSelect; 375 S32 mItemHeight; 376 bool mDestroyOnSleep; 377 bool mSupportMouseDragging; 378 bool mMultipleSelections; 379 bool mDeleteObjectAllowed; 380 bool mDragToItemAllowed; 381 bool mClearAllOnSingleSelection; ///< When clicking on an already selected item, clear all other selections 382 bool mCompareToObjectID; 383 384 /// Used to hide the root tree element, defaults to true. 385 bool mShowRoot; 386 387 /// If true, object IDs will be included in inspector tree item labels. 388 bool mShowObjectIds; 389 390 /// If true, class names will be included in inspector tree item labels. 391 bool mShowClassNames; 392 393 /// If true, object names will be included in inspector tree item labels. 394 bool mShowObjectNames; 395 396 /// If true, internal names will be included in inspector tree item labels. 397 bool mShowInternalNames; 398 399 /// If true, class names will be used as object names for unnamed objects. 400 bool mShowClassNameForUnnamedObjects; 401 402 /// If true then tooltips will be automatically 403 /// generated for all Inspector items 404 bool mUseInspectorTooltips; 405 406 /// If true then only render item tooltips if the item 407 /// extends past the displayable width 408 bool mTooltipOnWidthOnly; 409 410 /// If true clicking on a selected item ( that is an object ) 411 /// will allow you to rename it. 412 bool mCanRenameObjects; 413 414 /// If true then object renaming operates on the internalName rather than 415 /// the object name. 416 bool mRenameInternal; 417 418 S32 mCurrentDragCell; 419 S32 mPreviousDragCell; 420 S32 mDragMidPoint; 421 bool mMouseDragged; 422 bool mDragStartInSelection; 423 Point2I mMouseDownPoint; 424 425 StringTableEntry mBitmapBase; 426 GFXTexHandle mTexRollover; 427 GFXTexHandle mTexSelected; 428 429 ColorI mAltFontColor; 430 ColorI mAltFontColorHL; 431 ColorI mAltFontColorSE; 432 433 SimObjectPtr<SimObject> mRootObject; 434 435 void _destroyChildren( Item* item, Item* parent, bool deleteObjects=true); 436 void _destroyItem( Item* item, bool deleteObject=true); 437 void _destroyTree(); 438 439 void _deleteItem(Item* item); 440 441 void _buildItem(Item* item, U32 tabLevel, bool bForceFullUpdate = false, bool skipFlter = false); 442 443 Item* _findItemByAmbiguousId( S32 itemOrObjectId, bool buildVirtual = true ); 444 445 void _expandObjectHierarchy( SimGroup* group ); 446 447 bool _hitTest(const Point2I & pnt, Item* & item, BitSet32 & flags); 448 449 S32 getInspectorItemIconsWidth(Item* & item); 450 451 virtual bool onVirtualParentBuild(Item *item, bool bForceFullUpdate = false); 452 virtual bool onVirtualParentExpand(Item *item); 453 virtual bool onVirtualParentCollapse(Item *item); 454 virtual void onItemSelected( Item *item ); 455 virtual void onRemoveSelection( Item *item ); 456 virtual void onClearSelection() {}; 457 458 Item* addInspectorDataItem(Item *parent, SimObject *obj); 459 460 virtual bool isValidDragTarget( Item* item ); 461 462 bool _isRootLevelItem( Item* item ) const 463 { 464 return ( item == mRoot && mShowRoot ) || ( item->mParent == mRoot && !mShowRoot ); 465 } 466 467 /// For inspector tree views, this is hooked to the SetModificationSignal of sets 468 /// so that the tree view knows when it needs to refresh. 469 void _onInspectorSetObjectModified( SetModification modification, SimSet* set, SimObject* object ); 470 471 public: 472 GuiTreeViewCtrl(); 473 virtual ~GuiTreeViewCtrl(); 474 475 //WLE Vince, Moving this into a function so I don't have to bounce off the console. 12/05/2013 476 const char* getSelectedObjectList(); 477 478 /// Used for syncing the mSelected and mSelectedItems lists. 479 void syncSelection(); 480 481 void lockSelection(bool lock); 482 void hideSelection(bool hide); 483 void toggleLockSelection(); 484 void toggleHideSelection(); 485 virtual bool canAddSelection( Item *item ) { return true; }; 486 void addSelection(S32 itemId, bool update = true, bool isLastSelection = true); 487 const Vector< Item*>& getSelectedItems() const { return mSelectedItems; } 488 const Vector< S32>& getSelected() const { return mSelected; } 489 490 const Vector< Item*>& getItems() const { return mItems; } 491 492 bool isSelected(S32 itemId) 493 { 494 return isSelected( getItem( itemId ) ); 495 } 496 bool isSelected(Item *item) 497 { 498 if ( !item ) 499 return false; 500 return mSelectedItems.contains( item ); 501 } 502 503 void setDebug( bool value ) { mDebug = value; } 504 505 /// Should use addSelection and removeSelection when calling from script 506 /// instead of setItemSelected. Use setItemSelected when you want to select 507 /// something in the treeview as it has script call backs. 508 void removeSelection(S32 itemId); 509 510 /// Sets the flag of the item with the matching itemId. 511 bool setItemSelected(S32 itemId, bool select); 512 bool setItemExpanded(S32 itemId, bool expand); 513 bool setItemValue(S32 itemId, StringTableEntry Value); 514 515 const char * getItemText(S32 itemId); 516 const char * getItemValue(S32 itemId); 517 StringTableEntry getTextToRoot(S32 itemId, const char *delimiter = ""); 518 519 Item* getRootItem() const { return mRoot; } 520 Item * getItem(S32 itemId) const; 521 Item * createItem(S32 icon); 522 bool editItem( S32 itemId, const char* newText, const char* newValue ); 523 524 bool markItem( S32 itemId, bool mark ); 525 526 S32 getItemAtPosition(Point2I position); 527 528 bool isItemSelected( S32 itemId ); 529 530 // insertion/removal 531 void unlinkItem(Item * item); 532 S32 insertItem(S32 parentId, const char * text, const char * value = "", const char * iconString = "", S16 normalImage = 0, S16 expandedImage = 1); 533 bool removeItem(S32 itemId, bool deleteObjects=true); 534 void removeAllChildren(S32 itemId); // Remove all children of the given item 535 536 bool buildIconTable(const char * icons); 537 538 bool setAddGroup(SimObject * obj); 539 540 S32 getIcon(const char * iconString); 541 542 // tree items 543 const S32 getFirstRootItem() const; 544 S32 getChildItem(S32 itemId); 545 S32 getParentItem(S32 itemId); 546 S32 getNextSiblingItem(S32 itemId); 547 S32 getPrevSiblingItem(S32 itemId); 548 S32 getItemCount(); 549 S32 getSelectedItem(); 550 S32 getSelectedItem(S32 index); // Given an item's index in the selection list, return its itemId 551 S32 getSelectedItemsCount() {return mSelectedItems.size();} // Returns the number of selected items 552 void moveItemUp( S32 itemId ); 553 void moveItemDown( S32 itemId ); 554 555 // misc. 556 bool scrollVisible( Item *item ); 557 bool scrollVisible( S32 itemId ); 558 bool scrollVisibleByObjectId( S32 objID ); 559 560 void deleteSelection(); 561 void clearSelection(); 562 563 S32 findItemByName(const char *name); 564 S32 findItemByValue(const char *name); 565 S32 findItemByObjectId(S32 iObjId); 566 S32 getItemObject(S32 itemId); 567 568 void sortTree( bool caseSensitive, bool traverseHierarchy, bool parentsFirst ); 569 570 /// @name Filtering 571 /// @{ 572 573 /// Get the current filter expression. Only tree items whose text matches this expression 574 /// are displayed. By default, the expression is empty and all items are shown. 575 const String& getFilterText() const { return mFilterText; } 576 577 /// Set the pattern by which to filter items in the tree. Only items in the tree whose text 578 /// matches this pattern are displayed. 579 void setFilterText( const String& text ); 580 581 void setFilterChildren(bool doFilter) { mDoFilterChildren = doFilter; } 582 void setItemFilterException(U32 item, bool isExempt); 583 void setItemHidden(U32 item, bool isHidden); 584 void clearHiddenItems() { mHiddenItemsList.clear(); } 585 586 /// Clear the current item filtering pattern. 587 void clearFilterText() { setFilterText( String::EmptyString ); } 588 589 void reparentItems(Vector<Item*> selectedItems, Item* newParent); 590 591 S32 getTabLevel(S32 itemId); 592 593 /// @} 594 595 // GuiControl 596 bool onAdd(); 597 bool onWake(); 598 void onSleep(); 599 void onPreRender(); 600 bool onKeyDown( const GuiEvent &event ); 601 void onMouseDown(const GuiEvent &event); 602 void onMiddleMouseDown(const GuiEvent &event); 603 void onMouseMove(const GuiEvent &event); 604 void onMouseEnter(const GuiEvent &event); 605 void onMouseLeave(const GuiEvent &event); 606 void onRightMouseDown(const GuiEvent &event); 607 void onRightMouseUp(const GuiEvent &event); 608 void onMouseDragged(const GuiEvent &event); 609 virtual void onMouseUp(const GuiEvent &event); 610 611 /// Returns false if the object is a child of one of the inner items. 612 bool childSearch(Item * item, SimObject *obj, bool yourBaby); 613 614 /// Find immediately available inspector items (eg ones that aren't children of other inspector items) 615 /// and then update their sets 616 void inspectorSearch(Item * item, Item * parent, SimSet * parentSet, SimSet * newParentSet); 617 618 /// Find the Item associated with a sceneObject, returns true if it found one 619 bool objectSearch( const SimObject *object, Item **item ); 620 621 // GuiArrayCtrl 622 void onRenderCell(Point2I offset, Point2I cell, bool, bool); 623 void onRender(Point2I offset, const RectI &updateRect); 624 625 bool renderTooltip( const Point2I &hoverPos, const Point2I& cursorPos, const char* tipText ); 626 627 static void initPersistFields(); 628 629 void inspectObject(SimObject * obj, bool okToEdit); 630 S32 insertObject(S32 parentId, SimObject * obj, bool okToEdit); 631 void buildVisibleTree(bool bForceFullUpdate = false); 632 633 void cancelRename(); 634 void onRenameValidate(); 635 void showItemRenameCtrl( Item* item ); 636 637 DECLARE_CONOBJECT(GuiTreeViewCtrl); 638 DECLARE_CATEGORY( "Gui Lists" ); 639 DECLARE_DESCRIPTION( "Hierarchical list of text items with optional icons.\nCan also be used to inspect SimObject hierarchies." ); 640}; 641 642#endif 643