| /** @file | |
| Implementation for handling user input from the User Interfaces. | |
| Copyright (c) 2004 - 2009, Intel Corporation | |
| All rights reserved. This program and the accompanying materials | |
| are licensed and made available under the terms and conditions of the BSD License | |
| which accompanies this distribution. The full text of the license may be found at | |
| http://opensource.org/licenses/bsd-license.php | |
| THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
| **/ | |
| #include "Setup.h" | |
| /** | |
| Get string or password input from user. | |
| @param MenuOption Pointer to the current input menu. | |
| @param Prompt The prompt string shown on popup window. | |
| @param StringPtr Destination for use input string. | |
| @retval EFI_SUCCESS If string input is read successfully | |
| @retval EFI_DEVICE_ERROR If operation fails | |
| **/ | |
| EFI_STATUS | |
| ReadString ( | |
| IN UI_MENU_OPTION *MenuOption, | |
| IN CHAR16 *Prompt, | |
| OUT CHAR16 *StringPtr | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_INPUT_KEY Key; | |
| CHAR16 NullCharacter; | |
| UINTN ScreenSize; | |
| CHAR16 Space[2]; | |
| CHAR16 KeyPad[2]; | |
| CHAR16 *TempString; | |
| CHAR16 *BufferedString; | |
| UINTN Index; | |
| UINTN Count; | |
| UINTN Start; | |
| UINTN Top; | |
| UINTN DimensionsWidth; | |
| UINTN DimensionsHeight; | |
| BOOLEAN CursorVisible; | |
| UINTN Minimum; | |
| UINTN Maximum; | |
| FORM_BROWSER_STATEMENT *Question; | |
| BOOLEAN IsPassword; | |
| DimensionsWidth = gScreenDimensions.RightColumn - gScreenDimensions.LeftColumn; | |
| DimensionsHeight = gScreenDimensions.BottomRow - gScreenDimensions.TopRow; | |
| NullCharacter = CHAR_NULL; | |
| ScreenSize = GetStringWidth (Prompt) / sizeof (CHAR16); | |
| Space[0] = L' '; | |
| Space[1] = CHAR_NULL; | |
| Question = MenuOption->ThisTag; | |
| Minimum = (UINTN) Question->Minimum; | |
| Maximum = (UINTN) Question->Maximum; | |
| if (Question->Operand == EFI_IFR_PASSWORD_OP) { | |
| IsPassword = TRUE; | |
| } else { | |
| IsPassword = FALSE; | |
| } | |
| TempString = AllocateZeroPool ((Maximum + 1)* sizeof (CHAR16)); | |
| ASSERT (TempString); | |
| if (ScreenSize < (Maximum + 1)) { | |
| ScreenSize = Maximum + 1; | |
| } | |
| if ((ScreenSize + 2) > DimensionsWidth) { | |
| ScreenSize = DimensionsWidth - 2; | |
| } | |
| BufferedString = AllocateZeroPool (ScreenSize * 2); | |
| ASSERT (BufferedString); | |
| Start = (DimensionsWidth - ScreenSize - 2) / 2 + gScreenDimensions.LeftColumn + 1; | |
| Top = ((DimensionsHeight - 6) / 2) + gScreenDimensions.TopRow - 1; | |
| // | |
| // Display prompt for string | |
| // | |
| CreateMultiStringPopUp (ScreenSize, 4, &NullCharacter, Prompt, Space, &NullCharacter); | |
| gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_BLACK, EFI_LIGHTGRAY)); | |
| CursorVisible = gST->ConOut->Mode->CursorVisible; | |
| gST->ConOut->EnableCursor (gST->ConOut, TRUE); | |
| do { | |
| Status = WaitForKeyStroke (&Key); | |
| ASSERT_EFI_ERROR (Status); | |
| gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_BLACK, EFI_LIGHTGRAY)); | |
| switch (Key.UnicodeChar) { | |
| case CHAR_NULL: | |
| switch (Key.ScanCode) { | |
| case SCAN_LEFT: | |
| break; | |
| case SCAN_RIGHT: | |
| break; | |
| case SCAN_ESC: | |
| FreePool (TempString); | |
| FreePool (BufferedString); | |
| gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK)); | |
| gST->ConOut->EnableCursor (gST->ConOut, CursorVisible); | |
| return EFI_DEVICE_ERROR; | |
| default: | |
| break; | |
| } | |
| break; | |
| case CHAR_CARRIAGE_RETURN: | |
| if (GetStringWidth (StringPtr) >= ((Minimum + 1) * sizeof (CHAR16))) { | |
| FreePool (TempString); | |
| FreePool (BufferedString); | |
| gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK)); | |
| gST->ConOut->EnableCursor (gST->ConOut, CursorVisible); | |
| return EFI_SUCCESS; | |
| } else { | |
| // | |
| // Simply create a popup to tell the user that they had typed in too few characters. | |
| // To save code space, we can then treat this as an error and return back to the menu. | |
| // | |
| do { | |
| CreateDialog (4, TRUE, 0, NULL, &Key, &NullCharacter, gMiniString, gPressEnter, &NullCharacter); | |
| } while (Key.UnicodeChar != CHAR_CARRIAGE_RETURN); | |
| FreePool (TempString); | |
| FreePool (BufferedString); | |
| gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK)); | |
| gST->ConOut->EnableCursor (gST->ConOut, CursorVisible); | |
| return EFI_DEVICE_ERROR; | |
| } | |
| break; | |
| case CHAR_BACKSPACE: | |
| if (StringPtr[0] != CHAR_NULL) { | |
| for (Index = 0; StringPtr[Index] != CHAR_NULL; Index++) { | |
| TempString[Index] = StringPtr[Index]; | |
| } | |
| // | |
| // Effectively truncate string by 1 character | |
| // | |
| TempString[Index - 1] = CHAR_NULL; | |
| StrCpy (StringPtr, TempString); | |
| } | |
| default: | |
| // | |
| // If it is the beginning of the string, don't worry about checking maximum limits | |
| // | |
| if ((StringPtr[0] == CHAR_NULL) && (Key.UnicodeChar != CHAR_BACKSPACE)) { | |
| StrnCpy (StringPtr, &Key.UnicodeChar, 1); | |
| StrnCpy (TempString, &Key.UnicodeChar, 1); | |
| } else if ((GetStringWidth (StringPtr) < ((Maximum + 1) * sizeof (CHAR16))) && (Key.UnicodeChar != CHAR_BACKSPACE)) { | |
| KeyPad[0] = Key.UnicodeChar; | |
| KeyPad[1] = CHAR_NULL; | |
| StrCat (StringPtr, KeyPad); | |
| StrCat (TempString, KeyPad); | |
| } | |
| // | |
| // If the width of the input string is now larger than the screen, we nee to | |
| // adjust the index to start printing portions of the string | |
| // | |
| SetUnicodeMem (BufferedString, ScreenSize - 1, L' '); | |
| PrintStringAt (Start + 1, Top + 3, BufferedString); | |
| if ((GetStringWidth (StringPtr) / 2) > (DimensionsWidth - 2)) { | |
| Index = (GetStringWidth (StringPtr) / 2) - DimensionsWidth + 2; | |
| } else { | |
| Index = 0; | |
| } | |
| if (IsPassword) { | |
| gST->ConOut->SetCursorPosition (gST->ConOut, Start + 1, Top + 3); | |
| } | |
| for (Count = 0; Index + 1 < GetStringWidth (StringPtr) / 2; Index++, Count++) { | |
| BufferedString[Count] = StringPtr[Index]; | |
| if (IsPassword) { | |
| PrintChar (L'*'); | |
| } | |
| } | |
| if (!IsPassword) { | |
| PrintStringAt (Start + 1, Top + 3, BufferedString); | |
| } | |
| break; | |
| } | |
| gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK)); | |
| gST->ConOut->SetCursorPosition (gST->ConOut, Start + GetStringWidth (StringPtr) / 2, Top + 3); | |
| } while (TRUE); | |
| } | |
| /** | |
| This routine reads a numeric value from the user input. | |
| @param Selection Pointer to current selection. | |
| @param MenuOption Pointer to the current input menu. | |
| @retval EFI_SUCCESS If numerical input is read successfully | |
| @retval EFI_DEVICE_ERROR If operation fails | |
| **/ | |
| EFI_STATUS | |
| GetNumericInput ( | |
| IN UI_MENU_SELECTION *Selection, | |
| IN UI_MENU_OPTION *MenuOption | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINTN Column; | |
| UINTN Row; | |
| CHAR16 InputText[MAX_NUMERIC_INPUT_WIDTH]; | |
| CHAR16 FormattedNumber[MAX_NUMERIC_INPUT_WIDTH - 1]; | |
| UINT64 PreviousNumber[MAX_NUMERIC_INPUT_WIDTH - 3]; | |
| UINTN Count; | |
| UINTN Loop; | |
| BOOLEAN ManualInput; | |
| BOOLEAN HexInput; | |
| BOOLEAN DateOrTime; | |
| UINTN InputWidth; | |
| UINT64 EditValue; | |
| UINT64 Step; | |
| UINT64 Minimum; | |
| UINT64 Maximum; | |
| UINTN EraseLen; | |
| UINT8 Digital; | |
| EFI_INPUT_KEY Key; | |
| EFI_HII_VALUE *QuestionValue; | |
| FORM_BROWSER_FORM *Form; | |
| FORM_BROWSER_FORMSET *FormSet; | |
| FORM_BROWSER_STATEMENT *Question; | |
| Column = MenuOption->OptCol; | |
| Row = MenuOption->Row; | |
| PreviousNumber[0] = 0; | |
| Count = 0; | |
| InputWidth = 0; | |
| Digital = 0; | |
| FormSet = Selection->FormSet; | |
| Form = Selection->Form; | |
| Question = MenuOption->ThisTag; | |
| QuestionValue = &Question->HiiValue; | |
| Step = Question->Step; | |
| Minimum = Question->Minimum; | |
| Maximum = Question->Maximum; | |
| if ((Question->Operand == EFI_IFR_DATE_OP) || (Question->Operand == EFI_IFR_TIME_OP)) { | |
| DateOrTime = TRUE; | |
| } else { | |
| DateOrTime = FALSE; | |
| } | |
| // | |
| // Prepare Value to be edit | |
| // | |
| EraseLen = 0; | |
| EditValue = 0; | |
| if (Question->Operand == EFI_IFR_DATE_OP) { | |
| Step = 1; | |
| Minimum = 1; | |
| switch (MenuOption->Sequence) { | |
| case 0: | |
| Maximum = 12; | |
| EraseLen = 4; | |
| EditValue = QuestionValue->Value.date.Month; | |
| break; | |
| case 1: | |
| Maximum = 31; | |
| EraseLen = 3; | |
| EditValue = QuestionValue->Value.date.Day; | |
| break; | |
| case 2: | |
| Maximum = 0xffff; | |
| EraseLen = 5; | |
| EditValue = QuestionValue->Value.date.Year; | |
| break; | |
| default: | |
| break; | |
| } | |
| } else if (Question->Operand == EFI_IFR_TIME_OP) { | |
| Step = 1; | |
| Minimum = 0; | |
| switch (MenuOption->Sequence) { | |
| case 0: | |
| Maximum = 23; | |
| EraseLen = 4; | |
| EditValue = QuestionValue->Value.time.Hour; | |
| break; | |
| case 1: | |
| Maximum = 59; | |
| EraseLen = 3; | |
| EditValue = QuestionValue->Value.time.Minute; | |
| break; | |
| case 2: | |
| Maximum = 59; | |
| EraseLen = 3; | |
| EditValue = QuestionValue->Value.time.Second; | |
| break; | |
| default: | |
| break; | |
| } | |
| } else { | |
| // | |
| // Numeric | |
| // | |
| EraseLen = gOptionBlockWidth; | |
| EditValue = QuestionValue->Value.u64; | |
| if (Maximum == 0) { | |
| Maximum = (UINT64) -1; | |
| } | |
| } | |
| if (Step == 0) { | |
| ManualInput = TRUE; | |
| } else { | |
| ManualInput = FALSE; | |
| } | |
| if ((Question->Operand == EFI_IFR_NUMERIC_OP) && | |
| ((Question->Flags & EFI_IFR_DISPLAY) == EFI_IFR_DISPLAY_UINT_HEX)) { | |
| HexInput = TRUE; | |
| } else { | |
| HexInput = FALSE; | |
| } | |
| if (ManualInput) { | |
| if (HexInput) { | |
| InputWidth = Question->StorageWidth * 2; | |
| } else { | |
| switch (Question->StorageWidth) { | |
| case 1: | |
| InputWidth = 3; | |
| break; | |
| case 2: | |
| InputWidth = 5; | |
| break; | |
| case 4: | |
| InputWidth = 10; | |
| break; | |
| case 8: | |
| InputWidth = 20; | |
| break; | |
| default: | |
| InputWidth = 0; | |
| break; | |
| } | |
| } | |
| InputText[0] = LEFT_NUMERIC_DELIMITER; | |
| SetUnicodeMem (InputText + 1, InputWidth, L' '); | |
| ASSERT (InputWidth + 2 < MAX_NUMERIC_INPUT_WIDTH); | |
| InputText[InputWidth + 1] = RIGHT_NUMERIC_DELIMITER; | |
| InputText[InputWidth + 2] = L'\0'; | |
| PrintAt (Column, Row, InputText); | |
| Column++; | |
| } | |
| // | |
| // First time we enter this handler, we need to check to see if | |
| // we were passed an increment or decrement directive | |
| // | |
| do { | |
| Key.UnicodeChar = CHAR_NULL; | |
| if (gDirection != 0) { | |
| Key.ScanCode = gDirection; | |
| gDirection = 0; | |
| goto TheKey2; | |
| } | |
| Status = WaitForKeyStroke (&Key); | |
| TheKey2: | |
| switch (Key.UnicodeChar) { | |
| case '+': | |
| case '-': | |
| if (Key.UnicodeChar == '+') { | |
| Key.ScanCode = SCAN_RIGHT; | |
| } else { | |
| Key.ScanCode = SCAN_LEFT; | |
| } | |
| Key.UnicodeChar = CHAR_NULL; | |
| goto TheKey2; | |
| case CHAR_NULL: | |
| switch (Key.ScanCode) { | |
| case SCAN_LEFT: | |
| case SCAN_RIGHT: | |
| if (DateOrTime) { | |
| // | |
| // By setting this value, we will return back to the caller. | |
| // We need to do this since an auto-refresh will destroy the adjustment | |
| // based on what the real-time-clock is showing. So we always commit | |
| // upon changing the value. | |
| // | |
| gDirection = SCAN_DOWN; | |
| } | |
| if (!ManualInput) { | |
| if (Key.ScanCode == SCAN_LEFT) { | |
| if (EditValue > Step) { | |
| EditValue = EditValue - Step; | |
| } else { | |
| EditValue = Minimum; | |
| } | |
| } else if (Key.ScanCode == SCAN_RIGHT) { | |
| EditValue = EditValue + Step; | |
| if (EditValue > Maximum) { | |
| EditValue = Maximum; | |
| } | |
| } | |
| ZeroMem (FormattedNumber, 21 * sizeof (CHAR16)); | |
| if (Question->Operand == EFI_IFR_DATE_OP) { | |
| if (MenuOption->Sequence == 2) { | |
| // | |
| // Year | |
| // | |
| UnicodeSPrint (FormattedNumber, 21 * sizeof (CHAR16), L"%04d", (UINT16) EditValue); | |
| } else { | |
| // | |
| // Month/Day | |
| // | |
| UnicodeSPrint (FormattedNumber, 21 * sizeof (CHAR16), L"%02d", (UINT8) EditValue); | |
| } | |
| if (MenuOption->Sequence == 0) { | |
| FormattedNumber[EraseLen - 2] = DATE_SEPARATOR; | |
| } else if (MenuOption->Sequence == 1) { | |
| FormattedNumber[EraseLen - 1] = DATE_SEPARATOR; | |
| } | |
| } else if (Question->Operand == EFI_IFR_TIME_OP) { | |
| UnicodeSPrint (FormattedNumber, 21 * sizeof (CHAR16), L"%02d", (UINT8) EditValue); | |
| if (MenuOption->Sequence == 0) { | |
| FormattedNumber[EraseLen - 2] = TIME_SEPARATOR; | |
| } else if (MenuOption->Sequence == 1) { | |
| FormattedNumber[EraseLen - 1] = TIME_SEPARATOR; | |
| } | |
| } else { | |
| QuestionValue->Value.u64 = EditValue; | |
| PrintFormattedNumber (Question, FormattedNumber, 21 * sizeof (CHAR16)); | |
| } | |
| gST->ConOut->SetAttribute (gST->ConOut, FIELD_TEXT | FIELD_BACKGROUND); | |
| for (Loop = 0; Loop < EraseLen; Loop++) { | |
| PrintAt (MenuOption->OptCol + Loop, MenuOption->Row, L" "); | |
| } | |
| gST->ConOut->SetAttribute (gST->ConOut, FIELD_TEXT_HIGHLIGHT | FIELD_BACKGROUND_HIGHLIGHT); | |
| if (MenuOption->Sequence == 0) { | |
| PrintCharAt (MenuOption->OptCol, Row, LEFT_NUMERIC_DELIMITER); | |
| Column = MenuOption->OptCol + 1; | |
| } | |
| PrintStringAt (Column, Row, FormattedNumber); | |
| if (!DateOrTime || MenuOption->Sequence == 2) { | |
| PrintChar (RIGHT_NUMERIC_DELIMITER); | |
| } | |
| } | |
| goto EnterCarriageReturn; | |
| break; | |
| case SCAN_UP: | |
| case SCAN_DOWN: | |
| goto EnterCarriageReturn; | |
| case SCAN_ESC: | |
| return EFI_DEVICE_ERROR; | |
| default: | |
| break; | |
| } | |
| break; | |
| EnterCarriageReturn: | |
| case CHAR_CARRIAGE_RETURN: | |
| // | |
| // Store Edit value back to Question | |
| // | |
| if (Question->Operand == EFI_IFR_DATE_OP) { | |
| switch (MenuOption->Sequence) { | |
| case 0: | |
| QuestionValue->Value.date.Month = (UINT8) EditValue; | |
| break; | |
| case 1: | |
| QuestionValue->Value.date.Day = (UINT8) EditValue; | |
| break; | |
| case 2: | |
| QuestionValue->Value.date.Year = (UINT16) EditValue; | |
| break; | |
| default: | |
| break; | |
| } | |
| } else if (Question->Operand == EFI_IFR_TIME_OP) { | |
| switch (MenuOption->Sequence) { | |
| case 0: | |
| QuestionValue->Value.time.Hour = (UINT8) EditValue; | |
| break; | |
| case 1: | |
| QuestionValue->Value.time.Minute = (UINT8) EditValue; | |
| break; | |
| case 2: | |
| QuestionValue->Value.time.Second = (UINT8) EditValue; | |
| break; | |
| default: | |
| break; | |
| } | |
| } else { | |
| // | |
| // Numeric | |
| // | |
| QuestionValue->Value.u64 = EditValue; | |
| } | |
| // | |
| // Check to see if the Value is something reasonable against consistency limitations. | |
| // If not, let's kick the error specified. | |
| // | |
| Status = ValidateQuestion (FormSet, Form, Question, EFI_HII_EXPRESSION_INCONSISTENT_IF); | |
| if (EFI_ERROR (Status)) { | |
| // | |
| // Input value is not valid, restore Question Value | |
| // | |
| GetQuestionValue (FormSet, Form, Question, TRUE); | |
| } else { | |
| SetQuestionValue (FormSet, Form, Question, TRUE); | |
| if (!DateOrTime || (Question->Storage != NULL)) { | |
| // | |
| // NV flag is unnecessary for RTC type of Date/Time | |
| // | |
| UpdateStatusBar (NV_UPDATE_REQUIRED, Question->QuestionFlags, TRUE); | |
| } | |
| } | |
| return Status; | |
| break; | |
| case CHAR_BACKSPACE: | |
| if (ManualInput) { | |
| if (Count == 0) { | |
| break; | |
| } | |
| // | |
| // Remove a character | |
| // | |
| EditValue = PreviousNumber[Count - 1]; | |
| UpdateStatusBar (INPUT_ERROR, Question->QuestionFlags, FALSE); | |
| Count--; | |
| Column--; | |
| PrintAt (Column, Row, L" "); | |
| } | |
| break; | |
| default: | |
| if (ManualInput) { | |
| if (HexInput) { | |
| if ((Key.UnicodeChar >= L'0') && (Key.UnicodeChar <= L'9')) { | |
| Digital = (UINT8) (Key.UnicodeChar - L'0'); | |
| } else if ((Key.UnicodeChar >= L'A') && (Key.UnicodeChar <= L'F')) { | |
| Digital = (UINT8) (Key.UnicodeChar - L'A' + 0x0A); | |
| } else if ((Key.UnicodeChar >= L'a') && (Key.UnicodeChar <= L'f')) { | |
| Digital = (UINT8) (Key.UnicodeChar - L'a' + 0x0A); | |
| } else { | |
| UpdateStatusBar (INPUT_ERROR, Question->QuestionFlags, TRUE); | |
| break; | |
| } | |
| } else { | |
| if (Key.UnicodeChar > L'9' || Key.UnicodeChar < L'0') { | |
| UpdateStatusBar (INPUT_ERROR, Question->QuestionFlags, TRUE); | |
| break; | |
| } | |
| } | |
| // | |
| // If Count exceed input width, there is no way more is valid | |
| // | |
| if (Count >= InputWidth) { | |
| break; | |
| } | |
| // | |
| // Someone typed something valid! | |
| // | |
| if (Count != 0) { | |
| if (HexInput) { | |
| EditValue = LShiftU64 (EditValue, 4) + Digital; | |
| } else { | |
| EditValue = MultU64x32 (EditValue, 10) + (Key.UnicodeChar - L'0'); | |
| } | |
| } else { | |
| if (HexInput) { | |
| EditValue = Digital; | |
| } else { | |
| EditValue = Key.UnicodeChar - L'0'; | |
| } | |
| } | |
| if (EditValue > Maximum) { | |
| UpdateStatusBar (INPUT_ERROR, Question->QuestionFlags, TRUE); | |
| ASSERT (Count < sizeof (PreviousNumber) / sizeof (PreviousNumber[0])); | |
| EditValue = PreviousNumber[Count]; | |
| break; | |
| } else { | |
| UpdateStatusBar (INPUT_ERROR, Question->QuestionFlags, FALSE); | |
| } | |
| Count++; | |
| ASSERT (Count < (sizeof (PreviousNumber) / sizeof (PreviousNumber[0]))); | |
| PreviousNumber[Count] = EditValue; | |
| PrintCharAt (Column, Row, Key.UnicodeChar); | |
| Column++; | |
| } | |
| break; | |
| } | |
| } while (TRUE); | |
| } | |
| /** | |
| Get selection for OneOf and OrderedList (Left/Right will be ignored). | |
| @param Selection Pointer to current selection. | |
| @param MenuOption Pointer to the current input menu. | |
| @retval EFI_SUCCESS If Option input is processed successfully | |
| @retval EFI_DEVICE_ERROR If operation fails | |
| **/ | |
| EFI_STATUS | |
| GetSelectionInputPopUp ( | |
| IN UI_MENU_SELECTION *Selection, | |
| IN UI_MENU_OPTION *MenuOption | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_INPUT_KEY Key; | |
| UINTN Index; | |
| CHAR16 *StringPtr; | |
| CHAR16 *TempStringPtr; | |
| UINTN Index2; | |
| UINTN TopOptionIndex; | |
| UINTN HighlightOptionIndex; | |
| UINTN Start; | |
| UINTN End; | |
| UINTN Top; | |
| UINTN Bottom; | |
| UINTN PopUpMenuLines; | |
| UINTN MenuLinesInView; | |
| UINTN PopUpWidth; | |
| CHAR16 Character; | |
| INT32 SavedAttribute; | |
| BOOLEAN ShowDownArrow; | |
| BOOLEAN ShowUpArrow; | |
| UINTN DimensionsWidth; | |
| LIST_ENTRY *Link; | |
| BOOLEAN OrderedList; | |
| UINT8 *ValueArray; | |
| UINT8 ValueType; | |
| EFI_HII_VALUE HiiValue; | |
| EFI_HII_VALUE *HiiValueArray; | |
| UINTN OptionCount; | |
| QUESTION_OPTION *OneOfOption; | |
| QUESTION_OPTION *CurrentOption; | |
| FORM_BROWSER_STATEMENT *Question; | |
| DimensionsWidth = gScreenDimensions.RightColumn - gScreenDimensions.LeftColumn; | |
| ValueArray = NULL; | |
| ValueType = 0; | |
| CurrentOption = NULL; | |
| ShowDownArrow = FALSE; | |
| ShowUpArrow = FALSE; | |
| StringPtr = AllocateZeroPool ((gOptionBlockWidth + 1) * 2); | |
| ASSERT (StringPtr); | |
| Question = MenuOption->ThisTag; | |
| if (Question->Operand == EFI_IFR_ORDERED_LIST_OP) { | |
| ValueArray = Question->BufferValue; | |
| ValueType = Question->ValueType; | |
| OrderedList = TRUE; | |
| } else { | |
| OrderedList = FALSE; | |
| } | |
| // | |
| // Calculate Option count | |
| // | |
| if (OrderedList) { | |
| for (Index = 0; Index < Question->MaxContainers; Index++) { | |
| if (GetArrayData (ValueArray, ValueType, Index) == 0) { | |
| break; | |
| } | |
| } | |
| OptionCount = Index; | |
| } else { | |
| OptionCount = 0; | |
| Link = GetFirstNode (&Question->OptionListHead); | |
| while (!IsNull (&Question->OptionListHead, Link)) { | |
| OneOfOption = QUESTION_OPTION_FROM_LINK (Link); | |
| OptionCount++; | |
| Link = GetNextNode (&Question->OptionListHead, Link); | |
| } | |
| } | |
| // | |
| // Prepare HiiValue array | |
| // | |
| HiiValueArray = AllocateZeroPool (OptionCount * sizeof (EFI_HII_VALUE)); | |
| ASSERT (HiiValueArray != NULL); | |
| Link = GetFirstNode (&Question->OptionListHead); | |
| for (Index = 0; Index < OptionCount; Index++) { | |
| if (OrderedList) { | |
| HiiValueArray[Index].Type = ValueType; | |
| HiiValueArray[Index].Value.u64 = GetArrayData (ValueArray, ValueType, Index); | |
| } else { | |
| OneOfOption = QUESTION_OPTION_FROM_LINK (Link); | |
| CopyMem (&HiiValueArray[Index], &OneOfOption->Value, sizeof (EFI_HII_VALUE)); | |
| Link = GetNextNode (&Question->OptionListHead, Link); | |
| } | |
| } | |
| // | |
| // Move Suppressed Option to list tail | |
| // | |
| PopUpMenuLines = 0; | |
| for (Index = 0; Index < OptionCount; Index++) { | |
| OneOfOption = ValueToOption (Question, &HiiValueArray[OptionCount - Index - 1]); | |
| if (OneOfOption == NULL) { | |
| return EFI_NOT_FOUND; | |
| } | |
| RemoveEntryList (&OneOfOption->Link); | |
| if ((OneOfOption->SuppressExpression != NULL) && | |
| (OneOfOption->SuppressExpression->Result.Value.b)) { | |
| // | |
| // This option is suppressed, insert to tail | |
| // | |
| InsertTailList (&Question->OptionListHead, &OneOfOption->Link); | |
| } else { | |
| // | |
| // Insert to head | |
| // | |
| InsertHeadList (&Question->OptionListHead, &OneOfOption->Link); | |
| PopUpMenuLines++; | |
| } | |
| } | |
| // | |
| // Get the number of one of options present and its size | |
| // | |
| PopUpWidth = 0; | |
| HighlightOptionIndex = 0; | |
| Link = GetFirstNode (&Question->OptionListHead); | |
| for (Index = 0; Index < PopUpMenuLines; Index++) { | |
| OneOfOption = QUESTION_OPTION_FROM_LINK (Link); | |
| StringPtr = GetToken (OneOfOption->Text, MenuOption->Handle); | |
| if (StrLen (StringPtr) > PopUpWidth) { | |
| PopUpWidth = StrLen (StringPtr); | |
| } | |
| FreePool (StringPtr); | |
| if (!OrderedList && CompareHiiValue (&Question->HiiValue, &OneOfOption->Value, NULL) == 0) { | |
| // | |
| // Find current selected Option for OneOf | |
| // | |
| HighlightOptionIndex = Index; | |
| } | |
| Link = GetNextNode (&Question->OptionListHead, Link); | |
| } | |
| // | |
| // Perform popup menu initialization. | |
| // | |
| PopUpWidth = PopUpWidth + POPUP_PAD_SPACE_COUNT; | |
| SavedAttribute = gST->ConOut->Mode->Attribute; | |
| gST->ConOut->SetAttribute (gST->ConOut, POPUP_TEXT | POPUP_BACKGROUND); | |
| if ((PopUpWidth + POPUP_FRAME_WIDTH) > DimensionsWidth) { | |
| PopUpWidth = DimensionsWidth - POPUP_FRAME_WIDTH; | |
| } | |
| Start = (DimensionsWidth - PopUpWidth - POPUP_FRAME_WIDTH) / 2 + gScreenDimensions.LeftColumn; | |
| End = Start + PopUpWidth + POPUP_FRAME_WIDTH; | |
| Top = gScreenDimensions.TopRow + NONE_FRONT_PAGE_HEADER_HEIGHT; | |
| Bottom = gScreenDimensions.BottomRow - STATUS_BAR_HEIGHT - FOOTER_HEIGHT - 1; | |
| MenuLinesInView = Bottom - Top - 1; | |
| if (MenuLinesInView >= PopUpMenuLines) { | |
| Top = Top + (MenuLinesInView - PopUpMenuLines) / 2; | |
| Bottom = Top + PopUpMenuLines + 1; | |
| } else { | |
| ShowDownArrow = TRUE; | |
| } | |
| if (HighlightOptionIndex > (MenuLinesInView - 1)) { | |
| TopOptionIndex = HighlightOptionIndex - MenuLinesInView + 1; | |
| } else { | |
| TopOptionIndex = 0; | |
| } | |
| do { | |
| // | |
| // Clear that portion of the screen | |
| // | |
| ClearLines (Start, End, Top, Bottom, POPUP_TEXT | POPUP_BACKGROUND); | |
| // | |
| // Draw "One of" pop-up menu | |
| // | |
| Character = BOXDRAW_DOWN_RIGHT; | |
| PrintCharAt (Start, Top, Character); | |
| for (Index = Start; Index + 2 < End; Index++) { | |
| if ((ShowUpArrow) && ((Index + 1) == (Start + End) / 2)) { | |
| Character = GEOMETRICSHAPE_UP_TRIANGLE; | |
| } else { | |
| Character = BOXDRAW_HORIZONTAL; | |
| } | |
| PrintChar (Character); | |
| } | |
| Character = BOXDRAW_DOWN_LEFT; | |
| PrintChar (Character); | |
| Character = BOXDRAW_VERTICAL; | |
| for (Index = Top + 1; Index < Bottom; Index++) { | |
| PrintCharAt (Start, Index, Character); | |
| PrintCharAt (End - 1, Index, Character); | |
| } | |
| // | |
| // Move to top Option | |
| // | |
| Link = GetFirstNode (&Question->OptionListHead); | |
| for (Index = 0; Index < TopOptionIndex; Index++) { | |
| Link = GetNextNode (&Question->OptionListHead, Link); | |
| } | |
| // | |
| // Display the One of options | |
| // | |
| Index2 = Top + 1; | |
| for (Index = TopOptionIndex; (Index < PopUpMenuLines) && (Index2 < Bottom); Index++) { | |
| OneOfOption = QUESTION_OPTION_FROM_LINK (Link); | |
| Link = GetNextNode (&Question->OptionListHead, Link); | |
| StringPtr = GetToken (OneOfOption->Text, MenuOption->Handle); | |
| ASSERT (StringPtr != NULL); | |
| // | |
| // If the string occupies multiple lines, truncate it to fit in one line, | |
| // and append a "..." for indication. | |
| // | |
| if (StrLen (StringPtr) > (PopUpWidth - 1)) { | |
| TempStringPtr = AllocateZeroPool (sizeof (CHAR16) * (PopUpWidth - 1)); | |
| ASSERT ( TempStringPtr != NULL ); | |
| CopyMem (TempStringPtr, StringPtr, (sizeof (CHAR16) * (PopUpWidth - 5))); | |
| FreePool (StringPtr); | |
| StringPtr = TempStringPtr; | |
| StrCat (StringPtr, L"..."); | |
| } | |
| if (Index == HighlightOptionIndex) { | |
| // | |
| // Highlight the selected one | |
| // | |
| CurrentOption = OneOfOption; | |
| gST->ConOut->SetAttribute (gST->ConOut, PICKLIST_HIGHLIGHT_TEXT | PICKLIST_HIGHLIGHT_BACKGROUND); | |
| PrintStringAt (Start + 2, Index2, StringPtr); | |
| gST->ConOut->SetAttribute (gST->ConOut, POPUP_TEXT | POPUP_BACKGROUND); | |
| } else { | |
| gST->ConOut->SetAttribute (gST->ConOut, POPUP_TEXT | POPUP_BACKGROUND); | |
| PrintStringAt (Start + 2, Index2, StringPtr); | |
| } | |
| Index2++; | |
| FreePool (StringPtr); | |
| } | |
| Character = BOXDRAW_UP_RIGHT; | |
| PrintCharAt (Start, Bottom, Character); | |
| for (Index = Start; Index + 2 < End; Index++) { | |
| if ((ShowDownArrow) && ((Index + 1) == (Start + End) / 2)) { | |
| Character = GEOMETRICSHAPE_DOWN_TRIANGLE; | |
| } else { | |
| Character = BOXDRAW_HORIZONTAL; | |
| } | |
| PrintChar (Character); | |
| } | |
| Character = BOXDRAW_UP_LEFT; | |
| PrintChar (Character); | |
| // | |
| // Get User selection | |
| // | |
| Key.UnicodeChar = CHAR_NULL; | |
| if ((gDirection == SCAN_UP) || (gDirection == SCAN_DOWN)) { | |
| Key.ScanCode = gDirection; | |
| gDirection = 0; | |
| goto TheKey; | |
| } | |
| Status = WaitForKeyStroke (&Key); | |
| TheKey: | |
| switch (Key.UnicodeChar) { | |
| case '+': | |
| if (OrderedList) { | |
| if ((TopOptionIndex > 0) && (TopOptionIndex == HighlightOptionIndex)) { | |
| // | |
| // Highlight reaches the top of the popup window, scroll one menu item. | |
| // | |
| TopOptionIndex--; | |
| ShowDownArrow = TRUE; | |
| } | |
| if (TopOptionIndex == 0) { | |
| ShowUpArrow = FALSE; | |
| } | |
| if (HighlightOptionIndex > 0) { | |
| HighlightOptionIndex--; | |
| ASSERT (CurrentOption != NULL); | |
| SwapListEntries (CurrentOption->Link.BackLink, &CurrentOption->Link); | |
| } | |
| } | |
| break; | |
| case '-': | |
| // | |
| // If an ordered list op-code, we will allow for a popup of +/- keys | |
| // to create an ordered list of items | |
| // | |
| if (OrderedList) { | |
| if (((TopOptionIndex + MenuLinesInView) < PopUpMenuLines) && | |
| (HighlightOptionIndex == (TopOptionIndex + MenuLinesInView - 1))) { | |
| // | |
| // Highlight reaches the bottom of the popup window, scroll one menu item. | |
| // | |
| TopOptionIndex++; | |
| ShowUpArrow = TRUE; | |
| } | |
| if ((TopOptionIndex + MenuLinesInView) == PopUpMenuLines) { | |
| ShowDownArrow = FALSE; | |
| } | |
| if (HighlightOptionIndex < (PopUpMenuLines - 1)) { | |
| HighlightOptionIndex++; | |
| ASSERT (CurrentOption != NULL); | |
| SwapListEntries (&CurrentOption->Link, CurrentOption->Link.ForwardLink); | |
| } | |
| } | |
| break; | |
| case CHAR_NULL: | |
| switch (Key.ScanCode) { | |
| case SCAN_UP: | |
| case SCAN_DOWN: | |
| if (Key.ScanCode == SCAN_UP) { | |
| if ((TopOptionIndex > 0) && (TopOptionIndex == HighlightOptionIndex)) { | |
| // | |
| // Highlight reaches the top of the popup window, scroll one menu item. | |
| // | |
| TopOptionIndex--; | |
| ShowDownArrow = TRUE; | |
| } | |
| if (TopOptionIndex == 0) { | |
| ShowUpArrow = FALSE; | |
| } | |
| if (HighlightOptionIndex > 0) { | |
| HighlightOptionIndex--; | |
| } | |
| } else { | |
| if (((TopOptionIndex + MenuLinesInView) < PopUpMenuLines) && | |
| (HighlightOptionIndex == (TopOptionIndex + MenuLinesInView - 1))) { | |
| // | |
| // Highlight reaches the bottom of the popup window, scroll one menu item. | |
| // | |
| TopOptionIndex++; | |
| ShowUpArrow = TRUE; | |
| } | |
| if ((TopOptionIndex + MenuLinesInView) == PopUpMenuLines) { | |
| ShowDownArrow = FALSE; | |
| } | |
| if (HighlightOptionIndex < (PopUpMenuLines - 1)) { | |
| HighlightOptionIndex++; | |
| } | |
| } | |
| break; | |
| case SCAN_ESC: | |
| gST->ConOut->SetAttribute (gST->ConOut, SavedAttribute); | |
| // | |
| // Restore link list order for orderedlist | |
| // | |
| if (OrderedList) { | |
| HiiValue.Type = ValueType; | |
| HiiValue.Value.u64 = 0; | |
| for (Index = 0; Index < Question->MaxContainers; Index++) { | |
| HiiValue.Value.u64 = GetArrayData (ValueArray, ValueType, Index); | |
| if (HiiValue.Value.u64 == 0) { | |
| break; | |
| } | |
| OneOfOption = ValueToOption (Question, &HiiValue); | |
| if (OneOfOption == NULL) { | |
| return EFI_NOT_FOUND; | |
| } | |
| RemoveEntryList (&OneOfOption->Link); | |
| InsertTailList (&Question->OptionListHead, &OneOfOption->Link); | |
| } | |
| } | |
| FreePool (HiiValueArray); | |
| return EFI_DEVICE_ERROR; | |
| default: | |
| break; | |
| } | |
| break; | |
| case CHAR_CARRIAGE_RETURN: | |
| // | |
| // return the current selection | |
| // | |
| if (OrderedList) { | |
| Index = 0; | |
| Link = GetFirstNode (&Question->OptionListHead); | |
| while (!IsNull (&Question->OptionListHead, Link)) { | |
| OneOfOption = QUESTION_OPTION_FROM_LINK (Link); | |
| SetArrayData (ValueArray, ValueType, Index, OneOfOption->Value.Value.u64); | |
| Index++; | |
| if (Index > Question->MaxContainers) { | |
| break; | |
| } | |
| Link = GetNextNode (&Question->OptionListHead, Link); | |
| } | |
| } else { | |
| ASSERT (CurrentOption != NULL); | |
| CopyMem (&Question->HiiValue, &CurrentOption->Value, sizeof (EFI_HII_VALUE)); | |
| } | |
| gST->ConOut->SetAttribute (gST->ConOut, SavedAttribute); | |
| FreePool (HiiValueArray); | |
| Status = ValidateQuestion (Selection->FormSet, Selection->Form, Question, EFI_HII_EXPRESSION_INCONSISTENT_IF); | |
| if (EFI_ERROR (Status)) { | |
| // | |
| // Input value is not valid, restore Question Value | |
| // | |
| GetQuestionValue (Selection->FormSet, Selection->Form, Question, TRUE); | |
| } else { | |
| SetQuestionValue (Selection->FormSet, Selection->Form, Question, TRUE); | |
| UpdateStatusBar (NV_UPDATE_REQUIRED, Question->QuestionFlags, TRUE); | |
| } | |
| return Status; | |
| default: | |
| break; | |
| } | |
| } while (TRUE); | |
| } | |
| /** | |
| Wait for a key to be pressed by user. | |
| @param Key The key which is pressed by user. | |
| @retval EFI_SUCCESS The function always completed successfully. | |
| **/ | |
| EFI_STATUS | |
| WaitForKeyStroke ( | |
| OUT EFI_INPUT_KEY *Key | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| do { | |
| UiWaitForSingleEvent (gST->ConIn->WaitForKey, 0, 0); | |
| Status = gST->ConIn->ReadKeyStroke (gST->ConIn, Key); | |
| } while (EFI_ERROR(Status)); | |
| return Status; | |
| } |