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