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