//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- #include "gui/controls/guiListBoxCtrl.h" IMPLEMENT_CONOBJECT(GuiListBoxCtrl); GuiListBoxCtrl::GuiListBoxCtrl() { mItems.clear(); mSelectedItems.clear(); mMultipleSelections = true; mFitParentWidth = true; mItemSize = Point2I(10,20); mLastClickItem = NULL; } GuiListBoxCtrl::~GuiListBoxCtrl() { clearItems(); } void GuiListBoxCtrl::initPersistFields() { Parent::initPersistFields(); addField( "AllowMultipleSelections", TypeBool, Offset( mMultipleSelections, GuiListBoxCtrl) ); addField( "FitParentWidth", TypeBool, Offset( mFitParentWidth, GuiListBoxCtrl) ); } bool GuiListBoxCtrl::onWake() { if( !Parent::onWake() ) return false; updateSize(); return true; } ////////////////////////////////////////////////////////////////////////// // Item Accessors ////////////////////////////////////////////////////////////////////////// ConsoleMethod( GuiListBoxCtrl, setMultipleSelection, void, 3, 3, "listBox.setMultipleSelection([true/false])" ) { object->setMultipleSelection( dAtob( argv[2] ) ); } ConsoleMethod( GuiListBoxCtrl, clearItems, void, 2, 2, "clearItems() - Clears all the items in the listbox" ) { object->clearItems(); } void GuiListBoxCtrl::clearItems() { // Free item list allocated memory while( mItems.size() ) deleteItem( 0 ); // Free our vector lists mItems.clear(); mSelectedItems.clear(); } ConsoleMethod( GuiListBoxCtrl, clearSelection, void, 2, 2, "clearSelection() - sets all currently selected items to unselected" ) { object->clearSelection(); } void GuiListBoxCtrl::clearSelection() { if( !mSelectedItems.size() ) return; VectorPtr::iterator i = mSelectedItems.begin(); for( ; i != mSelectedItems.end(); i++ ) (*i)->isSelected = false; mSelectedItems.clear(); } ConsoleMethod( GuiListBoxCtrl, setSelected, void, 3, 4, "setSelected(index, [true]/false) - sets the item at the index specified to selected or not") { bool value = true; if( argc == 4 ) value = dAtob( argv[3] ); if( value == true ) object->addSelection( dAtoi( argv[2] ) ); else object->removeSelection( dAtoi( argv[2] ) ); } void GuiListBoxCtrl::removeSelection( S32 index ) { // Range Check if( index >= mItems.size() || index < 0 ) { Con::warnf("GuiListBoxCtrl::removeSelection - index out of range!" ); return; } removeSelection( mItems[index], index ); } void GuiListBoxCtrl::removeSelection( LBItem *item, S32 index ) { if( !mSelectedItems.size() ) return; if( !item ) return; for( S32 i = 0 ; i < mSelectedItems.size(); i++ ) { if( mSelectedItems[i] == item ) { mSelectedItems.erase( &mSelectedItems[i] ); item->isSelected = false; Con::executef(this, 3, "onUnSelect", Con::getIntArg( index ), item->itemText); return; } } } void GuiListBoxCtrl::addSelection( S32 index ) { // Range Check if( index >= mItems.size() || index < 0 ) { Con::warnf("GuiListBoxCtrl::addSelection- index out of range!" ); return; } addSelection( mItems[index], index ); } void GuiListBoxCtrl::addSelection( LBItem *item, S32 index ) { if( !mMultipleSelections ) { if( !mSelectedItems.empty() ) { LBItem* selItem = mSelectedItems.front(); if( selItem != item ) clearSelection(); else return; } } else { if( !mSelectedItems.empty() ) { for( S32 i = 0; i < mSelectedItems.size(); i++ ) { if( mSelectedItems[ i ] == item ) return; } } } item->isSelected = true; mSelectedItems.push_front( item ); Con::executef(this, 3, "onSelect", Con::getIntArg( index ), item->itemText); } S32 GuiListBoxCtrl::getItemIndex( LBItem *item ) { if( mItems.empty() ) return -1; // Lookup the index of an item in our list, by the pointer to the item for( S32 i = 0; i < mItems.size(); i++ ) if( mItems[i] == item ) return i; return -1; } ConsoleMethod( GuiListBoxCtrl, getItemCount, S32, 2, 2, "getItemCount() - returns the number of items in the list") { return object->getItemCount(); } S32 GuiListBoxCtrl::getItemCount() { return mItems.size(); } ConsoleMethod( GuiListBoxCtrl, getSelCount, S32, 2, 2, "getSelCount() - returns the number of items currently selected") { return object->getSelCount(); } S32 GuiListBoxCtrl::getSelCount() { return mSelectedItems.size(); } ConsoleMethod( GuiListBoxCtrl, getSelectedItem, S32, 2, 2, "getSelectedItem() - returns the selected items index. " "If multiple selections exist it returns the first selected item" ) { return object->getSelectedItem(); } S32 GuiListBoxCtrl::getSelectedItem() { if( mSelectedItems.empty() || mItems.empty() ) return -1; S32 i = 0; for( S32 i = 0 ; i < mItems.size(); i++ ) if( mItems[i]->isSelected ) return i; return -1; } ConsoleMethod( GuiListBoxCtrl, getSelectedItems, const char*, 2, 2, "getSelectedItems() - returns a space delimited list " "of the selected items indexes in the list") { S32 selCount = object->getSelCount(); if( selCount == -1 || selCount == 0 ) return StringTable->lookup("-1"); else if( selCount == 1 ) return Con::getIntArg(object->getSelectedItem()); Vector selItems; object->getSelectedItems( selItems ); if( selItems.empty() ) return StringTable->lookup("-1"); UTF8 *retBuffer = Con::getReturnBuffer( selItems.size() * 4 ); dMemset( retBuffer, 0, selItems.size() * 4 ); Vector::iterator i = selItems.begin(); for( ; i != selItems.end(); i++ ) { UTF8 retFormat[12]; dSprintf( retFormat, 12, "%d ", (*i) ); dStrcat( retBuffer, retFormat ); } return retBuffer; } void GuiListBoxCtrl::getSelectedItems( Vector &Items ) { // Clear our return vector Items.clear(); // If there are no selected items, return an empty vector if( mSelectedItems.empty() ) return; for( S32 i = 0; i < mItems.size(); i++ ) if( mItems[i]->isSelected ) Items.push_back( i ); } ConsoleMethod(GuiListBoxCtrl, findItemText, S32, 3, 4, "listBox.findItemText( myItemText, [?caseSensitive - false] ) - Returns index of item with matching text") { bool bCaseSensitive = false; if( argc == 4 ) bCaseSensitive = dAtob( argv[3] ); return object->findItemText( argv[2], bCaseSensitive ); } S32 GuiListBoxCtrl::findItemText( StringTableEntry text, bool caseSensitive ) { // Check Proper Arguments if( !text || !text[0] || text == StringTable->lookup("") ) { Con::warnf("GuiListBoxCtrl::findItemText - No Text Specified!"); return -1; } // Check Items Exist. if( mItems.empty() ) return -1; // Lookup the index of an item in our list, by the pointer to the item for( S32 i = 0; i < mItems.size(); i++ ) { // Case Sensitive Compare? if( caseSensitive && ( dStrcmp( mItems[i]->itemText, text ) == 0 ) ) return i; else if (!caseSensitive && ( dStricmp( mItems[i]->itemText, text ) == 0 )) return i; } // Not Found! return -1; } ConsoleMethod(GuiListBoxCtrl, setCurSel, void, 3, 3, "setCurSel(index) - sets the currently selected item at the specified index") { object->setCurSel( dAtoi( argv[2] ) ); } void GuiListBoxCtrl::setCurSel( S32 index ) { // Range Check if( index >= mItems.size() ) { Con::warnf("GuiListBoxCtrl::setCurSel - index out of range!" ); return; } // If index -1 is specified, we clear the selection if( index == -1 ) { mSelectedItems.clear(); return; } // Add the selection addSelection( mItems[ index ], index ); } ConsoleMethod( GuiListBoxCtrl, setCurSelRange, void, 3, 4, "setCurSelRange(start,[stop]) - sets the current selection range from" " index start to stop. if no stop is specified it sets from start index to the end of the list") { if( argc == 4 ) object->setCurSelRange( dAtoi(argv[2]) , dAtoi( argv[3] ) ); else object->setCurSelRange( dAtoi(argv[2]), 999999 ); } void GuiListBoxCtrl::setCurSelRange( S32 start, S32 stop ) { // Verify Selection Range if( start < 0 ) start = 0; else if( start > mItems.size() ) start = mItems.size(); if( stop < 0 ) stop = 0; else if( stop > mItems.size() ) stop = mItems.size(); S32 iterStart = ( start < stop ) ? start : stop; S32 iterStop = ( start < stop ) ? stop : start; for( ; iterStart <= iterStop; iterStart++ ) addSelection( mItems[iterStart], iterStart ); } ConsoleMethod( GuiListBoxCtrl, addItem, void, 3, 4, "addItem(text, color) - adds an item to the end of the list with an optional color" ) { if(argc == 3) { object->addItem( argv[2] ); } else if(argc == 4) { U32 elementCount = GuiListBoxCtrl::getStringElementCount(argv[3]); if(elementCount == 3) { F32 red, green, blue; red = dAtof(GuiListBoxCtrl::getStringElement( argv[3], 0 )); green = dAtof(GuiListBoxCtrl::getStringElement( argv[3], 1 )); blue = dAtof(GuiListBoxCtrl::getStringElement( argv[3], 2 )); object->addItemWithColor( argv[2], ColorF(red, green, blue) ); } else { Con::warnf("GuiListBoxCtrl::addItem() - Invalid number of parameters for the color!"); } } else { Con::warnf("GuiListBoxCtrl::addItem() - Invalid number of parameters!"); } } S32 GuiListBoxCtrl::addItem( StringTableEntry text, void *itemData ) { // This just calls insert item at the end of the list return insertItem( mItems.size(), text, itemData ); } S32 GuiListBoxCtrl::addItemWithColor( StringTableEntry text, ColorF color, void *itemData ) { // This just calls insert item at the end of the list return insertItemWithColor( mItems.size(), text, color, itemData ); } ConsoleMethod(GuiListBoxCtrl, setItemColor, void, 4, 4, "(index, color)") { U32 elementCount = GuiListBoxCtrl::getStringElementCount(argv[3]); if(elementCount == 3) { F32 red = dAtof(GuiListBoxCtrl::getStringElement( argv[3], 0 )); F32 green = dAtof(GuiListBoxCtrl::getStringElement( argv[3], 1 )); F32 blue = dAtof(GuiListBoxCtrl::getStringElement( argv[3], 2 )); object->setItemColor( dAtoi(argv[2]), ColorF(red, green, blue) ); } else Con::warnf("GuiListBoxCtrl::addItem() - Invalid number of parameters for the color!"); } void GuiListBoxCtrl::setItemColor( S32 index, ColorF color ) { if ((index >= mItems.size()) || index < 0) { Con::warnf("GuiListBoxCtrl::setItemColor - invalid index"); return; } LBItem* item = mItems[index]; item->hasColor = true; item->color = color; } ConsoleMethod(GuiListBoxCtrl, clearItemColor, void, 3, 3, "(index)") { object->clearItemColor(dAtoi(argv[2])); } void GuiListBoxCtrl::clearItemColor( S32 index ) { if ((index >= mItems.size()) || index < 0) { Con::warnf("GuiListBoxCtrl::setItemColor - invalid index"); return; } LBItem* item = mItems[index]; item->hasColor = false; } ConsoleMethod( GuiListBoxCtrl, insertItem, void, 4, 4, "insertItem( text, index ) - inserts an item into the list at the specified index") { object->insertItem( dAtoi( argv[3] ), argv[2] ); } S32 GuiListBoxCtrl::insertItem( S32 index, StringTableEntry text, void *itemData ) { // If the index is greater than our list size, insert it at the end if( index >= mItems.size() ) index = mItems.size(); // Sanity checking if( !text ) { Con::warnf("GuiListBoxCtrl::insertItem - cannot add NULL string" ); return -1; } LBItem *newItem = new LBItem; if( !newItem ) { Con::warnf("GuiListBoxCtrl::insertItem - error allocating item memory!" ); return -1; } // Assign item data newItem->itemText = StringTable->insert(text); newItem->itemData = itemData; newItem->isSelected = false; newItem->hasColor = false; // Add to list mItems.insert(&mItems[index], newItem ); // Resize our list to fit our items updateSize(); // Return our index in list (last) return index; } S32 GuiListBoxCtrl::insertItemWithColor( S32 index, StringTableEntry text, ColorF color, void *itemData ) { // If the index is greater than our list size, insert it at the end if( index >= mItems.size() ) index = mItems.size(); // Sanity checking if( !text ) { Con::warnf("GuiListBoxCtrl::insertItem - cannot add NULL string" ); return -1; } if( color == ColorF(-1, -1, -1) ) { Con::warnf("GuiListBoxCtrl::insertItem - cannot add NULL color" ); return -1; } LBItem *newItem = new LBItem; if( !newItem ) { Con::warnf("GuiListBoxCtrl::insertItem - error allocating item memory!" ); return -1; } // Assign item data newItem->itemText = StringTable->insert(text); newItem->itemData = itemData; newItem->isSelected = false; newItem->hasColor = true; newItem->color = color; // Add to list mItems.insert(&mItems[index], newItem ); // Resize our list to fit our items updateSize(); // Return our index in list (last) return index; } ConsoleMethod ( GuiListBoxCtrl, deleteItem, void, 3, 3, "deleteItem(itemIndex)" ) { object->deleteItem( dAtoi( argv[2] ) ); } void GuiListBoxCtrl::deleteItem( S32 index ) { // Range Check if( index >= mItems.size() || index < 0 ) { Con::warnf("GuiListBoxCtrl::deleteItem - index out of range!" ); return; } // Grab our item LBItem* item = mItems[ index ]; if( !item ) { Con::warnf("GuiListBoxCtrl::deleteItem - Bad Item Data!" ); return; } // Remove it from the selected list. if( item->isSelected ) { for( VectorPtr::iterator i = mSelectedItems.begin(); i != mSelectedItems.end(); i++ ) { if( item == *i ) { mSelectedItems.erase_fast( i ); break; } } } // Remove it from the list mItems.erase( &mItems[ index ] ); // Free the memory associated with it delete item; } ConsoleMethod( GuiListBoxCtrl, getItemText, const char*, 3, 3, "getItemText(index) - returns the text of the item at the specified index") { return object->getItemText( dAtoi( argv[2] ) ); } StringTableEntry GuiListBoxCtrl::getItemText( S32 index ) { // Range Checking if( index > mItems.size() || index < 0 ) { Con::warnf( "GuiListBoxCtrl::getItemText - index out of range!" ); return StringTable->lookup(""); } return mItems[ index ]->itemText; } ConsoleMethod( GuiListBoxCtrl, setItemText, void, 4, 4, "setItemText(index, newtext) - sets the items text at the specified index" ) { object->setItemText( dAtoi( argv[2] ), argv[3] ); } void GuiListBoxCtrl::setItemText( S32 index, StringTableEntry text ) { // Sanity Checking if( !text ) { Con::warnf("GuiListBoxCtrl::setItemText - Invalid Text Specified!" ); return; } // Range Checking if( index > mItems.size() || index < 0 ) { Con::warnf( "GuiListBoxCtrl::getItemText - index out of range!" ); return; } mItems[ index ]->itemText = StringTable->insert( text ); } ////////////////////////////////////////////////////////////////////////// // Sizing Functions ////////////////////////////////////////////////////////////////////////// void GuiListBoxCtrl::updateSize() { if( !mProfile ) return; GFont *font = mProfile->mFont; GuiScrollCtrl* parent = dynamic_cast(getParent()); if ( mFitParentWidth && parent ) mItemSize.x = parent->getContentExtent().x; else { // Find the maximum width cell: S32 maxWidth = 1; for ( U32 i = 0; i < mItems.size(); i++ ) { S32 width = font->getStrWidth( mItems[i]->itemText ); if( width > maxWidth ) maxWidth = width; } mItemSize.x = maxWidth + 6; } mItemSize.y = font->getHeight() + 2; Point2I newExtent( mItemSize.x, mItemSize.y * mItems.size() ); resize( mBounds.point, newExtent ); } void GuiListBoxCtrl::parentResized(const Point2I &oldParentExtent, const Point2I &newParentExtent) { Parent::parentResized( oldParentExtent, newParentExtent ); updateSize(); } ////////////////////////////////////////////////////////////////////////// // Overrides ////////////////////////////////////////////////////////////////////////// void GuiListBoxCtrl::onRender( Point2I offset, const RectI &updateRect ) { RectI clipRect(updateRect.point, updateRect.extent); if( !mProfile ) return; // Save our original clip rect RectI oldClipRect = clipRect; for ( S32 i = 0; i < mItems.size(); i++) { S32 colorBoxSize = 0; ColorI boxColor = ColorI(0, 0, 0); // Only render visible items if ((i + 1) * mItemSize.y + offset.y < updateRect.point.y) continue; // Break our once we're no longer in visible item range if( i * mItemSize.y + offset.y >= updateRect.point.y + updateRect.extent.y) break; // Render color box if needed if(mItems[i]->hasColor) { // Set the size of the color box to be drawn next to the item text colorBoxSize = 3; boxColor = ColorI(mItems[i]->color); // Draw the box first ColorI black = ColorI(0, 0, 0); drawBox( Point2I(offset.x + mProfile->mTextOffset.x + colorBoxSize, offset.y + ( i * mItemSize.y ) + 8), colorBoxSize, black, boxColor ); } RectI itemRect = RectI( offset.x + mProfile->mTextOffset.x + (colorBoxSize * 2), offset.y + ( i * mItemSize.y ), mItemSize.x, mItemSize.y ); // Render our item onRenderItem( itemRect, mItems[i] ); } dglSetClipRect( oldClipRect ); } void GuiListBoxCtrl::onRenderItem( RectI itemRect, LBItem *item ) { if( item->isSelected ) dglDrawRectFill( itemRect, mProfile->mFillColor ); dglSetBitmapModulation(mProfile->mFontColor); renderJustifiedText(itemRect.point + Point2I( 2, 0 ), itemRect.extent, item->itemText); } void GuiListBoxCtrl::drawBox(const Point2I &box, S32 size, ColorI &outlineColor, ColorI &boxColor) { RectI r(box.x - size, box.y - size, 2 * size + 1, 2 * size + 1); r.inset(1, 1); dglDrawRectFill(r, boxColor); r.inset(-1, -1); dglDrawRect(r, outlineColor); } ////////////////////////////////////////////////////////////////////////// // Item Selections ////////////////////////////////////////////////////////////////////////// void GuiListBoxCtrl::onMouseDown( const GuiEvent &event ) { Point2I localPoint = globalToLocalCoord(event.mousePoint); S32 itemHit = ( localPoint.y < 0 ) ? -1 : (S32)mFloor( (F32)localPoint.y / (F32)mItemSize.y ); if ( itemHit >= mItems.size() || itemHit == -1 ) return; LBItem *hitItem = mItems[ itemHit ]; if ( hitItem == NULL ) return; // If we're not a multiple selection listbox, we simply select/unselect an item if( !mMultipleSelections ) { // No current selection? Just select the cell and move on S32 selItem = getSelectedItem(); if ( selItem != itemHit && selItem != -1 ) clearSelection(); // Set the current selection setCurSel( itemHit ); if( itemHit == selItem && event.mouseClickCount == 2 && isMethod("onDoubleClick") ) Con::executef( this, 2, "onDoubleClick" ); // Store the clicked item mLastClickItem = hitItem; // Evaluate the console command if we clicked the same item twice if( selItem == itemHit && event.mouseClickCount > 1 && mAltConsoleCommand[0] ) Con::evaluate( mAltConsoleCommand, false ); return; } // Deal with multiple selections if( event.modifier & SI_CTRL) { // Ctrl-Click toggles selection if( hitItem->isSelected ) { removeSelection( hitItem, itemHit ); // We return here when we deselect an item because we don't store last clicked when we deselect return; } else addSelection( hitItem, itemHit ); } else if( event.modifier & SI_SHIFT ) { if( !mLastClickItem ) addSelection( hitItem, itemHit ); else setCurSelRange( getItemIndex( mLastClickItem ), itemHit ); } else { if( getSelCount() != 0 ) { S32 selItem = getSelectedItem(); if( selItem != -1 && mItems[selItem] != hitItem ) clearSelection(); } addSelection( hitItem, itemHit ); } if( hitItem == mLastClickItem && event.mouseClickCount == 2 && isMethod("onDoubleClick") ) Con::executef( this, 2, "onDoubleClick" ); mLastClickItem = hitItem; } U32 GuiListBoxCtrl::getStringElementCount( const char* inString ) { // Non-whitespace chars. static const char* set = " \t\n"; // End of string. if ( *inString == 0 ) return 0; U32 wordCount = 0; U8 search = 0; // Search String. while( *inString ) { // Get string element. search = *inString; // End of string? if ( search == 0 ) break; // Move to next element. inString++; // Search for seperators. for( U32 i = 0; set[i]; i++ ) { // Found one? if( search == set[i] ) { // Yes... search = 0; break; } } // Found a seperator? if ( search == 0 ) continue; // We've found a non-seperator. wordCount++; // Search for end of non-seperator. while( 1 ) { // Get string element. search = *inString; // End of string? if ( search == 0 ) break; // Move to next element. inString++; // Search for seperators. for( U32 i = 0; set[i]; i++ ) { // Found one? if( search == set[i] ) { // Yes... search = 0; break; } } // Found Seperator? if ( search == 0 ) break; } // End of string? if ( *inString == 0 ) { // Bah! break; } } // We've finished. return wordCount; } //------------------------------------------------------------------------------ // Get String Element. //------------------------------------------------------------------------------ const char* GuiListBoxCtrl::getStringElement( const char* inString, const U32 index ) { // Non-whitespace chars. static const char* set = " \t\n"; U32 wordCount = 0; U8 search = 0; const char* pWordStart = NULL; // End of string? if ( *inString != 0 ) { // No, so search string. while( *inString ) { // Get string element. search = *inString; // End of string? if ( search == 0 ) break; // Move to next element. inString++; // Search for seperators. for( U32 i = 0; set[i]; i++ ) { // Found one? if( search == set[i] ) { // Yes... search = 0; break; } } // Found a seperator? if ( search == 0 ) continue; // Found are word? if ( wordCount == index ) { // Yes, so mark it. pWordStart = inString-1; } // We've found a non-seperator. wordCount++; // Search for end of non-seperator. while( 1 ) { // Get string element. search = *inString; // End of string? if ( search == 0 ) break; // Move to next element. inString++; // Search for seperators. for( U32 i = 0; set[i]; i++ ) { // Found one? if( search == set[i] ) { // Yes... search = 0; break; } } // Found Seperator? if ( search == 0 ) break; } // Have we found our word? if ( pWordStart ) { // Yes, so we've got our word... // Result Buffer. static char buffer[4096]; // Calculate word length. const U32 length = inString - pWordStart - ((*inString)?1:0); // Copy Word. dStrncpy( buffer, pWordStart, length); buffer[length] = '\0'; // Return Word. return buffer; } // End of string? if ( *inString == 0 ) { // Bah! break; } } } // Sanity! AssertFatal( false, "t2dSceneObject::getStringElement() - Couldn't find specified string element!" ); // Didn't find it return " "; }