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