core/MenuManager.cpp
author David Anderson <dvander@alliedmods.net>
Fri Aug 03 11:08:12 2007 +0000 (2007-08-03)
changeset 1257 6b41a24f3f36
parent 1243 51a088fbf549
child 1258 f89bcd42fff5
permissions -rw-r--r--
fixed amb708 - render algorithm didn't draw descending pagination right
     1 /**
     2  * vim: set ts=4 :
     3  * ================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2007 AlliedModders LLC.  All rights reserved.
     6  * ================================================================
     7  *
     8  * This program is free software; you can redistribute it and/or 
     9  * modify it under the terms of the GNU General Public License, 
    10  * version 3.0, as published by the Free Software Foundation.
    11  * 
    12  * This program is distributed in the hope that it will be useful,
    13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    15  * GNU General Public License for more details.
    16  *
    17  * You should have received a copy of the GNU General Public License
    18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    19  *
    20  * As a special exception, AlliedModders LLC gives you permission to 
    21  * link the code of this program (as well as its derivative works) to 
    22  * "Half-Life 2," the "Source Engine," the "SourcePawn JIT," and any 
    23  * Game MODs that run on software by the Valve Corporation.  You must 
    24  * obey the GNU General Public License in all respects for all other 
    25  * code used. Additionally, AlliedModders LLC grants this exception 
    26  * to all derivative works. AlliedModders LLC defines further 
    27  * exceptions, found in LICENSE.txt (as of this writing, version 
    28  * JULY-31-2007), or <http://www.sourcemod.net/license.php>.
    29  *
    30  * Version: $Id$
    31  */
    32 
    33 #include <time.h>
    34 #include <stdarg.h>
    35 #include <stdlib.h>
    36 #include "MenuManager.h"
    37 #include "sm_stringutil.h"
    38 #include "sourcemm_api.h"
    39 #include "PlayerManager.h"
    40 #include "MenuStyle_Valve.h"
    41 #include "ShareSys.h"
    42 #include "HandleSys.h"
    43 #include "sourcemm_api.h"
    44 
    45 MenuManager g_Menus;
    46 
    47 ConVar sm_menu_sounds("sm_menu_sounds", "1", 0, "Sets whether SourceMod menus play trigger sounds");
    48 
    49 /*******************************
    50  *******************************
    51  ******** VOTE HANDLER *********
    52  *******************************
    53  *******************************/
    54 
    55 unsigned int VoteMenuHandler::GetMenuAPIVersion2()
    56 {
    57 	return m_pHandler->GetMenuAPIVersion2();
    58 }
    59 
    60 bool VoteMenuHandler::IsVoteInProgress()
    61 {
    62 	return (m_pCurMenu != NULL);
    63 }
    64 
    65 void VoteMenuHandler::InitializeVoting(IBaseMenu *menu)
    66 {
    67 	m_Items = menu->GetItemCount();
    68 
    69 	if (m_Votes.size() < (size_t)m_Items)
    70 	{
    71 		/* Only clear the items we need to... */
    72 		size_t size = m_Votes.size();
    73 		for (size_t i=0; i<size; i++)
    74 		{
    75 			m_Votes[i] = 0;
    76 		}
    77 		m_Votes.resize(m_Items, 0);
    78 	} else {
    79 		for (unsigned int i=0; i<m_Items; i++)
    80 		{
    81 			m_Votes[i] = 0;
    82 		}
    83 	}
    84 
    85 	m_pCurMenu = menu;
    86 
    87 	m_pHandler->OnMenuStart(m_pCurMenu);
    88 }
    89 
    90 void VoteMenuHandler::StartVoting()
    91 {
    92 	m_bStarted = true;
    93 
    94 	m_pHandler->OnMenuVoteStart(m_pCurMenu);
    95 
    96 	/* By now we know how many clients were set.  
    97 	 * If there are none, we should end IMMEDIATELY.
    98 	 */
    99 	if (m_Clients == 0)
   100 	{
   101 		EndVoting();
   102 	}
   103 }
   104 
   105 void VoteMenuHandler::DecrementPlayerCount()
   106 {
   107 	assert(m_Clients > 0);
   108 
   109 	m_Clients--;
   110 
   111 	if (m_bStarted && m_Clients == 0)
   112 	{
   113 		EndVoting();
   114 	}
   115 }
   116 
   117 void VoteMenuHandler::EndVoting()
   118 {
   119 	if (m_bCancelled)
   120 	{
   121 		/* If we were cancelled, don't bother tabulating anything.
   122 		 * Reset just in case someone tries to redraw, which means
   123 		 * we need to save our states.
   124 		 */
   125 		IBaseMenu *menu = m_pCurMenu;
   126 		InternalReset();
   127 		m_pHandler->OnMenuVoteCancel(menu);
   128 		m_pHandler->OnMenuEnd(menu, MenuEnd_VotingCancelled);
   129 		return;
   130 	}
   131 
   132 	unsigned int chosen = 0;
   133 	unsigned int highest = 0;
   134 	unsigned int dup_count = 0;
   135 	unsigned int total = m_Votes.size() ? m_Votes[0] : 0;
   136 
   137 	/* If we got zero votes, take a shortcut. */
   138 	if (m_NumVotes == 0)
   139 	{
   140 		/* Pick a random item and then jump far, far away. */
   141 		srand((unsigned int)(time(NULL)));
   142 		chosen = (unsigned int)rand() % m_Items;
   143 		goto picked_item;
   144 	}
   145 
   146 	/* We can't have more dups than this!
   147 	 * This is the max number of players.
   148 	 */
   149 	unsigned int dup_array[256];
   150 
   151 	for (size_t i=1; i<m_Items; i++)
   152 	{
   153 		if (m_Votes[i] > m_Votes[highest])
   154 		{
   155 			/* If we have a new highest count, mark it and trash the duplicate
   156 			 * list by setting the total to 0.
   157 			 */
   158 			highest = i;
   159 			dup_count = 0;
   160 		} else if (m_Votes[i] == m_Votes[highest]) {
   161 			/* If they're equal, mark it in the duplicate list.
   162 			 * We'll add in the original later.
   163 			 */
   164 			dup_array[dup_count++] = i;
   165 		}
   166 		total += m_Votes[i];
   167 	}
   168 
   169 	/* Check if we need to pick from the duplicate list */
   170 	if (dup_count)
   171 	{
   172 		/* Re-add the original to the list because it's not in there. */
   173 		dup_array[dup_count++] = highest;
   174 
   175 		/* Pick a random slot. */
   176 		srand((unsigned int)(time(NULL)));
   177 		unsigned int r = (unsigned int)rand() % dup_count;
   178 
   179 		/* Pick the item. */
   180 		chosen = dup_array[r];
   181 	} else {
   182 		chosen = highest;
   183 	}
   184 
   185 picked_item:
   186 	m_pHandler->OnMenuVoteEnd(m_pCurMenu, chosen, m_Votes[highest], total);
   187 	m_pHandler->OnMenuEnd(m_pCurMenu, MenuEnd_VotingDone);
   188 	InternalReset();
   189 }
   190 
   191 void VoteMenuHandler::OnMenuStart(IBaseMenu *menu)
   192 {
   193 	m_Clients++;
   194 }
   195 
   196 void VoteMenuHandler::OnMenuEnd(IBaseMenu *menu, MenuEndReason reason)
   197 {
   198 	DecrementPlayerCount();
   199 }
   200 
   201 void VoteMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
   202 {
   203 	m_pHandler->OnMenuCancel(menu, client, reason);
   204 }
   205 
   206 void VoteMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display)
   207 {
   208 	m_pHandler->OnMenuDisplay(menu, client, display);
   209 }
   210 
   211 unsigned int VoteMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, int client, IMenuPanel *panel, unsigned int item, const ItemDrawInfo &dr)
   212 {
   213 	return m_pHandler->OnMenuDisplayItem(menu, client, panel, item, dr);
   214 }
   215 
   216 void VoteMenuHandler::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style)
   217 {
   218 	m_pHandler->OnMenuDrawItem(menu, client, item, style);
   219 }
   220 
   221 void VoteMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
   222 {
   223 	/* Check by our item count, NOT the vote array size */
   224 	if (item < m_Items)
   225 	{
   226 		m_Votes[item]++;
   227 		m_NumVotes++;
   228 	}
   229 
   230 	m_pHandler->OnMenuSelect(menu, client, item);
   231 }
   232 
   233 void VoteMenuHandler::Reset(IMenuHandler *mh)
   234 {
   235 	m_pHandler = mh;
   236 	InternalReset();
   237 }
   238 
   239 void VoteMenuHandler::InternalReset()
   240 {
   241 	m_Clients = 0;
   242 	m_Items = 0;
   243 	m_bStarted = false;
   244 	m_pCurMenu = NULL;
   245 	m_NumVotes = 0;
   246 	m_bCancelled = false;
   247 }
   248 
   249 void VoteMenuHandler::CancelVoting()
   250 {
   251 	m_bCancelled = true;
   252 }
   253 
   254 /*******************************
   255  *******************************
   256  ******** MENU MANAGER *********
   257  *******************************
   258  *******************************/
   259 
   260 MenuManager::MenuManager()
   261 {
   262 	m_Styles.push_back(&g_ValveMenuStyle);
   263 	SetDefaultStyle(&g_ValveMenuStyle);
   264 }
   265 
   266 void MenuManager::OnSourceModAllInitialized()
   267 {
   268 	g_ShareSys.AddInterface(NULL, this);
   269 
   270 	HandleAccess access;
   271 	g_HandleSys.InitAccessDefaults(NULL, &access);
   272 
   273 	/* Deny cloning to menus */
   274 	access.access[HandleAccess_Clone] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY;
   275 	m_MenuType = g_HandleSys.CreateType("IBaseMenu", this, 0, NULL, &access, g_pCoreIdent, NULL);
   276 
   277 	/* Also deny deletion to styles */
   278 	access.access[HandleAccess_Delete] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY;
   279 	m_StyleType = g_HandleSys.CreateType("IMenuStyle", this, 0, NULL, &access, g_pCoreIdent, NULL);
   280 }
   281 
   282 void MenuManager::OnSourceModAllShutdown()
   283 {
   284 	g_HandleSys.RemoveType(m_MenuType, g_pCoreIdent);
   285 	g_HandleSys.RemoveType(m_StyleType, g_pCoreIdent);
   286 
   287 	while (!m_VoteHandlers.empty())
   288 	{
   289 		delete m_VoteHandlers.front();
   290 		m_VoteHandlers.pop();
   291 	}
   292 }
   293 
   294 void MenuManager::OnHandleDestroy(HandleType_t type, void *object)
   295 {
   296 	if (type == m_MenuType)
   297 	{
   298 		IBaseMenu *menu = (IBaseMenu *)object;
   299 		menu->Destroy(false);
   300 	} else if (type == m_StyleType) {
   301 		/* Do nothing */
   302 	}
   303 }
   304 
   305 Handle_t MenuManager::CreateMenuHandle(IBaseMenu *menu, IdentityToken_t *pOwner)
   306 {
   307 	if (m_MenuType == NO_HANDLE_TYPE)
   308 	{
   309 		return BAD_HANDLE;
   310 	}
   311 
   312 	return g_HandleSys.CreateHandle(m_MenuType, menu, pOwner, g_pCoreIdent, NULL);
   313 }
   314 
   315 Handle_t MenuManager::CreateStyleHandle(IMenuStyle *style)
   316 {
   317 	if (m_StyleType == NO_HANDLE_TYPE)
   318 	{
   319 		return BAD_HANDLE;
   320 	}
   321 
   322 	return g_HandleSys.CreateHandle(m_StyleType, style, g_pCoreIdent, g_pCoreIdent, NULL);
   323 }
   324 
   325 HandleError MenuManager::ReadMenuHandle(Handle_t handle, IBaseMenu **menu)
   326 {
   327 	HandleSecurity sec;
   328 
   329 	sec.pIdentity = g_pCoreIdent;
   330 	sec.pOwner = NULL;
   331 
   332 	return g_HandleSys.ReadHandle(handle, m_MenuType, &sec, (void **)menu);
   333 }
   334 
   335 HandleError MenuManager::ReadStyleHandle(Handle_t handle, IMenuStyle **style)
   336 {
   337 	HandleSecurity sec;
   338 
   339 	sec.pIdentity = g_pCoreIdent;
   340 	sec.pOwner = g_pCoreIdent;
   341 
   342 	return g_HandleSys.ReadHandle(handle, m_StyleType, &sec, (void **)style);
   343 }
   344 
   345 bool MenuManager::SetDefaultStyle(IMenuStyle *style)
   346 {
   347 	if (!style)
   348 	{
   349 		return false;
   350 	}
   351 
   352 	m_pDefaultStyle = style;
   353 
   354 	return true;
   355 }
   356 
   357 IMenuStyle *MenuManager::GetStyle(unsigned int index)
   358 {
   359 	if (index >= GetStyleCount())
   360 	{
   361 		return NULL;
   362 	}
   363 
   364 	return m_Styles[index];
   365 }
   366 
   367 void MenuManager::AddStyle(IMenuStyle *style)
   368 {
   369 	m_Styles.push_back(style);
   370 }
   371 
   372 unsigned int MenuManager::GetStyleCount()
   373 {
   374 	return (unsigned int)m_Styles.size();
   375 }
   376 
   377 IMenuStyle *MenuManager::FindStyleByName(const char *name)
   378 {
   379 	unsigned int count = GetStyleCount();
   380 	for (unsigned int i=0; i<count; i++)
   381 	{
   382 		IMenuStyle *ptr = GetStyle(i);
   383 		if (strcasecmp(ptr->GetStyleName(), name) == 0)
   384 		{
   385 			return ptr;
   386 		}
   387 	}
   388 
   389 	return NULL;
   390 }
   391 
   392 inline bool IsSlotItem(IMenuPanel *display,
   393 					   unsigned int style)
   394 {
   395 	if (!display->CanDrawItem(style))
   396 	{
   397 	return false;
   398 	}
   399 	if ((style & ITEMDRAW_IGNORE) == ITEMDRAW_IGNORE)
   400 	{
   401 		return false;
   402 	}
   403 	if (style & ITEMDRAW_RAWLINE)
   404 	{
   405 		return false;
   406 	}
   407 
   408 	return true;
   409 }
   410 
   411 IMenuPanel *MenuManager::RenderMenu(int client, menu_states_t &md, ItemOrder order)
   412 {
   413 	IBaseMenu *menu = md.menu;
   414 
   415 	if (!menu)
   416 	{
   417 		return NULL;
   418 	}
   419 
   420 	struct
   421 	{
   422 		unsigned int position;
   423 		ItemDrawInfo draw;
   424 	} drawItems[10];
   425 
   426 	/* Figure out how many items to draw */
   427 	IMenuStyle *style = menu->GetDrawStyle();
   428 	unsigned int pgn = menu->GetPagination();
   429 	unsigned int maxItems = style->GetMaxPageItems();
   430 	if (pgn != MENU_NO_PAGINATION)
   431 	{
   432 		maxItems = pgn;
   433 	}
   434 
   435 	unsigned int totalItems = menu->GetItemCount();
   436 	unsigned int startItem = 0;
   437 
   438 	/* For pagination, find the starting point. */
   439 	if (pgn != MENU_NO_PAGINATION)
   440 	{
   441 		if (order == ItemOrder_Ascending)
   442 		{
   443 			startItem = md.lastItem;
   444 			/* This shouldn't happen with well-coded menus.
   445 			 * If the item is out of bounds, switch the order to
   446 			 * Items_Descending and make us start from the top.
   447 			 */
   448 			if (startItem >= totalItems)
   449 			{
   450 				startItem = totalItems - 1;
   451 				order = ItemOrder_Descending;
   452 			}
   453 		} else if (order == ItemOrder_Descending) {
   454 			startItem = md.firstItem;
   455 			/* This shouldn't happen with well-coded menus.
   456 			 * If searching backwards doesn't give us enough room,
   457 			 * start from the beginning and change to ascending.
   458 			 */
   459 			if (startItem <= maxItems)
   460 			{
   461 				startItem = 0;
   462 				order = ItemOrder_Ascending;
   463 			}
   464 		}
   465 	}
   466 
   467 	/* Get our Display pointer and initialize some crap */
   468 	IMenuPanel *display = menu->CreatePanel();
   469 	IMenuHandler *mh = md.mh;
   470 	bool foundExtra = false;
   471 	unsigned int extraItem = 0;
   472 
   473 	/**
   474 	 * We keep searching until:
   475 	 * 1) There are no more items
   476 	 * 2) We reach one OVER the maximum number of slot items
   477 	 * 3) We have reached maxItems and pagination is MENU_NO_PAGINATION
   478 	 */
   479 	unsigned int i = startItem;
   480 	unsigned int foundItems = 0;
   481 	while (true)
   482 	{
   483 		ItemDrawInfo &dr = drawItems[foundItems].draw;
   484 		/* Is the item valid? */
   485 		if (menu->GetItemInfo(i, &dr) != NULL)
   486 		{
   487 			/* Ask the user to change the style, if necessary */
   488 			mh->OnMenuDrawItem(menu, client, i, dr.style);
   489 			/* Check if it's renderable */
   490 			if (IsSlotItem(display, dr.style))
   491 			{
   492 				/* If we've already found the max number of items,
   493 				 * This means we should just cancel out and log our
   494 				 * "last item."
   495 				 */
   496 				if (foundItems >= maxItems)
   497 				{
   498 					foundExtra = true;
   499 					extraItem = i;
   500 					break;
   501 				}
   502 				drawItems[foundItems++].position = i;
   503 			}
   504 		}
   505 		/* If there's no pagination, stop once the menu is full. */
   506 		if (pgn == MENU_NO_PAGINATION)
   507 		{
   508 			/* If we've filled up, then stop */
   509 			if (foundItems >= maxItems)
   510 			{
   511 				break;
   512 			}
   513 		}
   514 		/* If we're descending and this is the first item, stop */
   515 		if (order == ItemOrder_Descending)
   516 		{
   517 			if (i == 0)
   518 			{
   519 				break;
   520 			}
   521 			i--;
   522 		} 
   523 		/* If we're ascending and this is the last item, stop */
   524 		else if (order == ItemOrder_Ascending)
   525 		{
   526 			if (i >= totalItems - 1)
   527 			{
   528 				break;
   529 			}
   530 			i++;
   531 		}
   532 	}
   533 
   534 	/* There were no items to draw! */
   535 	if (!foundItems)
   536 	{
   537 		display->DeleteThis();
   538 		return NULL;
   539 	}
   540 
   541 	/* Check initial buttons */
   542 	bool displayPrev = false;
   543 	bool displayNext = false;
   544 	if (foundExtra)
   545 	{
   546 		if (order == ItemOrder_Descending)
   547 		{
   548 			displayPrev = true;
   549 			md.firstItem = extraItem;
   550 		} else if (order == ItemOrder_Ascending) {
   551 			displayNext = true;
   552 			md.lastItem = extraItem;
   553 		}
   554 	}
   555 
   556 	/**
   557 	 * If we're paginated, we have to find if there is another page.
   558 	 * Sadly, the only way to do this is to try drawing more items!
   559 	 */
   560 	if (pgn != MENU_NO_PAGINATION)
   561 	{
   562 		unsigned int lastItem = 0;
   563 		ItemDrawInfo dr;
   564 		/* Find the last feasible item to search from. */
   565 		if (order == ItemOrder_Descending)
   566 		{
   567 			lastItem = drawItems[0].position;
   568 			if (lastItem >= totalItems - 1)
   569 			{
   570 				goto skip_search;
   571 			}
   572 			while (++lastItem < totalItems)
   573 			{
   574 				if (menu->GetItemInfo(lastItem, &dr) != NULL)
   575 				{
   576 					mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
   577 					if (IsSlotItem(display, dr.style))
   578 					{
   579 						displayNext = true;
   580 						md.lastItem = lastItem;
   581 						break;
   582 					}
   583 				}
   584 			}
   585 		} else if (order == ItemOrder_Ascending) {
   586 			lastItem = drawItems[0].position;
   587 			if (lastItem == 0)
   588 			{
   589 				goto skip_search;
   590 			}
   591 			lastItem--;
   592 			while (lastItem != 0)
   593 			{
   594 				if (menu->GetItemInfo(lastItem, &dr) != NULL)
   595 				{
   596 					mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
   597 					if (IsSlotItem(display, dr.style))
   598 					{
   599 						displayPrev = true;
   600 						md.firstItem = lastItem;
   601 						break;
   602 					}
   603 				}
   604 				lastItem--;
   605 			}
   606 		}
   607 	}
   608 skip_search:
   609 
   610 	/* Draw the item according to the order */
   611 	menu_slots_t *slots = md.slots;
   612 	unsigned int position = 0;			/* Keep track of the last position */
   613 	if (order == ItemOrder_Ascending)
   614 	{
   615 		for (unsigned int i=0; i<foundItems; i++)
   616 		{
   617 			ItemDrawInfo &dr = drawItems[i].draw;
   618 			if ((position = mh->OnMenuDisplayItem(menu, client, display, drawItems[i].position, dr)) == 0)
   619 			{
   620 				position = display->DrawItem(dr);
   621 			}
   622 			if (position != 0)
   623 			{
   624 				slots[position].item = drawItems[i].position;
   625 				slots[position].type = ItemSel_Item;
   626 			}
   627 		}
   628 	} else if (order == ItemOrder_Descending) {
   629 		unsigned int i = foundItems;
   630 		/* NOTE: There will always be at least one item because
   631 		 * of the check earlier.
   632 		 */
   633 		while (i--)
   634 		{
   635 			ItemDrawInfo &dr = drawItems[i].draw;
   636 			if ((position = mh->OnMenuDisplayItem(menu, client, display, drawItems[i].position, dr)) == 0)
   637 			{
   638 				position = display->DrawItem(dr);
   639 			}
   640 			if (position != 0)
   641 			{
   642 				slots[position].item = drawItems[i].position;
   643 				slots[position].type = ItemSel_Item;
   644 			}
   645 		}
   646 	}
   647 
   648 	/* Now, we need to check if we need to add anything extra */
   649 	if (pgn != MENU_NO_PAGINATION)
   650 	{
   651 		bool canDrawDisabled = display->CanDrawItem(ITEMDRAW_DISABLED|ITEMDRAW_CONTROL);
   652 		bool exitButton = menu->GetExitButton();
   653 		bool exitBackButton = menu->GetExitBackButton();
   654 		char text[50];
   655 
   656 		/* Calculate how many items we are allowed for control stuff */
   657 		unsigned int padding = style->GetMaxPageItems() - maxItems;
   658 		
   659 		/* Add the number of available slots */
   660 		padding += (maxItems - foundItems);
   661 
   662 		/* Someday, if we are able to re-enable this, we will be very lucky men. */
   663 #if 0
   664 		if (!style->FeatureExists(MenuStyleFeature_ImplicitExit))
   665 		{
   666 #endif
   667 		/* Even if we don't draw an exit button, we invalidate the slot. */
   668 		padding--;
   669 #if 0
   670 		} else {
   671 			/* Otherwise, we don't draw anything and leave the slot available */
   672 			exitButton = false;
   673 		}
   674 #endif
   675 
   676 		/* Subtract two slots for the displayNext/displayPrev padding */
   677 		padding -= 2;
   678 
   679 		/* If we have an "Exit Back" button and the space to draw it, do so. */
   680 		if (exitBackButton)
   681 		{
   682 			if (!displayPrev)
   683 			{
   684 				displayPrev = true;
   685 			} else {
   686 				exitBackButton = false;
   687 			}
   688 		}
   689 
   690 		/**
   691 		 * We allow next/prev to be undrawn if neither exists.
   692 		 * Thus, we only need padding if one of them will be drawn,
   693 		 * or the exit button will be drawn.
   694 		 */
   695 		ItemDrawInfo padItem(NULL, ITEMDRAW_SPACER);
   696 		if (exitButton || (displayNext || displayPrev))
   697 		{
   698 			/* If there are no control options,
   699 			 * Instead just pad with invisible slots.
   700 			 */
   701 			if (!displayPrev && !displayPrev)
   702 			{
   703 				padItem.style = ITEMDRAW_NOTEXT;
   704 			}
   705 			/* Add spacers so we can pad to the end */
   706 			for (unsigned int i=0; i<padding; i++)
   707 			{
   708 				position = display->DrawItem(padItem);
   709 				slots[position].type = ItemSel_None;
   710 			}
   711 		}
   712 
   713 		/* Put a fake spacer before control stuff, if possible */
   714 		if ((displayPrev || displayNext) || exitButton)
   715 		{
   716 			ItemDrawInfo draw("", ITEMDRAW_RAWLINE|ITEMDRAW_SPACER);
   717 			display->DrawItem(draw);
   718 		}
   719 
   720 		ItemDrawInfo dr(text, 0);
   721 		/**
   722 		 * If we have one or the other, we need to have spacers for both.
   723 		 */
   724 		if (displayPrev || displayNext)
   725 		{
   726 			/* PREVIOUS */
   727 			ItemDrawInfo padCtrlItem(NULL, ITEMDRAW_SPACER|ITEMDRAW_CONTROL);
   728 			if (displayPrev || canDrawDisabled)
   729 			{
   730 				if (exitBackButton)
   731 				{
   732 					CorePlayerTranslate(client, text, sizeof(text), "Back", NULL);
   733 					dr.style = ITEMDRAW_CONTROL;
   734 					position = display->DrawItem(dr);
   735 					slots[position].type = ItemSel_ExitBack;
   736 				} else {
   737 					CorePlayerTranslate(client, text, sizeof(text), "Previous", NULL);
   738 					dr.style = (displayPrev ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
   739 					position = display->DrawItem(dr);
   740 					slots[position].type = ItemSel_Back;
   741 				}
   742 			} else if (displayNext || exitButton) {
   743 				/* If we can't display this, and there is an exit button,
   744 				 * we need to pad!
   745 				 */
   746 				position = display->DrawItem(padCtrlItem);
   747 				slots[position].type = ItemSel_None;
   748 			}
   749 
   750 			/* NEXT */
   751 			if (displayNext || canDrawDisabled)
   752 			{
   753 				CorePlayerTranslate(client, text, sizeof(text), "Next", NULL);
   754 				dr.style = (displayNext ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
   755 				position = display->DrawItem(dr);
   756 				slots[position].type = ItemSel_Next;
   757 			} else if (exitButton) {
   758 				/* If we can't display this,
   759 				 * but there is an "exit" button, we need to pad!
   760 				 */
   761 				position = display->DrawItem(padCtrlItem);
   762 				slots[position].type = ItemSel_None;
   763 			}
   764 		} else {
   765 			/* Otherwise, bump to two slots! */
   766 			ItemDrawInfo numBump(NULL, ITEMDRAW_NOTEXT);
   767 			position = display->DrawItem(numBump);
   768 			slots[position].type = ItemSel_None;
   769 			position = display->DrawItem(numBump);
   770 			slots[position].type = ItemSel_None;
   771 		}
   772 
   773 		/* EXIT */
   774 		if (exitButton)
   775 		{
   776 			CorePlayerTranslate(client, text, sizeof(text), "Exit", NULL);
   777 			dr.style = ITEMDRAW_CONTROL;
   778 			position = display->DrawItem(dr);
   779 			slots[position].type = ItemSel_Exit;
   780 		}
   781 	}
   782 
   783 	/* Lastly, fill in any slots we could have missed */
   784 	for (unsigned int i = position + 1; i < 10; i++)
   785 	{
   786 		slots[i].type = ItemSel_None;
   787 	}
   788 
   789 	/* Do title stuff */
   790 	mh->OnMenuDisplay(menu, client, display);
   791 	display->DrawTitle(menu->GetDefaultTitle(), true);
   792 
   793 	return display;
   794 }
   795 
   796 IMenuStyle *MenuManager::GetDefaultStyle()
   797 {
   798 	return m_pDefaultStyle;
   799 }
   800 
   801 IVoteMenuHandler *MenuManager::CreateVoteWrapper(IMenuHandler *mh)
   802 {
   803 	VoteMenuHandler *vh = NULL;
   804 
   805 	if (m_VoteHandlers.empty())
   806 	{
   807 		vh = new VoteMenuHandler;
   808 	} else {
   809 		vh = m_VoteHandlers.front();
   810 		m_VoteHandlers.pop();
   811 	}
   812 
   813 	vh->Reset(mh);
   814 
   815 	return vh;
   816 }
   817 
   818 void MenuManager::ReleaseVoteWrapper(IVoteMenuHandler *mh)
   819 {
   820 	if (mh == NULL)
   821 	{
   822 		return;
   823 	}
   824 
   825 	m_VoteHandlers.push((VoteMenuHandler *)mh);
   826 }
   827 
   828 bool MenuManager::MenuSoundsEnabled()
   829 {
   830 	return (sm_menu_sounds.GetInt() != 0);
   831 }
   832 
   833 ConfigResult MenuManager::OnSourceModConfigChanged(const char *key,
   834 												   const char *value,
   835 												   ConfigSource source,
   836 												   char *error,
   837 												   size_t maxlength)
   838 {
   839 	if (strcmp(key, "MenuItemSound") == 0)
   840 	{
   841 		m_SelectSound.assign(value);
   842 		return ConfigResult_Accept;
   843 	} else if (strcmp(key, "MenuExitBackSound") == 0) {
   844 		m_ExitBackSound.assign(value);
   845 		return ConfigResult_Accept;
   846 	} else if (strcmp(key, "MenuExitSound") == 0) {
   847 		m_ExitSound.assign(value);
   848 		return ConfigResult_Accept;
   849 	}
   850 
   851 	return ConfigResult_Ignore;
   852 }
   853 
   854 const char *MenuManager::GetMenuSound(ItemSelection sel)
   855 {
   856 	const char *sound = NULL;
   857 
   858 	switch (sel)
   859 	{
   860 	case ItemSel_Back:
   861 	case ItemSel_Next:
   862 	case ItemSel_Item:
   863 		{
   864 			if (m_SelectSound.size() > 0)
   865 			{
   866 				sound = m_SelectSound.c_str();
   867 			}
   868 			break;
   869 		}
   870 	case ItemSel_ExitBack:
   871 		{
   872 			if (m_ExitBackSound.size() > 0)
   873 			{
   874 				sound = m_ExitBackSound.c_str();
   875 			}
   876 			break;
   877 		}
   878 	case ItemSel_Exit:
   879 		{
   880 			if (m_ExitSound.size() > 0)
   881 			{
   882 				sound= m_ExitSound.c_str();
   883 			}
   884 			break;
   885 		}
   886 	default:
   887 		{
   888 			break;
   889 		}
   890 	}
   891 
   892 	return sound;
   893 }