2 * $Id: editbox.c,v 1.85 2022/04/06 08:01:23 tom Exp $
4 * editbox.c -- implements the edit box
6 * Copyright 2007-2020,2022 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
10 * as published by the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this program; if not, write to
19 * Free Software Foundation, Inc.
20 * 51 Franklin St., Fifth Floor
21 * Boston, MA 02110, USA.
24 #include <dlg_internals.h>
32 dlg_exiterr("File too large");
36 grow_list(char ***list
, int *have
, int want
)
39 size_t last
= (size_t) *have
;
40 size_t need
= (size_t) (want
| 31) + 3;
42 (*list
) = dlg_realloc(char *, need
, *list
);
46 while (++last
< need
) {
54 load_list(const char *file
, char ***list
, int *rows
)
63 if (stat(file
, &sb
) < 0 ||
64 (sb
.st_mode
& S_IFMT
) != S_IFREG
)
65 dlg_exiterr("Not a file: %s", file
);
67 size
= (size_t) sb
.st_size
;
68 if ((blob
= dlg_malloc(char, size
+ 2)) == 0) {
76 if ((fp
= fopen(file
, "r")) == 0)
77 dlg_exiterr("Cannot open: %s", file
);
78 size
= fread(blob
, sizeof(char), size
, fp
);
82 * If the file is not empty, ensure that it ends with a newline.
84 if (size
!= 0 && blob
[size
- 1] != '\n') {
85 blob
[++size
- 1] = '\n';
89 for (pass
= 0; pass
< 2; ++pass
) {
93 for (n
= 0; n
< size
; ++n
) {
95 (*list
)[need
] = blob
+ n
;
98 if (blob
[n
] == '\n') {
107 (*list
)[0] = dlg_strclone("");
110 for (n
= 0; n
< need
; ++n
) {
111 (*list
)[n
] = dlg_strclone((*list
)[n
]);
116 grow_list(list
, rows
, (int) need
+ 1);
124 free_list(char ***list
, int *rows
)
128 for (n
= 0; n
< (*rows
); ++n
) {
139 * Display a single row in the editing window:
140 * thisrow is the actual row number that's being displayed.
141 * show_row is the row number that's highlighted for edit.
142 * base_row is the first row number in the window
145 display_one(WINDOW
*win
,
158 ((thisrow
== show_row
)
159 ? form_active_text_attr
174 display_all(WINDOW
*win
,
181 int limit
= getmaxy(win
);
184 dlg_attr_clear(win
, getmaxy(win
), getmaxx(win
), dialog_attr
);
185 if (lastrow
- firstrow
>= limit
)
186 lastrow
= firstrow
+ limit
;
187 for (row
= firstrow
; row
< lastrow
; ++row
) {
188 if (!display_one(win
, list
[row
],
189 row
, show_row
, firstrow
,
190 (row
== show_row
) ? chr_offset
: 0))
196 size_list(char **list
)
201 while (*list
++ != 0) {
209 scroll_to(int pagesize
, int rows
, int *base_row
, int *this_row
, int target
)
213 if (target
< *base_row
) {
215 if (*base_row
== 0 && *this_row
== 0) {
227 } else if (target
>= rows
) {
228 if (*this_row
< rows
- 1) {
229 *this_row
= rows
- 1;
230 *base_row
= rows
- 1;
235 } else if (target
>= *base_row
+ pagesize
) {
243 if (pagesize
< rows
) {
244 if (*base_row
+ pagesize
>= rows
) {
245 *base_row
= rows
- pagesize
;
254 col_to_chr_offset(const char *text
, int col
)
256 const int *cols
= dlg_index_columns(text
);
257 const int *indx
= dlg_index_wchars(text
);
261 unsigned len
= (unsigned) dlg_count_wchars(text
);
263 for (n
= 0; n
< len
; ++n
) {
264 if (cols
[n
] <= col
&& cols
[n
+ 1] > col
) {
270 if (!found
&& len
&& cols
[len
] == col
) {
276 #define Scroll_To(target) scroll_to(pagesize, listsize, &base_row, &thisrow, target)
277 #define SCROLL_TO(target) show_all = Scroll_To(target)
279 #define PREV_ROW (*list)[thisrow - 1]
280 #define THIS_ROW (*list)[thisrow]
281 #define NEXT_ROW (*list)[thisrow + 1]
283 #define UPDATE_COL(input) col_offset = dlg_edit_offset(input, chr_offset, box_width)
286 widest_line(char **list
)
288 int result
= dlg_max_input(-1);
293 while ((value
= *list
++) != 0) {
294 int check
= (int) strlen(value
);
302 #define NAVIGATE_BINDINGS \
303 DLG_KEYS_DATA( DLGK_GRID_DOWN, KEY_DOWN ), \
304 DLG_KEYS_DATA( DLGK_GRID_RIGHT, KEY_RIGHT ), \
305 DLG_KEYS_DATA( DLGK_GRID_LEFT, KEY_LEFT ), \
306 DLG_KEYS_DATA( DLGK_GRID_UP, KEY_UP ), \
307 DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ), \
308 DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ), \
309 DLG_KEYS_DATA( DLGK_PAGE_FIRST, KEY_HOME ), \
310 DLG_KEYS_DATA( DLGK_PAGE_LAST, KEY_END ), \
311 DLG_KEYS_DATA( DLGK_PAGE_LAST, KEY_LL ), \
312 DLG_KEYS_DATA( DLGK_PAGE_NEXT, KEY_NPAGE ), \
313 DLG_KEYS_DATA( DLGK_PAGE_NEXT, DLGK_MOUSE(KEY_NPAGE) ), \
314 DLG_KEYS_DATA( DLGK_PAGE_PREV, KEY_PPAGE ), \
315 DLG_KEYS_DATA( DLGK_PAGE_PREV, DLGK_MOUSE(KEY_PPAGE) )
317 * Display a dialog box for editing a copy of a file
320 dlg_editbox(const char *title
,
327 static DLG_KEYS_BINDING binding
[] = {
334 static DLG_KEYS_BINDING binding2
[] = {
339 /* no TOGGLEKEY_BINDINGS, since that includes space... */
345 int old_height
= height
;
346 int old_width
= width
;
348 int x
, y
, box_y
, box_x
, box_height
, box_width
;
350 int thisrow
, base_row
, lastrow
;
356 int listsize
= size_list(*list
);
357 int result
= DLG_EXIT_UNKNOWN
;
359 size_t max_len
= (size_t) dlg_max_input(widest_line(*list
));
361 bool show_all
, show_one
;
362 bool first_trace
= TRUE
;
365 DIALOG_VARS save_vars
;
366 const char **buttons
= dlg_ok_labels();
367 int mincols
= (3 * COLS
/ 4);
369 DLG_TRACE(("# editbox args:\n"));
370 DLG_TRACE2S("title", title
);
371 /* FIXME dump the rows & list */
372 DLG_TRACE2N("height", height
);
373 DLG_TRACE2N("width", width
);
375 dlg_save_vars(&save_vars
);
376 dialog_vars
.separate_output
= TRUE
;
380 buffer
= dlg_malloc(char, max_len
+ 1);
381 assert_ptr(buffer
, "dlg_editbox");
383 thisrow
= base_row
= lastrow
= 0;
389 state
= dialog_vars
.default_button
>= 0 ? dlg_default_button() : sTEXT
;
392 dlg_button_layout(buttons
, &mincols
);
393 dlg_auto_size(title
, "", &height
, &width
, 3 * LINES
/ 4, mincols
);
394 dlg_print_size(height
, width
);
395 dlg_ctl_size(height
, width
);
397 x
= dlg_box_x_ordinate(width
);
398 y
= dlg_box_y_ordinate(height
);
400 dialog
= dlg_new_window(height
, width
, y
, x
);
401 dlg_register_window(dialog
, "editbox", binding
);
402 dlg_register_buttons(dialog
, "editbox", buttons
);
404 dlg_mouse_setbase(x
, y
);
406 dlg_draw_box2(dialog
, 0, 0, height
, width
, dialog_attr
, border_attr
, border2_attr
);
407 dlg_draw_bottom_box2(dialog
, border_attr
, border2_attr
, dialog_attr
);
408 dlg_draw_title(dialog
, title
);
410 dlg_attrset(dialog
, dialog_attr
);
412 /* Draw the editing field in a box */
415 box_width
= width
- 2 - (2 * MARGIN
);
416 box_height
= height
- (4 * MARGIN
);
423 border_attr
, border2_attr
);
424 dlg_mouse_mkbigregion(box_y
+ MARGIN
,
426 box_height
- (2 * MARGIN
),
427 box_width
- (2 * MARGIN
),
429 editing
= dlg_sub_window(dialog
,
430 box_height
- (2 * MARGIN
),
431 box_width
- (2 * MARGIN
),
432 getbegy(dialog
) + box_y
+ 1,
433 getbegx(dialog
) + box_x
+ 1);
434 dlg_register_window(editing
, "editbox2", binding2
);
438 pagesize
= getmaxy(editing
);
440 while (result
== DLG_EXIT_UNKNOWN
) {
445 display_all(editing
, *list
, thisrow
, base_row
, listsize
, chr_offset
);
446 display_one(editing
, THIS_ROW
,
447 thisrow
, thisrow
, base_row
, chr_offset
);
451 if (thisrow
!= lastrow
) {
452 display_one(editing
, (*list
)[lastrow
],
453 lastrow
, thisrow
, base_row
, 0);
458 display_one(editing
, THIS_ROW
,
459 thisrow
, thisrow
, base_row
, chr_offset
);
460 getyx(editing
, y
, x
);
461 dlg_draw_scrollbar(dialog
,
467 box_x
+ getmaxx(editing
),
469 box_y
+ getmaxy(editing
) + 1,
472 wmove(editing
, y
, x
);
479 * The last field drawn determines where the cursor is shown:
482 show_buttons
= FALSE
;
484 if (state
!= sTEXT
) {
485 display_one(editing
, input
, thisrow
,
489 dlg_draw_buttons(dialog
,
493 (state
!= sTEXT
) ? state
: 99,
496 if (state
== sTEXT
) {
497 display_one(editing
, input
, thisrow
,
498 thisrow
, base_row
, chr_offset
);
504 dlg_trace_win(dialog
);
507 key
= dlg_mouse_wgetch((state
== sTEXT
) ? editing
: dialog
, &fkey
);
509 result
= DLG_EXIT_ERROR
;
511 } else if (key
== ESC
) {
512 result
= DLG_EXIT_ESC
;
515 if (state
!= sTEXT
) {
516 if (dlg_result_key(key
, fkey
, &result
)) {
517 if (!dlg_button_key(result
, &code
, &key
, &fkey
))
522 was_mouse
= (fkey
&& is_DLGK_MOUSE(key
));
527 * Handle mouse clicks first, since we want to know if this is a
528 * button, or something that dlg_edit_string() should handle.
532 && (code
= dlg_ok_buttoncode(key
)) >= 0) {
538 && (key
>= KEY_MAX
)) {
539 int wide
= getmaxx(editing
);
540 int cell
= key
- KEY_MAX
;
541 int check
= (cell
/ wide
) + base_row
;
542 if (check
< listsize
) {
544 col_offset
= (cell
% wide
);
545 chr_offset
= col_to_chr_offset(THIS_ROW
, col_offset
);
547 if (state
!= sTEXT
) {
555 } else if (was_mouse
&& key
>= KEY_MIN
) {
556 key
= dlg_lookup_key(dialog
, key
, &fkey
);
559 if (state
== sTEXT
) { /* editing box selected */
563 * Intercept scrolling keys that dlg_edit_string() does not
571 SCROLL_TO(thisrow
- 1);
574 SCROLL_TO(thisrow
+ 1);
576 case DLGK_PAGE_FIRST
:
583 SCROLL_TO(base_row
+ pagesize
);
586 if (thisrow
> base_row
) {
589 SCROLL_TO(base_row
- pagesize
);
592 case DLGK_DELETE_LEFT
:
593 if (chr_offset
== 0) {
597 size_t len
= (strlen(THIS_ROW
) +
598 strlen(PREV_ROW
) + 1);
599 char *tmp
= dlg_malloc(char, len
);
601 assert_ptr(tmp
, "dlg_editbox");
603 chr_offset
= dlg_count_wchars(PREV_ROW
);
604 UPDATE_COL(PREV_ROW
);
605 goal_col
= col_offset
;
607 sprintf(tmp
, "%s%s", PREV_ROW
, THIS_ROW
);
613 for (y
= thisrow
; y
< listsize
; ++y
) {
614 (*list
)[y
] = (*list
)[y
+ 1];
618 (void) Scroll_To(thisrow
);
623 /* dlg_edit_string() can handle this case */
632 if (thisrow
!= lastrow
) {
634 goal_col
= col_offset
;
635 chr_offset
= col_to_chr_offset(THIS_ROW
, goal_col
);
637 UPDATE_COL(THIS_ROW
);
642 strncpy(buffer
, input
, max_len
- 1)[max_len
- 1] = '\0';
643 if (chr_offset
> (int) (max_len
- 1))
644 chr_offset
= (int) (max_len
- 1);
645 edit
= dlg_edit_string(buffer
, &chr_offset
, key
, fkey
, FALSE
);
648 goal_col
= UPDATE_COL(input
);
649 if (strcmp(input
, buffer
)) {
651 THIS_ROW
= dlg_strclone(buffer
);
654 display_one(editing
, input
, thisrow
,
655 thisrow
, base_row
, chr_offset
);
660 /* handle non-functionkeys */
661 if (!fkey
&& (code
= dlg_char_to_button(key
, buttons
)) >= 0) {
662 dlg_del_window(dialog
);
663 result
= dlg_ok_buttoncode(code
);
667 /* handle functionkeys */
672 case DLGK_FIELD_PREV
:
674 state
= dlg_prev_ok_buttonindex(state
, sTEXT
);
676 case DLGK_GRID_RIGHT
:
678 case DLGK_FIELD_NEXT
:
680 state
= dlg_next_ok_buttonindex(state
, sTEXT
);
683 if (state
== sTEXT
) {
684 const int *indx
= dlg_index_wchars(THIS_ROW
);
685 int split
= indx
[chr_offset
];
686 char *tmp
= dlg_strclone(THIS_ROW
+ split
);
688 assert_ptr(tmp
, "dlg_editbox");
689 grow_list(list
, rows
, listsize
+ 1);
691 for (y
= listsize
; y
> thisrow
; --y
) {
692 (*list
)[y
] = (*list
)[y
- 1];
694 THIS_ROW
[split
] = '\0';
699 (void) Scroll_To(thisrow
);
702 result
= dlg_enter_buttoncode(state
);
707 result
= dlg_ok_buttoncode(state
);
711 dlg_will_resize(dialog
);
716 dlg_del_window(editing
);
717 dlg_unregister_window(editing
);
718 _dlg_resize_cleanup(dialog
);
722 if (state
!= sTEXT
) {
723 result
= dlg_ok_buttoncode(state
);
732 } else if (key
> 0) {
737 dlg_unregister_window(editing
);
738 dlg_del_window(editing
);
739 dlg_del_window(dialog
);
740 dlg_mouse_free_regions();
743 * The caller's copy of the (*list)[] array has been updated, but for
744 * consistency with the other widgets, we put the "real" result in
747 if (result
== DLG_EXIT_OK
) {
749 for (n
= 0; n
< listsize
; ++n
) {
750 dlg_add_result((*list
)[n
]);
753 dlg_add_last_key(-1);
756 dlg_restore_vars(&save_vars
);
761 dialog_editbox(const char *title
, const char *file
, int height
, int width
)
767 load_list(file
, &list
, &rows
);
768 result
= dlg_editbox(title
, &list
, &rows
, height
, width
);
769 free_list(&list
, &rows
);