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