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