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