mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 12:44:46 +00:00
Streamlined the toolbar for the gui and world editors to utilize a stack, making the behavior and manipulation of toolbar elements significantly more consistent. Added Settings and Asset Browser buttons to both gui and world editor toolbars for easier access. Moved all tool toolbars over to work with the stack system to make them more consistent and better formatting Added saving of asset browser's last position and extent so it remembers it on load. Added editor setting to close the asset browser after completing a drag-n-drop action. Added keybind to editor keybind list, making space toggle the asset browser
1549 lines
47 KiB
C++
1549 lines
47 KiB
C++
//-----------------------------------------------------------------------------
|
|
// 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 boxBounds(offset.x + mProfile->mTextOffset.x, offset.y+2, coloredboxsize.x, coloredboxsize.y);
|
|
GFX->getDrawUtil()->drawRectFill(boxBounds, boxColor);
|
|
GFX->getDrawUtil()->drawRect(boxBounds, 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
|
|
|
|
INIT_IMAGEASSET_ARRAY(Bitmap, 0);
|
|
INIT_IMAGEASSET_ARRAY(Bitmap, 1);
|
|
|
|
mBitmapBounds.set(16, 16); // Added
|
|
mIdMax = -1;
|
|
mBackground = NULL;
|
|
mTl = NULL;
|
|
mSc = NULL;
|
|
mReplaceText = false;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
GuiPopUpMenuCtrl::~GuiPopUpMenuCtrl()
|
|
{
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void GuiPopUpMenuCtrl::initPersistFields(void)
|
|
{
|
|
addField("maxPopupHeight", TypeS32, Offset(mMaxPopupHeight, GuiPopUpMenuCtrl));
|
|
addField("sbUsesNAColor", TypeBool, Offset(mRenderScrollInNA, GuiPopUpMenuCtrl));
|
|
addField("reverseTextList", TypeBool, Offset(mReverseTextList, GuiPopUpMenuCtrl));
|
|
|
|
addProtectedField("bitmap", TypeImageFilename, Offset(mBitmapName, GuiPopUpMenuCtrl), _setBitmaps, defaultProtectedGetFn, "");
|
|
addProtectedField("bitmapAsset", TypeImageAssetId, Offset(mBitmapAssetId, GuiPopUpMenuCtrl), _setBitmaps, defaultProtectedGetFn, "");
|
|
|
|
addField("bitmapBounds", TypePoint2I, Offset(mBitmapBounds, GuiPopUpMenuCtrl));
|
|
|
|
Parent::initPersistFields();
|
|
}
|
|
|
|
bool GuiPopUpMenuCtrl::_setBitmaps(void* obj, const char* index, const char* data)
|
|
{
|
|
bool ret = false;
|
|
GuiPopUpMenuCtrl* object = static_cast<GuiPopUpMenuCtrl*>(obj);
|
|
|
|
object->setBitmap(data);
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
DefineEngineMethod( GuiPopUpMenuCtrl, add, void, (const char * name, S32 idNum, U32 scheme), ("", -1, 0), "(string name, int idNum, int scheme=0)")
|
|
{
|
|
object->addEntry(name, idNum, scheme);
|
|
}
|
|
|
|
DefineEngineMethod( 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 );
|
|
}
|
|
|
|
DefineEngineMethod( GuiPopUpMenuCtrl, getText, const char*, (), , "")
|
|
{
|
|
return object->getText();
|
|
}
|
|
|
|
DefineEngineMethod( GuiPopUpMenuCtrl, clear, void, (), , "Clear the popup list.")
|
|
{
|
|
object->clear();
|
|
}
|
|
|
|
//FIXME: clashes with SimSet.sort
|
|
DefineEngineMethod(GuiPopUpMenuCtrl, sort, void, (), , "Sort the list alphabetically.")
|
|
{
|
|
object->sort();
|
|
}
|
|
|
|
// Added to sort the entries by ID
|
|
DefineEngineMethod(GuiPopUpMenuCtrl, sortID, void, (), , "Sort the list by ID.")
|
|
{
|
|
object->sortID();
|
|
}
|
|
|
|
DefineEngineMethod( GuiPopUpMenuCtrl, forceOnAction, void, (), , "")
|
|
{
|
|
object->onAction();
|
|
}
|
|
|
|
DefineEngineMethod( GuiPopUpMenuCtrl, forceClose, void, (), , "")
|
|
{
|
|
object->closePopUp();
|
|
}
|
|
|
|
DefineEngineMethod( GuiPopUpMenuCtrl, getSelected, S32, (), , "Gets the selected index")
|
|
{
|
|
return object->getSelected();
|
|
}
|
|
|
|
DefineEngineMethod( GuiPopUpMenuCtrl, setSelected, void, (S32 id, bool scriptCallback), (true), "(int id, [scriptCallback=true])")
|
|
{
|
|
object->setSelected( id, scriptCallback );
|
|
}
|
|
|
|
DefineEngineMethod( GuiPopUpMenuCtrl, setFirstSelected, void, (bool scriptCallback), (true), "([scriptCallback=true])")
|
|
{
|
|
object->setFirstSelected( scriptCallback );
|
|
|
|
}
|
|
|
|
DefineEngineMethod( GuiPopUpMenuCtrl, setNoneSelected, void, (), , "")
|
|
{
|
|
object->setNoneSelected();
|
|
}
|
|
|
|
DefineEngineMethod( GuiPopUpMenuCtrl, getTextById, const char*, (S32 id), , "(int id)")
|
|
{
|
|
return(object->getTextById(id));
|
|
}
|
|
|
|
DefineEngineMethod( GuiPopUpMenuCtrl, changeTextById, void, ( S32 id, const char * text ), , "( int id, string text )" )
|
|
{
|
|
object->setEntryText( id, text );
|
|
}
|
|
|
|
DefineEngineMethod( 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] );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
DefineEngineMethod( 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 ) );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
DefineEngineMethod( GuiPopUpMenuCtrl, size, S32, (), , "Get the size of the menu - the number of entries in it.")
|
|
{
|
|
return( object->getNumEntries() );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
DefineEngineMethod( GuiPopUpMenuCtrl, replaceText, void, (bool doReplaceText), , "(bool doReplaceText)")
|
|
{
|
|
object->replaceText(S32(doReplaceText));
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Added
|
|
bool GuiPopUpMenuCtrl::onWake()
|
|
{
|
|
if ( !Parent::onWake() )
|
|
return false;
|
|
|
|
// Set the bitmap for the popup.
|
|
setBitmap(getBitmap(Normal));
|
|
|
|
// 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()
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
DefineEngineMethod( 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) );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Added
|
|
void GuiPopUpMenuCtrl::setBitmap( const char *name )
|
|
{
|
|
StringTableEntry bitmapName = StringTable->insert(name);
|
|
|
|
if ( bitmapName != StringTable->EmptyString() )
|
|
{
|
|
char buffer[1024];
|
|
char *p;
|
|
dStrcpy(buffer, bitmapName, 1024);
|
|
p = buffer + dStrlen(buffer);
|
|
S32 pLen = 1024 - dStrlen(buffer);
|
|
|
|
dStrcpy(p, "_n", pLen);
|
|
|
|
_setBitmap((StringTableEntry)buffer, Normal);
|
|
|
|
dStrcpy(p, "_d", pLen);
|
|
_setBitmap((StringTableEntry)buffer, Depressed);
|
|
|
|
if ( !mBitmap[Depressed] )
|
|
mBitmap[Depressed] = mBitmap[Normal];
|
|
}
|
|
else
|
|
{
|
|
_setBitmap(StringTable->EmptyString(), Normal);
|
|
_setBitmap(StringTable->EmptyString(), Depressed);
|
|
}
|
|
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 ( String::compare( 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, 256 );
|
|
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 ( String::compare( 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 baseRect( offset, getExtent() );
|
|
if ( mInAction )
|
|
{
|
|
S32 left = baseRect.point.x, right = baseRect.point.x + baseRect.extent.x - 1;
|
|
S32 top = baseRect.point.y, bottom = baseRect.point.y + baseRect.extent.y - 1;
|
|
|
|
// Do we render a bitmap border or lines?
|
|
if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() )
|
|
{
|
|
if (mProfile->mBitmapArrayRects[0].extent.y < baseRect.extent.y)
|
|
{
|
|
//if our bitmap is smaller than the height of our ctrl, we'll nudge it towards the center
|
|
U32 nudge = (baseRect.extent.y - mProfile->mBitmapArrayRects[0].extent.y) / 2;
|
|
baseRect.point.y += nudge;
|
|
}
|
|
// Render the fixed, filled in border
|
|
renderFixedBitmapBordersFilled(baseRect, 3, mProfile );
|
|
|
|
}
|
|
else
|
|
{
|
|
//renderSlightlyLoweredBox(r, mProfile);
|
|
drawUtil->drawRectFill(baseRect, mProfile->mFillColor );
|
|
}
|
|
|
|
// Draw a bitmap over the background?
|
|
if ( mBitmap[Depressed] )
|
|
{
|
|
RectI rect(offset, mBitmapBounds);
|
|
drawUtil->clearBitmapModulation();
|
|
drawUtil->drawBitmapStretch( mBitmap[Depressed], rect );
|
|
}
|
|
else if ( mBitmap[Normal] )
|
|
{
|
|
RectI rect(offset, mBitmapBounds);
|
|
drawUtil->clearBitmapModulation();
|
|
drawUtil->drawBitmapStretch( mBitmap[Normal], rect );
|
|
}
|
|
|
|
// Do we render a bitmap border or lines?
|
|
if ( !( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) )
|
|
{
|
|
drawUtil->drawLine(left, top, left, bottom, colorWhite );
|
|
drawUtil->drawLine(left, top, right, top, colorWhite );
|
|
drawUtil->drawLine(left + 1, bottom, right, bottom, mProfile->mBorderColor );
|
|
drawUtil->drawLine(right, top + 1, right, bottom - 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 left = baseRect.point.x, right = baseRect.point.x + baseRect.extent.x - 1;
|
|
S32 top = baseRect.point.y, bottom = baseRect.point.y + baseRect.extent.y - 1;
|
|
|
|
// Do we render a bitmap border or lines?
|
|
if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() )
|
|
{
|
|
if (mProfile->mBitmapArrayRects[0].extent.y < baseRect.extent.y)
|
|
{
|
|
//if our bitmap is smaller than the height of our ctrl, we'll nudge it towards the center
|
|
U32 nudge = (baseRect.extent.y - mProfile->mBitmapArrayRects[0].extent.y) / 2;
|
|
baseRect.point.y += nudge;
|
|
}
|
|
// Render the fixed, filled in border
|
|
renderFixedBitmapBordersFilled(baseRect, 2, mProfile );
|
|
|
|
}
|
|
else
|
|
{
|
|
drawUtil->drawRectFill(baseRect, mProfile->mFillColorHL );
|
|
}
|
|
|
|
// Draw a bitmap over the background?
|
|
if ( mBitmap[Normal] )
|
|
{
|
|
RectI rect( offset, mBitmapBounds );
|
|
drawUtil->clearBitmapModulation();
|
|
drawUtil->drawBitmapStretch( mBitmap[Normal], rect );
|
|
}
|
|
|
|
// Do we render a bitmap border or lines?
|
|
if ( !( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) )
|
|
{
|
|
drawUtil->drawLine(left, top, left, bottom, colorWhite);
|
|
drawUtil->drawLine(left, top, right, top, colorWhite);
|
|
drawUtil->drawLine(left + 1, bottom, right, bottom, mProfile->mBorderColor);
|
|
drawUtil->drawLine(right, top + 1, right, bottom - 1, mProfile->mBorderColor);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Do we render a bitmap border or lines?
|
|
if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() )
|
|
{
|
|
if (mProfile->mBitmapArrayRects[0].extent.y < baseRect.extent.y)
|
|
{
|
|
//if our bitmap is smaller than the height of our ctrl, we'll nudge it towards the center
|
|
U32 nudge = (baseRect.extent.y - mProfile->mBitmapArrayRects[0].extent.y) / 2;
|
|
baseRect.point.y += nudge;
|
|
}
|
|
// Render the fixed, filled in border
|
|
renderFixedBitmapBordersFilled(baseRect, 1, mProfile );
|
|
}
|
|
else
|
|
{
|
|
drawUtil->drawRectFill(baseRect, mProfile->mFillColorNA );
|
|
}
|
|
|
|
// Draw a bitmap over the background?
|
|
if ( mBitmap[Normal] )
|
|
{
|
|
RectI rect(offset, mBitmapBounds);
|
|
drawUtil->clearBitmapModulation();
|
|
drawUtil->drawBitmapStretch( mBitmap[Normal], rect );
|
|
}
|
|
|
|
// Do we render a bitmap border or lines?
|
|
if ( !( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) )
|
|
{
|
|
if (mProfile->mBitmapArrayRects[0].extent.y < baseRect.extent.y)
|
|
{
|
|
//if our bitmap is smaller than the height of our ctrl, we'll nudge it towards the center
|
|
U32 nudge = (baseRect.extent.y - mProfile->mBitmapArrayRects[0].extent.y) / 2;
|
|
baseRect.point.y += nudge;
|
|
}
|
|
drawUtil->drawRect( baseRect, 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 boxBounds( offset.x + mProfile->mTextOffset.x, offset.y + ( (getHeight() - coloredboxsize.y ) / 2 ), coloredboxsize.x, coloredboxsize.y );
|
|
drawUtil->drawRectFill(boxBounds, boxColor);
|
|
drawUtil->drawRect(boxBounds, 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 colTxt_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() - colTxt_w - bitmapBounds[2].extent.x, localStart.y ) );
|
|
drawUtil->drawText( mProfile->mFont, textpos, buff, mProfile->mFontColors );
|
|
|
|
} else
|
|
{
|
|
Point2I textpos = localToGlobalCoord( Point2I( getWidth() - colTxt_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()) )
|
|
{
|
|
if (mProfile->mBitmapArrayRects[0].extent.y < baseRect.extent.y)
|
|
{
|
|
//if our bitmap is smaller than the height of our ctrl, we'll nudge it towards the center
|
|
U32 nudge = (baseRect.extent.y - mProfile->mBitmapArrayRects[0].extent.y) / 2;
|
|
baseRect.point.y += nudge;
|
|
}
|
|
|
|
// Draw a triangle (down arrow)
|
|
S32 left = baseRect.point.x + baseRect.extent.x - 12;
|
|
S32 right = left + 8;
|
|
S32 middle = left + 4;
|
|
S32 top = baseRect.extent.y / 2 + baseRect.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 );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Added
|
|
bool 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);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Added
|
|
void GuiPopUpMenuCtrl::onMouseEnter( const GuiEvent &event )
|
|
{
|
|
mMouseOver = true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Added
|
|
void 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;
|
|
}
|