diff options
Diffstat (limited to 'editbox.c')
| -rw-r--r-- | editbox.c | 719 | 
1 files changed, 719 insertions, 0 deletions
| diff --git a/editbox.c b/editbox.c new file mode 100644 index 0000000000000..7889024196dc2 --- /dev/null +++ b/editbox.c @@ -0,0 +1,719 @@ +/* + *  $Id: editbox.c,v 1.54 2010/04/28 21:02:01 tom Exp $ + * + *  editbox.c -- implements the edit box + * + *  Copyright 2007-2009,2010 Thomas E. Dickey + * + *  This program is free software; you can redistribute it and/or modify + *  it under the terms of the GNU Lesser General Public License, version 2.1 + * + *  This program is distributed in the hope that it will be useful, but + *  WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + *  Lesser General Public License for more details. + * + *  You should have received a copy of the GNU Lesser General Public + *  License along with this program; if not, write to + *	Free Software Foundation, Inc. + *	51 Franklin St., Fifth Floor + *	Boston, MA 02110, USA. + */ + +#include <dialog.h> +#include <dlg_keys.h> + +#include <sys/stat.h> + +#define sTEXT -1 + +static void +fail_list(void) +{ +    dlg_exiterr("File too large"); +} + +static void +grow_list(char ***list, int *have, int want) +{ +    if (want > *have) { +	size_t last = (size_t) *have; +	size_t need = (size_t) (want | 31) + 3; +	*have = (int) need; +	(*list) = dlg_realloc(char *, need, *list); +	if ((*list) == 0) { +	    fail_list(); +	} +	while (++last < need) { +	    (*list)[last] = 0; +	} +    } +} + +static void +load_list(const char *file, char ***list, int *rows) +{ +    FILE *fp; +    char *blob = 0; +    struct stat sb; +    unsigned n, pass; +    unsigned need; +    size_t size; + +    *list = 0; +    *rows = 0; + +    if (stat(file, &sb) < 0 || +	(sb.st_mode & S_IFMT) != S_IFREG) +	dlg_exiterr("Not a file: %s", file); + +    size = (size_t) sb.st_size; +    if ((blob = dlg_malloc(char, size + 1)) == 0) +	  fail_list(); +    blob[size] = '\0'; + +    if ((fp = fopen(file, "r")) == 0) +	dlg_exiterr("Cannot open: %s", file); +    size = fread(blob, sizeof(char), size, fp); +    fclose(fp); + +    for (pass = 0; pass < 2; ++pass) { +	int first = TRUE; +	need = 0; +	for (n = 0; n < size; ++n) { +	    if (first && pass) { +		(*list)[need] = blob + n; +		first = FALSE; +	    } +	    if (blob[n] == '\n') { +		first = TRUE; +		++need; +		if (pass) +		    blob[n] = '\0'; +	    } +	} +	if (pass) { +	    if (need == 0) { +		(*list)[0] = dlg_strclone(""); +		(*list)[1] = 0; +	    } else { +		for (n = 0; n < need; ++n) { +		    (*list)[n] = dlg_strclone((*list)[n]); +		} +		(*list)[need] = 0; +	    } +	} else { +	    grow_list(list, rows, (int) need + 1); +	} +    } +    free(blob); +} + +static void +free_list(char ***list, int *rows) +{ +    if (*list != 0) { +	int n; +	for (n = 0; n < (*rows); ++n) { +	    if ((*list)[n] != 0) +		free((*list)[n]); +	} +	free(*list); +	*list = 0; +    } +    *rows = 0; +} + +/* + * Display a single row in the editing window: + * thisrow is the actual row number that's being displayed. + * show_row is the row number that's highlighted for edit. + * base_row is the first row number in the window + */ +static bool +display_one(WINDOW *win, +	    char *text, +	    int thisrow, +	    int show_row, +	    int base_row, +	    int chr_offset) +{ +    bool result; + +    if (text != 0) { +	dlg_show_string(win, +			text, +			chr_offset, +			((thisrow == show_row) +			 ? form_active_text_attr +			 : form_text_attr), +			thisrow - base_row, +			0, +			getmaxx(win), +			FALSE, +			FALSE); +	result = TRUE; +    } else { +	result = FALSE; +    } +    return result; +} + +static void +display_all(WINDOW *win, +	    char **list, +	    int show_row, +	    int firstrow, +	    int lastrow, +	    int chr_offset) +{ +    int limit = getmaxy(win); +    int row; + +    dlg_attr_clear(win, getmaxy(win), getmaxx(win), dialog_attr); +    if (lastrow - firstrow >= limit) +	lastrow = firstrow + limit; +    for (row = firstrow; row < lastrow; ++row) { +	if (!display_one(win, list[row], +			 row, show_row, firstrow, +			 (row == show_row) ? chr_offset : 0)) +	    break; +    } +} + +static int +size_list(char **list) +{ +    int result = 0; + +    if (list != 0) { +	while (*list++ != 0) { +	    ++result; +	} +    } +    return result; +} + +static bool +scroll_to(int pagesize, int rows, int *base_row, int *this_row, int target) +{ +    bool result = FALSE; + +    if (target < *base_row) { +	if (target < 0) { +	    if (*base_row == 0 && *this_row == 0) { +		beep(); +	    } else { +		*this_row = 0; +		*base_row = 0; +		result = TRUE; +	    } +	} else { +	    *this_row = target; +	    *base_row = target; +	    result = TRUE; +	} +    } else if (target >= rows) { +	if (*this_row < rows - 1) { +	    *this_row = rows - 1; +	    *base_row = rows - 1; +	    result = TRUE; +	} else { +	    beep(); +	} +    } else if (target >= *base_row + pagesize) { +	*this_row = target; +	*base_row = target; +	result = TRUE; +    } else { +	*this_row = target; +	result = FALSE; +    } +    if (pagesize < rows) { +	if (*base_row + pagesize >= rows) { +	    *base_row = rows - pagesize; +	} +    } else { +	*base_row = 0; +    } +    return result; +} + +static int +col_to_chr_offset(const char *text, int col) +{ +    const int *cols = dlg_index_columns(text); +    const int *indx = dlg_index_wchars(text); +    bool found = FALSE; +    int result = 0; +    unsigned n; +    unsigned len = (unsigned) dlg_count_wchars(text); + +    for (n = 0; n < len; ++n) { +	if (cols[n] <= col && cols[n + 1] > col) { +	    result = indx[n]; +	    found = TRUE; +	    break; +	} +    } +    if (!found && len && cols[len] == col) { +	result = indx[len]; +    } +    return result; +} + +#define SCROLL_TO(target) show_all = scroll_to(pagesize, listsize, &base_row, &thisrow, target) + +#define PREV_ROW (*list)[thisrow - 1] +#define THIS_ROW (*list)[thisrow] +#define NEXT_ROW (*list)[thisrow + 1] + +#define UPDATE_COL(input) col_offset = dlg_edit_offset(input, chr_offset, box_width) + +static int +widest_line(char **list) +{ +    int result = MAX_LEN; +    char *value; + +    if (list != 0) { +	while ((value = *list++) != 0) { +	    int check = (int) strlen(value); +	    if (check > result) +		result = check; +	} +    } +    return result; +} + +#define NAVIGATE_BINDINGS \ +	DLG_KEYS_DATA( DLGK_GRID_DOWN,	KEY_DOWN ), \ +	DLG_KEYS_DATA( DLGK_GRID_RIGHT,	KEY_RIGHT ), \ +	DLG_KEYS_DATA( DLGK_GRID_LEFT,	KEY_LEFT ), \ +	DLG_KEYS_DATA( DLGK_GRID_UP,	KEY_UP ), \ +	DLG_KEYS_DATA( DLGK_FIELD_NEXT,	TAB ), \ +	DLG_KEYS_DATA( DLGK_FIELD_PREV,	KEY_BTAB ), \ +	DLG_KEYS_DATA( DLGK_PAGE_FIRST,	KEY_HOME ), \ +	DLG_KEYS_DATA( DLGK_PAGE_LAST,	KEY_END ), \ +	DLG_KEYS_DATA( DLGK_PAGE_LAST,	KEY_LL ), \ +	DLG_KEYS_DATA( DLGK_PAGE_NEXT,	KEY_NPAGE ), \ +	DLG_KEYS_DATA( DLGK_PAGE_NEXT,	DLGK_MOUSE(KEY_NPAGE) ), \ +	DLG_KEYS_DATA( DLGK_PAGE_PREV,	KEY_PPAGE ), \ +	DLG_KEYS_DATA( DLGK_PAGE_PREV,	DLGK_MOUSE(KEY_PPAGE) ) +/* + * Display a dialog box for editing a copy of a file + */ +int +dlg_editbox(const char *title, +	    char ***list, +	    int *rows, +	    int height, +	    int width) +{ +    /* *INDENT-OFF* */ +    static DLG_KEYS_BINDING binding[] = { +	ENTERKEY_BINDINGS, +	NAVIGATE_BINDINGS, +	END_KEYS_BINDING +    }; +    static DLG_KEYS_BINDING binding2[] = { +	INPUTSTR_BINDINGS, +	ENTERKEY_BINDINGS, +	NAVIGATE_BINDINGS, +	END_KEYS_BINDING +    }; +    /* *INDENT-ON* */ + +#ifdef KEY_RESIZE +    int old_height = height; +    int old_width = width; +#endif +    int x, y, box_y, box_x, box_height, box_width; +    int show_buttons; +    int thisrow, base_row, lastrow; +    int goal_col = -1; +    int col_offset = 0; +    int chr_offset = 0; +    int key, fkey, code; +    int pagesize; +    int listsize = size_list(*list); +    int result = DLG_EXIT_UNKNOWN; +    int state; +    size_t max_len = (size_t) dlg_max_input(widest_line(*list)); +    char *input, *buffer; +    bool show_all, show_one, was_mouse; +    WINDOW *dialog; +    WINDOW *editing; +    DIALOG_VARS save_vars; +    const char **buttons = dlg_ok_labels(); +    int mincols = (3 * COLS / 4); + +    dlg_save_vars(&save_vars); +    dialog_vars.separate_output = TRUE; + +    dlg_does_output(); + +    buffer = dlg_malloc(char, max_len + 1); +    assert_ptr(buffer, "dlg_editbox"); + +    thisrow = base_row = lastrow = 0; + +#ifdef KEY_RESIZE +  retry: +#endif +    show_buttons = TRUE; +    state = dialog_vars.defaultno ? dlg_defaultno_button() : sTEXT; +    key = fkey = 0; + +    dlg_button_layout(buttons, &mincols); +    dlg_auto_size(title, "", &height, &width, 3 * LINES / 4, mincols); +    dlg_print_size(height, width); +    dlg_ctl_size(height, width); + +    x = dlg_box_x_ordinate(width); +    y = dlg_box_y_ordinate(height); + +    dialog = dlg_new_window(height, width, y, x); +    dlg_register_window(dialog, "editbox", binding); +    dlg_register_buttons(dialog, "editbox", buttons); + +    dlg_mouse_setbase(x, y); + +    dlg_draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr); +    dlg_draw_bottom_box(dialog); +    dlg_draw_title(dialog, title); + +    wattrset(dialog, dialog_attr); + +    /* Draw the editing field in a box */ +    box_y = MARGIN + 0; +    box_x = MARGIN + 1; +    box_width = width - 2 - (2 * MARGIN); +    box_height = height - (4 * MARGIN); + +    dlg_draw_box(dialog, +		 box_y, +		 box_x, +		 box_height, +		 box_width, +		 border_attr, dialog_attr); +    dlg_mouse_mkbigregion(box_y + MARGIN, +			  box_x + MARGIN, +			  box_height - (2 * MARGIN), +			  box_width - (2 * MARGIN), +			  KEY_MAX, 1, 1, 3); +    editing = dlg_sub_window(dialog, +			     box_height - (2 * MARGIN), +			     box_width - (2 * MARGIN), +			     getbegy(dialog) + box_y + 1, +			     getbegx(dialog) + box_x + 1); +    dlg_register_window(editing, "editbox", binding2); + +    show_all = TRUE; +    show_one = FALSE; +    pagesize = getmaxy(editing); + +    while (result == DLG_EXIT_UNKNOWN) { +	int edit = 0; + +	if (show_all) { +	    display_all(editing, *list, thisrow, base_row, listsize, chr_offset); +	    display_one(editing, THIS_ROW, +			thisrow, thisrow, base_row, chr_offset); +	    show_all = FALSE; +	    show_one = TRUE; +	} else { +	    if (thisrow != lastrow) { +		display_one(editing, (*list)[lastrow], +			    lastrow, thisrow, base_row, 0); +		show_one = TRUE; +	    } +	} +	if (show_one) { +	    display_one(editing, THIS_ROW, +			thisrow, thisrow, base_row, chr_offset); +	    getyx(editing, y, x); +	    dlg_draw_scrollbar(dialog, +			       base_row, +			       base_row, +			       base_row + pagesize, +			       listsize, +			       box_x, +			       box_x + getmaxx(editing), +			       box_y + 0, +			       box_y + getmaxy(editing) + 1, +			       dialog_attr, +			       border_attr); +	    wmove(editing, y, x); +	    show_one = FALSE; +	} +	lastrow = thisrow; +	input = THIS_ROW; + +	/* +	 * The last field drawn determines where the cursor is shown: +	 */ +	if (show_buttons) { +	    show_buttons = FALSE; +	    UPDATE_COL(input); +	    if (state != sTEXT) { +		display_one(editing, input, thisrow, +			    -1, base_row, 0); +		wrefresh(editing); +	    } +	    dlg_draw_buttons(dialog, +			     height - 2, +			     0, +			     buttons, +			     (state != sTEXT) ? state : 99, +			     FALSE, +			     width); +	    if (state == sTEXT) { +		display_one(editing, input, thisrow, +			    thisrow, base_row, chr_offset); +	    } +	} + +	key = dlg_mouse_wgetch((state == sTEXT) ? editing : dialog, &fkey); +	if (key == ERR) { +	    result = DLG_EXIT_ERROR; +	    break; +	} else if (key == ESC) { +	    result = DLG_EXIT_ESC; +	    break; +	} +	if (state != sTEXT) { +	    if (dlg_result_key(key, fkey, &result)) +		break; +	} + +	was_mouse = (fkey && is_DLGK_MOUSE(key)); +	if (was_mouse) +	    key -= M_EVENT; + +	/* +	 * Handle mouse clicks first, since we want to know if this is a +	 * button, or something that dlg_edit_string() should handle. +	 */ +	if (fkey +	    && was_mouse +	    && (code = dlg_ok_buttoncode(key)) >= 0) { +	    result = code; +	    continue; +	} + +	if (was_mouse +	    && (key >= KEY_MAX)) { +	    int wide = getmaxx(editing); +	    int cell = key - KEY_MAX; +	    thisrow = (cell / wide) + base_row; +	    col_offset = (cell % wide); +	    chr_offset = col_to_chr_offset(THIS_ROW, col_offset); +	    show_one = TRUE; +	    if (state != sTEXT) { +		state = sTEXT; +		show_buttons = TRUE; +	    } +	    continue; +	} else if (was_mouse && key >= KEY_MIN) { +	    key = dlg_lookup_key(dialog, key, &fkey); +	} + +	if (state == sTEXT) {	/* editing box selected */ +	    /* +	     * Intercept scrolling keys that dlg_edit_string() does not +	     * understand. +	     */ +	    if (fkey) { +		bool moved = TRUE; + +		switch (key) { +		case DLGK_GRID_UP: +		    SCROLL_TO(thisrow - 1); +		    break; +		case DLGK_GRID_DOWN: +		    SCROLL_TO(thisrow + 1); +		    break; +		case DLGK_PAGE_FIRST: +		    SCROLL_TO(0); +		    break; +		case DLGK_PAGE_LAST: +		    SCROLL_TO(listsize); +		    break; +		case DLGK_PAGE_NEXT: +		    SCROLL_TO(base_row + pagesize); +		    break; +		case DLGK_PAGE_PREV: +		    if (thisrow > base_row) { +			SCROLL_TO(base_row); +		    } else { +			SCROLL_TO(base_row - pagesize); +		    } +		    break; +		case DLGK_DELETE_LEFT: +		    if (chr_offset == 0) { +			if (thisrow == 0) { +			    beep(); +			} else { +			    size_t len = (strlen(THIS_ROW) + +					  strlen(PREV_ROW) + 1); +			    char *tmp = dlg_malloc(char, len); + +			    assert_ptr(tmp, "dlg_editbox"); + +			    chr_offset = dlg_count_wchars(PREV_ROW); +			    UPDATE_COL(PREV_ROW); +			    goal_col = col_offset; + +			    sprintf(tmp, "%s%s", PREV_ROW, THIS_ROW); +			    if (len > max_len) +				tmp[max_len] = '\0'; + +			    free(PREV_ROW); +			    PREV_ROW = tmp; +			    for (y = thisrow; y < listsize; ++y) { +				(*list)[y] = (*list)[y + 1]; +			    } +			    --listsize; +			    --thisrow; +			    SCROLL_TO(thisrow); + +			    show_all = TRUE; +			} +		    } else { +			/* dlg_edit_string() can handle this case */ +			moved = FALSE; +		    } +		    break; +		default: +		    moved = FALSE; +		    break; +		} +		if (moved) { +		    if (thisrow != lastrow) { +			if (goal_col < 0) +			    goal_col = col_offset; +			chr_offset = col_to_chr_offset(THIS_ROW, goal_col); +		    } else { +			UPDATE_COL(THIS_ROW); +		    } +		    continue; +		} +	    } +	    strncpy(buffer, input, max_len - 1)[max_len - 1] = '\0'; +	    edit = dlg_edit_string(buffer, &chr_offset, key, fkey, FALSE); + +	    if (edit) { +		goal_col = UPDATE_COL(input); +		if (strcmp(input, buffer)) { +		    free(input); +		    THIS_ROW = dlg_strclone(buffer); +		    input = THIS_ROW; +		} +		display_one(editing, input, thisrow, +			    thisrow, base_row, chr_offset); +		continue; +	    } +	} + +	/* handle non-functionkeys */ +	if (!fkey && (code = dlg_char_to_button(key, buttons)) >= 0) { +	    dlg_del_window(dialog); +	    result = dlg_ok_buttoncode(code); +	    continue; +	} + +	/* handle functionkeys */ +	if (fkey) { +	    switch (key) { +	    case DLGK_FIELD_PREV: +		show_buttons = TRUE; +		state = dlg_prev_ok_buttonindex(state, sTEXT); +		break; +	    case DLGK_FIELD_NEXT: +		show_buttons = TRUE; +		state = dlg_next_ok_buttonindex(state, sTEXT); +		break; +	    case DLGK_ENTER: +		if (state == sTEXT) { +		    const int *indx = dlg_index_wchars(THIS_ROW); +		    int split = indx[chr_offset]; +		    char *tmp = dlg_strclone(THIS_ROW + split); + +		    assert_ptr(tmp, "dlg_editbox"); +		    grow_list(list, rows, listsize + 1); +		    ++listsize; +		    for (y = listsize; y > thisrow; --y) { +			(*list)[y] = (*list)[y - 1]; +		    } +		    THIS_ROW[split] = '\0'; +		    ++thisrow; +		    chr_offset = 0; +		    col_offset = 0; +		    THIS_ROW = tmp; +		    SCROLL_TO(thisrow); +		    show_all = TRUE; +		} else { +		    result = dlg_ok_buttoncode(state); +		} +		break; +#ifdef KEY_RESIZE +	    case KEY_RESIZE: +		/* reset data */ +		height = old_height; +		width = old_width; +		/* repaint */ +		dlg_clear(); +		dlg_del_window(editing); +		dlg_del_window(dialog); +		refresh(); +		dlg_mouse_free_regions(); +		goto retry; +#endif +	    default: +		beep(); +		break; +	    } +	} else { +	    if ((key == ' ') && (state != sTEXT)) { +		result = dlg_ok_buttoncode(state); +	    } else { +		beep(); +	    } +	} +    } + +    dlg_unregister_window(editing); +    dlg_del_window(editing); +    dlg_del_window(dialog); +    dlg_mouse_free_regions(); + +    /* +     * The caller's copy of the (*list)[] array has been updated, but for +     * consistency with the other widgets, we put the "real" result in +     * the output buffer. +     */ +    if (result == DLG_EXIT_OK) { +	int n; +	for (n = 0; n < listsize; ++n) { +	    dlg_add_result((*list)[n]); +	    dlg_add_separator(); +	} +    } +    free(buffer); +    dlg_restore_vars(&save_vars); +    return result; +} + +int +dialog_editbox(const char *title, const char *file, int height, int width) +{ +    int result; +    char **list; +    int rows; + +    load_list(file, &list, &rows); +    result = dlg_editbox(title, &list, &rows, height, width); +    free_list(&list, &rows); +    return result; +} | 
