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