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