2 * $Id: editbox.c,v 1.63 2015/01/25 22:57:49 tom Exp $
4 * editbox.c -- implements the edit box
6 * Copyright 2007-2013,2015 Thomas E. Dickey
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License, version 2.1
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this program; if not, write to
18 * Free Software Foundation, Inc.
19 * 51 Franklin St., Fifth Floor
20 * Boston, MA 02110, USA.
33 dlg_exiterr("File too large");
37 grow_list(char ***list
, int *have
, int want
)
40 size_t last
= (size_t) *have
;
41 size_t need
= (size_t) (want
| 31) + 3;
43 (*list
) = dlg_realloc(char *, need
, *list
);
47 while (++last
< need
) {
55 load_list(const char *file
, char ***list
, int *rows
)
67 if (stat(file
, &sb
) < 0 ||
68 (sb
.st_mode
& S_IFMT
) != S_IFREG
)
69 dlg_exiterr("Not a file: %s", file
);
71 size
= (size_t) sb
.st_size
;
72 if ((blob
= dlg_malloc(char, size
+ 1)) == 0) {
77 if ((fp
= fopen(file
, "r")) == 0)
78 dlg_exiterr("Cannot open: %s", file
);
79 size
= fread(blob
, sizeof(char), size
, fp
);
82 for (pass
= 0; pass
< 2; ++pass
) {
85 for (n
= 0; n
< size
; ++n
) {
87 (*list
)[need
] = blob
+ n
;
90 if (blob
[n
] == '\n') {
99 (*list
)[0] = dlg_strclone("");
102 for (n
= 0; n
< need
; ++n
) {
103 (*list
)[n
] = dlg_strclone((*list
)[n
]);
108 grow_list(list
, rows
, (int) need
+ 1);
116 free_list(char ***list
, int *rows
)
120 for (n
= 0; n
< (*rows
); ++n
) {
131 * Display a single row in the editing window:
132 * thisrow is the actual row number that's being displayed.
133 * show_row is the row number that's highlighted for edit.
134 * base_row is the first row number in the window
137 display_one(WINDOW
*win
,
150 ((thisrow
== show_row
)
151 ? form_active_text_attr
166 display_all(WINDOW
*win
,
173 int limit
= getmaxy(win
);
176 dlg_attr_clear(win
, getmaxy(win
), getmaxx(win
), dialog_attr
);
177 if (lastrow
- firstrow
>= limit
)
178 lastrow
= firstrow
+ limit
;
179 for (row
= firstrow
; row
< lastrow
; ++row
) {
180 if (!display_one(win
, list
[row
],
181 row
, show_row
, firstrow
,
182 (row
== show_row
) ? chr_offset
: 0))
188 size_list(char **list
)
193 while (*list
++ != 0) {
201 scroll_to(int pagesize
, int rows
, int *base_row
, int *this_row
, int target
)
205 if (target
< *base_row
) {
207 if (*base_row
== 0 && *this_row
== 0) {
219 } else if (target
>= rows
) {
220 if (*this_row
< rows
- 1) {
221 *this_row
= rows
- 1;
222 *base_row
= rows
- 1;
227 } else if (target
>= *base_row
+ pagesize
) {
235 if (pagesize
< rows
) {
236 if (*base_row
+ pagesize
>= rows
) {
237 *base_row
= rows
- pagesize
;
246 col_to_chr_offset(const char *text
, int col
)
248 const int *cols
= dlg_index_columns(text
);
249 const int *indx
= dlg_index_wchars(text
);
253 unsigned len
= (unsigned) dlg_count_wchars(text
);
255 for (n
= 0; n
< len
; ++n
) {
256 if (cols
[n
] <= col
&& cols
[n
+ 1] > col
) {
262 if (!found
&& len
&& cols
[len
] == col
) {
268 #define SCROLL_TO(target) show_all = scroll_to(pagesize, listsize, &base_row, &thisrow, target)
270 #define PREV_ROW (*list)[thisrow - 1]
271 #define THIS_ROW (*list)[thisrow]
272 #define NEXT_ROW (*list)[thisrow + 1]
274 #define UPDATE_COL(input) col_offset = dlg_edit_offset(input, chr_offset, box_width)
277 widest_line(char **list
)
279 int result
= MAX_LEN
;
283 while ((value
= *list
++) != 0) {
284 int check
= (int) strlen(value
);
292 #define NAVIGATE_BINDINGS \
293 DLG_KEYS_DATA( DLGK_GRID_DOWN, KEY_DOWN ), \
294 DLG_KEYS_DATA( DLGK_GRID_RIGHT, KEY_RIGHT ), \
295 DLG_KEYS_DATA( DLGK_GRID_LEFT, KEY_LEFT ), \
296 DLG_KEYS_DATA( DLGK_GRID_UP, KEY_UP ), \
297 DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ), \
298 DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ), \
299 DLG_KEYS_DATA( DLGK_PAGE_FIRST, KEY_HOME ), \
300 DLG_KEYS_DATA( DLGK_PAGE_LAST, KEY_END ), \
301 DLG_KEYS_DATA( DLGK_PAGE_LAST, KEY_LL ), \
302 DLG_KEYS_DATA( DLGK_PAGE_NEXT, KEY_NPAGE ), \
303 DLG_KEYS_DATA( DLGK_PAGE_NEXT, DLGK_MOUSE(KEY_NPAGE) ), \
304 DLG_KEYS_DATA( DLGK_PAGE_PREV, KEY_PPAGE ), \
305 DLG_KEYS_DATA( DLGK_PAGE_PREV, DLGK_MOUSE(KEY_PPAGE) )
307 * Display a dialog box for editing a copy of a file
310 dlg_editbox(const char *title
,
317 static DLG_KEYS_BINDING binding
[] = {
323 static DLG_KEYS_BINDING binding2
[] = {
333 int old_height
= height
;
334 int old_width
= width
;
336 int x
, y
, box_y
, box_x
, box_height
, box_width
;
338 int thisrow
, base_row
, lastrow
;
344 int listsize
= size_list(*list
);
345 int result
= DLG_EXIT_UNKNOWN
;
347 size_t max_len
= (size_t) dlg_max_input(widest_line(*list
));
348 char *input
, *buffer
;
349 bool show_all
, show_one
, was_mouse
;
350 bool first_trace
= TRUE
;
353 DIALOG_VARS save_vars
;
354 const char **buttons
= dlg_ok_labels();
355 int mincols
= (3 * COLS
/ 4);
357 dlg_save_vars(&save_vars
);
358 dialog_vars
.separate_output
= TRUE
;
362 buffer
= dlg_malloc(char, max_len
+ 1);
363 assert_ptr(buffer
, "dlg_editbox");
365 thisrow
= base_row
= lastrow
= 0;
371 state
= dialog_vars
.default_button
>= 0 ? dlg_default_button() : sTEXT
;
374 dlg_button_layout(buttons
, &mincols
);
375 dlg_auto_size(title
, "", &height
, &width
, 3 * LINES
/ 4, mincols
);
376 dlg_print_size(height
, width
);
377 dlg_ctl_size(height
, width
);
379 x
= dlg_box_x_ordinate(width
);
380 y
= dlg_box_y_ordinate(height
);
382 dialog
= dlg_new_window(height
, width
, y
, x
);
383 dlg_register_window(dialog
, "editbox", binding
);
384 dlg_register_buttons(dialog
, "editbox", buttons
);
386 dlg_mouse_setbase(x
, y
);
388 dlg_draw_box2(dialog
, 0, 0, height
, width
, dialog_attr
, border_attr
, border2_attr
);
389 dlg_draw_bottom_box2(dialog
, border_attr
, border2_attr
, dialog_attr
);
390 dlg_draw_title(dialog
, title
);
392 (void) wattrset(dialog
, dialog_attr
);
394 /* Draw the editing field in a box */
397 box_width
= width
- 2 - (2 * MARGIN
);
398 box_height
= height
- (4 * MARGIN
);
405 border_attr
, border2_attr
);
406 dlg_mouse_mkbigregion(box_y
+ MARGIN
,
408 box_height
- (2 * MARGIN
),
409 box_width
- (2 * MARGIN
),
411 editing
= dlg_sub_window(dialog
,
412 box_height
- (2 * MARGIN
),
413 box_width
- (2 * MARGIN
),
414 getbegy(dialog
) + box_y
+ 1,
415 getbegx(dialog
) + box_x
+ 1);
416 dlg_register_window(editing
, "editbox2", binding2
);
420 pagesize
= getmaxy(editing
);
422 while (result
== DLG_EXIT_UNKNOWN
) {
426 display_all(editing
, *list
, thisrow
, base_row
, listsize
, chr_offset
);
427 display_one(editing
, THIS_ROW
,
428 thisrow
, thisrow
, base_row
, chr_offset
);
432 if (thisrow
!= lastrow
) {
433 display_one(editing
, (*list
)[lastrow
],
434 lastrow
, thisrow
, base_row
, 0);
439 display_one(editing
, THIS_ROW
,
440 thisrow
, thisrow
, base_row
, chr_offset
);
441 getyx(editing
, y
, x
);
442 dlg_draw_scrollbar(dialog
,
448 box_x
+ getmaxx(editing
),
450 box_y
+ getmaxy(editing
) + 1,
453 wmove(editing
, y
, x
);
460 * The last field drawn determines where the cursor is shown:
463 show_buttons
= FALSE
;
465 if (state
!= sTEXT
) {
466 display_one(editing
, input
, thisrow
,
470 dlg_draw_buttons(dialog
,
474 (state
!= sTEXT
) ? state
: 99,
477 if (state
== sTEXT
) {
478 display_one(editing
, input
, thisrow
,
479 thisrow
, base_row
, chr_offset
);
485 dlg_trace_win(dialog
);
488 key
= dlg_mouse_wgetch((state
== sTEXT
) ? editing
: dialog
, &fkey
);
490 result
= DLG_EXIT_ERROR
;
492 } else if (key
== ESC
) {
493 result
= DLG_EXIT_ESC
;
496 if (state
!= sTEXT
) {
497 if (dlg_result_key(key
, fkey
, &result
))
501 was_mouse
= (fkey
&& is_DLGK_MOUSE(key
));
506 * Handle mouse clicks first, since we want to know if this is a
507 * button, or something that dlg_edit_string() should handle.
511 && (code
= dlg_ok_buttoncode(key
)) >= 0) {
517 && (key
>= KEY_MAX
)) {
518 int wide
= getmaxx(editing
);
519 int cell
= key
- KEY_MAX
;
520 thisrow
= (cell
/ wide
) + base_row
;
521 col_offset
= (cell
% wide
);
522 chr_offset
= col_to_chr_offset(THIS_ROW
, col_offset
);
524 if (state
!= sTEXT
) {
529 } else if (was_mouse
&& key
>= KEY_MIN
) {
530 key
= dlg_lookup_key(dialog
, key
, &fkey
);
533 if (state
== sTEXT
) { /* editing box selected */
535 * Intercept scrolling keys that dlg_edit_string() does not
543 SCROLL_TO(thisrow
- 1);
546 SCROLL_TO(thisrow
+ 1);
548 case DLGK_PAGE_FIRST
:
555 SCROLL_TO(base_row
+ pagesize
);
558 if (thisrow
> base_row
) {
561 SCROLL_TO(base_row
- pagesize
);
564 case DLGK_DELETE_LEFT
:
565 if (chr_offset
== 0) {
569 size_t len
= (strlen(THIS_ROW
) +
570 strlen(PREV_ROW
) + 1);
571 char *tmp
= dlg_malloc(char, len
);
573 assert_ptr(tmp
, "dlg_editbox");
575 chr_offset
= dlg_count_wchars(PREV_ROW
);
576 UPDATE_COL(PREV_ROW
);
577 goal_col
= col_offset
;
579 sprintf(tmp
, "%s%s", PREV_ROW
, THIS_ROW
);
585 for (y
= thisrow
; y
< listsize
; ++y
) {
586 (*list
)[y
] = (*list
)[y
+ 1];
595 /* dlg_edit_string() can handle this case */
604 if (thisrow
!= lastrow
) {
606 goal_col
= col_offset
;
607 chr_offset
= col_to_chr_offset(THIS_ROW
, goal_col
);
609 UPDATE_COL(THIS_ROW
);
614 strncpy(buffer
, input
, max_len
- 1)[max_len
- 1] = '\0';
615 edit
= dlg_edit_string(buffer
, &chr_offset
, key
, fkey
, FALSE
);
618 goal_col
= UPDATE_COL(input
);
619 if (strcmp(input
, buffer
)) {
621 THIS_ROW
= dlg_strclone(buffer
);
624 display_one(editing
, input
, thisrow
,
625 thisrow
, base_row
, chr_offset
);
630 /* handle non-functionkeys */
631 if (!fkey
&& (code
= dlg_char_to_button(key
, buttons
)) >= 0) {
632 dlg_del_window(dialog
);
633 result
= dlg_ok_buttoncode(code
);
637 /* handle functionkeys */
642 case DLGK_FIELD_PREV
:
644 state
= dlg_prev_ok_buttonindex(state
, sTEXT
);
646 case DLGK_GRID_RIGHT
:
648 case DLGK_FIELD_NEXT
:
650 state
= dlg_next_ok_buttonindex(state
, sTEXT
);
653 if (state
== sTEXT
) {
654 const int *indx
= dlg_index_wchars(THIS_ROW
);
655 int split
= indx
[chr_offset
];
656 char *tmp
= dlg_strclone(THIS_ROW
+ split
);
658 assert_ptr(tmp
, "dlg_editbox");
659 grow_list(list
, rows
, listsize
+ 1);
661 for (y
= listsize
; y
> thisrow
; --y
) {
662 (*list
)[y
] = (*list
)[y
- 1];
664 THIS_ROW
[split
] = '\0';
672 result
= dlg_ok_buttoncode(state
);
682 dlg_del_window(editing
);
683 dlg_del_window(dialog
);
685 dlg_mouse_free_regions();
693 if ((key
== ' ') && (state
!= sTEXT
)) {
694 result
= dlg_ok_buttoncode(state
);
701 dlg_unregister_window(editing
);
702 dlg_del_window(editing
);
703 dlg_del_window(dialog
);
704 dlg_mouse_free_regions();
707 * The caller's copy of the (*list)[] array has been updated, but for
708 * consistency with the other widgets, we put the "real" result in
711 if (result
== DLG_EXIT_OK
) {
713 for (n
= 0; n
< listsize
; ++n
) {
714 dlg_add_result((*list
)[n
]);
717 dlg_add_last_key(-1);
720 dlg_restore_vars(&save_vars
);
725 dialog_editbox(const char *title
, const char *file
, int height
, int width
)
731 load_list(file
, &list
, &rows
);
732 result
= dlg_editbox(title
, &list
, &rows
, height
, width
);
733 free_list(&list
, &rows
);