mirror of
https://github.com/reactos/reactos.git
synced 2024-06-27 16:31:30 +00:00
c25a0e1919
- UiMessageBox(): Enlarge the default buffer used to printf msgbox strings. - TuiUpdateDateTime(): When displaying the time, don't pad too much with spaces on the left. - TuiDrawShadow(): * Pre-calculate whether we need to show the right/bottom shadows, and whether the right-shadow has a double width. * Cap the right and bottom upper-bound coordinates before looping, in order to avoid buffer overflows if the given coordinates go beyond the screen. - TuiDrawMsgBoxCommon(): Improve how the message box centering calculations are done: * When the "full-UI" is used (and center-menu is used), try to center the message box in the space between the header and the status bar. * Ensure the top-left box corner is inside the screen, in case the calculated message box borders go off-screen. - TuiCalcMenuBoxSize(): * Uniformize the way the menu box coordinates are calculated. * Reduce the space between menu box margins and the longest item. * Ensure the top-left menu corner is inside the screen, in case the calculated menu box borders go off-screen.
568 lines
17 KiB
C
568 lines
17 KiB
C
/*
|
|
* COPYRIGHT: See COPYING in the top level directory
|
|
* PROJECT: FreeLoader
|
|
* FILE: boot/freeldr/freeldr/ui/tuimenu.c
|
|
* PURPOSE: Text UI Menu Functions
|
|
* PROGRAMMERS: Alex Ionescu (alex@relsoft.net)
|
|
* Brian Palmer (brianp@sginet.com)
|
|
*/
|
|
|
|
/* INCLUDES ******************************************************************/
|
|
|
|
#include <freeldr.h>
|
|
|
|
/* FUNCTIONS *****************************************************************/
|
|
|
|
static VOID
|
|
TuiCalcMenuBoxSize(
|
|
_In_ PUI_MENU_INFO MenuInfo);
|
|
|
|
static ULONG
|
|
TuiProcessMenuKeyboardEvent(
|
|
_In_ PUI_MENU_INFO MenuInfo,
|
|
_In_ UiMenuKeyPressFilterCallback KeyPressFilter);
|
|
|
|
static VOID
|
|
TuiDrawMenuTimeout(
|
|
_In_ PUI_MENU_INFO MenuInfo);
|
|
|
|
BOOLEAN
|
|
TuiDisplayMenu(
|
|
IN PCSTR MenuHeader,
|
|
IN PCSTR MenuFooter OPTIONAL,
|
|
IN BOOLEAN ShowBootOptions,
|
|
IN PCSTR MenuItemList[],
|
|
IN ULONG MenuItemCount,
|
|
IN ULONG DefaultMenuItem,
|
|
IN LONG MenuTimeOut,
|
|
OUT PULONG SelectedMenuItem,
|
|
IN BOOLEAN CanEscape,
|
|
IN UiMenuKeyPressFilterCallback KeyPressFilter OPTIONAL,
|
|
IN PVOID Context OPTIONAL)
|
|
{
|
|
UI_MENU_INFO MenuInformation;
|
|
ULONG LastClockSecond;
|
|
ULONG CurrentClockSecond;
|
|
ULONG KeyPress;
|
|
|
|
/*
|
|
* Before taking any default action if there is no timeout,
|
|
* check whether the supplied key filter callback function
|
|
* may handle a specific user keypress. If it does, the
|
|
* timeout is cancelled.
|
|
*/
|
|
if (!MenuTimeOut && KeyPressFilter && MachConsKbHit())
|
|
{
|
|
/* Get the key (get the extended key if needed) */
|
|
KeyPress = MachConsGetCh();
|
|
if (KeyPress == KEY_EXTENDED)
|
|
KeyPress = MachConsGetCh();
|
|
|
|
/*
|
|
* Call the supplied key filter callback function to see
|
|
* if it is going to handle this keypress.
|
|
*/
|
|
if (KeyPressFilter(KeyPress, DefaultMenuItem, Context))
|
|
{
|
|
/* It processed the key character, cancel the timeout */
|
|
MenuTimeOut = -1;
|
|
}
|
|
}
|
|
|
|
/* Check if there is no timeout */
|
|
if (!MenuTimeOut)
|
|
{
|
|
/* Return the default selected item */
|
|
if (SelectedMenuItem) *SelectedMenuItem = DefaultMenuItem;
|
|
return TRUE;
|
|
}
|
|
|
|
/* Setup the MENU_INFO structure */
|
|
MenuInformation.MenuHeader = MenuHeader;
|
|
MenuInformation.MenuFooter = MenuFooter;
|
|
MenuInformation.ShowBootOptions = ShowBootOptions;
|
|
MenuInformation.MenuItemList = MenuItemList;
|
|
MenuInformation.MenuItemCount = MenuItemCount;
|
|
MenuInformation.MenuTimeRemaining = MenuTimeOut;
|
|
MenuInformation.SelectedMenuItem = DefaultMenuItem;
|
|
MenuInformation.Context = Context;
|
|
|
|
/* Calculate the size of the menu box */
|
|
TuiCalcMenuBoxSize(&MenuInformation);
|
|
|
|
/* Draw the menu */
|
|
UiVtbl.DrawMenu(&MenuInformation);
|
|
|
|
/* Get the current second of time */
|
|
LastClockSecond = ArcGetTime()->Second;
|
|
|
|
/* Process keys */
|
|
while (TRUE)
|
|
{
|
|
/* Process key presses */
|
|
KeyPress = TuiProcessMenuKeyboardEvent(&MenuInformation, KeyPressFilter);
|
|
|
|
/* Check for ENTER or ESC */
|
|
if (KeyPress == KEY_ENTER) break;
|
|
if (CanEscape && KeyPress == KEY_ESC) return FALSE;
|
|
|
|
/* Get the updated time, and check if more than a second has elapsed */
|
|
CurrentClockSecond = ArcGetTime()->Second;
|
|
if (CurrentClockSecond != LastClockSecond)
|
|
{
|
|
/* Update the time information */
|
|
LastClockSecond = CurrentClockSecond;
|
|
|
|
// FIXME: Theme-specific
|
|
/* Update the date & time */
|
|
TuiUpdateDateTime();
|
|
|
|
/* If there is a countdown, update it */
|
|
if (MenuInformation.MenuTimeRemaining > 0)
|
|
{
|
|
MenuInformation.MenuTimeRemaining--;
|
|
TuiDrawMenuTimeout(&MenuInformation);
|
|
}
|
|
else if (MenuInformation.MenuTimeRemaining == 0)
|
|
{
|
|
/* A timeout occurred, exit this loop and return selection */
|
|
VideoCopyOffScreenBufferToVRAM();
|
|
break;
|
|
}
|
|
VideoCopyOffScreenBufferToVRAM();
|
|
}
|
|
|
|
MachHwIdle();
|
|
}
|
|
|
|
/* Return the selected item */
|
|
if (SelectedMenuItem) *SelectedMenuItem = MenuInformation.SelectedMenuItem;
|
|
return TRUE;
|
|
}
|
|
|
|
static VOID
|
|
TuiCalcMenuBoxSize(
|
|
_In_ PUI_MENU_INFO MenuInfo)
|
|
{
|
|
ULONG i;
|
|
ULONG Width = 0;
|
|
ULONG Height;
|
|
ULONG Length;
|
|
|
|
/* Height is the menu item count plus 2 (top border & bottom border) */
|
|
Height = MenuInfo->MenuItemCount + 2;
|
|
|
|
/* Loop every item */
|
|
for (i = 0; i < MenuInfo->MenuItemCount; ++i)
|
|
{
|
|
/* Get the string length and make it become the new width if necessary */
|
|
if (MenuInfo->MenuItemList[i])
|
|
{
|
|
Length = (ULONG)strlen(MenuInfo->MenuItemList[i]);
|
|
Width = max(Width, Length);
|
|
}
|
|
}
|
|
|
|
/* Allow room for left & right borders, plus 4 spaces on each side */
|
|
Width += 10;
|
|
|
|
/* Check if we're drawing a centered menu */
|
|
if (UiCenterMenu)
|
|
{
|
|
/* Calculate the centered menu box area, also ensuring that the top-left
|
|
* corner is always visible if the borders are partly off-screen */
|
|
MenuInfo->Left = (UiScreenWidth - min(Width, UiScreenWidth)) / 2;
|
|
if (Height <= UiScreenHeight - TUI_TITLE_BOX_CHAR_HEIGHT - 1)
|
|
{
|
|
/* Exclude the header and the status bar */
|
|
// MenuInfo->Top = (UiScreenHeight - TUI_TITLE_BOX_CHAR_HEIGHT - 1 - Height) / 2
|
|
// + TUI_TITLE_BOX_CHAR_HEIGHT;
|
|
MenuInfo->Top = (UiScreenHeight + TUI_TITLE_BOX_CHAR_HEIGHT - 1 - Height) / 2;
|
|
}
|
|
else
|
|
{
|
|
MenuInfo->Top = (UiScreenHeight - min(Height, UiScreenHeight)) / 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Put the menu in the default left-corner position */
|
|
MenuInfo->Left = -1;
|
|
MenuInfo->Top = 4;
|
|
}
|
|
|
|
/* The other margins are the same */
|
|
MenuInfo->Right = MenuInfo->Left + Width - 1;
|
|
MenuInfo->Bottom = MenuInfo->Top + Height - 1;
|
|
}
|
|
|
|
VOID
|
|
TuiDrawMenu(
|
|
_In_ PUI_MENU_INFO MenuInfo)
|
|
{
|
|
ULONG i;
|
|
|
|
// FIXME: Theme-specific
|
|
/* Draw the backdrop */
|
|
UiDrawBackdrop();
|
|
|
|
/* Draw the menu box */
|
|
TuiDrawMenuBox(MenuInfo);
|
|
|
|
/* Draw each line of the menu */
|
|
for (i = 0; i < MenuInfo->MenuItemCount; ++i)
|
|
{
|
|
TuiDrawMenuItem(MenuInfo, i);
|
|
}
|
|
|
|
// FIXME: Theme-specific
|
|
/* Update the status bar */
|
|
UiVtbl.DrawStatusText("Use \x18 and \x19 to select, then press ENTER.");
|
|
|
|
/* Display the boot options if needed */
|
|
if (MenuInfo->ShowBootOptions)
|
|
{
|
|
DisplayBootTimeOptions();
|
|
}
|
|
|
|
VideoCopyOffScreenBufferToVRAM();
|
|
}
|
|
|
|
static VOID
|
|
TuiDrawMenuTimeout(
|
|
_In_ PUI_MENU_INFO MenuInfo)
|
|
{
|
|
ULONG Length;
|
|
CHAR MenuLineText[80];
|
|
|
|
/* If there is a timeout, draw the time remaining */
|
|
if (MenuInfo->MenuTimeRemaining >= 0)
|
|
{
|
|
/* Find whether the time text string is escaped
|
|
* with %d for specific countdown insertion. */
|
|
PCHAR ptr = UiTimeText;
|
|
while ((ptr = strchr(ptr, '%')) && (ptr[1] != 'd'))
|
|
{
|
|
/* Ignore any following character (including a following
|
|
* '%' that would be escaped), thus skip two characters.
|
|
* If this is the last character, ignore it and stop. */
|
|
if (*++ptr)
|
|
++ptr;
|
|
}
|
|
ASSERT(!ptr || (ptr[0] == '%' && ptr[1] == 'd'));
|
|
|
|
if (ptr)
|
|
{
|
|
/* Copy the time text string up to the '%d' insertion point and
|
|
* skip it, add the remaining time and the rest of the string. */
|
|
RtlStringCbPrintfA(MenuLineText, sizeof(MenuLineText),
|
|
"%.*s%d%s",
|
|
ptr - UiTimeText, UiTimeText,
|
|
MenuInfo->MenuTimeRemaining,
|
|
ptr + 2);
|
|
}
|
|
else
|
|
{
|
|
/* Copy the time text string, append a separating blank,
|
|
* and add the remaining time. */
|
|
RtlStringCbPrintfA(MenuLineText, sizeof(MenuLineText),
|
|
"%s %d",
|
|
UiTimeText,
|
|
MenuInfo->MenuTimeRemaining);
|
|
}
|
|
|
|
Length = (ULONG)strlen(MenuLineText);
|
|
}
|
|
else
|
|
{
|
|
/* Erase the timeout with blanks */
|
|
Length = 0;
|
|
}
|
|
|
|
/**
|
|
* How to pad/fill:
|
|
*
|
|
* Center Box What to do:
|
|
* 0 0 or 1 Pad on the right with blanks.
|
|
* 1 0 Pad on the left with blanks.
|
|
* 1 1 Pad on the left with blanks + box bottom border.
|
|
**/
|
|
|
|
if (UiCenterMenu)
|
|
{
|
|
/* In boxed menu mode, pad on the left with blanks and box border,
|
|
* otherwise, pad over all the box length until its right edge. */
|
|
TuiFillArea(0,
|
|
MenuInfo->Bottom,
|
|
UiMenuBox
|
|
? MenuInfo->Left - 1 /* Left side of the box bottom */
|
|
: MenuInfo->Right, /* Left side + all box length */
|
|
MenuInfo->Bottom,
|
|
UiBackdropFillStyle,
|
|
ATTR(UiBackdropFgColor, UiBackdropBgColor));
|
|
|
|
if (UiMenuBox)
|
|
{
|
|
/* Fill with box bottom border */
|
|
TuiDrawBoxBottomLine(MenuInfo->Left,
|
|
MenuInfo->Bottom,
|
|
MenuInfo->Right,
|
|
D_VERT,
|
|
D_HORZ,
|
|
ATTR(UiMenuFgColor, UiMenuBgColor));
|
|
|
|
/* In centered boxed menu mode, the timeout string
|
|
* does not go past the right border, in principle... */
|
|
}
|
|
|
|
if (Length > 0)
|
|
{
|
|
/* Display the timeout at the bottom-right part of the menu */
|
|
UiDrawText(MenuInfo->Right - Length - 1,
|
|
MenuInfo->Bottom,
|
|
MenuLineText,
|
|
ATTR(UiMenuFgColor, UiMenuBgColor));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Length > 0)
|
|
{
|
|
/* Display the timeout under the menu directly */
|
|
UiDrawText(0,
|
|
MenuInfo->Bottom + 4,
|
|
MenuLineText,
|
|
ATTR(UiMenuFgColor, UiMenuBgColor));
|
|
}
|
|
|
|
/* Pad on the right with blanks, to erase
|
|
* characters when the string length decreases. */
|
|
TuiFillArea(Length,
|
|
MenuInfo->Bottom + 4,
|
|
Length ? (Length + 1) : (UiScreenWidth - 1),
|
|
MenuInfo->Bottom + 4,
|
|
UiBackdropFillStyle,
|
|
ATTR(UiBackdropFgColor, UiBackdropBgColor)
|
|
);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
TuiDrawMenuBox(
|
|
_In_ PUI_MENU_INFO MenuInfo)
|
|
{
|
|
// FIXME: Theme-specific
|
|
/* Draw the menu box if requested */
|
|
if (UiMenuBox)
|
|
{
|
|
UiDrawBox(MenuInfo->Left,
|
|
MenuInfo->Top,
|
|
MenuInfo->Right,
|
|
MenuInfo->Bottom,
|
|
D_VERT,
|
|
D_HORZ,
|
|
FALSE, // Filled
|
|
TRUE, // Shadow
|
|
ATTR(UiMenuFgColor, UiMenuBgColor));
|
|
}
|
|
|
|
/* Update the date & time */
|
|
TuiUpdateDateTime();
|
|
TuiDrawMenuTimeout(MenuInfo);
|
|
}
|
|
|
|
VOID
|
|
TuiDrawMenuItem(
|
|
_In_ PUI_MENU_INFO MenuInfo,
|
|
_In_ ULONG MenuItemNumber)
|
|
{
|
|
ULONG SpaceLeft;
|
|
ULONG SpaceRight;
|
|
UCHAR Attribute;
|
|
CHAR MenuLineText[80];
|
|
|
|
/* If this is a separator */
|
|
if (MenuInfo->MenuItemList[MenuItemNumber] == NULL)
|
|
{
|
|
// FIXME: Theme-specific
|
|
/* Draw its left box corner */
|
|
if (UiMenuBox)
|
|
{
|
|
UiDrawText(MenuInfo->Left,
|
|
MenuInfo->Top + 1 + MenuItemNumber,
|
|
"\xC7",
|
|
ATTR(UiMenuFgColor, UiMenuBgColor));
|
|
}
|
|
|
|
/* Make it a separator line and use menu colors */
|
|
RtlZeroMemory(MenuLineText, sizeof(MenuLineText));
|
|
RtlFillMemory(MenuLineText,
|
|
min(sizeof(MenuLineText), (MenuInfo->Right - MenuInfo->Left - 1)),
|
|
0xC4);
|
|
|
|
/* Draw the item */
|
|
UiDrawText(MenuInfo->Left + 1,
|
|
MenuInfo->Top + 1 + MenuItemNumber,
|
|
MenuLineText,
|
|
ATTR(UiMenuFgColor, UiMenuBgColor));
|
|
|
|
// FIXME: Theme-specific
|
|
/* Draw its right box corner */
|
|
if (UiMenuBox)
|
|
{
|
|
UiDrawText(MenuInfo->Right,
|
|
MenuInfo->Top + 1 + MenuItemNumber,
|
|
"\xB6",
|
|
ATTR(UiMenuFgColor, UiMenuBgColor));
|
|
}
|
|
|
|
/* We are done */
|
|
return;
|
|
}
|
|
|
|
/* This is not a separator */
|
|
ASSERT(MenuInfo->MenuItemList[MenuItemNumber]);
|
|
|
|
/* Check if using centered menu */
|
|
if (UiCenterMenu)
|
|
{
|
|
/*
|
|
* We will want the string centered so calculate
|
|
* how many spaces will be to the left and right.
|
|
*/
|
|
ULONG SpaceTotal =
|
|
(MenuInfo->Right - MenuInfo->Left - 2) -
|
|
(ULONG)strlen(MenuInfo->MenuItemList[MenuItemNumber]);
|
|
SpaceLeft = (SpaceTotal / 2) + 1;
|
|
SpaceRight = (SpaceTotal - SpaceLeft) + 1;
|
|
}
|
|
else
|
|
{
|
|
/* Simply left-align it */
|
|
SpaceLeft = 4;
|
|
SpaceRight = 0;
|
|
}
|
|
|
|
/* Format the item text string */
|
|
RtlStringCbPrintfA(MenuLineText, sizeof(MenuLineText),
|
|
"%*s%s%*s",
|
|
SpaceLeft, "", // Left padding
|
|
MenuInfo->MenuItemList[MenuItemNumber],
|
|
SpaceRight, ""); // Right padding
|
|
|
|
if (MenuItemNumber == MenuInfo->SelectedMenuItem)
|
|
{
|
|
/* If this is the selected item, use the selected colors */
|
|
Attribute = ATTR(UiSelectedTextColor, UiSelectedTextBgColor);
|
|
}
|
|
else
|
|
{
|
|
/* Normal item colors */
|
|
Attribute = ATTR(UiTextColor, UiMenuBgColor);
|
|
}
|
|
|
|
/* Draw the item */
|
|
UiDrawText(MenuInfo->Left + 1,
|
|
MenuInfo->Top + 1 + MenuItemNumber,
|
|
MenuLineText,
|
|
Attribute);
|
|
}
|
|
|
|
static ULONG
|
|
TuiProcessMenuKeyboardEvent(
|
|
_In_ PUI_MENU_INFO MenuInfo,
|
|
_In_ UiMenuKeyPressFilterCallback KeyPressFilter)
|
|
{
|
|
ULONG KeyEvent = 0;
|
|
ULONG Selected, Count;
|
|
|
|
/* Check for a keypress */
|
|
if (!MachConsKbHit())
|
|
return 0; // None, bail out
|
|
|
|
/* Check if the timeout is not already complete */
|
|
if (MenuInfo->MenuTimeRemaining != -1)
|
|
{
|
|
/* Cancel it and remove it */
|
|
MenuInfo->MenuTimeRemaining = -1;
|
|
TuiDrawMenuTimeout(MenuInfo);
|
|
}
|
|
|
|
/* Get the key (get the extended key if needed) */
|
|
KeyEvent = MachConsGetCh();
|
|
if (KeyEvent == KEY_EXTENDED)
|
|
KeyEvent = MachConsGetCh();
|
|
|
|
/*
|
|
* Call the supplied key filter callback function to see
|
|
* if it is going to handle this keypress.
|
|
*/
|
|
if (KeyPressFilter &&
|
|
KeyPressFilter(KeyEvent, MenuInfo->SelectedMenuItem, MenuInfo->Context))
|
|
{
|
|
/* It processed the key character, so redraw and exit */
|
|
UiVtbl.DrawMenu(MenuInfo);
|
|
return 0;
|
|
}
|
|
|
|
/* Process the key */
|
|
if ((KeyEvent == KEY_UP ) || (KeyEvent == KEY_DOWN) ||
|
|
(KeyEvent == KEY_HOME) || (KeyEvent == KEY_END ))
|
|
{
|
|
/* Get the current selected item and count */
|
|
Selected = MenuInfo->SelectedMenuItem;
|
|
Count = MenuInfo->MenuItemCount - 1;
|
|
|
|
/* Check the key and change the selected menu item */
|
|
if ((KeyEvent == KEY_UP) && (Selected > 0))
|
|
{
|
|
/* Deselect previous item and go up */
|
|
MenuInfo->SelectedMenuItem--;
|
|
TuiDrawMenuItem(MenuInfo, Selected);
|
|
Selected--;
|
|
|
|
/* Skip past any separators */
|
|
if ((Selected > 0) &&
|
|
(MenuInfo->MenuItemList[Selected] == NULL))
|
|
{
|
|
MenuInfo->SelectedMenuItem--;
|
|
}
|
|
}
|
|
else if ( ((KeyEvent == KEY_UP) && (Selected == 0)) ||
|
|
(KeyEvent == KEY_END) )
|
|
{
|
|
/* Go to the end */
|
|
MenuInfo->SelectedMenuItem = Count;
|
|
TuiDrawMenuItem(MenuInfo, Selected);
|
|
}
|
|
else if ((KeyEvent == KEY_DOWN) && (Selected < Count))
|
|
{
|
|
/* Deselect previous item and go down */
|
|
MenuInfo->SelectedMenuItem++;
|
|
TuiDrawMenuItem(MenuInfo, Selected);
|
|
Selected++;
|
|
|
|
/* Skip past any separators */
|
|
if ((Selected < Count) &&
|
|
(MenuInfo->MenuItemList[Selected] == NULL))
|
|
{
|
|
MenuInfo->SelectedMenuItem++;
|
|
}
|
|
}
|
|
else if ( ((KeyEvent == KEY_DOWN) && (Selected == Count)) ||
|
|
(KeyEvent == KEY_HOME) )
|
|
{
|
|
/* Go to the beginning */
|
|
MenuInfo->SelectedMenuItem = 0;
|
|
TuiDrawMenuItem(MenuInfo, Selected);
|
|
}
|
|
|
|
/* Select new item and update video buffer */
|
|
TuiDrawMenuItem(MenuInfo, MenuInfo->SelectedMenuItem);
|
|
VideoCopyOffScreenBufferToVRAM();
|
|
}
|
|
|
|
/* Return the pressed key */
|
|
return KeyEvent;
|
|
}
|