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