[FREELDR] Check for any user keypress in menu even when the timeout is zero.
[reactos.git] / boot / freeldr / freeldr / ui / tuimenu.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: FreeLoader
4 * FILE: boot/freeldr/freeldr/ui/tuimenu.c
5 * PURPOSE: UI Menu Functions
6 * PROGRAMMERS: Alex Ionescu (alex@relsoft.net)
7 * Brian Palmer (brianp@sginet.com)
8 */
9
10 /* INCLUDES ******************************************************************/
11 #ifndef _M_ARM
12 #include <freeldr.h>
13
14 /* FUNCTIONS *****************************************************************/
15
16 BOOLEAN
17 TuiDisplayMenu(PCSTR MenuHeader,
18 PCSTR MenuFooter,
19 BOOLEAN ShowBootOptions,
20 PCSTR MenuItemList[],
21 ULONG MenuItemCount,
22 ULONG DefaultMenuItem,
23 LONG MenuTimeOut,
24 ULONG* SelectedMenuItem,
25 BOOLEAN CanEscape,
26 UiMenuKeyPressFilterCallback KeyPressFilter)
27 {
28 UI_MENU_INFO MenuInformation;
29 ULONG LastClockSecond;
30 ULONG CurrentClockSecond;
31 ULONG KeyPress;
32
33 //
34 // Before taking any default action if there is no timeout,
35 // check whether the supplied key filter callback function
36 // may handle a specific user keypress. If it does, the
37 // timeout is cancelled.
38 //
39 if (!MenuTimeOut && KeyPressFilter && MachConsKbHit())
40 {
41 //
42 // Get the key
43 //
44 KeyPress = MachConsGetCh();
45
46 //
47 // Is it extended? Then get the extended key
48 //
49 if (!KeyPress) KeyPress = MachConsGetCh();
50
51 //
52 // Call the supplied key filter callback function to see
53 // if it is going to handle this keypress.
54 //
55 if (KeyPressFilter(KeyPress))
56 {
57 //
58 // It processed the key character, cancel the timeout
59 //
60 MenuTimeOut = -1;
61 }
62 }
63
64 //
65 // Check if there's no timeout
66 //
67 if (!MenuTimeOut)
68 {
69 //
70 // Return the default selected item
71 //
72 if (SelectedMenuItem) *SelectedMenuItem = DefaultMenuItem;
73 return TRUE;
74 }
75
76 //
77 // Setup the MENU_INFO structure
78 //
79 MenuInformation.MenuHeader = MenuHeader;
80 MenuInformation.MenuFooter = MenuFooter;
81 MenuInformation.ShowBootOptions = ShowBootOptions;
82 MenuInformation.MenuItemList = MenuItemList;
83 MenuInformation.MenuItemCount = MenuItemCount;
84 MenuInformation.MenuTimeRemaining = MenuTimeOut;
85 MenuInformation.SelectedMenuItem = DefaultMenuItem;
86
87 //
88 // Calculate the size of the menu box
89 //
90 TuiCalcMenuBoxSize(&MenuInformation);
91
92 //
93 // Draw the menu
94 //
95 UiVtbl.DrawMenu(&MenuInformation);
96
97 //
98 // Get the current second of time
99 //
100 LastClockSecond = ArcGetTime()->Second;
101
102 //
103 // Process keys
104 //
105 while (TRUE)
106 {
107 //
108 // Process key presses
109 //
110 KeyPress = TuiProcessMenuKeyboardEvent(&MenuInformation,
111 KeyPressFilter);
112
113 //
114 // Check for ENTER or ESC
115 //
116 if (KeyPress == KEY_ENTER) break;
117 if (CanEscape && KeyPress == KEY_ESC) return FALSE;
118
119 //
120 // Update the date & time
121 //
122 TuiUpdateDateTime();
123 VideoCopyOffScreenBufferToVRAM();
124
125 //
126 // Check if there is a countdown
127 //
128 if (MenuInformation.MenuTimeRemaining > 0)
129 {
130 //
131 // Get the updated time, seconds only
132 //
133 CurrentClockSecond = ArcGetTime()->Second;
134
135 //
136 // Check if more then a second has now elapsed
137 //
138 if (CurrentClockSecond != LastClockSecond)
139 {
140 //
141 // Update the time information
142 //
143 LastClockSecond = CurrentClockSecond;
144 MenuInformation.MenuTimeRemaining--;
145
146 //
147 // Update the menu
148 //
149 TuiDrawMenuBox(&MenuInformation);
150 VideoCopyOffScreenBufferToVRAM();
151 }
152 }
153 else if (MenuInformation.MenuTimeRemaining == 0)
154 {
155 //
156 // A time out occurred, exit this loop and return default OS
157 //
158 break;
159 }
160
161 MachHwIdle();
162 }
163
164 //
165 // Return the selected item
166 //
167 if (SelectedMenuItem) *SelectedMenuItem = MenuInformation.SelectedMenuItem;
168 return TRUE;
169 }
170
171 VOID
172 NTAPI
173 TuiCalcMenuBoxSize(PUI_MENU_INFO MenuInfo)
174 {
175 ULONG i;
176 ULONG Width = 0;
177 ULONG Height;
178 ULONG Length;
179
180 //
181 // Height is the menu item count plus 2 (top border & bottom border)
182 //
183 Height = MenuInfo->MenuItemCount + 2;
184 Height -= 1; // Height is zero-based
185
186 //
187 // Loop every item
188 //
189 for(i = 0; i < MenuInfo->MenuItemCount; i++)
190 {
191 //
192 // Get the string length and make it become the new width if necessary
193 //
194 if (MenuInfo->MenuItemList[i])
195 {
196 Length = (ULONG)strlen(MenuInfo->MenuItemList[i]);
197 if (Length > Width) Width = Length;
198 }
199 }
200
201 //
202 // Allow room for left & right borders, plus 8 spaces on each side
203 //
204 Width += 18;
205
206 //
207 // Check if we're drawing a centered menu
208 //
209 if (UiCenterMenu)
210 {
211 //
212 // Calculate the menu box area for a centered menu
213 //
214 MenuInfo->Left = (UiScreenWidth - Width) / 2;
215 MenuInfo->Top = (((UiScreenHeight - TUI_TITLE_BOX_CHAR_HEIGHT) -
216 Height) / 2) + TUI_TITLE_BOX_CHAR_HEIGHT;
217 }
218 else
219 {
220 //
221 // Put the menu in the default left-corner position
222 //
223 MenuInfo->Left = -1;
224 MenuInfo->Top = 4;
225 }
226
227 //
228 // The other margins are the same
229 //
230 MenuInfo->Right = (MenuInfo->Left) + Width;
231 MenuInfo->Bottom = (MenuInfo->Top) + Height;
232 }
233
234 VOID
235 TuiDrawMenu(PUI_MENU_INFO MenuInfo)
236 {
237 ULONG i;
238
239 //
240 // Draw the backdrop
241 //
242 UiDrawBackdrop();
243
244 //
245 // Update the status bar
246 //
247 UiVtbl.DrawStatusText("Use \x18 and \x19 to select, then press ENTER.");
248
249 //
250 // Draw the menu box
251 //
252 TuiDrawMenuBox(MenuInfo);
253
254 //
255 // Draw each line of the menu
256 //
257 for (i = 0; i < MenuInfo->MenuItemCount; i++)
258 {
259 TuiDrawMenuItem(MenuInfo, i);
260 }
261
262 /* Display the boot options if needed */
263 if (MenuInfo->ShowBootOptions)
264 {
265 DisplayBootTimeOptions();
266 }
267
268 VideoCopyOffScreenBufferToVRAM();
269 }
270
271 VOID
272 NTAPI
273 TuiDrawMenuBox(PUI_MENU_INFO MenuInfo)
274 {
275 CHAR MenuLineText[80], TempString[80];
276 ULONG i;
277
278 //
279 // Draw the menu box if requested
280 //
281 if (UiMenuBox)
282 {
283 UiDrawBox(MenuInfo->Left,
284 MenuInfo->Top,
285 MenuInfo->Right,
286 MenuInfo->Bottom,
287 D_VERT,
288 D_HORZ,
289 FALSE, // Filled
290 TRUE, // Shadow
291 ATTR(UiMenuFgColor, UiMenuBgColor));
292 }
293
294 //
295 // If there is a timeout draw the time remaining
296 //
297 if (MenuInfo->MenuTimeRemaining >= 0)
298 {
299 //
300 // Copy the integral time text string, and remove the last 2 chars
301 //
302 strcpy(TempString, UiTimeText);
303 i = (ULONG)strlen(TempString);
304 TempString[i - 2] = 0;
305
306 //
307 // Display the first part of the string and the remaining time
308 //
309 strcpy(MenuLineText, TempString);
310 _itoa(MenuInfo->MenuTimeRemaining, TempString, 10);
311 strcat(MenuLineText, TempString);
312
313 //
314 // Add the last 2 chars
315 //
316 strcat(MenuLineText, &UiTimeText[i - 2]);
317
318 //
319 // Check if this is a centered menu
320 //
321 if (UiCenterMenu)
322 {
323 //
324 // Display it in the center of the menu
325 //
326 UiDrawText(MenuInfo->Right - (ULONG)strlen(MenuLineText) - 1,
327 MenuInfo->Bottom,
328 MenuLineText,
329 ATTR(UiMenuFgColor, UiMenuBgColor));
330 }
331 else
332 {
333 //
334 // Display under the menu directly
335 //
336 UiDrawText(0,
337 MenuInfo->Bottom + 4,
338 MenuLineText,
339 ATTR(UiMenuFgColor, UiMenuBgColor));
340 }
341 }
342 else
343 {
344 //
345 // Erase the timeout string with spaces, and 0-terminate for sure
346 //
347 for (i=0; i<sizeof(MenuLineText)-1; i++)
348 {
349 MenuLineText[i] = ' ';
350 }
351 MenuLineText[sizeof(MenuLineText)-1] = 0;
352
353 //
354 // Draw this "empty" string to erase
355 //
356 if (UiCenterMenu)
357 {
358 UiDrawText(MenuInfo->Right - (ULONG)strlen(MenuLineText) - 1,
359 MenuInfo->Bottom,
360 MenuLineText,
361 ATTR(UiMenuFgColor, UiMenuBgColor));
362 }
363 else
364 {
365 UiDrawText(0,
366 MenuInfo->Bottom + 4,
367 MenuLineText,
368 ATTR(UiMenuFgColor, UiMenuBgColor));
369 }
370 }
371
372 //
373 // Loop each item
374 //
375 for (i = 0; i < MenuInfo->MenuItemCount; i++)
376 {
377 //
378 // Check if it's a separator
379 //
380 if (MenuInfo->MenuItemList[i] == NULL)
381 {
382 //
383 // Draw the separator line
384 //
385 UiDrawText(MenuInfo->Left,
386 MenuInfo->Top + i + 1,
387 "\xC7",
388 ATTR(UiMenuFgColor, UiMenuBgColor));
389 UiDrawText(MenuInfo->Right,
390 MenuInfo->Top + i + 1,
391 "\xB6",
392 ATTR(UiMenuFgColor, UiMenuBgColor));
393 }
394 }
395 }
396
397 VOID
398 NTAPI
399 TuiDrawMenuItem(PUI_MENU_INFO MenuInfo,
400 ULONG MenuItemNumber)
401 {
402 ULONG i;
403 CHAR MenuLineText[80];
404 ULONG SpaceTotal;
405 ULONG SpaceLeft;
406 ULONG SpaceRight = 0;
407 UCHAR Attribute = ATTR(UiTextColor, UiMenuBgColor);
408
409 //
410 // Check if using centered menu
411 //
412 if (UiCenterMenu)
413 {
414 //
415 // We will want the string centered so calculate
416 // how many spaces will be to the left and right
417 //
418 SpaceTotal = (MenuInfo->Right - MenuInfo->Left - 2) -
419 (ULONG)(MenuInfo->MenuItemList[MenuItemNumber] ?
420 strlen(MenuInfo->MenuItemList[MenuItemNumber]) : 0);
421 SpaceLeft = (SpaceTotal / 2) + 1;
422 SpaceRight = (SpaceTotal - SpaceLeft) + 1;
423
424 //
425 // Insert the spaces on the left
426 //
427 for (i = 0; i < SpaceLeft; i++) MenuLineText[i] = ' ';
428 MenuLineText[i] = '\0';
429 }
430 else
431 {
432 //
433 // Simply left-align it
434 //
435 MenuLineText[0] = '\0';
436 strcat(MenuLineText, " ");
437 }
438
439 //
440 // Now append the text string
441 //
442 if (MenuInfo->MenuItemList[MenuItemNumber])
443 strcat(MenuLineText, MenuInfo->MenuItemList[MenuItemNumber]);
444
445 //
446 // Check if using centered menu, and add spaces on the right if so
447 //
448 if (UiCenterMenu) for (i=0; i < SpaceRight; i++) strcat(MenuLineText, " ");
449
450 //
451 // If it is a separator
452 //
453 if (MenuInfo->MenuItemList[MenuItemNumber] == NULL)
454 {
455 //
456 // Make it a separator line and use menu colors
457 //
458 memset(MenuLineText, 0, 80);
459 memset(MenuLineText, 0xC4, (MenuInfo->Right - MenuInfo->Left - 1));
460 Attribute = ATTR(UiMenuFgColor, UiMenuBgColor);
461 }
462 else if (MenuItemNumber == MenuInfo->SelectedMenuItem)
463 {
464 //
465 // If this is the selected item, use the selected colors
466 //
467 Attribute = ATTR(UiSelectedTextColor, UiSelectedTextBgColor);
468 }
469
470 //
471 // Draw the item
472 //
473 UiDrawText(MenuInfo->Left + 1,
474 MenuInfo->Top + 1 + MenuItemNumber,
475 MenuLineText,
476 Attribute);
477 }
478
479 ULONG
480 NTAPI
481 TuiProcessMenuKeyboardEvent(PUI_MENU_INFO MenuInfo,
482 UiMenuKeyPressFilterCallback KeyPressFilter)
483 {
484 ULONG KeyEvent = 0;
485 ULONG Selected, Count;
486
487 //
488 // Check for a keypress
489 //
490 if (MachConsKbHit())
491 {
492 //
493 // Check if the timeout is not already complete
494 //
495 if (MenuInfo->MenuTimeRemaining != -1)
496 {
497 //
498 // Cancel it and remove it
499 //
500 MenuInfo->MenuTimeRemaining = -1;
501 TuiDrawMenuBox(MenuInfo); // FIXME: Remove for minimal UI too
502 }
503
504 //
505 // Get the key
506 //
507 KeyEvent = MachConsGetCh();
508
509 //
510 // Is it extended? Then get the extended key
511 //
512 if (!KeyEvent) KeyEvent = MachConsGetCh();
513
514 //
515 // Call the supplied key filter callback function to see
516 // if it is going to handle this keypress.
517 //
518 if ((KeyPressFilter) && (KeyPressFilter(KeyEvent)))
519 {
520 //
521 // It processed the key character, so redraw and exit
522 //
523 UiVtbl.DrawMenu(MenuInfo);
524 return 0;
525 }
526
527 //
528 // Process the key
529 //
530 if ((KeyEvent == KEY_UP ) || (KeyEvent == KEY_DOWN) ||
531 (KeyEvent == KEY_HOME) || (KeyEvent == KEY_END ))
532 {
533 //
534 // Get the current selected item and count
535 //
536 Selected = MenuInfo->SelectedMenuItem;
537 Count = MenuInfo->MenuItemCount - 1;
538
539 //
540 // Check the key and change the selected menu item
541 //
542 if ((KeyEvent == KEY_UP) && (Selected > 0))
543 {
544 //
545 // Deselect previous item and go up
546 //
547 MenuInfo->SelectedMenuItem--;
548 TuiDrawMenuItem(MenuInfo, Selected);
549 Selected--;
550
551 // Skip past any separators
552 if ((Selected > 0) &&
553 (MenuInfo->MenuItemList[Selected] == NULL))
554 {
555 MenuInfo->SelectedMenuItem--;
556 }
557 }
558 else if ( ((KeyEvent == KEY_UP) && (Selected == 0)) ||
559 (KeyEvent == KEY_END) )
560 {
561 //
562 // Go to the end
563 //
564 MenuInfo->SelectedMenuItem = Count;
565 TuiDrawMenuItem(MenuInfo, Selected);
566 }
567 else if ((KeyEvent == KEY_DOWN) && (Selected < Count))
568 {
569 //
570 // Deselect previous item and go down
571 //
572 MenuInfo->SelectedMenuItem++;
573 TuiDrawMenuItem(MenuInfo, Selected);
574 Selected++;
575
576 // Skip past any separators
577 if ((Selected < Count) &&
578 (MenuInfo->MenuItemList[Selected] == NULL))
579 {
580 MenuInfo->SelectedMenuItem++;
581 }
582 }
583 else if ( ((KeyEvent == KEY_DOWN) && (Selected == Count)) ||
584 (KeyEvent == KEY_HOME) )
585 {
586 //
587 // Go to the beginning
588 //
589 MenuInfo->SelectedMenuItem = 0;
590 TuiDrawMenuItem(MenuInfo, Selected);
591 }
592
593 //
594 // Select new item and update video buffer
595 //
596 TuiDrawMenuItem(MenuInfo, MenuInfo->SelectedMenuItem);
597 VideoCopyOffScreenBufferToVRAM();
598 }
599 }
600
601 //
602 // Return the pressed key
603 //
604 return KeyEvent;
605 }
606 #endif