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