1 /* 2 * Copyright 2004-2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "bios.h" 8 #include "console.h" 9 #include "keyboard.h" 10 #include "smp.h" 11 #include "video.h" 12 13 #include <boot/platform.h> 14 #include <boot/menu.h> 15 #include <safemode.h> 16 17 #include <string.h> 18 19 20 // position 21 static const int32 kFirstLine = 8; 22 static const int32 kOffsetX = 10; 23 static const int32 kHelpLines = 3; 24 25 // colors 26 static const console_color kBackgroundColor = BLACK; 27 static const console_color kTextColor = WHITE; 28 static const console_color kCopyrightColor = CYAN; 29 static const console_color kTitleColor = WHITE; 30 static const console_color kTitleBackgroundColor = RED; 31 static const console_color kHelpTextColor = WHITE; 32 33 static const console_color kItemColor = GRAY; 34 static const console_color kSelectedItemColor = WHITE; 35 static const console_color kItemBackgroundColor = kBackgroundColor; 36 static const console_color kSelectedItemBackgroundColor = GRAY; 37 static const console_color kDisabledColor = DARK_GRAY; 38 39 static const console_color kSliderColor = CYAN; 40 static const console_color kSliderBackgroundColor = DARK_GRAY; 41 static const console_color kArrowColor = GRAY; 42 43 static int32 sMenuOffset = 0; 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) 63 { 64 console_set_cursor(console_width() / 2 - strlen(text) / 2, line); 65 printf("%s", text); 66 67 console_set_cursor(0, 0); 68 // this avoids unwanted line feeds 69 } 70 71 72 static void 73 print_item_at(int32 line, MenuItem *item, bool clearHelp = true) 74 { 75 bool selected = item->IsSelected(); 76 77 line -= sMenuOffset; 78 if (line < 0 || line >= menu_height()) 79 return; 80 81 console_color background = selected ? kSelectedItemBackgroundColor : kItemBackgroundColor; 82 console_color foreground = selected ? kSelectedItemColor : kItemColor; 83 84 if (!item->IsEnabled()) 85 foreground = kDisabledColor; 86 87 console_set_cursor(kOffsetX, line + kFirstLine); 88 console_set_color(foreground, background); 89 90 size_t length = strlen(item->Label()) + 1; 91 92 if (item->Type() == MENU_ITEM_MARKABLE) { 93 console_set_color(DARK_GRAY, background); 94 printf(" ["); 95 console_set_color(foreground, background); 96 printf("%c", item->IsMarked() ? 'x' : ' '); 97 console_set_color(DARK_GRAY, background); 98 printf("] "); 99 console_set_color(foreground, background); 100 101 length += 4; 102 } else 103 printf(" "); 104 105 printf(item->Label()); 106 107 if (item->Submenu() && item->Submenu()->Type() == CHOICE_MENU) { 108 // show the current choice (if any) 109 const char *text = " (Current: "; 110 printf(text); 111 length += strlen(text); 112 113 Menu *subMenu = item->Submenu(); 114 if (subMenu->ChoiceText() != NULL) 115 text = subMenu->ChoiceText(); 116 else 117 text = "None"; 118 length += strlen(text); 119 120 console_set_color(selected ? DARK_GRAY : WHITE, background); 121 122 printf(text); 123 124 console_set_color(foreground, background); 125 putchar(')'); 126 length++; 127 } 128 129 print_spacing(console_width() - length - 2*kOffsetX); 130 131 if (!selected) 132 return; 133 134 console_set_cursor(0, console_height() - kHelpLines); 135 console_set_color(kHelpTextColor, kBackgroundColor); 136 137 if (clearHelp) { 138 // clear help text area 139 for (int32 i = 0; i < console_width() * 2 - 1; i++) 140 putchar(' '); 141 142 console_set_cursor(0, console_height() - kHelpLines); 143 } 144 145 if (item->HelpText() != NULL) { 146 // show help text at the bottom of the screen, 147 // center it, and wrap it correctly 148 149 const char *text = item->HelpText(); 150 int32 width = console_width() - 2 * kOffsetX; 151 int32 length = strlen(text); 152 153 if (length > width * 2) 154 width += 2 * kOffsetX - 1; 155 156 char buffer[width + 1]; 157 buffer[width] = '\0'; 158 // make sure the buffer is always terminated 159 160 int32 row = 0; 161 162 for (int32 i = 0; i < length && row < 2; i++) { 163 while (text[i] == ' ') 164 i++; 165 166 // copy as much bytes as possible 167 int32 bytes = width; 168 if (bytes > length - i) 169 bytes = length - i; 170 171 memcpy(buffer, text + i, bytes); 172 buffer[bytes] = '\0'; 173 174 char *pos = strchr(buffer, '\n'); 175 if (pos != NULL) 176 i = pos - buffer; 177 else if (bytes < length - i) { 178 // search for possible line breaks 179 pos = strrchr(buffer, ' '); 180 if (pos != NULL) 181 i = pos - buffer; 182 else { 183 // no wrapping possible 184 i += bytes; 185 } 186 } else 187 i += bytes; 188 189 buffer[i] = '\0'; 190 print_centered(console_height() - kHelpLines + row, buffer); 191 row++; 192 } 193 } 194 } 195 196 197 static void 198 draw_menu(Menu *menu) 199 { 200 console_set_color(kTextColor, kBackgroundColor); 201 console_clear_screen(); 202 203 print_centered(1, "Welcome To The"); 204 print_centered(2, "Haiku Boot Loader"); 205 206 console_set_color(kCopyrightColor, kBackgroundColor); 207 print_centered(4, "Copyright 2004-2005 Haiku Inc."); 208 209 if (menu->Title()) { 210 console_set_cursor(kOffsetX, kFirstLine - 2); 211 console_set_color(kTitleColor, kTitleBackgroundColor); 212 213 printf(" %s", menu->Title()); 214 print_spacing(console_width() - 1 - strlen(menu->Title()) - 2*kOffsetX); 215 } 216 217 MenuItemIterator iterator = menu->ItemIterator(); 218 MenuItem *item; 219 int32 i = 0; 220 221 while ((item = iterator.Next()) != NULL) { 222 if (item->Type() == MENU_ITEM_SEPARATOR) { 223 putchar('\n'); 224 i++; 225 continue; 226 } 227 228 print_item_at(i++, item, false); 229 } 230 231 int32 height = menu_height(); 232 if (menu->CountItems() >= height) { 233 int32 x = console_width() - kOffsetX; 234 console_set_cursor(x, kFirstLine); 235 console_set_color(kArrowColor, kBackgroundColor); 236 putchar(30/*24*/); 237 height--; 238 239 int32 start = sMenuOffset * height / menu->CountItems(); 240 int32 end = (sMenuOffset + height) * height / menu->CountItems(); 241 242 for (i = 1; i < height; i++) { 243 console_set_cursor(x, kFirstLine + i); 244 if (i >= start && i <= end) 245 console_set_color(WHITE, kSliderColor); 246 else 247 console_set_color(WHITE, kSliderBackgroundColor); 248 249 putchar(' '); 250 } 251 252 console_set_cursor(x, kFirstLine + i); 253 console_set_color(kArrowColor, kBackgroundColor); 254 putchar(31/*25*/); 255 } 256 } 257 258 259 static int32 260 first_selectable_item(Menu *menu) 261 { 262 int32 index = -1; 263 MenuItem *item; 264 265 while ((item = menu->ItemAt(++index)) != NULL) { 266 if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR) 267 break; 268 } 269 270 return index; 271 } 272 273 274 static int32 275 last_selectable_item(Menu *menu) 276 { 277 int32 index = menu->CountItems(); 278 MenuItem *item; 279 280 while ((item = menu->ItemAt(--index)) != NULL) { 281 if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR) 282 break; 283 } 284 285 return index; 286 } 287 288 289 static bool 290 make_item_visible(Menu *menu, int32 selected) 291 { 292 if (sMenuOffset > selected 293 || sMenuOffset + menu_height() <= selected) { 294 if (sMenuOffset > selected) 295 sMenuOffset = selected; 296 else 297 sMenuOffset = selected + 1 - menu_height(); 298 299 draw_menu(menu); 300 return true; 301 } 302 303 return false; 304 } 305 306 307 static int32 308 select_previous_valid_item(Menu *menu, int32 selected) 309 { 310 MenuItem *item; 311 while ((item = menu->ItemAt(selected)) != NULL) { 312 if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR) 313 break; 314 315 selected--; 316 } 317 318 if (selected < 0) 319 return first_selectable_item(menu); 320 321 return selected; 322 } 323 324 325 static int32 326 select_next_valid_item(Menu *menu, int32 selected) 327 { 328 MenuItem *item; 329 while ((item = menu->ItemAt(selected)) != NULL) { 330 if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR) 331 break; 332 333 selected++; 334 } 335 336 if (selected >= menu->CountItems()) 337 return last_selectable_item(menu); 338 339 return selected; 340 } 341 342 343 static void 344 run_menu(Menu *menu) 345 { 346 sMenuOffset = 0; 347 menu->Show(); 348 349 draw_menu(menu); 350 351 // Get selected entry, or select the last one, if there is none 352 int32 selected; 353 MenuItem *item = menu->FindSelected(&selected); 354 if (item == NULL) { 355 selected = menu->CountItems() - 1; 356 item = menu->ItemAt(selected); 357 if (item != NULL) 358 item->Select(true); 359 } 360 361 make_item_visible(menu, selected); 362 363 while (true) { 364 union key key = wait_for_key(); 365 366 item = menu->ItemAt(selected); 367 368 if (key.code.ascii == 0) { 369 int32 oldSelected = selected; 370 371 switch (key.code.bios) { 372 case BIOS_KEY_UP: 373 selected = select_previous_valid_item(menu, selected - 1); 374 break; 375 case BIOS_KEY_DOWN: 376 selected = select_next_valid_item(menu, selected + 1); 377 break; 378 case BIOS_KEY_PAGE_UP: 379 selected = select_previous_valid_item(menu, selected - menu_height() + 1); 380 break; 381 case BIOS_KEY_PAGE_DOWN: 382 selected = select_next_valid_item(menu, selected + menu_height() - 1); 383 break; 384 case BIOS_KEY_HOME: 385 selected = first_selectable_item(menu); 386 break; 387 case BIOS_KEY_END: 388 selected = last_selectable_item(menu); 389 break; 390 } 391 392 // check if selected has changed 393 if (selected != oldSelected) { 394 MenuItem *item = menu->ItemAt(selected); 395 if (item != NULL) 396 item->Select(true); 397 398 make_item_visible(menu, selected); 399 // make sure that the new selected entry is visible 400 if (sMenuOffset > selected 401 || sMenuOffset + menu_height() <= selected) { 402 if (sMenuOffset > selected) 403 sMenuOffset = selected; 404 else 405 sMenuOffset = selected + 1 - menu_height(); 406 407 draw_menu(menu); 408 } 409 } 410 } else if (key.code.ascii == 0xd || key.code.ascii == ' ') { 411 // leave the menu 412 if (item->Submenu() != NULL) { 413 int32 offset = sMenuOffset; 414 menu->Hide(); 415 416 run_menu(item->Submenu()); 417 if (item->Target() != NULL) 418 (*item->Target())(menu, item); 419 420 // restore current menu 421 sMenuOffset = offset; 422 menu->FindSelected(&selected); 423 menu->Show(); 424 draw_menu(menu); 425 } else if (item->Type() == MENU_ITEM_MARKABLE) { 426 // toggle state 427 item->SetMarked(!item->IsMarked()); 428 print_item_at(selected, item); 429 430 if (item->Target() != NULL) 431 (*item->Target())(menu, item); 432 } else if (key.code.ascii == 0xd) { 433 // the space key does not exit the menu 434 435 if (menu->Type() == CHOICE_MENU 436 && item->Type() != MENU_ITEM_NO_CHOICE 437 && item->Type() != MENU_ITEM_TITLE) 438 item->SetMarked(true); 439 440 if (item->Target() != NULL) 441 (*item->Target())(menu, item); 442 443 break; 444 } 445 } else if (key.code.ascii == 0x1b && menu->Type() != MAIN_MENU) 446 // escape key was hit 447 break; 448 } 449 450 menu->Hide(); 451 } 452 453 454 // #pragma mark - 455 456 457 void 458 platform_add_menus(Menu *menu) 459 { 460 MenuItem *item; 461 462 switch (menu->Type()) { 463 case MAIN_MENU: 464 menu->AddItem(item = new MenuItem("Select fail-safe video mode", video_mode_menu())); 465 item->SetTarget(video_mode_hook); 466 break; 467 case SAFE_MODE_MENU: 468 smp_add_safemode_menus(menu); 469 470 menu->AddItem(item = new MenuItem("Don't call the BIOS")); 471 item->SetType(MENU_ITEM_MARKABLE); 472 break; 473 default: 474 break; 475 } 476 } 477 478 479 void 480 platform_update_menu_item(Menu *menu, MenuItem *item) 481 { 482 if (menu->IsHidden()) 483 return; 484 485 int32 index = menu->IndexOf(item); 486 if (index == -1) 487 return; 488 489 print_item_at(index, item); 490 } 491 492 493 void 494 platform_run_menu(Menu *menu) 495 { 496 // platform_switch_to_text_mode(); 497 498 run_menu(menu); 499 500 // platform_switch_to_logo(); 501 } 502 503