/* * Copyright 2004-2010, Axel Dörfler, axeld@pinc-software.de. * Copyright 2011, Rene Gollent, rene@gollent.com. * Distributed under the terms of the MIT License. */ #include #include #include #include #include #include // position static const int32 kFirstLine = 8; static const int32 kOffsetX = 10; static const int32 kHelpLines = 3; // colors static const console_color kBackgroundColor = BLACK; static const console_color kTextColor = WHITE; static const console_color kCopyrightColor = CYAN; static const console_color kTitleColor = YELLOW; static const console_color kTitleBackgroundColor = kBackgroundColor; static const console_color kHelpTextColor = WHITE; static const console_color kItemColor = GRAY; static const console_color kSelectedItemColor = WHITE; static const console_color kItemBackgroundColor = kBackgroundColor; static const console_color kSelectedItemBackgroundColor = GRAY; static const console_color kDisabledColor = DARK_GRAY; static const console_color kSliderColor = CYAN; static const console_color kSliderBackgroundColor = DARK_GRAY; static const console_color kArrowColor = GRAY; static int32 sMenuOffset = 0; static void run_menu(Menu* menu); static int32 menu_height() { return console_height() - kFirstLine - 1 - kHelpLines; } static void print_spacing(int32 count) { for (int32 i = 0; i < count; i++) putchar(' '); } static void print_centered(int32 line, const char *text, bool resetPosition = true) { console_set_cursor(console_width() / 2 - strlen(text) / 2, line); printf("%s", text); if (resetPosition) { console_set_cursor(0, 0); // this avoids unwanted line feeds } } static void print_right(int32 line, const char *text, bool resetPosition = true) { console_set_cursor(console_width() - (strlen(text) + 1), line); printf("%s", text); if (resetPosition) { console_set_cursor(0, 0); // this avoids unwanted line feeds } } static void print_item_at(int32 line, MenuItem *item, bool clearHelp = true) { bool selected = item->IsSelected(); line -= sMenuOffset; if (line < 0 || line >= menu_height()) return; console_color background = selected ? kSelectedItemBackgroundColor : kItemBackgroundColor; console_color foreground = selected ? kSelectedItemColor : kItemColor; if (!item->IsEnabled()) foreground = kDisabledColor; console_set_cursor(kOffsetX, line + kFirstLine); console_set_color(foreground, background); size_t length = strlen(item->Label()) + 1; if (item->Type() == MENU_ITEM_MARKABLE) { console_set_color(DARK_GRAY, background); printf(" ["); console_set_color(foreground, background); printf("%c", item->IsMarked() ? 'x' : ' '); console_set_color(DARK_GRAY, background); printf("] "); console_set_color(foreground, background); length += 4; } else printf(" "); printf(item->Label()); if (item->Submenu() && item->Submenu()->Type() == CHOICE_MENU) { // show the current choice (if any) const char *text = " (Current: "; printf(text); length += strlen(text); Menu *subMenu = item->Submenu(); if (subMenu->ChoiceText() != NULL) text = subMenu->ChoiceText(); else text = "None"; length += strlen(text); console_set_color(selected ? DARK_GRAY : WHITE, background); printf(text); console_set_color(foreground, background); putchar(')'); length++; } print_spacing(console_width() - length - 2*kOffsetX); if (!selected) return; console_set_cursor(0, console_height() - kHelpLines); console_set_color(kHelpTextColor, kBackgroundColor); if (clearHelp) { // clear help text area for (int32 i = 0; i < console_width() - 1; i++) putchar(' '); putchar('\n'); for (int32 i = 0; i < console_width() - 1; i++) putchar(' '); console_set_cursor(0, console_height() - kHelpLines); } if (item->HelpText() != NULL) { // show help text at the bottom of the screen, // center it, and wrap it correctly const char *text = item->HelpText(); int32 width = console_width() - 2 * kOffsetX; int32 length = strlen(text); if (length > width * 2) width += 2 * kOffsetX - 1; char* buffer = (char*)malloc(width + 1); if (buffer == NULL) return; buffer[width] = '\0'; // make sure the buffer is always terminated int32 row = 0; for (int32 i = 0; i < length && row < 2; i++) { while (text[i] == ' ') i++; // copy as much bytes as possible int32 bytes = width; if (bytes > length - i) bytes = length - i; memcpy(buffer, text + i, bytes); buffer[bytes] = '\0'; char *pos = strchr(buffer, '\n'); if (pos != NULL) bytes = pos - buffer; else if (bytes < length - i) { // search for possible line breaks pos = strrchr(buffer, ' '); if (pos != NULL) bytes = pos - buffer; else { // no wrapping possible } } i += bytes; buffer[bytes] = '\0'; print_centered(console_height() - kHelpLines + row, buffer); row++; } free(buffer); } } static void draw_menu(Menu *menu) { console_set_color(kTextColor, kBackgroundColor); console_clear_screen(); print_centered(1, "Welcome to the"); print_centered(2, "Haiku Boot Loader"); console_set_color(kCopyrightColor, kBackgroundColor); print_right(console_height() - 1, get_haiku_revision()); console_set_color(kCopyrightColor, kBackgroundColor); print_centered(4, "Copyright 2004-2022 Haiku, Inc."); if (menu->Title()) { console_set_cursor(kOffsetX, kFirstLine - 2); console_set_color(kTitleColor, kTitleBackgroundColor); printf(" %s", menu->Title()); print_spacing(console_width() - 1 - strlen(menu->Title()) - 2 * kOffsetX); } MenuItemIterator iterator = menu->ItemIterator(); MenuItem *item; int32 i = 0; while ((item = iterator.Next()) != NULL) { if (item->Type() == MENU_ITEM_SEPARATOR) { putchar('\n'); i++; continue; } print_item_at(i++, item, false); } int32 height = menu_height(); if (menu->CountItems() >= height) { int32 x = console_width() - kOffsetX; console_set_cursor(x, kFirstLine); console_set_color(kArrowColor, kBackgroundColor); putchar(30/*24*/); height--; int32 start = sMenuOffset * height / menu->CountItems(); int32 end = (sMenuOffset + height) * height / menu->CountItems(); for (i = 1; i < height; i++) { console_set_cursor(x, kFirstLine + i); if (i >= start && i <= end) console_set_color(WHITE, kSliderColor); else console_set_color(WHITE, kSliderBackgroundColor); putchar(' '); } console_set_cursor(x, kFirstLine + i); console_set_color(kArrowColor, kBackgroundColor); putchar(31/*25*/); } } static int32 first_selectable_item(Menu *menu) { int32 index = -1; MenuItem *item; while ((item = menu->ItemAt(++index)) != NULL) { if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR) break; } return index; } static int32 last_selectable_item(Menu *menu) { int32 index = menu->CountItems(); MenuItem *item; while ((item = menu->ItemAt(--index)) != NULL) { if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR) break; } return index; } static bool make_item_visible(Menu *menu, int32 selected) { if (sMenuOffset > selected || sMenuOffset + menu_height() <= selected) { if (sMenuOffset > selected) sMenuOffset = selected; else sMenuOffset = selected + 1 - menu_height(); draw_menu(menu); return true; } return false; } static int32 select_previous_valid_item(Menu *menu, int32 selected) { MenuItem *item; while ((item = menu->ItemAt(selected)) != NULL) { if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR) break; selected--; } if (selected < 0) return first_selectable_item(menu); return selected; } static int32 select_next_valid_item(Menu *menu, int32 selected) { MenuItem *item; while ((item = menu->ItemAt(selected)) != NULL) { if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR) break; selected++; } if (selected >= menu->CountItems()) return last_selectable_item(menu); return selected; } static bool invoke_item(Menu* menu, MenuItem* item, int32& selected, char key) { // leave the menu if (item->Submenu() != NULL && key == TEXT_CONSOLE_KEY_RETURN) { int32 offset = sMenuOffset; menu->Hide(); run_menu(item->Submenu()); if (item->Target() != NULL) (*item->Target())(menu, item); // restore current menu sMenuOffset = offset; menu->FindSelected(&selected); menu->Show(); draw_menu(menu); } else if (item->Type() == MENU_ITEM_MARKABLE) { // toggle state item->SetMarked(!item->IsMarked()); print_item_at(selected, item); if (item->Target() != NULL) (*item->Target())(menu, item); } else if (key == TEXT_CONSOLE_KEY_RETURN) { // the space key does not exit the menu, only return does if (menu->Type() == CHOICE_MENU && item->Type() != MENU_ITEM_NO_CHOICE && item->Type() != MENU_ITEM_TITLE) item->SetMarked(true); if (item->Target() != NULL) (*item->Target())(menu, item); return true; } return false; } static void run_menu(Menu* menu) { sMenuOffset = 0; menu->Entered(); menu->Show(); draw_menu(menu); // Get selected entry, or select the last one, if there is none int32 selected; MenuItem *item = menu->FindSelected(&selected); if (item == NULL) { selected = 0; item = menu->ItemAt(selected); if (item != NULL) item->Select(true); } make_item_visible(menu, selected); while (true) { int key = console_wait_for_key(); item = menu->ItemAt(selected); if (TEXT_CONSOLE_IS_CURSOR_KEY(key) || key == 'j' || key == 'J' || key == 'k' || key == 'K') { if (item == NULL) continue; int32 oldSelected = selected; switch (key) { case TEXT_CONSOLE_KEY_UP: case 'k': case 'K': selected = select_previous_valid_item(menu, selected - 1); break; case TEXT_CONSOLE_KEY_DOWN: case 'j': case 'J': selected = select_next_valid_item(menu, selected + 1); break; case TEXT_CONSOLE_KEY_PAGE_UP: case TEXT_CONSOLE_KEY_LEFT: selected = select_previous_valid_item(menu, selected - menu_height() + 1); break; case TEXT_CONSOLE_KEY_PAGE_DOWN: case TEXT_CONSOLE_KEY_RIGHT: selected = select_next_valid_item(menu, selected + menu_height() - 1); break; case TEXT_CONSOLE_KEY_HOME: selected = first_selectable_item(menu); break; case TEXT_CONSOLE_KEY_END: selected = last_selectable_item(menu); break; } // check if selected has changed if (selected != oldSelected) { MenuItem *item = menu->ItemAt(selected); if (item != NULL) item->Select(true); make_item_visible(menu, selected); // make sure that the new selected entry is visible if (sMenuOffset > selected || sMenuOffset + menu_height() <= selected) { if (sMenuOffset > selected) sMenuOffset = selected; else sMenuOffset = selected + 1 - menu_height(); draw_menu(menu); } } } else if (key == TEXT_CONSOLE_KEY_RETURN || key == TEXT_CONSOLE_KEY_SPACE) { if (item != NULL && invoke_item(menu, item, selected, key)) break; } else if (key == '\t') { if (item == NULL) continue; int32 oldSelected = selected; // Use tab to cycle between items (on some platforms, arrow keys // are not available) selected = select_next_valid_item(menu, selected + 1); if (selected == oldSelected) selected = first_selectable_item(menu); // check if selected has changed if (selected != oldSelected) { MenuItem *item = menu->ItemAt(selected); if (item != NULL) item->Select(true); make_item_visible(menu, selected); // make sure that the new selected entry is visible if (sMenuOffset > selected || sMenuOffset + menu_height() <= selected) { if (sMenuOffset > selected) sMenuOffset = selected; else sMenuOffset = selected + 1 - menu_height(); draw_menu(menu); } } } else if (key == TEXT_CONSOLE_KEY_ESCAPE && menu->Type() != MAIN_MENU) { // escape key was hit break; } else { // Shortcut processing shortcut_hook function = menu->FindShortcut(key); if (function != NULL) function(key); else { item = menu->FindItemByShortcut(key); if (item != NULL && invoke_item(menu, item, selected, TEXT_CONSOLE_KEY_RETURN)) { break; } } } } menu->Hide(); menu->Exited(); } // #pragma mark - void platform_generic_update_text_menu_item(Menu *menu, MenuItem *item) { if (menu->IsHidden()) return; int32 index = menu->IndexOf(item); if (index == -1) return; print_item_at(index, item); } void platform_generic_run_text_menu(Menu *menu) { // platform_switch_to_text_mode(); run_menu(menu); // platform_switch_to_logo(); } size_t platform_generic_get_user_input_text(Menu* menu, MenuItem* item, char* buffer, size_t bufferSize) { size_t pos = 0; memset(buffer, 0, bufferSize); int32 promptLength = strlen(item->Label()) + 2; int32 line = menu->IndexOf(item) - sMenuOffset; if (line < 0 || line >= menu_height()) return 0; line += kFirstLine; console_set_cursor(kOffsetX, line); int32 x = kOffsetX + 1; console_set_cursor(0, line); console_set_color(kSelectedItemColor, kSelectedItemBackgroundColor); print_spacing(console_width()); console_set_color(kTextColor, kBackgroundColor); console_set_cursor(0, line); print_spacing(x); printf(item->Label()); printf(": "); x += promptLength; console_set_color(kSelectedItemColor, kSelectedItemBackgroundColor); console_show_cursor(); console_set_cursor(x, line); int32 scrollOffset = 0; bool doScroll = false; int key = 0; size_t dataLength = 0; while (true) { key = console_wait_for_key(); if (key == TEXT_CONSOLE_KEY_RETURN || key == TEXT_CONSOLE_KEY_ESCAPE) break; else if (key >= TEXT_CONSOLE_CURSOR_KEYS_START && key < TEXT_CONSOLE_CURSOR_KEYS_END) { switch (key) { case TEXT_CONSOLE_KEY_LEFT: if (pos > 0) pos--; else if (scrollOffset > 0) { scrollOffset--; doScroll = true; } break; case TEXT_CONSOLE_KEY_RIGHT: if (pos < dataLength) { if (x + (int32)pos == console_width() - 1) { scrollOffset++; doScroll = true; } else pos++; } break; default: break; } } else if (key == TEXT_CONSOLE_KEY_BACKSPACE) { if (pos != 0 || scrollOffset > 0) { if (pos > 0) pos--; else if (scrollOffset > 0) scrollOffset--; dataLength--; int32 offset = pos + scrollOffset; memmove(buffer + offset, buffer + offset + 1, dataLength - offset); console_set_cursor(x + pos, line); putchar(' '); // if this was a mid-line backspace, the line will need to be redrawn if (pos + scrollOffset < dataLength) doScroll = true; } // only accept printable ascii characters } else if (key > 32 || key == TEXT_CONSOLE_KEY_SPACE) { if (pos < (bufferSize - 1)) { buffer[pos + scrollOffset] = key; if (x + (int32)pos < console_width() - 1) { putchar(key); pos++; } else { scrollOffset++; doScroll = true; } dataLength++; } } if (doScroll) { console_set_cursor(x, line); for (int32 i = x; i < console_width() - 1; i++) putchar(buffer[scrollOffset + i - x]); doScroll = false; } console_set_cursor(x + pos, line); } console_hide_cursor(); draw_menu(menu); return key == TEXT_CONSOLE_KEY_RETURN ? pos : 0; }