//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- #include "core/frameAllocator.h" #include "gui/controls/guiTreeViewCtrl.h" #include "gui/containers/guiScrollCtrl.h" #include "console/consoleTypes.h" #include "console/console.h" #include "dgl/dgl.h" #include "gui/core/guiTypes.h" #include "platform/event.h" IMPLEMENT_CONOBJECT(GuiTreeViewCtrl); //----------------------------------------------------------------------------- // TreeView Item //----------------------------------------------------------------------------- GuiTreeViewCtrl::Item::Item( GuiControlProfile *pProfile ) { AssertFatal( pProfile != NULL , "Cannot create a tree item without a valid tree and control profile!"); mState = 0; mId = -1; mTabLevel = 0; mIcon = 0; mDataRenderWidth = 0; mScriptInfo.mText = NULL; mScriptInfo.mValue = NULL; mParent = NULL; mChild = NULL; mNext = NULL; mPrevious = NULL; mProfile = pProfile; } GuiTreeViewCtrl::Item::~Item() { if( ! mState.test(InspectorData) ) { if ( getText() ) { delete [] getText(); setText(NULL); } if ( getValue() ) { delete [] getValue(); setValue( NULL ); } } } void GuiTreeViewCtrl::Item::setNormalImage(S8 id) { if(mState.test(InspectorData)) { Con::errorf("Tried to set normal image %d for item %d, which is InspectorData!", id, mId); return; } mScriptInfo.mNormalImage = id; } void GuiTreeViewCtrl::Item::setExpandedImage(S8 id) { if(mState.test(InspectorData)) { Con::errorf("Tried to set expanded image %d for item %d, which is InspectorData!", id, mId); return; } mScriptInfo.mExpandedImage = id; } void GuiTreeViewCtrl::Item::setText(char *txt) { if(mState.test(InspectorData)) { Con::errorf("Tried to set text for item %d, which is InspectorData!", mId); return; } mScriptInfo.mText = txt; // Update Render Data if( !mProfile.isNull() ) mDataRenderWidth = getDisplayTextWidth( mProfile->mFont ); } void GuiTreeViewCtrl::Item::setValue(const char *val) { if(mState.test(InspectorData)) { Con::errorf("Tried to set value for item %d, which is InspectorData!", mId); return; } mScriptInfo.mValue = const_cast(val); // mValue really ought to be a StringTableEntry // Update Render Data if( !mProfile.isNull() ) mDataRenderWidth = getDisplayTextWidth( mProfile->mFont ); } const S8 GuiTreeViewCtrl::Item::getNormalImage() const { if(mState.test(InspectorData)) { Con::errorf("Tried to get the normal image for item %d, which is InspectorData!", mId); return 0; // fail safe for width determinations } return mScriptInfo.mNormalImage; } const S8 GuiTreeViewCtrl::Item::getExpandedImage() const { if(mState.test(InspectorData)) { Con::errorf("Tried to get the expanded image for item %d, which is InspectorData!", mId); return 0; // fail safe for width determinations } return mScriptInfo.mExpandedImage; } char *GuiTreeViewCtrl::Item::getText() { if(mState.test(InspectorData)) { Con::errorf("Tried to get the text for item %d, which is InspectorData!", mId); return NULL; } return mScriptInfo.mText; } char *GuiTreeViewCtrl::Item::getValue() { if(mState.test(InspectorData)) { Con::errorf("Tried to get the value for item %d, which is InspectorData!", mId); return NULL; } return mScriptInfo.mValue; } void GuiTreeViewCtrl::Item::setObject(SimObject *obj) { if(!mState.test(InspectorData)) { Con::errorf("Tried to set the object for item %d, which is not InspectorData!", mId); return; } mInspectorInfo.mObject = obj; // Update Render Data if( !mProfile.isNull() ) mDataRenderWidth = getDisplayTextWidth( mProfile->mFont ); } SimObject *GuiTreeViewCtrl::Item::getObject() { if(!mState.test(InspectorData)) { Con::errorf("Tried to get the object for item %d, which is not InspectorData!", mId); return NULL; } return mInspectorInfo.mObject; } const U32 GuiTreeViewCtrl::Item::getDisplayTextLength() { if(mState.test(Item::InspectorData)) { SimObject *obj = getObject(); if(!obj) return 0; // For the results buffer, it's prefix along with a bunch of other stuff. // So we'll be mostly accurate and add a bit of fudge. return (16 + (obj->getName() ? dStrlen(obj->getName()) : 0) + dStrlen(obj->getClassName()) + dStrlen(obj->getIdString()) + 20 ); } char *pText = getText(); if( pText == NULL ) return 0; return dStrlen( pText ) + 1; } void GuiTreeViewCtrl::Item::getDisplayText(U32 bufLen, char *buf) { FrameAllocatorMarker txtAlloc; char *displayText = NULL; if(mState.test(Item::InspectorData)) { // Inspector data! SimObject *pObject = getObject(); if(pObject) { const char* pObjName = pObject->getName(); const char* pInternalName = pObject->getInternalName(); if( pObjName != NULL ) dSprintf(buf, bufLen, "%d: %s - %s", pObject->getId(), pObject->getClassName(), pObjName ); else if ( pInternalName != NULL ) dSprintf(buf, bufLen, "%d: %s [%s]", pObject->getId(), pObject->getClassName(), pInternalName); else dSprintf(buf, bufLen, "%d: %s", pObject->getId(), pObject->getClassName()); } } else { // Script data! (copy it in) dStrncpy(buf, getText(), bufLen); } } const S32 GuiTreeViewCtrl::Item::getDisplayTextWidth(GFont *font) { if( !font ) return 0; FrameAllocatorMarker txtAlloc; U32 bufLen = getDisplayTextLength(); if( bufLen == 0 ) return 0; char *buf = (char*)txtAlloc.alloc(bufLen); getDisplayText(bufLen, buf); return font->getStrWidth(buf); } const bool GuiTreeViewCtrl::Item::isParent() const { if(mState.test(VirtualParent)) { if( !isInspectorData() ) return true; // Does our object have any children? if(mInspectorInfo.mObject) { SimSet *pSimSet = dynamic_cast( (SimObject*)mInspectorInfo.mObject); if ( pSimSet != NULL && pSimSet->size() > 0) return pSimSet->size(); } } // Otherwise, just return whether the child list is populated. return mChild; } const bool GuiTreeViewCtrl::Item::isExpanded() const { if(mState.test(InspectorData)) return mInspectorInfo.mObject ? mInspectorInfo.mObject->isExpanded() : false; else return mState.test(Expanded); } void GuiTreeViewCtrl::Item::setExpanded(bool f) { if(mState.test(InspectorData) && !mInspectorInfo.mObject.isNull() ) mInspectorInfo.mObject->setExpanded(f); else mState.set(Expanded, f); } void GuiTreeViewCtrl::Item::setVirtualParent( bool value ) { mState.set(VirtualParent, value); } GuiTreeViewCtrl::Item *GuiTreeViewCtrl::Item::findChildByValue(const SimObject *obj) { // Iterate over our children and try to find the given // SimObject Item *pResultObj = mChild; while(pResultObj) { // Skip non-inspector data stuff. if(pResultObj->mState.test(InspectorData)) { if(pResultObj->getObject() == obj) break; // Whoa. } pResultObj = pResultObj->mNext; } // If the loop terminated we are NULL, otherwise we have the result in res. return pResultObj; } GuiTreeViewCtrl::Item *GuiTreeViewCtrl::Item::findChildByValue( StringTableEntry Value ) { // Iterate over our children and try to find the given Value // Note : This is a case-insensitive search Item *pResultObj = mChild; while(pResultObj) { // check the script value of the item against the specified value if( pResultObj->mScriptInfo.mValue != NULL && dStricmp( pResultObj->mScriptInfo.mValue, Value ) == 0 ) return pResultObj; pResultObj = pResultObj->mNext; } // If the loop terminated we didn't find an item with the specified script value return NULL; } //------------------------------------------------------------------------------ GuiTreeViewCtrl::GuiTreeViewCtrl() { VECTOR_SET_ASSOCIATION(mItems); VECTOR_SET_ASSOCIATION(mVisibleItems); VECTOR_SET_ASSOCIATION(mSelectedItems); VECTOR_SET_ASSOCIATION(mSelected); mItemFreeList = NULL; mRoot = NULL; mInstantGroup = 0; mItemCount = 0; mSelectedItem = 0; mStart = 0; mTicksPassed = 0; mTreeRefreshInterval = 30; mDraggedToItem = 0; mOldDragY = 0; mCurrentDragCell = 0; mPreviousDragCell = 0; mDragMidPoint = NomDragMidPoint; mMouseDragged = false; mDebug = false; // persist info.. mTabSize = 16; mTextOffset = 2; mFullRowSelect = false; mItemHeight = 20; // setSize(Point2I(1, 0)); // Set up default state mFlags.set(ShowTreeLines); mFlags.set(IsEditable, false); mDestroyOnSleep = true; mSupportMouseDragging = true; mMultipleSelections = true; mDeleteObjectAllowed = true; mDragToItemAllowed = true; mBitmapBase = StringTable->insert(""); mTexRollover = NULL; mTexSelected = NULL; } GuiTreeViewCtrl::~GuiTreeViewCtrl() { destroyTree(); } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::initPersistFields() { Parent::initPersistFields(); addGroup("TreeView"); addField("tabSize", TypeS32, Offset(mTabSize, GuiTreeViewCtrl)); addField("textOffset", TypeS32, Offset(mTextOffset, GuiTreeViewCtrl)); addField("fullRowSelect", TypeBool, Offset(mFullRowSelect, GuiTreeViewCtrl)); addField("itemHeight", TypeS32, Offset(mItemHeight, GuiTreeViewCtrl)); addField("destroyTreeOnSleep", TypeBool, Offset(mDestroyOnSleep, GuiTreeViewCtrl)); addField("MouseDragging", TypeBool, Offset(mSupportMouseDragging, GuiTreeViewCtrl)); addField("MultipleSelections", TypeBool, Offset(mMultipleSelections, GuiTreeViewCtrl)); addField("DeleteObjectAllowed", TypeBool, Offset(mDeleteObjectAllowed, GuiTreeViewCtrl)); addField("DragToItemAllowed", TypeBool, Offset(mDragToItemAllowed, GuiTreeViewCtrl)); endGroup("TreeView"); } //------------------------------------------------------------------------------ GuiTreeViewCtrl::Item * GuiTreeViewCtrl::getItem(S32 itemId) { if((itemId > 0) && (itemId <= mItems.size())) return(mItems[itemId-1]); return(0); } //------------------------------------------------------------------------------ GuiTreeViewCtrl::Item * GuiTreeViewCtrl::createItem(S32 icon) { Item * pNewItem = NULL; // grab from the free list? if( mItemFreeList ) { pNewItem = mItemFreeList; mItemFreeList = pNewItem->mNext; // re-add to vector mItems[ pNewItem->mId - 1 ] = pNewItem; } else { pNewItem = new Item( mProfile ); AssertFatal( pNewItem != NULL, "Fatal : unable to allocate tree item!"); mItems.push_back( pNewItem ); // set the id pNewItem->mId = mItems.size(); } // reset if (icon) pNewItem->mIcon = icon; else pNewItem->mIcon = Default; //default icon to stick next to an item pNewItem->mState.clear(); pNewItem->mState = 0; pNewItem->mTabLevel = 0; // Null out item pointers pNewItem->mNext = 0; pNewItem->mPrevious = 0; pNewItem->mChild = 0; pNewItem->mParent = 0; mItemCount++; return pNewItem; } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::destroyChildren(Item * item, Item * parent) { if ( !item || item == parent ) return; if ( item->isParent() && item->mChild ) destroyChildren(item->mChild, parent); else if( item->mNext ) destroyChildren(item->mNext, parent); else { // destroy the item and back up Item * pPrevItem = item->mPrevious; destroyItem( item ); destroyChildren( pPrevItem, parent ); } } void GuiTreeViewCtrl::destroyItem(Item * item) { if(!item) return; if(item->isInspectorData()) { // make sure the SimObjectPtr is clean! //Con::executef(this,2, "onDeleteObject",Con::getIntArg(item->mInspectorInfo.mObject->getIdString())); SimObject *pObject = item->getObject(); if( pObject && pObject->isProperlyAdded() ) pObject->deleteObject(); item->setObject( NULL ); } else { char *pCleanup = item->getText(); // Clean up the memory... if ( pCleanup != NULL ) { delete []pCleanup; item->setText( NULL ); } pCleanup = item->getValue(); if ( pCleanup != NULL ) { delete []pCleanup; item->setValue( NULL ); } } // unlink if( item->mPrevious ) item->mPrevious->mNext = item->mNext; if( item->mNext ) item->mNext->mPrevious = item->mPrevious; if( item->mParent && ( item->mParent->mChild == item ) ) item->mParent->mChild = item->mNext; // remove from vector mItems[item->mId-1] = 0; // set as root free item item->mNext = mItemFreeList; mItemFreeList = item; mItemCount--; } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::deleteItem(Item *item) { removeItem(item->mId); } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::destroyTree() { // clear the item list for(U32 i = 0; i < mItems.size(); i++) { Item *pFreeItem = mItems[ i ]; if( pFreeItem != NULL ) delete pFreeItem; } mItems.clear(); // clear the free list while(mItemFreeList) { Item *next = mItemFreeList->mNext; delete mItemFreeList; mItemFreeList = next; } mVisibleItems.clear(); mSelectedItems.clear(); // mRoot = NULL; mItemFreeList = NULL; mItemCount = 0; mSelectedItem = 0; mDraggedToItem = 0; } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::buildItem( Item* item, U32 tabLevel, bool bForceFullUpdate ) { if (!item || !mActive || !isVisible() || !mProfile ) return; // If it's inspector data, make sure we still have it, if not, kill it. if(item->isInspectorData() && !item->getObject() ) { removeItem(item->mId); return; } // If it's a virtual parent, give a chance to update itself... if(item->mState.test( Item::VirtualParent) ) { // If it returns false the item has been removed. if(!onVirtualParentBuild(item, bForceFullUpdate)) return; } item->mTabLevel = tabLevel; mVisibleItems.push_back( item ); if ( mProfile != NULL && !mProfile->mFont.isNull() ) { S32 width = ( tabLevel + 1 ) * mTabSize + item->getDisplayTextWidth(mProfile->mFont); if ( mProfile->mBitmapArrayRects.size() > 0 ) width += mProfile->mBitmapArrayRects[0].extent.x; width += (item->mTabLevel+1) * mItemHeight; // using mItemHeight for icon width, close enough // this will only fail if somebody starts using super wide icons. if ( width > mMaxWidth ) mMaxWidth = width; } // if expanded, then add all the children items as well if ( item->isExpanded() || bForceFullUpdate) { Item * child = item->mChild; while ( child ) { // Bit of a hack so we can safely remove items as we // traverse. Item *pChildTemp = child; child = child->mNext; buildItem( pChildTemp, tabLevel + 1, bForceFullUpdate ); } } } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::buildVisibleTree(bool bForceFullUpdate) { // Recursion Prevention. if( mFlags.test( BuildingVisTree ) ) return; mFlags.set( BuildingVisTree, true ); mMaxWidth = 0; mVisibleItems.clear(); // Update the flags. mFlags.clear(RebuildVisible); // build the root items Item *traverse = mRoot; while(traverse) { buildItem(traverse, 0, bForceFullUpdate); traverse = traverse->mNext; } // adjust the GuiArrayCtrl mCellSize.set(mMaxWidth+1, mItemHeight); setSize(Point2I(1, mVisibleItems.size())); syncSelection(); // Done Recursing. mFlags.clear( BuildingVisTree ); } //------------------------------------------------------------------------------ bool GuiTreeViewCtrl::scrollVisible( S32 itemId ) { Item* item = getItem(itemId); if(item) return scrollVisible(item); return false; } bool GuiTreeViewCtrl::scrollVisible( Item *item ) { // Now, make sure it's visible (ie, all parents expanded) Item *parent = item->mParent; if( !item->isInspectorData() && item->mState.test(Item::VirtualParent) ) onVirtualParentExpand(item); while(parent) { parent->setExpanded(true); if( !parent->isInspectorData() && parent->mState.test(Item::VirtualParent) ) onVirtualParentExpand(parent); parent = parent->mParent; } // Get our scroll-pappy, if any. GuiScrollCtrl *pScrollParent = dynamic_cast( getParent() ); if ( !pScrollParent ) { Con::warnf("GuiTreeViewCtrl::scrollVisible - parent control is not a GuiScrollCtrl!"); return false; } // And now, build the visible tree so we know where we have to scroll. buildVisibleTree(); // All done, let's figure out where we have to scroll... for(S32 i=0; iscrollRectVisible(RectI(0, i * mItemHeight, mMaxWidth, mItemHeight)); return true; } } // If we got here, it's probably bad... Con::errorf("GuiTreeViewCtrl::scrollVisible - was unable to find specified item in visible list!"); return false; } //------------------------------------------------------------------------------ S32 GuiTreeViewCtrl::insertItem(S32 parentId, const char * text, const char * value, const char * iconString, S16 normalImage, S16 expandedImage) { if( ( parentId < 0 ) || ( parentId > mItems.size() ) ) { Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::insertItem: invalid parent id!"); return 0; } if((parentId != 0) && (mItems[parentId-1] == 0)) { Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::insertItem: parent item invalid!"); return 0; } S32 icon = getIcon(iconString); // create an item (assigns id) Item * pNewItem = createItem(icon); if( pNewItem == NULL ) return 0; const char * pItemText = ( text != NULL ) ? text : ""; const char * pItemValue = ( value != NULL ) ? value : ""; char *tmp = new char[dStrlen( pItemText ) + 1]; dStrcpy( tmp, pItemText ); pNewItem->setText( tmp ); tmp = new char[dStrlen( pItemValue ) + 1]; dStrcpy( tmp, pItemValue ); pNewItem->setValue( tmp ); pNewItem->setNormalImage( normalImage ); pNewItem->setExpandedImage( expandedImage ); // root level? if(parentId == 0) { // insert back if( mRoot != NULL ) { Item * pTreeTraverse = mRoot; while( pTreeTraverse != NULL && pTreeTraverse->mNext != NULL ) pTreeTraverse = pTreeTraverse->mNext; pTreeTraverse->mNext = pNewItem; pNewItem->mPrevious = pTreeTraverse; } else mRoot = pNewItem; mFlags.set(RebuildVisible); } else if( mItems.size() >= ( parentId - 1 ) ) { Item * pParentItem = mItems[parentId-1]; // insert back if( pParentItem != NULL && pParentItem->mChild) { Item * pTreeTraverse = pParentItem->mChild; while( pTreeTraverse != NULL && pTreeTraverse->mNext != NULL ) pTreeTraverse = pTreeTraverse->mNext; pTreeTraverse->mNext = pNewItem; pNewItem->mPrevious = pTreeTraverse; } else pParentItem->mChild = pNewItem; pNewItem->mParent = pParentItem; if( pParentItem->isExpanded() ) mFlags.set(RebuildVisible); } // if(mFlags.test(RebuildVisible)) buildVisibleTree(); return pNewItem->mId; } //------------------------------------------------------------------------------ bool GuiTreeViewCtrl::removeItem(S32 itemId) { // tree? if(itemId == 0) { destroyTree(); return(true); } Item * item = getItem(itemId); if(!item) { //Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::removeItem: invalid item id!"); return false; } // root? if(item == mRoot) mRoot = item->mNext; // Dispose of any children... if (item->mChild) destroyChildren(item->mChild, item); // Kill the item... destroyItem(item); // Update the rendered tree... buildVisibleTree(); return true; } void GuiTreeViewCtrl::removeAllChildren(S32 itemId) { Item * item = getItem(itemId); if(item) { destroyChildren(item->mChild, item); } } //------------------------------------------------------------------------------ const S32 GuiTreeViewCtrl::getFirstRootItem() const { return (mRoot ? mRoot->mId : 0); } //------------------------------------------------------------------------------ S32 GuiTreeViewCtrl::getChildItem(S32 itemId) { Item * item = getItem(itemId); if(!item) { Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::getChild: invalid item id!"); return(0); } return(item->mChild ? item->mChild->mId : 0); } S32 GuiTreeViewCtrl::getParentItem(S32 itemId) { Item * item = getItem(itemId); if(!item) { Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::getParent: invalid item id!"); return(0); } return(item->mParent ? item->mParent->mId : 0); } S32 GuiTreeViewCtrl::getNextSiblingItem(S32 itemId) { Item * item = getItem(itemId); if(!item) { Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::getNextSibling: invalid item id!"); return(0); } return(item->mNext ? item->mNext->mId : 0); } S32 GuiTreeViewCtrl::getPrevSiblingItem(S32 itemId) { Item * item = getItem(itemId); if(!item) { Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::getPrevSibling: invalid item id!"); return(0); } return(item->mPrevious ? item->mPrevious->mId : 0); } //------------------------------------------------------------------------------ S32 GuiTreeViewCtrl::getItemCount() { return(mItemCount); } S32 GuiTreeViewCtrl::getSelectedItem() { return mSelectedItem; } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::moveItemUp( S32 itemId ) { GuiTreeViewCtrl::Item* pItem = getItem( itemId ); if ( !pItem ) { Con::errorf( ConsoleLogEntry::General, "GuiTreeViewCtrl::moveItemUp: invalid item id!"); return; } Item * pParent = pItem->mParent; Item * pPrevItem = pItem->mPrevious; if ( pPrevItem == NULL || pParent == NULL ) { Con::errorf( ConsoleLogEntry::General, "GuiTreeViewCtrl::moveItemUp: Unable to move item up, bad data!"); return; } // Diddle the linked list! if ( pPrevItem->mPrevious ) pPrevItem->mPrevious->mNext = pItem; else if ( pItem->mParent ) pItem->mParent->mChild = pItem; if ( pItem->mNext ) pItem->mNext->mPrevious = pPrevItem; pItem->mPrevious = pPrevItem->mPrevious; pPrevItem->mNext = pItem->mNext; pItem->mNext = pPrevItem; pPrevItem->mPrevious = pItem; // Update SimObjects if Appropriate. SimObject * pSimObject = NULL; SimSet * pParentSet = NULL; // Fetch Current Add Set if( pParent->isInspectorData() ) pParentSet = dynamic_cast( pParent->getObject() ); else { // parent is probably script data so we search up the tree for a // set to put our object in Item * pTraverse = pItem->mParent; while ( pTraverse != NULL && !pTraverse->isInspectorData() ) pTraverse = pTraverse->mParent; // found an ancestor who is an inspectorData? pParentSet = pTraverse->isInspectorData() ? dynamic_cast( pTraverse->getObject() ) : NULL; } // Reorder the item and make sure that the children of the item get updated // correctly prev item may be script... so find a prevItem if there is. // We only need to reorder if there you move it above an inspector item. if ( pSimObject != NULL && pParentSet != NULL ) { Item * pTraverse = pItem->mNext; while(pTraverse) { if (pTraverse->isInspectorData()) break; pTraverse = pTraverse->mNext; } if (pTraverse && pItem->getObject() && pTraverse->getObject()) pParentSet->reOrder(pItem->getObject(), pTraverse->getObject()); } buildVisibleTree(); } void GuiTreeViewCtrl::moveItemDown( S32 itemId ) { GuiTreeViewCtrl::Item* item = getItem( itemId ); if ( !item ) { Con::errorf( ConsoleLogEntry::General, "GuiTreeViewCtrl::moveItemDown: invalid item id!"); return; } Item* nextItem = item->mNext; if ( !nextItem ) { Con::errorf( ConsoleLogEntry::General, "GuiTreeViewCtrl::moveItemDown: no next sibling?"); return; } // Diddle the linked list! if ( nextItem->mNext ) nextItem->mNext->mPrevious = item; if ( item->mPrevious ) item->mPrevious->mNext = nextItem; else if ( item->mParent ) item->mParent->mChild = nextItem; item->mNext = nextItem->mNext; nextItem->mPrevious = item->mPrevious; item->mPrevious = nextItem; nextItem->mNext = item; // And update the simobjects if apppropriate... SimObject * simobj = NULL; if (item->isInspectorData()) simobj = item->getObject(); SimSet *parentSet = NULL; // grab the current parentSet if there is any... if(item->mParent->isInspectorData()) parentSet = dynamic_cast(item->mParent->getObject()); else { // parent is probably script data so we search up the tree for a // set to put our object in Item * temp = item->mParent; while (!temp->isInspectorData()) temp = temp->mParent; // found an ancestor who is an inspectorData? parentSet = temp->isInspectorData() ? dynamic_cast(temp->getObject()) : NULL; } // Reorder the item and make sure that the children of the item get updated // correctly prev item may be script... so find a prevItem if there is. // We only need to reorder if there you move it above an inspector item. if (simobj && parentSet) { Item * temp = item->mPrevious; while(temp) { if (temp->isInspectorData()) break; temp = temp->mPrevious; } if (temp && item->getObject() && temp->getObject()) parentSet->reOrder(temp->getObject(), item->getObject()); } buildVisibleTree(); } //------------------------------------------------------------------------------ bool GuiTreeViewCtrl::onWake() { if(!Parent::onWake() || !mProfile->constructBitmapArray()) return false; // If destroy on sleep, then we have to give things a chance to rebuild. if(mDestroyOnSleep) { destroyTree(); Con::executef(this, 1, "onWake"); // (Re)build our icon table. const char * res = Con::executef(this, 1, "onDefineIcons"); // If no icons were defined in script then use defaults. if(!(dAtob(res))) { buildIconTable(NULL); } } // Update the row height, if appropriate. if(mProfile->mAutoSizeHeight) { // make sure it's big enough for both bitmap AND font... mItemHeight = getMax((S32)mFont->getHeight(), (S32)mProfile->mBitmapArrayRects[0].extent.y); } return true; } void GuiTreeViewCtrl::onSleep() { Parent::onSleep(); // If appropriate, blast the tree. (We probably rebuild it on wake.) if( mDestroyOnSleep ) destroyTree(); } bool GuiTreeViewCtrl::buildIconTable(const char * icons) { // Icons should be designated by the bitmap/png file names (minus the file extensions) // and separated by colons (:). This list should be synchronized with the Icons enum. // This is an abominal piece of code. -- BJG if (!icons) { icons = "common/ui/default:" "common/ui/simgroup:" "common/ui/simgroup_closed:" "common/ui/simgroup_selected:" "common/ui/simgroup_selected_closed:" "common/ui/audio:" "common/ui/camera:" "common/ui/fxfoliage:" "common/ui/fxlight:" "common/ui/fxshapereplicator:" "common/ui/fxsunlight:" "common/ui/hidden:" "common/ui/interior:" "common/ui/lightning:" "common/ui/common/ui/shll_icon_passworded_hi:" "common/ui/shll_icon_passworded:" "common/ui/mission_area:" "common/ui/particle:" "common/ui/path:" "common/ui/pathmarker:" "common/ui/physical_area:" "common/ui/precipitation:" "common/ui/shape:" "common/ui/sky:" "common/ui/static_shape:" "common/ui/sun:" "common/ui/terrain:" "common/ui/trigger:" "common/ui/water:" "common/ui/default"; } // Figure the size of the buffer we need... const char* temp = dStrchr( icons, '\t' ); U32 textLen = temp ? ( temp - icons ) : dStrlen( icons ); // Allocate temporary space. FrameAllocatorMarker txtBuff; char* drawText = (char*)txtBuff.alloc(sizeof(char) * (textLen + 4)); dStrncpy( drawText, icons, textLen ); drawText[textLen] = '\0'; U32 numIcons = 0; char *buf = (char*)txtBuff.alloc(sizeof(char) * 256); char* token = dStrtok( drawText, ":" ); // Count the number of icons and store them. while (token && numIcons < MaxIcons) { dSprintf( buf, sizeof( char ) * 256, "%s", token ); mIconTable[numIcons] = TextureHandle( buf, BitmapKeepTexture ); token = dStrtok( NULL, ":" ); numIcons++; } return true; } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::onPreRender() { Parent::onPreRender(); S32 nRootItemId = getFirstRootItem(); if( nRootItemId == 0 ) return; Item *pRootItem = getItem( nRootItemId ); if( pRootItem == NULL ) return; mTicksPassed++; if( mTicksPassed > mTreeRefreshInterval ) { // Update every render in case new objects are added buildVisibleTree(); mTicksPassed = 0; } } //------------------------------------------------------------------------------ bool GuiTreeViewCtrl::hitTest(const Point2I & pnt, Item* & item, BitSet32 & flags) { // Initialize some things. const Point2I pos = globalToLocalCoord(pnt); flags.clear(); item = 0; // get the hit cell Point2I cell((pos.x < 0 ? -1 : pos.x / mCellSize.x), (pos.y < 0 ? -1 : pos.y / mCellSize.y)); // valid? if((cell.x < 0 || cell.x >= mSize.x) || (cell.y < 0 || cell.y >= mSize.y)) return false; flags.set(OnRow); // Grab the cell. if (cell.y >= mVisibleItems.size()) return false; //Invalid cell, so don't do anything item = mVisibleItems[cell.y]; S32 min = mTabSize * item->mTabLevel; // left of icon/text? if(pos.x < min) { flags.set(OnIndent); return true; } // check image S32 image = BmpChild; if(item->isInspectorData()) image = item->isExpanded() ? BmpExp : BmpCon; else image = item->isExpanded() ? item->getExpandedImage() : item->getNormalImage(); if((image >= 0) && (image < mProfile->mBitmapArrayRects.size())) min += mProfile->mBitmapArrayRects[image].extent.x; // Is it on the image? if(pos.x < min) { flags.set(OnImage); return(true); } // Bump over to the start of the text. min += mTextOffset; // Check against the text. FrameAllocatorMarker txtAlloc; U32 bufLen = item->getDisplayTextLength(); char *buf = (char*)txtAlloc.alloc(bufLen); item->getDisplayText(bufLen, buf); min += mProfile->mFont->getStrWidth(buf); if(pos.x < min) flags.set(OnText); return true; } void GuiTreeViewCtrl::setInstantGroup(SimObject * obj) { // make sure we're talking about a group. SimGroup * grp = dynamic_cast(obj); // Set the instant group variable. if(grp) { Con::setVariable("instantGroup", grp->getIdString()); // Notify Script Con::executef(this,2,"onInstantGroupSelected",Con::getIntArg(grp->getId())); } } void GuiTreeViewCtrl::syncSelection() { // for each visible item check to see if it is on the mSelected list. // if it is then make sure that it is on the mSelectedItems list as well. for (S32 i = 0; i < mVisibleItems.size(); i++) { for (S32 j = 0; j < mSelected.size(); j++) { if (mVisibleItems[i]->mId == mSelected[j]) { // check to see if it is on the visible items list. bool addToSelectedItems = true; for (S32 k = 0; k < mSelectedItems.size(); k++) { if (mSelected[j] == mSelectedItems[k]->mId) { // don't add it addToSelectedItems = false; } } if (addToSelectedItems) { mVisibleItems[i]->mState.set(Item::Selected, true); mSelectedItems.push_front(mVisibleItems[i]); break; } } else if (mVisibleItems[i]->isInspectorData()) { if (mVisibleItems[i]->getObject() && mVisibleItems[i]->getObject()->getId() == mSelected[j]) { // check to see if it is on the visible items list. bool addToSelectedItems = true; for (S32 k = 0; k < mSelectedItems.size(); k++) { if (mSelectedItems[k]->isInspectorData()) { if (mSelected[j] == mSelectedItems[k]->getObject()->getId()) { // don't add it addToSelectedItems = false; } } else { if (mSelected[j] == mSelectedItems[k]->mId) { // don't add it addToSelectedItems = false; } } } if (addToSelectedItems) { mVisibleItems[i]->mState.set(Item::Selected, true); mSelectedItems.push_front(mVisibleItems[i]); break; } } } } } } void GuiTreeViewCtrl::removeSelection(S32 itemId) { if (mDebug) Con::printf("removeSelection called"); Item * item = getItem(itemId); // the item may have been selected at one point but was never created/visible in the tree // so remove it. for (S32 j = 0; j isInspectorData()) { if (item->getObject() && item->getObject()->getId() == mSelected[j]) { mSelected.erase(j); break; } } } if (mSelected[j] == itemId) { mSelected.erase(j); break; } } if(!item) { // maybe what we were passed wasn't an item id but an object id. for (S32 i = 0; i isInspectorData()) { if (mItems[i]->getObject()->getId() == itemId) { item = mItems[i]; break; } } } } if (!item) { //Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::removeSelection: invalid item id! Perhaps it isn't visible yet"); return; } } item->mState.set(Item::Selected, false); for (S32 i = 0; i < mSelectedItems.size(); i++) { if (mSelectedItems[i] == item) { mSelectedItems.erase(i); break; } } // Callback - onRemoveSelection( %itemID ) char buf[16]; dSprintf(buf, 16, "%d", item->mId); Con::executef(this, 2, "onRemoveSelection", buf); } void GuiTreeViewCtrl::addSelection(S32 itemId) { if (mDebug) Con::printf("addSelection called"); Item * item = getItem(itemId); if(!item) { // maybe what we were passed wasn't an item id but an object id. for (S32 i = 0; i isInspectorData()) { if ( mItems[i]->getObject() && mItems[i]->getObject()->getId() == itemId) { item = mItems[i]; //looks like it is. check to see if it is on the list bool alreadySelected = false; Vector::iterator i; for(i = mSelectedItems.begin(); i != mSelectedItems.end(); i++) { if (*(i) == item) { //already a selected item which means this call should be ignored alreadySelected = true; return; } } break; } } } } if (!item) { // Do we want to allow more than one selected item? if( !mMultipleSelections ) clearSelection(); //Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::addSelection: invalid item id! Perhaps it isn't visible yet."); mSelected.push_front(itemId); return; } } else { // Do we want to allow more than one selected item? if( !mMultipleSelections ) clearSelection(); // regardless of whether we found an item, we keep track of the Id that was passed // as the item may simply not have been created/visible yet. mSelected.push_front(itemId); } item->mState.set(Item::Selected, true); // Also make it so we can see it if we didn't already. scrollVisible(item); // Do we want to allow more than one selected item? //if( !mMultipleSelections ) // clearSelection(); mSelectedItems.push_front(item); // Callback - onAddSelection( %itemID ) char buf[16]; dSprintf(buf, 16, "%d", item->mId); Con::executef(this, 2, "onAddSelection", buf); } void GuiTreeViewCtrl::onItemSelected( Item *item ) { char buf[16]; dSprintf(buf, 16, "%d", item->mId); if (item->isInspectorData()) { if(item->getObject()) Con::executef(this, 2, "onSelect", Con::getIntArg(item->getObject()->getId())); if (!(item->isParent()) && item->getObject()) Con::executef(this, 2, "onInspect", Con::getIntArg(item->getObject()->getId())); } else { Con::executef(this, 2, "onSelect", buf); if (!(item->isParent())) Con::executef(this, 2, "onInspect", buf); } mSelectedItem = item->getID(); } bool GuiTreeViewCtrl::setItemSelected(S32 itemId, bool select) { Item * item = getItem(itemId); if (select) { if (mDebug) Con::printf("setItemSelected called true"); mSelected.push_front(itemId); } else { if (mDebug) Con::printf("setItemSelected called false"); // remove it from the mSelected list for (S32 j = 0; j isInspectorData()) { if (item->getObject()) { if(item->getObject()->getId() == mSelected[j]) { mSelected.erase(j); break; } } else { // Zombie, kill it! mSelected.erase(j); j--; } } } if (mSelected[j] == itemId) { mSelected.erase(j); break; } } } if(!item) { // maybe what we were passed wasn't an item id but an object id. for (S32 i = 0; i isInspectorData()) { if (mItems[i]->getObject()) { if(mItems[i]->getObject()->getId() == itemId) { item = mItems[i]; break; } } else { // It's a zombie, blast it. mItems.erase(i); i--; } } } } if (!item) { //Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::setItemSelected: invalid item id! Perhaps it isn't visible yet."); return(false); } } if(select) { addSelection( item->mId ); onItemSelected( item ); } else { // unselect the item, if it's present. item->mState.set(Item::Selected, false); if (item->isInspectorData() && item->getObject()) Con::executef(this, 2, "onUnSelect", Con::getIntArg(item->getObject()->getId())); else Con::executef(this, 2, "onUnSelect", Con::getIntArg(item->mId)); // remove it from the selected items list for (S32 i = 0; i < mSelectedItems.size(); i++) { if (mSelectedItems[i] == item) { mSelectedItems.erase(i); break; } } } setUpdate(); return(true); } // Given an item's index in the selection list, return its itemId S32 GuiTreeViewCtrl::getSelectedItem(S32 index) { if(index >= 0 && index < getSelectedItemsCount()) { return mSelectedItems[index]->mId; } return -1; } bool GuiTreeViewCtrl::setItemExpanded(S32 itemId, bool expand) { Item * item = getItem(itemId); if(!item) { Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::setItemExpanded: invalid item id!"); return(false); } if(item->isExpanded() == expand) return(true); // expand parents if(expand) { while(item) { if(item->mState.test(Item::VirtualParent)) onVirtualParentExpand(item); item->setExpanded(true); item = item->mParent; } } else { if(item->mState.test(Item::VirtualParent)) onVirtualParentCollapse(item); item->setExpanded(false); } return(true); } bool GuiTreeViewCtrl::setItemValue(S32 itemId, StringTableEntry Value) { Item * item = getItem(itemId); if(!item) { Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::setItemValue: invalid item id!"); return(false); } item->setValue( ( Value ) ? Value : "" ); return(true); } const char * GuiTreeViewCtrl::getItemText(S32 itemId) { Item * item = getItem(itemId); if(!item) { Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::getItemText: invalid item id!"); return(""); } return(item->getText() ? item->getText() : ""); } const char * GuiTreeViewCtrl::getItemValue(S32 itemId) { Item * item = getItem(itemId); if(!item) { Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::getItemValue: invalid item id!"); return(""); } if(item->mState.test(Item::InspectorData)) { // If it's InspectorData, we let people use this call to get an object reference. return item->mInspectorInfo.mObject->getIdString(); } else { // Just return the script value... return(item->getValue() ? item->getValue() : ""); } } bool GuiTreeViewCtrl::editItem( S32 itemId, const char* newText, const char* newValue ) { Item* item = getItem( itemId ); if ( !item ) { Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::editItem: invalid item id!"); return false; } if ( item->mState.test(Item::InspectorData) ) { Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::editItem: item %d is inspector data and may not be modified!", itemId); return false; } delete [] item->getText(); item->setText (new char[dStrlen( newText ) + 1]); dStrcpy( item->getText(), newText ); delete [] item->getValue(); item->setValue( new char[dStrlen( newValue ) + 1] ); dStrcpy( item->getValue(), newValue ); // Update the widths and such: buildVisibleTree(); return true; } void GuiTreeViewCtrl::deleteSelection() { Con::executef(this, 1, "onDeleteSelection"); if (mSelectedItems.empty()) { for (S32 i = 0; i < mSelected.size(); i++) { S32 objectId = mSelected[i]; // find the object SimObject* obj = Sim::findObject(objectId); obj->deleteObject(); } } else { Vector delSelection; delSelection = mSelectedItems; mSelectedItems.clear(); while (!delSelection.empty()) { Item * item = delSelection.front(); setItemSelected(item->mId,false); if ( item->mParent ) deleteItem( item ); delSelection.pop_front(); } } mSelected.clear(); mSelectedItems.clear(); Con::executef( this, 1, "onObjectDeleteCompleted"); } //------------------------------------------------------------------------------ // keyboard movement of items is restricted to just one item at a time // if more than one item is selected then movement operations are not performed bool GuiTreeViewCtrl::onKeyDown( const GuiEvent& event ) { if ( !mVisible || !mActive || !mAwake ) return true; // All the keyboard functionality requires a selected item, so if none exists... // Deal with enter and delete if ( event.modifier == 0 ) { if ( event.keyCode == KEY_RETURN ) { if ( mAltConsoleCommand[0] ) Con::evaluate( mAltConsoleCommand ); return true; } if ( event.keyCode == KEY_DELETE && mDeleteObjectAllowed ) { // Don't delete the root! if (mSelectedItems.empty()) return true; //this may be fighting with the world editor delete deleteSelection(); return true; } //call a generic bit of script that will let the subclass know that a key was pressed Con::executef(this, 3, "onKeyDown", Con::getIntArg(event.modifier), Con::getIntArg(event.keyCode)); } // only do operations if only one item is selected if ( mSelectedItems.empty() || (mSelectedItems.size() > 1)) return true; Item* item = mSelectedItems.first(); if ( !item ) return true; // The Alt key lets you move items around! if ( mFlags.test(IsEditable) && event.modifier & SI_ALT ) { switch ( event.keyCode ) { case KEY_UP: // Move us up. if ( item->mPrevious ) { moveItemUp( item->mId ); scrollVisible(item); } return true; case KEY_DOWN: // Move the item under us up. if ( item->mNext ) { moveItemUp( item->mNext->mId ); scrollVisible(item); } return true; case KEY_LEFT: if ( item->mParent ) { if ( item->mParent->mParent ) { // Ok, we have both an immediate parent, and a grandparent. // The goal of left-arrow alt is to become the child of our // grandparent, ie, to become a sibling of our parent. // First, unlink item from its siblings. if ( item->mPrevious ) item->mPrevious->mNext = item->mNext; else item->mParent->mChild = item->mNext; if ( item->mNext ) item->mNext->mPrevious = item->mPrevious; // Now, relink as the next sibling of our parent. item->mPrevious = item->mParent; item->mNext = item->mParent->mNext; // If there was already a next sibling, deal with that case. if ( item->mNext ) item->mNext->mPrevious = item; item->mParent->mNext = item; // Snag the current parent set if any... SimSet *parentSet = NULL; if(item->mParent->isInspectorData()) parentSet = dynamic_cast(item->mParent->getObject()); else { // parent is probably script data so we search up the tree for a // set to put our object in Item * temp = item->mParent; while (!temp->isInspectorData()) temp = temp->mParent; // found a ancestor who is an inspectorData if (temp->isInspectorData()) parentSet = dynamic_cast(temp->getObject()); else parentSet = NULL; } // Get our active SimObject if any SimObject *simObj = NULL; if(item->isInspectorData()) simObj = item->getObject(); // Remove from the old parentset... if(simObj && parentSet) { if (parentSet->size()>0) { SimObject *lastObject = parentSet->last(); parentSet->removeObject(simObj); parentSet->reOrder(lastObject); } else parentSet->removeObject(simObj); } // And finally, update our item item->mParent = item->mParent->mParent; // Snag the newparent set if any... SimSet *newParentSet = NULL; if(item->mParent->isInspectorData()) newParentSet = dynamic_cast(item->mParent->getObject()); else { // parent is probably script data so we search up the tree for a // set to put our object in Item * temp = item->mParent; while (!temp->isInspectorData()) temp = temp->mParent; // found a ancestor who is an inspectorData if (temp->isInspectorData()) newParentSet = dynamic_cast(temp->getObject()); else newParentSet = NULL; } if(simObj && newParentSet) { newParentSet->addObject(simObj); Item * temp = item->mNext; // item->mNext may be script, so find an inspector item to reorder with if any if (temp) { do { if (temp->isInspectorData()) break; temp = temp->mNext; } while (temp); if (temp && item->getObject() && temp->getObject()) //do we still have a item->mNext? If not then don't bother reordering newParentSet->reOrder(item->getObject(), temp->getObject()); } } else if (!simObj&&newParentSet) { // our current item is script data. but it may have children who // is inspector data who need an updated set if (item->mChild) inspectorSearch(item->mChild, item, parentSet, newParentSet); } // And update everything hurrah. buildVisibleTree(); scrollVisible(item); } } return true; case KEY_RIGHT: if ( item->mPrevious ) { // Make the item the last child of its previous sibling. // First, unlink from the current position in the list item->mPrevious->mNext = item->mNext; if ( item->mNext ) item->mNext->mPrevious = item->mPrevious; // Get the object we're poking with. SimObject *simObj = NULL; SimSet *parentSet = NULL; if(item->isInspectorData()) simObj = item->getObject(); if(item->mParent->isInspectorData()) parentSet = dynamic_cast(item->mParent->getObject()); else { // parent is probably script data so we search up the tree for a // set to put our object in Item * temp = item->mParent; while (!temp->isInspectorData()) temp = temp->mParent; // found an ancestor who is an inspectorData if (temp->isInspectorData()) parentSet = dynamic_cast(temp->getObject()); } // If appropriate, remove from the current SimSet. if(parentSet && simObj) { if (parentSet->size()>0) { SimObject *lastObject = parentSet->last(); parentSet->removeObject(simObj); parentSet->reOrder(lastObject); } else parentSet->removeObject(simObj); } // Now, make our previous sibling our parent... item->mParent = item->mPrevious; item->mNext = NULL; // And sink us down to the end of its siblings, if appropriate. if ( item->mParent->mChild ) { Item* temp = item->mParent->mChild; while ( temp->mNext ) temp = temp->mNext; temp->mNext = item; item->mPrevious = temp; } else { // only child... item->mParent->mChild = item; item->mPrevious = NULL; } // Make sure the new parent is expanded: if ( !item->mParent->mState.test( Item::Expanded ) ) setItemExpanded( item->mParent->mId, true ); // Snag the new parent simset if any. SimSet *newParentSet = NULL; // new parent might be script. so figure out what set we need to add it to. if(item->mParent->isInspectorData()) newParentSet = dynamic_cast(item->mParent->getObject()); else { // parent is probably script data so we search up the tree for a // set to put our object in if (mDebug) Con::printf("oh nos my parent is script!"); Item * temp = item->mParent; while (!temp->isInspectorData()) temp = temp->mParent; // found a ancestor who is an inspectorData if (temp->isInspectorData()) newParentSet = dynamic_cast(temp->getObject()); else newParentSet = NULL; } // Add the item's SimObject to the new parent simset, at the end. if(newParentSet && simObj) newParentSet->addObject(simObj); else if (!simObj&&newParentSet&&parentSet) { // our current item is script data. but it may have children who // is inspector data who need an updated set if (item->mChild) { inspectorSearch(item->mChild, item, parentSet, newParentSet); } } scrollVisible(item); } return true; } } // Explorer-esque navigation... switch( event.keyCode ) { case KEY_UP: // Select previous visible item: if ( item->mPrevious ) { item = item->mPrevious; while ( item->isParent() && item->isExpanded() ) { item = item->mChild; while ( item->mNext ) item = item->mNext; } setItemSelected(mSelectedItems.first()->mId,false); setItemSelected( item->mId, true ); scrollVisible(item); return true; } // or select parent: if ( item->mParent ) { setItemSelected(mSelectedItems.first()->mId,false); setItemSelected( item->mParent->mId, true ); scrollVisible(item->mParent); return true; } return false; break; case KEY_DOWN: // Selected child if it is visible: if ( item->isParent() && item->isExpanded() ) { setItemSelected(mSelectedItems.first()->mId,false); setItemSelected( item->mChild->mId, true ); scrollVisible(item->mChild); return true; } // or select next sibling (recursively): do { if ( item->mNext ) { setItemSelected(mSelectedItems.first()->mId,false); setItemSelected(item->mNext->mId, true ); scrollVisible(item->mNext); return true; } item = item->mParent; } while ( item ); return false; break; case KEY_LEFT: // Contract current menu: if ( item->isExpanded() ) { setItemExpanded( item->mId, false ); scrollVisible(item); return true; } // or select parent: if ( item->mParent ) { setItemSelected(mSelectedItems.first()->mId,false); setItemSelected( item->mParent->mId, true ); scrollVisible(item->mParent); return true; } return false; break; case KEY_RIGHT: // Expand selected item: if ( item->isParent() ) { if ( !item->isExpanded() ) { setItemExpanded( item->mId, true ); scrollVisible(item); return true; } // or select child: setItemSelected(mSelectedItems.first()->mId,false); setItemSelected( item->mChild->mId, true ); scrollVisible(item->mChild); return true; } return false; break; } // Not processed, so pass the event on: return Parent::onKeyDown( event ); } //------------------------------------------------------------------------------ // on mouse up look at the current item and check to see if it is valid // to move the selected item(s) to it. void GuiTreeViewCtrl::onMouseUp(const GuiEvent &event) { if( !mActive || !mAwake || !mVisible ) return; mouseUnlock(); if ( mSelectedItems.empty()) { mDragMidPoint = NomDragMidPoint; return; } if (!mMouseDragged) return; else mMouseDragged = false; Item* newItem = NULL; Item* newItem2 = NULL; if (mFlags.test(IsEditable)) { Parent::onMouseMove( event ); if (mOldDragY != mMouseOverCell.y) { mOldDragY = mMouseOverCell.y; BitSet32 hitFlags = 0; if ( !hitTest( event.mousePoint, newItem2, hitFlags ) ) { mDragMidPoint = NomDragMidPoint; return; } newItem2->mState.clear(Item::MouseOverBmp | Item::MouseOverText ); // if the newItem isn't in the mSelectedItemList then continue. Vector::iterator k; for(k = mSelectedItems.begin(); k != mSelectedItems.end(); k++) { newItem = newItem2; if (*(k) == newItem) { mDragMidPoint = NomDragMidPoint; return; } Item * temp = *(k); Item * grandpaTemp = newItem->mParent; // grandpa check, kick out if an item would be its own ancestor while (grandpaTemp) { if (temp == grandpaTemp) { if (mDebug) { Con::printf("grandpa check"); if (temp->isInspectorData()) Con::printf("temp's name: %s",temp->getObject()->getName()); if (grandpaTemp->isInspectorData()) Con::printf("grandpa's name: %s",grandpaTemp->getObject()->getName()); } mDragMidPoint = NomDragMidPoint; return; } grandpaTemp = grandpaTemp->mParent; } } for (S32 i = 0; i mState.clear(Item::MouseOverBmp | Item::MouseOverText ); // move the selected item to the newItem Item* oldParent = item->mParent; // Snag the current parent set if any for future reference SimSet *parentSet = NULL; if(oldParent->isInspectorData()) parentSet = dynamic_cast(oldParent->getObject()); else { // parent is probably script data so we search up the tree for a // set to put our object in Item * temp = oldParent; while (temp) { if (temp->isInspectorData()) break; temp = temp->mParent; } // found an ancestor who is an inspectorData if (temp) { if (temp->isInspectorData()) parentSet = dynamic_cast(temp->getObject()); } } // unlink from the current position in the list unlinkItem(item); // update the parent's children // check if we an only child if (item->mParent->mChild == item) { if (item->mNext) item->mParent->mChild = item->mNext; else item->mParent->mChild = NULL; } if (mDragMidPoint != NomDragMidPoint) { //if it is below an expanded tree, place as last item in the tree //if it is below a parent who isn't expanded put below it // position the item above or below another item if (mDragMidPoint == AbovemDragMidPoint) { // easier to treat everything as "Below the mDragMidPoint" so make some adjustments if (mDebug) Con::printf("adding item above mDragMidPoint"); // above the mid point of an item, so grab either the parent // or the previous sibling // does the item have a previous sibling? if (newItem->mPrevious) { newItem = newItem->mPrevious; if (mDebug) Con::printf("treating as if below an item that isn't expanded"); // otherwise add below that item as a sibling item->mParent = newItem->mParent; item->mPrevious = newItem; item->mNext = newItem->mNext; if (newItem->mNext) newItem->mNext->mPrevious = item; newItem->mNext = item; } else { if (mDebug) Con::printf("treating as if adding below the parent of the item"); // instead we add as the first item below the newItem's parent item->mParent = newItem->mParent; item->mNext = newItem; item->mPrevious = NULL; newItem->mPrevious = item; item->mParent->mChild = item; } } else if (mDragMidPoint == BelowmDragMidPoint) { if ((newItem->isParent())&&(newItem->isExpanded())) { if (mDebug) Con::printf("adding item to an expanded parent below the mDragMidPoint"); item->mParent = newItem; // then add the new item as a child item->mNext = newItem->mChild; if (newItem->mChild) newItem->mChild->mPrevious = item; item->mParent->mChild = item; item->mPrevious = NULL; } else if ((!newItem->mNext)&&(newItem->mParent)&&(newItem->mParent->mParent)) { // add below it's parent. if (mDebug) Con::printf("adding below a tree"); item->mParent = newItem->mParent->mParent; item->mNext = newItem->mParent->mNext; item->mPrevious = newItem->mParent; if (newItem->mParent->mNext) newItem->mParent->mNext->mPrevious = item; newItem->mParent->mNext = item; } else { // adding below item not as a child if (mDebug) Con::printf("adding item below the mDragMidPoint of an item"); item->mParent = newItem->mParent; // otherwise the item is a sibling if (newItem->mNext) newItem->mNext->mPrevious = item; item->mNext = newItem->mNext; item->mPrevious = newItem; newItem->mNext = item; } } } // if we're not allowed to add to items, then try to add to the parent of the hit item. // if we are, just add to the item we hit. else { if (mDebug) { if (item->isInspectorData() && item->getObject()) Con::printf("Item: %i",item->getObject()->getId()); if (newItem->isInspectorData() && newItem->getObject()) Con::printf("Parent: %i",newItem->getObject()->getId()); Con::printf("dragged onto an item"); } // if the hit item is not already a group, and we're not allowed to drag to items, // then try to add to the parent. if(!mDragToItemAllowed && !newItem->isParent()) { // add to the item's parent. if(!newItem->mParent || !newItem->mParent->isParent()) { if(mDebug) Con::printf("could not find the parent of that item. dragging to an item is not allowed, kicking out."); mDragMidPoint = NomDragMidPoint; return; } newItem = newItem->mParent; } // new parent is the item in the current cell item->mParent = newItem; // adjust children if any if (newItem->mChild) { if (mDebug) Con::printf("not the first child"); // put it at the top of the list (easier to find if there are many children) if (newItem->mChild) newItem->mChild->mPrevious = item; item->mNext = newItem->mChild; newItem->mChild = item; item->mPrevious = NULL; } else { if (mDebug) Con::printf("first child"); // only child newItem->mChild = item; item->mNext = NULL; item->mPrevious = NULL; } } // expand the item we added to, if it isn't expanded already if ( !item->mParent->mState.test( Item::Expanded ) ) setItemExpanded( item->mParent->mId, true ); //---------------------------------------------------------------- // handle objects // Get our active SimObject if any SimObject *simObj = NULL; if(item->isInspectorData()) { simObj = item->getObject(); } // Remove from the old parentset if((simObj && parentSet)&&(oldParent != item->mParent)) { if (mDebug) Con::printf("removing item from old parentset"); // hack to get around the way removeObject takes the last item of the set // and moves it into the place of the object we removed if (parentSet->size()>0) { SimObject *lastObject = parentSet->last(); parentSet->removeObject(simObj); parentSet->reOrder(lastObject); } else { parentSet->removeObject(simObj); } } // Snag the newparent set if any... SimSet *newParentSet = NULL; if(item->mParent->isInspectorData()) { if (mDebug) Con::printf("getting a new parent set"); SimObject * tmpObj = item->mParent->getObject(); newParentSet = dynamic_cast(tmpObj); } else { // parent is probably script data so we search up the tree for a // set to put our object in if (mDebug) Con::printf("oh nos my parent is script!"); Item * temp = item->mParent; while (temp) { if (temp->isInspectorData()) break; temp = temp->mParent; } // found a ancestor who is an inspectorData if (temp) { if (temp->isInspectorData()) newParentSet = dynamic_cast(temp->getObject()); } else { newParentSet = NULL; } } if(simObj && newParentSet) { if (mDebug) Con::printf("simobj and new ParentSet"); if (oldParent != item->mParent) newParentSet->addObject(simObj); //order the objects in the simset according to their //order in the tree view control if(!item->mNext) { if(!item->mPrevious) break; //bring to the end of the set SimObject *prevObject = item->mPrevious->getObject(); if (prevObject && item->getObject()) { newParentSet->reOrder(item->getObject(), prevObject); } } else { //reorder within the set SimObject *nextObject = item->mNext->getObject(); if(nextObject && item->getObject()) { newParentSet->reOrder(item->getObject(), nextObject); } } } else if (!simObj&&newParentSet) { // our current item is script data. but it may have children who // is inspector data who need an updated set if (mDebug) Con::printf("no simobj but new parentSet"); if (item->mChild) inspectorSearch(item->mChild, item, parentSet, newParentSet); } else if (simObj&&!newParentSet) { if (mDebug) Con::printf("simobject and no new parent set"); } else if (mDebug) Con::printf("no simobject and no new parent set"); } // And update everything. scrollVisible(newItem); } } mDragMidPoint = NomDragMidPoint; } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::onMouseDragged(const GuiEvent &event) { if(!mSupportMouseDragging) return; if( !mActive || !mAwake || !mVisible ) return; if (mSelectedItems.size() == 0) return; Point2I pt = globalToLocalCoord(event.mousePoint); Parent::onMouseMove(event); mouseLock(); mMouseDragged = true; // whats our mDragMidPoint? mCurrentDragCell = mMouseOverCell.y; S32 midpCell = mCurrentDragCell * mItemHeight + (mItemHeight/2); S32 currentY = pt.y; S32 yDiff = currentY-midpCell; S32 variance = (mItemHeight/5); if (mPreviousDragCell >= 0) mVisibleItems[mPreviousDragCell]->mState.clear( Item::MouseOverBmp | Item::MouseOverText ); if (mAbs(yDiff) > variance) { //above or below an item? if (yDiff < 0) mDragMidPoint = AbovemDragMidPoint; else mDragMidPoint = BelowmDragMidPoint; } else { mDragMidPoint = NomDragMidPoint; // highlight the current item // hittest to detect whether we are on an item // ganked from onMouseMouse // used for tracking what our last cell was so we can clear it. mPreviousDragCell = mCurrentDragCell; if (mCurrentDragCell >= 0) { Item* item = NULL; BitSet32 hitFlags = 0; if ( !hitTest( event.mousePoint, item, hitFlags ) ) return; if ( hitFlags.test( OnImage ) ) item->mState.set( Item::MouseOverBmp ); if ( hitFlags.test( OnText )) item->mState.set( Item::MouseOverText ); // Always redraw the entire mouse over item, since we are distinguishing // between the bitmap and the text: setUpdateRegion( Point2I( mMouseOverCell.x * mCellSize.x, mMouseOverCell.y * mCellSize.y ), mCellSize ); } } } void GuiTreeViewCtrl::onMiddleMouseDown(const GuiEvent & event) { //for debugging items if (mDebug) { Item* item; BitSet32 hitFlags = 0; hitTest( event.mousePoint, item, hitFlags ); Con::printf("debugging %d", item->mId); Point2I pt = globalToLocalCoord(event.mousePoint); if (item->isInspectorData() && item->getObject()) { Con::printf("object data:"); Con::printf("name:%s",item->getObject()->getName()); Con::printf("className:%s",item->getObject()->getClassName()); } Con::printf("contents of mSelectedItems:"); for(S32 i = 0; i < mSelectedItems.size(); i++) { if (mSelectedItems[i]->isInspectorData()) { Con::printf("%d",mSelectedItems[i]->getObject()->getId()); } else Con::printf("wtf %d", mSelectedItems[i]); } Con::printf("contents of mSelected"); for (S32 j = 0; j < mSelected.size(); j++) { Con::printf("%d", mSelected[j]); } S32 mCurrentDragCell = mMouseOverCell.y; S32 midpCell = (mCurrentDragCell) * mItemHeight + (mItemHeight/2); S32 currentY = pt.y; S32 yDiff = currentY-midpCell; S32 variance = (mItemHeight/5); Con::printf("cell info: (%d,%d) mCurrentDragCell=%d est=(%d,%d,%d) ydiff=%d",pt.x,pt.y,mCurrentDragCell,mCurrentDragCell*mItemHeight, midpCell, (mCurrentDragCell+1)*mItemHeight,yDiff); } } void GuiTreeViewCtrl::onMouseDown(const GuiEvent & event) { if( !mActive || !mAwake || !mVisible ) { Parent::onMouseDown(event); return; } if ( mProfile->mCanKeyFocus ) setFirstResponder(); Item * item = 0; BitSet32 hitFlags; mOldDragY = 0; mDragMidPoint = NomDragMidPoint; // if(!hitTest(event.mousePoint, item, hitFlags)) return; // if(event.modifier & SI_CTRL) { bool selectFlag = item->mState.test(Item::Selected); if (selectFlag == true) { // already selected, so unselect it and remove it removeSelection(item->mId); if (item->isInspectorData() && item->getObject()) Con::executef(this,2,"onRemoveSelection",Con::getIntArg(item->getObject()->getId())); } else { // otherwise select it and add it to the list // check if it is already on the list. /*bool newSelection = true; for (S32 i = 0; i < mSelectedItems.size(); i++) { if (mSelectedItems[i] == item) { newSelection = false; } }*/ //if (newSelection) { addSelection(item->mId); if (item->isInspectorData() && item->getObject()) Con::executef(this,2,"onAddSelection",Con::getIntArg(item->getObject()->getId())); //} } } else if (event.modifier & SI_SHIFT) { // is something already selected? S32 firstSelectedIndex = 0; Item * firstItem = NULL; if (!mSelectedItems.empty()) { firstItem = mSelectedItems.front(); for (S32 i = 0; i < mVisibleItems.size();i++) { if (mVisibleItems[i] == mSelectedItems.front()) { firstSelectedIndex = i; break; } } S32 mCurrentDragCell = mMouseOverCell.y; if (mVisibleItems[firstSelectedIndex] != firstItem ) { /* Con::printf("something isn't right..."); if (mVisibleItems[firstSelectedIndex]->isInspectorData()) Con::printf("visibleItem %s",mVisibleItems[firstSelectedIndex]->getObject()->getName()); if (firstItem->isInspectorData()) Con::printf("firstItem %s",firstItem->getObject()->getName()); */ } else { // select the cells if ((mCurrentDragCell) < firstSelectedIndex) { //select up for (S32 j = (mCurrentDragCell); j < firstSelectedIndex; j++) { //if the item isn't already selected, then select it bool newSelection = true; Vector::iterator k; for(k = mSelectedItems.begin(); k != mSelectedItems.end(); k++) { if (mVisibleItems[j] == *(k)){ newSelection = false; break; } } if (newSelection) { addSelection(mVisibleItems[j]->mId); if (mVisibleItems[j]->isInspectorData()) Con::executef(this,2,"onAddSelection",Con::getIntArg(mVisibleItems[j]->getObject()->getId())); } } } else { // select down for (S32 j = firstSelectedIndex+1; j < (mCurrentDragCell+1); j++) { bool newSelection = true; Vector::iterator k; for(k = mSelectedItems.begin(); k != mSelectedItems.end(); k++) { if (mVisibleItems[j] == *(k)){ newSelection = false; break; } } if (newSelection) { addSelection(mVisibleItems[j]->mId); if (mVisibleItems[j]->isInspectorData()) Con::executef(this,2,"onAddSelection",Con::getIntArg(mVisibleItems[j]->getObject()->getId())); } } } } } } else if (event.modifier & SI_ALT) { if (item->isInspectorData() && item->getObject()) { setInstantGroup(item->getObject()); mInstantGroup = item->mId; } } else if (!hitFlags.test(OnImage)) { // first check to see if the item is already selected bool newSelection = true; Vector::iterator k; for(k = mSelectedItems.begin(); k != mSelectedItems.end(); k++) { if(*(k) == item) { newSelection = false; break; } } // if the item is not already selected then we have a //newly selected item, so clear our list of selected items if (newSelection) { clearSelection(); //Con::executef(this, 1, "onClearSelection"); //mSelectedItems.clear(); setItemSelected(item->mId,true); } } if ( hitFlags.test( OnText ) && ( event.mouseClickCount > 1 ) && mAltConsoleCommand[0] ) Con::evaluate( mAltConsoleCommand ); if(!item->isParent()) return; // if ( mFullRowSelect || hitFlags.test( OnImage ) ) { item->setExpanded(!item->isExpanded()); if( !item->isInspectorData() && item->mState.test(Item::VirtualParent) ) onVirtualParentExpand(item); scrollVisible(item); } } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::onMouseMove( const GuiEvent &event ) { if ( mMouseOverCell.y >= 0 ) mVisibleItems[mMouseOverCell.y]->mState.clear( Item::MouseOverBmp | Item::MouseOverText ); Parent::onMouseMove( event ); if ( mMouseOverCell.y >= 0 ) { Item* item = NULL; BitSet32 hitFlags = 0; if ( !hitTest( event.mousePoint, item, hitFlags ) ) return; if ( hitFlags.test( OnImage ) ) item->mState.set( Item::MouseOverBmp ); if ( hitFlags.test( OnText )) item->mState.set( Item::MouseOverText ); // Always redraw the entire mouse over item, since we are distinguishing // between the bitmap and the text: setUpdateRegion( Point2I( mMouseOverCell.x * mCellSize.x, mMouseOverCell.y * mCellSize.y ), mCellSize ); } } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::onMouseEnter( const GuiEvent &event ) { Parent::onMouseEnter( event ); onMouseMove( event ); } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::onMouseLeave( const GuiEvent &event ) { if ( mMouseOverCell.y >= 0 ) mVisibleItems[mMouseOverCell.y]->mState.clear( Item::MouseOverBmp | Item::MouseOverText ); Parent::onMouseLeave( event ); } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::onRightMouseDown(const GuiEvent & event) { if(!mActive) { Parent::onRightMouseDown(event); return; } Item * item = NULL; BitSet32 hitFlags; // if(!hitTest(event.mousePoint, item, hitFlags)) return; // char bufs[2][32]; dSprintf(bufs[0], 32, "%d", item->mId); dSprintf(bufs[1], 32, "%d %d", event.mousePoint.x, event.mousePoint.y); if (item->isInspectorData() && item->getObject()) Con::executef(this,4, "onRightMouseDown", bufs[0],bufs[1],Con::getIntArg(item->getObject()->getId())); else Con::executef(this, 3, "onRightMouseDown", bufs[0], bufs[1]); } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::onRender(Point2I offset, const RectI &updateRect) { // Get all our contents drawn! Parent::onRender(offset,updateRect); // Deal with drawing the drag & drop line, if any... dglSetClipRect(updateRect); // only do it if we have a mDragMidPoint if (mDragMidPoint == NomDragMidPoint || !mSupportMouseDragging ) return; ColorF greyLine(0.5,0.5,0.5,1); Point2F squarePt; glLineWidth(2.f); // draw mDragMidPoint lines with a diamond if (mDragMidPoint == AbovemDragMidPoint) { S32 tempY = mItemHeight*mCurrentDragCell+offset.y ; squarePt.y = tempY; squarePt.x = 125+offset.x; dglDrawLine(0+offset.x, tempY, 250+offset.x, tempY,greyLine); dglDraw2DSquare(squarePt, 6, 90 ); } if (mDragMidPoint == BelowmDragMidPoint) { S32 tempY2 = mItemHeight*(mCurrentDragCell+1) +offset.y; squarePt.y = tempY2; squarePt.x = 125+offset.x; dglDrawLine(0+offset.x, tempY2, 250+offset.x, tempY2,greyLine); dglDraw2DSquare(squarePt,6, 90 ); } glLineWidth(1.f); } void GuiTreeViewCtrl::onRenderCell(Point2I offset, Point2I cell, bool, bool ) { if( !mVisibleItems.size() ) return; // Do some sanity checking and data retrieval. AssertFatal(cell.y < mVisibleItems.size(), "GuiTreeViewCtrl::onRenderCell: invalid cell"); Item * item = mVisibleItems[cell.y]; // If there's no object, deal with it. if(item->isInspectorData()) if(!item->getObject()) return; RectI drawRect( offset, mCellSize ); dglClearBitmapModulation(); FrameAllocatorMarker txtBuff; // Ok, we have the item. There are a few possibilities at this point: // - We need to draw inheritance lines and a treeview-chosen icon // OR // - We have to draw an item-dependent icon // - If we're mouseover, we have to highlight it. // // - We have to draw the text for the item // - Taking into account various mouseover states // - Taking into account the value (set or not) // - If it's an inspector data, we have to do some custom rendering // Ok, first draw the tab and icon. // Do we draw the tree lines? if(mFlags.test(ShowTreeLines)) { drawRect.point.x += ( mTabSize * item->mTabLevel ); Item* parent = item->mParent; for ( S32 i = item->mTabLevel; ( parent && i > 0 ); i-- ) { drawRect.point.x -= mTabSize; if ( parent->mNext ) dglDrawBitmapSR( mProfile->mTextureHandle, drawRect.point, mProfile->mBitmapArrayRects[BmpLine] ); parent = parent->mParent; } } // Now, the icon... drawRect.point.x = offset.x + mTabSize * item->mTabLevel; // First, draw the rollover glow, if it's an inner node. if ( item->isParent() && item->mState.test( Item::MouseOverBmp ) ) dglDrawBitmapSR( mProfile->mTextureHandle, drawRect.point, mProfile->mBitmapArrayRects[BmpGlow] ); // Now, do we draw a treeview-selected item or an item dependent one? S32 newOffset = 0; // This is stored so we can render glow, then update render pos. if(item->isInspectorData()) { S32 bitmap = 0; // Ok, draw the treeview lines as appropriate. if ( !item->isParent() ) { bitmap = item->mNext ? BmpChild : BmpLastChild; } else { bitmap = item->isExpanded() ? BmpExp : BmpCon; if ( item->mParent || item->mPrevious ) bitmap += ( item->mNext ? 3 : 2 ); else bitmap += ( item->mNext ? 1 : 0 ); } if ( ( bitmap >= 0 ) && ( bitmap < mProfile->mBitmapArrayRects.size() ) ) { dglDrawBitmapSR( mProfile->mTextureHandle, drawRect.point, mProfile->mBitmapArrayRects[bitmap] ); newOffset = mProfile->mBitmapArrayRects[bitmap].extent.x; } // draw lock icon if need be S32 icon = Lock1; S32 icon2 = Hidden; if (item->getObject() && item->getObject()->isLocked()) { if (mIconTable[icon]) { //drawRect.point.x = offset.x + mTabSize * item->mTabLevel + mIconTable[icon].getWidth(); drawRect.point.x += mIconTable[icon].getWidth(); dglDrawBitmap( mIconTable[icon], drawRect.point, 0 ); } } if (item->getObject() && item->getObject()->isHidden()) { if (mIconTable[icon2]) { //drawRect.point.x = offset.x + mTabSize * item->mTabLevel + mIconTable[icon].getWidth(); drawRect.point.x += mIconTable[icon2].getWidth(); dglDrawBitmap( mIconTable[icon2], drawRect.point, 0 ); } } SimObject * pObject = item->getObject(); SimGroup * pGroup = ( pObject == NULL ) ? NULL : dynamic_cast( pObject ); // draw the icon associated with the item if (item->isParent()) { if ( pGroup != NULL) { if (item->isExpanded()) item->mIcon = SimGroup1; else item->mIcon = SimGroup2; } else item->mIcon = SimGroup2; } if (mInstantGroup == item->mId) { if (item->isInspectorData()) { if ( pGroup != NULL ) { if (item->isExpanded()) item->mIcon = SimGroup3; else item->mIcon = SimGroup4; } } } if (item->mIcon) { if (mIconTable[item->mIcon]) { S32 iconHeight = (mItemHeight - mIconTable[item->mIcon].getHeight()) / 2; S32 oldHeight = drawRect.point.y; if(iconHeight > 0) drawRect.point.y += iconHeight; drawRect.point.x += mIconTable[item->mIcon].getWidth(); dglDrawBitmap( mIconTable[item->mIcon], drawRect.point, 0 ); drawRect.point.y = oldHeight; } } } else { S32 bitmap = 0; // Ok, draw the treeview lines as appropriate. if ( !item->isParent() ) bitmap = item->mNext ? BmpChild : BmpLastChild; else { bitmap = item->isExpanded() ? BmpExp : BmpCon; if ( item->mParent || item->mPrevious ) bitmap += ( item->mNext ? 3 : 2 ); else bitmap += ( item->mNext ? 1 : 0 ); } if ( ( bitmap >= 0 ) && ( bitmap < mProfile->mBitmapArrayRects.size() ) ) { dglDrawBitmapSR( mProfile->mTextureHandle, drawRect.point, mProfile->mBitmapArrayRects[bitmap] ); newOffset = mProfile->mBitmapArrayRects[bitmap].extent.x; } S32 icon = item->isExpanded() ? item->mScriptInfo.mExpandedImage : item->mScriptInfo.mNormalImage; if ( icon ) { if (mIconTable[icon]) { S32 iconHeight = (mItemHeight - mIconTable[icon].getHeight()) / 2; S32 oldHeight = drawRect.point.y; if(iconHeight > 0) drawRect.point.y += iconHeight; drawRect.point.x += mIconTable[icon].getWidth(); dglDrawBitmap( mIconTable[icon], drawRect.point, 0 ); drawRect.point.y = oldHeight; } } } // Ok, update offset so we can render some text! drawRect.point.x += newOffset; // Ok, now we're off to rendering the actual data for the treeview item. U32 bufLen = item->mDataRenderWidth + 1; char *displayText = (char *)txtBuff.alloc(bufLen); displayText[bufLen-1] = 0; item->getDisplayText(bufLen, displayText); // Draw the rollover/selected bitmap, if one was specified. drawRect.extent.x = mProfile->mFont->getStrWidth( displayText ) + ( 2 * mTextOffset ); if ( item->mState.test( Item::Selected ) && mTexSelected ) dglDrawBitmapStretch( mTexSelected, drawRect ); else if ( item->mState.test( Item::MouseOverText ) && mTexRollover ) dglDrawBitmapStretch( mTexRollover, drawRect ); // Offset a bit so as to space text properly. drawRect.point.x += mTextOffset; // Determine what color the font should be. ColorI fontColor; fontColor = item->mState.test( Item::Selected ) ? mProfile->mFontColorSEL : ( item->mState.test( Item::MouseOverText ) ? mProfile->mFontColorHL : mProfile->mFontColor ); if (item->mState.test(Item::Selected)) { dglDrawRectFill(drawRect, mProfile->mFillColorHL); } else if (item->mState.test(Item::MouseOverText)) { dglDrawRectFill(drawRect, mProfile->mFontColorNA); } if( mInstantGroup == item->mId) { fontColor = mProfile->mFontColorHL; } dglSetBitmapModulation( fontColor ); // Center the text horizontally. S32 height = (mItemHeight - mProfile->mFont->getHeight()) / 2; if(height > 0) drawRect.point.y += height; // JDD - offset by two pixels or so to keep the text from rendering RIGHT ONTOP of the outline drawRect.point.x += 2; dglDrawText( mProfile->mFont, drawRect.point, displayText, mProfile->mFontColors ); } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::clearSelection() { while (!mSelectedItems.empty()) { if(!setItemSelected(mSelectedItems.last()->mId, false)) mSelectedItems.pop_back(); } mSelectedItems.clear(); mSelected.clear(); Con::executef(this, 1, "onClearSelection"); } void GuiTreeViewCtrl::lockSelection(bool lock) { for(U32 i = 0; i < mSelectedItems.size(); i++) { if(mSelectedItems[i]->isInspectorData()) mSelectedItems[i]->getObject()->setLocked(lock); } } void GuiTreeViewCtrl::hideSelection(bool hide) { for(U32 i = 0; i < mSelectedItems.size(); i++) { if(mSelectedItems[i]->isInspectorData()) mSelectedItems[i]->getObject()->setHidden(hide); } } //------------------------------------------------------------------------------ // handles icon assignments S32 GuiTreeViewCtrl::getIcon(const char * iconString) { if( iconString == NULL ) return Default; S32 icon = Default; if (!dStrcmp(iconString, "Sky")) icon = Sky; else if (!dStrcmp(iconString, "Sun")) icon = Sun; else if (!dStrcmp(iconString, "Lightning")) icon = Lightning; else if (!dStrcmp(iconString, "Water")) icon = Water; else if (!dStrcmp(iconString, "WaterBlock")) icon = Water; else if (!dStrcmp(iconString, "Terrain")) icon = Terrain; else if (!dStrcmp(iconString, "TerrainBlock")) icon = Terrain; else if (!dStrcmp(iconString, "AudioEmitter")) icon = Audio; else if (!dStrcmp(iconString, "Precipitation")) icon = Precipitation; else if (!dStrcmp(iconString, "ParticleEmitter")) icon = Particle; else if (!dStrcmp(iconString, "ParticleEmitterNode")) icon = Particle; else if (!dStrcmp(iconString, "fxSunLight")) icon = fxSunLight; else if (!dStrcmp(iconString, "fxLight")) icon = fxLight; else if (!dStrcmp(iconString, "fxShapeReplicator")) icon = fxShapeReplicator; else if (!dStrcmp(iconString, "fxFoliageReplicator")) icon = fxFoliageReplicator; else if (!dStrcmp(iconString, "MissionArea")) icon = MissionArea; else if (!dStrcmp(iconString, "Path")) icon = Path; else if (!dStrcmp(iconString, "PathMarker")) icon = Pathmarker; else if (!dStrcmp(iconString, "Marker")) icon = Pathmarker; else if (!dStrcmp(iconString, "Trigger")) icon = Trigger; else if (!dStrcmp(iconString, "PhysicalZone")) icon = PhysicalArea; else if (!dStrcmp(iconString, "Camera")) icon = Camera; else if (!dStrcmp(iconString, "SimGroup")) icon = SimGroup1; else if (!dStrcmp(iconString, "Interior")) icon = Interior; else if (!dStrcmp(iconString, "InteriorInstance")) icon = Interior; else if (!dStrcmp(iconString, "TSStatic")) icon = StaticShape; else if (!dStrcmp(iconString, "Item")) icon = Shape; return icon; } void GuiTreeViewCtrl::addInspectorDataItem(Item *parent, SimObject *obj) { S32 icon = getIcon(obj->getClassName()); Item *item = createItem(icon); item->mState.set(Item::InspectorData); // Deal with child objects... if(dynamic_cast(obj)) item->mState.set(Item::VirtualParent); // Actually store the data! item->setObject(obj); // Now add us to the data structure... if(parent) { // Add as child of parent. if(parent->mChild) { Item * traverse = parent->mChild; while(traverse->mNext) traverse = traverse->mNext; traverse->mNext = item; item->mPrevious = traverse; } else parent->mChild = item; item->mParent = parent; } else { // If no parent, add to root. item->mNext = mRoot; mRoot = item; item->mParent = NULL; } if(!parent || parent->isExpanded()) mFlags.set(RebuildVisible); buildVisibleTree(); } void GuiTreeViewCtrl::unlinkItem(Item * item) { if (item->mPrevious) item->mPrevious->mNext = item->mNext; if (item->mNext) item->mNext->mPrevious = item->mPrevious; } bool GuiTreeViewCtrl::childSearch(Item * item, SimObject *obj, bool yourBaby) { Item * temp = item->mChild; while (temp) { //do you have my baby? if (temp->isInspectorData()) { if (temp->getObject() == obj) yourBaby = false; //probably a child of an inner script } yourBaby = childSearch(temp,obj, yourBaby); temp = temp->mNext; } return yourBaby; } void GuiTreeViewCtrl::inspectorSearch(Item * item, Item * parent, SimSet * parentSet, SimSet * newParentSet) { if (!parentSet||!newParentSet) return; if (item == parent->mNext) return; if (item) { if (item->isInspectorData()) { // remove the object from the parentSet and add it to the newParentSet SimObject* simObj = item->getObject(); if (parentSet->size()) { SimObject *lastObject = parentSet->last(); parentSet->removeObject(simObj); parentSet->reOrder(lastObject); } else parentSet->removeObject(simObj); newParentSet->addObject(simObj); if (item->mNext) { inspectorSearch(item->mNext, parent, parentSet, newParentSet); return; } else { // end of children so backing up if (item->mParent == parent) return; else { inspectorSearch(item->mParent->mNext, parent, parentSet, newParentSet); return; } } } if (item->mChild) { inspectorSearch(item->mChild, parent, parentSet, newParentSet); return; } if (item->mNext) { inspectorSearch(item->mNext, parent, parentSet, newParentSet); return; } } } bool GuiTreeViewCtrl::onVirtualParentBuild(Item *item, bool bForceFullUpdate) { if(!item->mState.test(Item::InspectorData)) return true; // Blast an item if it doesn't have a corresponding SimObject... if(item->mInspectorInfo.mObject == NULL) { removeItem(item->mId); return false; } // Skip the next stuff unless we're expanded... if(!item->isExpanded() && !bForceFullUpdate) return true; // Verify that we have all the kids we should in here... SimSet *srcObj = dynamic_cast(&(*item->mInspectorInfo.mObject)); // If it's not a SimSet... WTF are we doing here? if(!srcObj) return true; SimSet::iterator i; // This is slow but probably ok. for(i = srcObj->begin(); i != srcObj->end(); i++) { SimObject *obj = *i; // If we can't find it, add it. // unless it has a parent that is a child that is a script Item *res = item->findChildByValue(obj); bool foundChild = true; // search the children. if any of them are the parent of the object then don't add it. foundChild = childSearch(item,obj,foundChild); if(!res && foundChild) { if (mDebug) Con::printf("adding something"); addInspectorDataItem(item, obj); } } return true; } bool GuiTreeViewCtrl::onVirtualParentExpand(Item *item) { // Do nothing... return true; } bool GuiTreeViewCtrl::onVirtualParentCollapse(Item *item) { // Do nothing... return true; } void GuiTreeViewCtrl::inspectObject(SimObject *obj, bool okToEdit) { destroyTree(); mFlags.set(IsEditable, okToEdit); //build our icon table const char * res = Con::executef(this, 1, "onDefineIcons"); if(!(dAtob(res))) { // if no icons were defined in script then use defaults. buildIconTable(NULL); } addInspectorDataItem(NULL, obj); } S32 GuiTreeViewCtrl::findItemByName(const char *name) { for (S32 i = 0; i < mItems.size(); i++) if (dStrcmp(mItems[i]->getText(),name) == 0) return mItems[i]->mId; return 0; } StringTableEntry GuiTreeViewCtrl::getTextToRoot( S32 itemId, const char * delimiter ) { Item * item = getItem(itemId); if(!item) { Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::getTextToRoot: invalid start item id!"); return StringTable->insert(""); } if(item->isInspectorData()) { Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::getTextToRoot: cannot get text to root of inspector data items"); return StringTable->insert(""); } char bufferOne[1024]; char bufferTwo[1024]; char bufferNodeText[128]; dMemset( bufferOne, 0, sizeof(bufferOne) ); dMemset( bufferTwo, 0, sizeof(bufferTwo) ); dStrcpy( bufferOne, item->getText() ); Item *prevNode = item->mParent; while ( prevNode ) { dMemset( bufferNodeText, 0, sizeof(bufferNodeText) ); dStrcpy( bufferNodeText, prevNode->getText() ); dSprintf( bufferTwo, 1024, "%s%s%s",bufferNodeText, delimiter, bufferOne ); dStrcpy( bufferOne, bufferTwo ); dMemset( bufferTwo, 0, sizeof(bufferTwo) ); prevNode = prevNode->mParent; } // Return the result, StringTable-ized. return StringTable->insert( bufferOne ); } //------------------------------------------------------------------------------ ConsoleMethod(GuiTreeViewCtrl, findItemByName, S32, 3, 3, "(find item by name and returns the mId)") { return(object->findItemByName(argv[2])); } ConsoleMethod(GuiTreeViewCtrl, insertItem, S32, 4, 8, "(TreeItemId parent, name, value, icon, normalImage=0, expandedImage=0)") { S32 norm=0, expand=0; if (argc > 6) { norm = dAtoi(argv[6]); if(argc > 7) expand = dAtoi(argv[7]); } return(object->insertItem(dAtoi(argv[2]), argv[3], argv[4], argv[5], norm, expand)); } ConsoleMethod(GuiTreeViewCtrl, lockSelection, void, 2, 3, "(locks selections)") { bool lock = true; if(argc == 3) lock = dAtob(argv[2]); object->lockSelection(lock); } ConsoleMethod(GuiTreeViewCtrl, clearSelection, void, 2, 2, "(clears selection)") { object->clearSelection(); } ConsoleMethod(GuiTreeViewCtrl, deleteSelection, void, 2, 2, "(deletes the selected items)") { object->deleteSelection(); } ConsoleMethod(GuiTreeViewCtrl, addSelection, void, 3, 3, "(selects an item)") { S32 id = dAtoi(argv[2]); object->addSelection(id); } ConsoleMethod(GuiTreeViewCtrl, addChildSelectionByValue, void, 4, 4, "addChildSelectionByValue(TreeItemId parent, value)") { S32 id = dAtoi(argv[2]); GuiTreeViewCtrl::Item* parentItem = object->getItem(id); GuiTreeViewCtrl::Item* child = parentItem->findChildByValue(argv[3]); object->addSelection(child->getID()); } ConsoleMethod(GuiTreeViewCtrl, removeSelection, void, 3, 3, "(deselects an item)") { S32 id = dAtoi(argv[2]); object->removeSelection(id); } ConsoleMethod(GuiTreeViewCtrl, removeChildSelectionByValue, void, 4, 4, "removeChildSelectionByValue(TreeItemId parent, value)") { S32 id = dAtoi(argv[2]); GuiTreeViewCtrl::Item* parentItem = object->getItem(id); if(parentItem) { GuiTreeViewCtrl::Item* child = parentItem->findChildByValue(argv[3]); if(child) { object->removeSelection(child->getID()); } } } ConsoleMethod(GuiTreeViewCtrl, selectItem, bool, 3, 4, "(TreeItemId item, bool select=true)") { S32 id = dAtoi(argv[2]); bool select = true; if(argc == 4) select = dAtob(argv[3]); return(object->setItemSelected(id, select)); } ConsoleMethod(GuiTreeViewCtrl, expandItem, bool, 3, 4, "(TreeItemId item, bool expand=true)") { S32 id = dAtoi(argv[2]); bool expand = true; if(argc == 4) expand = dAtob(argv[3]); return(object->setItemExpanded(id, expand)); } // Make the given item visible. ConsoleMethod(GuiTreeViewCtrl, scrollVisible, void, 3, 3, "(TreeItemId item)") { object->scrollVisible(dAtoi(argv[2])); } ConsoleMethod(GuiTreeViewCtrl, buildIconTable, bool, 3,3, "(builds an icon table)") { const char * icons = argv[2]; return object->buildIconTable(icons); } ConsoleMethod( GuiTreeViewCtrl, open, void, 3, 4, "(SimSet obj, bool okToEdit=true) Set the root of the tree view to the specified object, or to the root set.") { SimSet *treeRoot = NULL; SimObject* target = Sim::findObject(argv[2]); bool okToEdit = true; if (argc == 4) okToEdit = dAtob(argv[3]); if (target) treeRoot = dynamic_cast(target); if (! treeRoot) Sim::findObject(RootGroupId, treeRoot); object->inspectObject(treeRoot,okToEdit); } ConsoleMethod(GuiTreeViewCtrl, getItemText, const char *, 3, 3, "(TreeItemId item)") { return(object->getItemText(dAtoi(argv[2]))); } ConsoleMethod(GuiTreeViewCtrl, getItemValue, const char *, 3, 3, "(TreeItemId item)") { return(object->getItemValue(dAtoi(argv[2]))); } ConsoleMethod(GuiTreeViewCtrl, editItem, bool, 5, 5, "(TreeItemId item, string newText, string newValue)") { return(object->editItem(dAtoi(argv[2]), argv[3], argv[4])); } ConsoleMethod(GuiTreeViewCtrl, removeItem, bool, 3, 3, "(TreeItemId item)") { return(object->removeItem(dAtoi(argv[2]))); } ConsoleMethod(GuiTreeViewCtrl, removeAllChildren, void, 3, 3, "removeAllChildren(TreeItemId parent)") { object->removeAllChildren(dAtoi(argv[2])); } ConsoleMethod(GuiTreeViewCtrl, clear, void, 2, 2, "() - empty tree") { object->removeItem(0); } ConsoleMethod(GuiTreeViewCtrl, getFirstRootItem, S32, 2, 2, "Get id for root item.") { return(object->getFirstRootItem()); } ConsoleMethod(GuiTreeViewCtrl, getChild, S32, 3, 3, "(TreeItemId item)") { return(object->getChildItem(dAtoi(argv[2]))); } ConsoleMethod(GuiTreeViewCtrl, buildVisibleTree, void, 3, 3, "Build the visible tree") { object->buildVisibleTree(dAtob(argv[2])); } ConsoleMethod(GuiTreeViewCtrl, getParent, S32, 3, 3, "(TreeItemId item)") { return(object->getParentItem(dAtoi(argv[2]))); } ConsoleMethod(GuiTreeViewCtrl, getNextSibling, S32, 3, 3, "(TreeItemId item)") { return(object->getNextSiblingItem(dAtoi(argv[2]))); } ConsoleMethod(GuiTreeViewCtrl, getPrevSibling, S32, 3, 3, "(TreeItemId item)") { return(object->getPrevSiblingItem(dAtoi(argv[2]))); } ConsoleMethod(GuiTreeViewCtrl, getItemCount, S32, 2, 2, "") { return(object->getItemCount()); } ConsoleMethod(GuiTreeViewCtrl, getSelectedItem, S32, 2, 2, "") { return ( object->getSelectedItem() ); } ConsoleMethod(GuiTreeViewCtrl, getSelectedObject, S32, 2, 2, "returns the currently selected simObject in inspector mode or -1") { GuiTreeViewCtrl::Item *item = object->getItem( object->getSelectedItem() ); if( item != NULL && item->isInspectorData() ) { SimObject *obj = item->getObject(); if( obj != NULL ) return object->getId(); } return -1; } ConsoleMethod(GuiTreeViewCtrl, moveItemUp, void, 3, 3, "(TreeItemId item)") { object->moveItemUp( dAtoi( argv[2] ) ); } ConsoleMethod(GuiTreeViewCtrl, getSelectedItemsCount, S32, 2, 2, "") { return ( object->getSelectedItemsCount() ); } ConsoleMethod(GuiTreeViewCtrl, moveItemDown, void, 3, 3, "(TreeItemId item)") { object->moveItemDown( dAtoi( argv[2] ) ); } //----------------------------------------------------------------------------- ConsoleMethod(GuiTreeViewCtrl, getTextToRoot, const char*,4,4,"(TreeItemId item,Delimiter=none) gets the text from the current node to the root, concatenating at each branch upward, with a specified delimiter optionally") { if ( argc < 4 ) { Con::warnf("GuiTreeViewCtrl::getTextToRoot - Invalid number of arguments!"); return (""); } S32 itemId = dAtoi( argv[2] ); StringTableEntry delimiter = argv[3]; return object->getTextToRoot( itemId, delimiter ); } ConsoleMethod(GuiTreeViewCtrl,getSelectedItemList,const char*, 2,2,"returns a space seperated list of mulitple item ids") { char* buff = Con::getReturnBuffer(1024); dSprintf(buff,1024,""); for(int i = 0; i < object->mSelected.size(); i++) { S32 id = object->mSelected[i]; //get the current length of the buffer U32 len = dStrlen(buff); //the start of the buffer where we want to write char* buffPart = buff+len; //the size of the remaining buffer (-1 cause dStrlen doesn't count the \0) S32 size = 1024-len-1; //write it: if(size < 1) { Con::errorf("GuiTreeViewCtrl::getSelectedItemList - Not enough room to return our object list"); return buff; } dSprintf(buffPart,size,"%d ", id); } //mSelected return buff; } S32 GuiTreeViewCtrl::findItemByObjectId(S32 iObjId) { for (S32 i = 0; i < mItems.size(); i++) { SimObject* pObj = mItems[i]->getObject(); if(pObj && pObj->getId() == iObjId) return mItems[i]->mId; } return -1; } //------------------------------------------------------------------------------ ConsoleMethod(GuiTreeViewCtrl, findItemByObjectId, S32, 3, 3, "(find item by object id and returns the mId)") { return(object->findItemByObjectId(dAtoi(argv[2]))); }