1 /*
2 * Copyright 2004-2010, Axel Dörfler, axeld@pinc-software.de.
3 * Copyright 2011, Rene Gollent, rene@gollent.com.
4 * Distributed under the terms of the MIT License.
5 */
6
7
8 #include <boot/platform.h>
9 #include <boot/menu.h>
10 #include <boot/platform/generic/text_console.h>
11 #include <boot/platform/generic/text_menu.h>
12
13 #include <string.h>
14 #include <system_revision.h>
15
16
17 // position
18 static const int32 kFirstLine = 8;
19 static const int32 kOffsetX = 10;
20 static const int32 kHelpLines = 3;
21
22 // colors
23 static const console_color kBackgroundColor = BLACK;
24 static const console_color kTextColor = WHITE;
25 static const console_color kCopyrightColor = CYAN;
26 static const console_color kTitleColor = YELLOW;
27 static const console_color kTitleBackgroundColor = kBackgroundColor;
28 static const console_color kHelpTextColor = WHITE;
29
30 static const console_color kItemColor = GRAY;
31 static const console_color kSelectedItemColor = WHITE;
32 static const console_color kItemBackgroundColor = kBackgroundColor;
33 static const console_color kSelectedItemBackgroundColor = GRAY;
34 static const console_color kDisabledColor = DARK_GRAY;
35
36 static const console_color kSliderColor = CYAN;
37 static const console_color kSliderBackgroundColor = DARK_GRAY;
38 static const console_color kArrowColor = GRAY;
39
40 static int32 sMenuOffset = 0;
41
42
43 static void run_menu(Menu* menu);
44
45
46 static int32
menu_height()47 menu_height()
48 {
49 return console_height() - kFirstLine - 1 - kHelpLines;
50 }
51
52
53 static void
print_spacing(int32 count)54 print_spacing(int32 count)
55 {
56 for (int32 i = 0; i < count; i++)
57 putchar(' ');
58 }
59
60
61 static void
print_centered(int32 line,const char * text,bool resetPosition=true)62 print_centered(int32 line, const char *text, bool resetPosition = true)
63 {
64 console_set_cursor(console_width() / 2 - strlen(text) / 2, line);
65 printf("%s", text);
66
67 if (resetPosition) {
68 console_set_cursor(0, 0);
69 // this avoids unwanted line feeds
70 }
71 }
72
73
74 static void
print_right(int32 line,const char * text,bool resetPosition=true)75 print_right(int32 line, const char *text, bool resetPosition = true)
76 {
77 console_set_cursor(console_width() - (strlen(text) + 1), line);
78 printf("%s", text);
79
80 if (resetPosition) {
81 console_set_cursor(0, 0);
82 // this avoids unwanted line feeds
83 }
84 }
85
86
87 static void
print_item_at(int32 line,MenuItem * item,bool clearHelp=true)88 print_item_at(int32 line, MenuItem *item, bool clearHelp = true)
89 {
90 bool selected = item->IsSelected();
91
92 line -= sMenuOffset;
93 if (line < 0 || line >= menu_height())
94 return;
95
96 console_color background = selected
97 ? kSelectedItemBackgroundColor : kItemBackgroundColor;
98 console_color foreground = selected
99 ? kSelectedItemColor : kItemColor;
100
101 if (!item->IsEnabled())
102 foreground = kDisabledColor;
103
104 console_set_cursor(kOffsetX, line + kFirstLine);
105 console_set_color(foreground, background);
106
107 size_t length = strlen(item->Label()) + 1;
108
109 if (item->Type() == MENU_ITEM_MARKABLE) {
110 console_set_color(DARK_GRAY, background);
111 printf(" [");
112 console_set_color(foreground, background);
113 printf("%c", item->IsMarked() ? 'x' : ' ');
114 console_set_color(DARK_GRAY, background);
115 printf("] ");
116 console_set_color(foreground, background);
117
118 length += 4;
119 } else
120 printf(" ");
121
122 printf(item->Label());
123
124 if (item->Submenu() && item->Submenu()->Type() == CHOICE_MENU) {
125 // show the current choice (if any)
126 const char *text = " (Current: ";
127 printf(text);
128 length += strlen(text);
129
130 Menu *subMenu = item->Submenu();
131 if (subMenu->ChoiceText() != NULL)
132 text = subMenu->ChoiceText();
133 else
134 text = "None";
135 length += strlen(text);
136
137 console_set_color(selected ? DARK_GRAY : WHITE, background);
138
139 printf(text);
140
141 console_set_color(foreground, background);
142 putchar(')');
143 length++;
144 }
145
146 print_spacing(console_width() - length - 2*kOffsetX);
147
148 if (!selected)
149 return;
150
151 console_set_cursor(0, console_height() - kHelpLines);
152 console_set_color(kHelpTextColor, kBackgroundColor);
153
154 if (clearHelp) {
155 // clear help text area
156 for (int32 i = 0; i < console_width() - 1; i++)
157 putchar(' ');
158 putchar('\n');
159 for (int32 i = 0; i < console_width() - 1; i++)
160 putchar(' ');
161
162 console_set_cursor(0, console_height() - kHelpLines);
163 }
164
165 if (item->HelpText() != NULL) {
166 // show help text at the bottom of the screen,
167 // center it, and wrap it correctly
168
169 const char *text = item->HelpText();
170 int32 width = console_width() - 2 * kOffsetX;
171 int32 length = strlen(text);
172
173 if (length > width * 2)
174 width += 2 * kOffsetX - 1;
175
176 char* buffer = (char*)malloc(width + 1);
177 if (buffer == NULL)
178 return;
179 buffer[width] = '\0';
180 // make sure the buffer is always terminated
181
182 int32 row = 0;
183
184 for (int32 i = 0; i < length && row < 2; i++) {
185 while (text[i] == ' ')
186 i++;
187
188 // copy as much bytes as possible
189 int32 bytes = width;
190 if (bytes > length - i)
191 bytes = length - i;
192
193 memcpy(buffer, text + i, bytes);
194 buffer[bytes] = '\0';
195
196 char *pos = strchr(buffer, '\n');
197 if (pos != NULL)
198 bytes = pos - buffer;
199 else if (bytes < length - i) {
200 // search for possible line breaks
201 pos = strrchr(buffer, ' ');
202 if (pos != NULL)
203 bytes = pos - buffer;
204 else {
205 // no wrapping possible
206 }
207 }
208
209 i += bytes;
210 buffer[bytes] = '\0';
211 print_centered(console_height() - kHelpLines + row, buffer);
212 row++;
213 }
214
215 free(buffer);
216 }
217 }
218
219
220 static void
draw_menu(Menu * menu)221 draw_menu(Menu *menu)
222 {
223 console_set_color(kTextColor, kBackgroundColor);
224 console_clear_screen();
225
226 print_centered(1, "Welcome to the");
227 print_centered(2, "Haiku Boot Loader");
228
229 console_set_color(kCopyrightColor, kBackgroundColor);
230 print_right(console_height() - 1, get_haiku_revision());
231
232 console_set_color(kCopyrightColor, kBackgroundColor);
233 print_centered(4, "Copyright 2004-2022 Haiku, Inc.");
234
235 if (menu->Title()) {
236 console_set_cursor(kOffsetX, kFirstLine - 2);
237 console_set_color(kTitleColor, kTitleBackgroundColor);
238
239 printf(" %s", menu->Title());
240 print_spacing(console_width() - 1
241 - strlen(menu->Title()) - 2 * kOffsetX);
242 }
243
244 MenuItemIterator iterator = menu->ItemIterator();
245 MenuItem *item;
246 int32 i = 0;
247
248 while ((item = iterator.Next()) != NULL) {
249 if (item->Type() == MENU_ITEM_SEPARATOR) {
250 putchar('\n');
251 i++;
252 continue;
253 }
254
255 print_item_at(i++, item, false);
256 }
257
258 int32 height = menu_height();
259 if (menu->CountItems() >= height) {
260 int32 x = console_width() - kOffsetX;
261 console_set_cursor(x, kFirstLine);
262 console_set_color(kArrowColor, kBackgroundColor);
263 putchar(30/*24*/);
264 height--;
265
266 int32 start = sMenuOffset * height / menu->CountItems();
267 int32 end = (sMenuOffset + height) * height / menu->CountItems();
268
269 for (i = 1; i < height; i++) {
270 console_set_cursor(x, kFirstLine + i);
271 if (i >= start && i <= end)
272 console_set_color(WHITE, kSliderColor);
273 else
274 console_set_color(WHITE, kSliderBackgroundColor);
275
276 putchar(' ');
277 }
278
279 console_set_cursor(x, kFirstLine + i);
280 console_set_color(kArrowColor, kBackgroundColor);
281 putchar(31/*25*/);
282 }
283 }
284
285
286 static int32
first_selectable_item(Menu * menu)287 first_selectable_item(Menu *menu)
288 {
289 int32 index = -1;
290 MenuItem *item;
291
292 while ((item = menu->ItemAt(++index)) != NULL) {
293 if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR)
294 break;
295 }
296
297 return index;
298 }
299
300
301 static int32
last_selectable_item(Menu * menu)302 last_selectable_item(Menu *menu)
303 {
304 int32 index = menu->CountItems();
305 MenuItem *item;
306
307 while ((item = menu->ItemAt(--index)) != NULL) {
308 if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR)
309 break;
310 }
311
312 return index;
313 }
314
315
316 static bool
make_item_visible(Menu * menu,int32 selected)317 make_item_visible(Menu *menu, int32 selected)
318 {
319 if (sMenuOffset > selected
320 || sMenuOffset + menu_height() <= selected) {
321 if (sMenuOffset > selected)
322 sMenuOffset = selected;
323 else
324 sMenuOffset = selected + 1 - menu_height();
325
326 draw_menu(menu);
327 return true;
328 }
329
330 return false;
331 }
332
333
334 static int32
select_previous_valid_item(Menu * menu,int32 selected)335 select_previous_valid_item(Menu *menu, int32 selected)
336 {
337 MenuItem *item;
338 while ((item = menu->ItemAt(selected)) != NULL) {
339 if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR)
340 break;
341
342 selected--;
343 }
344
345 if (selected < 0)
346 return first_selectable_item(menu);
347
348 return selected;
349 }
350
351
352 static int32
select_next_valid_item(Menu * menu,int32 selected)353 select_next_valid_item(Menu *menu, int32 selected)
354 {
355 MenuItem *item;
356 while ((item = menu->ItemAt(selected)) != NULL) {
357 if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR)
358 break;
359
360 selected++;
361 }
362
363 if (selected >= menu->CountItems())
364 return last_selectable_item(menu);
365
366 return selected;
367 }
368
369
370 static bool
invoke_item(Menu * menu,MenuItem * item,int32 & selected,char key)371 invoke_item(Menu* menu, MenuItem* item, int32& selected, char key)
372 {
373 // leave the menu
374 if (item->Submenu() != NULL && key == TEXT_CONSOLE_KEY_RETURN) {
375 int32 offset = sMenuOffset;
376 menu->Hide();
377
378 run_menu(item->Submenu());
379 if (item->Target() != NULL)
380 (*item->Target())(menu, item);
381
382 // restore current menu
383 sMenuOffset = offset;
384 menu->FindSelected(&selected);
385 menu->Show();
386 draw_menu(menu);
387 } else if (item->Type() == MENU_ITEM_MARKABLE) {
388 // toggle state
389 item->SetMarked(!item->IsMarked());
390 print_item_at(selected, item);
391
392 if (item->Target() != NULL)
393 (*item->Target())(menu, item);
394 } else if (key == TEXT_CONSOLE_KEY_RETURN) {
395 // the space key does not exit the menu, only return does
396 if (menu->Type() == CHOICE_MENU
397 && item->Type() != MENU_ITEM_NO_CHOICE
398 && item->Type() != MENU_ITEM_TITLE)
399 item->SetMarked(true);
400
401 if (item->Target() != NULL)
402 (*item->Target())(menu, item);
403 return true;
404 }
405
406 return false;
407 }
408
409
410 static void
run_menu(Menu * menu)411 run_menu(Menu* menu)
412 {
413 sMenuOffset = 0;
414 menu->Entered();
415 menu->Show();
416
417 draw_menu(menu);
418
419 // Get selected entry, or select the last one, if there is none
420 int32 selected;
421 MenuItem *item = menu->FindSelected(&selected);
422 if (item == NULL) {
423 selected = 0;
424 item = menu->ItemAt(selected);
425 if (item != NULL)
426 item->Select(true);
427 }
428
429 make_item_visible(menu, selected);
430
431 while (true) {
432 int key = console_wait_for_key();
433
434 item = menu->ItemAt(selected);
435
436 if (TEXT_CONSOLE_IS_CURSOR_KEY(key) || key == 'j' || key == 'J'
437 || key == 'k' || key == 'K') {
438 if (item == NULL)
439 continue;
440
441 int32 oldSelected = selected;
442
443 switch (key) {
444 case TEXT_CONSOLE_KEY_UP:
445 case 'k':
446 case 'K':
447 selected = select_previous_valid_item(menu, selected - 1);
448 break;
449 case TEXT_CONSOLE_KEY_DOWN:
450 case 'j':
451 case 'J':
452 selected = select_next_valid_item(menu, selected + 1);
453 break;
454 case TEXT_CONSOLE_KEY_PAGE_UP:
455 case TEXT_CONSOLE_KEY_LEFT:
456 selected = select_previous_valid_item(menu,
457 selected - menu_height() + 1);
458 break;
459 case TEXT_CONSOLE_KEY_PAGE_DOWN:
460 case TEXT_CONSOLE_KEY_RIGHT:
461 selected = select_next_valid_item(menu,
462 selected + menu_height() - 1);
463 break;
464 case TEXT_CONSOLE_KEY_HOME:
465 selected = first_selectable_item(menu);
466 break;
467 case TEXT_CONSOLE_KEY_END:
468 selected = last_selectable_item(menu);
469 break;
470 }
471
472 // check if selected has changed
473 if (selected != oldSelected) {
474 MenuItem *item = menu->ItemAt(selected);
475 if (item != NULL)
476 item->Select(true);
477
478 make_item_visible(menu, selected);
479 // make sure that the new selected entry is visible
480 if (sMenuOffset > selected
481 || sMenuOffset + menu_height() <= selected) {
482 if (sMenuOffset > selected)
483 sMenuOffset = selected;
484 else
485 sMenuOffset = selected + 1 - menu_height();
486
487 draw_menu(menu);
488 }
489 }
490 } else if (key == TEXT_CONSOLE_KEY_RETURN
491 || key == TEXT_CONSOLE_KEY_SPACE) {
492 if (item != NULL && invoke_item(menu, item, selected, key))
493 break;
494 } else if (key == '\t') {
495 if (item == NULL)
496 continue;
497
498 int32 oldSelected = selected;
499
500 // Use tab to cycle between items (on some platforms, arrow keys
501 // are not available)
502 selected = select_next_valid_item(menu, selected + 1);
503
504 if (selected == oldSelected)
505 selected = first_selectable_item(menu);
506
507 // check if selected has changed
508 if (selected != oldSelected) {
509 MenuItem *item = menu->ItemAt(selected);
510 if (item != NULL)
511 item->Select(true);
512
513 make_item_visible(menu, selected);
514 // make sure that the new selected entry is visible
515 if (sMenuOffset > selected
516 || sMenuOffset + menu_height() <= selected) {
517 if (sMenuOffset > selected)
518 sMenuOffset = selected;
519 else
520 sMenuOffset = selected + 1 - menu_height();
521
522 draw_menu(menu);
523 }
524 }
525 } else if (key == TEXT_CONSOLE_KEY_ESCAPE
526 && menu->Type() != MAIN_MENU) {
527 // escape key was hit
528 break;
529 } else {
530 // Shortcut processing
531 shortcut_hook function = menu->FindShortcut(key);
532 if (function != NULL)
533 function(key);
534 else {
535 item = menu->FindItemByShortcut(key);
536 if (item != NULL && invoke_item(menu, item, selected,
537 TEXT_CONSOLE_KEY_RETURN)) {
538 break;
539 }
540 }
541 }
542 }
543
544 menu->Hide();
545 menu->Exited();
546 }
547
548
549 // #pragma mark -
550
551
552 void
platform_generic_update_text_menu_item(Menu * menu,MenuItem * item)553 platform_generic_update_text_menu_item(Menu *menu, MenuItem *item)
554 {
555 if (menu->IsHidden())
556 return;
557
558 int32 index = menu->IndexOf(item);
559 if (index == -1)
560 return;
561
562 print_item_at(index, item);
563 }
564
565
566 void
platform_generic_run_text_menu(Menu * menu)567 platform_generic_run_text_menu(Menu *menu)
568 {
569 // platform_switch_to_text_mode();
570
571 run_menu(menu);
572
573 // platform_switch_to_logo();
574 }
575
576
577 size_t
platform_generic_get_user_input_text(Menu * menu,MenuItem * item,char * buffer,size_t bufferSize)578 platform_generic_get_user_input_text(Menu* menu, MenuItem* item, char* buffer,
579 size_t bufferSize)
580 {
581 size_t pos = 0;
582
583 memset(buffer, 0, bufferSize);
584
585 int32 promptLength = strlen(item->Label()) + 2;
586 int32 line = menu->IndexOf(item) - sMenuOffset;
587 if (line < 0 || line >= menu_height())
588 return 0;
589
590 line += kFirstLine;
591 console_set_cursor(kOffsetX, line);
592 int32 x = kOffsetX + 1;
593 console_set_cursor(0, line);
594 console_set_color(kSelectedItemColor, kSelectedItemBackgroundColor);
595 print_spacing(console_width());
596 console_set_color(kTextColor, kBackgroundColor);
597 console_set_cursor(0, line);
598 print_spacing(x);
599 printf(item->Label());
600 printf(": ");
601 x += promptLength;
602 console_set_color(kSelectedItemColor, kSelectedItemBackgroundColor);
603 console_show_cursor();
604 console_set_cursor(x, line);
605
606 int32 scrollOffset = 0;
607 bool doScroll = false;
608 int key = 0;
609 size_t dataLength = 0;
610 while (true) {
611 key = console_wait_for_key();
612 if (key == TEXT_CONSOLE_KEY_RETURN || key == TEXT_CONSOLE_KEY_ESCAPE)
613 break;
614 else if (key >= TEXT_CONSOLE_CURSOR_KEYS_START
615 && key < TEXT_CONSOLE_CURSOR_KEYS_END)
616 {
617 switch (key) {
618 case TEXT_CONSOLE_KEY_LEFT:
619 if (pos > 0)
620 pos--;
621 else if (scrollOffset > 0) {
622 scrollOffset--;
623 doScroll = true;
624 }
625 break;
626 case TEXT_CONSOLE_KEY_RIGHT:
627 if (pos < dataLength) {
628 if (x + (int32)pos == console_width() - 1) {
629 scrollOffset++;
630 doScroll = true;
631 } else
632 pos++;
633 }
634 break;
635 default:
636 break;
637 }
638 } else if (key == TEXT_CONSOLE_KEY_BACKSPACE) {
639 if (pos != 0 || scrollOffset > 0) {
640 if (pos > 0)
641 pos--;
642 else if (scrollOffset > 0)
643 scrollOffset--;
644 dataLength--;
645 int32 offset = pos + scrollOffset;
646 memmove(buffer + offset, buffer + offset + 1, dataLength - offset);
647 console_set_cursor(x + pos, line);
648 putchar(' ');
649 // if this was a mid-line backspace, the line will need to be redrawn
650 if (pos + scrollOffset < dataLength)
651 doScroll = true;
652 }
653 // only accept printable ascii characters
654 } else if (key > 32 || key == TEXT_CONSOLE_KEY_SPACE) {
655 if (pos < (bufferSize - 1)) {
656 buffer[pos + scrollOffset] = key;
657 if (x + (int32)pos < console_width() - 1) {
658 putchar(key);
659 pos++;
660 } else {
661 scrollOffset++;
662 doScroll = true;
663 }
664
665 dataLength++;
666 }
667 }
668
669 if (doScroll) {
670 console_set_cursor(x, line);
671 for (int32 i = x; i < console_width() - 1; i++)
672 putchar(buffer[scrollOffset + i - x]);
673 doScroll = false;
674 }
675 console_set_cursor(x + pos, line);
676 }
677
678 console_hide_cursor();
679 draw_menu(menu);
680
681 return key == TEXT_CONSOLE_KEY_RETURN ? pos : 0;
682 }
683