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