popupMenu.cpp
Engine/source/gui/editor/popupMenu.cpp
Classes:
class
Event class used to remove popup menus from the event notification in a safe way.
Public Variables
Public Functions
ConsoleDocClass(PopupMenu , "@brief <a href="/coding/class/classpopupmenu/">PopupMenu</a> represents <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> system <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">menu.\n\n</a>" "You can add menu items <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the menu, but there is no torque object associated " "with these menu items, they exist only in <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> platform specific <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">manner.\n\n</a>" " @note Internal use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only\n\n</a>" " @internal" )
DefineEngineMethod(PopupMenu , checkItem , void , (S32 pos, bool checked) , "(pos, checked)" )
DefineEngineMethod(PopupMenu , clearItems , void , () , "()" )
DefineEngineMethod(PopupMenu , enableItem , void , (S32 pos, bool enabled) , "(pos, enabled)" )
DefineEngineMethod(PopupMenu , getItemCount , S32 , () , "()" )
DefineEngineMethod(PopupMenu , getItemText , const char * , (S32 pos) , "(pos)" )
DefineEngineMethod(PopupMenu , isItemChecked , bool , (S32 pos) , "(pos)" )
DefineEngineMethod(PopupMenu , removeItem , void , (S32 pos) , "(pos)" )
Detailed Description
Public Variables
U32 sMaxPopupGUID
Public Functions
ConsoleDocClass(PopupMenu , "@brief <a href="/coding/class/classpopupmenu/">PopupMenu</a> represents <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> system <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">menu.\n\n</a>" "You can add menu items <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the menu, but there is no torque object associated " "with these menu items, they exist only in <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> platform specific <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">manner.\n\n</a>" " @note Internal use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only\n\n</a>" " @internal" )
DefineEngineMethod(PopupMenu , checkItem , void , (S32 pos, bool checked) , "(pos, checked)" )
DefineEngineMethod(PopupMenu , checkRadioItem , void , (S32 firstPos, S32 lastPos, S32 checkPos) , "(firstPos, lastPos, checkPos)" )
DefineEngineMethod(PopupMenu , clearItems , void , () , "()" )
DefineEngineMethod(PopupMenu , enableItem , void , (S32 pos, bool enabled) , "(pos, enabled)" )
DefineEngineMethod(PopupMenu , getItemCount , S32 , () , "()" )
DefineEngineMethod(PopupMenu , getItemText , const char * , (S32 pos) , "(pos)" )
DefineEngineMethod(PopupMenu , insertItem , S32 , (S32 pos, const char *title, const char *accelerator, const char *cmd, S32 bitmapIndex) , ("", "", "", -1) , "(pos[, title][, accelerator][, cmd][, bitmapIndex])" )
DefineEngineMethod(PopupMenu , insertSubMenu , S32 , (S32 pos, String title, String subMenu) , "(pos, title, subMenu)" )
DefineEngineMethod(PopupMenu , isItemChecked , bool , (S32 pos) , "(pos)" )
DefineEngineMethod(PopupMenu , removeItem , void , (S32 pos) , "(pos)" )
DefineEngineMethod(PopupMenu , setItem , bool , (S32 pos, const char *title, const char *accelerator, const char *cmd) , ("") , "(pos, title[, accelerator][, cmd])" )
DefineEngineMethod(PopupMenu , showPopup , void , (const char *canvasName, S32 x, S32 y) , (-1, -1) , "(Canvas,[x, y])" )
IMPLEMENT_CONOBJECT(PopupMenu )
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23#include "gui/editor/popupMenu.h" 24#include "console/consoleTypes.h" 25#include "console/engineAPI.h" 26#include "gui/core/guiCanvas.h" 27#include "core/util/safeDelete.h" 28#include "gui/editor/guiPopupMenuCtrl.h" 29#include "gui/editor/guiMenuBar.h" 30 31static U32 sMaxPopupGUID = 0; 32PopupMenuEvent PopupMenu::smPopupMenuEvent; 33bool PopupMenu::smSelectionEventHandled = false; 34 35/// Event class used to remove popup menus from the event notification in a safe way 36class PopUpNotifyRemoveEvent : public SimEvent 37{ 38public: 39 void process(SimObject *object) 40 { 41 PopupMenu::smPopupMenuEvent.remove((PopupMenu *)object, &PopupMenu::handleSelectEvent); 42 } 43}; 44 45//----------------------------------------------------------------------------- 46// Constructor/Destructor 47//----------------------------------------------------------------------------- 48PopupMenu::PopupMenu() 49{ 50 mMenuItems = NULL; 51 mMenuBarCtrl = nullptr; 52 53 mBarTitle = StringTable->EmptyString(); 54 mBounds = RectI(0, 0, 64, 64); 55 mVisible = false; 56 57 mBitmapIndex = -1; 58 mDrawBitmapOnly = false; 59 mDrawBorder = false; 60 61 mTextList = nullptr; 62 63 mIsSubmenu = false; 64 65 mRadioSelection = true; 66} 67 68PopupMenu::~PopupMenu() 69{ 70 PopupMenu::smPopupMenuEvent.remove(this, &PopupMenu::handleSelectEvent); 71} 72 73IMPLEMENT_CONOBJECT(PopupMenu); 74 75ConsoleDocClass( PopupMenu, 76 "@brief PopupMenu represents a system menu.\n\n" 77 "You can add menu items to the menu, but there is no torque object associated " 78 "with these menu items, they exist only in a platform specific manner.\n\n" 79 "@note Internal use only\n\n" 80 "@internal" 81); 82 83//----------------------------------------------------------------------------- 84void PopupMenu::initPersistFields() 85{ 86 Parent::initPersistFields(); 87 88 addField("barTitle", TypeCaseString, Offset(mBarTitle, PopupMenu), ""); 89 addField("radioSelection", TypeBool, Offset(mRadioSelection, PopupMenu), ""); 90 addField("visible", TypeBool, Offset(mVisible, PopupMenu), ""); 91} 92 93//----------------------------------------------------------------------------- 94bool PopupMenu::onAdd() 95{ 96 if(! Parent::onAdd()) 97 return false; 98 99 Con::executef(this, "onAdd"); 100 return true; 101} 102 103void PopupMenu::onRemove() 104{ 105 Con::executef(this, "onRemove"); 106 107 Parent::onRemove(); 108} 109 110//----------------------------------------------------------------------------- 111void PopupMenu::onMenuSelect() 112{ 113 Con::executef(this, "onMenuSelect"); 114} 115 116//----------------------------------------------------------------------------- 117void PopupMenu::handleSelectEvent(U32 popID, U32 command) 118{ 119} 120 121//----------------------------------------------------------------------------- 122bool PopupMenu::onMessageReceived(StringTableEntry queue, const char* event, const char* data) 123{ 124 return Con::executef(this, "onMessageReceived", queue, event, data); 125} 126 127bool PopupMenu::onMessageObjectReceived(StringTableEntry queue, Message *msg ) 128{ 129 return Con::executef(this, "onMessageReceived", queue, Con::getIntArg(msg->getId())); 130} 131 132////////////////////////////////////////////////////////////////////////// 133// Platform Menu Data 134////////////////////////////////////////////////////////////////////////// 135GuiMenuBar* PopupMenu::getMenuBarCtrl() 136{ 137 return mMenuBarCtrl; 138} 139 140////////////////////////////////////////////////////////////////////////// 141// Public Methods 142////////////////////////////////////////////////////////////////////////// 143S32 PopupMenu::insertItem(S32 pos, const char *title, const char* accelerator, const char* cmd, S32 bitmapIndex) 144{ 145 String titleString = title; 146 147 MenuItem newItem; 148 newItem.mID = pos; 149 newItem.mText = titleString; 150 newItem.mCMD = cmd; 151 newItem.mBitmapIndex = bitmapIndex; 152 153 if (titleString.isEmpty() || titleString == String("-")) 154 newItem.mIsSpacer = true; 155 else 156 newItem.mIsSpacer = false; 157 158 if (accelerator[0]) 159 newItem.mAccelerator = dStrdup(accelerator); 160 else 161 newItem.mAccelerator = NULL; 162 163 newItem.mVisible = true; 164 newItem.mIsChecked = false; 165 newItem.mAcceleratorIndex = 0; 166 newItem.mEnabled = !newItem.mIsSpacer; 167 168 newItem.mIsSubmenu = false; 169 newItem.mSubMenu = nullptr; 170 newItem.mSubMenuParentMenu = nullptr; 171 172 mMenuItems.push_back(newItem); 173 174 return pos; 175} 176 177S32 PopupMenu::insertSubMenu(S32 pos, const char *title, PopupMenu *submenu) 178{ 179 S32 itemPos = insertItem(pos, title, "", ""); 180 181 mMenuItems[itemPos].mIsSubmenu = true; 182 mMenuItems[itemPos].mSubMenu = submenu; 183 mMenuItems[itemPos].mSubMenuParentMenu = this; 184 185 submenu->mIsSubmenu = true; 186 187 return itemPos; 188} 189 190bool PopupMenu::setItem(S32 pos, const char *title, const char* accelerator, const char* cmd) 191{ 192 String titleString = title; 193 194 for (U32 i = 0; i < mMenuItems.size(); i++) 195 { 196 if (mMenuItems[i].mText == titleString) 197 { 198 mMenuItems[i].mID = pos; 199 mMenuItems[i].mCMD = cmd; 200 201 if (accelerator && accelerator[0]) 202 mMenuItems[i].mAccelerator = dStrdup(accelerator); 203 else 204 mMenuItems[i].mAccelerator = NULL; 205 return true; 206 } 207 } 208 209 return false; 210} 211 212void PopupMenu::removeItem(S32 itemPos) 213{ 214 if (mMenuItems.empty() || mMenuItems.size() < itemPos || itemPos < 0) 215 return; 216 217 mMenuItems.erase(itemPos); 218} 219 220////////////////////////////////////////////////////////////////////////// 221void PopupMenu::enableItem(S32 pos, bool enable) 222{ 223 if (mMenuItems.empty() || mMenuItems.size() < pos || pos < 0) 224 return; 225 226 mMenuItems[pos].mEnabled = enable; 227} 228 229void PopupMenu::checkItem(S32 pos, bool checked) 230{ 231 if (mMenuItems.empty() || mMenuItems.size() < pos || pos < 0) 232 return; 233 234 if (checked && mMenuItems[pos].mCheckGroup != -1 && mRadioSelection) 235 { 236 // first, uncheck everything in the group: 237 for (U32 i = 0; i < mMenuItems.size(); i++) 238 if (mMenuItems[i].mCheckGroup == mMenuItems[pos].mCheckGroup && mMenuItems[i].mIsChecked) 239 mMenuItems[i].mIsChecked = false; 240 } 241 242 mMenuItems[pos].mIsChecked = checked; 243} 244 245void PopupMenu::checkRadioItem(S32 firstPos, S32 lastPos, S32 checkPos) 246{ 247 for (U32 i = 0; i < mMenuItems.size(); i++) 248 { 249 if (mMenuItems[i].mID >= firstPos && mMenuItems[i].mID <= lastPos) 250 { 251 mMenuItems[i].mIsChecked = false; 252 } 253 } 254} 255 256bool PopupMenu::isItemChecked(S32 pos) 257{ 258 if (mMenuItems.empty() || mMenuItems.size() < pos || pos < 0) 259 return false; 260 261 return mMenuItems[pos].mIsChecked; 262} 263 264U32 PopupMenu::getItemCount() 265{ 266 return mMenuItems.size(); 267} 268 269void PopupMenu::clearItems() 270{ 271 mMenuItems.clear(); 272} 273 274String PopupMenu::getItemText(S32 pos) 275{ 276 if (mMenuItems.empty() || mMenuItems.size() < pos || pos < 0) 277 return String::EmptyString; 278 279 return mMenuItems[pos].mText; 280} 281 282////////////////////////////////////////////////////////////////////////// 283bool PopupMenu::canHandleID(U32 id) 284{ 285 return true; 286} 287 288bool PopupMenu::handleSelect(U32 command, const char *text /* = NULL */) 289{ 290 return dAtob(Con::executef(this, "onSelectItem", Con::getIntArg(command), text ? text : "")); 291} 292 293////////////////////////////////////////////////////////////////////////// 294void PopupMenu::showPopup(GuiCanvas *owner, S32 x /* = -1 */, S32 y /* = -1 */) 295{ 296 if (owner == NULL) 297 return; 298 299 GuiPopupMenuBackgroundCtrl* backgroundCtrl; 300 Sim::findObject("PopUpMenuControl", backgroundCtrl); 301 302 GuiControlProfile* profile; 303 Sim::findObject("ToolsGuiMenuBarProfile", profile); 304 305 if (!profile) 306 return; 307 308 if (mTextList == nullptr) 309 { 310 mTextList = new GuiPopupMenuTextListCtrl(); 311 mTextList->registerObject(); 312 mTextList->setControlProfile(profile); 313 mTextList->mRowHeightPadding = 5; 314 315 mTextList->mPopup = this; 316 mTextList->mMenuBar = getMenuBarCtrl(); 317 } 318 319 if (!backgroundCtrl) 320 { 321 backgroundCtrl = new GuiPopupMenuBackgroundCtrl(); 322 323 backgroundCtrl->registerObject("PopUpMenuControl"); 324 } 325 326 if (!backgroundCtrl || !mTextList) 327 return; 328 329 if (!mIsSubmenu) 330 { 331 //if we're a 'parent' menu, then tell the background to clear out all existing other popups 332 333 backgroundCtrl->clearPopups(); 334 } 335 336 //find out if we're doing a first-time add 337 S32 popupIndex = backgroundCtrl->findPopupMenu(this); 338 339 if (popupIndex == -1) 340 { 341 backgroundCtrl->addObject(mTextList); 342 backgroundCtrl->mPopups.push_back(this); 343 } 344 345 mTextList->mBackground = backgroundCtrl; 346 347 owner->pushDialogControl(backgroundCtrl, 10); 348 349 //Set the background control's menubar, if any, and if it's not already set 350 if(backgroundCtrl->mMenuBarCtrl == nullptr) 351 backgroundCtrl->mMenuBarCtrl = getMenuBarCtrl(); 352 353 backgroundCtrl->setExtent(owner->getExtent()); 354 355 mTextList->clear(); 356 357 S32 textWidth = 0, width = 0; 358 S32 acceleratorWidth = 0; 359 GFont *font = profile->mFont; 360 361 Point2I maxBitmapSize = Point2I(0, 0); 362 363 S32 numBitmaps = profile->mBitmapArrayRects.size(); 364 if (numBitmaps) 365 { 366 RectI *bitmapBounds = profile->mBitmapArrayRects.address(); 367 for (S32 i = 0; i < numBitmaps; i++) 368 { 369 if (bitmapBounds[i].extent.x > maxBitmapSize.x) 370 maxBitmapSize.x = bitmapBounds[i].extent.x; 371 if (bitmapBounds[i].extent.y > maxBitmapSize.y) 372 maxBitmapSize.y = bitmapBounds[i].extent.y; 373 } 374 } 375 376 for (U32 i = 0; i < mMenuItems.size(); i++) 377 { 378 if (!mMenuItems[i].mVisible) 379 continue; 380 381 S32 iTextWidth = font->getStrWidth(mMenuItems[i].mText.c_str()); 382 S32 iAcceleratorWidth = mMenuItems[i].mAccelerator ? font->getStrWidth(mMenuItems[i].mAccelerator) : 0; 383 384 if (iTextWidth > textWidth) 385 textWidth = iTextWidth; 386 if (iAcceleratorWidth > acceleratorWidth) 387 acceleratorWidth = iAcceleratorWidth; 388 } 389 390 width = textWidth + acceleratorWidth + maxBitmapSize.x * 2 + 2 + 4; 391 392 mTextList->setCellSize(Point2I(width, font->getHeight() + 2)); 393 mTextList->clearColumnOffsets(); 394 mTextList->addColumnOffset(-1); // add an empty column in for the bitmap index. 395 mTextList->addColumnOffset(maxBitmapSize.x + 1); 396 mTextList->addColumnOffset(maxBitmapSize.x + 1 + textWidth + 4); 397 398 U32 entryCount = 0; 399 400 for (U32 i = 0; i < mMenuItems.size(); i++) 401 { 402 if (!mMenuItems[i].mVisible) 403 continue; 404 405 char buf[512]; 406 407 // If this menu item is a submenu, then set the isSubmenu to 2 to indicate 408 // an arrow should be drawn. Otherwise set the isSubmenu normally. 409 char isSubmenu = 1; 410 if (mMenuItems[i].mIsSubmenu) 411 isSubmenu = 2; 412 413 char bitmapIndex = 1; 414 if (mMenuItems[i].mBitmapIndex >= 0 && (mMenuItems[i].mBitmapIndex * 3 <= profile->mBitmapArrayRects.size())) 415 bitmapIndex = mMenuItems[i].mBitmapIndex + 2; 416 417 dSprintf(buf, sizeof(buf), "%c%c\t%s\t%s", bitmapIndex, isSubmenu, mMenuItems[i].mText.c_str(), mMenuItems[i].mAccelerator ? mMenuItems[i].mAccelerator : ""); 418 mTextList->addEntry(entryCount, buf); 419 420 if (!mMenuItems[i].mEnabled) 421 mTextList->setEntryActive(entryCount, false); 422 423 entryCount++; 424 } 425 426 Point2I pos = Point2I::Zero; 427 428 if (x == -1 && y == -1) 429 pos = owner->getCursorPos(); 430 else 431 pos = Point2I(x, y); 432 433 mTextList->setPosition(pos); 434 435 //nudge in if we'd overshoot the screen 436 S32 widthDiff = (mTextList->getPosition().x + mTextList->getExtent().x) - backgroundCtrl->getWidth(); 437 if (widthDiff > 0) 438 { 439 Point2I popupPos = mTextList->getPosition(); 440 mTextList->setPosition(popupPos.x - widthDiff, popupPos.y); 441 } 442 443 //If we'd overshoot the screen vertically, just mirror the axis so we're above the mouse 444 S32 heightDiff = (mTextList->getPosition().y + mTextList->getExtent().y) - backgroundCtrl->getHeight(); 445 if (heightDiff > 0) 446 { 447 Point2I popupPos = mTextList->getPosition(); 448 mTextList->setPosition(popupPos.x, popupPos.y - mTextList->getExtent().y); 449 } 450 451 452 mTextList->setHidden(false); 453 454 mVisible = true; 455} 456 457void PopupMenu::hidePopup() 458{ 459 if (mTextList) 460 { 461 mTextList->setHidden(true); 462 } 463 464 hidePopupSubmenus(); 465 466 mVisible = false; 467} 468 469void PopupMenu::hidePopupSubmenus() 470{ 471 for (U32 i = 0; i < mMenuItems.size(); i++) 472 { 473 if (mMenuItems[i].mSubMenu != nullptr) 474 mMenuItems[i].mSubMenu->hidePopup(); 475 } 476} 477 478//----------------------------------------------------------------------------- 479// Console Methods 480//----------------------------------------------------------------------------- 481DefineEngineMethod(PopupMenu, insertItem, S32, (S32 pos, const char * title, const char * accelerator, const char* cmd, S32 bitmapIndex), ("", "", "", -1), "(pos[, title][, accelerator][, cmd][, bitmapIndex])") 482{ 483 return object->insertItem(pos, title, accelerator, cmd, bitmapIndex); 484} 485 486DefineEngineMethod(PopupMenu, removeItem, void, (S32 pos), , "(pos)") 487{ 488 object->removeItem(pos); 489} 490 491DefineEngineMethod(PopupMenu, insertSubMenu, S32, (S32 pos, String title, String subMenu), , "(pos, title, subMenu)") 492{ 493 PopupMenu *mnu = dynamic_cast<PopupMenu *>(Sim::findObject(subMenu)); 494 if(mnu == NULL) 495 { 496 Con::errorf("PopupMenu::insertSubMenu - Invalid PopupMenu object specified for submenu"); 497 return -1; 498 } 499 return object->insertSubMenu(pos, title, mnu); 500} 501 502DefineEngineMethod(PopupMenu, setItem, bool, (S32 pos, const char * title, const char * accelerator, const char *cmd), (""), "(pos, title[, accelerator][, cmd])") 503{ 504 return object->setItem(pos, title, accelerator, cmd); 505} 506 507//----------------------------------------------------------------------------- 508 509DefineEngineMethod(PopupMenu, enableItem, void, (S32 pos, bool enabled), , "(pos, enabled)") 510{ 511 object->enableItem(pos, enabled); 512} 513 514DefineEngineMethod(PopupMenu, checkItem, void, (S32 pos, bool checked), , "(pos, checked)") 515{ 516 object->checkItem(pos, checked); 517} 518 519DefineEngineMethod(PopupMenu, getItemText, const char*, (S32 pos), , "(pos)") 520{ 521 return object->getItemText(pos).c_str(); 522} 523 524 525DefineEngineMethod(PopupMenu, checkRadioItem, void, (S32 firstPos, S32 lastPos, S32 checkPos), , "(firstPos, lastPos, checkPos)") 526{ 527 object->checkRadioItem(firstPos, lastPos, checkPos); 528} 529 530DefineEngineMethod(PopupMenu, isItemChecked, bool, (S32 pos), , "(pos)") 531{ 532 return object->isItemChecked(pos); 533} 534 535DefineEngineMethod(PopupMenu, getItemCount, S32, (), , "()") 536{ 537 return object->getItemCount(); 538} 539 540DefineEngineMethod(PopupMenu, clearItems, void, (), , "()") 541{ 542 return object->clearItems(); 543} 544 545//----------------------------------------------------------------------------- 546DefineEngineMethod(PopupMenu, showPopup, void, (const char * canvasName, S32 x, S32 y), ( -1, -1), "(Canvas,[x, y])") 547{ 548 GuiCanvas *pCanvas = dynamic_cast<GuiCanvas*>(Sim::findObject(canvasName)); 549 object->showPopup(pCanvas, x, y); 550} 551