#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" #include "editor/worldEditor.h" #include "sim/sceneObject.h" IMPLEMENT_CONOBJECT(GuiTreeViewCtrl); GuiTreeViewCtrl::Item::Item() { // Just null our memory, it's safest! mState = 0; mScriptInfo.mText = 0; mScriptInfo.mValue = 0; } GuiTreeViewCtrl::Item::~Item() { if(mState.test(InspectorData)) { // DO NO THING } else { 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; } 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 } 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; } 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 ); } else { return dStrlen(getText()); } // Should never be here. (This useful comment provided by BJG) } void GuiTreeViewCtrl::Item::getDisplayText(U32 bufLen, char *buf) { FrameAllocatorMarker txtAlloc; char *displayText = NULL; if(mState.test(Item::InspectorData)) { // Inspector data! SimObject *obj = getObject(); if(obj) { // For the prefix, max size is 3 + 1 + 3 + 1 = 8, so 16 for safety char *prefix = (char*)txtAlloc.alloc(16); // Alright, let's get some info... bool isH = obj->isHidden(); bool isL = obj->isLocked(); // z - not really used. // Calculate prefix string. (BJG TODO - simpler to have lookup table?) dSprintf(prefix, sizeof(prefix), "%s%s%s%s", (isH ? "" : ""), (isH && isL ? "" : ""), (isL ? "" : ""), (isL || isH ? "" : "") ); dSprintf(buf, bufLen, "%s%d: %s - %s", prefix, obj->getId(), obj->getName() ? obj->getName() : "", obj->getClassName() ); } } else { // Script data! (copy it in) dStrncpy(buf, getText(), bufLen); } } const S32 GuiTreeViewCtrl::Item::getDisplayTextWidth(GFont *font) { FrameAllocatorMarker txtAlloc; U32 bufLen = getDisplayTextLength(); 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 *s = dynamic_cast( (SimObject*)mInspectorInfo.mObject); if (s->size() > 0) return s->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->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 *res = mChild; while(res) { // Skip non-inspector data stuff. if(res->mState.test(InspectorData)) { if(res->getObject() == obj) break; // Whoa. } res = res->mNext; } // If the loop terminated we are NULL, otherwise we have the result in res. return res; } 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 *res = mChild; while(res) { // check the script value of the item against the specified value if( dStricmp( res->mScriptInfo.mValue, Value ) == 0 ) return res; res = res->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 = 0; mRoot = 0; mInstantGroup = 0; mItemCount = 0; mSelectedItem = 0; mStart = 0; 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); mFlags.set(IsDeletable); mDestroyOnSleep = true; mSupportMouseDragging = true; mMultipleSelections = true; } 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)); 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 * 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 if (icon) item->mIcon = icon; else item->mIcon = Default; //default icon to stick next to an item item->mNext = item->mPrevious = item->mParent = item->mChild = 0; item->mState.clear(); item->setText(0); item->setValue(0); item->mState = 0; item->mTabLevel = 0; mItemCount++; return(item); } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::destroyChildren(Item * item, Item * parent) { if (!item || item == parent) return; if (item->isParent()) destroyChildren(item->mChild, parent); else if(item->mNext) destroyChildren(item->mNext, parent); else { // destroy the item and back up Item * prevItem = item->mPrevious; destroyItem(item); destroyChildren(prevItem, 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 *obj = item->getObject(); if( obj ) obj->deleteObject(); item->setObject(NULL); } else { // Clean up the memory... if ( item->getText() ) { delete [] item->getText(); item->setText(NULL); } if ( item->getValue() ) { delete [] item->getValue(); 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++) delete mItems[i]; mItems.clear(); // clear the free list while(mItemFreeList) { Item * next = mItemFreeList->mNext; delete mItemFreeList; mItemFreeList = next; } mVisibleItems.clear(); mSelectedItems.clear(); // mItemFreeList = 0; mRoot = 0; mItemCount = 0; mSelectedItem = 0; mDraggedToItem = 0; } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::buildItem( Item* item, U32 tabLevel ) { if (!item ) return; // If it's inspector data, make sure we still have it, if not, kill it. if(item->isInspectorData()) { // Blast an item if it doesn't have a corresponding SimObject... if(!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 true the item has been removed. if(!onVirtualParentBuild(item)) return; } item->mTabLevel = tabLevel; mVisibleItems.push_back( item ); if ( bool( mProfile->mFont ) ) { 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() ) { Item * child = item->mChild; while ( child ) { // Bit of a hack so we can safely remove items as we // traverse. Item *tmp = child; child = child->mNext; buildItem( tmp, tabLevel + 1 ); } } } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::buildVisibleTree() { mMaxWidth = 0; mVisibleItems.clear(); // Update the flags. mFlags.clear(RebuildVisible); // 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())); syncSelection(); } //------------------------------------------------------------------------------ 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 *pappy = dynamic_cast( getParent() ); if ( !pappy ) { 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 * item = createItem(icon); // fill the data item->setText( new char[dStrlen( text ) + 1] ); dStrcpy( item->getText(), text ); item->setValue( new char[dStrlen( value ) + 1] ); dStrcpy( item->getValue(), value ); item->setNormalImage( normalImage ); item->setExpandedImage( 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->isExpanded()) 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; // Dispose of any children... if (item->mChild) destroyChildren(item->mChild, item); // Kill the item... destroyItem(item); // Update the rendered tree... buildVisibleTree(); return true; } //------------------------------------------------------------------------------ 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* 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; } // Diddle the linked list! 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; // 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->mNext; while(temp) { if (temp->isInspectorData()) break; temp = temp->mNext; } if (temp) parentSet->reOrder(item->getObject(), temp->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) * 64); char* token = dStrtok( drawText, ":" ); // Count the number of icons and store them. while (token && numIcons < MaxIcons) { dSprintf( buf, sizeof( buf ), "%s", token ); mIconTable[numIcons] = TextureHandle( buf, BitmapKeepTexture ); token = dStrtok( NULL, ":" ); numIcons++; } return true; } //------------------------------------------------------------------------------ void GuiTreeViewCtrl::onPreRender() { Parent::onPreRender(); // Update every render in case new objects are added buildVisibleTree(); } //------------------------------------------------------------------------------ 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()); } } 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()->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()->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; } } } 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()->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); } void GuiTreeViewCtrl::onItemSelected( Item *item ) { char buf[16]; dSprintf(buf, 16, "%d", item->mId); if (item->isInspectorData()) { 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()) { if(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); } 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 { while (!mSelectedItems.empty()) { Item * item = mSelectedItems.last(); setItemSelected(item->mId,false); if ( item->mParent ) { deleteItem( item ); // If we're still the last item (ie, haven't removed ourselves) // then remove it manually. if(item == mSelectedItems.back()) mSelectedItems.pop_back(); } } } mSelected.clear(); mSelectedItems.clear(); } //------------------------------------------------------------------------------ // 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 && mFlags.test(IsDeletable)) { // Don't delete the root! if (mSelectedItems.empty()) return true; //this may be fighting with the world editor delete deleteSelection(); return true; } } // 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) //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; } } } else { if (mDebug) { if (item->isInspectorData()) Con::printf("Item: %s",item->getObject()->getName()); if (newItem->isInspectorData()) Con::printf("Parent: %s",newItem->getObject()->getName()); Con::printf("dragged onto an item"); } // just dragging on an item, so just put the item into it. // 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); SimObject *firstObject = newParentSet->first(); if (firstObject) newParentSet->reOrder(item->getObject(), firstObject); } 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( !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()) { 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()) 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()) 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()) { 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()) 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 ) { // 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()->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()->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 ); } } // draw the icon associated with the item if ( !dStrcmp(item->getObject()->getClassName(), "SimGroup") ) { if (item->isParent()) { if (item->isExpanded()) item->mIcon = SimGroup1; else item->mIcon = SimGroup2; } else item->mIcon = SimGroup2; } if (mInstantGroup == item->mId) { if (item->isInspectorData()) setInstantGroup(item->getObject()); // make sure the instant group is properly set if (item->isInspectorData()) { if ( !dStrcmp(item->getObject()->getClassName(), "SimGroup") ) { 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->getDisplayTextLength() + 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; if(item->isInspectorData()) { // If it's an inspector data, we just do a straight determination. fontColor = item->mState.test( Item::Selected ) ? mProfile->mFontColorSEL : ( item->mState.test( Item::MouseOverText ) ? mProfile->mFontColorHL : mProfile->mFontColor ); } else { // If it's script data, our color is determined by whether or not we're // zero. A bit gross and hacky, maybe this should go. if ( dStrcmp( item->getValue(), "0" ) ) fontColor = item->mState.test( Item::Selected ) ? mProfile->mFontColorSEL : ( item->mState.test( Item::MouseOverText ) ? mProfile->mFontColorHL : mProfile->mFontColor ); else fontColor = item->mState.test( Item::Selected ) ? mAltFontColorSE : ( item->mState.test( Item::MouseOverText ) ? mAltFontColorHL : mAltFontColor ); } if (item->mState.test(Item::Selected)) { dglDrawRectFill(drawRect, mProfile->mFillColorHL); } else if (item->mState.test(Item::MouseOverText)) { dglDrawRectFill(drawRect, mProfile->mFontColorNA); } 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(); } 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) { 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) { if(!item->isInspectorData()) return true; // Skip the next stuff unless we're expanded... if(!item->isExpanded()) 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, removeSelection, void, 3, 3, "(deselects an item)") { S32 id = dAtoi(argv[2]); object->removeSelection(id); } 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)); } 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, 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, 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, moveItemUp, void, 3, 3, "(TreeItemId item)") { object->moveItemUp( 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 ); }