Word Unperfect
public
Read
Owner: themaster
Branch: main
Commits: 0
Git CLI clone URL
git clone https://www.xt-emporium.com/git/word-unperfect.git
Fullscreen desktop URL
Code
Commits
History
Branches
Bug Reports
Discussions
Compare
Settings
word-unperfect
/
rev
/
wp_msdos_frontend.c
File editor
#include "wp_msdos_frontend.h" #include "wp_application.h" #include "wp_display_buffer.h" #include "wp_document_model.h" #include "wp_file_format.h" #include "wp_key_dispatcher.h" #include "wp_layout_shared.h" #include "wp_text_export.h" #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #ifdef __WATCOMC__ #include <conio.h> #include <dos.h> extern int getch(void); #else #include <sys/select.h> #include <sys/time.h> #include <sys/ioctl.h> #include <termios.h> #include <unistd.h> #endif #define WP_FRONTEND_LINE_UNIT_SCALE 100U #define WP_FRONTEND_MENU_ROW 0 #define WP_FRONTEND_DOCUMENT_TOP_ROW 1 #define WP_FRONTEND_MESSAGE_ROW (WP_SCREEN_HEIGHT - 2) #define WP_FRONTEND_STATUS_ROW (WP_SCREEN_HEIGHT - 1) #define WP_FRONTEND_CONTENT_ROWS (WP_SCREEN_HEIGHT - 3) #define WP_FRONTEND_REVEAL_SPLIT_ROW 12 #define WP_FRONTEND_PATH_MAX 260U #define WP_FRONTEND_INPUT_MAX 79U #ifdef WORD_UNPERFECT_POSIX_PATHS #define WP_FRONTEND_UNTITLED_NAME "untitled.wp" #define WP_FRONTEND_OPEN_PROMPT "Type a POSIX document path." #define WP_FRONTEND_SAVE_AS_PROMPT "Type the new POSIX document path." #else #define WP_FRONTEND_UNTITLED_NAME "UNTITLED" #define WP_FRONTEND_OPEN_PROMPT "Type a WP document path." #define WP_FRONTEND_SAVE_AS_PROMPT "Type the new WP document path." #define WP_FRONTEND_UNTITLED_SAVE_PATH "UNTITLED.WP" #endif #ifndef WP_FRONTEND_UNTITLED_SAVE_PATH #define WP_FRONTEND_UNTITLED_SAVE_PATH WP_FRONTEND_UNTITLED_NAME #endif typedef enum WpFrontendDialog { WP_FRONTEND_DIALOG_NONE = 0, WP_FRONTEND_DIALOG_HELP, WP_FRONTEND_DIALOG_OPEN, WP_FRONTEND_DIALOG_OPEN_DIRTY, WP_FRONTEND_DIALOG_PRINT, WP_FRONTEND_DIALOG_SAVE_AS } WpFrontendDialog; typedef enum WpFrontendKeyKind { WP_FRONTEND_KEY_NONE = 0, WP_FRONTEND_KEY_CHARACTER, WP_FRONTEND_KEY_LEFT, WP_FRONTEND_KEY_RIGHT, WP_FRONTEND_KEY_UP, WP_FRONTEND_KEY_DOWN, WP_FRONTEND_KEY_PAGE_UP, WP_FRONTEND_KEY_PAGE_DOWN, WP_FRONTEND_KEY_HOME, WP_FRONTEND_KEY_END, WP_FRONTEND_KEY_DELETE, WP_FRONTEND_KEY_BACKSPACE, WP_FRONTEND_KEY_ENTER, WP_FRONTEND_KEY_TAB, WP_FRONTEND_KEY_COMMAND, WP_FRONTEND_KEY_SAVE_AS } WpFrontendKeyKind; typedef struct WpFrontendKey { WpFrontendKeyKind kind; unsigned char ch; WpCommand command; } WpFrontendKey; typedef enum WpFrontendMenuAction { WP_FRONTEND_MENU_ACTION_NONE = 0, WP_FRONTEND_MENU_ACTION_COMMAND, WP_FRONTEND_MENU_ACTION_SAVE_AS } WpFrontendMenuAction; typedef struct WpFrontendMenuItem { const char *label; WpFrontendMenuAction action; WpCommand command; } WpFrontendMenuItem; typedef struct WpFrontendState { char filename[WP_FRONTEND_PATH_MAX]; char pending_path[WP_FRONTEND_PATH_MAX]; WpLoadedFile file; WpDocumentModel model; WpDisplayBuffer db; WpApplicationScreenStats stats; WpFrontendDialog dialog; size_t cursor_text_offset; size_t text_units; size_t cursor_row; size_t cursor_col; size_t cursor_page; size_t first_row; int dirty; int reveal; int running; int exit_prompt; int loaded; int has_filename; int menu_active; size_t menu_index; size_t menu_item_index; char message[WP_SCREEN_WIDTH + 1]; char dialog_message[WP_SCREEN_WIDTH + 1]; char input[WP_FRONTEND_INPUT_MAX + 1U]; size_t input_len; } WpFrontendState; static void wp_frontend_refresh_cursor(WpFrontendState *state); static const char *wp_frontend_menu_names[] = { "File", "Edit", "Search", "Layout", "Mark", "Tools", "Font", "Graphics", "Help" }; static const WpFrontendMenuItem wp_frontend_file_menu_items[] = { { "Open...", WP_FRONTEND_MENU_ACTION_COMMAND, WP_CMD_LIST }, { "Save", WP_FRONTEND_MENU_ACTION_COMMAND, WP_CMD_SAVE }, { "Save As...", WP_FRONTEND_MENU_ACTION_SAVE_AS, WP_CMD_NONE }, { "Print...", WP_FRONTEND_MENU_ACTION_COMMAND, WP_CMD_PRINT }, { "Exit", WP_FRONTEND_MENU_ACTION_COMMAND, WP_CMD_EXIT } }; #ifndef __WATCOMC__ static struct termios orig_termios; static int terminal_mode_set; static size_t wp_host_parse_terminal_size_env(const char *name) { const char *value; char *end; unsigned long parsed; value = getenv(name); if (value == NULL || value[0] == '\0') { return 0U; } parsed = strtoul(value, &end, 10); if (end == value || parsed == 0UL) { return 0U; } if (parsed > 1000UL) { parsed = 1000UL; } return (size_t)parsed; } static void wp_host_terminal_size(size_t *out_cols, size_t *out_rows) { size_t cols = 0U; size_t rows = 0U; #ifdef TIOCGWINSZ struct winsize ws; memset(&ws, 0, sizeof(ws)); if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) { cols = (size_t)ws.ws_col; rows = (size_t)ws.ws_row; } #endif if (cols == 0U) { cols = wp_host_parse_terminal_size_env("COLUMNS"); } if (rows == 0U) { rows = wp_host_parse_terminal_size_env("LINES"); } if (cols == 0U) { cols = WP_SCREEN_WIDTH; } if (rows == 0U) { rows = WP_SCREEN_HEIGHT; } if (out_cols != NULL) { *out_cols = cols; } if (out_rows != NULL) { *out_rows = rows; } } static size_t wp_host_fit_source_row(size_t screen_row, size_t render_rows) { size_t body_rows; if (render_rows >= WP_SCREEN_HEIGHT) { return screen_row; } if (render_rows == 0U) { return 0U; } if (render_rows == 1U) { return WP_SCREEN_HEIGHT - 1U; } if (render_rows == 2U) { return WP_SCREEN_HEIGHT - 2U + screen_row; } body_rows = render_rows - 2U; if (screen_row < body_rows) { return screen_row; } return WP_SCREEN_HEIGHT - (render_rows - screen_row); } static void reset_terminal_mode(void) { if (terminal_mode_set) { tcsetattr(0, TCSANOW, &orig_termios); terminal_mode_set = 0; } printf("\x1b[?25h"); printf("\x1b[0m\x1b[2J\x1b[H"); fflush(stdout); } static void set_conio_terminal_mode(void) { struct termios new_termios; if (tcgetattr(0, &orig_termios) != 0) { return; } memcpy(&new_termios, &orig_termios, sizeof(new_termios)); atexit(reset_terminal_mode); new_termios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); new_termios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); new_termios.c_cflag &= ~(CSIZE | PARENB); new_termios.c_cflag |= CS8; new_termios.c_cc[VMIN] = 1; new_termios.c_cc[VTIME] = 0; tcsetattr(0, TCSANOW, &new_termios); terminal_mode_set = 1; printf("\x1b[?25l\x1b[2J\x1b[H"); fflush(stdout); } static int wp_host_read_byte_timeout(unsigned char *out_ch, unsigned timeout_ms) { fd_set fds; struct timeval tv; int ready; unsigned char ch; if (out_ch == NULL) { return 0; } FD_ZERO(&fds); FD_SET(STDIN_FILENO, &fds); tv.tv_sec = (long)(timeout_ms / 1000U); tv.tv_usec = (long)((timeout_ms % 1000U) * 1000U); ready = select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv); if (ready <= 0) { return 0; } if (read(STDIN_FILENO, &ch, 1) != 1) { return 0; } *out_ch = ch; return 1; } #endif static WpFrontendKey wp_frontend_key(WpFrontendKeyKind kind) { WpFrontendKey key; memset(&key, 0, sizeof(key)); key.kind = kind; return key; } static WpFrontendKey wp_frontend_char_key(unsigned char ch) { WpFrontendKey key; key = wp_frontend_key(WP_FRONTEND_KEY_CHARACTER); key.ch = ch; return key; } static WpFrontendKey wp_frontend_command_key(WpCommand command) { WpFrontendKey key; key = wp_frontend_key(WP_FRONTEND_KEY_COMMAND); key.command = command; return key; } static int wp_frontend_copy_text(char *dst, size_t dst_size, const char *src) { size_t i; if (dst == NULL || dst_size == 0U) { return 0; } dst[0] = '\0'; if (src == NULL) { return 1; } for (i = 0U; i + 1U < dst_size && src[i] != '\0'; ++i) { dst[i] = src[i]; } dst[i] = '\0'; return src[i] == '\0'; } static void wp_frontend_set_message(WpFrontendState *state, const char *text) { size_t i; if (state == NULL) { return; } memset(state->message, 0, sizeof(state->message)); if (text == NULL) { return; } for (i = 0U; i < WP_SCREEN_WIDTH && text[i] != '\0'; ++i) { state->message[i] = text[i]; } } static void wp_frontend_set_dialog_message(WpFrontendState *state, const char *text) { size_t i; if (state == NULL) { return; } memset(state->dialog_message, 0, sizeof(state->dialog_message)); if (text == NULL) { return; } for (i = 0U; i < WP_SCREEN_WIDTH && text[i] != '\0'; ++i) { state->dialog_message[i] = text[i]; } } static void wp_frontend_clear_dialog(WpFrontendState *state) { if (state == NULL) { return; } state->dialog = WP_FRONTEND_DIALOG_NONE; state->input_len = 0U; state->input[0] = '\0'; state->pending_path[0] = '\0'; wp_frontend_set_dialog_message(state, ""); } static void wp_frontend_open_dialog(WpFrontendState *state, WpFrontendDialog dialog, const char *initial, const char *message) { if (state == NULL) { return; } state->menu_active = 0; state->dialog = dialog; state->input_len = 0U; state->input[0] = '\0'; if (initial != NULL) { wp_frontend_copy_text(state->input, sizeof(state->input), initial); state->input_len = strlen(state->input); } wp_frontend_set_dialog_message(state, message); } static void wp_frontend_input_append(WpFrontendState *state, unsigned char ch) { if (state == NULL || ch < 0x20U || ch > 0x7eU || state->input_len >= WP_FRONTEND_INPUT_MAX) { return; } state->input[state->input_len++] = (char)ch; state->input[state->input_len] = '\0'; } static void wp_frontend_input_backspace(WpFrontendState *state) { if (state == NULL || state->input_len == 0U) { return; } state->input[--state->input_len] = '\0'; } static int wp_frontend_code_is_logical_text(uint8_t code) { return code == 0x80U || code == 0x81U || code == 0x82U || code == 0x83U || code == 0x84U || code == 0x8DU; } static int wp_frontend_token_is_extended_char(const WpDocumentToken *token) { return token != NULL && token->type == WP_DOCUMENT_TOKEN_FIXED_RECORD && token->record_type == WP_CODE_FIXED_LENGTH && token->code == 0xC0U && token->complete; } static int wp_frontend_token_has_text_unit(const WpDocumentToken *token) { if (token == NULL) { return 0; } if (token->type == WP_DOCUMENT_TOKEN_TEXT) { return token->length != 0U; } if (token->type == WP_DOCUMENT_TOKEN_SINGLE_BYTE && token->length == 1U && wp_frontend_code_is_logical_text(token->code)) { return 1; } return wp_frontend_token_is_extended_char(token); } static void wp_frontend_advance_position(uint8_t code, size_t *page, size_t *row, size_t *col) { size_t next_col; if (page == NULL || row == NULL || col == NULL) { return; } if (code == 0x82U || code == 0x83U) { (*page)++; *row = 0U; *col = 0U; return; } if (code == 0x80U || code == 0x81U) { (*row)++; *col = 0U; return; } if (code == 0x84U) { next_col = ((*col / 8U) + 1U) * 8U; } else if (code == 0x8DU) { next_col = *col + 5U; } else { next_col = *col + 1U; } if (next_col >= WP_SCREEN_WIDTH) { (*row)++; *col = next_col % WP_SCREEN_WIDTH; } else { *col = next_col; } } static void wp_frontend_measure_cursor(const WpDocumentModel *model, size_t cursor_text_offset, size_t *out_page, size_t *out_row, size_t *out_col) { size_t page = 1U; size_t row = 0U; size_t col = 0U; size_t offset = 0U; size_t i; if (model == NULL) { if (out_page != NULL) { *out_page = 1U; } if (out_row != NULL) { *out_row = 0U; } if (out_col != NULL) { *out_col = 0U; } return; } for (i = 0U; i < model->token_count; ++i) { const WpDocumentToken *token = &model->tokens[i]; if (token->type == WP_DOCUMENT_TOKEN_TEXT) { size_t j; for (j = 0U; j < token->length; ++j) { if (offset >= cursor_text_offset) { goto done; } wp_frontend_advance_position(token->bytes[j], &page, &row, &col); offset++; } } else if (wp_frontend_token_has_text_unit(token)) { uint8_t code = token->code; if (offset >= cursor_text_offset) { goto done; } if (wp_frontend_token_is_extended_char(token)) { code = 'x'; } wp_frontend_advance_position(code, &page, &row, &col); offset++; } } done: if (out_page != NULL) { *out_page = page; } if (out_row != NULL) { *out_row = row; } if (out_col != NULL) { *out_col = col; } } static size_t wp_frontend_offset_for_row_col(const WpDocumentModel *model, size_t target_page, size_t target_row, size_t target_col) { size_t page = 1U; size_t row = 0U; size_t col = 0U; size_t offset = 0U; size_t i; if (model == NULL) { return 0U; } for (i = 0U; i < model->token_count; ++i) { const WpDocumentToken *token = &model->tokens[i]; if (token->type == WP_DOCUMENT_TOKEN_TEXT) { size_t j; for (j = 0U; j < token->length; ++j) { if (page > target_page || (page == target_page && row > target_row) || (page == target_page && row == target_row && col >= target_col)) { return offset; } wp_frontend_advance_position(token->bytes[j], &page, &row, &col); offset++; } } else if (wp_frontend_token_has_text_unit(token)) { uint8_t code = token->code; if (page > target_page || (page == target_page && row > target_row) || (page == target_page && row == target_row && col >= target_col)) { return offset; } if (wp_frontend_token_is_extended_char(token)) { code = 'x'; } wp_frontend_advance_position(code, &page, &row, &col); offset++; } } return offset; } static size_t wp_frontend_scaled_position(size_t value) { size_t one_based; one_based = value + 1U; if (one_based > SIZE_MAX / WP_FRONTEND_LINE_UNIT_SCALE) { return SIZE_MAX; } return one_based * WP_FRONTEND_LINE_UNIT_SCALE; } static void wp_frontend_write_row(WpDisplayBuffer *db, int row, const char *text, uint8_t attr) { int x; if (db == NULL || row < 0 || row >= WP_SCREEN_HEIGHT) { return; } for (x = 0; x < WP_SCREEN_WIDTH; ++x) { uint8_t ch = ' '; if (text != NULL && text[x] != '\0') { ch = (uint8_t)text[x]; } wp_display_putc(db, x, row, ch, attr); } } static void wp_frontend_write_at(WpDisplayBuffer *db, int col, int row, const char *text, uint8_t attr) { int x; if (db == NULL || text == NULL || row < 0 || row >= WP_SCREEN_HEIGHT || col >= WP_SCREEN_WIDTH) { return; } if (col < 0) { col = 0; } for (x = col; x < WP_SCREEN_WIDTH && *text != '\0'; ++x, ++text) { wp_display_putc(db, x, row, (uint8_t)*text, attr); } } static void wp_frontend_draw_box(WpDisplayBuffer *db, int left, int top, int right, int bottom, const char *title) { char row[WP_SCREEN_WIDTH + 1]; int x; int y; if (db == NULL || left < 0 || top < 0 || right >= WP_SCREEN_WIDTH || bottom >= WP_SCREEN_HEIGHT || left >= right || top >= bottom) { return; } for (y = top; y <= bottom; ++y) { memset(row, ' ', WP_SCREEN_WIDTH); row[WP_SCREEN_WIDTH] = '\0'; row[left] = '|'; row[right] = '|'; if (y == top || y == bottom) { for (x = left; x <= right; ++x) { row[x] = '-'; } row[left] = '+'; row[right] = '+'; } wp_frontend_write_row(db, y, row, 0x70U); } if (title != NULL) { wp_frontend_write_at(db, left + 2, top, title, 0x1fU); } } static void wp_frontend_apply_help_dialog(WpFrontendState *state) { if (state == NULL) { return; } wp_frontend_draw_box(&state->db, 8, 3, 71, 18, " word unperfect Help "); wp_frontend_write_at(&state->db, 12, 5, "F5 File menu open, save, print, or exit", 0x70U); wp_frontend_write_at(&state->db, 12, 6, "F10 Save save the current document", 0x70U); wp_frontend_write_at(&state->db, 12, 7, "Ctrl-W Save As write the document to another file", 0x70U); wp_frontend_write_at(&state->db, 12, 8, "Shift-F7 Print print/export text to a file", 0x70U); wp_frontend_write_at(&state->db, 12, 9, "Alt-F3 Reveal toggle Reveal Codes", 0x70U); wp_frontend_write_at(&state->db, 12, 10, "F7 Exit exit, with save confirmation", 0x70U); wp_frontend_write_at(&state->db, 12, 12, "Arrows, PgUp/PgDn, Home, End move the cursor.", 0x70U); wp_frontend_write_at(&state->db, 12, 13, "Typing, Enter, Tab, Backspace, and Delete edit.", 0x70U); wp_frontend_write_at(&state->db, 12, 16, "Press any key to return to the document.", 0x1fU); } static void wp_frontend_apply_input_dialog(WpFrontendState *state, const char *title, const char *label) { char row[WP_SCREEN_WIDTH + 1]; if (state == NULL) { return; } wp_frontend_draw_box(&state->db, 6, 6, 73, 14, title); wp_frontend_write_at(&state->db, 10, 8, label, 0x70U); memset(row, ' ', WP_SCREEN_WIDTH); row[WP_SCREEN_WIDTH] = '\0'; row[10] = '>'; row[11] = ' '; wp_frontend_write_row(&state->db, 10, row, 0x70U); wp_frontend_write_at(&state->db, 12, 10, state->input, 0x1fU); if (state->dialog_message[0] != '\0') { wp_frontend_write_at(&state->db, 10, 12, state->dialog_message, 0x70U); } wp_frontend_write_at(&state->db, 10, 13, "Enter accepts. Esc cancels. Backspace edits.", 0x70U); } static void wp_frontend_apply_open_dirty_dialog(WpFrontendState *state) { if (state == NULL) { return; } wp_frontend_draw_box(&state->db, 6, 7, 73, 14, " Unsaved Changes "); wp_frontend_write_at(&state->db, 10, 9, "Save current document before opening the new file?", 0x70U); wp_frontend_write_at(&state->db, 10, 11, "Y Save and open", 0x1fU); wp_frontend_write_at(&state->db, 10, 12, "N Discard and open", 0x1fU); wp_frontend_write_at(&state->db, 10, 13, "Esc Cancel", 0x1fU); } static void wp_frontend_apply_dialog(WpFrontendState *state) { if (state == NULL) { return; } switch (state->dialog) { case WP_FRONTEND_DIALOG_HELP: wp_frontend_apply_help_dialog(state); break; case WP_FRONTEND_DIALOG_OPEN: wp_frontend_apply_input_dialog(state, " List Files / Open ", "Document path:"); break; case WP_FRONTEND_DIALOG_PRINT: wp_frontend_apply_input_dialog(state, " Print to File ", "Output text file:"); break; case WP_FRONTEND_DIALOG_SAVE_AS: wp_frontend_apply_input_dialog(state, " Save As ", "New document path:"); break; case WP_FRONTEND_DIALOG_OPEN_DIRTY: wp_frontend_apply_open_dirty_dialog(state); break; case WP_FRONTEND_DIALOG_NONE: default: break; } } static size_t wp_frontend_menu_count(void) { return sizeof(wp_frontend_menu_names) / sizeof(wp_frontend_menu_names[0]); } static size_t wp_frontend_file_menu_count(void) { return sizeof(wp_frontend_file_menu_items) / sizeof(wp_frontend_file_menu_items[0]); } static void wp_frontend_shift_document_for_menu(WpFrontendState *state) { int row; if (state == NULL) { return; } for (row = WP_FRONTEND_DOCUMENT_TOP_ROW + WP_FRONTEND_CONTENT_ROWS - 1; row >= WP_FRONTEND_DOCUMENT_TOP_ROW; --row) { memcpy(state->db.cells[row], state->db.cells[row - 1], sizeof(state->db.cells[row])); } for (row = 0; row < WP_SCREEN_WIDTH; ++row) { wp_display_putc(&state->db, row, WP_FRONTEND_MENU_ROW, (uint8_t)' ', 0x17U); } } static void wp_frontend_apply_file_menu(WpFrontendState *state) { size_t count; size_t i; if (state == NULL || state->menu_index != 0U) { return; } count = wp_frontend_file_menu_count(); if (state->menu_item_index >= count) { state->menu_item_index = 0U; } wp_frontend_draw_box(&state->db, 0, 1, 21, 8, " File "); for (i = 1U; i <= 8U; ++i) { size_t col; for (col = 0U; col < WP_SCREEN_WIDTH; ++col) { state->db.cells[i][col].attr = 0x17U; state->db.cells[i][col].is_dirty = 1; } } wp_frontend_write_at(&state->db, 3, 1, "File", 0x1fU); for (i = 0U; i < count; ++i) { uint8_t attr = i == state->menu_item_index ? 0x1fU : 0x17U; wp_frontend_write_at(&state->db, 2, (int)(i + 2U), wp_frontend_file_menu_items[i].label, attr); } } static void wp_frontend_apply_menu_bar(WpFrontendState *state) { const char *help = "(Press F3 for Help)"; size_t count; size_t i; size_t col = 1U; if (state == NULL) { return; } for (i = 0U; i < WP_SCREEN_WIDTH; ++i) { wp_display_putc(&state->db, (int)i, WP_FRONTEND_MENU_ROW, (uint8_t)' ', 0x17U); } count = wp_frontend_menu_count(); for (i = 0U; i < count && col < WP_SCREEN_WIDTH; ++i) { uint8_t attr = state->menu_active && i == state->menu_index ? 0x1fU : 0x17U; wp_frontend_write_at(&state->db, (int)col, WP_FRONTEND_MENU_ROW, wp_frontend_menu_names[i], attr); col += strlen(wp_frontend_menu_names[i]) + 1U; } if (strlen(help) < WP_SCREEN_WIDTH) { wp_frontend_write_at(&state->db, WP_SCREEN_WIDTH - (int)strlen(help) - 1, WP_FRONTEND_MENU_ROW, help, 0x17U); } if (state->menu_active) { wp_frontend_apply_file_menu(state); } } static void wp_frontend_apply_cursor(WpFrontendState *state) { size_t screen_row; if (state == NULL || state->cursor_page == 0U || state->cursor_row < state->first_row || state->cursor_row >= state->first_row + WP_FRONTEND_CONTENT_ROWS) { return; } screen_row = state->cursor_row - state->first_row + WP_FRONTEND_DOCUMENT_TOP_ROW; if (screen_row >= WP_FRONTEND_MESSAGE_ROW || state->cursor_col >= WP_SCREEN_WIDTH) { return; } state->db.cells[screen_row][state->cursor_col].attr = 0x70U; state->db.cells[screen_row][state->cursor_col].is_dirty = 1; } static void wp_frontend_apply_reveal(WpFrontendState *state) { WpLayoutGlobals wl; if (state == NULL || !state->reveal) { return; } memset(&wl, 0, sizeof(wl)); if (!wp_file_bind_primary_stream(&state->file, &wl, 4096U)) { return; } state->db.reveal_codes_active = 1; state->db.reveal_codes_split_row = WP_FRONTEND_REVEAL_SPLIT_ROW; wp_reveal_codes_sync_view(&wl, &state->db); } static void wp_frontend_apply_message(WpFrontendState *state) { char row[WP_SCREEN_WIDTH + 1]; size_t i; if (state == NULL) { return; } memset(row, ' ', WP_SCREEN_WIDTH); row[WP_SCREEN_WIDTH] = '\0'; if (state->message[0] != '\0') { for (i = 0U; i < WP_SCREEN_WIDTH && state->message[i] != '\0'; ++i) { row[i] = state->message[i]; } } else { const char *help = "F5 File F10 Save Ctrl-W SaveAs Shift-F7 Print F7 Exit F3 Help"; for (i = 0U; i < WP_SCREEN_WIDTH && help[i] != '\0'; ++i) { row[i] = help[i]; } if (state->dirty && i + 10U < WP_SCREEN_WIDTH) { memcpy(row + i + 2U, "Modified", 8U); } } wp_frontend_write_row(&state->db, WP_FRONTEND_MESSAGE_ROW, row, 0x70U); } static int wp_frontend_apply_model(WpFrontendState *state) { if (state == NULL) { return 0; } if (!wp_document_model_apply_to_file(&state->model, &state->file)) { wp_frontend_set_message(state, "Could not update the in-memory document."); return 0; } return 1; } static int wp_frontend_init_blank_file(WpLoadedFile *file) { uint8_t *prefix; if (file == NULL) { return 0; } memset(file, 0, sizeof(*file)); prefix = (uint8_t *)calloc(WP_FILE_PREFIX_SIZE, 1U); if (prefix == NULL) { return 0; } prefix[0] = 0xffU; prefix[1] = 'W'; prefix[2] = 'P'; prefix[3] = 'C'; prefix[4] = (uint8_t)(WP_FILE_PREFIX_SIZE & 0xffU); prefix[5] = 0x00U; prefix[6] = 0x00U; prefix[7] = 0x00U; prefix[8] = 0x01U; prefix[9] = 0x0aU; prefix[10] = 0x00U; prefix[11] = 0x01U; file->header.signature[0] = prefix[0]; file->header.signature[1] = prefix[1]; file->header.signature[2] = prefix[2]; file->header.signature[3] = prefix[3]; file->header.data_offset = WP_FILE_PREFIX_SIZE; file->header.product_type = 0x01U; file->header.file_type = 0x0aU; file->header.major_version = 0x00U; file->header.minor_version = 0x01U; file->header.file_size = WP_FILE_PREFIX_SIZE; file->prefix_bytes = prefix; file->prefix_size = WP_FILE_PREFIX_SIZE; return 1; } static int wp_frontend_load_blank_document(WpFrontendState *state) { WpLoadedFile new_file; WpDocumentModel new_model; if (state == NULL) { return 0; } memset(&new_file, 0, sizeof(new_file)); wp_document_model_init(&new_model); if (!wp_frontend_init_blank_file(&new_file) || !wp_document_model_load(&new_model, &new_file)) { wp_document_model_free(&new_model); wp_file_free(&new_file); return 0; } if (state->loaded) { wp_document_model_free(&state->model); wp_file_free(&state->file); } state->file = new_file; state->model = new_model; state->loaded = 1; wp_frontend_copy_text(state->filename, sizeof(state->filename), WP_FRONTEND_UNTITLED_NAME); state->has_filename = 0; state->cursor_text_offset = 0U; state->first_row = 0U; state->cursor_row = 0U; state->cursor_col = 0U; state->cursor_page = 1U; state->dirty = 0; state->reveal = 0; wp_frontend_clear_dialog(state); wp_frontend_set_message(state, ""); wp_frontend_refresh_cursor(state); return 1; } static int wp_frontend_save(WpFrontendState *state) { int saved; if (state == NULL) { return 0; } if (!state->has_filename) { wp_frontend_open_dialog(state, WP_FRONTEND_DIALOG_SAVE_AS, WP_FRONTEND_UNTITLED_SAVE_PATH, WP_FRONTEND_SAVE_AS_PROMPT); return 1; } if (!wp_frontend_apply_model(state)) { wp_frontend_set_message(state, "Save failed."); return 0; } saved = wp_file_save_atomic(state->filename, &state->file); #ifdef __WATCOMC__ if (!saved) { saved = wp_file_save(state->filename, &state->file); } #endif if (!saved) { wp_frontend_set_message(state, "Save failed."); return 0; } state->dirty = 0; wp_frontend_set_message(state, "Document saved."); return 1; } static int wp_frontend_load_document(WpFrontendState *state, const char *filename, int announce) { WpLoadedFile new_file; WpDocumentModel new_model; int ok = 0; if (state == NULL || filename == NULL || filename[0] == '\0') { return 0; } if (!wp_frontend_copy_text(state->pending_path, sizeof(state->pending_path), filename)) { return 0; } memset(&new_file, 0, sizeof(new_file)); wp_document_model_init(&new_model); if (wp_file_load_body(filename, &new_file) && wp_document_model_load(&new_model, &new_file)) { ok = 1; } if (!ok) { wp_document_model_free(&new_model); wp_file_free(&new_file); return 0; } if (state->loaded) { wp_document_model_free(&state->model); wp_file_free(&state->file); } state->file = new_file; state->model = new_model; state->loaded = 1; wp_frontend_copy_text(state->filename, sizeof(state->filename), state->pending_path); state->has_filename = 1; state->cursor_text_offset = 0U; state->first_row = 0U; state->cursor_row = 0U; state->cursor_col = 0U; state->cursor_page = 1U; state->dirty = 0; state->reveal = 0; wp_frontend_clear_dialog(state); if (announce) { wp_frontend_set_message(state, "Document opened."); } wp_frontend_refresh_cursor(state); return 1; } static int wp_frontend_save_as(WpFrontendState *state, const char *filename) { int saved; if (state == NULL || filename == NULL || filename[0] == '\0') { return 0; } if (!wp_frontend_apply_model(state)) { return 0; } saved = wp_file_save_atomic(filename, &state->file); #ifdef __WATCOMC__ if (!saved) { saved = wp_file_save(filename, &state->file); } #endif if (!saved) { return 0; } wp_frontend_copy_text(state->filename, sizeof(state->filename), filename); state->has_filename = 1; state->dirty = 0; wp_frontend_set_message(state, "Document saved as new file."); return 1; } static int wp_frontend_print_to_file(WpFrontendState *state, const char *filename) { WpTextExportOptions options; WpTextExportStats stats; char *text = NULL; FILE *out; size_t capacity; size_t out_len = 0U; int attempts = 0; int ok = 0; if (state == NULL || filename == NULL || filename[0] == '\0') { return 0; } if (!wp_frontend_apply_model(state)) { return 0; } wp_text_export_default_options(&options); if (state->file.logical_size > (SIZE_MAX - 1024U) / 4U) { return 0; } capacity = state->file.logical_size * 4U + 1024U; if (capacity < 2048U) { capacity = 2048U; } while (attempts++ < 8) { free(text); text = (char *)malloc(capacity); if (text == NULL) { return 0; } ok = wp_text_export_loaded_file(&state->file, text, capacity, &out_len, &options, &stats); if (ok || !stats.output_truncated || capacity > (SIZE_MAX / 2U)) { break; } capacity *= 2U; } if (!ok) { free(text); return 0; } out = fopen(filename, "wb"); if (out == NULL) { free(text); return 0; } ok = fwrite(text, 1, out_len, out) == out_len; if (fclose(out) != 0) { ok = 0; } free(text); return ok; } static void wp_frontend_refresh_cursor(WpFrontendState *state) { if (state == NULL) { return; } if (!wp_document_model_text_length(&state->model, &state->text_units)) { state->text_units = 0U; } if (state->cursor_text_offset > state->text_units) { state->cursor_text_offset = state->text_units; } wp_frontend_measure_cursor(&state->model, state->cursor_text_offset, &state->cursor_page, &state->cursor_row, &state->cursor_col); if (state->cursor_row < state->first_row) { state->first_row = state->cursor_row; } else if (state->cursor_row >= state->first_row + WP_FRONTEND_CONTENT_ROWS) { state->first_row = state->cursor_row - WP_FRONTEND_CONTENT_ROWS + 1U; } } static int wp_frontend_render(WpFrontendState *state) { size_t line_units; size_t pos_units; int ok; if (state == NULL) { return 0; } wp_frontend_refresh_cursor(state); line_units = wp_frontend_scaled_position(state->cursor_row); pos_units = wp_frontend_scaled_position(state->cursor_col); wp_display_init(&state->db); ok = wp_application_render_loaded_file_screen_view_status_units_pos_units_render_page(&state->file, state->filename, state->cursor_page, state->cursor_page, state->first_row, line_units, pos_units, &state->db, &state->stats); if (!ok) { wp_display_clear(&state->db); wp_frontend_write_row(&state->db, 0, "Unable to render document screen.", 0x70U); } else { wp_frontend_shift_document_for_menu(state); } wp_frontend_apply_reveal(state); wp_frontend_apply_cursor(state); wp_frontend_apply_message(state); wp_frontend_apply_menu_bar(state); wp_frontend_apply_dialog(state); return ok; } #ifdef __WATCOMC__ static void render_msdos_buffer(WpDisplayBuffer *db) { unsigned char far *video; int y; int x; if (db == NULL) { return; } video = (unsigned char far *)MK_FP(0xb800U, 0U); for (y = 0; y < WP_SCREEN_HEIGHT; ++y) { for (x = 0; x < WP_SCREEN_WIDTH; ++x) { unsigned offset = (unsigned)(((y * WP_SCREEN_WIDTH) + x) * 2); video[offset] = db->cells[y][x].ch; video[offset + 1U] = db->cells[y][x].attr; } } wp_display_render(db); } #else static void wp_host_set_attr(uint8_t attr, uint8_t *current_attr) { if (current_attr != NULL && *current_attr == attr) { return; } if (current_attr != NULL) { *current_attr = attr; } if (attr == 0x1fU) { printf("\x1b[97;44m"); } else if (attr == 0x17U) { printf("\x1b[37;44m"); } else if (attr == 0x60U) { printf("\x1b[30;43m"); } else if (attr == 0x75U) { printf("\x1b[35;47m"); } else if (attr == 0x70U) { printf("\x1b[7m"); } else if (attr == 0x03U) { printf("\x1b[36;40m"); } else { printf("\x1b[0m"); } } static void render_msdos_buffer(WpDisplayBuffer *db) { size_t terminal_cols; size_t terminal_rows; size_t render_cols; size_t render_rows; size_t left_pad; size_t top_pad; size_t y; size_t x; uint8_t current_attr = 0xffU; if (db == NULL) { return; } wp_host_terminal_size(&terminal_cols, &terminal_rows); render_cols = terminal_cols < WP_SCREEN_WIDTH ? terminal_cols : WP_SCREEN_WIDTH; render_rows = terminal_rows < WP_SCREEN_HEIGHT ? terminal_rows : WP_SCREEN_HEIGHT; left_pad = terminal_cols > render_cols ? (terminal_cols - render_cols) / 2U : 0U; top_pad = terminal_rows > render_rows ? (terminal_rows - render_rows) / 2U : 0U; printf("\x1b[2J"); for (y = 0U; y < render_rows; ++y) { size_t source_y = wp_host_fit_source_row(y, render_rows); printf("\x1b[%lu;%luH", (unsigned long)(top_pad + y + 1U), (unsigned long)(left_pad + 1U)); for (x = 0U; x < render_cols; ++x) { WpGlyph g = db->cells[source_y][x]; wp_host_set_attr(g.attr, ¤t_attr); putchar((int)g.ch); } printf("\x1b[0m"); current_attr = 0xffU; } fflush(stdout); wp_display_render(db); } #endif #ifdef __WATCOMC__ static WpFrontendKey wp_frontend_read_key(void) { int ch; int scan; ch = getch(); if (ch == 0 || ch == 0xe0) { scan = getch(); switch (scan) { case 0x4b: return wp_frontend_key(WP_FRONTEND_KEY_LEFT); case 0x4d: return wp_frontend_key(WP_FRONTEND_KEY_RIGHT); case 0x48: return wp_frontend_key(WP_FRONTEND_KEY_UP); case 0x50: return wp_frontend_key(WP_FRONTEND_KEY_DOWN); case 0x49: return wp_frontend_key(WP_FRONTEND_KEY_PAGE_UP); case 0x51: return wp_frontend_key(WP_FRONTEND_KEY_PAGE_DOWN); case 0x47: return wp_frontend_key(WP_FRONTEND_KEY_HOME); case 0x4f: return wp_frontend_key(WP_FRONTEND_KEY_END); case 0x53: return wp_frontend_key(WP_FRONTEND_KEY_DELETE); case 0x3d: return wp_frontend_command_key(WP_CMD_HELP); case 0x3f: return wp_frontend_command_key(WP_CMD_LIST); case 0x41: return wp_frontend_command_key(WP_CMD_EXIT); case 0x44: return wp_frontend_command_key(WP_CMD_SAVE); case 0x5a: return wp_frontend_command_key(WP_CMD_PRINT); case 0x6a: return wp_frontend_command_key(WP_CMD_REVEAL); default: break; } return wp_frontend_key(WP_FRONTEND_KEY_NONE); } if (ch == 8) { return wp_frontend_key(WP_FRONTEND_KEY_BACKSPACE); } if (ch == 9) { return wp_frontend_key(WP_FRONTEND_KEY_TAB); } if (ch == 13) { return wp_frontend_key(WP_FRONTEND_KEY_ENTER); } if (ch == 17) { return wp_frontend_command_key(WP_CMD_EXIT); } if (ch == 18) { return wp_frontend_command_key(WP_CMD_REVEAL); } if (ch == 19) { return wp_frontend_command_key(WP_CMD_SAVE); } if (ch == 23) { return wp_frontend_key(WP_FRONTEND_KEY_SAVE_AS); } if (ch == 27) { return wp_frontend_command_key(WP_CMD_EXIT); } if (ch >= 0x20 && ch <= 0x7e) { return wp_frontend_char_key((unsigned char)ch); } return wp_frontend_key(WP_FRONTEND_KEY_NONE); } #else static WpFrontendKey wp_frontend_read_ansi_sequence(void) { unsigned char ch; char digits[8]; size_t digits_len = 0U; if (!wp_host_read_byte_timeout(&ch, 40U)) { return wp_frontend_command_key(WP_CMD_EXIT); } if (ch == 'O') { if (!wp_host_read_byte_timeout(&ch, 40U)) { return wp_frontend_key(WP_FRONTEND_KEY_NONE); } if (ch == 'P') { return wp_frontend_command_key(WP_CMD_CANCEL); } if (ch == 'R') { return wp_frontend_command_key(WP_CMD_HELP); } return wp_frontend_key(WP_FRONTEND_KEY_NONE); } if (ch != '[') { return wp_frontend_key(WP_FRONTEND_KEY_NONE); } if (!wp_host_read_byte_timeout(&ch, 40U)) { return wp_frontend_key(WP_FRONTEND_KEY_NONE); } if (ch == 'A') { return wp_frontend_key(WP_FRONTEND_KEY_UP); } if (ch == 'B') { return wp_frontend_key(WP_FRONTEND_KEY_DOWN); } if (ch == 'C') { return wp_frontend_key(WP_FRONTEND_KEY_RIGHT); } if (ch == 'D') { return wp_frontend_key(WP_FRONTEND_KEY_LEFT); } if (ch == 'H') { return wp_frontend_key(WP_FRONTEND_KEY_HOME); } if (ch == 'F') { return wp_frontend_key(WP_FRONTEND_KEY_END); } while (isdigit((unsigned char)ch) && digits_len + 1U < sizeof(digits)) { digits[digits_len++] = (char)ch; if (!wp_host_read_byte_timeout(&ch, 40U)) { return wp_frontend_key(WP_FRONTEND_KEY_NONE); } } digits[digits_len] = '\0'; if (ch != '~') { return wp_frontend_key(WP_FRONTEND_KEY_NONE); } if (strcmp(digits, "3") == 0) { return wp_frontend_key(WP_FRONTEND_KEY_DELETE); } if (strcmp(digits, "5") == 0) { return wp_frontend_key(WP_FRONTEND_KEY_PAGE_UP); } if (strcmp(digits, "6") == 0) { return wp_frontend_key(WP_FRONTEND_KEY_PAGE_DOWN); } if (strcmp(digits, "13") == 0) { return wp_frontend_command_key(WP_CMD_HELP); } if (strcmp(digits, "15") == 0) { return wp_frontend_command_key(WP_CMD_LIST); } if (strcmp(digits, "18") == 0) { return wp_frontend_command_key(WP_CMD_EXIT); } if (strcmp(digits, "21") == 0) { return wp_frontend_command_key(WP_CMD_SAVE); } return wp_frontend_key(WP_FRONTEND_KEY_NONE); } static WpFrontendKey wp_frontend_read_key(void) { unsigned char ch; ssize_t got; got = read(STDIN_FILENO, &ch, 1); if (got == 0) { return wp_frontend_command_key(WP_CMD_EXIT); } if (got != 1) { return wp_frontend_key(WP_FRONTEND_KEY_NONE); } if (ch == 27U) { return wp_frontend_read_ansi_sequence(); } if (ch == 8U || ch == 127U) { return wp_frontend_key(WP_FRONTEND_KEY_BACKSPACE); } if (ch == 9U) { return wp_frontend_key(WP_FRONTEND_KEY_TAB); } if (ch == '\r' || ch == '\n') { return wp_frontend_key(WP_FRONTEND_KEY_ENTER); } if (ch == 17U) { return wp_frontend_command_key(WP_CMD_EXIT); } if (ch == 18U) { return wp_frontend_command_key(WP_CMD_REVEAL); } if (ch == 19U) { return wp_frontend_command_key(WP_CMD_SAVE); } if (ch == 23U) { return wp_frontend_key(WP_FRONTEND_KEY_SAVE_AS); } if (ch >= 0x20U && ch <= 0x7eU) { return wp_frontend_char_key(ch); } return wp_frontend_key(WP_FRONTEND_KEY_NONE); } #endif static void wp_frontend_move_to_row_delta(WpFrontendState *state, int delta) { size_t target_row; if (state == NULL) { return; } wp_frontend_refresh_cursor(state); if (delta < 0 && state->cursor_row < (size_t)(-delta)) { target_row = 0U; } else { target_row = state->cursor_row + (size_t)delta; } state->cursor_text_offset = wp_frontend_offset_for_row_col(&state->model, state->cursor_page, target_row, state->cursor_col); } static int wp_frontend_key_cancels_dialog(const WpFrontendKey *key) { return key != NULL && key->kind == WP_FRONTEND_KEY_COMMAND && (key->command == WP_CMD_CANCEL || key->command == WP_CMD_EXIT); } static void wp_frontend_accept_dialog(WpFrontendState *state) { char path[WP_FRONTEND_PATH_MAX]; if (state == NULL) { return; } switch (state->dialog) { case WP_FRONTEND_DIALOG_OPEN: if (state->input[0] == '\0') { wp_frontend_set_dialog_message(state, "Enter a document path."); } else if (state->dirty) { wp_frontend_copy_text(state->pending_path, sizeof(state->pending_path), state->input); state->dialog = WP_FRONTEND_DIALOG_OPEN_DIRTY; } else if (!wp_frontend_load_document(state, state->input, 1)) { wp_frontend_copy_text(path, sizeof(path), state->input); wp_frontend_open_dialog(state, WP_FRONTEND_DIALOG_OPEN, path, "Could not open that document."); } break; case WP_FRONTEND_DIALOG_PRINT: if (state->input[0] == '\0') { wp_frontend_set_dialog_message(state, "Enter an output file."); } else if (wp_frontend_print_to_file(state, state->input)) { wp_frontend_clear_dialog(state); wp_frontend_set_message(state, "Document printed to file."); } else { wp_frontend_set_dialog_message(state, "Print/export failed."); } break; case WP_FRONTEND_DIALOG_SAVE_AS: if (state->input[0] == '\0') { wp_frontend_set_dialog_message(state, "Enter a document path."); } else if (wp_frontend_save_as(state, state->input)) { wp_frontend_clear_dialog(state); } else { wp_frontend_set_dialog_message(state, "Save As failed."); } break; case WP_FRONTEND_DIALOG_HELP: case WP_FRONTEND_DIALOG_OPEN_DIRTY: case WP_FRONTEND_DIALOG_NONE: default: break; } } static void wp_frontend_process_open_dirty_key(WpFrontendState *state, const WpFrontendKey *key) { char path[WP_FRONTEND_PATH_MAX]; if (state == NULL || key == NULL) { return; } if (wp_frontend_key_cancels_dialog(key)) { wp_frontend_clear_dialog(state); return; } if (key->kind != WP_FRONTEND_KEY_CHARACTER) { return; } wp_frontend_copy_text(path, sizeof(path), state->pending_path); if (key->ch == 'Y' || key->ch == 'y') { if (!wp_frontend_save(state)) { state->dialog = WP_FRONTEND_DIALOG_OPEN_DIRTY; wp_frontend_copy_text(state->pending_path, sizeof(state->pending_path), path); wp_frontend_set_dialog_message(state, "Save failed."); return; } if (!wp_frontend_load_document(state, path, 1)) { wp_frontend_open_dialog(state, WP_FRONTEND_DIALOG_OPEN, path, "Could not open that document."); } } else if (key->ch == 'N' || key->ch == 'n') { if (!wp_frontend_load_document(state, path, 1)) { wp_frontend_open_dialog(state, WP_FRONTEND_DIALOG_OPEN, path, "Could not open that document."); } } } static void wp_frontend_process_dialog_key(WpFrontendState *state, const WpFrontendKey *key) { if (state == NULL || key == NULL || state->dialog == WP_FRONTEND_DIALOG_NONE) { return; } if (state->dialog == WP_FRONTEND_DIALOG_HELP) { wp_frontend_clear_dialog(state); return; } if (state->dialog == WP_FRONTEND_DIALOG_OPEN_DIRTY) { wp_frontend_process_open_dirty_key(state, key); return; } if (wp_frontend_key_cancels_dialog(key)) { wp_frontend_clear_dialog(state); return; } if (key->kind == WP_FRONTEND_KEY_ENTER) { wp_frontend_accept_dialog(state); } else if (key->kind == WP_FRONTEND_KEY_BACKSPACE) { wp_frontend_input_backspace(state); } else if (key->kind == WP_FRONTEND_KEY_CHARACTER) { wp_frontend_input_append(state, key->ch); } } static void wp_frontend_process_command(WpFrontendState *state, WpCommand command) { if (state == NULL) { return; } if (command != WP_CMD_LIST) { state->menu_active = 0; } switch (command) { case WP_CMD_SAVE: wp_frontend_save(state); break; case WP_CMD_EXIT: if (state->dirty) { state->exit_prompt = 1; wp_frontend_set_message(state, "Save changes before exit? Y=Save N=Exit Esc=Cancel"); } else { state->running = 0; } break; case WP_CMD_REVEAL: state->reveal = !state->reveal; wp_frontend_set_message(state, state->reveal ? "Reveal Codes on." : "Reveal Codes off."); break; case WP_CMD_HELP: wp_frontend_open_dialog(state, WP_FRONTEND_DIALOG_HELP, "", ""); break; case WP_CMD_LIST: state->exit_prompt = 0; state->menu_active = 1; state->menu_index = 0U; state->menu_item_index = 0U; wp_frontend_set_message(state, ""); break; case WP_CMD_PRINT: wp_frontend_open_dialog(state, WP_FRONTEND_DIALOG_PRINT, "PRINT.TXT", "Print exports visible text to this file."); break; case WP_CMD_CANCEL: state->exit_prompt = 0; wp_frontend_set_message(state, ""); break; case WP_CMD_NONE: default: break; } } static void wp_frontend_run_file_menu_item(WpFrontendState *state) { const WpFrontendMenuItem *item; if (state == NULL) { return; } if (state->menu_item_index >= wp_frontend_file_menu_count()) { state->menu_item_index = 0U; } item = &wp_frontend_file_menu_items[state->menu_item_index]; state->menu_active = 0; if (item->action == WP_FRONTEND_MENU_ACTION_SAVE_AS) { wp_frontend_open_dialog(state, WP_FRONTEND_DIALOG_SAVE_AS, state->has_filename ? state->filename : WP_FRONTEND_UNTITLED_SAVE_PATH, WP_FRONTEND_SAVE_AS_PROMPT); return; } if (item->action != WP_FRONTEND_MENU_ACTION_COMMAND) { return; } if (item->command == WP_CMD_LIST) { wp_frontend_open_dialog(state, WP_FRONTEND_DIALOG_OPEN, "", WP_FRONTEND_OPEN_PROMPT); return; } wp_frontend_process_command(state, item->command); } static void wp_frontend_process_menu_key(WpFrontendState *state, const WpFrontendKey *key) { size_t count; size_t item_count; if (state == NULL || key == NULL || !state->menu_active) { return; } count = wp_frontend_menu_count(); item_count = wp_frontend_file_menu_count(); switch (key->kind) { case WP_FRONTEND_KEY_LEFT: if (state->menu_index == 0U) { state->menu_index = count - 1U; } else { state->menu_index--; } state->menu_item_index = 0U; break; case WP_FRONTEND_KEY_RIGHT: state->menu_index = (state->menu_index + 1U) % count; state->menu_item_index = 0U; break; case WP_FRONTEND_KEY_UP: if (state->menu_index == 0U) { if (state->menu_item_index == 0U) { state->menu_item_index = item_count - 1U; } else { state->menu_item_index--; } } break; case WP_FRONTEND_KEY_DOWN: if (state->menu_index == 0U) { state->menu_item_index = (state->menu_item_index + 1U) % item_count; } break; case WP_FRONTEND_KEY_ENTER: if (state->menu_index == 0U) { wp_frontend_run_file_menu_item(state); } else if (state->menu_index == count - 1U) { state->menu_active = 0; wp_frontend_open_dialog(state, WP_FRONTEND_DIALOG_HELP, "", ""); } else { state->menu_active = 0; wp_frontend_set_message(state, "That WordPerfect menu is visible but not implemented yet."); } break; case WP_FRONTEND_KEY_COMMAND: if (key->command == WP_CMD_CANCEL || key->command == WP_CMD_EXIT) { state->menu_active = 0; wp_frontend_set_message(state, ""); } else if (key->command == WP_CMD_HELP) { state->menu_active = 0; wp_frontend_open_dialog(state, WP_FRONTEND_DIALOG_HELP, "", ""); } else if (key->command == WP_CMD_LIST) { state->menu_index = 0U; state->menu_item_index = 0U; } else { state->menu_active = 0; wp_frontend_process_command(state, key->command); } break; case WP_FRONTEND_KEY_SAVE_AS: state->menu_active = 0; wp_frontend_open_dialog(state, WP_FRONTEND_DIALOG_SAVE_AS, state->has_filename ? state->filename : WP_FRONTEND_UNTITLED_SAVE_PATH, WP_FRONTEND_SAVE_AS_PROMPT); break; case WP_FRONTEND_KEY_CHARACTER: if (key->ch == 'F' || key->ch == 'f') { state->menu_index = 0U; state->menu_item_index = 0U; } else if (key->ch == 'H' || key->ch == 'h') { state->menu_index = count - 1U; state->menu_item_index = 0U; } break; case WP_FRONTEND_KEY_NONE: case WP_FRONTEND_KEY_PAGE_UP: case WP_FRONTEND_KEY_PAGE_DOWN: case WP_FRONTEND_KEY_HOME: case WP_FRONTEND_KEY_END: case WP_FRONTEND_KEY_DELETE: case WP_FRONTEND_KEY_BACKSPACE: case WP_FRONTEND_KEY_TAB: default: break; } } static void wp_frontend_process_exit_prompt(WpFrontendState *state, const WpFrontendKey *key) { if (state == NULL || key == NULL) { return; } if (key->kind == WP_FRONTEND_KEY_CHARACTER && (key->ch == 'Y' || key->ch == 'y')) { if (wp_frontend_save(state)) { state->running = 0; } return; } if (key->kind == WP_FRONTEND_KEY_CHARACTER && (key->ch == 'N' || key->ch == 'n')) { state->running = 0; return; } if (key->kind == WP_FRONTEND_KEY_COMMAND && (key->command == WP_CMD_CANCEL || key->command == WP_CMD_EXIT)) { state->exit_prompt = 0; wp_frontend_set_message(state, ""); } } static void wp_frontend_insert_text(WpFrontendState *state, const char *text, size_t len) { if (state == NULL || text == NULL) { return; } if (wp_document_model_insert_host_text_at_text_offset(&state->model, state->cursor_text_offset, text, len)) { state->cursor_text_offset++; state->dirty = 1; wp_frontend_set_message(state, ""); wp_frontend_apply_model(state); } else { wp_frontend_set_message(state, "Edit refused at this document position."); } } static void wp_frontend_process_key(WpFrontendState *state, const WpFrontendKey *key) { char text[2]; if (state == NULL || key == NULL || key->kind == WP_FRONTEND_KEY_NONE) { return; } if (state->dialog != WP_FRONTEND_DIALOG_NONE) { wp_frontend_process_dialog_key(state, key); return; } if (state->menu_active) { wp_frontend_process_menu_key(state, key); return; } if (state->exit_prompt) { wp_frontend_process_exit_prompt(state, key); return; } switch (key->kind) { case WP_FRONTEND_KEY_CHARACTER: text[0] = (char)key->ch; text[1] = '\0'; wp_frontend_insert_text(state, text, 1U); break; case WP_FRONTEND_KEY_ENTER: wp_frontend_insert_text(state, "\n", 1U); break; case WP_FRONTEND_KEY_TAB: wp_frontend_insert_text(state, "\t", 1U); break; case WP_FRONTEND_KEY_BACKSPACE: if (state->cursor_text_offset != 0U && wp_document_model_delete_text_units(&state->model, state->cursor_text_offset - 1U, 1U)) { state->cursor_text_offset--; state->dirty = 1; wp_frontend_set_message(state, ""); wp_frontend_apply_model(state); } else { wp_frontend_set_message(state, "Backspace refused at this document position."); } break; case WP_FRONTEND_KEY_DELETE: wp_frontend_refresh_cursor(state); if (state->cursor_text_offset < state->text_units && wp_document_model_delete_text_units(&state->model, state->cursor_text_offset, 1U)) { state->dirty = 1; wp_frontend_set_message(state, ""); wp_frontend_apply_model(state); } else { wp_frontend_set_message(state, "Delete refused at this document position."); } break; case WP_FRONTEND_KEY_LEFT: if (state->cursor_text_offset != 0U) { state->cursor_text_offset--; } break; case WP_FRONTEND_KEY_RIGHT: wp_frontend_refresh_cursor(state); if (state->cursor_text_offset < state->text_units) { state->cursor_text_offset++; } break; case WP_FRONTEND_KEY_UP: wp_frontend_move_to_row_delta(state, -1); break; case WP_FRONTEND_KEY_DOWN: wp_frontend_move_to_row_delta(state, 1); break; case WP_FRONTEND_KEY_PAGE_UP: wp_frontend_move_to_row_delta(state, -(int)WP_FRONTEND_CONTENT_ROWS); break; case WP_FRONTEND_KEY_PAGE_DOWN: wp_frontend_move_to_row_delta(state, (int)WP_FRONTEND_CONTENT_ROWS); break; case WP_FRONTEND_KEY_HOME: wp_frontend_refresh_cursor(state); state->cursor_text_offset = wp_frontend_offset_for_row_col(&state->model, state->cursor_page, state->cursor_row, 0U); break; case WP_FRONTEND_KEY_END: wp_frontend_refresh_cursor(state); state->cursor_text_offset = wp_frontend_offset_for_row_col(&state->model, state->cursor_page, state->cursor_row, WP_SCREEN_WIDTH - 1U); break; case WP_FRONTEND_KEY_COMMAND: wp_frontend_process_command(state, key->command); break; case WP_FRONTEND_KEY_SAVE_AS: state->menu_active = 0; wp_frontend_open_dialog(state, WP_FRONTEND_DIALOG_SAVE_AS, state->has_filename ? state->filename : WP_FRONTEND_UNTITLED_SAVE_PATH, WP_FRONTEND_SAVE_AS_PROMPT); break; case WP_FRONTEND_KEY_NONE: default: break; } } static int wp_frontend_load(WpFrontendState *state, const char *filename) { if (state == NULL) { return 0; } memset(state, 0, sizeof(*state)); state->running = 1; wp_document_model_init(&state->model); if (filename == NULL || filename[0] == '\0') { if (!wp_frontend_load_blank_document(state)) { return 0; } } else if (!wp_frontend_load_document(state, filename, 0)) { return 0; } wp_display_init(&state->db); wp_frontend_refresh_cursor(state); return 1; } static void wp_frontend_destroy(WpFrontendState *state) { if (state == NULL) { return; } wp_document_model_free(&state->model); if (state->loaded) { wp_file_free(&state->file); state->loaded = 0; } } int wp_msdos_frontend_run(const char *filename) { WpFrontendState *state; int result = 0; state = (WpFrontendState *)malloc(sizeof(*state)); if (state == NULL) { fprintf(stderr, "word unperfect: out of memory opening GUI\n"); return 1; } if (!wp_frontend_load(state, filename)) { fprintf(stderr, "word unperfect: could not open GUI document '%s'\n", filename != NULL ? filename : "(null)"); wp_frontend_destroy(state); free(state); return 1; } #ifdef __WATCOMC__ wp_display_init(&state->db); wp_display_clear(&state->db); render_msdos_buffer(&state->db); #else set_conio_terminal_mode(); #endif while (state->running) { WpFrontendKey key; if (!wp_frontend_render(state)) { result = 2; } render_msdos_buffer(&state->db); key = wp_frontend_read_key(); wp_frontend_process_key(state, &key); } #ifdef __WATCOMC__ wp_display_init(&state->db); wp_display_clear(&state->db); render_msdos_buffer(&state->db); #else reset_terminal_mode(); #endif wp_frontend_destroy(state); free(state); return result; }
Commit message
This repository is read-only for this account.
Repository snapshot
Current branch
main
Visibility
public
Your access
Read
Remote
None
File activity
View file history