[FREELDR] Code fixes and enhancements.
[reactos.git] / boot / freeldr / freeldr / ui / directui.c
1 /*
2 * PROJECT: ReactOS Boot Loader
3 * LICENSE: BSD - See COPYING.ARM in the top level directory
4 * FILE: boot/freeldr/freeldr/ui/directui.c
5 * PURPOSE: FreeLDR UI Routines
6 * PROGRAMMERS: ReactOS Portable Systems Group
7 */
8 #ifdef _M_ARM
9
10 /* INCLUDES *******************************************************************/
11
12 #include <freeldr.h>
13
14 /* GLOBALS ********************************************************************/
15
16 /* FUNCTIONS ******************************************************************/
17
18 ULONG UiScreenWidth;
19 ULONG UiScreenHeight;
20 UCHAR UiMenuFgColor = COLOR_GRAY;
21 UCHAR UiMenuBgColor = COLOR_BLACK;
22 UCHAR UiTextColor = COLOR_GRAY;
23 UCHAR UiSelectedTextColor = COLOR_BLACK;
24 UCHAR UiSelectedTextBgColor = COLOR_GRAY;
25 CHAR UiTimeText[260] = "Seconds until highlighted choice will be started automatically: ";
26
27 INT
28 TuiPrintf(const char *Format,
29 ...)
30 {
31 int i;
32 int Length;
33 va_list ap;
34 CHAR Buffer[512];
35
36 va_start(ap, Format);
37 Length = _vsnprintf(Buffer, sizeof(Buffer), Format, ap);
38 va_end(ap);
39
40 if (Length == -1) Length = sizeof(Buffer);
41
42 for (i = 0; i < Length; i++)
43 {
44 MachConsPutChar(Buffer[i]);
45 }
46
47 return Length;
48 }
49
50 BOOLEAN
51 UiInitialize(IN BOOLEAN ShowGui)
52 {
53 ULONG Depth;
54
55 /* Nothing to do */
56 if (!ShowGui) return TRUE;
57
58 /* Set mode and query size */
59 MachVideoSetDisplayMode(NULL, TRUE);
60 MachVideoGetDisplaySize(&UiScreenWidth, &UiScreenHeight, &Depth);
61 return TRUE;
62 }
63
64 VOID
65 UiUnInitialize(IN PCSTR BootText)
66 {
67 /* Nothing to do */
68 return;
69 }
70
71 VOID
72 UiDrawBackdrop(VOID)
73 {
74 /* Clear the screen */
75 MachVideoClearScreen(ATTR(COLOR_WHITE, COLOR_BLACK));
76 }
77
78 VOID
79 UiDrawText(IN ULONG X,
80 IN ULONG Y,
81 IN PCSTR Text,
82 IN UCHAR Attr)
83 {
84 ULONG i, j;
85
86 /* Draw the text character by character, but don't exceed the width */
87 for (i = X, j = 0; Text[j] && i < UiScreenWidth; i++, j++)
88 {
89 /* Write the character */
90 MachVideoPutChar(Text[j], Attr, i, Y);
91 }
92 }
93
94 VOID
95 UiDrawText2(IN ULONG X,
96 IN ULONG Y,
97 IN ULONG MaxNumChars,
98 IN PCSTR Text,
99 IN UCHAR Attr)
100 {
101 ULONG i, j;
102
103 /* Draw the text character by character, but don't exceed the width */
104 for (i = X, j = 0; Text[j] && i < UiScreenWidth && (MaxNumChars > 0 ? j < MaxNumChars : TRUE); i++, j++)
105 {
106 /* Write the character */
107 MachVideoPutChar(Text[j], Attr, i, Y);
108 }
109 }
110
111 VOID
112 UiDrawCenteredText(IN ULONG Left,
113 IN ULONG Top,
114 IN ULONG Right,
115 IN ULONG Bottom,
116 IN PCSTR TextString,
117 IN UCHAR Attr)
118 {
119 ULONG TextLength, BoxWidth, BoxHeight, LineBreakCount, Index, LastIndex;
120 ULONG RealLeft, RealTop, X, Y;
121 CHAR Temp[2];
122
123 /* Query text length */
124 TextLength = strlen(TextString);
125
126 /* Count the new lines and the box width */
127 LineBreakCount = 0;
128 BoxWidth = 0;
129 LastIndex = 0;
130 for (Index=0; Index < TextLength; Index++)
131 {
132 /* Scan for new lines */
133 if (TextString[Index] == '\n')
134 {
135 /* Remember the new line */
136 LastIndex = Index;
137 LineBreakCount++;
138 }
139 else
140 {
141 /* Check for new larger box width */
142 if ((Index - LastIndex) > BoxWidth)
143 {
144 /* Update it */
145 BoxWidth = (Index - LastIndex);
146 }
147 }
148 }
149
150 /* Base the box height on the number of lines */
151 BoxHeight = LineBreakCount + 1;
152
153 /* Create the centered coordinates */
154 RealLeft = (((Right - Left) - BoxWidth) / 2) + Left;
155 RealTop = (((Bottom - Top) - BoxHeight) / 2) + Top;
156
157 /* Now go for a second scan */
158 LastIndex = 0;
159 for (Index=0; Index < TextLength; Index++)
160 {
161 /* Look for new lines again */
162 if (TextString[Index] == '\n')
163 {
164 /* Update where the text should start */
165 RealTop++;
166 LastIndex = 0;
167 }
168 else
169 {
170 /* We've got a line of text to print, do it */
171 X = RealLeft + LastIndex;
172 Y = RealTop;
173 LastIndex++;
174 Temp[0] = TextString[Index];
175 Temp[1] = 0;
176 UiDrawText(X, Y, Temp, Attr);
177 }
178 }
179 }
180
181 VOID
182 UiDrawStatusText(IN PCSTR StatusText)
183 {
184 return;
185 }
186
187 VOID
188 UiInfoBox(IN PCSTR MessageText)
189 {
190 TuiPrintf(MessageText);
191 }
192
193 VOID
194 UiMessageBox(IN PCSTR MessageText)
195 {
196 TuiPrintf(MessageText);
197 }
198
199 VOID
200 UiMessageBoxCritical(IN PCSTR MessageText)
201 {
202 TuiPrintf(MessageText);
203 }
204
205 VOID
206 UiDrawProgressBarCenter(IN ULONG Position,
207 IN ULONG Range,
208 IN PCHAR ProgressText)
209 {
210 ULONG Left, Top, Right, Bottom, Width, Height;
211
212 /* Build the coordinates and sizes */
213 Height = 2;
214 Width = UiScreenWidth;
215 Left = 0;
216 Right = (Left + Width) - 1;
217 Top = UiScreenHeight - Height - 4;
218 Bottom = Top + Height + 1;
219
220 /* Draw the progress bar */
221 UiDrawProgressBar(Left, Top, Right, Bottom, Position, Range, ProgressText);
222 }
223
224 VOID
225 UiDrawProgressBar(IN ULONG Left,
226 IN ULONG Top,
227 IN ULONG Right,
228 IN ULONG Bottom,
229 IN ULONG Position,
230 IN ULONG Range,
231 IN PCHAR ProgressText)
232 {
233 ULONG i, ProgressBarWidth;
234
235 /* Calculate the width of the bar proper */
236 ProgressBarWidth = (Right - Left) - 3;
237
238 /* First make sure the progress bar text fits */
239 UiTruncateStringEllipsis(ProgressText, ProgressBarWidth - 4);
240 if (Position > Range) Position = Range;
241
242 /* Draw the "Loading..." text */
243 UiDrawCenteredText(Left + 2, Top + 1, Right - 2, Top + 1, ProgressText, ATTR(7, 0));
244
245 /* Draw the percent complete */
246 for (i = 0; i < (Position * ProgressBarWidth) / Range; i++)
247 {
248 /* Use the fill character */
249 UiDrawText(Left + 2 + i, Top + 2, "\xDB", ATTR(UiTextColor, UiMenuBgColor));
250 }
251 }
252
253 VOID
254 UiShowMessageBoxesInSection(
255 IN ULONG_PTR SectionId)
256 {
257 return;
258 }
259
260 VOID
261 UiShowMessageBoxesInArgv(
262 IN ULONG Argc,
263 IN PCHAR Argv[])
264 {
265 return;
266 }
267
268 VOID
269 UiTruncateStringEllipsis(IN PCHAR StringText,
270 IN ULONG MaxChars)
271 {
272 /* If it's too large, just add some ellipsis past the maximum */
273 if (strlen(StringText) > MaxChars)
274 strcpy(&StringText[MaxChars - 3], "...");
275 }
276
277 VOID
278 UiDrawMenuBox(IN PUI_MENU_INFO MenuInfo)
279 {
280 CHAR MenuLineText[80], TempString[80];
281 ULONG i;
282
283 /* If there is a timeout draw the time remaining */
284 if (MenuInfo->MenuTimeRemaining >= 0)
285 {
286 /* Copy the integral time text string, and remove the last 2 chars */
287 strcpy(TempString, UiTimeText);
288 i = strlen(TempString);
289 TempString[i - 2] = 0;
290
291 /* Display the first part of the string and the remaining time */
292 strcpy(MenuLineText, TempString);
293 _itoa(MenuInfo->MenuTimeRemaining, TempString, 10);
294 strcat(MenuLineText, TempString);
295
296 /* Add the last 2 chars */
297 strcat(MenuLineText, &UiTimeText[i - 2]);
298
299 /* Display under the menu directly */
300 UiDrawText(0,
301 MenuInfo->Bottom + 4,
302 MenuLineText,
303 ATTR(UiMenuFgColor, UiMenuBgColor));
304 }
305 else
306 {
307 /* Erase the timeout string with spaces, and 0-terminate for sure */
308 for (i=0; i<sizeof(MenuLineText)-1; i++)
309 {
310 MenuLineText[i] = ' ';
311 }
312 MenuLineText[sizeof(MenuLineText)-1] = 0;
313
314 /* Draw this "empty" string to erase */
315 UiDrawText(0,
316 MenuInfo->Bottom + 4,
317 MenuLineText,
318 ATTR(UiMenuFgColor, UiMenuBgColor));
319 }
320
321 /* Loop each item */
322 for (i = 0; i < MenuInfo->MenuItemCount; i++)
323 {
324 /* Check if it's a separator */
325 if (MenuInfo->MenuItemList[i] == NULL)
326 {
327 /* Draw the separator line */
328 UiDrawText(MenuInfo->Left,
329 MenuInfo->Top + i + 1,
330 "\xC7",
331 ATTR(UiMenuFgColor, UiMenuBgColor));
332 UiDrawText(MenuInfo->Right,
333 MenuInfo->Top + i + 1,
334 "\xB6",
335 ATTR(UiMenuFgColor, UiMenuBgColor));
336 }
337 }
338 }
339
340 VOID
341 UiDrawMenuItem(IN PUI_MENU_INFO MenuInfo,
342 IN ULONG MenuItemNumber)
343 {
344 CHAR MenuLineText[80];
345 UCHAR Attribute = ATTR(UiTextColor, UiMenuBgColor);
346
347 /* Simply left-align it */
348 MenuLineText[0] = '\0';
349 strcat(MenuLineText, " ");
350
351 /* Now append the text string */
352 if (MenuInfo->MenuItemList[MenuItemNumber])
353 strcat(MenuLineText, MenuInfo->MenuItemList[MenuItemNumber]);
354
355 /* If it is a separator */
356 if (MenuInfo->MenuItemList[MenuItemNumber] == NULL)
357 {
358 /* Make it a separator line and use menu colors */
359 memset(MenuLineText, 0, 80);
360 memset(MenuLineText, 0xC4, (MenuInfo->Right - MenuInfo->Left - 1));
361 Attribute = ATTR(UiMenuFgColor, UiMenuBgColor);
362 }
363 else if (MenuItemNumber == MenuInfo->SelectedMenuItem)
364 {
365 /* If this is the selected item, use the selected colors */
366 Attribute = ATTR(UiSelectedTextColor, UiSelectedTextBgColor);
367 }
368
369 /* Draw the item */
370 UiDrawText(MenuInfo->Left + 1,
371 MenuInfo->Top + 1 + MenuItemNumber,
372 MenuLineText,
373 Attribute);
374 }
375
376 VOID
377 UiDrawMenu(IN PUI_MENU_INFO MenuInfo)
378 {
379 ULONG i;
380
381 /* No GUI status bar text, just minimal text. Show the menu header. */
382 if (MenuInfo->MenuHeader)
383 {
384 UiDrawText(0,
385 MenuInfo->Top - 2,
386 MenuInfo->MenuHeader,
387 ATTR(UiMenuFgColor, UiMenuBgColor));
388 }
389
390 /* Now tell the user how to choose */
391 UiDrawText(0,
392 MenuInfo->Bottom + 1,
393 "Use \x18 and \x19 to move the highlight to your choice.",
394 ATTR(UiMenuFgColor, UiMenuBgColor));
395 UiDrawText(0,
396 MenuInfo->Bottom + 2,
397 "Press ENTER to choose.",
398 ATTR(UiMenuFgColor, UiMenuBgColor));
399
400 /* And show the menu footer */
401 if (MenuInfo->MenuFooter)
402 {
403 UiDrawText(0,
404 UiScreenHeight - 4,
405 MenuInfo->MenuFooter,
406 ATTR(UiMenuFgColor, UiMenuBgColor));
407 }
408
409 /* Draw the menu box */
410 UiDrawMenuBox(MenuInfo);
411
412 /* Draw each line of the menu */
413 for (i = 0; i < MenuInfo->MenuItemCount; i++)
414 {
415 UiDrawMenuItem(MenuInfo, i);
416 }
417
418 /* Display the boot options if needed */
419 if (MenuInfo->ShowBootOptions)
420 {
421 DisplayBootTimeOptions();
422 }
423 }
424
425 ULONG
426 UiProcessMenuKeyboardEvent(IN PUI_MENU_INFO MenuInfo,
427 IN UiMenuKeyPressFilterCallback KeyPressFilter)
428 {
429 ULONG KeyEvent = 0;
430 ULONG Selected, Count;
431
432 /* Check for a keypress */
433 if (!MachConsKbHit())
434 return 0; // None, bail out
435
436 /* Check if the timeout is not already complete */
437 if (MenuInfo->MenuTimeRemaining != -1)
438 {
439 /* Cancel it and remove it */
440 MenuInfo->MenuTimeRemaining = -1;
441 UiDrawMenuBox(MenuInfo);
442 }
443
444 /* Get the key (get the extended key if needed) */
445 KeyEvent = MachConsGetCh();
446 if (KeyEvent == KEY_EXTENDED)
447 KeyEvent = MachConsGetCh();
448
449 /*
450 * Call the supplied key filter callback function to see
451 * if it is going to handle this keypress.
452 */
453 if (KeyPressFilter &&
454 KeyPressFilter(KeyEvent, MenuInfo->SelectedMenuItem, MenuInfo->Context))
455 {
456 /* It processed the key character, so redraw and exit */
457 UiDrawMenu(MenuInfo);
458 return 0;
459 }
460
461 /* Process the key */
462 if ((KeyEvent == KEY_UP ) || (KeyEvent == KEY_DOWN) ||
463 (KeyEvent == KEY_HOME) || (KeyEvent == KEY_END ))
464 {
465 /* Get the current selected item and count */
466 Selected = MenuInfo->SelectedMenuItem;
467 Count = MenuInfo->MenuItemCount - 1;
468
469 /* Check the key and change the selected menu item */
470 if ((KeyEvent == KEY_UP) && (Selected > 0))
471 {
472 /* Deselect previous item and go up */
473 MenuInfo->SelectedMenuItem--;
474 UiDrawMenuItem(MenuInfo, Selected);
475 Selected--;
476
477 // Skip past any separators
478 if ((Selected > 0) &&
479 (MenuInfo->MenuItemList[Selected] == NULL))
480 {
481 MenuInfo->SelectedMenuItem--;
482 }
483 }
484 else if ( ((KeyEvent == KEY_UP) && (Selected == 0)) ||
485 (KeyEvent == KEY_END) )
486 {
487 /* Go to the end */
488 MenuInfo->SelectedMenuItem = Count;
489 UiDrawMenuItem(MenuInfo, Selected);
490 }
491 else if ((KeyEvent == KEY_DOWN) && (Selected < Count))
492 {
493 /* Deselect previous item and go down */
494 MenuInfo->SelectedMenuItem++;
495 UiDrawMenuItem(MenuInfo, Selected);
496 Selected++;
497
498 // Skip past any separators
499 if ((Selected < Count) &&
500 (MenuInfo->MenuItemList[Selected] == NULL))
501 {
502 MenuInfo->SelectedMenuItem++;
503 }
504 }
505 else if ( ((KeyEvent == KEY_DOWN) && (Selected == Count)) ||
506 (KeyEvent == KEY_HOME) )
507 {
508 /* Go to the beginning */
509 MenuInfo->SelectedMenuItem = 0;
510 UiDrawMenuItem(MenuInfo, Selected);
511 }
512
513 /* Select new item and update video buffer */
514 UiDrawMenuItem(MenuInfo, MenuInfo->SelectedMenuItem);
515 }
516
517 /* Return the pressed key */
518 return KeyEvent;
519 }
520
521 VOID
522 UiCalcMenuBoxSize(IN PUI_MENU_INFO MenuInfo)
523 {
524 ULONG i, Width = 0, Height, Length;
525
526 /* Height is the menu item count plus 2 (top border & bottom border) */
527 Height = MenuInfo->MenuItemCount + 2;
528 Height -= 1; // Height is zero-based
529
530 /* Loop every item */
531 for (i = 0; i < MenuInfo->MenuItemCount; i++)
532 {
533 /* Get the string length and make it become the new width if necessary */
534 if (MenuInfo->MenuItemList[i])
535 {
536 Length = (ULONG)strlen(MenuInfo->MenuItemList[i]);
537 if (Length > Width) Width = Length;
538 }
539 }
540
541 /* Allow room for left & right borders, plus 8 spaces on each side */
542 Width += 18;
543
544 /* Put the menu in the default left-corner position */
545 MenuInfo->Left = -1;
546 MenuInfo->Top = 4;
547
548 /* The other margins are the same */
549 MenuInfo->Right = (MenuInfo->Left) + Width;
550 MenuInfo->Bottom = (MenuInfo->Top) + Height;
551 }
552
553 BOOLEAN
554 UiDisplayMenu(
555 IN PCSTR MenuHeader,
556 IN PCSTR MenuFooter OPTIONAL,
557 IN BOOLEAN ShowBootOptions,
558 IN PCSTR MenuItemList[],
559 IN ULONG MenuItemCount,
560 IN ULONG DefaultMenuItem,
561 IN LONG MenuTimeOut,
562 OUT PULONG SelectedMenuItem,
563 IN BOOLEAN CanEscape,
564 IN UiMenuKeyPressFilterCallback KeyPressFilter OPTIONAL,
565 IN PVOID Context OPTIONAL)
566 {
567 UI_MENU_INFO MenuInformation;
568 ULONG LastClockSecond;
569 ULONG CurrentClockSecond;
570 ULONG KeyPress;
571
572 /*
573 * Before taking any default action if there is no timeout,
574 * check whether the supplied key filter callback function
575 * may handle a specific user keypress. If it does, the
576 * timeout is cancelled.
577 */
578 if (!MenuTimeOut && KeyPressFilter && MachConsKbHit())
579 {
580 /* Get the key (get the extended key if needed) */
581 KeyPress = MachConsGetCh();
582 if (KeyPress == KEY_EXTENDED)
583 KeyPress = MachConsGetCh();
584
585 /*
586 * Call the supplied key filter callback function to see
587 * if it is going to handle this keypress.
588 */
589 if (KeyPressFilter(KeyPress, DefaultMenuItem, Context))
590 {
591 /* It processed the key character, cancel the timeout */
592 MenuTimeOut = -1;
593 }
594 }
595
596 /* Check if there's no timeout */
597 if (!MenuTimeOut)
598 {
599 /* Return the default selected item */
600 if (SelectedMenuItem) *SelectedMenuItem = DefaultMenuItem;
601 return TRUE;
602 }
603
604 /* Setup the MENU_INFO structure */
605 MenuInformation.MenuHeader = MenuHeader;
606 MenuInformation.MenuFooter = MenuFooter;
607 MenuInformation.ShowBootOptions = ShowBootOptions;
608 MenuInformation.MenuItemList = MenuItemList;
609 MenuInformation.MenuItemCount = MenuItemCount;
610 MenuInformation.MenuTimeRemaining = MenuTimeOut;
611 MenuInformation.SelectedMenuItem = DefaultMenuItem;
612 MenuInformation.Context = Context;
613
614 /* Calculate the size of the menu box */
615 UiCalcMenuBoxSize(&MenuInformation);
616
617 /* Draw the menu */
618 UiDrawMenu(&MenuInformation);
619
620 /* Get the current second of time */
621 LastClockSecond = ArcGetTime()->Second;
622
623 /* Process keys */
624 while (TRUE)
625 {
626 /* Process key presses */
627 KeyPress = UiProcessMenuKeyboardEvent(&MenuInformation, KeyPressFilter);
628
629 /* Check for ENTER or ESC */
630 if (KeyPress == KEY_ENTER) break;
631 if (CanEscape && KeyPress == KEY_ESC) return FALSE;
632
633 /* Check if there is a countdown */
634 if (MenuInformation.MenuTimeRemaining > 0)
635 {
636 /* Get the updated time, seconds only */
637 CurrentClockSecond = ArcGetTime()->Second;
638
639 /* Check if more then a second has now elapsed */
640 if (CurrentClockSecond != LastClockSecond)
641 {
642 /* Update the time information */
643 LastClockSecond = CurrentClockSecond;
644 MenuInformation.MenuTimeRemaining--;
645
646 /* Update the menu */
647 UiDrawMenuBox(&MenuInformation);
648 }
649 }
650 else if (MenuInformation.MenuTimeRemaining == 0)
651 {
652 /* A time out occurred, exit this loop and return default OS */
653 break;
654 }
655 }
656
657 /* Return the selected item */
658 if (SelectedMenuItem) *SelectedMenuItem = MenuInformation.SelectedMenuItem;
659 return TRUE;
660 }
661
662 #endif