| /* |
| * BRLTTY - A background process providing access to the console screen (when in |
| * text mode) for a blind person using a refreshable braille display. |
| * |
| * Copyright (C) 1995-2023 by The BRLTTY Developers. |
| * |
| * BRLTTY comes with ABSOLUTELY NO WARRANTY. |
| * |
| * This is free software, placed under the terms of the |
| * GNU Lesser General Public License, as published by the Free Software |
| * Foundation; either version 2.1 of the License, or (at your option) any |
| * later version. Please see the file LICENSE-LGPL for details. |
| * |
| * Web Page: http://brltty.app/ |
| * |
| * This software is maintained by Dave Mielke <dave@mielke.cc>. |
| */ |
| |
| #include "prologue.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include "parameters.h" |
| #include "log.h" |
| #include "strfmt.h" |
| #include "scr.h" |
| #include "scr_special.h" |
| #include "scr_menu.h" |
| #include "update.h" |
| #include "brl_cmds.h" |
| #include "cmd_queue.h" |
| #include "alert.h" |
| #include "utf8.h" |
| #include "core.h" |
| |
| typedef struct { |
| MenuItem *item; |
| size_t length; |
| size_t settingIndent; |
| wchar_t text[0]; |
| } RenderedMenuItem; |
| |
| static Menu *rootMenu = NULL; |
| |
| static int screenUpdated; |
| static RenderedMenuItem **screenLines; |
| static unsigned int lineCount; |
| static unsigned int screenHeight; |
| |
| static Menu *screenMenu; |
| static unsigned int screenColumn; |
| static unsigned int screenRow; |
| static unsigned int screenWidth; |
| |
| static inline const MenuItem * |
| getCurrentItem (void) { |
| Menu *menu = screenMenu; |
| |
| return getMenuItem(menu, getMenuIndex(menu)); |
| } |
| |
| static inline void |
| setFocusedItem (void) { |
| changeMenuItem(screenLines[ses->winy]->item); |
| } |
| |
| static RenderedMenuItem * |
| newRenderedMenuItem (Menu *menu) { |
| MenuItem *item = getCurrentMenuItem(menu); |
| |
| char labelString[0X100]; |
| size_t labelLength; |
| { |
| const char *title = getMenuItemTitle(item); |
| const char *subtitle = getMenuItemSubtitle(item); |
| |
| STR_BEGIN(labelString, ARRAY_COUNT(labelString)); |
| STR_PRINTF("%s", title); |
| |
| if (*subtitle) { |
| if (*title) STR_PRINTF(" "); |
| STR_PRINTF("%s", subtitle); |
| } |
| |
| if (labelString[0]) { |
| if (!isMenuItemAction(item)) STR_PRINTF(":"); |
| STR_PRINTF(" "); |
| } |
| |
| labelLength = STR_LENGTH; |
| STR_END; |
| } |
| |
| char settingString[0X100]; |
| size_t settingLength; |
| { |
| const char *text = getMenuItemText(item); |
| const char *comment = getMenuItemComment(item); |
| |
| if (!text) { |
| text = ""; |
| } else if (!*text) { |
| text = gettext("<off>"); |
| } |
| |
| STR_BEGIN(settingString, ARRAY_COUNT(settingString)); |
| STR_PRINTF("%s", text); |
| if (*comment) STR_PRINTF(" (%s)", comment); |
| settingLength = STR_LENGTH; |
| STR_END; |
| } |
| |
| { |
| size_t maximumLength = labelLength + settingLength; |
| wchar_t characters[maximumLength]; |
| |
| size_t settingIndent; |
| size_t actualLength; |
| |
| { |
| size_t currentLength = 0; |
| |
| currentLength += makeWcharsFromUtf8(labelString, &characters[currentLength], maximumLength-currentLength); |
| settingIndent = currentLength; |
| |
| currentLength += makeWcharsFromUtf8(settingString, &characters[currentLength], maximumLength-currentLength); |
| actualLength = currentLength; |
| } |
| |
| { |
| RenderedMenuItem *rmi; |
| size_t size = sizeof(*rmi) + (actualLength * sizeof(rmi->text[0])); |
| |
| if ((rmi = malloc(size))) { |
| memset(rmi, 0, sizeof(*rmi)); |
| wmemcpy(rmi->text, characters, actualLength); |
| |
| rmi->item = item; |
| rmi->length = actualLength; |
| rmi->settingIndent = settingIndent; |
| |
| return rmi; |
| } else { |
| logMallocError(); |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static void |
| destroyRenderedMenuItem (RenderedMenuItem *rmi) { |
| free(rmi); |
| } |
| |
| static void |
| removeLines (void) { |
| while (screenHeight > 0) { |
| destroyRenderedMenuItem(screenLines[--screenHeight]); |
| } |
| } |
| |
| static int |
| setScreenRow (void) { |
| const MenuItem *item = getCurrentItem(); |
| |
| for (unsigned int row=0; row<screenHeight; row+=1) { |
| const RenderedMenuItem *rmi = screenLines[row]; |
| |
| if (rmi->item == item) { |
| screenRow = row; |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int |
| reloadScreen (int constructing) { |
| Menu *menu = getCurrentSubmenu(rootMenu); |
| unsigned int index = getMenuIndex(menu); |
| |
| screenMenu = menu; |
| screenWidth = 0; |
| removeLines(); |
| |
| if (changeMenuItemFirst(menu)) { |
| { |
| unsigned int count = getMenuSize(menu); |
| |
| if (count > lineCount) { |
| RenderedMenuItem **lines = realloc(screenLines, ARRAY_SIZE(screenLines, count)); |
| |
| if (!lines) { |
| logMallocError(); |
| return 0; |
| } |
| |
| screenLines = lines; |
| lineCount = count; |
| } |
| } |
| |
| do { |
| RenderedMenuItem *rmi = newRenderedMenuItem(menu); |
| |
| if (!rmi) return 0; |
| screenLines[screenHeight++] = rmi; |
| if (screenWidth < rmi->length) screenWidth = rmi->length; |
| } while (changeMenuItemNext(menu, 0)); |
| |
| if (changeMenuItemIndex(menu, index)) { |
| if (constructing) { |
| screenRow = 0; |
| screenColumn = 0; |
| } else { |
| if (!setScreenRow()) return 0; |
| screenColumn = screenLines[screenRow]->settingIndent; |
| } |
| |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int |
| refresh_MenuScreen (void) { |
| if (screenUpdated) { |
| if (!reloadScreen(0)) return 0; |
| screenUpdated = 0; |
| } |
| |
| return 1; |
| } |
| |
| static int |
| construct_MenuScreen (Menu *menu) { |
| rootMenu = menu; |
| |
| screenUpdated = 0; |
| screenLines = NULL; |
| lineCount = 0; |
| screenHeight = 0; |
| |
| return reloadScreen(1); |
| } |
| |
| static void |
| destruct_MenuScreen (void) { |
| if (screenLines) { |
| removeLines(); |
| free(screenLines); |
| screenLines = NULL; |
| } |
| |
| rootMenu = NULL; |
| } |
| |
| static int |
| currentVirtualTerminal_MenuScreen (void) { |
| return userVirtualTerminal(2 + getMenuNumber(screenMenu)); |
| } |
| |
| static const char * |
| getTitle_MenuScreen (void) { |
| return gettext("Preferences Menu"); |
| } |
| |
| static void |
| describe_MenuScreen (ScreenDescription *description) { |
| description->cols = MAX(screenWidth, 1); |
| description->rows = MAX(screenHeight, 1); |
| |
| description->posx = screenColumn; |
| description->posy = screenRow; |
| |
| description->number = currentVirtualTerminal_MenuScreen(); |
| } |
| |
| static int |
| readCharacters_MenuScreen (const ScreenBox *box, ScreenCharacter *buffer) { |
| if (validateScreenBox(box, screenWidth, screenHeight)) { |
| ScreenCharacter *character = buffer; |
| |
| for (unsigned int row=0; row<box->height; row+=1) { |
| const RenderedMenuItem *rmi = screenLines[row + box->top]; |
| |
| for (unsigned int column=0; column<box->width; column+=1) { |
| unsigned int index = column + box->left; |
| |
| character->text = (index < rmi->length)? rmi->text[index]: WC_C(' '); |
| character->attributes = SCR_COLOUR_DEFAULT; |
| character += 1; |
| } |
| } |
| |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static void |
| commandRejected (void) { |
| alert(ALERT_COMMAND_REJECTED); |
| } |
| |
| static void |
| itemChanged (void) { |
| setScreenRow(); |
| screenColumn = 0; |
| } |
| |
| static void |
| settingChanged (void) { |
| screenUpdated = 1; |
| } |
| |
| void |
| menuScreenUpdated (void) { |
| screenUpdated = 1; |
| |
| if (isSpecialScreen(SCR_MENU)) { |
| scheduleUpdateIn("menu screen updated", SCREEN_UPDATE_SCHEDULE_DELAY); |
| } |
| } |
| |
| static int |
| handleCommand_MenuScreen (int command) { |
| switch (command) { |
| case BRL_CMD_KEY(BACKSPACE): |
| case BRL_CMD_MENU_PREV_LEVEL: { |
| Menu *menu = screenMenu; |
| |
| if (menu != rootMenu) { |
| if (!changeMenuItemIndex(screenMenu, 0)) { |
| commandRejected(); |
| } else if (!changeMenuSettingNext(menu, 0)) { |
| commandRejected(); |
| } else { |
| setFocusedItem(); |
| settingChanged(); |
| } |
| |
| return 1; |
| } |
| } |
| /* fall through */ |
| case BRL_CMD_KEY(ESCAPE): |
| case BRL_CMD_KEY(ENTER): { |
| int handled = handleCommand(BRL_CMD_PREFMENU); |
| if (handled) setFocusedItem(); |
| return handled; |
| } |
| |
| case BRL_CMD_KEY(HOME): { |
| int handled = handleCommand(BRL_CMD_PREFLOAD); |
| |
| if (handled) settingChanged(); |
| return handled; |
| } |
| |
| case BRL_CMD_KEY(END): { |
| int handled = handleCommand(BRL_CMD_PREFSAVE); |
| |
| if (handled) setFocusedItem(); |
| return handled; |
| } |
| |
| case BRL_CMD_KEY(PAGE_UP): |
| case BRL_CMD_MENU_FIRST_ITEM: { |
| if (changeMenuItemFirst(screenMenu)) { |
| itemChanged(); |
| } else { |
| commandRejected(); |
| } |
| |
| return 1; |
| } |
| |
| case BRL_CMD_KEY(PAGE_DOWN): |
| case BRL_CMD_MENU_LAST_ITEM: { |
| if (changeMenuItemLast(screenMenu)) { |
| itemChanged(); |
| } else { |
| commandRejected(); |
| } |
| |
| return 1; |
| } |
| |
| case BRL_CMD_KEY(CURSOR_UP): |
| case BRL_CMD_MENU_PREV_ITEM: { |
| if (changeMenuItemPrevious(screenMenu, 1)) { |
| itemChanged(); |
| } else { |
| commandRejected(); |
| } |
| |
| return 1; |
| } |
| |
| case BRL_CMD_KEY(CURSOR_DOWN): |
| case BRL_CMD_MENU_NEXT_ITEM: { |
| if (changeMenuItemNext(screenMenu, 1)) { |
| itemChanged(); |
| } else { |
| commandRejected(); |
| } |
| |
| return 1; |
| } |
| |
| case BRL_CMD_KEY(CURSOR_LEFT): |
| case BRL_CMD_BACK: |
| case BRL_CMD_MENU_PREV_SETTING: { |
| setFocusedItem(); |
| |
| if (changeMenuSettingPrevious(screenMenu, 1)) { |
| settingChanged(); |
| } else { |
| commandRejected(); |
| } |
| |
| return 1; |
| } |
| |
| case BRL_CMD_KEY(CURSOR_RIGHT): |
| case BRL_CMD_HOME: |
| case BRL_CMD_RETURN: |
| case BRL_CMD_MENU_NEXT_SETTING: { |
| setFocusedItem(); |
| |
| if (changeMenuSettingNext(screenMenu, 1)) { |
| settingChanged(); |
| } else { |
| commandRejected(); |
| } |
| |
| return 1; |
| } |
| |
| case BRL_CMD_CSRJMP_VERT: { |
| setFocusedItem(); |
| return 1; |
| } |
| |
| default: { |
| if ((command & BRL_MSK_BLK) == BRL_CMD_BLK(ROUTE)) { |
| unsigned int key = command & BRL_MSK_ARG; |
| unsigned int count = brl.textColumns; |
| |
| if (key < count) { |
| setFocusedItem(); |
| |
| if (changeMenuSettingScaled(screenMenu, key, count)) { |
| settingChanged(); |
| } else { |
| commandRejected(); |
| } |
| } else if ((key >= statusStart) && (key < (statusStart + statusCount))) { |
| switch (key - statusStart) { |
| default: |
| commandRejected(); |
| break; |
| } |
| } else { |
| commandRejected(); |
| } |
| |
| return 1; |
| } |
| |
| break; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static KeyTableCommandContext |
| getCommandContext_MenuScreen (void) { |
| return KTB_CTX_MENU; |
| } |
| |
| void |
| initializeMenuScreen (MenuScreen *menu) { |
| initializeBaseScreen(&menu->base); |
| |
| menu->base.currentVirtualTerminal = currentVirtualTerminal_MenuScreen; |
| menu->base.getTitle = getTitle_MenuScreen; |
| |
| menu->base.refresh = refresh_MenuScreen; |
| menu->base.describe = describe_MenuScreen; |
| menu->base.readCharacters = readCharacters_MenuScreen; |
| |
| menu->base.handleCommand = handleCommand_MenuScreen; |
| menu->base.getCommandContext = getCommandContext_MenuScreen; |
| |
| menu->construct = construct_MenuScreen; |
| menu->destruct = destruct_MenuScreen; |
| } |