You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
683 lines
17 KiB
683 lines
17 KiB
/*++
|
|
|
|
Copyright (c) 1993 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
spmenu.c
|
|
|
|
Abstract:
|
|
|
|
Text setup menu support.
|
|
|
|
Author:
|
|
|
|
Ted Miller (tedm) 8-September-1993
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
|
|
#include "spprecmp.h"
|
|
#pragma hdrstop
|
|
|
|
#define MENUITEM_NORMAL 0x00000000
|
|
#define MENUITEM_STATIC 0x00000001
|
|
|
|
|
|
typedef struct _MENU_ITEM {
|
|
|
|
PWSTR Text;
|
|
|
|
ULONG Flags;
|
|
|
|
ULONG LeftX;
|
|
|
|
ULONG_PTR UserData;
|
|
|
|
ULONG OriginalLength;
|
|
|
|
} MENU_ITEM, *PMENU_ITEM;
|
|
|
|
|
|
typedef struct _MENU {
|
|
|
|
PMENU_ITEM Items;
|
|
ULONG ItemCount;
|
|
|
|
ULONG TopY;
|
|
ULONG Height;
|
|
|
|
ULONG LeftX;
|
|
ULONG Width;
|
|
|
|
ULONG TopDisplayedIndex;
|
|
|
|
BOOLEAN MoreUp,MoreDown;
|
|
|
|
} MENU, *PMENU;
|
|
|
|
|
|
|
|
VOID
|
|
pSpMnDrawMenu(
|
|
IN PMENU pMenu,
|
|
IN ULONG SelectedIndex,
|
|
IN BOOLEAN DrawFrame,
|
|
IN BOOLEAN IndicateMore,
|
|
IN PWSTR MoreUpText,
|
|
IN PWSTR MoreDownText
|
|
);
|
|
|
|
|
|
PVOID
|
|
SpMnCreate(
|
|
IN ULONG LeftX,
|
|
IN ULONG TopY,
|
|
IN ULONG Width,
|
|
IN ULONG Height
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Create a new menu by allocating space for a new menu structure
|
|
and initializing its fields.
|
|
|
|
Arguments:
|
|
|
|
LeftX - supplies the 0-based X coordinate of the leftmost column
|
|
of the menu.
|
|
|
|
TopY - supplies the 0-based Y coordinate of the topmost line
|
|
of the menu.
|
|
|
|
Width - supplies the maximum displayed width for lines in the menu.
|
|
|
|
Height - supplies the maximum displayed height of the menu.
|
|
The menu will scroll if it is too long to fit in the
|
|
allotted space.
|
|
|
|
Return Value:
|
|
|
|
Menu handle (expressed as a pvoid) of NULL if memory couldn't
|
|
be allocated.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMENU p;
|
|
|
|
if(p = SpMemAlloc(sizeof(MENU))) {
|
|
|
|
RtlZeroMemory(p,sizeof(MENU));
|
|
|
|
if(p->Items = SpMemAlloc(0)) {
|
|
p->LeftX = LeftX;
|
|
p->TopY = TopY;
|
|
p->Width = Width;
|
|
p->Height = Height;
|
|
} else {
|
|
SpMemFree(p);
|
|
p = NULL;
|
|
}
|
|
}
|
|
|
|
return(p);
|
|
}
|
|
|
|
|
|
VOID
|
|
SpMnDestroy(
|
|
IN PVOID Menu
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Destroy a menu, releasing all memory associated with it.
|
|
|
|
Arguments:
|
|
|
|
Menu - supplies handle to menu to destroy.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMENU pMenu = Menu;
|
|
ULONG u;
|
|
|
|
for(u=0; u<pMenu->ItemCount; u++) {
|
|
if(pMenu->Items[u].Text) {
|
|
SpMemFree(pMenu->Items[u].Text);
|
|
}
|
|
}
|
|
|
|
SpMemFree(pMenu->Items);
|
|
|
|
SpMemFree(pMenu);
|
|
}
|
|
|
|
|
|
|
|
BOOLEAN
|
|
SpMnAddItem(
|
|
IN PVOID Menu,
|
|
IN PWSTR Text,
|
|
IN ULONG LeftX,
|
|
IN ULONG Width,
|
|
IN BOOLEAN Selectable,
|
|
IN ULONG_PTR UserData
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Add an item to a menu.
|
|
|
|
Arguments:
|
|
|
|
Menu - supplies handle to menu to which the item is to be added.
|
|
|
|
Text - supplies text that comprises the menu selection. This routine
|
|
will make a copy of the text.
|
|
|
|
LeftX - supplies 0-based x coordinate of leftmost character of the text
|
|
when it is displayed.
|
|
|
|
Width - supplies width in characters of the field for this selection.
|
|
If this is larger than the number of characters in the text, then
|
|
the text is padded to the right with blanks when highlighted.
|
|
|
|
Selectable - if FALSE, then this text is static -- ie, not selectable.
|
|
|
|
UserData - supplies a ulong's worth of caller-specific data to be associated
|
|
with this menu item.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the menu item was added successfully; FALSE if insufficient memory.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMENU pMenu = Menu;
|
|
PMENU_ITEM p;
|
|
ULONG TextLen;
|
|
ULONG PaddedLen;
|
|
PWSTR String;
|
|
ULONG u;
|
|
ULONG ColumnLen;
|
|
ULONG FillLen;
|
|
|
|
//
|
|
// Build a string that is padded to the right with blanks to make
|
|
// it the right width.
|
|
//
|
|
TextLen = wcslen(Text);
|
|
PaddedLen = max(TextLen,Width);
|
|
ColumnLen = SplangGetColumnCount(Text);
|
|
FillLen = (PaddedLen <= ColumnLen) ? 0 : PaddedLen - ColumnLen;
|
|
|
|
String = SpMemAlloc((PaddedLen+1)*sizeof(WCHAR));
|
|
if(!String) {
|
|
return(FALSE);
|
|
}
|
|
|
|
wcsncpy(String,Text,TextLen);
|
|
for(u=0; u<FillLen; u++) {
|
|
String[TextLen+u] = L' ';
|
|
}
|
|
String[TextLen+u] = 0;
|
|
|
|
//
|
|
// Make space for the item.
|
|
//
|
|
if((p = SpMemRealloc(pMenu->Items,(pMenu->ItemCount+1) * sizeof(MENU_ITEM))) == NULL) {
|
|
SpMemFree(String);
|
|
return(FALSE);
|
|
}
|
|
|
|
pMenu->Items = p;
|
|
|
|
//
|
|
// Calculate the address of the new menu item and
|
|
// indicate that there is now an additional item in the menu.
|
|
//
|
|
p = &pMenu->Items[pMenu->ItemCount++];
|
|
|
|
//
|
|
// Set the fields of the menu.
|
|
//
|
|
p->LeftX = LeftX;
|
|
p->UserData = UserData;
|
|
|
|
p->Flags = Selectable ? MENUITEM_NORMAL : MENUITEM_STATIC;
|
|
|
|
p->Text = String;
|
|
|
|
p->OriginalLength = TextLen;
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
PWSTR
|
|
SpMnGetText(
|
|
IN PVOID Menu,
|
|
IN ULONG_PTR UserData
|
|
)
|
|
{
|
|
PMENU pMenu = Menu;
|
|
ULONG i;
|
|
|
|
for(i=0; i<pMenu->ItemCount; i++) {
|
|
if(pMenu->Items[i].UserData == UserData) {
|
|
return(pMenu->Items[i].Text);
|
|
}
|
|
}
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
PWSTR
|
|
SpMnGetTextDup(
|
|
IN PVOID Menu,
|
|
IN ULONG_PTR UserData
|
|
)
|
|
{
|
|
PMENU pMenu = Menu;
|
|
ULONG i;
|
|
PWSTR p;
|
|
|
|
for(i=0; i<pMenu->ItemCount; i++) {
|
|
if(pMenu->Items[i].UserData == UserData) {
|
|
//
|
|
// Make a duplicate; leave off trailing pad spaces.
|
|
//
|
|
p = SpMemAlloc((pMenu->Items[i].OriginalLength+1)*sizeof(WCHAR));
|
|
wcsncpy(p,pMenu->Items[i].Text,pMenu->Items[i].OriginalLength);
|
|
p[pMenu->Items[i].OriginalLength] = 0;
|
|
return(p);
|
|
}
|
|
}
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
|
|
VOID
|
|
SpMnDisplay(
|
|
IN PVOID Menu,
|
|
IN ULONG_PTR UserDataOfHighlightedItem,
|
|
IN BOOLEAN Framed,
|
|
IN PULONG ValidKeys,
|
|
IN PULONG Mnemonics, OPTIONAL
|
|
IN PMENU_CALLBACK_ROUTINE NewHighlightCallback, OPTIONAL
|
|
IN PMENU_SELECTION_CALLBACK_ROUTINE SelectionCallbackRoutine,OPTIONAL
|
|
OUT PULONG KeyPressed,
|
|
OUT PULONG_PTR UserDataOfSelectedItem
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Display a menu and accept keystrokes.
|
|
|
|
When the user presses a menu keystroke (up/down arrow keys), this
|
|
routine automatically updates the highlight and calls a callback function
|
|
to inform the caller that a new item has the highlight.
|
|
|
|
When the user presses a keystroke in a list provided by the caller,
|
|
this routine returns, providing information about the key pressed and
|
|
the item that was highlighted when the key was pressed.
|
|
|
|
Arguments:
|
|
|
|
Menu - supplies handle to menu to be displayed.
|
|
|
|
UserDataOfHighlightedItem - supplies user data of the menu item which
|
|
is to receive the highlight initially.
|
|
|
|
Framed - if TRUE, then draw a single-line bordera around the menu.
|
|
|
|
ValidKeys - supplies a list of keystrokes that cause this routine to
|
|
return to the caller. The list must be terminated with a 0 entry.
|
|
|
|
NewHighlightCallback - If specified, supplies a routine to be called
|
|
when a new item has received the highlight.
|
|
|
|
SelectionCallbackRoutine - If specified, supplies a routine to be called
|
|
when a item in the menu is selected.
|
|
|
|
KeyPressed - receives the key press that caused this routine to exit.
|
|
This will be a valid from the ValidKeys array.
|
|
|
|
UserDataOfSelectedItem - receives the UserData of the item that had the
|
|
highlight when the user pressed a key in ValidKeys.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
ULONG ValidMenuKeys[3] = { KEY_UP, KEY_DOWN, 0 };
|
|
ULONG key;
|
|
PMENU pMenu = Menu;
|
|
ULONG SelectedIndex,OldIndex;
|
|
BOOLEAN FoundNewItem;
|
|
ULONG NewTopDisplayedIndex;
|
|
BOOLEAN MustScroll;
|
|
PWSTR MoreUpText,MoreDownText;
|
|
|
|
|
|
//
|
|
// Get the text for the text that indicate that there are more
|
|
// selections.
|
|
//
|
|
SpFormatMessage(TemporaryBuffer,sizeof(TemporaryBuffer),SP_TEXT_MORE_UP);
|
|
MoreUpText = SpDupStringW(TemporaryBuffer);
|
|
SpFormatMessage(TemporaryBuffer,sizeof(TemporaryBuffer),SP_TEXT_MORE_DOWN);
|
|
MoreDownText = SpDupStringW(TemporaryBuffer);
|
|
|
|
//
|
|
// Locate the seleccted item.
|
|
//
|
|
for(SelectedIndex=0; SelectedIndex<pMenu->ItemCount; SelectedIndex++) {
|
|
if(!(pMenu->Items[SelectedIndex].Flags & MENUITEM_STATIC)
|
|
&& (pMenu->Items[SelectedIndex].UserData == UserDataOfHighlightedItem))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
ASSERT(SelectedIndex < pMenu->ItemCount);
|
|
|
|
//
|
|
// In free builds we bugcheck later on because of it being equal so
|
|
// inserting code to handle this situation even if it is a remote case
|
|
// but can occur.
|
|
//
|
|
if (SelectedIndex >= pMenu->ItemCount){
|
|
SelectedIndex = pMenu->ItemCount - 1;
|
|
}
|
|
|
|
|
|
//
|
|
// Make sure the selected item will be visible when we draw the menu.
|
|
//
|
|
pMenu->TopDisplayedIndex = 0;
|
|
while(SelectedIndex >= pMenu->TopDisplayedIndex + pMenu->Height) {
|
|
pMenu->TopDisplayedIndex += pMenu->Height;
|
|
}
|
|
|
|
//
|
|
// Draw the menu itself.
|
|
//
|
|
pSpMnDrawMenu(pMenu,SelectedIndex,Framed,Framed,MoreUpText,MoreDownText);
|
|
|
|
while(1) {
|
|
|
|
//
|
|
// Wait for a valid keypress.
|
|
//
|
|
key = SpWaitValidKey(ValidKeys,ValidMenuKeys,Mnemonics);
|
|
|
|
//
|
|
// If the key is a menu keystroke, handle it here.
|
|
//
|
|
FoundNewItem = FALSE;
|
|
MustScroll = FALSE;
|
|
OldIndex = SelectedIndex;
|
|
|
|
switch(key) {
|
|
|
|
case KEY_UP:
|
|
|
|
//
|
|
// Locate the previous selectable item.
|
|
//
|
|
if(SelectedIndex) {
|
|
|
|
for(SelectedIndex=SelectedIndex-1; (LONG)SelectedIndex>=0; SelectedIndex--) {
|
|
if(!(pMenu->Items[SelectedIndex].Flags & MENUITEM_STATIC)) {
|
|
FoundNewItem = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(FoundNewItem) {
|
|
//
|
|
// Figure out whether we have to scroll the menu.
|
|
//
|
|
if(SelectedIndex < pMenu->TopDisplayedIndex) {
|
|
MustScroll = TRUE;
|
|
NewTopDisplayedIndex = SelectedIndex;
|
|
}
|
|
} else {
|
|
//
|
|
// If the first lines are static text, there might be no
|
|
// way to get them back on the screen -- the tests above
|
|
// fail in this case. So if the user hits the up arrow
|
|
// when he's at the topmost selectable item but there are
|
|
// static items above him, we'll simply scroll the menu
|
|
// so that item #0 is at the top.
|
|
//
|
|
FoundNewItem = TRUE;
|
|
NewTopDisplayedIndex = 0;
|
|
MustScroll = TRUE;
|
|
SelectedIndex = OldIndex;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KEY_DOWN:
|
|
|
|
//
|
|
// Locate the next selectable item.
|
|
//
|
|
if(SelectedIndex < pMenu->ItemCount) {
|
|
|
|
for(SelectedIndex=SelectedIndex+1; SelectedIndex < pMenu->ItemCount; SelectedIndex++) {
|
|
|
|
if(!(pMenu->Items[SelectedIndex].Flags & MENUITEM_STATIC)) {
|
|
FoundNewItem = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(FoundNewItem) {
|
|
//
|
|
// Figure out whether we have to scroll the menu.
|
|
//
|
|
if(SelectedIndex >= pMenu->TopDisplayedIndex + pMenu->Height) {
|
|
MustScroll = TRUE;
|
|
NewTopDisplayedIndex = pMenu->TopDisplayedIndex + SelectedIndex - OldIndex;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (SelectionCallbackRoutine){
|
|
if (!SelectionCallbackRoutine(pMenu->Items[SelectedIndex].UserData,
|
|
key)){
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//
|
|
// User pressed a non-menu key.
|
|
//
|
|
*KeyPressed = key;
|
|
*UserDataOfSelectedItem = pMenu->Items[SelectedIndex].UserData;
|
|
|
|
SpMemFree(MoreUpText);
|
|
SpMemFree(MoreDownText);
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
if(FoundNewItem) {
|
|
|
|
//
|
|
// Unhighlight the currently selected item.
|
|
//
|
|
SpvidDisplayString(
|
|
pMenu->Items[OldIndex].Text,
|
|
DEFAULT_ATTRIBUTE,
|
|
pMenu->Items[OldIndex].LeftX,
|
|
pMenu->TopY + OldIndex - pMenu->TopDisplayedIndex
|
|
);
|
|
|
|
|
|
//
|
|
// Highlight the newly selected item. This may involve
|
|
// scrolling the menu.
|
|
//
|
|
if(MustScroll) {
|
|
//
|
|
// Redraw the menu so the newly highlighted line is in view.
|
|
//
|
|
pMenu->TopDisplayedIndex = NewTopDisplayedIndex;
|
|
|
|
pSpMnDrawMenu(pMenu,SelectedIndex,FALSE,Framed,MoreUpText,MoreDownText);
|
|
}
|
|
|
|
//
|
|
// Highlight the newly selected item.
|
|
//
|
|
SpvidDisplayString(
|
|
pMenu->Items[SelectedIndex].Text,
|
|
ATT_BG_WHITE | ATT_FG_BLUE,
|
|
pMenu->Items[SelectedIndex].LeftX,
|
|
pMenu->TopY + SelectedIndex - pMenu->TopDisplayedIndex
|
|
);
|
|
|
|
|
|
//
|
|
// Inform the caller.
|
|
//
|
|
if(NewHighlightCallback){
|
|
NewHighlightCallback(pMenu->Items[SelectedIndex].UserData);
|
|
}
|
|
|
|
} else {
|
|
SelectedIndex = OldIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
pSpMnDrawMenu(
|
|
IN PMENU pMenu,
|
|
IN ULONG SelectedIndex,
|
|
IN BOOLEAN DrawFrame,
|
|
IN BOOLEAN IndicateMore,
|
|
IN PWSTR MoreUpText,
|
|
IN PWSTR MoreDownText
|
|
)
|
|
{
|
|
ULONG item;
|
|
BOOLEAN MoreUp,MoreDown,MoreStatusChanged;
|
|
|
|
//
|
|
// Blank out the on-screen menu display.
|
|
//
|
|
SpvidClearScreenRegion(
|
|
pMenu->LeftX,
|
|
pMenu->TopY,
|
|
pMenu->Width,
|
|
pMenu->Height,
|
|
DEFAULT_BACKGROUND
|
|
);
|
|
|
|
|
|
MoreUp = (BOOLEAN)(pMenu->TopDisplayedIndex > 0);
|
|
MoreDown = (BOOLEAN)(pMenu->TopDisplayedIndex + pMenu->Height < pMenu->ItemCount);
|
|
|
|
//
|
|
// We want to force the frame to be drawn if there is a change in whether
|
|
// there are more selections above or below us.
|
|
//
|
|
MoreStatusChanged = (BOOLEAN)( IndicateMore
|
|
&& ( (pMenu->MoreUp != MoreUp)
|
|
|| (pMenu->MoreDown != MoreDown)
|
|
)
|
|
);
|
|
|
|
if(DrawFrame || MoreStatusChanged) {
|
|
|
|
ASSERT(pMenu->LeftX);
|
|
ASSERT(pMenu->TopY);
|
|
|
|
SpDrawFrame(
|
|
pMenu->LeftX-1,
|
|
pMenu->Width+2,
|
|
pMenu->TopY-1,
|
|
pMenu->Height+2,
|
|
DEFAULT_ATTRIBUTE,
|
|
FALSE
|
|
);
|
|
}
|
|
|
|
//
|
|
// Draw each item that is currently on-screen.
|
|
//
|
|
ASSERT(pMenu->TopDisplayedIndex < pMenu->ItemCount);
|
|
for(item = pMenu->TopDisplayedIndex;
|
|
item < min(pMenu->TopDisplayedIndex+pMenu->Height,pMenu->ItemCount);
|
|
item++)
|
|
{
|
|
SpvidDisplayString(
|
|
pMenu->Items[item].Text,
|
|
(UCHAR)((item == SelectedIndex) ? ATT_BG_WHITE | ATT_FG_BLUE : DEFAULT_ATTRIBUTE),
|
|
pMenu->Items[item].LeftX,
|
|
pMenu->TopY + item - pMenu->TopDisplayedIndex
|
|
);
|
|
}
|
|
|
|
|
|
//
|
|
// If there are more selections above or below us,
|
|
// indicate so by placing a small bit of text on the frame.
|
|
// Note that the arrow chars can sometimes be DBCS.
|
|
//
|
|
if(MoreStatusChanged) {
|
|
|
|
if(MoreUp) {
|
|
SpvidDisplayString(
|
|
MoreUpText,
|
|
DEFAULT_ATTRIBUTE,
|
|
pMenu->LeftX + pMenu->Width - SplangGetColumnCount(MoreUpText) - 1,
|
|
pMenu->TopY - 1
|
|
);
|
|
}
|
|
|
|
if(MoreDown) {
|
|
SpvidDisplayString(
|
|
MoreDownText,
|
|
DEFAULT_ATTRIBUTE,
|
|
pMenu->LeftX + pMenu->Width - SplangGetColumnCount(MoreDownText) - 1,
|
|
pMenu->TopY + pMenu->Height
|
|
);
|
|
}
|
|
|
|
pMenu->MoreUp = MoreUp;
|
|
pMenu->MoreDown = MoreDown;
|
|
}
|
|
}
|