| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499 | //-----------------------------------------------------------------------------// Copyright (c) 2012 GarageGames, LLC//// Permission is hereby granted, free of charge, to any person obtaining a copy// of this software and associated documentation files (the "Software"), to// deal in the Software without restriction, including without limitation the// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or// sell copies of the Software, and to permit persons to whom the Software is// furnished to do so, subject to the following conditions://// The above copyright notice and this permission notice shall be included in// all copies or substantial portions of the Software.//// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS// IN THE SOFTWARE.//-----------------------------------------------------------------------------#include "gui/core/guiCanvas.h"#include "gui/controls/guiPopUpCtrl.h"#include "console/consoleTypes.h"#include "console/engineAPI.h"#include "gui/core/guiDefaultControlRender.h"#include "gfx/primBuilder.h"#include "gfx/gfxDrawUtil.h"#include "console/engineAPI.h"static ColorI colorWhite(255,255,255); //  Added// Function to return the number of columns in 'string' given delimeters in 'set'static U32 getColumnCount(const char *string, const char *set){   U32 count = 0;   U8 last = 0;   while(*string)   {      last = *string++;      for(U32 i =0; set[i]; i++)      {         if(last == set[i])         {            count++;            last = 0;            break;         }         }   }   if(last)      count++;   return count;}   // Function to return the 'index' column from 'string' given delimeters in 'set'static const char *getColumn(const char *string, char* returnbuff, U32 index, const char *set){   U32 sz;   while(index--)   {      if(!*string)         return "";      sz = dStrcspn(string, set);      if (string[sz] == 0)         return "";      string += (sz + 1);       }   sz = dStrcspn(string, set);   if (sz == 0)      return "";   char *ret = returnbuff;   dStrncpy(ret, string, sz);   ret[sz] = '\0';   return ret;}   GuiPopUpBackgroundCtrl::GuiPopUpBackgroundCtrl(GuiPopUpMenuCtrl *ctrl, GuiPopupTextListCtrl *textList){   mPopUpCtrl = ctrl;   mTextList = textList;}void GuiPopUpBackgroundCtrl::onMouseDown(const GuiEvent &event){   mPopUpCtrl->mBackgroundCancel = true; //  Set that the user didn't click within the text list.  Replaces the line above.   mPopUpCtrl->closePopUp();}//------------------------------------------------------------------------------GuiPopupTextListCtrl::GuiPopupTextListCtrl(){   mPopUpCtrl = NULL;}//------------------------------------------------------------------------------GuiPopupTextListCtrl::GuiPopupTextListCtrl(GuiPopUpMenuCtrl *ctrl){   mPopUpCtrl = ctrl;}//------------------------------------------------------------------------------//------------------------------------------------------------------------------//void GuiPopUpTextListCtrl::onCellSelected( Point2I /*cell*/ )//{//   // Do nothing, the parent control will take care of everything...//}void GuiPopupTextListCtrl::onCellSelected( Point2I cell ){   //  The old function is above.  This new one will only call the the select   //      functions if we were not cancelled by a background click.   //  Check if we were cancelled by the user clicking on the Background ie: anywhere   //      other than within the text list.   if(mPopUpCtrl->mBackgroundCancel)      return;   if( isMethod( "onSelect" ) )      Con::executef(this, "onSelect", Con::getFloatArg(cell.x), Con::getFloatArg(cell.y));   //call the console function   execConsoleCallback();   //if (mConsoleCommand[0])   //   Con::evaluate(mConsoleCommand, false);}//------------------------------------------------------------------------------bool GuiPopupTextListCtrl::onKeyDown(const GuiEvent &event){   //if the control is a dead end, don't process the input:   if ( !mVisible || !mActive || !mAwake )      return false;   //see if the key down is a <return> or not   if ( event.modifier == 0 )   {      if ( event.keyCode == KEY_RETURN )      {         mPopUpCtrl->closePopUp();         return true;      }      else if ( event.keyCode == KEY_ESCAPE )      {         mSelectedCell.set( -1, -1 );         mPopUpCtrl->closePopUp();         return true;      }   }   //otherwise, pass the event to it's parent   return Parent::onKeyDown(event);}void GuiPopupTextListCtrl::onMouseUp(const GuiEvent &event){   Parent::onMouseUp( event );   mPopUpCtrl->closePopUp();}//------------------------------------------------------------------------------void GuiPopupTextListCtrl::onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver){   Point2I size;   getCellSize( size );   // Render a background color for the cell   if ( mouseOver )   {            RectI cellR( offset.x, offset.y, size.x, size.y );      GFX->getDrawUtil()->drawRectFill( cellR, mProfile->mFillColorHL );   }   else if ( selected )   {      RectI cellR( offset.x, offset.y, size.x, size.y );      GFX->getDrawUtil()->drawRectFill( cellR, mProfile->mFillColorSEL );   }   //  Define the default x offset for the text   U32 textXOffset = offset.x + mProfile->mTextOffset.x;   //  Do we also draw a colored box beside the text?   ColorI boxColor;   bool drawbox = mPopUpCtrl->getColoredBox( boxColor, mList[cell.y].id);   if(drawbox)   {      Point2I coloredboxsize(15,10);      RectI r(offset.x + mProfile->mTextOffset.x, offset.y+2, coloredboxsize.x, coloredboxsize.y);      GFX->getDrawUtil()->drawRectFill( r, boxColor);      GFX->getDrawUtil()->drawRect( r, ColorI(0,0,0));      textXOffset += coloredboxsize.x + mProfile->mTextOffset.x;   }   ColorI fontColor;   mPopUpCtrl->getFontColor( fontColor, mList[cell.y].id, selected, mouseOver );   GFX->getDrawUtil()->setBitmapModulation( fontColor );   //GFX->drawText( mFont, Point2I( offset.x + 4, offset.y ), mList[cell.y].text );   //  Get the number of columns in the cell   S32 colcount = getColumnCount(mList[cell.y].text, "\t");   //  Are there two or more columns?   if(colcount >= 2)   {      char buff[256];      // Draw the first column      getColumn(mList[cell.y].text, buff, 0, "\t");      GFX->getDrawUtil()->drawText( mFont, Point2I( textXOffset, offset.y ), buff ); //  Used mTextOffset as a margin for the text list rather than the hard coded value of '4'.      // Draw the second column to the right      getColumn(mList[cell.y].text, buff, 1, "\t");      S32 txt_w = mFont->getStrWidth(buff);      GFX->getDrawUtil()->drawText( mFont, Point2I( offset.x+size.x-mProfile->mTextOffset.x-txt_w, offset.y ), buff ); //  Used mTextOffset as a margin for the text list rather than the hard coded value of '4'.   } else   {      GFX->getDrawUtil()->drawText( mFont, Point2I( textXOffset, offset.y ), mList[cell.y].text ); //  Used mTextOffset as a margin for the text list rather than the hard coded value of '4'.   }}//------------------------------------------------------------------------------//------------------------------------------------------------------------------IMPLEMENT_CONOBJECT(GuiPopUpMenuCtrl);ConsoleDocClass( GuiPopUpMenuCtrl,   "@brief A control that allows to select a value from a drop-down list.\n\n"   "For a nearly identical GUI with additional features, use GuiPopUpMenuCtrlEx.\n\n"   "@tsexample\n"   "new GuiPopUpMenuCtrl()\n"   "{\n"   "  maxPopupHeight = \"200\";\n"   "  sbUsesNAColor = \"0\";\n"   "  reverseTextList = \"0\";\n"   "  bitmapBounds = \"16 16\";\n"   "  maxLength = \"1024\";\n"   "  position = \"56 31\";\n"   "  extent = \"64 64\";\n"   "  minExtent = \"8 2\";\n"   "  profile = \"GuiPopUpMenuProfile\";\n"   "  tooltipProfile = \"GuiToolTipProfile\";\n"   "};\n"   "@endtsexample\n\n"   "@note This is definitely going to be deprecated soon.\n\n"   "@see GuiPopUpMenuCtrlEx for more features and better explanations.\n"   "@ingroup GuiControls\n");GuiPopUpMenuCtrl::GuiPopUpMenuCtrl(void){   VECTOR_SET_ASSOCIATION(mEntries);   VECTOR_SET_ASSOCIATION(mSchemes);   mSelIndex = -1;   mActive = true;   mMaxPopupHeight = 200;   mScrollDir = GuiScrollCtrl::None;   mScrollCount = 0;   mLastYvalue = 0;   mIncValue = 0;   mRevNum = 0;   mInAction = false;   mMouseOver = false; //  Added   mRenderScrollInNA = false; //  Added   mBackgroundCancel = false; //  Added   mReverseTextList = false; //  Added - Don't reverse text list if displaying up   mBitmapName = StringTable->EmptyString(); //  Added   mBitmapBounds.set(16, 16); //  Added   mIdMax = -1;}//------------------------------------------------------------------------------GuiPopUpMenuCtrl::~GuiPopUpMenuCtrl(){}//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::initPersistFields(void){   addField("maxPopupHeight",           TypeS32,          Offset(mMaxPopupHeight, GuiPopUpMenuCtrl));   addField("sbUsesNAColor",            TypeBool,         Offset(mRenderScrollInNA, GuiPopUpMenuCtrl));   addField("reverseTextList",          TypeBool,         Offset(mReverseTextList, GuiPopUpMenuCtrl));   addField("bitmap",                   TypeFilename,     Offset(mBitmapName, GuiPopUpMenuCtrl));   addField("bitmapBounds",             TypePoint2I,      Offset(mBitmapBounds, GuiPopUpMenuCtrl));   Parent::initPersistFields();}//------------------------------------------------------------------------------DefineConsoleMethod( GuiPopUpMenuCtrl, add, void, (const char * name, S32 idNum, U32 scheme), ("", -1, 0), "(string name, int idNum, int scheme=0)"){   object->addEntry(name, idNum, scheme);}DefineConsoleMethod( GuiPopUpMenuCtrl, addScheme, void, (U32 id, ColorI fontColor, ColorI fontColorHL, ColorI fontColorSEL), ,    "(int id, ColorI fontColor, ColorI fontColorHL, ColorI fontColorSEL)"){   object->addScheme( id, fontColor, fontColorHL, fontColorSEL );}DefineConsoleMethod( GuiPopUpMenuCtrl, getText, const char*, (), , ""){   return object->getText();}DefineConsoleMethod( GuiPopUpMenuCtrl, clear, void, (), , "Clear the popup list."){   object->clear();}//FIXME: clashes with SimSet.sortDefineConsoleMethod(GuiPopUpMenuCtrl, sort, void, (), , "Sort the list alphabetically."){   object->sort();}//  Added to sort the entries by IDDefineConsoleMethod(GuiPopUpMenuCtrl, sortID, void, (), , "Sort the list by ID."){   object->sortID();}DefineConsoleMethod( GuiPopUpMenuCtrl, forceOnAction, void, (), , ""){   object->onAction();}DefineConsoleMethod( GuiPopUpMenuCtrl, forceClose, void, (), , ""){   object->closePopUp();}DefineConsoleMethod( GuiPopUpMenuCtrl, getSelected, S32, (), , "Gets the selected index"){   return object->getSelected();}DefineConsoleMethod( GuiPopUpMenuCtrl, setSelected, void, (S32 id,  bool scriptCallback), (true), "(int id, [scriptCallback=true])"){   object->setSelected( id, scriptCallback );}DefineConsoleMethod( GuiPopUpMenuCtrl, setFirstSelected, void, (bool scriptCallback), (true), "([scriptCallback=true])"){   object->setFirstSelected( scriptCallback );}DefineConsoleMethod( GuiPopUpMenuCtrl, setNoneSelected, void, (), , ""){   object->setNoneSelected();}DefineConsoleMethod( GuiPopUpMenuCtrl, getTextById, const char*, (S32 id), ,  "(int id)"){   return(object->getTextById(id));}DefineConsoleMethod( GuiPopUpMenuCtrl, changeTextById, void, ( S32 id, const char * text ), , "( int id, string text )" ){   object->setEntryText( id, text );}DefineConsoleMethod( GuiPopUpMenuCtrl, setEnumContent, void, (const char * className, const char * enumName), , "(string class, string enum)"              "This fills the popup with a classrep's field enumeration type info.\n\n"              "More of a helper function than anything.   If console access to the field list is added, "              "at least for the enumerated types, then this should go away.."){   AbstractClassRep * classRep = AbstractClassRep::getClassList();   // walk the class list to get our class   while(classRep)   {      if(!dStricmp(classRep->getClassName(), className))         break;      classRep = classRep->getNextClass();   }   // get it?   if(!classRep)   {      Con::warnf(ConsoleLogEntry::General, "failed to locate class rep for '%s'", className);      return;   }   // walk the fields to check for this one (findField checks StringTableEntry ptrs...)   U32 i;   for(i = 0; i < classRep->mFieldList.size(); i++)      if(!dStricmp(classRep->mFieldList[i].pFieldname, enumName))         break;   // found it?      if(i == classRep->mFieldList.size())   {         Con::warnf(ConsoleLogEntry::General, "failed to locate field '%s' for class '%s'", enumName, className);      return;   }   const AbstractClassRep::Field & field = classRep->mFieldList[i];   ConsoleBaseType* conType = ConsoleBaseType::getType( field.type );   // check the type   if( !conType->getEnumTable() )   {      Con::warnf(ConsoleLogEntry::General, "field '%s' is not an enumeration for class '%s'", enumName, className);      return;   }   // fill it   const EngineEnumTable& table = *( conType->getEnumTable() );   const U32 numValues = table.getNumValues();      for(i = 0; i < numValues; i++)      object->addEntry( table[i].getName(), table[i] );}//------------------------------------------------------------------------------DefineConsoleMethod( GuiPopUpMenuCtrl, findText, S32, (const char * text), , "(string text)"              "Returns the position of the first entry containing the specified text or -1 if not found."){   return( object->findText( text ) );   }//------------------------------------------------------------------------------DefineConsoleMethod( GuiPopUpMenuCtrl, size, S32, (), , "Get the size of the menu - the number of entries in it."){   return( object->getNumEntries() ); }//------------------------------------------------------------------------------DefineConsoleMethod( GuiPopUpMenuCtrl, replaceText, void, (bool doReplaceText), , "(bool doReplaceText)"){   object->replaceText(S32(doReplaceText));  }//------------------------------------------------------------------------------//  Addedbool GuiPopUpMenuCtrl::onWake(){   if ( !Parent::onWake() )      return false;   // Set the bitmap for the popup.   setBitmap( mBitmapName );   // Now update the Form Control's bitmap array, and possibly the child's too   mProfile->constructBitmapArray();   if ( mProfile->getChildrenProfile() )      mProfile->getChildrenProfile()->constructBitmapArray();   return true;}//------------------------------------------------------------------------------bool GuiPopUpMenuCtrl::onAdd(){   if ( !Parent::onAdd() )      return false;   mSelIndex = -1;   mReplaceText = true;   return true;}//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::onSleep(){   mTextureNormal = NULL; //  Added   mTextureDepressed = NULL; //  Added   Parent::onSleep();   closePopUp();  // Tests in function.}//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::clear(){   mEntries.setSize(0);   setText("");   mSelIndex = -1;   mRevNum = 0;   mIdMax = -1;}//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::clearEntry( S32 entry ){     if( entry == -1 )      return;   U32 i = 0;   for ( ; i < mEntries.size(); i++ )   {      if ( mEntries[i].id == entry )         break;   }   mEntries.erase( i );   if( mEntries.size() <= 0 )   {      mEntries.setSize(0);      setText("");      mSelIndex = -1;      mRevNum = 0;   }   else   {      if (entry < mSelIndex)      {         mSelIndex--;      }      else if( entry == mSelIndex )      {         setText("");         mSelIndex = -1;      }   }}//------------------------------------------------------------------------------DefineConsoleMethod( GuiPopUpMenuCtrl, clearEntry, void, (S32 entry), , "(S32 entry)"){   object->clearEntry(entry);}//------------------------------------------------------------------------------static S32 QSORT_CALLBACK textCompare(const void *a,const void *b){   GuiPopUpMenuCtrl::Entry *ea = (GuiPopUpMenuCtrl::Entry *) (a);   GuiPopUpMenuCtrl::Entry *eb = (GuiPopUpMenuCtrl::Entry *) (b);   return (dStrnatcasecmp(ea->buf, eb->buf));} //  Added to sort by entry ID//------------------------------------------------------------------------------static S32 QSORT_CALLBACK idCompare(const void *a,const void *b){   GuiPopUpMenuCtrl::Entry *ea = (GuiPopUpMenuCtrl::Entry *) (a);   GuiPopUpMenuCtrl::Entry *eb = (GuiPopUpMenuCtrl::Entry *) (b);   return ( (ea->id < eb->id) ? -1 : ((ea->id > eb->id) ? 1 : 0) );} //------------------------------------------------------------------------------//  Addedvoid GuiPopUpMenuCtrl::setBitmap( const char *name ){   mBitmapName = StringTable->insert( name );   if ( !isAwake() )      return;   if ( *mBitmapName )   {      char buffer[1024];      char *p;      dStrcpy(buffer, name);      p = buffer + dStrlen(buffer);      dStrcpy(p, "_n");      mTextureNormal = GFXTexHandle( (StringTableEntry)buffer, &GFXDefaultGUIProfile, avar("%s() - mTextureNormal (line %d)", __FUNCTION__, __LINE__) );      dStrcpy(p, "_d");      mTextureDepressed = GFXTexHandle( (StringTableEntry)buffer, &GFXDefaultGUIProfile, avar("%s() - mTextureDepressed (line %d)", __FUNCTION__, __LINE__) );      if ( !mTextureDepressed )         mTextureDepressed = mTextureNormal;   }   else   {      mTextureNormal = NULL;      mTextureDepressed = NULL;   }   setUpdate();}   //------------------------------------------------------------------------------void GuiPopUpMenuCtrl::sort(){   S32 selId = getSelected();      S32 size = mEntries.size();   if( size > 0 )      dQsort( mEntries.address(), size, sizeof(Entry), textCompare);         if( selId != -1 )      setSelected( selId, false );}// Added to sort by entry ID//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::sortID(){   S32 selId = getSelected();      S32 size = mEntries.size();   if( size > 0 )      dQsort( mEntries.address(), size, sizeof(Entry), idCompare);   if( selId != -1 )      setSelected( selId, false );}//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::addEntry( const char *buf, S32 id, U32 scheme ){   if( !buf )   {      //Con::printf( "GuiPopupMenuCtrlEx::addEntry - Invalid buffer!" );      return;   }      // Ensure that there are no other entries with exactly the same name   for ( U32 i = 0; i < mEntries.size(); i++ )   {      if ( dStrcmp( mEntries[i].buf, buf ) == 0 )         return;   }   // If we don't give an id, create one from mIdMax   if( id == -1 )      id = mIdMax + 1;      // Increase mIdMax when an id is greater than it   if( id > mIdMax )      mIdMax = id;   Entry e;   dStrcpy( e.buf, buf );   e.id = id;   e.scheme = scheme;   // see if there is a shortcut key   char * cp = dStrchr( e.buf, '~' );   e.ascii = cp ? cp[1] : 0;   //  See if there is a colour box defined with the text   char *cb = dStrchr( e.buf, '|' );   if ( cb )   {      e.usesColorBox = true;      cb[0] = '\0';      char* red = &cb[1];      cb = dStrchr(red, '|');      cb[0] = '\0';      char* green = &cb[1];      cb = dStrchr(green, '|');      cb[0] = '\0';      char* blue = &cb[1];      U32 r = dAtoi(red);      U32 g = dAtoi(green);      U32 b = dAtoi(blue);      e.colorbox = ColorI(r,g,b);   }    else   {      e.usesColorBox = false;   }   mEntries.push_back(e);   if ( mInAction && mTl )   {      // Add the new entry:      mTl->addEntry( e.id, e.buf );      repositionPopup();   }}//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::addScheme( U32 id, ColorI fontColor, ColorI fontColorHL, ColorI fontColorSEL ){   if ( !id )      return;   Scheme newScheme;   newScheme.id = id;   newScheme.fontColor = fontColor;   newScheme.fontColorHL = fontColorHL;   newScheme.fontColorSEL = fontColorSEL;   mSchemes.push_back( newScheme );}//------------------------------------------------------------------------------S32 GuiPopUpMenuCtrl::getSelected(){   if (mSelIndex == -1)      return 0;   return mEntries[mSelIndex].id;}//------------------------------------------------------------------------------bool GuiPopUpMenuCtrl::setEntryText( S32 id, const char* buf ){   const U32 numEntries = getNumEntries();   for( U32 i = 0; i < numEntries; i++ )   {      if( mEntries[ i ].id == id )      {         Entry& entry = mEntries[ i ];         dStrncpy( entry.buf, buf, sizeof( entry.buf ) );         entry.buf[ sizeof( entry.buf ) - 1 ] = '\0';         return true;      }   }   return false;}//------------------------------------------------------------------------------const char* GuiPopUpMenuCtrl::getTextById(S32 id){   for ( U32 i = 0; i < mEntries.size(); i++ )   {      if ( mEntries[i].id == id )         return( mEntries[i].buf );   }   return( "" );}//------------------------------------------------------------------------------S32 GuiPopUpMenuCtrl::findText( const char* text ){   for ( U32 i = 0; i < mEntries.size(); i++ )   {      if ( dStrcmp( text, mEntries[i].buf ) == 0 )         return( mEntries[i].id );           }   return( -1 );}//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::setSelected(S32 id, bool bNotifyScript ){   for( S32 i = 0; i < mEntries.size(); i++ )   {      if( id == mEntries[i].id )      {         i = ( mRevNum > i ) ? mRevNum - i : i;         mSelIndex = i;                  if( mReplaceText ) //  Only change the displayed text if appropriate.            setText( mEntries[ i ].buf );         // Now perform the popup action:                  if( bNotifyScript )         {            if( isMethod( "onSelect" ) )               Con::executef( this, "onSelect", Con::getIntArg( mEntries[ mSelIndex ].id ), mEntries[mSelIndex].buf );                           execConsoleCallback();         }                  return;      }   }   if( mReplaceText ) //  Only change the displayed text if appropriate.   {      setText("");   }   mSelIndex = -1;   if( bNotifyScript && isMethod( "onCancel" ) )      Con::executef( this, "onCancel" );   if( id == -1 )      return;   // Execute the popup console command:   if( bNotifyScript )      execConsoleCallback();}//------------------------------------------------------------------------------//  Added to set the first item as selected.void GuiPopUpMenuCtrl::setFirstSelected( bool bNotifyScript ){   if( mEntries.size() > 0 )   {      mSelIndex = 0;      if ( mReplaceText ) //  Only change the displayed text if appropriate.      {         setText( mEntries[0].buf );      }      // Execute the popup console command:      if( bNotifyScript )      {         if ( isMethod( "onSelect" ) )            Con::executef( this, "onSelect", Con::getIntArg( mEntries[ mSelIndex ].id ), mEntries[mSelIndex].buf );         execConsoleCallback();      }   }   else   {      if ( mReplaceText ) //  Only change the displayed text if appropriate.         setText("");            mSelIndex = -1;      if( bNotifyScript )      {         Con::executef( this, "onCancel" );         execConsoleCallback();      }   }}//------------------------------------------------------------------------------//  Added to set no items as selected.void GuiPopUpMenuCtrl::setNoneSelected(){   if ( mReplaceText ) //  Only change the displayed text if appropriate.   {      setText("");   }   mSelIndex = -1;}//------------------------------------------------------------------------------const char *GuiPopUpMenuCtrl::getScriptValue(){   return getText();}//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::onRender( Point2I offset, const RectI &updateRect ){   TORQUE_UNUSED(updateRect);   Point2I localStart;   if ( mScrollDir != GuiScrollCtrl::None )      autoScroll();   GFXDrawUtil* drawUtil = GFX->getDrawUtil();   RectI r( offset, getExtent() );   if ( mInAction )   {      S32 l = r.point.x, r2 = r.point.x + r.extent.x - 1;      S32 t = r.point.y, b = r.point.y + r.extent.y - 1;      // Do we render a bitmap border or lines?      if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() )      {         // Render the fixed, filled in border         renderFixedBitmapBordersFilled(r, 3, mProfile );      }       else      {         //renderSlightlyLoweredBox(r, mProfile);         drawUtil->drawRectFill( r, mProfile->mFillColor );      }      //  Draw a bitmap over the background?      if ( mTextureDepressed )      {         RectI rect(offset, mBitmapBounds);         drawUtil->clearBitmapModulation();         drawUtil->drawBitmapStretch( mTextureDepressed, rect );      }       else if ( mTextureNormal )      {         RectI rect(offset, mBitmapBounds);         drawUtil->clearBitmapModulation();         drawUtil->drawBitmapStretch( mTextureNormal, rect );      }      // Do we render a bitmap border or lines?      if ( !( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) )      {         drawUtil->drawLine( l, t, l, b, colorWhite );         drawUtil->drawLine( l, t, r2, t, colorWhite );         drawUtil->drawLine( l + 1, b, r2, b, mProfile->mBorderColor );         drawUtil->drawLine( r2, t + 1, r2, b - 1, mProfile->mBorderColor );      }   }   else         // TODO: Implement      // TODO: Add onMouseEnter() and onMouseLeave() and a definition of mMouseOver (see guiButtonBaseCtrl) for this to work.      if ( mMouseOver )       {         S32 l = r.point.x, r2 = r.point.x + r.extent.x - 1;         S32 t = r.point.y, b = r.point.y + r.extent.y - 1;         // Do we render a bitmap border or lines?         if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() )         {            // Render the fixed, filled in border            renderFixedBitmapBordersFilled( r, 2, mProfile );         }          else         {            drawUtil->drawRectFill( r, mProfile->mFillColorHL );         }         //  Draw a bitmap over the background?         if ( mTextureNormal )         {            RectI rect( offset, mBitmapBounds );            drawUtil->clearBitmapModulation();            drawUtil->drawBitmapStretch( mTextureNormal, rect );         }         // Do we render a bitmap border or lines?         if ( !( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) )         {            drawUtil->drawLine( l, t, l, b, colorWhite );            drawUtil->drawLine( l, t, r2, t, colorWhite );            drawUtil->drawLine( l + 1, b, r2, b, mProfile->mBorderColor );            drawUtil->drawLine( r2, t + 1, r2, b - 1, mProfile->mBorderColor );         }      }      else      {         // Do we render a bitmap border or lines?         if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() )         {            // Render the fixed, filled in border            renderFixedBitmapBordersFilled( r, 1, mProfile );         }          else         {            drawUtil->drawRectFill( r, mProfile->mFillColorNA );         }         //  Draw a bitmap over the background?         if ( mTextureNormal )         {            RectI rect(offset, mBitmapBounds);            drawUtil->clearBitmapModulation();            drawUtil->drawBitmapStretch( mTextureNormal, rect );         }         // Do we render a bitmap border or lines?         if ( !( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) )         {            drawUtil->drawRect( r, mProfile->mBorderColorNA );         }      }      //      renderSlightlyRaisedBox(r, mProfile); //  Used to be the only 'else' condition to mInAction above.      S32 txt_w = mProfile->mFont->getStrWidth(mText);      localStart.x = 0;      localStart.y = (getHeight() - (mProfile->mFont->getHeight())) / 2;      // align the horizontal      switch (mProfile->mAlignment)      {      case GuiControlProfile::RightJustify:         if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() )         {            // We're making use of a bitmap border, so take into account the            // right cap of the border.            RectI* bitmapBounds = mProfile->mBitmapArrayRects.address();            localStart.x = getWidth() - bitmapBounds[2].extent.x - txt_w;         }          else         {            localStart.x = getWidth() - txt_w;           }         break;      case GuiControlProfile::CenterJustify:         if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() )         {            // We're making use of a bitmap border, so take into account the            // right cap of the border.            RectI* bitmapBounds = mProfile->mBitmapArrayRects.address();            localStart.x = (getWidth() - bitmapBounds[2].extent.x - txt_w) / 2;         } else         {            localStart.x = (getWidth() - txt_w) / 2;         }         break;      default:         // GuiControlProfile::LeftJustify         if ( txt_w > getWidth() )         {            //  The width of the text is greater than the width of the control.            // In this case we will right justify the text and leave some space            // for the down arrow.            if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() )            {               // We're making use of a bitmap border, so take into account the               // right cap of the border.               RectI* bitmapBounds = mProfile->mBitmapArrayRects.address();               localStart.x = getWidth() - bitmapBounds[2].extent.x - txt_w;            }             else            {               localStart.x = getWidth() - txt_w - 12;            }         }          else         {            localStart.x = mProfile->mTextOffset.x; //  Use mProfile->mTextOffset as a controlable margin for the control's text.         }         break;      }      //  Do we first draw a coloured box beside the text?      ColorI boxColor;      bool drawbox = getColoredBox( boxColor, mSelIndex);      if ( drawbox )      {         Point2I coloredboxsize( 15, 10 );         RectI r( offset.x + mProfile->mTextOffset.x, offset.y + ( (getHeight() - coloredboxsize.y ) / 2 ), coloredboxsize.x, coloredboxsize.y );         drawUtil->drawRectFill( r, boxColor);         drawUtil->drawRect( r, ColorI(0,0,0));         localStart.x += coloredboxsize.x + mProfile->mTextOffset.x;      }      // Draw the text      Point2I globalStart = localToGlobalCoord( localStart );      ColorI fontColor   = mActive ? ( mInAction ? mProfile->mFontColor : mProfile->mFontColorNA ) : mProfile->mFontColorNA;      drawUtil->setBitmapModulation( fontColor ); //  was: (mProfile->mFontColor);      //  Get the number of columns in the text      S32 colcount = getColumnCount( mText, "\t" );      //  Are there two or more columns?      if ( colcount >= 2 )      {         char buff[256];         // Draw the first column         getColumn( mText, buff, 0, "\t" );         drawUtil->drawText( mProfile->mFont, globalStart, buff, mProfile->mFontColors );         // Draw the second column to the right         getColumn( mText, buff, 1, "\t" );         S32 txt_w = mProfile->mFont->getStrWidth( buff );         if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() )         {            // We're making use of a bitmap border, so take into account the            // right cap of the border.            RectI* bitmapBounds = mProfile->mBitmapArrayRects.address();            Point2I textpos = localToGlobalCoord( Point2I( getWidth() - txt_w - bitmapBounds[2].extent.x, localStart.y ) );            drawUtil->drawText( mProfile->mFont, textpos, buff, mProfile->mFontColors );         } else         {            Point2I textpos = localToGlobalCoord( Point2I( getWidth() - txt_w - 12, localStart.y ) );            drawUtil->drawText( mProfile->mFont, textpos, buff, mProfile->mFontColors );         }      } else      {         drawUtil->drawText( mProfile->mFont, globalStart, mText, mProfile->mFontColors );      }      // If we're rendering a bitmap border, then it will take care of the arrow.      if ( !(mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size()) )      {         //  Draw a triangle (down arrow)         S32 left = r.point.x + r.extent.x - 12;         S32 right = left + 8;         S32 middle = left + 4;         S32 top = r.extent.y / 2 + r.point.y - 4;         S32 bottom = top + 8;         PrimBuild::color( mProfile->mFontColor );         PrimBuild::begin( GFXTriangleList, 3 );            PrimBuild::vertex2fv( Point3F( (F32)left, (F32)top, 0.0f ) );            PrimBuild::vertex2fv( Point3F( (F32)right, (F32)top, 0.0f ) );            PrimBuild::vertex2fv( Point3F( (F32)middle, (F32)bottom, 0.0f ) );         PrimBuild::end();      }}//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::closePopUp(){   if ( !mInAction )      return;   // Get the selection from the text list:      if( !mBackgroundCancel )   {      mSelIndex = mTl->getSelectedCell().y;      mSelIndex = ( mRevNum >= mSelIndex && mSelIndex != -1 ) ? mRevNum - mSelIndex : mSelIndex;      if ( mSelIndex != -1 )      {         if ( mReplaceText )            setText( mEntries[mSelIndex].buf );         setIntVariable( mEntries[mSelIndex].id );      }   }   // Release the mouse:   mInAction = false;   mTl->mouseUnlock();   // Now perform the popup action:   if( mSelIndex != -1 && !mBackgroundCancel )   {      if ( isMethod( "onSelect" ) )         Con::executef( this, "onSelect", Con::getIntArg( mEntries[ mSelIndex ].id ), mEntries[mSelIndex].buf );      // Execute the popup console command:      execConsoleCallback();   }   else if ( isMethod( "onCancel" ) )      Con::executef( this, "onCancel" );   // Pop the background:   GuiCanvas *root = getRoot();   if ( root )      root->popDialogControl(mBackground);   // Kill the popup:   mBackground->removeObject( mSc );   mTl->deleteObject();   mSc->deleteObject();   mBackground->deleteObject();      mBackground = NULL;   mTl = NULL;   mSc = NULL;   // Set this as the first responder:   setFirstResponder();}//------------------------------------------------------------------------------bool GuiPopUpMenuCtrl::onKeyDown(const GuiEvent &event){   //if the control is a dead end, don't process the input:   if ( !mVisible || !mActive || !mAwake )      return false;   //see if the key down is a <return> or not   if ( event.keyCode == KEY_RETURN && event.modifier == 0 )   {      onAction();      return true;   }   //otherwise, pass the event to its parent   return Parent::onKeyDown( event );}//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::onAction(){   GuiControl *canCtrl = getParent();   addChildren();   GuiCanvas *root = getRoot();   Point2I windowExt = root->getExtent();   mBackground->resize( Point2I(0,0), root->getExtent() );      S32 textWidth = 0, width = getWidth();   const S32 textSpace = 2;   bool setScroll = false;   for ( U32 i = 0; i < mEntries.size(); ++i )      if ( S32(mProfile->mFont->getStrWidth( mEntries[i].buf )) > textWidth )         textWidth = mProfile->mFont->getStrWidth( mEntries[i].buf );   S32 sbWidth = mSc->getControlProfile()->mBorderThickness * 2 + mSc->scrollBarThickness(); //  Calculate the scroll bar width   if ( textWidth > ( getWidth() - sbWidth-mProfile->mTextOffset.x - mSc->getChildMargin().x * 2 ) ) //  The text draw area to test against is the width of the drop-down minus the scroll bar width, the text margin and the scroll bar child margins.   {      textWidth +=sbWidth + mProfile->mTextOffset.x + mSc->getChildMargin().x * 2; //  The new width is the width of the text plus the scroll bar width plus the text margin size plus the scroll bar child margins.      width = textWidth;      //  If a child margin is not defined for the scroll control, let's add      //      some space between the text and scroll control for readability      if(mSc->getChildMargin().x == 0)         width += textSpace;   }   mTl->setCellSize(Point2I(width, mProfile->mFont->getHeight() + textSpace));   for ( U32 j = 0; j < mEntries.size(); ++j )      mTl->addEntry( mEntries[j].id, mEntries[j].buf );   if ( mSelIndex >= 0 )      mTl->setSelectedCell( Point2I( 0, mSelIndex ) );   Point2I pointInGC = canCtrl->localToGlobalCoord( getPosition() );   Point2I scrollPoint( pointInGC.x, pointInGC.y + getHeight() );    //Calc max Y distance, so Scroll Ctrl will fit on window    S32 sbBorder = mSc->getControlProfile()->mBorderThickness * 2 + mSc->getChildMargin().y * 2;   S32 maxYdis = windowExt.y - pointInGC.y - getHeight() - sbBorder;   //If scroll bars need to be added   mRevNum = 0;   if ( maxYdis < mTl->getHeight() + sbBorder )   {      //Should we pop menu list above the button      if ( maxYdis < pointInGC.y )      {         if(mReverseTextList)            reverseTextList();         maxYdis = pointInGC.y;         //Does the menu need a scroll bar          if ( maxYdis < mTl->getHeight() + sbBorder )         {            setScroll = true;         }         //No scroll bar needed         else         {            maxYdis = mTl->getHeight() + sbBorder;         }         //  Added the next two lines         scrollPoint.set(pointInGC.x, pointInGC.y - maxYdis); //  Used to have the following on the end: '-1);'      }       //Scroll bar needed but Don't pop above button      else      {         setScroll = true;      }   }   //No scroll bar needed   else   {      maxYdis = mTl->getHeight() + sbBorder;   }   RectI newBounds = mSc->getBounds();   //offset it from the background so it lines up properly   newBounds.point = mBackground->globalToLocalCoord( scrollPoint );   if ( newBounds.point.x + width > mBackground->getWidth() )      if ( width - getWidth() > 0 )         newBounds.point.x -= width - getWidth();   newBounds.extent.set( width, maxYdis );   mSc->setBounds( newBounds );   mSc->registerObject();   mTl->registerObject();   mBackground->registerObject();   mSc->addObject( mTl );   mBackground->addObject( mSc );   mBackgroundCancel = false; //  Setup check if user clicked on the background instead of the text list (ie: didn't want to change their current selection).   root->pushDialogControl( mBackground, 99 );   if ( setScroll )   {      // Resize the text list     Point2I cellSize;     mTl->getCellSize( cellSize );     cellSize.x = width - mSc->scrollBarThickness() - sbBorder;     mTl->setCellSize( cellSize );     mTl->setWidth( cellSize.x );      if ( mSelIndex )         mTl->scrollCellVisible( Point2I( 0, mSelIndex ) );      else         mTl->scrollCellVisible( Point2I( 0, 0 ) );   }   mTl->setFirstResponder();   mInAction = true;}//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::addChildren(){   // Create Text List.   mTl = new GuiPopupTextListCtrl( this );   AssertFatal( mTl, "Failed to create the GuiPopUpTextListCtrl for the PopUpMenu" );   // Use the children's profile rather than the parent's profile, if it exists.   mTl->setControlProfile( mProfile->getChildrenProfile() ? mProfile->getChildrenProfile() : mProfile );    mTl->setField("noDuplicates", "false");   mSc = new GuiScrollCtrl;   AssertFatal( mSc, "Failed to create the GuiScrollCtrl for the PopUpMenu" );   GuiControlProfile *prof;   if ( Sim::findObject( "GuiScrollProfile", prof ) )   {      mSc->setControlProfile( prof );   }   else   {      // Use the children's profile rather than the parent's profile, if it exists.     mSc->setControlProfile( mProfile->getChildrenProfile() ? mProfile->getChildrenProfile() : mProfile );   }   mSc->setField( "hScrollBar", "AlwaysOff" );   mSc->setField( "vScrollBar", "dynamic" );   //if(mRenderScrollInNA) //  Force the scroll control to render using fillColorNA rather than fillColor   // mSc->mUseNABackground = true;   mBackground = new GuiPopUpBackgroundCtrl( this, mTl );   AssertFatal( mBackground, "Failed to create the GuiBackgroundCtrl for the PopUpMenu" );}//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::repositionPopup(){   if ( !mInAction || !mSc || !mTl )      return;   // I'm not concerned with this right now...}//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::reverseTextList(){   mTl->clear();   for ( S32 i = mEntries.size()-1; i >= 0; --i )      mTl->addEntry( mEntries[i].id, mEntries[i].buf );   // Don't lose the selected cell:   if ( mSelIndex >= 0 )      mTl->setSelectedCell( Point2I( 0, mEntries.size() - mSelIndex - 1 ) );    mRevNum = mEntries.size() - 1;}//------------------------------------------------------------------------------bool GuiPopUpMenuCtrl::getFontColor( ColorI &fontColor, S32 id, bool selected, bool mouseOver ){   U32 i;   Entry* entry = NULL;   for ( i = 0; i < mEntries.size(); i++ )   {      if ( mEntries[i].id == id )      {         entry = &mEntries[i];         break;      }   }   if ( !entry )      return( false );   if ( entry->scheme != 0 )   {      // Find the entry's color scheme:      for ( i = 0; i < mSchemes.size(); i++ )      {         if ( mSchemes[i].id == entry->scheme )         {            fontColor = selected ? mSchemes[i].fontColorSEL : mouseOver ? mSchemes[i].fontColorHL : mSchemes[i].fontColor;            return( true );         }      }   }   // Default color scheme...   fontColor = selected ? mProfile->mFontColorSEL : mouseOver ? mProfile->mFontColorHL : mProfile->mFontColorNA; //  Modified the final color choice from mProfile->mFontColor to mProfile->mFontColorNA   return( true );}//------------------------------------------------------------------------------//  Addedbool GuiPopUpMenuCtrl::getColoredBox( ColorI &fontColor, S32 id ){   U32 i;   Entry* entry = NULL;   for ( i = 0; i < mEntries.size(); i++ )   {      if ( mEntries[i].id == id )      {         entry = &mEntries[i];         break;      }   }   if ( !entry )      return false;   if ( entry->usesColorBox == false )      return false;   fontColor = entry->colorbox;   return true;}//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::onMouseDown( const GuiEvent &event ){   TORQUE_UNUSED(event);   if( !mVisible || !mActive || !mAwake )      return;   onAction();}//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::onMouseUp( const GuiEvent &event ){   TORQUE_UNUSED(event);}//------------------------------------------------------------------------------//  Addedvoid GuiPopUpMenuCtrl::onMouseEnter( const GuiEvent &event ){   mMouseOver = true;}//------------------------------------------------------------------------------//  Addedvoid GuiPopUpMenuCtrl::onMouseLeave( const GuiEvent &event ){   mMouseOver = false;}//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::setupAutoScroll( const GuiEvent &event ){   GuiControl *parent = getParent();   if ( !parent )       return;   Point2I mousePt = mSc->globalToLocalCoord( event.mousePoint );   mEventSave = event;         if ( mLastYvalue != mousePt.y )   {      mScrollDir = GuiScrollCtrl::None;      if ( mousePt.y > mSc->getHeight() || mousePt.y < 0 )      {         S32 topOrBottom = ( mousePt.y > mSc->getHeight() ) ? 1 : 0;         mSc->scrollTo( 0, topOrBottom );         return;      }         F32 percent = (F32)mousePt.y / (F32)mSc->getHeight();      if ( percent > 0.7f && mousePt.y > mLastYvalue )      {         mIncValue = percent - 0.5f;         mScrollDir = GuiScrollCtrl::DownArrow;      }      else if ( percent < 0.3f && mousePt.y < mLastYvalue )      {         mIncValue = 0.5f - percent;                  mScrollDir = GuiScrollCtrl::UpArrow;      }      mLastYvalue = mousePt.y;   }}//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::autoScroll(){   mScrollCount += mIncValue;   while ( mScrollCount > 1 )   {      mSc->autoScroll( mScrollDir );      mScrollCount -= 1;   }   mTl->onMouseMove( mEventSave );}//------------------------------------------------------------------------------void GuiPopUpMenuCtrl::replaceText(S32 boolVal){   mReplaceText = boolVal;}
 |