]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/dialog/editbox.c
bhnd(9): Fix a few mandoc related issues
[FreeBSD/FreeBSD.git] / contrib / dialog / editbox.c
1 /*
2  *  $Id: editbox.c,v 1.70 2018/06/19 22:57:01 tom Exp $
3  *
4  *  editbox.c -- implements the edit box
5  *
6  *  Copyright 2007-2016,2018 Thomas E. Dickey
7  *
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  *
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.
15  *
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.
21  */
22
23 #include <dialog.h>
24 #include <dlg_keys.h>
25
26 #include <sys/stat.h>
27
28 #define sTEXT -1
29
30 static void
31 fail_list(void)
32 {
33     dlg_exiterr("File too large");
34 }
35
36 static void
37 grow_list(char ***list, int *have, int want)
38 {
39     if (want > *have) {
40         size_t last = (size_t) *have;
41         size_t need = (size_t) (want | 31) + 3;
42         *have = (int) need;
43         (*list) = dlg_realloc(char *, need, *list);
44         if ((*list) == 0) {
45             fail_list();
46         } else {
47             while (++last < need) {
48                 (*list)[last] = 0;
49             }
50         }
51     }
52 }
53
54 static void
55 load_list(const char *file, char ***list, int *rows)
56 {
57     FILE *fp;
58     char *blob = 0;
59     struct stat sb;
60     unsigned n, pass;
61     unsigned need;
62     size_t size;
63
64     *list = 0;
65     *rows = 0;
66
67     if (stat(file, &sb) < 0 ||
68         (sb.st_mode & S_IFMT) != S_IFREG)
69         dlg_exiterr("Not a file: %s", file);
70
71     size = (size_t) sb.st_size;
72     if ((blob = dlg_malloc(char, size + 2)) == 0) {
73         fail_list();
74     } else {
75         blob[size] = '\0';
76
77         if ((fp = fopen(file, "r")) == 0)
78             dlg_exiterr("Cannot open: %s", file);
79         size = fread(blob, sizeof(char), size, fp);
80         fclose(fp);
81
82         /*
83          * If the file is not empty, ensure that it ends with a newline.
84          */
85         if (size != 0 && blob[size - 1] != '\n') {
86             blob[++size - 1] = '\n';
87             blob[size] = '\0';
88         }
89
90         for (pass = 0; pass < 2; ++pass) {
91             int first = TRUE;
92             need = 0;
93             for (n = 0; n < size; ++n) {
94                 if (first && pass) {
95                     (*list)[need] = blob + n;
96                     first = FALSE;
97                 }
98                 if (blob[n] == '\n') {
99                     first = TRUE;
100                     ++need;
101                     if (pass)
102                         blob[n] = '\0';
103                 }
104             }
105             if (pass) {
106                 if (need == 0) {
107                     (*list)[0] = dlg_strclone("");
108                     (*list)[1] = 0;
109                 } else {
110                     for (n = 0; n < need; ++n) {
111                         (*list)[n] = dlg_strclone((*list)[n]);
112                     }
113                     (*list)[need] = 0;
114                 }
115             } else {
116                 grow_list(list, rows, (int) need + 1);
117             }
118         }
119         free(blob);
120     }
121 }
122
123 static void
124 free_list(char ***list, int *rows)
125 {
126     if (*list != 0) {
127         int n;
128         for (n = 0; n < (*rows); ++n) {
129             if ((*list)[n] != 0)
130                 free((*list)[n]);
131         }
132         free(*list);
133         *list = 0;
134     }
135     *rows = 0;
136 }
137
138 /*
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
143  */
144 static bool
145 display_one(WINDOW *win,
146             char *text,
147             int thisrow,
148             int show_row,
149             int base_row,
150             int chr_offset)
151 {
152     bool result;
153
154     if (text != 0) {
155         dlg_show_string(win,
156                         text,
157                         chr_offset,
158                         ((thisrow == show_row)
159                          ? form_active_text_attr
160                          : form_text_attr),
161                         thisrow - base_row,
162                         0,
163                         getmaxx(win),
164                         FALSE,
165                         FALSE);
166         result = TRUE;
167     } else {
168         result = FALSE;
169     }
170     return result;
171 }
172
173 static void
174 display_all(WINDOW *win,
175             char **list,
176             int show_row,
177             int firstrow,
178             int lastrow,
179             int chr_offset)
180 {
181     int limit = getmaxy(win);
182     int row;
183
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))
191             break;
192     }
193 }
194
195 static int
196 size_list(char **list)
197 {
198     int result = 0;
199
200     if (list != 0) {
201         while (*list++ != 0) {
202             ++result;
203         }
204     }
205     return result;
206 }
207
208 static bool
209 scroll_to(int pagesize, int rows, int *base_row, int *this_row, int target)
210 {
211     bool result = FALSE;
212
213     if (target < *base_row) {
214         if (target < 0) {
215             if (*base_row == 0 && *this_row == 0) {
216                 beep();
217             } else {
218                 *this_row = 0;
219                 *base_row = 0;
220                 result = TRUE;
221             }
222         } else {
223             *this_row = target;
224             *base_row = target;
225             result = TRUE;
226         }
227     } else if (target >= rows) {
228         if (*this_row < rows - 1) {
229             *this_row = rows - 1;
230             *base_row = rows - 1;
231             result = TRUE;
232         } else {
233             beep();
234         }
235     } else if (target >= *base_row + pagesize) {
236         *this_row = target;
237         *base_row = target;
238         result = TRUE;
239     } else {
240         *this_row = target;
241         result = FALSE;
242     }
243     if (pagesize < rows) {
244         if (*base_row + pagesize >= rows) {
245             *base_row = rows - pagesize;
246         }
247     } else {
248         *base_row = 0;
249     }
250     return result;
251 }
252
253 static int
254 col_to_chr_offset(const char *text, int col)
255 {
256     const int *cols = dlg_index_columns(text);
257     const int *indx = dlg_index_wchars(text);
258     bool found = FALSE;
259     int result = 0;
260     unsigned n;
261     unsigned len = (unsigned) dlg_count_wchars(text);
262
263     for (n = 0; n < len; ++n) {
264         if (cols[n] <= col && cols[n + 1] > col) {
265             result = indx[n];
266             found = TRUE;
267             break;
268         }
269     }
270     if (!found && len && cols[len] == col) {
271         result = indx[len];
272     }
273     return result;
274 }
275
276 #define SCROLL_TO(target) show_all = scroll_to(pagesize, listsize, &base_row, &thisrow, target)
277
278 #define PREV_ROW (*list)[thisrow - 1]
279 #define THIS_ROW (*list)[thisrow]
280 #define NEXT_ROW (*list)[thisrow + 1]
281
282 #define UPDATE_COL(input) col_offset = dlg_edit_offset(input, chr_offset, box_width)
283
284 static int
285 widest_line(char **list)
286 {
287     int result = MAX_LEN;
288     char *value;
289
290     if (list != 0) {
291         while ((value = *list++) != 0) {
292             int check = (int) strlen(value);
293             if (check > result)
294                 result = check;
295         }
296     }
297     return result;
298 }
299
300 #define NAVIGATE_BINDINGS \
301         DLG_KEYS_DATA( DLGK_GRID_DOWN,  KEY_DOWN ), \
302         DLG_KEYS_DATA( DLGK_GRID_RIGHT, KEY_RIGHT ), \
303         DLG_KEYS_DATA( DLGK_GRID_LEFT,  KEY_LEFT ), \
304         DLG_KEYS_DATA( DLGK_GRID_UP,    KEY_UP ), \
305         DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ), \
306         DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ), \
307         DLG_KEYS_DATA( DLGK_PAGE_FIRST, KEY_HOME ), \
308         DLG_KEYS_DATA( DLGK_PAGE_LAST,  KEY_END ), \
309         DLG_KEYS_DATA( DLGK_PAGE_LAST,  KEY_LL ), \
310         DLG_KEYS_DATA( DLGK_PAGE_NEXT,  KEY_NPAGE ), \
311         DLG_KEYS_DATA( DLGK_PAGE_NEXT,  DLGK_MOUSE(KEY_NPAGE) ), \
312         DLG_KEYS_DATA( DLGK_PAGE_PREV,  KEY_PPAGE ), \
313         DLG_KEYS_DATA( DLGK_PAGE_PREV,  DLGK_MOUSE(KEY_PPAGE) )
314 /*
315  * Display a dialog box for editing a copy of a file
316  */
317 int
318 dlg_editbox(const char *title,
319             char ***list,
320             int *rows,
321             int height,
322             int width)
323 {
324     /* *INDENT-OFF* */
325     static DLG_KEYS_BINDING binding[] = {
326         HELPKEY_BINDINGS,
327         ENTERKEY_BINDINGS,
328         NAVIGATE_BINDINGS,
329         TOGGLEKEY_BINDINGS,
330         END_KEYS_BINDING
331     };
332     static DLG_KEYS_BINDING binding2[] = {
333         INPUTSTR_BINDINGS,
334         HELPKEY_BINDINGS,
335         ENTERKEY_BINDINGS,
336         NAVIGATE_BINDINGS,
337         /* no TOGGLEKEY_BINDINGS, since that includes space... */
338         END_KEYS_BINDING
339     };
340     /* *INDENT-ON* */
341
342 #ifdef KEY_RESIZE
343     int old_height = height;
344     int old_width = width;
345 #endif
346     int x, y, box_y, box_x, box_height, box_width;
347     int show_buttons;
348     int thisrow, base_row, lastrow;
349     int goal_col = -1;
350     int col_offset = 0;
351     int chr_offset = 0;
352     int key, fkey, code;
353     int pagesize;
354     int listsize = size_list(*list);
355     int result = DLG_EXIT_UNKNOWN;
356     int state;
357     size_t max_len = (size_t) dlg_max_input(widest_line(*list));
358     char *input, *buffer;
359     bool show_all, show_one, was_mouse;
360     bool first_trace = TRUE;
361     WINDOW *dialog;
362     WINDOW *editing;
363     DIALOG_VARS save_vars;
364     const char **buttons = dlg_ok_labels();
365     int mincols = (3 * COLS / 4);
366
367     DLG_TRACE(("# editbox args:\n"));
368     DLG_TRACE2S("title", title);
369     /* FIXME dump the rows & list */
370     DLG_TRACE2N("height", height);
371     DLG_TRACE2N("width", width);
372
373     dlg_save_vars(&save_vars);
374     dialog_vars.separate_output = TRUE;
375
376     dlg_does_output();
377
378     buffer = dlg_malloc(char, max_len + 1);
379     assert_ptr(buffer, "dlg_editbox");
380
381     thisrow = base_row = lastrow = 0;
382
383 #ifdef KEY_RESIZE
384   retry:
385 #endif
386     show_buttons = TRUE;
387     state = dialog_vars.default_button >= 0 ? dlg_default_button() : sTEXT;
388     fkey = 0;
389
390     dlg_button_layout(buttons, &mincols);
391     dlg_auto_size(title, "", &height, &width, 3 * LINES / 4, mincols);
392     dlg_print_size(height, width);
393     dlg_ctl_size(height, width);
394
395     x = dlg_box_x_ordinate(width);
396     y = dlg_box_y_ordinate(height);
397
398     dialog = dlg_new_window(height, width, y, x);
399     dlg_register_window(dialog, "editbox", binding);
400     dlg_register_buttons(dialog, "editbox", buttons);
401
402     dlg_mouse_setbase(x, y);
403
404     dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr);
405     dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr);
406     dlg_draw_title(dialog, title);
407
408     dlg_attrset(dialog, dialog_attr);
409
410     /* Draw the editing field in a box */
411     box_y = MARGIN + 0;
412     box_x = MARGIN + 1;
413     box_width = width - 2 - (2 * MARGIN);
414     box_height = height - (4 * MARGIN);
415
416     dlg_draw_box(dialog,
417                  box_y,
418                  box_x,
419                  box_height,
420                  box_width,
421                  border_attr, border2_attr);
422     dlg_mouse_mkbigregion(box_y + MARGIN,
423                           box_x + MARGIN,
424                           box_height - (2 * MARGIN),
425                           box_width - (2 * MARGIN),
426                           KEY_MAX, 1, 1, 3);
427     editing = dlg_sub_window(dialog,
428                              box_height - (2 * MARGIN),
429                              box_width - (2 * MARGIN),
430                              getbegy(dialog) + box_y + 1,
431                              getbegx(dialog) + box_x + 1);
432     dlg_register_window(editing, "editbox2", binding2);
433
434     show_all = TRUE;
435     show_one = FALSE;
436     pagesize = getmaxy(editing);
437
438     while (result == DLG_EXIT_UNKNOWN) {
439         int edit = 0;
440
441         if (show_all) {
442             display_all(editing, *list, thisrow, base_row, listsize, chr_offset);
443             display_one(editing, THIS_ROW,
444                         thisrow, thisrow, base_row, chr_offset);
445             show_all = FALSE;
446             show_one = TRUE;
447         } else {
448             if (thisrow != lastrow) {
449                 display_one(editing, (*list)[lastrow],
450                             lastrow, thisrow, base_row, 0);
451                 show_one = TRUE;
452             }
453         }
454         if (show_one) {
455             display_one(editing, THIS_ROW,
456                         thisrow, thisrow, base_row, chr_offset);
457             getyx(editing, y, x);
458             dlg_draw_scrollbar(dialog,
459                                base_row,
460                                base_row,
461                                base_row + pagesize,
462                                listsize,
463                                box_x,
464                                box_x + getmaxx(editing),
465                                box_y + 0,
466                                box_y + getmaxy(editing) + 1,
467                                border2_attr,
468                                border_attr);
469             wmove(editing, y, x);
470             show_one = FALSE;
471         }
472         lastrow = thisrow;
473         input = THIS_ROW;
474
475         /*
476          * The last field drawn determines where the cursor is shown:
477          */
478         if (show_buttons) {
479             show_buttons = FALSE;
480             UPDATE_COL(input);
481             if (state != sTEXT) {
482                 display_one(editing, input, thisrow,
483                             -1, base_row, 0);
484                 wrefresh(editing);
485             }
486             dlg_draw_buttons(dialog,
487                              height - 2,
488                              0,
489                              buttons,
490                              (state != sTEXT) ? state : 99,
491                              FALSE,
492                              width);
493             if (state == sTEXT) {
494                 display_one(editing, input, thisrow,
495                             thisrow, base_row, chr_offset);
496             }
497         }
498
499         if (first_trace) {
500             first_trace = FALSE;
501             dlg_trace_win(dialog);
502         }
503
504         key = dlg_mouse_wgetch((state == sTEXT) ? editing : dialog, &fkey);
505         if (key == ERR) {
506             result = DLG_EXIT_ERROR;
507             break;
508         } else if (key == ESC) {
509             result = DLG_EXIT_ESC;
510             break;
511         }
512         if (state != sTEXT) {
513             if (dlg_result_key(key, fkey, &result))
514                 break;
515         }
516
517         was_mouse = (fkey && is_DLGK_MOUSE(key));
518         if (was_mouse)
519             key -= M_EVENT;
520
521         /*
522          * Handle mouse clicks first, since we want to know if this is a
523          * button, or something that dlg_edit_string() should handle.
524          */
525         if (fkey
526             && was_mouse
527             && (code = dlg_ok_buttoncode(key)) >= 0) {
528             result = code;
529             continue;
530         }
531
532         if (was_mouse
533             && (key >= KEY_MAX)) {
534             int wide = getmaxx(editing);
535             int cell = key - KEY_MAX;
536             int check = (cell / wide) + base_row;
537             if (check < listsize) {
538                 thisrow = check;
539                 col_offset = (cell % wide);
540                 chr_offset = col_to_chr_offset(THIS_ROW, col_offset);
541                 show_one = TRUE;
542                 if (state != sTEXT) {
543                     state = sTEXT;
544                     show_buttons = TRUE;
545                 }
546             } else {
547                 beep();
548             }
549             continue;
550         } else if (was_mouse && key >= KEY_MIN) {
551             key = dlg_lookup_key(dialog, key, &fkey);
552         }
553
554         if (state == sTEXT) {   /* editing box selected */
555             /*
556              * Intercept scrolling keys that dlg_edit_string() does not
557              * understand.
558              */
559             if (fkey) {
560                 bool moved = TRUE;
561
562                 switch (key) {
563                 case DLGK_GRID_UP:
564                     SCROLL_TO(thisrow - 1);
565                     break;
566                 case DLGK_GRID_DOWN:
567                     SCROLL_TO(thisrow + 1);
568                     break;
569                 case DLGK_PAGE_FIRST:
570                     SCROLL_TO(0);
571                     break;
572                 case DLGK_PAGE_LAST:
573                     SCROLL_TO(listsize);
574                     break;
575                 case DLGK_PAGE_NEXT:
576                     SCROLL_TO(base_row + pagesize);
577                     break;
578                 case DLGK_PAGE_PREV:
579                     if (thisrow > base_row) {
580                         SCROLL_TO(base_row);
581                     } else {
582                         SCROLL_TO(base_row - pagesize);
583                     }
584                     break;
585                 case DLGK_DELETE_LEFT:
586                     if (chr_offset == 0) {
587                         if (thisrow == 0) {
588                             beep();
589                         } else {
590                             size_t len = (strlen(THIS_ROW) +
591                                           strlen(PREV_ROW) + 1);
592                             char *tmp = dlg_malloc(char, len);
593
594                             assert_ptr(tmp, "dlg_editbox");
595
596                             chr_offset = dlg_count_wchars(PREV_ROW);
597                             UPDATE_COL(PREV_ROW);
598                             goal_col = col_offset;
599
600                             sprintf(tmp, "%s%s", PREV_ROW, THIS_ROW);
601                             if (len > max_len)
602                                 tmp[max_len] = '\0';
603
604                             free(PREV_ROW);
605                             PREV_ROW = tmp;
606                             for (y = thisrow; y < listsize; ++y) {
607                                 (*list)[y] = (*list)[y + 1];
608                             }
609                             --listsize;
610                             --thisrow;
611                             SCROLL_TO(thisrow);
612
613                             show_all = TRUE;
614                         }
615                     } else {
616                         /* dlg_edit_string() can handle this case */
617                         moved = FALSE;
618                     }
619                     break;
620                 default:
621                     moved = FALSE;
622                     break;
623                 }
624                 if (moved) {
625                     if (thisrow != lastrow) {
626                         if (goal_col < 0)
627                             goal_col = col_offset;
628                         chr_offset = col_to_chr_offset(THIS_ROW, goal_col);
629                     } else {
630                         UPDATE_COL(THIS_ROW);
631                     }
632                     continue;
633                 }
634             }
635             strncpy(buffer, input, max_len - 1)[max_len - 1] = '\0';
636             edit = dlg_edit_string(buffer, &chr_offset, key, fkey, FALSE);
637
638             if (edit) {
639                 goal_col = UPDATE_COL(input);
640                 if (strcmp(input, buffer)) {
641                     free(input);
642                     THIS_ROW = dlg_strclone(buffer);
643                     input = THIS_ROW;
644                 }
645                 display_one(editing, input, thisrow,
646                             thisrow, base_row, chr_offset);
647                 continue;
648             }
649         }
650
651         /* handle non-functionkeys */
652         if (!fkey && (code = dlg_char_to_button(key, buttons)) >= 0) {
653             dlg_del_window(dialog);
654             result = dlg_ok_buttoncode(code);
655             continue;
656         }
657
658         /* handle functionkeys */
659         if (fkey) {
660             switch (key) {
661             case DLGK_GRID_UP:
662             case DLGK_GRID_LEFT:
663             case DLGK_FIELD_PREV:
664                 show_buttons = TRUE;
665                 state = dlg_prev_ok_buttonindex(state, sTEXT);
666                 break;
667             case DLGK_GRID_RIGHT:
668             case DLGK_GRID_DOWN:
669             case DLGK_FIELD_NEXT:
670                 show_buttons = TRUE;
671                 state = dlg_next_ok_buttonindex(state, sTEXT);
672                 break;
673             case DLGK_ENTER:
674                 if (state == sTEXT) {
675                     const int *indx = dlg_index_wchars(THIS_ROW);
676                     int split = indx[chr_offset];
677                     char *tmp = dlg_strclone(THIS_ROW + split);
678
679                     assert_ptr(tmp, "dlg_editbox");
680                     grow_list(list, rows, listsize + 1);
681                     ++listsize;
682                     for (y = listsize; y > thisrow; --y) {
683                         (*list)[y] = (*list)[y - 1];
684                     }
685                     THIS_ROW[split] = '\0';
686                     ++thisrow;
687                     chr_offset = 0;
688                     col_offset = 0;
689                     THIS_ROW = tmp;
690                     SCROLL_TO(thisrow);
691                     show_all = TRUE;
692                 } else {
693                     result = dlg_ok_buttoncode(state);
694                 }
695                 break;
696 #ifdef KEY_RESIZE
697             case KEY_RESIZE:
698                 dlg_will_resize(dialog);
699                 /* reset data */
700                 height = old_height;
701                 width = old_width;
702                 dlg_clear();
703                 dlg_unregister_window(editing);
704                 dlg_del_window(editing);
705                 dlg_del_window(dialog);
706                 dlg_mouse_free_regions();
707                 /* repaint */
708                 goto retry;
709 #endif
710             case DLGK_TOGGLE:
711                 if (state != sTEXT) {
712                     result = dlg_ok_buttoncode(state);
713                 } else {
714                     beep();
715                 }
716                 break;
717             default:
718                 beep();
719                 break;
720             }
721         } else {
722             beep();
723         }
724     }
725
726     dlg_unregister_window(editing);
727     dlg_del_window(editing);
728     dlg_del_window(dialog);
729     dlg_mouse_free_regions();
730
731     /*
732      * The caller's copy of the (*list)[] array has been updated, but for
733      * consistency with the other widgets, we put the "real" result in
734      * the output buffer.
735      */
736     if (result == DLG_EXIT_OK) {
737         int n;
738         for (n = 0; n < listsize; ++n) {
739             dlg_add_result((*list)[n]);
740             dlg_add_separator();
741         }
742         dlg_add_last_key(-1);
743     }
744     free(buffer);
745     dlg_restore_vars(&save_vars);
746     return result;
747 }
748
749 int
750 dialog_editbox(const char *title, const char *file, int height, int width)
751 {
752     int result;
753     char **list;
754     int rows;
755
756     load_list(file, &list, &rows);
757     result = dlg_editbox(title, &list, &rows, height, width);
758     free_list(&list, &rows);
759     return result;
760 }