This repository has been archived on 2024-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
magicka/deps/odoors/ODPopup.c
2017-03-19 07:49:46 +10:00

717 lines
23 KiB
C

/* OpenDoors Online Software Programming Toolkit
* (C) Copyright 1991 - 1999 by Brian Pirie.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
* File: ODPopUp.c
*
* Description: Implements od_popup_menu(), for displaying a menu in
* a window, allowing the user to make a selection using
* "hot keys" or by using arrow keys.
*
* Revisions: Date Ver Who Change
* ---------------------------------------------------------------
* Oct 13, 1994 6.00 BP New file header format.
* Dec 09, 1994 6.00 BP Standardized coding style.
* Jan 15, 1995 6.00 BP Free menu structure on menu destroy.
* Feb 02, 1995 6.00 BP Added od_yield() call in for(;;) loop.
* Aug 19, 1995 6.00 BP 32-bit portability.
* Nov 11, 1995 6.00 BP Removed register keyword.
* Nov 14, 1995 6.00 BP Change valid range of nLevel to 0-10.
* Nov 16, 1995 6.00 BP Removed oddoor.h, added odcore.h.
* Nov 17, 1995 6.00 BP Use new input queue mechanism.
* Dec 12, 1995 6.00 BP Added entry, exit and kernel macros.
* Dec 23, 1995 6.00 BP Restore original color on exit.
* Dec 30, 1995 6.00 BP Added ODCALL for calling convention.
* Jan 04, 1996 6.00 BP Use od_get_input().
* Jan 12, 1996 6.00 BP Claim exclusive use of arrow keys.
* Jan 30, 1996 6.00 BP Replaced od_yield() with od_sleep().
* Jan 31, 1996 6.00 BP Added timeout for od_get_input().
* Jan 31, 1996 6.00 BP Add ODPopupCheckForKey() wait param.
* Jan 31, 1996 6.00 BP Ignore left & right if !MENU_PULLDOWN.
* Feb 13, 1996 6.00 BP Added od_get_input() flags parameter.
* Feb 19, 1996 6.00 BP Changed version number to 6.00.
* Mar 03, 1996 6.10 BP Begin version 6.10.
* Aug 10, 2003 6.23 SH *nix support
*/
#define BUILDING_OPENDOORS
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include "OpenDoor.h"
#include "ODCore.h"
#include "ODGen.h"
#include "ODPlat.h"
#include "ODKrnl.h"
#include "ODStat.h"
/* Configurable od_popup_menu() parameters. */
/* Maximum menu level. */
#define MENU_LEVELS 11
/* Maximum number of items in a menu. */
#define MAX_MENU_ITEMS 21
/* Maximum width of any menu item. */
#define MAX_ITEM_WIDTH 76
/* Other manifest constants. */
#define NO_COMMAND -10
/* Local data types. */
/* Information on an individual menu item. */
typedef struct
{
char szItemText[MAX_ITEM_WIDTH + 1];
BYTE btKeyIndex;
} tMenuItem;
/* Information on a popup menu level. */
typedef struct
{
tMenuItem *paMenuItems;
BYTE btNumMenuItems;
BYTE btWidth;
BYTE btRight;
BYTE btBottom;
BYTE btCursor;
BYTE btLeft;
BYTE btTop;
WORD wFlags;
void *pWindow;
} tMenuLevelInfo;
/* Private variables. */
/* Array of information on each menu level. */
tMenuLevelInfo MenuLevelInfo[MENU_LEVELS];
/* Current menu settings. */
static BYTE btCorrectItem;
static INT nCommand;
static WORD wCurrentFlags;
static BYTE btCurrentNumMenuItems;
static INT nCurrentLevel;
/* Private helper functions used by od_popup_menu(). */
static void ODPopupCheckForKey(BOOL bWaitForInput);
static void ODPopupDisplayMenuItem(BYTE btLeft, BYTE btTop,
tMenuItem *paMenuItems, BYTE btItemIndex, BOOL bHighlighted, BYTE btWidth,
BOOL bPositionCursor);
/* ----------------------------------------------------------------------------
* od_popup_menu()
*
* Displays a popup menu on the local and remote screens.
*
* Parameters: pszTitle - Text to show as the window title of the popup menu.
* If no title is desired, this parameter should be set
* to either "" or NULL.
*
* pszText - String which contains the menu definition. In the
* menu definition string, individual menu items are
* separated by a pipe ('|') character, and hotkeys are
* proceeded by a carat ('^') character.
*
* nLeft - The 1-based column number of the upper right corner
* of the menu.
*
* nTop - The 1-based row number of the upper right corner of
* the menu.
*
* nLevel - Menu level, which must be a value between 0 and
* MENU_LEVELS.
*
* uFlags - One or more flags, combined by the bitwise or (|)
* operator.
*
* Return: POPUP_ERROR on error, POPUP_ESCAPE if user pressed the Escape
* key, POPUP_LEFT if the user choose to move to the next menu to
* the left, POPUP_RIGHT if the user choose to move to the next
* menu to the right, or a postive value if the user choose an item
* from the menu. In this case, the return value is the 1-based
* index of the selected menu item.
*/
ODAPIDEF INT ODCALL od_popup_menu(char *pszTitle, char *pszText, INT nLeft,
INT nTop, INT nLevel, WORD uFlags)
{
tMenuItem *paMenuItems = NULL;
BYTE btCount;
BYTE btWidth;
BYTE btRight;
BYTE btBottom;
BYTE btCursor;
BYTE btLeft;
BYTE btTop;
void *pWindow;
BYTE btBetweenSize;
BYTE btTitleSize;
BYTE btRemaining;
BYTE btLineCount;
INT16 nOriginalAttrib;
/* Log function entry if running in trace mode. */
TRACE(TRACE_API, "od_popup_menu()");
/* Initialize OpenDoors, if not already done. */
if(!bODInitialized) od_init();
OD_API_ENTRY();
/* Setup od_box_chars appropriately. */
if(od_control.od_box_chars[BOX_BOTTOM] == 0)
{
od_control.od_box_chars[BOX_BOTTOM] = od_control.od_box_chars[BOX_TOP];
}
if(od_control.od_box_chars[BOX_RIGHT] == 0)
{
od_control.od_box_chars[BOX_RIGHT] = od_control.od_box_chars[BOX_LEFT];
}
/* Store initial display color. */
nOriginalAttrib = od_control.od_cur_attrib;
/* check level bounds */
if(nLevel < 0 || nLevel > MENU_LEVELS)
{
od_control.od_error = ERR_LIMIT;
OD_API_EXIT();
return(POPUP_ERROR);
}
/* normalize level */
nCurrentLevel = nLevel;
if(MenuLevelInfo[nLevel].pWindow == NULL)
{
btLeft = nLeft;
btTop = nTop;
wCurrentFlags = uFlags;
if(pszText == NULL)
{
od_control.od_error = ERR_PARAMETER;
OD_API_EXIT();
return(POPUP_ERROR);
}
if(paMenuItems == NULL)
{
if((paMenuItems = malloc(sizeof(tMenuItem) * MAX_MENU_ITEMS)) == NULL)
{
od_control.od_error = ERR_PARAMETER;
OD_API_EXIT();
return(POPUP_ERROR);
}
}
MenuLevelInfo[nLevel].paMenuItems = paMenuItems;
btCurrentNumMenuItems = 0;
btWidth = 0;
btCount = 0;
nCommand = NO_COMMAND;
paMenuItems[0].btKeyIndex = 0;
while(*pszText && btCurrentNumMenuItems < MAX_MENU_ITEMS)
{
switch(*pszText)
{
case '|':
paMenuItems[btCurrentNumMenuItems++].szItemText[btCount]
= '\0';
if(btCount > btWidth) btWidth = btCount;
btCount = 0;
paMenuItems[btCurrentNumMenuItems].btKeyIndex = 0;
break;
case '^':
if(btCount < MAX_ITEM_WIDTH)
{
paMenuItems[btCurrentNumMenuItems].btKeyIndex = btCount;
}
break;
default:
if(btCount < MAX_ITEM_WIDTH)
{
paMenuItems[btCurrentNumMenuItems].szItemText[btCount++] =
*pszText;
}
}
++pszText;
}
/* If we were in the middle of a menu item when we encountered the end */
/* of the string, then it should form an additional menu entry. This */
/* handles the case of a menu string to no terminating | for the last */
/* entry. */
if(btCount != 0)
{
/* null-terminate current menu entry string */
paMenuItems[btCurrentNumMenuItems++].szItemText[btCount] = '\0';
/* If this is the widest entry, update he menu width appropriately */
if(btCount > btWidth) btWidth = btCount;
}
/* If the menu description string does not contain any menu items */
if(btCurrentNumMenuItems == 0)
{
/* Return with parameter error */
od_control.od_error = ERR_PARAMETER;
OD_API_EXIT();
return(POPUP_ERROR);
}
/* Adjust menu width to allow title to fit, if possible */
/* If a title string was passed, and that string is wider than widest */
/* menu entry ... */
if(pszTitle != NULL && strlen(pszTitle) + 2 > btWidth)
{
/* Then width of menu window should be large enough to allow up to */
/* the first 76 characters of the title to fit. */
btWidth = strlen(pszTitle) + 2 > MAX_ITEM_WIDTH
? MAX_ITEM_WIDTH : strlen(pszTitle) + 2;
}
/* Based on number and size of menu items, and width of title, */
/* determine the bottom, right and inside width of the menu. */
btBottom = btTop + btCurrentNumMenuItems + 1;
btRight = btLeft + btWidth + 3;
btBetweenSize = (btRight - btLeft) - 1;
/* If neither ANSI nor AVATAR mode is available, return with an error */
if(!(od_control.user_ansi || od_control.user_avatar))
{
od_control.od_error = ERR_NOGRAPHICS;
OD_API_EXIT();
return(POPUP_ERROR);
}
/* If menu would "fall off" edge of screen, return with an error */
if(btLeft < 1 || btTop < 1 || btRight > OD_SCREEN_WIDTH
|| btBottom > OD_SCREEN_HEIGHT || btRight - btLeft < 2
|| btBottom - btTop < 2)
{
od_control.od_error = ERR_PARAMETER;
OD_API_EXIT();
return(POPUP_ERROR);
}
/* Allocate space to store window information. If unable to allocate */
/* enough space, return with an error. */
if((pWindow = malloc((btRight - btLeft + 1) * 2
+ (btBottom - btTop + 1) * 160)) == NULL)
{
od_control.od_error = ERR_MEMORY;
OD_API_EXIT();
return(POPUP_ERROR);
}
/* Store contents of screen where memu will be drawn in the temporary */
/* buffer. */
if(!od_gettext(btLeft, btTop, btRight, btBottom, pWindow))
{
free(pWindow);
pWindow = NULL;
/* Note that od_error code has been set in od_gettext(). */
OD_API_EXIT();
return(POPUP_ERROR);
}
/* Determine number of characters of title to be displayed */
if(pszTitle == NULL)
{
btTitleSize = 0;
}
else
{
if((btTitleSize = strlen(pszTitle)) > (btBetweenSize - 4))
{
btTitleSize = btBetweenSize - 4;
}
}
od_set_cursor(btTop,btLeft);
od_set_attrib(od_control.od_menu_border_col);
od_putch(od_control.od_box_chars[BOX_UPPERLEFT]);
if(btTitleSize == 0)
{
od_repeat(od_control.od_box_chars[BOX_TOP], btBetweenSize);
}
else
{
od_repeat(od_control.od_box_chars[BOX_TOP],
btRemaining = ((btBetweenSize - btTitleSize - 2) / 2));
od_set_attrib(od_control.od_menu_title_col);
od_putch(' ');
od_disp(pszTitle,btTitleSize, TRUE);
od_putch(' ');
od_set_attrib(od_control.od_menu_border_col);
od_repeat(od_control.od_box_chars[BOX_TOP],
(BYTE)(btBetweenSize - btRemaining - btTitleSize - 2));
}
od_putch(od_control.od_box_chars[BOX_UPPERRIGHT]);
btLineCount = btTop + 1;
btCorrectItem = 0;
ODPopupCheckForKey(FALSE);
btCursor = btCorrectItem;
for(btCount = 0; btCount < btCurrentNumMenuItems
&& btLineCount < btBottom; ++btCount)
{
ODPopupCheckForKey(FALSE);
if(nCommand != NO_COMMAND && !(wCurrentFlags & MENU_KEEP))
{
goto exit_now;
}
od_set_cursor(btLineCount,btLeft);
od_putch(od_control.od_box_chars[BOX_LEFT]);
od_set_attrib(od_control.od_menu_text_col);
if(btCount == btCursor)
{
ODPopupDisplayMenuItem(btLeft, btTop, paMenuItems, btCount,
TRUE, btWidth, FALSE);
}
else
{
ODPopupDisplayMenuItem(btLeft, btTop, paMenuItems, btCount,
FALSE, btWidth, FALSE);
}
od_set_attrib(od_control.od_menu_border_col);
od_putch(od_control.od_box_chars[BOX_RIGHT]);
++btLineCount;
}
od_set_cursor(btBottom, btLeft);
od_putch(od_control.od_box_chars[BOX_LOWERLEFT]);
od_repeat(od_control.od_box_chars[BOX_BOTTOM], btBetweenSize);
od_putch(od_control.od_box_chars[BOX_LOWERRIGHT]);
od_set_cursor(btTop + 1, btLeft + 1);
}
else
{
paMenuItems = MenuLevelInfo[nLevel].paMenuItems;
btCurrentNumMenuItems = MenuLevelInfo[nLevel].btNumMenuItems;
btWidth = MenuLevelInfo[nLevel].btWidth;
btRight = MenuLevelInfo[nLevel].btRight;
btBottom = MenuLevelInfo[nLevel].btBottom;
btLeft = MenuLevelInfo[nLevel].btLeft;
btTop = MenuLevelInfo[nLevel].btTop;
wCurrentFlags = MenuLevelInfo[nLevel].wFlags;
pWindow = MenuLevelInfo[nLevel].pWindow;
btCorrectItem = btCursor = MenuLevelInfo[nLevel].btCursor;
nCommand = NO_COMMAND;
if(uFlags & MENU_DESTROY)
{
nCommand = POPUP_ESCAPE;
goto destroy;
}
/* Otherwise, position flashing hardware cursor appropriately */
od_set_cursor(btTop + btCursor + 1, btLeft + 1);
}
/* Claim exclusive use of arrow keys. */
ODStatStartArrowUse();
for(;;)
{
ODPopupCheckForKey(TRUE);
if(btCorrectItem != btCursor)
{
ODPopupDisplayMenuItem(btLeft, btTop, paMenuItems, btCursor,
FALSE, btWidth, TRUE);
btCursor = btCorrectItem;
ODWaitDrain(25);
ODPopupCheckForKey(FALSE);
ODPopupDisplayMenuItem(btLeft, btTop, paMenuItems, btCursor,
TRUE, btWidth, TRUE);
}
if(nCommand != NO_COMMAND)
{
goto exit_now;
}
}
exit_now:
if((!(wCurrentFlags & MENU_KEEP)) || nCommand <= 0)
{
destroy:
od_puttext(btLeft, btTop, btRight, btBottom, pWindow);
free(pWindow);
MenuLevelInfo[nLevel].pWindow = NULL;
if(paMenuItems != NULL)
{
free(paMenuItems);
MenuLevelInfo[nLevel].paMenuItems = NULL;
}
}
else if(wCurrentFlags & MENU_KEEP)
{
MenuLevelInfo[nLevel].paMenuItems = paMenuItems;
MenuLevelInfo[nLevel].btNumMenuItems = btCurrentNumMenuItems;
MenuLevelInfo[nLevel].btWidth = btWidth;
MenuLevelInfo[nLevel].btRight = btRight;
MenuLevelInfo[nLevel].btBottom = btBottom;
MenuLevelInfo[nLevel].btCursor = btCursor;
MenuLevelInfo[nLevel].btLeft = btLeft;
MenuLevelInfo[nLevel].btTop = btTop;
MenuLevelInfo[nLevel].wFlags = wCurrentFlags;
MenuLevelInfo[nLevel].pWindow = pWindow;
}
/* Restore original display color. */
od_set_attrib(nOriginalAttrib);
/* Release exclusive use of arrow keys. */
ODStatEndArrowUse();
OD_API_EXIT();
return(nCommand);
}
/* ----------------------------------------------------------------------------
* ODPopupCheckForKey() *** PRIVATE FUNCTION ***
*
* Checks whether or not the user has pressed any key. If one or more keys
* have been pressed, then these keystrokes are processed. This function
* returns when no more keys are waiting in the inbound buffer, or when a key
* has been pressed that requires immediate action (such as the [ENTER] key).
*
* Parameters: bWaitForInput - Indicates whether this function should return
* immediately if no input is waiting (FALSE), or
* wait for the next input even before returning
* (TRUE).
*
* Return: void
*/
static void ODPopupCheckForKey(BOOL bWaitForInput)
{
BYTE btCount;
tODInputEvent InputEvent;
BOOL bDoneAnythingYet = FALSE;
/* Loop, processing keys. If a command has been selected, stop looping */
/* immediately. If there are no more keys waiting, stop looping */
while(nCommand == NO_COMMAND)
{
CALL_KERNEL_IF_NEEDED();
if(!od_get_input(&InputEvent, bWaitForInput && !bDoneAnythingYet
? OD_NO_TIMEOUT : 0, GETIN_NORMAL))
{
/* Return right away if no input event is waiting. */
return;
}
bDoneAnythingYet = TRUE;
if(InputEvent.EventType == EVENT_EXTENDED_KEY)
{
switch(InputEvent.chKeyPress)
{
case OD_KEY_UP:
up_arrow:
if(btCorrectItem == 0)
{
btCorrectItem = btCurrentNumMenuItems - 1;
}
else
{
--btCorrectItem;
}
break;
case OD_KEY_DOWN:
down_arrow:
if(++btCorrectItem >= btCurrentNumMenuItems)
{
btCorrectItem = 0;
}
break;
case OD_KEY_LEFT:
left_arrow:
if(wCurrentFlags & MENU_PULLDOWN)
{
nCommand = POPUP_LEFT;
return;
}
break;
case OD_KEY_RIGHT:
right_arrow:
if(wCurrentFlags & MENU_PULLDOWN)
{
nCommand = POPUP_RIGHT;
return;
}
break;
}
}
else if(InputEvent.EventType == EVENT_CHARACTER)
{
if(InputEvent.chKeyPress == '\n' || InputEvent.chKeyPress == '\r')
{
nCommand = btCorrectItem + 1;
return;
}
else if(InputEvent.chKeyPress == 27)
{
if(wCurrentFlags & MENU_ALLOW_CANCEL)
{
nCommand = POPUP_ESCAPE;
return;
}
}
else
{
/* Check whether key is a menu "hot key" */
for(btCount = 0; btCount < btCurrentNumMenuItems; ++btCount)
{
if(toupper(MenuLevelInfo[nCurrentLevel].paMenuItems[btCount]
.szItemText[MenuLevelInfo[nCurrentLevel].paMenuItems[btCount]
.btKeyIndex]) == toupper(InputEvent.chKeyPress))
{
btCorrectItem = btCount;
nCommand = btCorrectItem + 1;
return;
}
}
/* At this point, we know that key was not one of the "hot keys" */
/* Check for 4, 6, 8 and 2 keys as arrow keys. */
if(InputEvent.chKeyPress == '4')
{
goto left_arrow;
}
else if(InputEvent.chKeyPress == '6')
{
goto right_arrow;
}
else if(InputEvent.chKeyPress == '8')
{
goto up_arrow;
}
else if(InputEvent.chKeyPress == '2')
{
goto down_arrow;
}
}
}
}
}
/* ----------------------------------------------------------------------------
* ODPopupDisplayMenuItem() *** PRIVATE FUNCTION ***
*
* Displays an individual menu item.
*
* Parameters: btLeft - Column number where the menu item will be
* displayed.
*
* btTop - Row number where the menu item will be
* displayed.
*
* paMenuItems - Pointer to array of available menu items.
*
* btItemIndex - Index into paMenuItems of the menu item that
* is to be displayed.
*
* bHighlighted - TRUE if the items is to be displayed as
* highlighted, FALSE if it is to be displayed as
* non-highlighted.
*
* btWidth - Width of the menu item, in characters.
*
* bPositionCursor - TRUE if the cursor needs to be positioned
* prior to drawing the menu item, FALSE if the
* cursor is already in the required position.
*
* Return: void
*/
static void ODPopupDisplayMenuItem(BYTE btLeft, BYTE btTop,
tMenuItem *paMenuItems, BYTE btItemIndex, BOOL bHighlighted, BYTE btWidth,
BOOL bPositionCursor)
{
BYTE btCount;
char *pchItemText;
BYTE btKeyPosition;
BYTE btTextColor;
BYTE btKeyColor;
/* Check that parameters are reasonable when operating in debug mode. */
ASSERT(paMenuItems != NULL);
ASSERT(btItemIndex < MAX_MENU_ITEMS);
ASSERT(btWidth < OD_SCREEN_WIDTH);
++btLeft;
++btTop;
btTextColor = bHighlighted ? od_control.od_menu_highlight_col
: od_control.od_menu_text_col;
btKeyColor = bHighlighted ? od_control.od_menu_highkey_col
: od_control.od_menu_key_col;
pchItemText = (char *)(paMenuItems[btItemIndex].szItemText);
btKeyPosition = paMenuItems[btItemIndex].btKeyIndex;
if(bPositionCursor) od_set_cursor(btTop + btItemIndex, btLeft);
od_set_attrib(btTextColor);
od_putch(' ');
for(btCount = 0; btCount < btWidth && *pchItemText; ++btCount)
{
if(btCount == btKeyPosition)
{
od_set_attrib(btKeyColor);
od_putch(*pchItemText++);
od_set_attrib(btTextColor);
}
else
{
od_putch(*pchItemText++);
}
}
od_repeat(' ', (BYTE)((btWidth - btCount) + 1));
if(bPositionCursor) od_set_cursor(btTop + btItemIndex, btLeft);
}