1 /* 2 * Copyright 2004-2009, 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() - 1; i++) 137 putchar(' '); 138 putchar('\n'); 139 for (int32 i = 0; i < console_width() - 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 bytes = pos - buffer; 177 else if (bytes < length - i) { 178 // search for possible line breaks 179 pos = strrchr(buffer, ' '); 180 if (pos != NULL) 181 bytes = pos - buffer; 182 else { 183 // no wrapping possible 184 } 185 } 186 187 i += bytes; 188 buffer[bytes] = '\0'; 189 print_centered(console_height() - kHelpLines + row, buffer); 190 row++; 191 } 192 } 193 } 194 195 196 static void 197 draw_menu(Menu *menu) 198 { 199 console_set_color(kTextColor, kBackgroundColor); 200 console_clear_screen(); 201 202 print_centered(1, "Welcome To The"); 203 print_centered(2, "Haiku Boot Loader"); 204 205 console_set_color(kCopyrightColor, kBackgroundColor); 206 print_centered(4, "Copyright 2004-2009 Haiku Inc."); 207 208 if (menu->Title()) { 209 console_set_cursor(kOffsetX, kFirstLine - 2); 210 console_set_color(kTitleColor, kTitleBackgroundColor); 211 212 printf(" %s", menu->Title()); 213 print_spacing(console_width() - 1 214 - 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 = 0; 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 int key = console_wait_for_key(); 365 366 item = menu->ItemAt(selected); 367 368 if (TEXT_CONSOLE_IS_CURSOR_KEY(key)) { 369 int32 oldSelected = selected; 370 371 switch (key) { 372 case TEXT_CONSOLE_KEY_UP: 373 selected = select_previous_valid_item(menu, selected - 1); 374 break; 375 case TEXT_CONSOLE_KEY_DOWN: 376 selected = select_next_valid_item(menu, selected + 1); 377 break; 378 case TEXT_CONSOLE_KEY_PAGE_UP: 379 case TEXT_CONSOLE_KEY_LEFT: 380 selected = select_previous_valid_item(menu, 381 selected - menu_height() + 1); 382 break; 383 case TEXT_CONSOLE_KEY_PAGE_DOWN: 384 case TEXT_CONSOLE_KEY_RIGHT: 385 selected = select_next_valid_item(menu, 386 selected + menu_height() - 1); 387 break; 388 case TEXT_CONSOLE_KEY_HOME: 389 selected = first_selectable_item(menu); 390 break; 391 case TEXT_CONSOLE_KEY_END: 392 selected = last_selectable_item(menu); 393 break; 394 } 395 396 // check if selected has changed 397 if (selected != oldSelected) { 398 MenuItem *item = menu->ItemAt(selected); 399 if (item != NULL) 400 item->Select(true); 401 402 make_item_visible(menu, selected); 403 // make sure that the new selected entry is visible 404 if (sMenuOffset > selected 405 || sMenuOffset + menu_height() <= selected) { 406 if (sMenuOffset > selected) 407 sMenuOffset = selected; 408 else 409 sMenuOffset = selected + 1 - menu_height(); 410 411 draw_menu(menu); 412 } 413 } 414 } else if (key == TEXT_CONSOLE_KEY_RETURN 415 || key == TEXT_CONSOLE_KEY_SPACE) { 416 // leave the menu 417 if (item->Submenu() != NULL && key == TEXT_CONSOLE_KEY_RETURN) { 418 int32 offset = sMenuOffset; 419 menu->Hide(); 420 421 run_menu(item->Submenu()); 422 if (item->Target() != NULL) 423 (*item->Target())(menu, item); 424 425 // restore current menu 426 sMenuOffset = offset; 427 menu->FindSelected(&selected); 428 menu->Show(); 429 draw_menu(menu); 430 } else if (item->Type() == MENU_ITEM_MARKABLE) { 431 // toggle state 432 item->SetMarked(!item->IsMarked()); 433 print_item_at(selected, item); 434 435 if (item->Target() != NULL) 436 (*item->Target())(menu, item); 437 } else if (key == TEXT_CONSOLE_KEY_RETURN) { 438 // the space key does not exit the menu 439 440 if (menu->Type() == CHOICE_MENU 441 && item->Type() != MENU_ITEM_NO_CHOICE 442 && item->Type() != MENU_ITEM_TITLE) 443 item->SetMarked(true); 444 445 if (item->Target() != NULL) 446 (*item->Target())(menu, item); 447 448 break; 449 } 450 } else if (key == TEXT_CONSOLE_KEY_ESCAPE && menu->Type() != MAIN_MENU) 451 // escape key was hit 452 break; 453 } 454 455 menu->Hide(); 456 } 457 458 459 // #pragma mark - 460 461 462 void 463 platform_generic_update_text_menu_item(Menu *menu, MenuItem *item) 464 { 465 if (menu->IsHidden()) 466 return; 467 468 int32 index = menu->IndexOf(item); 469 if (index == -1) 470 return; 471 472 print_item_at(index, item); 473 } 474 475 476 void 477 platform_generic_run_text_menu(Menu *menu) 478 { 479 // platform_switch_to_text_mode(); 480 481 run_menu(menu); 482 483 // platform_switch_to_logo(); 484 } 485 486