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 47 menu_height() 48 { 49 return console_height() - kFirstLine - 1 - kHelpLines; 50 } 51 52 53 static void 54 print_spacing(int32 count) 55 { 56 for (int32 i = 0; i < count; i++) 57 putchar(' '); 58 } 59 60 61 static void 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 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 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 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 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 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 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 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 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 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 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 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 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 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