1 /* Textarea form item handlers */
2
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
6
7 #ifndef _GNU_SOURCE
8 #define _GNU_SOURCE /* XXX: we want memrchr() ! */
9 #endif
10
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #ifdef HAVE_UNISTD_H
15 #include <unistd.h>
16 #endif
17
18 #include "elinks.h"
19
20 #include "bfu/dialog.h"
21 #include "document/document.h"
22 #include "document/forms.h"
23 #include "document/html/parser.h"
24 #include "document/html/renderer.h"
25 #include "document/view.h"
26 #include "intl/gettext/libintl.h"
27 #include "session/session.h"
28 #include "terminal/draw.h"
29 #include "terminal/window.h"
30 #include "util/error.h"
31 #include "util/file.h"
32 #include "util/memory.h"
33 #include "util/string.h"
34 #include "viewer/action.h"
35 #include "viewer/text/form.h"
36 #include "viewer/text/textarea.h"
37 #include "viewer/text/view.h"
38
39
40 struct line_info {
41 int start;
42 int end;
43 };
44
45 /* We add two extra entries to the table so the ending info can be added
46 * without reallocating. */
47 #define realloc_line_info(info, size) \
48 mem_align_alloc(info, size, (size) + 3, struct line_info, 0xFF)
49
50 /* Allocates a line_info table describing the layout of the textarea buffer.
51 *
52 * @width is max width and the offset at which text will be wrapped
53 * @wrap controls how the wrapping of text is performed
54 * @format is non zero the @text will be modified to make it suitable for
55 * encoding it for form posting
56 */
57 static struct line_info *
format_text(unsigned char * text,int width,enum form_wrap wrap,int format)58 format_text(unsigned char *text, int width, enum form_wrap wrap, int format)
59 {
60 struct line_info *line = NULL;
61 int line_number = 0;
62 int begin = 0;
63 int pos = 0;
64 int skip;
65
66 assert(text);
67 if_assert_failed return NULL;
68
69 /* Allocate the ending entries */
70 if (!realloc_line_info(&line, 0))
71 return NULL;
72
73 while (text[pos]) {
74 if (text[pos] == '\n') {
75 skip = 1;
76
77 } else if (wrap == FORM_WRAP_NONE || pos - begin < width) {
78 pos++;
79 continue;
80
81 } else {
82 unsigned char *wrappos;
83
84 /* Find a place to wrap the text */
85 wrappos = memrchr(&text[begin], ' ', pos - begin);
86 if (wrappos) {
87 /* When formatting text for form submitting we
88 * have to apply the wrapping mode. */
89 if (wrap == FORM_WRAP_HARD && format)
90 *wrappos = '\n';
91 pos = wrappos - text;
92 }
93 skip = !!wrappos;
94 }
95
96 if (!realloc_line_info(&line, line_number)) {
97 mem_free_if(line);
98 return NULL;
99 }
100
101 line[line_number].start = begin;
102 line[line_number++].end = pos;
103 begin = pos += skip;
104 }
105
106 /* Flush the last text before the loop ended */
107 line[line_number].start = begin;
108 line[line_number++].end = pos;
109
110 /* Add end marker */
111 line[line_number].start = line[line_number].end = -1;
112
113 return line;
114 }
115
116 /* Searches for @cursor_position (aka. position in the fs->value string) for
117 * the corresponding entry in the @line info. Returns the index or -1 if
118 * position is not found. */
119 static int
get_textarea_line_number(struct line_info * line,int cursor_position)120 get_textarea_line_number(struct line_info *line, int cursor_position)
121 {
122 int idx;
123
124 for (idx = 0; line[idx].start != -1; idx++) {
125 int wrap;
126
127 if (cursor_position < line[idx].start) continue;
128
129 wrap = (line[idx + 1].start == line[idx].end);
130 if (cursor_position >= line[idx].end + !wrap) continue;
131
132 return idx;
133 }
134
135 return -1;
136 }
137
138 /* Fixes up the vpos and vypos members of the form_state. Returns the
139 * logical position in the textarea view. */
140 int
area_cursor(struct form_control * fc,struct form_state * fs)141 area_cursor(struct form_control *fc, struct form_state *fs)
142 {
143 struct line_info *line;
144 int x, y;
145
146 assert(fc && fs);
147 if_assert_failed return 0;
148
149 line = format_text(fs->value, fc->cols, fc->wrap, 0);
150 if (!line) return 0;
151
152 y = get_textarea_line_number(line, fs->state);
153 if (y == -1) {
154 mem_free(line);
155 return 0;
156 }
157
158 x = fs->state - line[y].start;
159
160 mem_free(line);
161
162 if (fc->wrap && x == fc->cols) x--;
163
164 int_bounds(&fs->vpos, x - fc->cols + 1, x);
165 int_bounds(&fs->vypos, y - fc->rows + 1, y);
166
167 x -= fs->vpos;
168 y -= fs->vypos;
169
170 return y * fc->cols + x;
171 }
172
173 void
draw_textarea(struct terminal * term,struct form_state * fs,struct document_view * doc_view,struct link * link)174 draw_textarea(struct terminal *term, struct form_state *fs,
175 struct document_view *doc_view, struct link *link)
176 {
177 struct line_info *line, *linex;
178 struct form_control *fc;
179 struct box *box;
180 int vx, vy;
181 int sl, ye;
182 int x, y;
183
184 assert(term && doc_view && doc_view->document && doc_view->vs && link);
185 if_assert_failed return;
186 fc = get_link_form_control(link);
187 assertm(fc, "link %d has no form control", (int) (link - doc_view->document->links));
188 if_assert_failed return;
189
190 box = &doc_view->box;
191 vx = doc_view->vs->x;
192 vy = doc_view->vs->y;
193
194 if (!link->npoints) return;
195 area_cursor(fc, fs);
196 linex = format_text(fs->value, fc->cols, fc->wrap, 0);
197 if (!linex) return;
198 line = linex;
199 sl = fs->vypos;
200 while (line->start != -1 && sl) sl--, line++;
201
202 x = link->points[0].x + box->x - vx;
203 y = link->points[0].y + box->y - vy;
204 ye = y + fc->rows;
205
206 for (; line->start != -1 && y < ye; line++, y++) {
207 int i;
208
209 if (!row_is_in_box(box, y)) continue;
210
211 for (i = 0; i < fc->cols; i++) {
212 unsigned char data;
213 int xi = x + i;
214
215 if (!col_is_in_box(box, xi))
216 continue;
217
218 if (i >= -fs->vpos
219 && i + fs->vpos < line->end - line->start)
220 data = fs->value[line->start + i + fs->vpos];
221 else
222 data = '_';
223
224 draw_char_data(term, xi, y, data);
225 }
226 }
227
228 for (; y < ye; y++) {
229 int i;
230
231 if (!row_is_in_box(box, y)) continue;
232
233 for (i = 0; i < fc->cols; i++) {
234 int xi = x + i;
235
236 if (col_is_in_box(box, xi))
237 draw_char_data(term, xi, y, '_');
238 }
239 }
240
241 mem_free(linex);
242 }
243
244
245 unsigned char *
encode_textarea(struct submitted_value * sv)246 encode_textarea(struct submitted_value *sv)
247 {
248 struct form_control *fc;
249 struct string newtext;
250 void *blabla;
251 int i;
252
253 assert(sv && sv->value);
254 if_assert_failed return NULL;
255
256 fc = sv->form_control;
257
258 /* We need to reformat text now if it has to be wrapped hard, just
259 * before encoding it. */
260 blabla = format_text(sv->value, fc->cols, fc->wrap, 1);
261 mem_free_if(blabla);
262
263 if (!init_string(&newtext)) return NULL;
264
265 for (i = 0; sv->value[i]; i++) {
266 if (sv->value[i] != '\n')
267 add_char_to_string(&newtext, sv->value[i]);
268 else
269 add_crlf_to_string(&newtext);
270 }
271
272 return newtext.source;
273 }
274
275
276 /* We use some evil hacking in order to make external textarea editor working.
277 * We need to have some way how to be notified that the editor finished and we
278 * should reload content of the textarea. So we use global variable
279 * textarea_editor as a flag whether we have one running, and if we have, we
280 * just call textarea_edit(1, ...). Then we recover our state from static
281 * variables, reload content of textarea back from file and clean up.
282 *
283 * Unfortunately, we can't support calling of editor from non-master links
284 * session, as it would be extremely ugly to hack (you would have to transfer
285 * the content of it back to master somehow, add special flags for not deleting
286 * of 'delete' etc) and I'm not going to do that now. Inter-links communication
287 * *NEEDS* rewrite, as it looks just like quick messy hack now. --pasky */
288
289 int textarea_editor = 0;
290
291 static unsigned char *
save_textarea_file(unsigned char * value)292 save_textarea_file(unsigned char *value)
293 {
294 unsigned char *filename;
295 FILE *file = NULL;
296 int h;
297
298 filename = get_tempdir_filename("elinks-area-XXXXXX");
299 if (!filename) return NULL;
300
301 h = safe_mkstemp(filename);
302 if (h >= 0) file = fdopen(h, "w");
303
304 if (file) {
305 fwrite(value, strlen(value), 1, file);
306 fclose(file);
307 } else {
308 mem_free_set(&filename, NULL);
309 }
310
311 return filename;
312 }
313
314 void
textarea_edit(int op,struct terminal * term_,struct form_state * fs_,struct document_view * doc_view_,struct link * link_)315 textarea_edit(int op, struct terminal *term_, struct form_state *fs_,
316 struct document_view *doc_view_, struct link *link_)
317 {
318 static size_t fc_maxlength;
319 static struct form_state *fs;
320 static struct terminal *term;
321 static struct document_view *doc_view;
322 static struct link *link;
323 static unsigned char *fn;
324
325 assert (op == 0 || op == 1);
326 if_assert_failed return;
327 assert (op == 1 || term_);
328 if_assert_failed return;
329
330 if (op == 0 && get_cmd_opt_bool("anonymous")) {
331 info_box(term_, 0, N_("Error"), ALIGN_CENTER,
332 N_("You cannot launch an external"
333 " editor in the anonymous mode."));
334 return;
335 }
336
337 if (op == 0 && !term_->master) {
338 info_box(term_, 0, N_("Error"), ALIGN_CENTER,
339 N_("You can do this only on the master terminal"));
340 return;
341 }
342
343 if (op == 0 && !textarea_editor) {
344 unsigned char *ed = get_opt_str("document.browse.forms.editor");
345 unsigned char *ex;
346
347 fn = save_textarea_file(fs_->value);
348 if (!fn) return;
349
350 if (!ed || !*ed) {
351 ed = getenv("EDITOR");
352 if (!ed || !*ed) ed = "vi";
353 }
354
355 ex = straconcat(ed, " ", fn, NULL);
356 if (!ex) {
357 unlink(fn);
358 goto free_and_return;
359 }
360
361 if (fs_) fs = fs_;
362 if (doc_view_) doc_view = doc_view_;
363 if (link_) {
364 link = link_;
365 fc_maxlength = get_link_form_control(link_)->maxlength;
366 }
367 if (term_) term = term_;
368
369 exec_on_terminal(term, ex, "", 1);
370 mem_free(ex);
371
372 textarea_editor = 1;
373
374 } else if (op == 1 && fs) {
375 struct string file;
376
377 if (!init_string(&file)
378 || !add_file_to_string(&file, fn)) {
379 textarea_editor = 0;
380 goto free_and_return;
381 }
382
383 if (file.length > fc_maxlength) {
384 file.source[fc_maxlength] = '\0';
385 /* Casting size_t fc_maxlength to unsigned int
386 * and formatting it with "%u" is safe,
387 * because fc_maxlength is smaller than
388 * file.length, which is an int. */
389 info_box(term, MSGBOX_FREE_TEXT, N_("Warning"),
390 ALIGN_CENTER,
391 msg_text(term,
392 N_("You have exceeded the textarea's"
393 " size limit: your input is %d"
394 " bytes, but the maximum is %u"
395 " bytes.\n\n"
396 "Your input has been truncated,"
397 " but you can still recover the"
398 " text that you entered from"
399 " this file: %s"), file.length,
400 (unsigned int) fc_maxlength, fn));
401 } else {
402 unlink(fn);
403 }
404
405 mem_free(fs->value);
406 fs->value = file.source;
407 fs->state = file.length;
408
409 if (doc_view && link)
410 draw_form_entry(term, doc_view, link);
411
412 textarea_editor = 0;
413 goto free_and_return;
414 }
415
416 return;
417
418 free_and_return:
419 mem_free_set(&fn, NULL);
420 fs = NULL;
421 }
422
423 /* menu_func_T */
424 void
menu_textarea_edit(struct terminal * term,void * xxx,void * ses_)425 menu_textarea_edit(struct terminal *term, void *xxx, void *ses_)
426 {
427 struct session *ses = ses_;
428 struct document_view *doc_view;
429 struct link *link;
430 struct form_state *fs;
431 struct form_control *fc;
432
433 assert(term && ses);
434 if_assert_failed return;
435
436 doc_view = current_frame(ses);
437
438 assert(doc_view && doc_view->vs && doc_view->document);
439 if_assert_failed return;
440
441 link = get_current_link(doc_view);
442 if (!link) return;
443
444 fc = get_link_form_control(link);
445 if (form_field_is_readonly(fc))
446 return;
447
448 fs = find_form_state(doc_view, fc);
449 if (!fs) return;
450
451 textarea_edit(0, term, fs, doc_view, link);
452 }
453
454 static enum frame_event_status
textarea_op(struct form_state * fs,struct form_control * fc,int (* do_op)(struct form_state *,struct line_info *,int))455 textarea_op(struct form_state *fs, struct form_control *fc,
456 int (*do_op)(struct form_state *, struct line_info *, int))
457 {
458 struct line_info *line;
459 int current, state;
460
461 assert(fs && fs->value && fc);
462 if_assert_failed return FRAME_EVENT_OK;
463
464 line = format_text(fs->value, fc->cols, fc->wrap, 0);
465 if (!line) return FRAME_EVENT_OK;
466
467 current = get_textarea_line_number(line, fs->state);
468 state = fs->state;
469 if (do_op(fs, line, current)) {
470 mem_free(line);
471 return FRAME_EVENT_IGNORED;
472 }
473
474 mem_free(line);
475
476 return fs->state == state ? FRAME_EVENT_OK : FRAME_EVENT_REFRESH;
477 }
478
479 static int
do_op_home(struct form_state * fs,struct line_info * line,int current)480 do_op_home(struct form_state *fs, struct line_info *line, int current)
481 {
482 if (current != -1) fs->state = line[current].start;
483 return 0;
484 }
485
486 static int
do_op_up(struct form_state * fs,struct line_info * line,int current)487 do_op_up(struct form_state *fs, struct line_info *line, int current)
488 {
489 if (current == -1) return 0;
490 if (!current) return 1;
491
492 fs->state -= line[current].start - line[current-1].start;
493 int_upper_bound(&fs->state, line[current-1].end);
494 return 0;
495 }
496
497 static int
do_op_down(struct form_state * fs,struct line_info * line,int current)498 do_op_down(struct form_state *fs, struct line_info *line, int current)
499 {
500 if (current == -1) return 0;
501 if (line[current+1].start == -1) return 1;
502
503 fs->state += line[current+1].start - line[current].start;
504 int_upper_bound(&fs->state, line[current+1].end);
505 return 0;
506 }
507
508 static int
do_op_end(struct form_state * fs,struct line_info * line,int current)509 do_op_end(struct form_state *fs, struct line_info *line, int current)
510 {
511 if (current == -1) {
512 fs->state = strlen(fs->value);
513
514 } else {
515 int wrap = line[current + 1].start == line[current].end;
516
517 /* Don't jump to next line when wrapping. */
518 fs->state = int_max(0, line[current].end - wrap);
519 }
520 return 0;
521 }
522
523 static int
do_op_bob(struct form_state * fs,struct line_info * line,int current)524 do_op_bob(struct form_state *fs, struct line_info *line, int current)
525 {
526 if (current == -1) return 0;
527
528 fs->state -= line[current].start;
529 int_upper_bound(&fs->state, line[0].end);
530 return 0;
531 }
532
533 static int
do_op_eob(struct form_state * fs,struct line_info * line,int current)534 do_op_eob(struct form_state *fs, struct line_info *line, int current)
535 {
536 if (current == -1) {
537 fs->state = strlen(fs->value);
538
539 } else {
540 int last = get_textarea_line_number(line, strlen(fs->value));
541
542 assertm(last != -1, "line info corrupt");
543
544 fs->state += line[last].start - line[current].start;
545 int_upper_bound(&fs->state, line[last].end);
546 }
547 return 0;
548 }
549
550 enum frame_event_status
textarea_op_home(struct form_state * fs,struct form_control * fc)551 textarea_op_home(struct form_state *fs, struct form_control *fc)
552 {
553 return textarea_op(fs, fc, do_op_home);
554 }
555
556 enum frame_event_status
textarea_op_up(struct form_state * fs,struct form_control * fc)557 textarea_op_up(struct form_state *fs, struct form_control *fc)
558 {
559 return textarea_op(fs, fc, do_op_up);
560 }
561
562 enum frame_event_status
textarea_op_down(struct form_state * fs,struct form_control * fc)563 textarea_op_down(struct form_state *fs, struct form_control *fc)
564 {
565 return textarea_op(fs, fc, do_op_down);
566 }
567
568 enum frame_event_status
textarea_op_end(struct form_state * fs,struct form_control * fc)569 textarea_op_end(struct form_state *fs, struct form_control *fc)
570 {
571 return textarea_op(fs, fc, do_op_end);
572 }
573
574 /* Set the form state so the cursor is on the first line of the buffer.
575 * Preserve the column if possible. */
576 enum frame_event_status
textarea_op_bob(struct form_state * fs,struct form_control * fc)577 textarea_op_bob(struct form_state *fs, struct form_control *fc)
578 {
579 return textarea_op(fs, fc, do_op_bob);
580 }
581
582 /* Set the form state so the cursor is on the last line of the buffer. Preserve
583 * the column if possible. This is done by getting current and last line and
584 * then shifting the state by the delta of both lines start position bounding
585 * the whole thing to the end of the last line. */
586 enum frame_event_status
textarea_op_eob(struct form_state * fs,struct form_control * fc)587 textarea_op_eob(struct form_state *fs, struct form_control *fc)
588 {
589 return textarea_op(fs, fc, do_op_eob);
590 }
591
592 enum frame_event_status
textarea_op_enter(struct form_state * fs,struct form_control * fc)593 textarea_op_enter(struct form_state *fs, struct form_control *fc)
594 {
595 assert(fs && fs->value && fc);
596 if_assert_failed return FRAME_EVENT_OK;
597
598 if (form_field_is_readonly(fc)
599 || strlen(fs->value) >= fc->maxlength
600 || !insert_in_string(&fs->value, fs->state, "\n", 1))
601 return FRAME_EVENT_OK;
602
603 fs->state++;
604 return FRAME_EVENT_REFRESH;
605 }
606
607
608 void
set_textarea(struct document_view * doc_view,int direction)609 set_textarea(struct document_view *doc_view, int direction)
610 {
611 struct form_control *fc;
612 struct form_state *fs;
613 struct link *link;
614
615 assert(doc_view && doc_view->vs && doc_view->document);
616 assert(direction == 1 || direction == -1);
617 if_assert_failed return;
618
619 link = get_current_link(doc_view);
620 if (!link || link->type != LINK_AREA)
621 return;
622
623 fc = get_link_form_control(link);
624 assertm(fc, "link has no form control");
625 if_assert_failed return;
626
627 if (fc->mode == FORM_MODE_DISABLED) return;
628
629 fs = find_form_state(doc_view, fc);
630 if (!fs || !fs->value) return;
631
632 /* Depending on which way we entered the textarea move cursor so that
633 * it is available at end or start. */
634 if (direction == 1)
635 textarea_op_eob(fs, fc);
636 else
637 textarea_op_bob(fs, fc);
638 }
639