//----------------------------------------------------------------------------- // V12 Engine // // Copyright (c) 2001 GarageGames.Com // Portions Copyright (c) 2001 by Sierra Online, Inc. //----------------------------------------------------------------------------- #include "GUI/guiTreeViewCtrl.h" #include "GUI/guiScrollCtrl.h" #include "console/consoleTypes.h" #include "console/console.h" #include "dgl/dgl.h" #include "GUI/guiTypes.h" #include "Platform/event.h" IMPLEMENT_CONOBJECT(GuiTreeViewCtrl); GuiTreeViewCtrl::Item::Item() { mText = NULL; mValue = NULL; } GuiTreeViewCtrl::Item::~Item() { if ( mText ) { delete [] mText; mText = NULL; } if ( mValue ) { delete [] mValue; mValue = NULL; } } //------------------------------------------------------------------------------ GuiTreeViewCtrl::GuiTreeViewCtrl() { VECTOR_SET_ASSOCIATION(mItems); VECTOR_SET_ASSOCIATION(mVisibleItems); VECTOR_SET_ASSOCIATION(mImageBounds); mItemFreeList = 0; mRoot = 0; mItemCount = 0; mSelectedItem = 0; // persist info.. mTabSize = 16; mImagesBitmap = StringTable->insert(""); mNumImages = 0; mTextOffset = 2; mFullRowSelect = false; mItemHeight = 20; // setSize(Point2I(1, 0)); } GuiTreeViewCtrl::~GuiTreeViewCtrl() { destroyTree(); } //------------------------------------------------------------------------------ GuiTreeViewCtrl::Item * GuiTreeViewCtrl::getItem(S32 itemId) { if((itemId > 0) && (itemId <= mItems.size())) return(mItems[itemId-1]); return(0); } //------------------------------------------------------------------------------ GuiTreeViewCtrl::Item * GuiTreeViewCtrl::createItem() { Item * item; // grab from the free list? if(mItemFreeList) { item = mItemFreeList; mItemFreeList = item->mNext; // re-add to vector mItems[item->mId-1] = item; } else { item = new Item; mItems.push_back(item); // set the id item->mId = mItems.size(); } // reset item->mNext = item->mPrevious = item->mParent = item->mChild = 0; item->mText = 0; item->mValue = 0; item->mState = 0; item->mTabLevel = 0; mItemCount++; return(item); } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::destroyItem(Item * item) { if(!item) return; // free memory: if ( item->mText ) { delete [] item->mText; item->mText = NULL; } if ( item->mValue ) { delete [] item->mValue; item->mValue = 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; // destroy all the children while(item->mChild) destroyItem(item->mChild); // remove from vector mItems[item->mId-1] = 0; // set as root free item item->mNext = mItemFreeList; mItemFreeList = item; // if(item->mState.test(Item::Expanded)) mFlags.set(RebuildVisible); mItemCount--; } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::destroyTree() { // clear the item list for(U32 i = 0; i < mItems.size(); i++) delete mItems[i]; mItems.clear(); // clear the free list while(mItemFreeList) { Item * next = mItemFreeList->mNext; delete mItemFreeList; mItemFreeList = next; } mVisibleItems.clear(); // mItemFreeList = 0; mRoot = 0; mItemCount = 0; mSelectedItem = 0; } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::buildItem( Item* item, U32 tabLevel ) { if ( !item ) return; item->mTabLevel = tabLevel; mVisibleItems.push_back( item ); if ( bool( mProfile->mFont ) && item->mText ) { S32 width = ( tabLevel + 1 ) * mTabSize + mProfile->mFont->getStrWidth(item->mText); if ( mNumImages > 0 ) width += mImageBounds[0].extent.x; if ( width > mMaxWidth ) mMaxWidth = width; } // if expanded, then add all the children items as well if ( item->mState.test( Item::Expanded ) ) { Item * child = item->mChild; while ( child ) { buildItem( child, tabLevel + 1 ); child = child->mNext; } } } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::buildVisibleTree() { mMaxWidth = 0; mVisibleItems.clear(); // build the root items Item * traverse = mRoot; while(traverse) { buildItem(traverse, 0); traverse = traverse->mNext; } // adjust the GuiArrayCtrl mCellSize.set(mMaxWidth+1, mItemHeight); setSize(Point2I(1, mVisibleItems.size())); // Now, if in a scroll control, make sure we are positioned correctly: GuiControl *pappy = getParent(); if ( !pappy || !dynamic_cast( pappy ) ) return; Point2I diff( pappy->mBounds.extent.x - mBounds.extent.x, pappy->mBounds.extent.y - mBounds.extent.y ); Point2I newPos( ( ( diff.x >= 0 ) ? 0 : ( mBounds.point.x < diff.x ) ? diff.x : mBounds.point.x ), ( ( diff.y >= 0 ) ? 0 : ( mBounds.point.y < diff.y ) ? diff.y : mBounds.point.y ) ); if ( newPos != mBounds.point ) resize( newPos, mBounds.extent ); } //------------------------------------------------------------------------------ S32 GuiTreeViewCtrl::insertItem(S32 parentId, const char * text, const char * value, 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); } // create an item (assigns id) Item * item = createItem(); // fill the data item->mText = new char[dStrlen( text ) + 1]; dStrcpy( item->mText, text ); item->mValue = new char[dStrlen( value ) + 1]; dStrcpy( item->mValue, value ); item->mNormalImage = normalImage; item->mExpandedImage = expandedImage; // root level? if(parentId == 0) { // insert back if(mRoot) { Item * traverse = mRoot; while(traverse->mNext) traverse = traverse->mNext; traverse->mNext = item; item->mPrevious = traverse; } else mRoot = item; mFlags.set(RebuildVisible); } else { Item * parent = mItems[parentId-1]; // insert back 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; if(parent->mState.test(Item::Expanded)) mFlags.set(RebuildVisible); } // if(mFlags.test(RebuildVisible)) buildVisibleTree(); return(item->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; if(itemId == mSelectedItem) { // Select the previous visible item: if ( item->mPrevious ) { Item* temp = item->mPrevious; while ( temp->mChild && temp->mState.test( Item::Expanded ) ) { temp = temp->mChild; while ( temp->mNext ) temp = temp->mNext; } selectItem( temp->mId, true ); buildVisibleTree(); } // or select parent: else if ( item->mParent ) { selectItem( item->mParent->mId, true ); buildVisibleTree(); } } destroyItem(item); if(mFlags.test(RebuildVisible)) buildVisibleTree(); return(true); } //------------------------------------------------------------------------------ S32 GuiTreeViewCtrl::getFirstRootItem() { if(!mRoot) return(0); return(mRoot->mId); } //------------------------------------------------------------------------------ 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 ) { Item* item = getItem( itemId ); if ( !item ) { Con::errorf( ConsoleLogEntry::General, "GuiTreeViewCtrl::moveItemUp: invalid item id!"); return; } Item* prevItem = item->mPrevious; if ( !prevItem ) { Con::errorf( ConsoleLogEntry::General, "GuiTreeViewCtrl::moveItemUp: no previous sibling - how'd this get called?"); return; } if ( prevItem->mPrevious ) prevItem->mPrevious->mNext = item; else if ( item->mParent ) item->mParent->mChild = item; if ( item->mNext ) item->mNext->mPrevious = prevItem; item->mPrevious = prevItem->mPrevious; prevItem->mNext = item->mNext; item->mNext = prevItem; prevItem->mPrevious = item; buildVisibleTree(); } //------------------------------------------------------------------------------ bool GuiTreeViewCtrl::onWake() { if(!Parent::onWake()) return(false); mImagesHandle = TextureHandle(mImagesBitmap, BitmapKeepTexture); if(!bool(mImagesHandle)) return(false); mImageBounds.setSize(mNumImages); if(!mImagesHandle.getBitmap()) return(false); // ugh if(!createBitmapArray(mImagesHandle.getBitmap(), &mImageBounds[0], 1, mNumImages)) return(false); return(true); } void GuiTreeViewCtrl::onSleep() { mImagesHandle = 0; mImageBounds.clear(); Parent::onSleep(); } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::onPreRender() { Parent::onPreRender(); } //------------------------------------------------------------------------------ bool GuiTreeViewCtrl::hitTest(const Point2I & pnt, Item* & item, BitSet32 & flags) { 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 it AssertFatal(cell.y < mVisibleItems.size(), "GuiTreeViewCtrl::onMouseDown: invalid cell"); item = mVisibleItems[cell.y]; S32 min = mTabSize * item->mTabLevel; // left of icon/text? if(pos.x < min) return(true); // check image S32 image = item->mState.test(Item::Expanded) ? item->mExpandedImage : item->mNormalImage; if((image >= 0) && (image < mNumImages)) min += mImageBounds[image].extent.x; // image? if(pos.x < min) { flags.set(OnImage); return(true); } // offset min += mTextOffset; // check text min += mProfile->mFont->getStrWidth(item->mText); if(pos.x < min) flags.set(OnText); return(true); } bool GuiTreeViewCtrl::selectItem(S32 itemId, bool select) { Item * item = getItem(itemId); if(!item) { Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::selectItem: invalid item id!"); return(false); } if(mSelectedItem) { Item * selected = getItem(mSelectedItem); selected->mState.set(Item::Selected, false); char buf[16]; dSprintf(buf, 16, "%d", mSelectedItem); Con::executef(this, 2, "onUnSelect", buf); mSelectedItem = 0; } if(select) { item->mState.set(Item::Selected, true); char buf[16]; dSprintf(buf, 16, "%d", item->mId); Con::executef(this, 2, "onSelect", buf); mSelectedItem = item->mId; } setUpdate(); return(true); } bool GuiTreeViewCtrl::expandItem(S32 itemId, bool expand) { Item * item = getItem(itemId); if(!item) { Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::expandItem: invalid item id!"); return(false); } if(item->mState.test(Item::Expanded) == expand) return(true); // expand parents if(expand) { while(item) { item->mState.set(Item::Expanded, true); item = item->mParent; } } else item->mState.set(Item::Expanded, false); // rebuild buildVisibleTree(); 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->mText ? item->mText : ""); } const char * GuiTreeViewCtrl::getItemValue(S32 itemId) { Item * item = getItem(itemId); if(!item) { Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::getItemValue: invalid item id!"); return(""); } return(item->mValue ? item->mValue : ""); } 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; } delete [] item->mText; item->mText = new char[dStrlen( newText ) + 1]; dStrcpy( item->mText, newText ); delete [] item->mValue; item->mValue = new char[dStrlen( newValue ) + 1]; dStrcpy( item->mValue, newValue ); // Update the widths and such: buildVisibleTree(); return true; } //------------------------------------------------------------------------------ bool GuiTreeViewCtrl::onKeyDown( const GuiEvent& event ) { if ( !mVisible || !mActive || !mAwake ) return true; // All the keyboard functionality requires a selected item, so if none exists... if ( !mSelectedItem ) return true; Item* item = getItem( mSelectedItem ); if ( !item ) return true; // Explorer-esque Navigation... switch( event.keyCode ) { case KEY_UP: // Select previous visible item: if ( item->mPrevious ) { item = item->mPrevious; while ( item->mChild && item->mState.test( Item::Expanded ) ) { item = item->mChild; while ( item->mNext ) item = item->mNext; } selectItem( item->mId, true ); buildVisibleTree(); return true; } // or select parent: if ( item->mParent ) { selectItem( item->mParent->mId, true ); buildVisibleTree(); return true; } break; case KEY_DOWN: // Selected child if it is visible: if ( item->mChild && item->mState.test( Item::Expanded ) ) { selectItem( item->mChild->mId, true ); buildVisibleTree(); return true; } // or select next sibling (recursively): do { if ( item->mNext ) { selectItem( item->mNext->mId, true ); buildVisibleTree(); return true; } item = item->mParent; } while ( item ); break; case KEY_LEFT: // Contract current menu: if ( item->mState.test( Item::Expanded ) ) { expandItem( item->mId, false ); buildVisibleTree(); return true; } // or select parent: if ( item->mParent ) { selectItem( item->mParent->mId, true ); buildVisibleTree(); return true; } break; case KEY_RIGHT: // Expand selected item: if ( item->mChild ) { if ( !item->mState.test( Item::Expanded ) ) { expandItem( item->mId, true ); buildVisibleTree(); return true; } // or select child: selectItem( item->mChild->mId, true ); buildVisibleTree(); return true; } break; } // Not processed, so pass the event on: return Parent::onKeyDown( event ); } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::onMouseDown(const GuiEvent & event) { if( !mActive || !mAwake || !mVisible ) { Parent::onMouseDown(event); return; } if ( mProfile->mCanKeyFocus ) setFirstResponder(); Item * item = 0; BitSet32 hitFlags; S32 prevSelected = mSelectedItem; // if(!hitTest(event.mousePoint, item, hitFlags)) return; // if(event.modifier & SI_CTRL) selectItem(item->mId, !item->mState.test(Item::Selected)); else selectItem(item->mId, true); if ( hitFlags.test( OnText ) && ( event.mouseClickCount > 1 ) && ( prevSelected == mSelectedItem ) && mAltConsoleCommand[0] ) Con::evaluate( mAltConsoleCommand ); if(!item->mChild) return; // if ( mFullRowSelect || hitFlags.test( OnImage ) ) { item->mState.toggle( Item::Expanded ); buildVisibleTree(); } } //------------------------------------------------------------------------------ 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 ); else 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 = 0; BitSet32 hitFlags; // if(!hitTest(event.mousePoint, item, hitFlags)) return; selectItem(item->mId, true); // char bufs[2][32]; dSprintf(bufs[0], 32, "%d", item->mId); dSprintf(bufs[1], 32, "%d %d", event.mousePoint.x, event.mousePoint.y); Con::executef(this, 3, "onRightMouseDown", bufs[0], bufs[1]); } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::onRenderCell(Point2I offset, Point2I cell, bool, bool ) { AssertFatal(cell.y < mVisibleItems.size(), "GuiTreeViewCtrl::onRenderCell: invalid cell"); Item * item = mVisibleItems[cell.y]; // draw the bitmap S32 image = item->mState.test(Item::Expanded) ? item->mExpandedImage : item->mNormalImage; U32 textOffset = mTextOffset; if((image >= 0) && (image < mNumImages)) { dglClearBitmapModulation(); dglDrawBitmapSR(mImagesHandle, Point2I(offset.x + mTabSize * item->mTabLevel, offset.y), mImageBounds[image]); textOffset += mImageBounds[image].extent.x; } if ( item->mState.test( Item::Selected ) ) { dglDrawRectFill( RectI( offset.x + ( mTabSize * item->mTabLevel ), offset.y, mCellSize.x, mCellSize.y ), mProfile->mFillColorHL ); dglSetBitmapModulation( mProfile->mFontColorHL ); } else dglSetBitmapModulation( item->mState.test( Item::MouseOverText ) ? mProfile->mFontColorHL : mProfile->mFontColor ); dglDrawText( mProfile->mFont, Point2I( textOffset + offset.x + ( mTabSize * item->mTabLevel ), offset.y ), item->mText, mProfile->mFontColors ); } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::initPersistFields() { Parent::initPersistFields(); addField("tabSize", TypeS32, Offset(mTabSize, GuiTreeViewCtrl)); addField("imagesBitmap", TypeString, Offset(mImagesBitmap, GuiTreeViewCtrl)); addField("numImages", TypeS32, Offset(mNumImages, GuiTreeViewCtrl)); addField("textOffset", TypeS32, Offset(mTextOffset, GuiTreeViewCtrl)); addField("fullRowSelect", TypeBool, Offset(mFullRowSelect, GuiTreeViewCtrl)); addField("itemHeight", TypeS32, Offset(mItemHeight, GuiTreeViewCtrl)); } //------------------------------------------------------------------------------ static S32 cInsertItem(SimObject * obj, S32 argc, const char ** argv) { argc; GuiTreeViewCtrl * tree = static_cast(obj); return(tree->insertItem(dAtoi(argv[2]), argv[3], argv[4], 0, 1)); } static const char * cGetItemText(SimObject * obj, S32, const char ** argv) { GuiTreeViewCtrl * tree = static_cast(obj); return(tree->getItemText(dAtoi(argv[2]))); } static const char * cGetItemValue(SimObject * obj, S32, const char ** argv) { GuiTreeViewCtrl * tree = static_cast(obj); return(tree->getItemValue(dAtoi(argv[2]))); } static bool cEditItem(SimObject * obj, S32, const char ** argv) { GuiTreeViewCtrl* tree = static_cast(obj); return(tree->editItem(dAtoi(argv[2]), argv[3], argv[4])); } static bool cRemoveItem(SimObject * obj, S32, const char ** argv) { GuiTreeViewCtrl * tree = static_cast(obj); return(tree->removeItem(dAtoi(argv[2]))); } static void cClear(SimObject * obj, S32, const char **) { GuiTreeViewCtrl * tree = static_cast(obj); tree->removeItem(0); } static bool cSelectItem(SimObject * obj, S32 argc, const char ** argv) { GuiTreeViewCtrl * tree = static_cast(obj); S32 id = dAtoi(argv[2]); bool select = true; if(argc == 4) select = dAtob(argv[3]); return(tree->selectItem(id, select)); } static bool cExpandItem(SimObject * obj, S32 argc, const char ** argv) { GuiTreeViewCtrl * tree = static_cast(obj); S32 id = dAtoi(argv[2]); bool expand = true; if(argc == 4) expand = dAtob(argv[3]); return(tree->expandItem(id, expand)); } static S32 cGetFirstRootItem(SimObject * obj, S32, const char **) { GuiTreeViewCtrl * tree = static_cast(obj); return(tree->getFirstRootItem()); } static S32 cGetChild(SimObject * obj, S32, const char ** argv) { GuiTreeViewCtrl * tree = static_cast(obj); return(tree->getChildItem(dAtoi(argv[2]))); } static S32 cGetParent(SimObject * obj, S32, const char ** argv) { GuiTreeViewCtrl * tree = static_cast(obj); return(tree->getParentItem(dAtoi(argv[2]))); } static S32 cGetNextSibling(SimObject * obj, S32, const char ** argv) { GuiTreeViewCtrl * tree = static_cast(obj); return(tree->getNextSiblingItem(dAtoi(argv[2]))); } static S32 cGetPrevSibling(SimObject * obj, S32, const char ** argv) { GuiTreeViewCtrl * tree = static_cast(obj); return(tree->getPrevSiblingItem(dAtoi(argv[2]))); } static S32 cGetItemCount(SimObject * obj, S32, const char **) { GuiTreeViewCtrl * tree = static_cast(obj); return(tree->getItemCount()); } static S32 cGetSelectedItem(SimObject * obj, S32, const char **) { GuiTreeViewCtrl* tree = static_cast(obj); return ( tree->getSelectedItem() ); } static void cMoveItemUp(SimObject* obj, S32, const char** argv) { GuiTreeViewCtrl* tree = static_cast(obj); tree->moveItemUp( dAtoi( argv[2] ) ); } void GuiTreeViewCtrl::consoleInit() { Con::addCommand("GuiTreeViewCtrl", "insertItem", cInsertItem, "tree.insertItem(parent, name, value, normalImage, expandedImage);", 4, 7); Con::addCommand("GuiTreeViewCtrl", "getItemText", cGetItemText, "tree.getItemText(item)", 3, 3); Con::addCommand("GuiTreeViewCtrl", "getItemValue", cGetItemValue, "tree.getItemValue(item)", 3, 3); Con::addCommand("GuiTreeViewCtrl", "editItem", cEditItem, "tree.editItem(item, \"text\", \"value\")", 5, 5); Con::addCommand("GuiTreeViewCtrl", "removeItem", cRemoveItem, "tree.removeItem(item);", 3, 3); Con::addCommand("GuiTreeViewCtrl", "clear", cClear, "tree.clear();", 2, 2); Con::addCommand("GuiTreeViewCtrl", "selectItem", cSelectItem, "tree.selectItem(item, );", 3, 4); Con::addCommand("GuiTreeViewCtrl", "expandItem", cExpandItem, "tree.expandItem(item, );", 3, 4); Con::addCommand("GuiTreeViewCtrl", "getFirstRootItem", cGetFirstRootItem, "tree.getFirstRootItem();", 2, 2); Con::addCommand("GuiTreeViewCtrl", "getChild", cGetChild, "tree.getChild(item);", 3, 3); Con::addCommand("GuiTreeViewCtrl", "getParent", cGetParent, "tree.getParent(item);", 3, 3); Con::addCommand("GuiTreeViewCtrl", "getNextSibling", cGetNextSibling, "tree.getNextSibling(item);", 3, 3); Con::addCommand("GuiTreeViewCtrl", "getPrevSibling", cGetPrevSibling, "tree.getPrevSibling(item);", 3, 3); Con::addCommand("GuiTreeViewCtrl", "getItemCount", cGetItemCount, "tree.getItemCount();", 2, 2); Con::addCommand("GuiTreeViewCtrl", "getSelectedItem", cGetSelectedItem, "tree.getSelectedItem();", 2, 2); Con::addCommand("GuiTreeViewCtrl", "moveItemUp", cMoveItemUp, "tree.moveItemUp(item);", 3, 3); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ static void cGuiTreeViewOpen(SimObject *obj, S32, const char **argv) { GuiTreeView *tree = static_cast(obj); SimSet *treeRoot = NULL; SimObject* target = Sim::findObject(argv[2]); if (target) treeRoot = dynamic_cast(target); if (! treeRoot) Sim::findObject(RootGroupId, treeRoot); tree->setTreeRoot(treeRoot); } void GuiTreeView::consoleInit() { Con::addCommand("GuiTreeView", "open", cGuiTreeViewOpen, "treeView.open(obj)", 3, 3); } void GuiTreeView::initPersistFields() { Parent::initPersistFields(); addField("allowMultipleSelections", TypeBool, Offset(mAllowMultipleSelections, GuiTreeView)); addField("recurseSets", TypeBool, Offset(mRecurseSets, GuiTreeView)); } GuiTreeView::GuiTreeView() { VECTOR_SET_ASSOCIATION(mObjectList); mAllowMultipleSelections = false; mRecurseSets = false; mSize = Point2I(1, 0); } bool GuiTreeView::onWake() { if (! Parent::onWake()) return false; //get the bitmap texture handle mTextureHandle = mProfile->mTextureHandle; //get the font mFont = mProfile->mFont; bool result = createBitmapArray(mTextureHandle.getBitmap(), mBitmapBounds, 1, BmpCount); AssertFatal(result, "Failed to create the bitmap array"); //init the size mCellSize.set(640, mBitmapBounds[BmpParentContinue].extent.y); return(true); } void GuiTreeView::onSleep() { Parent::onSleep(); mTextureHandle = NULL; mFont = NULL; } void GuiTreeView::buildTree(SimSet *srcObj, S32 srcLevel, S32 srcParentIndex) { //loop through the children of the src object SimSet::iterator i; for(i = srcObj->begin(); i != srcObj->end(); i++) { //push the child mObjectList.push_back(ObjNode(srcLevel, *i, i+1 == srcObj->end(), srcParentIndex)); //if the child is expanded, build the tree under the child SimSet *g = dynamic_cast(*i); if(g && g->isExpanded()) { buildTree(g, srcLevel + 1, mObjectList.size() - 1); } } } void GuiTreeView::setTreeRoot(SimSet *srcObj) { if (srcObj) { mObjectList.clear(); //push the root mObjectList.push_back(ObjNode(0, srcObj, true, -1)); if(srcObj->isExpanded()) { buildTree(srcObj, 1, 0); } //set the size setSize(Point2I(1, mObjectList.size())); mRootObject = srcObj; } } void GuiTreeView::onMouseDown(const GuiEvent &event) { if(! mActive) { Parent::onMouseDown(event); return; } Point2I pt = globalToLocalCoord(event.mousePoint); //find out which cell was hit Point2I cell((pt.x < 0 ? -1 : pt.x / mCellSize.x), (pt.y < 0 ? -1 : pt.y / mCellSize.y)); if (cell.x >= 0 && cell.x < mSize.x && cell.y >= 0 && cell.y < mSize.y) { //see where in the cell the hit happened ObjNode *hit = &mObjectList[cell.y]; S32 statusWidth = mBitmapBounds[BmpParentContinue].extent.x; S32 statusOffset = statusWidth * hit->level; //if we clicked on the expanded icon SimSet *grp = dynamic_cast(hit->object); if (pt.x >= statusOffset && pt.x <= statusOffset + statusWidth && grp && grp->size()) { grp->setExpanded(! grp->isExpanded()); setTreeRoot(mRootObject); } //if we clicked on the object's name... else if (pt.x >= 2 + ((hit->level + 1) * statusWidth)) { if(!hit->object) return; if(mAllowMultipleSelections) { // calls 'onInspect' for the clicked object and 'onSelect/onUnselect' for // every object (for set recursion) if(event.modifier & SI_CTRL) toggleSelected(hit->object); else if(event.modifier & SI_SHIFT) selectObject(hit->object); else if(event.modifier & SI_ALT) setInstantGroup(hit->object); else setSelected(hit->object); // inspectObject(hit->object); } else // will not call 'onInspect' setSelected(hit->object); } } } GuiTreeView::ObjNode * GuiTreeView::getHitNode(const GuiEvent & event) { Point2I pt = globalToLocalCoord(event.mousePoint); //find out which cell was hit Point2I cell((pt.x < 0 ? -1 : pt.x / mCellSize.x), (pt.y < 0 ? -1 : pt.y / mCellSize.y)); if (cell.x >= 0 && cell.x < mSize.x && cell.y >= 0 && cell.y < mSize.y) { //see where in the cell the hit happened ObjNode *hit = &mObjectList[cell.y]; if(!hit->object) return(0); S32 statusWidth = mBitmapBounds[BmpParentContinue].extent.x; //if we clicked on the object's name... if (pt.x >= 2 + ((hit->level + 1) * statusWidth)) return(hit); } return(0); } void GuiTreeView::onRightMouseDown(const GuiEvent & event) { if(!mActive) { Parent::onMouseDown(event); return; } ObjNode * hit; if(!(hit = getHitNode(event))) return; if(!hit->object) return; setSelected(hit->object); } void GuiTreeView::onRightMouseUp(const GuiEvent & event) { if(!mActive) { Parent::onMouseDown(event); return; } ObjNode * hit; if(!(hit = getHitNode(event))) return; if(!hit->object) return; // char buf1[32]; dSprintf(buf1, sizeof(buf1), "%d %d", event.mousePoint.x, event.mousePoint.y); char buf2[16]; dSprintf(buf2, sizeof(buf2), "%d", hit->object->getId()); Con::executef(this, 3, "onContextMenu", buf1, buf2); } void GuiTreeView::setInstantGroup(SimObject * obj) { // make sure a group SimGroup * grp = dynamic_cast(obj); if(!grp) return; char buf[16]; dSprintf(buf, sizeof(buf), "%d", grp->getId()); Con::setVariable("instantGroup", buf); } void GuiTreeView::inspectObject(SimObject * obj) { char buf[16]; dSprintf(buf, sizeof(buf), "%d", obj->getId()); Con::executef(this, 2, "onInspect", buf); } void GuiTreeView::selectObject(SimObject * obj) { if(mRecurseSets) { SimSet * set = dynamic_cast(obj); if(set) for(SimSet::iterator itr = set->begin(); itr != set->end(); itr++) selectObject(*itr); } // obj->setSelected(true); // char buf[16]; dSprintf(buf, sizeof(buf), "%d", obj->getId()); Con::executef(this, 2, "onSelect", buf); } void GuiTreeView::unselectObject(SimObject * obj) { if(mRecurseSets) { SimSet * set = dynamic_cast(obj); if(set) for(SimSet::iterator itr = set->begin(); itr != set->end(); itr++) unselectObject(*itr); } // obj->setSelected(false); // char buf[16]; dSprintf(buf, sizeof(buf), "%d", obj->getId()); Con::executef(this, 2, "onUnselect", buf); } void GuiTreeView::clearSelected() { for(U32 i = 0; i < mObjectList.size(); i++) if(mObjectList[i].object->isSelected()) unselectObject(mObjectList[i].object); } void GuiTreeView::toggleSelected(SimObject * obj) { if(obj->isSelected()) unselectObject(obj); else selectObject(obj); } void GuiTreeView::setSelected(SimObject *selObj) { clearSelected(); selectObject(selObj); } void GuiTreeView::onPreRender() { setTreeRoot(mRootObject); } void GuiTreeView::onRenderCell(Point2I offset, Point2I cell, bool, bool) { Point2I cellOffset = offset; ObjNode *obj = &(mObjectList[cell.y]); ObjNode *prev = cell.y ? &(mObjectList[cell.y - 1]) : NULL; /* S32 bitmap = (mVBarEnabled ? ((curHitRegion == UpArrow && stateDepressed) ? BmpStates * BmpUp + BmpHilite : BmpStates * BmpUp) : BmpStates * BmpUp + BmpDisabled); dglClearBitmapModulation(); dglDrawBitmapSR(mTextureHandle, pos, mBitmapBounds[bitmap]); */ S32 statusWidth = mBitmapBounds[BmpParentContinue].extent.x; bool sel = obj->object->isSelected(); //draw the background behind the selected cell if (sel) { RectI selRect(Point2I(cellOffset.x + 2, cellOffset.y), Point2I(mBounds.extent.x - 2, mBounds.extent.y)); dglDrawRectFill(selRect, ColorI(255, 255, 255)); } // check if instantGroup - should draw a bitmap or something else... const char * instantGroupName = Con::getVariable("instantGroup"); SimGroup * instantGroup = dynamic_cast(Sim::findObject(instantGroupName)); if(instantGroup && instantGroup->getId() == obj->object->getId()) { RectI selRect(Point2I(cellOffset.x + 2, cellOffset.y), Point2I(mBounds.extent.x - 2, mBounds.extent.y)); dglDrawRectFill(selRect, ColorI(200, 200, 200)); } //find the status bmp index S32 bmpIndex = -1; SimSet *grp = dynamic_cast(obj->object); if(grp && grp->size()) { if(grp->isExpanded()) bmpIndex = BmpParentOpen; else bmpIndex = BmpParentClosed; } else bmpIndex = BmpNone; //add to the index based on the status if (! prev || obj->level == 0) { if (!obj->lastInGroup) bmpIndex += 1; } else if (obj->lastInGroup) bmpIndex += 2; else bmpIndex += 3; //now we have our bmpIndex, draw the status if (bmpIndex >= 0) { dglClearBitmapModulation(); dglDrawBitmapSR(mTextureHandle, Point2I(cellOffset.x + (obj->level * statusWidth), cellOffset.y), mBitmapBounds[bmpIndex]); } //draw in all the required continuation lines S32 parent = obj->parentIndex; while(parent != -1) { ObjNode *p = &(mObjectList[parent]); if (!p->lastInGroup) { dglClearBitmapModulation(); dglDrawBitmapSR(mTextureHandle, Point2I(cellOffset.x + (p->level * statusWidth), cellOffset.y), mBitmapBounds[BmpParentContinue]); } parent = p->parentIndex; } //draw the name - JFF temp hidden/locked char buf[256]; if(obj->object->isHidden()) dSprintf(buf, sizeof(buf), "(H) %d: %s - %s", obj->object->getId(), obj->object->getName() ? obj->object->getName() : "", obj->object->getClassName()); else if(obj->object->isLocked()) dSprintf(buf, sizeof(buf), "(L) %d: %s - %s", obj->object->getId(), obj->object->getName() ? obj->object->getName() : "", obj->object->getClassName()); else dSprintf(buf, sizeof(buf), "%d: %s - %s", obj->object->getId(), obj->object->getName() ? obj->object->getName() : "", obj->object->getClassName()); dglSetBitmapModulation(sel ? mProfile->mFontColorHL : mProfile->mFontColor); dglDrawText(mFont, Point2I(cellOffset.x + (obj->level + 1) * statusWidth, cellOffset.y), buf, mProfile->mFontColors); }