1 /* $LynxId: LYStrings.c,v 1.214 2013/05/28 23:38:17 tom Exp $ */
2 #include <HTUtils.h>
3 #include <HTCJK.h>
4 #include <UCAux.h>
5 #include <LYGlobalDefs.h>
6 #include <LYUtils.h>
7 #include <LYStrings.h>
8 #include <GridText.h>
9 #include <LYKeymap.h>
10 #include <LYClean.h>
11 #include <LYMail.h>
12 #include <LYNews.h>
13 #include <LYOptions.h>
14 #include <LYCharSets.h>
15 #include <HTAlert.h>
16 #include <HTString.h>
17 #include <LYCharUtils.h>
18 #include <HTList.h>
19 #include <HTParse.h>
20 #ifdef USE_MOUSE
21 #include <LYMainLoop.h>
22 #endif
23
24 #ifdef DJGPP_KEYHANDLER
25 #include <pc.h>
26 #include <keys.h>
27 #endif /* DJGPP_KEYHANDLER */
28
29 #ifdef USE_COLOR_STYLE
30 #include <LYHash.h>
31 #include <AttrList.h>
32 #endif
33
34 #ifdef USE_SCROLLBAR
35 #include <LYMainLoop.h>
36 #endif
37
38 #ifdef USE_CMD_LOGGING
39 #include <LYReadCFG.h>
40 #include <LYrcFile.h>
41 #endif
42
43 #include <LYShowInfo.h>
44 #include <LYLeaks.h>
45
46 #if defined(WIN_EX)
47 #undef BUTTON_CTRL
48 #define BUTTON_CTRL 0 /* Quick hack */
49 #endif
50
51 #ifdef DEBUG_EDIT
52 #define CTRACE_EDIT(p) CTRACE(p)
53 #else
54 #define CTRACE_EDIT(p) /*nothing */
55 #endif
56
57 /*
58 * The edit_history lists allow the user to press tab when entering URL to get
59 * the closest match in the closet
60 */
61 #define LYClosetSize 100
62
63 static HTList *URL_edit_history;
64 static HTList *MAIL_edit_history;
65
66 /* If you want to add mouse support for some new platform, it's fairly
67 * simple to do. Once you've determined the X and Y coordinates of
68 * the mouse event, loop through the elements in the links[] array and
69 * see if the coordinates fall within a highlighted link area. If so,
70 * the code must set mouse_link to the index of the chosen link,
71 * and return a key value that corresponds to LYK_ACTIVATE. The
72 * LYK_ACTIVATE code in LYMainLoop.c will then check mouse_link
73 * and activate that link. If the mouse event didn't fall within a
74 * link, the code should just set mouse_link to -1 and return -1. --AMK
75 */
76
77 /* The number of the link selected w/ the mouse (-1 if none) */
78 static int mouse_link = -1;
79
80 static int have_levent;
81
82 #if defined(USE_MOUSE) && defined(NCURSES)
83 static MEVENT levent;
84 #endif
85
86 /* Return the value of mouse_link */
peek_mouse_levent(void)87 int peek_mouse_levent(void)
88 {
89 #if defined(USE_MOUSE) && defined(NCURSES)
90 if (have_levent > 0) {
91 ungetmouse(&levent);
92 have_levent--;
93 return 1;
94 }
95 #endif
96 return 0;
97 }
98
99 /* Return the value of mouse_link, erasing it */
get_mouse_link(void)100 int get_mouse_link(void)
101 {
102 int t;
103
104 t = mouse_link;
105 mouse_link = -1;
106 if (t < 0)
107 t = -1; /* Backward compatibility. */
108 return t;
109 }
110
111 /* Return the value of mouse_link */
peek_mouse_link(void)112 int peek_mouse_link(void)
113 {
114 return mouse_link;
115 }
116
fancy_mouse(WINDOW * win,int row,int * position)117 int fancy_mouse(WINDOW * win, int row,
118 int *position)
119 {
120 int cmd = LYK_DO_NOTHING;
121
122 #ifdef USE_MOUSE
123 /*********************************************************************/
124
125 #if defined(WIN_EX) && defined(PDCURSES)
126
127 request_mouse_pos();
128
129 if (BUTTON_STATUS(1)
130 && (MOUSE_X_POS >= getbegx(win) &&
131 MOUSE_X_POS < (getbegx(win) + getmaxx(win)))) {
132 int mypos = MOUSE_Y_POS - getbegy(win);
133 int delta = mypos - row;
134
135 if (mypos + 1 == getmaxy(win)) {
136 /* At the decorative border: scroll forward */
137 if (BUTTON_STATUS(1) & BUTTON1_TRIPLE_CLICKED)
138 cmd = LYK_END;
139 else if (BUTTON_STATUS(1) & BUTTON1_DOUBLE_CLICKED)
140 cmd = LYK_NEXT_PAGE;
141 else
142 cmd = LYK_NEXT_LINK;
143 } else if (mypos >= getmaxy(win)) {
144 if (BUTTON_STATUS(1) & (BUTTON1_DOUBLE_CLICKED | BUTTON1_TRIPLE_CLICKED))
145 cmd = LYK_END;
146 else
147 cmd = LYK_NEXT_PAGE;
148 } else if (mypos == 0) {
149 /* At the decorative border: scroll back */
150 if (BUTTON_STATUS(1) & BUTTON1_TRIPLE_CLICKED)
151 cmd = LYK_HOME;
152 else if (BUTTON_STATUS(1) & BUTTON1_DOUBLE_CLICKED)
153 cmd = LYK_PREV_PAGE;
154 else
155 cmd = LYK_PREV_LINK;
156 } else if (mypos < 0) {
157 if (BUTTON_STATUS(1) & (BUTTON1_DOUBLE_CLICKED | BUTTON1_TRIPLE_CLICKED))
158 cmd = LYK_HOME;
159 else
160 cmd = LYK_PREV_PAGE;
161 #ifdef KNOW_HOW_TO_TOGGLE
162 } else if (BUTTON_STATUS(1) & (BUTTON_CTRL)) {
163 cur_selection += delta;
164 cmd = LYX_TOGGLE;
165 #endif
166 } else if (BUTTON_STATUS(1) & (BUTTON_ALT | BUTTON_SHIFT | BUTTON_CTRL)) {
167 /* Probably some unrelated activity, such as selecting some text.
168 * Select, but do nothing else.
169 */
170 *position += delta;
171 cmd = -1;
172 } else {
173 /* No scrolling or overflow checks necessary. */
174 *position += delta;
175 cmd = LYK_ACTIVATE;
176 }
177 } else if (BUTTON_STATUS(1) & (BUTTON3_CLICKED | BUTTON3_DOUBLE_CLICKED | BUTTON3_TRIPLE_CLICKED)) {
178 cmd = LYK_QUIT;
179 }
180 #else
181 #if defined(NCURSES)
182 #define ButtonModifiers (BUTTON_ALT | BUTTON_SHIFT | BUTTON_CTRL)
183 MEVENT event;
184
185 getmouse(&event);
186 if ((event.bstate & (BUTTON1_CLICKED |
187 BUTTON1_DOUBLE_CLICKED |
188 BUTTON1_TRIPLE_CLICKED))) {
189 int mypos = event.y - getbegy(win);
190 int delta = mypos - row;
191
192 if ((event.x < getbegx(win) ||
193 event.x >= (getbegx(win) + getmaxx(win)))
194 && !(event.bstate & ButtonModifiers))
195 return LYK_QUIT; /* User clicked outside, wants to quit? */
196 if (mypos + 1 == getmaxy(win)) {
197 /* At the decorative border: scroll forward */
198 if (event.bstate & BUTTON1_TRIPLE_CLICKED)
199 cmd = LYK_END;
200 else if (event.bstate & BUTTON1_DOUBLE_CLICKED)
201 cmd = LYK_NEXT_PAGE;
202 else
203 cmd = LYK_NEXT_LINK;
204 } else if (mypos >= getmaxy(win)) {
205 if (event.bstate & (BUTTON1_DOUBLE_CLICKED |
206 BUTTON1_TRIPLE_CLICKED))
207 cmd = LYK_END;
208 else
209 cmd = LYK_NEXT_PAGE;
210 } else if (mypos == 0) {
211 /* At the decorative border: scroll back */
212 if (event.bstate & BUTTON1_TRIPLE_CLICKED)
213 cmd = LYK_HOME;
214 else if (event.bstate & BUTTON1_DOUBLE_CLICKED)
215 cmd = LYK_PREV_PAGE;
216 else
217 cmd = LYK_PREV_LINK;
218 } else if (mypos < 0) {
219 if (event.bstate & (BUTTON1_DOUBLE_CLICKED |
220 BUTTON1_TRIPLE_CLICKED))
221 cmd = LYK_HOME;
222 else
223 cmd = LYK_PREV_PAGE;
224 #ifdef KNOW_HOW_TO_TOGGLE
225 } else if (event.bstate & (BUTTON_CTRL)) {
226 cur_selection += delta;
227 cmd = LYX_TOGGLE;
228 #endif
229 } else if (event.x <= getbegx(win) + 1 ||
230 event.x >= getbegx(win) + getmaxx(win) - 2) {
231 /* Click on left or right border for positioning without
232 * immediate action: select, but do nothing else.
233 * Actually, allow an error of one position inwards. - kw
234 */
235 *position += delta;
236 cmd = -1;
237 } else if (event.bstate & ButtonModifiers) {
238 /* Probably some unrelated activity, such as selecting some text.
239 * Select, but do nothing else.
240 */
241 /* Possibly this is never returned by ncurses, so this case
242 * may be useless depending on situation (kind of mouse support
243 * and library versions). - kw
244 */
245 *position += delta;
246 cmd = -1;
247 } else {
248 /* No scrolling or overflow checks necessary. */
249 *position += delta;
250 cmd = LYK_ACTIVATE;
251 }
252 } else if (event.bstate & (BUTTON3_CLICKED | BUTTON3_DOUBLE_CLICKED | BUTTON3_TRIPLE_CLICKED)) {
253 cmd = LYK_QUIT;
254 }
255 #endif /* NCURSES */
256 #endif /* PDCURSES */
257
258 /************************************************************************/
259 #endif /* USE_MOUSE */
260 (void) win;
261 (void) row;
262 (void) position;
263
264 return cmd;
265 }
266
267 /*
268 * Manage the collection of edit-histories
269 */
whichRecall(RecallType recall)270 static HTList *whichRecall(RecallType recall)
271 {
272 HTList **list;
273
274 switch (recall) {
275 case RECALL_CMD:
276 return LYcommandList();
277 case RECALL_MAIL:
278 list = &MAIL_edit_history;
279 break;
280 default:
281 list = &URL_edit_history;
282 break;
283 }
284 if (*list == 0)
285 *list = HTList_new();
286 return *list;
287 }
288
289 /*
290 * Remove the oldest item in the closet
291 */
LYRemoveFromCloset(HTList * list)292 static void LYRemoveFromCloset(HTList *list)
293 {
294 void *data = HTList_removeFirstObject(list);
295
296 if (data != 0)
297 FREE(data);
298 }
299
LYCloseCloset(RecallType recall)300 void LYCloseCloset(RecallType recall)
301 {
302 HTList *list = whichRecall(recall);
303
304 while (!HTList_isEmpty(list)) {
305 LYRemoveFromCloset(list);
306 }
307 HTList_delete(list); /* should already be empty */
308 }
309
310 /*
311 * Strategy: We begin at the top and search downwards. We return the first
312 * match, i.e., the newest since we search from the top. This should be made
313 * more intelligent, but works for now.
314 */
LYFindInCloset(RecallType recall,char * base)315 static char *LYFindInCloset(RecallType recall, char *base)
316 {
317 HTList *list = whichRecall(recall);
318 char *data;
319 size_t len = strlen(base);
320
321 while (!HTList_isEmpty(list)) {
322 data = (char *) HTList_nextObject(list);
323 if (data != NULL && !StrNCmp(base, data, len))
324 return (data);
325 }
326
327 return (0);
328 }
329
LYAddToCloset(RecallType recall,char * str)330 static void LYAddToCloset(RecallType recall, char *str)
331 {
332 HTList *list = whichRecall(recall);
333 char *data = NULL;
334
335 StrAllocCopy(data, str);
336 HTList_addObject(list, data);
337 while (HTList_count(list) > LYClosetSize)
338 LYRemoveFromCloset(list);
339 }
340
341 #ifdef USE_MOUSE
XYdist(int x1,int y1,int x2,int y2,int dx2)342 static int XYdist(int x1,
343 int y1,
344 int x2,
345 int y2,
346 int dx2)
347 {
348 int xerr = 3 * (x2 - x1), yerr = 9 * (y2 - y1);
349
350 if (xerr < 0)
351 xerr = 3 * (x1 - x2 - dx2) + 1; /* pos after string not really in it */
352 if (xerr < 0)
353 xerr = 0;
354 if (yerr < 0)
355 yerr = -yerr;
356 if (!yerr) /* same line is good */
357 return (xerr > 0) ? (xerr * 2 - 1) : 0;
358 if (xerr < 9 && yerr) /* x-dist of 3 cell better than y-dist of 1 cell */
359 yerr += (9 - xerr);
360 return 2 * xerr + yerr; /* Subjective factor; ratio -> approx. 6 / 9 */
361 /*
362 old: (IZ 1999-07-30)
363 3 2 2 2 1 1 1 XX XX XX XX XX 0 1 1 1 2 2 2 3 3
364 4\ 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3/ 4 4
365 5 4 4 4\ 3 3 3 3 3 3 3 3 3 3 3 3/ 4 4 4 5 5
366 6 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 6 5
367 now: (kw 1999-10-23)
368 41 35 29|23 17 11 5 XX XX XX XX XX 1 7 13 19 25|31 37 43 49
369 45 39 33\27 24 21 18 18 18 18 18 19 22 25 28/34 40 46 50
370 48 42 36 33 30\27 27 27 27 27 28/31 34 37 43 49
371 51 45 42 39 36 36 36 36 36 37 40 43 46 49
372 51 48 45 45 45 45 45 46 49 52
373 */
374 }
375
376 /* Given X and Y coordinates of a mouse event, set mouse_link to the
377 * index of the corresponding hyperlink, or set mouse_link to -1 if no
378 * link matches the event. Returns -1 if no link matched the click,
379 * or a keycode that must be returned from LYgetch() to activate the
380 * link.
381 */
382
set_clicked_link(int x,int y,int code,int clicks)383 static int set_clicked_link(int x,
384 int y,
385 int code,
386 int clicks)
387 {
388 int left = 6;
389 int right = LYcolLimit - 5;
390
391 /* yes, I am assuming that my screen will be a certain width. */
392 int i;
393 int c = -1;
394
395 if (y == (LYlines - 1) || y == 0) { /* First or last row */
396 /* XXXX In fact # is not always at x==0? KANJI_CODE_OVERRIDE? */
397 int toolbar = (y == 0 && HText_hasToolbar(HTMainText));
398
399 mouse_link = -2;
400 if (x == 0 && toolbar) /* On '#' */
401 c = LAC_TO_LKC0(LYK_TOOLBAR);
402 #if defined(CAN_CUT_AND_PASTE) && defined(USE_COLOR_STYLE)
403 else if (y == 0 && x == LYcolLimit && s_hot_paste != NOSTYLE)
404 c = LAC_TO_LKC0(LYK_PASTE_URL);
405 #endif
406 else if (clicks > 1) {
407 if (x < left + toolbar)
408 c = (code == FOR_PROMPT && y)
409 ? HOME : LAC_TO_LKC0(LYK_MAIN_MENU);
410 else if (x > right)
411 c = (code == FOR_PROMPT && y)
412 ? END_KEY : LAC_TO_LKC0(LYK_VLINKS);
413 else if (y) /* Last row */
414 c = LAC_TO_LKC0(LYK_END);
415 else /* First row */
416 c = LAC_TO_LKC0(LYK_HOME);
417 } else {
418 if (x < left + toolbar)
419 c = (code == FOR_PROMPT && y)
420 ? LTARROW
421 : (
422 #ifdef USE_COLOR_STYLE
423 (s_forw_backw != NOSTYLE && x - toolbar >= 3)
424 ? LAC_TO_LKC0(LYK_NEXT_DOC)
425 : LAC_TO_LKC0(LYK_PREV_DOC)
426 #else
427 LAC_TO_LKC0(LYK_NEXT_DOC)
428 #endif
429 );
430 else if (x > right)
431 c = (code == FOR_PROMPT && y)
432 ? RTARROW : LAC_TO_LKC0(LYK_HISTORY);
433 else if (y) /* Last row */
434 c = LAC_TO_LKC0(LYK_NEXT_PAGE);
435 else /* First row */
436 c = LAC_TO_LKC0(LYK_PREV_PAGE);
437 }
438 #ifdef USE_SCROLLBAR
439 } else if (x == (LYcols - 1) && LYShowScrollbar && LYsb_begin >= 0) {
440 int h = display_lines - 2 * (LYsb_arrow != 0);
441
442 mouse_link = -2;
443 y -= 1 + (LYsb_arrow != 0);
444 if (y < 0)
445 return LAC_TO_LKC0(LYK_UP_TWO);
446 if (y >= h)
447 return LAC_TO_LKC0(LYK_DOWN_TWO);
448
449 if (clicks >= 2) {
450 double frac = (1. * y) / (h - 1);
451 int l = HText_getNumOfLines() + 1; /* NOL() off by one? */
452
453 l -= display_lines;
454 if (l > 0)
455 LYSetNewline((int) (frac * l + 1 + 0.5));
456 return LYReverseKeymap(LYK_DO_NOTHING);
457 }
458
459 if (y < LYsb_begin)
460 return LAC_TO_LKC0(LYK_PREV_PAGE);
461 if (y >= LYsb_end)
462 return LAC_TO_LKC0(LYK_NEXT_PAGE);
463 mouse_link = -1; /* No action in edit fields */
464 #endif
465 } else {
466 int mouse_err = 29, /* subjctv-dist better than this for approx stuff */ cur_err;
467
468 /* Loop over the links and see if we can get a match */
469 for (i = 0; i < nlinks; i++) {
470 int len, lx = links[i].lx, is_text = 0;
471 int count = 0;
472 const char *text = LYGetHiliteStr(i, count);
473
474 if (links[i].type == WWW_FORM_LINK_TYPE
475 && F_TEXTLIKE(links[i].l_form->type))
476 is_text = 1;
477
478 /* Check the first line of the link */
479 if (text != NULL) {
480 if (is_text)
481 len = links[i].l_form->size;
482 else
483 len = (int) LYstrCells(text);
484 cur_err = XYdist(x, y, links[i].lx, links[i].ly, len);
485 /* Check the second line */
486 while (cur_err > 0
487 && (text = LYGetHiliteStr(i, ++count)) != NULL) {
488 /* Note that there is at most one hightext if is_text */
489 int cur_err_2 = XYdist(x, y,
490 LYGetHilitePos(i, count),
491 links[i].ly + count,
492 (int) LYstrCells(text));
493
494 cur_err = HTMIN(cur_err, cur_err_2);
495 }
496 if (cur_err > 0 && is_text)
497 cur_err--; /* a bit of preference for text fields,
498 enter field if hit exactly at end - kw */
499 if (cur_err == 0) {
500 int cury, curx;
501
502 LYGetYX(cury, curx);
503 /* double-click, if we care:
504 submit text submit fields. - kw */
505 if (clicks > 1 && is_text &&
506 links[i].l_form->type == F_TEXT_SUBMIT_TYPE) {
507 if (code != FOR_INPUT
508 /* submit current input field directly */
509 || !(cury == y &&
510 (curx >= lx) &&
511 ((curx - lx) <= len))) {
512 c = LAC_TO_LKC0(LYK_MOUSE_SUBMIT);
513 mouse_link = i;
514 } else {
515 c = LAC_TO_LKC0(LYK_MOUSE_SUBMIT);
516 mouse_link = -1;
517 }
518 mouse_err = 0;
519 break;
520 }
521 if (code != FOR_INPUT
522 /* Do not pick up the current input field */
523 || !((cury == y && (curx >= lx) && ((curx - lx) <= len)))) {
524 if (is_text) {
525 have_levent = 1;
526 #if defined(TEXTFIELDS_MAY_NEED_ACTIVATION) && defined(INACTIVE_INPUT_STYLE_VH)
527 if (x == links[i].lx && y == links[i].ly)
528 textinput_redrawn = FALSE;
529 #endif /* TEXTFIELDS_MAY_NEED_ACTIVATION && INACTIVE_INPUT_STYLE_VH */
530 }
531 mouse_link = i;
532 } else
533 mouse_link = -1;
534 mouse_err = 0;
535 break;
536 } else if (cur_err < mouse_err) {
537 mouse_err = cur_err;
538 mouse_link = i;
539 }
540 }
541 }
542 /*
543 * If a link was hit, we must look for a key which will activate
544 * LYK_ACTIVATE We expect to find LYK_ACTIVATE (it's usually mapped to
545 * the Enter key).
546 */
547 if (mouse_link >= 0) {
548 if (mouse_err == 0) {
549 if (c == -1)
550 c = LAC_TO_LKC0(LYK_ACTIVATE);
551 } else if (mouse_err >= 0)
552 c = LAC_TO_LKC0(LYK_CHANGE_LINK);
553 } else {
554 if (2 * y > LYlines) { /* Bottom Half of the screen */
555 if (4 * y < 3 * LYlines) {
556 c = LAC_TO_LKC0(LYK_DOWN_TWO); /* Third quarter */
557 } else
558 c = LAC_TO_LKC0(LYK_DOWN_HALF); /* Fourth quarter */
559 } else { /* Upper Half of the screen */
560 if (4 * y < LYlines) {
561 c = LAC_TO_LKC0(LYK_UP_HALF); /* First quarter */
562 } else
563 c = LAC_TO_LKC0(LYK_UP_TWO); /* Second quarter */
564 }
565 }
566 }
567 return c;
568 }
569 #endif /* USE_MOUSE */
570
571 /*
572 * LYstrncpy() terminates strings with a null byte. Writes a null byte into
573 * the n+1 byte of dst.
574 */
LYstrncpy(char * dst,const char * src,int n)575 char *LYstrncpy(char *dst,
576 const char *src,
577 int n)
578 {
579 char *val = dst;
580 int len;
581
582 if (src == 0)
583 src = "";
584 len = (int) strlen(src);
585
586 if (n > 0) {
587 if (n > len)
588 n = len;
589 (void) StrNCpy(dst, src, n);
590 } else {
591 n = 0;
592 }
593 dst[n] = '\0';
594 return val;
595 }
596
597 #define IS_NEW_GLYPH(ch) (utf_flag && (UCH(ch)&0xc0) != 0x80)
598 #define IS_UTF_EXTRA(ch) (utf_flag && (UCH(ch)&0xc0) == 0x80)
599
600 /*
601 * LYmbcsstrncpy() terminates strings with a null byte. It takes account of
602 * multibyte characters. The src string is copied until either end of string
603 * or max number of either bytes or glyphs (mbcs sequences) (CJK or UTF8). The
604 * utf_flag argument should be TRUE for UTF8. - KW & FM
605 */
LYmbcsstrncpy(char * dst,const char * src,int n_bytes,int n_glyphs,int utf_flag)606 char *LYmbcsstrncpy(char *dst,
607 const char *src,
608 int n_bytes,
609 int n_glyphs,
610 int utf_flag)
611 {
612 char *val = dst;
613 int i_bytes = 0, i_glyphs = 0;
614
615 if (n_bytes < 0)
616 n_bytes = 0;
617 if (n_glyphs < 0)
618 n_glyphs = 0;
619
620 for (; *src != '\0' && i_bytes < n_bytes; i_bytes++) {
621 if (IS_NEW_GLYPH(*src)) {
622 if (i_glyphs++ >= n_glyphs) {
623 *dst = '\0';
624 return val;
625 }
626 }
627 *(dst++) = *(src++);
628 }
629 *dst = '\0';
630
631 return val;
632 }
633
634 /*
635 * LYmbcs_skip_glyphs() skips a given number of character positions in a string
636 * and returns the resulting pointer. It takes account of UTF-8 encoded
637 * characters. - KW
638 */
LYmbcs_skip_glyphs(const char * data,int n_glyphs,int utf_flag)639 const char *LYmbcs_skip_glyphs(const char *data,
640 int n_glyphs,
641 int utf_flag)
642 {
643 int i_glyphs = 0;
644
645 if (n_glyphs < 0)
646 n_glyphs = 0;
647
648 if (non_empty(data)) {
649 if (!utf_flag) {
650 while (n_glyphs-- > 0) {
651 if (!*++data)
652 break;
653 }
654 } else {
655 while (*data) {
656 if (IS_NEW_GLYPH(*data)) {
657 if (i_glyphs++ >= n_glyphs) {
658 break;
659 }
660 }
661 data++;
662 }
663 }
664 }
665 return data;
666 }
667
668 /*
669 * LYmbcs_skip_cells() skips a given number of display positions in a string
670 * and returns the resulting pointer. It takes account of UTF-8 encoded
671 * characters. - TD
672 */
LYmbcs_skip_cells(const char * data,int n_cells,int utf_flag)673 const char *LYmbcs_skip_cells(const char *data,
674 int n_cells,
675 int utf_flag)
676 {
677 const char *result;
678 int actual;
679 int target = n_cells;
680
681 do {
682 result = LYmbcs_skip_glyphs(data, target--, utf_flag);
683 actual = LYstrExtent2(data, (int) (result - data));
684 } while ((actual > 0) && (actual > n_cells));
685 return result;
686 }
687
688 /*
689 * LYmbcsstrlen() returns the printable length of a string that might contain
690 * IsSpecial or multibyte (CJK or UTF8) characters. - FM
691 *
692 * Counts glyph cells if count_gcells is set. (Full-width characters in CJK
693 * mode count as two.) Counts character glyphs if count_gcells is unset.
694 * (Full- width characters in CJK mode count as one.) - kw
695 */
LYmbcsstrlen(const char * str,int utf_flag,int count_gcells)696 int LYmbcsstrlen(const char *str,
697 int utf_flag,
698 int count_gcells)
699 {
700 int i, j, len = 0;
701
702 if (non_empty(str)) {
703 #ifdef WIDEC_CURSES
704 if (count_gcells) {
705 len = LYstrCells(str);
706 } else
707 #endif
708 {
709 for (i = 0; str[i] != '\0'; i++) {
710 if (!IsSpecialAttrChar(str[i])) {
711 len++;
712 if (IS_NEW_GLYPH(str[i])) {
713 j = 0;
714 while (IsNormalChar(str[(i + 1)]) &&
715 j < 5 &&
716 IS_UTF_EXTRA(str[(i + 1)])) {
717 i++;
718 j++;
719 }
720 } else if (!utf_flag && IS_CJK_TTY && !count_gcells &&
721 is8bits(str[i]) &&
722 IsNormalChar(str[(i + 1)])) {
723 i++;
724 }
725 }
726 }
727 }
728 }
729 return (len);
730 }
731
732 #undef GetChar
733
734 #ifdef USE_SLANG
735 #if defined(VMS)
736 #define GetChar() ttgetc()
737 #elif defined(__DJGPP__)
738 #define GetChar() getxkey() /* HTDos.c */
739 #elif defined(__CYGWIN__)
740 #define GetChar SLkp_getkey
741 #else
742 #define GetChar (int)SLang_getkey
743 #endif
744 #else /* curses */
745 #if defined(DJGPP)
746 #define GetChar() (djgpp_idle_loop(), wgetch(LYtopwindow()))
747 #elif defined(NCURSES_VERSION) && defined(__BEOS__)
748 #define GetChar() myGetCharNodelay()
749 #elif defined(NCURSES)
750 #define GetChar() wgetch(LYtopwindow())
751 #endif
752 #endif
753
754 #ifdef USE_CURSES_NODELAY
755 /* PDCurses - until version 2.7 in 2005 - defined ERR as 0, unlike other
756 * versions of curses. Generally both EOF and ERR are defined as -1s.
757 * However, there is a special case (see HTCheckForInterrupt()) to handle a
758 * case where no select() function is used in the win32 environment.
759 *
760 * HTCheckForInterrupt() uses nodelay() in this special case to check for
761 * pending input. That normally returns ERR. But LYgetch_for() checks the
762 * return value of this function for EOF (to handle some antique runtime
763 * libraries which did not set the state for feof/ferror). Returning a zero
764 * (0) is safer since normally that is not mapped to any commands, and will be
765 * ignored by lynx.
766 */
myGetCharNodelay(void)767 static int myGetCharNodelay(void)
768 {
769 int c = wgetch(LYwin);
770
771 if (c == -1)
772 c = 0;
773
774 return c;
775 }
776 #else
777 #define myGetCharNodelay() wgetch(LYwin)
778 #endif
779
780 #if !defined(GetChar) && defined(PDCURSES) && defined(PDC_BUILD) && PDC_BUILD >= 2401
781 /* PDCurses sends back key-modifiers that we don't use, but would waste time
782 * upon, e.g., repainting the status line
783 */
myGetChar(void)784 static int myGetChar(void)
785 {
786 int c;
787 BOOL done = FALSE;
788
789 do {
790 switch (c = myGetCharNodelay()) {
791 case KEY_SHIFT_L:
792 case KEY_SHIFT_R:
793 case KEY_CONTROL_L:
794 case KEY_CONTROL_R:
795 case KEY_ALT_L:
796 case KEY_ALT_R:
797 case KEY_RESIZE:
798 break;
799 default:
800 done = TRUE;
801 break;
802 }
803 } while (!done);
804
805 return c;
806 }
807 #define GetChar() myGetChar()
808 #endif
809
810 #if !defined(GetChar) && defined(SNAKE)
811 #define GetChar() wgetch(LYwin)
812 #endif
813
814 #if !defined(GetChar) && defined(VMS)
815 #define GetChar() ttgetc()
816 #endif
817
818 #if !defined(GetChar)
819 #ifdef HAVE_KEYPAD
820 #define GetChar() getch()
821 #else
822 #ifndef USE_GETCHAR
823 #define USE_GETCHAR
824 #endif /* !USE_GETCHAR */
825 #define GetChar() getchar() /* used to be "getc(stdin)" and "getch()" */
826 #endif /* HAVE_KEYPAD */
827 #endif /* !defined(GetChar) */
828
829 #if defined(USE_SLANG) && defined(USE_MOUSE)
sl_parse_mouse_event(int * x,int * y,int * button)830 static int sl_parse_mouse_event(int *x, int *y, int *button)
831 {
832 /* "ESC [ M" has already been processed. There more characters are
833 * expected: BUTTON X Y
834 */
835 *button = (int) SLang_getkey();
836 switch (*button) {
837 case 040: /* left button */
838 case 041: /* middle button */
839 case 042: /* right button */
840 *button -= 040;
841 break;
842
843 default: /* Hmmm.... */
844 SLang_flush_input();
845 return -1;
846 }
847
848 *x = (int) SLang_getkey();
849 if (*x == CH_ESC) /* Undo 7-bit replace for large x - kw */
850 *x = (int) SLang_getkey() + 64 - 33;
851 else
852 *x -= 33;
853 *y = (int) SLang_getkey();
854 if (*y == CH_ESC) /* Undo 7-bit replace for large y - kw */
855 *y = (int) SLang_getkey() + 64 - 33;
856 else
857 *y -= 33;
858 return 0;
859 }
860
sl_read_mouse_event(int code)861 static int sl_read_mouse_event(int code)
862 {
863 int mouse_x, mouse_y, button;
864
865 mouse_link = -1;
866 if (-1 != sl_parse_mouse_event(&mouse_x, &mouse_y, &button)) {
867 if (button == 0) /* left */
868 return set_clicked_link(mouse_x, mouse_y, FOR_PANEL, 1);
869
870 if (button == 1) /* middle */
871 return LYReverseKeymap(LYK_VIEW_BOOKMARK);
872
873 if (button == 2) /* right */
874 {
875 /* Right button: go back to prev document.
876 * The problem is that we need to determine
877 * what to return to achieve this.
878 */
879 return LYReverseKeymap(LYK_PREV_DOC);
880 }
881 }
882 if (code == FOR_INPUT || code == FOR_PROMPT)
883 return DO_NOTHING;
884 else
885 return -1;
886 }
887 #endif /* USE_SLANG and USE_MOUSE */
888
889 static BOOLEAN csi_is_csi = TRUE;
ena_csi(int flag)890 void ena_csi(int flag)
891 {
892 csi_is_csi = (BOOLEAN) flag;
893 }
894
895 #if defined(USE_KEYMAPS)
896
897 #ifdef USE_SLANG
898 #define define_key(string, code) \
899 SLkm_define_keysym ((SLFUTURE_CONST char*)(string), \
900 (unsigned) code, \
901 Keymap_List)
902 #if SLANG_VERSION < 20000
903 #define expand_substring(dst, first, last, final) \
904 (SLexpand_escaped_string(dst, \
905 (char *)first, \
906 (char *)last), 1)
SLang_get_error(void)907 static int SLang_get_error(void)
908 {
909 return SLang_Error;
910 }
911 #else
912 int LY_Slang_UTF8_Mode = 0;
913
914 #define expand_substring(dst, first, last, final) \
915 (SLexpand_escaped_string(dst, \
916 (char *)first, \
917 (char *)last, \
918 LY_Slang_UTF8_Mode), 1)
919 #endif
920
921 static SLKeyMap_List_Type *Keymap_List;
922
923 /* This value should be larger than anything in LYStrings.h */
924 #define MOUSE_KEYSYM 0x0400
925 #endif
926
927 /*
928 * For ncurses, we use the predefined keysyms, since that lets us also reuse
929 * the CSI logic and other special cases for VMS, NCSA telnet, etc.
930 */
931 #ifdef USE_SLANG
932 # ifdef VMS
933 # define EXTERN_KEY(string,string1,lynx,curses) {string,lynx}
934 # else
935 # define EXTERN_KEY(string,string1,lynx,curses) {string,lynx},{string1,lynx}
936 # endif
937 # define INTERN_KEY(string,lynx,curses) {string,lynx}
938 #else
939 #define INTERN_KEY(string,lynx,curses) {string,curses}
940 #define EXTERN_KEY(string,string1,lynx,curses) {string,curses}
941 #endif
942
943 typedef struct {
944 const char *string;
945 int value;
946 } Keysym_String_List;
947 /* *INDENT-OFF* */
948 static Keysym_String_List Keysym_Strings [] =
949 {
950 INTERN_KEY( "UPARROW", UPARROW, KEY_UP ),
951 INTERN_KEY( "DNARROW", DNARROW, KEY_DOWN ),
952 INTERN_KEY( "RTARROW", RTARROW, KEY_RIGHT ),
953 INTERN_KEY( "LTARROW", LTARROW, KEY_LEFT ),
954 INTERN_KEY( "PGDOWN", PGDOWN, KEY_NPAGE ),
955 INTERN_KEY( "PGUP", PGUP, KEY_PPAGE ),
956 INTERN_KEY( "HOME", HOME, KEY_HOME ),
957 INTERN_KEY( "END", END_KEY, KEY_END ),
958 INTERN_KEY( "F1", F1, KEY_F(1) ),
959 INTERN_KEY( "DO_KEY", DO_KEY, KEY_F(16) ),
960 INTERN_KEY( "FIND_KEY", FIND_KEY, KEY_FIND ),
961 INTERN_KEY( "SELECT_KEY", SELECT_KEY, KEY_SELECT ),
962 INTERN_KEY( "INSERT_KEY", INSERT_KEY, KEY_IC ),
963 INTERN_KEY( "REMOVE_KEY", REMOVE_KEY, KEY_DC ),
964 INTERN_KEY( "DO_NOTHING", DO_NOTHING, DO_NOTHING|LKC_ISLKC ),
965 INTERN_KEY( NULL, -1, ERR )
966 };
967 /* *INDENT-ON* */
968
969 #ifdef NCURSES_VERSION
970 /*
971 * Ncurses stores the termcap/terminfo names in arrays sorted to match the
972 * array of strings in the TERMTYPE struct.
973 */
lookup_tiname(char * name,NCURSES_CONST char * const * names)974 static int lookup_tiname(char *name, NCURSES_CONST char *const *names)
975 {
976 int code;
977
978 for (code = 0; names[code] != 0; code++)
979 if (!strcmp(names[code], name))
980 return code;
981 return -1;
982 }
983
expand_tiname(const char * first,size_t len,char ** result,char * final)984 static const char *expand_tiname(const char *first, size_t len, char **result, char *final)
985 {
986 char name[BUFSIZ];
987 int code;
988
989 LYStrNCpy(name, first, len);
990 if ((code = lookup_tiname(name, strnames)) >= 0
991 || (code = lookup_tiname(name, strfnames)) >= 0) {
992 if (cur_term->type.Strings[code] != 0) {
993 LYStrNCpy(*result, cur_term->type.Strings[code], (final - *result));
994 (*result) += strlen(*result);
995 }
996 }
997 return first + len;
998 }
999
expand_tichar(const char * first,char ** result,char * final)1000 static const char *expand_tichar(const char *first, char **result, char *final)
1001 {
1002 int ch;
1003 int limit = 0;
1004 int radix = 0;
1005 int value = 0;
1006 const char *name = 0;
1007
1008 switch (ch = *first++) {
1009 case 'E':
1010 case 'e':
1011 value = 27;
1012 break;
1013 case 'a':
1014 name = "bel";
1015 break;
1016 case 'b':
1017 value = '\b';
1018 break;
1019 case 'f':
1020 value = '\f';
1021 break;
1022 case 'n':
1023 value = '\n';
1024 break;
1025 case 'r':
1026 value = '\r';
1027 break;
1028 case 't':
1029 value = '\t';
1030 break;
1031 case 'v':
1032 value = '\v';
1033 break;
1034 case 'd':
1035 radix = 10;
1036 limit = 3;
1037 break;
1038 case 'x':
1039 radix = 16;
1040 limit = 2;
1041 break;
1042 default:
1043 if (isdigit(ch)) {
1044 radix = 8;
1045 limit = 3;
1046 first--;
1047 } else {
1048 value = *first;
1049 }
1050 break;
1051 }
1052
1053 if (radix != 0) {
1054 char *last = 0;
1055 char tmp[80];
1056
1057 LYStrNCpy(tmp, first, limit);
1058 value = (int) strtol(tmp, &last, radix);
1059 if (last != 0 && last != tmp)
1060 first += (last - tmp);
1061 }
1062
1063 if (name != 0) {
1064 (void) expand_tiname(name, strlen(name), result, final);
1065 } else {
1066 **result = (char) value;
1067 (*result) += 1;
1068 }
1069
1070 return first;
1071 }
1072
expand_substring(char * dst,const char * first,const char * last,char * final)1073 static BOOLEAN expand_substring(char *dst,
1074 const char *first,
1075 const char *last,
1076 char *final)
1077 {
1078 int ch;
1079
1080 while (first < last) {
1081 switch (ch = *first++) {
1082 case ESCAPE:
1083 first = expand_tichar(first, &dst, final);
1084 break;
1085 case '^':
1086 ch = *first++;
1087 if (ch == LPAREN) {
1088 const char *s = strchr(first, RPAREN);
1089 char *was = dst;
1090
1091 if (s == 0)
1092 s = first + strlen(first);
1093 first = expand_tiname(first, (size_t) (s - first), &dst, final);
1094 if (dst == was)
1095 return FALSE;
1096 if (*first)
1097 first++;
1098 } else if (ch == '?') { /* ASCII delete? */
1099 *dst++ = 127;
1100 } else if ((ch & 0x3f) < 0x20) { /* ASCII control char? */
1101 *dst++ = (char) (ch & 0x1f);
1102 } else {
1103 *dst++ = '^';
1104 first--; /* not legal... */
1105 }
1106 break;
1107 case 0: /* convert nulls for terminfo */
1108 ch = 0200;
1109 /* FALLTHRU */
1110 default:
1111 *dst++ = (char) ch;
1112 break;
1113 }
1114 }
1115 *dst = '\0';
1116 return TRUE;
1117 }
1118 #endif
1119
unescaped_char(const char * parse,int * keysym)1120 static void unescaped_char(const char *parse, int *keysym)
1121 {
1122 size_t len = strlen(parse);
1123 char buf[BUFSIZ];
1124
1125 if (len >= 3) {
1126 (void) expand_substring(buf,
1127 parse + 1,
1128 parse + len - 1,
1129 buf + sizeof(buf) - 1);
1130 if (strlen(buf) == 1)
1131 *keysym = *buf;
1132 }
1133 }
1134
unescape_string(char * src,char * dst,char * final)1135 static BOOLEAN unescape_string(char *src, char *dst, char *final)
1136 {
1137 BOOLEAN ok = FALSE;
1138
1139 if (*src == SQUOTE) {
1140 int keysym = -1;
1141
1142 unescaped_char(src, &keysym);
1143 if (keysym >= 0) {
1144 dst[0] = (char) keysym;
1145 dst[1] = '\0';
1146 ok = TRUE;
1147 }
1148 } else if (*src == DQUOTE) {
1149 if (expand_substring(dst, src + 1, src + strlen(src) - 1, final))
1150 ok = TRUE;
1151 (void) final;
1152 }
1153 return ok;
1154 }
1155
map_string_to_keysym(const char * str,int * keysym)1156 int map_string_to_keysym(const char *str, int *keysym)
1157 {
1158 int modifier = 0;
1159
1160 *keysym = -1;
1161
1162 if (strncasecomp(str, "LAC:", 4) == 0) {
1163 char *other = strchr(str + 4, ':');
1164
1165 if (other) {
1166 int othersym = lecname_to_lec(other + 1);
1167 char buf[BUFSIZ];
1168
1169 if (othersym >= 0 && other - str - 4 < BUFSIZ) {
1170 LYStrNCpy(buf, str + 4, (other - str - 4));
1171 *keysym = lacname_to_lac(buf);
1172 if (*keysym >= 0) {
1173 *keysym = LACLEC_TO_LKC0(*keysym, othersym);
1174 return (*keysym);
1175 }
1176 }
1177 }
1178 *keysym = lacname_to_lac(str + 4);
1179 if (*keysym >= 0) {
1180 *keysym = LAC_TO_LKC0(*keysym);
1181 return (*keysym);
1182 }
1183 }
1184 if (strncasecomp(str, "Meta-", 5) == 0) {
1185 str += 5;
1186 modifier = LKC_MOD2;
1187 if (*str) {
1188 size_t len = strlen(str);
1189
1190 if (len == 1) {
1191 return (*keysym = (UCH(str[0])) | modifier);
1192 } else if (len == 2 && str[0] == '^' &&
1193 (isalpha(UCH(str[1])) ||
1194 (TOASCII(str[1]) >= '@' && TOASCII(str[1]) <= '_'))) {
1195 return (*keysym = FROMASCII(UCH(str[1] & 0x1f)) | modifier);
1196 } else if (len == 2 && str[0] == '^' &&
1197 str[1] == '?') {
1198 return (*keysym = CH_DEL | modifier);
1199 }
1200 if (*str == '^' || *str == '\\') {
1201 char buf[BUFSIZ];
1202
1203 (void) expand_substring(buf,
1204 str,
1205 str + HTMIN(len, 28),
1206 buf + sizeof(buf) - 1);
1207 if (strlen(buf) <= 1)
1208 return (*keysym = (UCH(buf[0])) | modifier);
1209 }
1210 }
1211 }
1212 if (*str == SQUOTE) {
1213 unescaped_char(str, keysym);
1214 } else if (isdigit(UCH(*str))) {
1215 char *tmp;
1216 long value = strtol(str, &tmp, 0);
1217
1218 if (!isalnum(UCH(*tmp))) {
1219 *keysym = (int) value;
1220 #ifndef USE_SLANG
1221 if (*keysym > 255)
1222 *keysym |= LKC_ISLKC; /* caller should remove this flag - kw */
1223 #endif
1224 }
1225 } else {
1226 Keysym_String_List *k;
1227
1228 k = Keysym_Strings;
1229 while (k->string != NULL) {
1230 if (0 == strcmp(k->string, str)) {
1231 *keysym = k->value;
1232 break;
1233 }
1234 k++;
1235 }
1236 }
1237
1238 if (*keysym >= 0)
1239 *keysym |= modifier;
1240 return (*keysym);
1241 }
1242
1243 /*
1244 * Starting at a nonblank character, skip over a token, counting quoted and
1245 * escaped characters.
1246 */
skip_keysym(char * parse)1247 static char *skip_keysym(char *parse)
1248 {
1249 int quoted = 0;
1250 int escaped = 0;
1251
1252 while (*parse) {
1253 if (escaped) {
1254 escaped = 0;
1255 } else if (quoted) {
1256 if (*parse == ESCAPE) {
1257 escaped = 1;
1258 } else if (*parse == quoted) {
1259 quoted = 0;
1260 }
1261 } else if (*parse == ESCAPE) {
1262 escaped = 1;
1263 } else if (*parse == DQUOTE || *parse == SQUOTE) {
1264 quoted = *parse;
1265 } else if (isspace(UCH(*parse))) {
1266 break;
1267 }
1268 parse++;
1269 }
1270 return (quoted || escaped) ? 0 : parse;
1271 }
1272
1273 /*
1274 * The first token is the string to define, the second is the name (of the
1275 * keysym) to define it to.
1276 */
1277 #define MY_TRACE(p) CTRACE2(TRACE_CFG, p)
1278
setkey_cmd(char * parse)1279 static int setkey_cmd(char *parse)
1280 {
1281 char *s, *t;
1282 int keysym;
1283 char buf[BUFSIZ];
1284
1285 MY_TRACE((tfp, "KEYMAP(PA): in=%s", parse)); /* \n-terminated */
1286 if ((s = skip_keysym(parse)) != 0) {
1287 if (isspace(UCH(*s))) {
1288 *s++ = '\0';
1289 s = LYSkipBlanks(s);
1290 if ((t = skip_keysym(s)) == 0) {
1291 MY_TRACE((tfp, "KEYMAP(SKIP) no key expansion found\n"));
1292 return -1;
1293 }
1294 if (t != s)
1295 *t = '\0';
1296 if (map_string_to_keysym(s, &keysym) >= 0) {
1297 if (!unescape_string(parse, buf, buf + sizeof(buf) - 1)) {
1298 MY_TRACE((tfp, "KEYMAP(SKIP) could unescape key\n"));
1299 return 0; /* Trace the failure and continue. */
1300 }
1301 if (LYTraceLogFP == 0) {
1302 MY_TRACE((tfp, "KEYMAP(DEF) keysym=%#x\n", keysym));
1303 } else {
1304 MY_TRACE((tfp, "KEYMAP(DEF) keysym=%#x, seq='%s'\n",
1305 keysym, buf));
1306 }
1307 return define_key(buf, keysym);
1308 } else {
1309 MY_TRACE((tfp, "KEYMAP(SKIP) could not map to keysym\n"));
1310 }
1311 } else {
1312 MY_TRACE((tfp, "KEYMAP(SKIP) junk after key description: '%s'\n", s));
1313 }
1314 } else {
1315 MY_TRACE((tfp, "KEYMAP(SKIP) no key description\n"));
1316 }
1317 return -1;
1318 }
1319 #undef MY_TRACE
1320
unsetkey_cmd(char * parse)1321 static int unsetkey_cmd(char *parse)
1322 {
1323 char *s = skip_keysym(parse);
1324
1325 if (s != parse) {
1326 *s = '\0';
1327 #ifdef NCURSES_VERSION
1328 /*
1329 * This won't work with Slang. Remove the definition for the given
1330 * keysym.
1331 */
1332 {
1333 int keysym;
1334
1335 if (map_string_to_keysym(parse, &keysym) >= 0)
1336 define_key((char *) 0, keysym);
1337 }
1338 #endif
1339 #ifdef USE_SLANG
1340 /* Slang implements this, for undefining the string which is associated
1341 * with a keysym (the reverse of what we normally want, but may
1342 * occasionally find useful).
1343 */
1344 SLang_undefine_key(parse, Keymap_List);
1345 if (SLang_get_error())
1346 return -1;
1347 #endif
1348 }
1349 return 0;
1350 }
1351
1352 #ifdef FNAMES_8_3
1353 #define FNAME_LYNX_KEYMAPS "_lynxkey.map"
1354 #else
1355 #define FNAME_LYNX_KEYMAPS ".lynx-keymaps"
1356 #endif /* FNAMES_8_3 */
1357
read_keymap_file(void)1358 static int read_keymap_file(void)
1359 {
1360 /* *INDENT-OFF* */
1361 static struct {
1362 const char *name;
1363 int (*func) (char *s);
1364 } table[] = {
1365 { "setkey", setkey_cmd },
1366 { "unsetkey", unsetkey_cmd },
1367 };
1368 /* *INDENT-ON* */
1369
1370 char *line = NULL;
1371 FILE *fp;
1372 char file[LY_MAXPATH];
1373 int linenum;
1374 size_t n;
1375
1376 LYAddPathToHome(file, sizeof(file), FNAME_LYNX_KEYMAPS);
1377
1378 if ((fp = fopen(file, "r")) == 0)
1379 return 0;
1380
1381 linenum = 0;
1382 while (LYSafeGets(&line, fp) != 0) {
1383 char *s = LYSkipBlanks(line);
1384
1385 linenum++;
1386
1387 if ((*s == 0) || (*s == '#'))
1388 continue;
1389
1390 for (n = 0; n < TABLESIZE(table); n++) {
1391 size_t len = strlen(table[n].name);
1392
1393 if (strlen(s) > len && !StrNCmp(s, table[n].name, len)
1394 && (*(table[n].func)) (LYSkipBlanks(s + len)) < 0)
1395 fprintf(stderr, FAILED_READING_KEYMAP, linenum, file);
1396 }
1397 }
1398 FREE(line);
1399 LYCloseInput(fp);
1400 return 0;
1401 }
1402
setup_vtXXX_keymap(void)1403 static void setup_vtXXX_keymap(void)
1404 {
1405 /* *INDENT-OFF* */
1406 static Keysym_String_List table[] = {
1407 INTERN_KEY( "\033[A", UPARROW, KEY_UP ),
1408 INTERN_KEY( "\033OA", UPARROW, KEY_UP ),
1409 INTERN_KEY( "\033[B", DNARROW, KEY_DOWN ),
1410 INTERN_KEY( "\033OB", DNARROW, KEY_DOWN ),
1411 INTERN_KEY( "\033[C", RTARROW, KEY_RIGHT ),
1412 INTERN_KEY( "\033OC", RTARROW, KEY_RIGHT ),
1413 INTERN_KEY( "\033[D", LTARROW, KEY_LEFT ),
1414 INTERN_KEY( "\033OD", LTARROW, KEY_LEFT ),
1415 INTERN_KEY( "\033[1~", FIND_KEY, KEY_FIND ),
1416 INTERN_KEY( "\033[2~", INSERT_KEY, KEY_IC ),
1417 INTERN_KEY( "\033[3~", REMOVE_KEY, KEY_DC ),
1418 INTERN_KEY( "\033[4~", SELECT_KEY, KEY_SELECT ),
1419 INTERN_KEY( "\033[5~", PGUP, KEY_PPAGE ),
1420 INTERN_KEY( "\033[6~", PGDOWN, KEY_NPAGE ),
1421 INTERN_KEY( "\033[7~", HOME, KEY_HOME),
1422 INTERN_KEY( "\033[8~", END_KEY, KEY_END ),
1423 INTERN_KEY( "\033[11~", F1, KEY_F(1) ),
1424 INTERN_KEY( "\033[28~", F1, KEY_F(1) ),
1425 INTERN_KEY( "\033OP", F1, KEY_F(1) ),
1426 INTERN_KEY( "\033[OP", F1, KEY_F(1) ),
1427 INTERN_KEY( "\033[29~", DO_KEY, KEY_F(16) ),
1428 #if defined(USE_SLANG) && (defined(__WIN32__) || defined(__MINGW32__))
1429 INTERN_KEY( "\xE0H", UPARROW, KEY_UP ),
1430 INTERN_KEY( "\xE0P", DNARROW, KEY_DOWN ),
1431 INTERN_KEY( "\xE0M", RTARROW, KEY_RIGHT ),
1432 INTERN_KEY( "\xE0K", LTARROW, KEY_LEFT ),
1433 INTERN_KEY( "\xE0R", INSERT_KEY, KEY_IC ),
1434 INTERN_KEY( "\xE0S", REMOVE_KEY, KEY_DC ),
1435 INTERN_KEY( "\xE0I", PGUP, KEY_PPAGE ),
1436 INTERN_KEY( "\xE0Q", PGDOWN, KEY_NPAGE ),
1437 INTERN_KEY( "\xE0G", HOME, KEY_HOME),
1438 INTERN_KEY( "\xE0O", END_KEY, KEY_END ),
1439 #endif
1440 #if defined(USE_SLANG) && !defined(VMS)
1441 INTERN_KEY( "^(ku)", UPARROW, KEY_UP ),
1442 INTERN_KEY( "^(kd)", DNARROW, KEY_DOWN ),
1443 INTERN_KEY( "^(kr)", RTARROW, KEY_RIGHT ),
1444 INTERN_KEY( "^(kl)", LTARROW, KEY_LEFT ),
1445 INTERN_KEY( "^(@0)", FIND_KEY, KEY_FIND ),
1446 INTERN_KEY( "^(kI)", INSERT_KEY, KEY_IC ),
1447 INTERN_KEY( "^(kD)", REMOVE_KEY, KEY_DC ),
1448 INTERN_KEY( "^(*6)", SELECT_KEY, KEY_SELECT ),
1449 INTERN_KEY( "^(kP)", PGUP, KEY_PPAGE ),
1450 INTERN_KEY( "^(kN)", PGDOWN, KEY_NPAGE ),
1451 INTERN_KEY( "^(@7)", END_KEY, KEY_END ),
1452 INTERN_KEY( "^(kh)", HOME, KEY_HOME),
1453 INTERN_KEY( "^(k1)", F1, KEY_F(1) ),
1454 INTERN_KEY( "^(F6)", DO_KEY, KEY_F(16) ),
1455 #endif /* SLANG && !VMS */
1456 };
1457 /* *INDENT-ON* */
1458
1459 size_t n;
1460
1461 for (n = 0; n < TABLESIZE(table); n++)
1462 define_key(table[n].string, table[n].value);
1463 }
1464
lynx_initialize_keymaps(void)1465 int lynx_initialize_keymaps(void)
1466 {
1467 #ifdef USE_SLANG
1468 int i;
1469 char keybuf[2];
1470
1471 /* The escape sequences may contain embedded termcap strings. Make
1472 * sure the library is initialized for that.
1473 */
1474 SLtt_get_terminfo();
1475
1476 if (NULL == (Keymap_List = SLang_create_keymap("Lynx", NULL)))
1477 return -1;
1478
1479 keybuf[1] = 0;
1480 for (i = 1; i < 256; i++) {
1481 keybuf[0] = (char) i;
1482 define_key(keybuf, i);
1483 }
1484
1485 setup_vtXXX_keymap();
1486 define_key("\033[M", MOUSE_KEYSYM);
1487
1488 if (SLang_get_error())
1489 SLang_exit_error("Unable to initialize keymaps");
1490 #else
1491 setup_vtXXX_keymap();
1492 #endif
1493 return read_keymap_file();
1494 }
1495
1496 #endif /* USE_KEYMAPS */
1497
1498 #if defined(USE_MOUSE) && (defined(NCURSES))
LYmouse_menu(int x,int y,int atlink,int code)1499 static int LYmouse_menu(int x, int y, int atlink, int code)
1500 {
1501 #define ENT_ONLY_DOC 1
1502 #define ENT_ONLY_LINK 2
1503 /* *INDENT-OFF* */
1504 static const struct {
1505 const char *txt;
1506 int action;
1507 unsigned int flag;
1508 } possible_entries[] = {
1509 {"Quit", LYK_ABORT, ENT_ONLY_DOC},
1510 {"Home page", LYK_MAIN_MENU, ENT_ONLY_DOC},
1511 {"Previous document", LYK_PREV_DOC, ENT_ONLY_DOC},
1512 {"Beginning of document", LYK_HOME, ENT_ONLY_DOC},
1513 {"Page up", LYK_PREV_PAGE, ENT_ONLY_DOC},
1514 {"Half page up", LYK_UP_HALF, ENT_ONLY_DOC},
1515 {"Two lines up", LYK_UP_TWO, ENT_ONLY_DOC},
1516 {"History", LYK_HISTORY, ENT_ONLY_DOC},
1517 {"Help", LYK_HELP, 0},
1518 {"Do nothing (refresh)", LYK_REFRESH, 0},
1519 {"Load again", LYK_RELOAD, ENT_ONLY_DOC},
1520 {"Edit Doc URL and load", LYK_ECGOTO, ENT_ONLY_DOC},
1521 {"Edit Link URL and load", LYK_ELGOTO, ENT_ONLY_LINK},
1522 {"Show info", LYK_INFO, 0},
1523 {"Search", LYK_WHEREIS, ENT_ONLY_DOC},
1524 {"Print", LYK_PRINT, ENT_ONLY_DOC},
1525 {"Two lines down", LYK_DOWN_TWO, ENT_ONLY_DOC},
1526 {"Half page down", LYK_DOWN_HALF, ENT_ONLY_DOC},
1527 {"Page down", LYK_NEXT_PAGE, ENT_ONLY_DOC},
1528 {"End of document", LYK_END, ENT_ONLY_DOC},
1529 {"Bookmarks", LYK_VIEW_BOOKMARK, ENT_ONLY_DOC},
1530 {"Cookie jar", LYK_COOKIE_JAR, ENT_ONLY_DOC},
1531 #ifdef USE_CACHEJAR
1532 {"Cache jar", LYK_CACHE_JAR, ENT_ONLY_DOC},
1533 #endif
1534 {"Search index", LYK_INDEX_SEARCH, ENT_ONLY_DOC},
1535 {"Set Options", LYK_OPTIONS, ENT_ONLY_DOC},
1536 {"Activate this link", LYK_MOUSE_SUBMIT, ENT_ONLY_LINK},
1537 {"Download", LYK_DOWNLOAD, ENT_ONLY_LINK}
1538 };
1539 /* *INDENT-ON* */
1540
1541 #define TOTAL_MENUENTRIES TABLESIZE(possible_entries)
1542 const char *choices[TOTAL_MENUENTRIES + 1];
1543 int actions[TOTAL_MENUENTRIES];
1544
1545 int c, c1, retlac;
1546 unsigned filter_out = (unsigned) (atlink ? ENT_ONLY_DOC : ENT_ONLY_LINK);
1547
1548 c = c1 = 0;
1549 while (c < (int) TOTAL_MENUENTRIES) {
1550 if (!(possible_entries[c].flag & filter_out)) {
1551 choices[c1] = possible_entries[c].txt;
1552 actions[c1++] = possible_entries[c].action;
1553 }
1554 c++;
1555 }
1556 choices[c1] = NULL;
1557
1558 /* Somehow the mouse is over the number instead of being over the
1559 name, so we decrease x. */
1560 c = LYChoosePopup((atlink ? 2 : 10) - 1, y, (x > 5 ? x - 5 : 1),
1561 choices, c1, FALSE, TRUE);
1562
1563 /*
1564 * LYhandlePopupList() wasn't really meant to be used outside of old-style
1565 * Options menu processing. One result of mis-using it here is that we
1566 * have to deal with side-effects regarding SIGINT signal handler and the
1567 * term_options global variable. - kw
1568 */
1569 if (term_options) {
1570 retlac = LYK_DO_NOTHING;
1571 term_options = FALSE;
1572 } else {
1573 retlac = actions[c];
1574 }
1575
1576 if (code == FOR_INPUT && mouse_link == -1) {
1577 switch (retlac) {
1578 case LYK_ABORT:
1579 retlac = LYK_QUIT; /* a bit softer... */
1580 /* fall through */
1581 case LYK_MAIN_MENU:
1582 case LYK_PREV_DOC:
1583 case LYK_HOME:
1584 case LYK_PREV_PAGE:
1585 case LYK_UP_HALF:
1586 case LYK_UP_TWO:
1587 case LYK_HISTORY:
1588 case LYK_HELP:
1589 /* case LYK_REFRESH:*/
1590 case LYK_RELOAD:
1591 case LYK_ECGOTO:
1592 case LYK_INFO:
1593 case LYK_WHEREIS:
1594 case LYK_PRINT:
1595 case LYK_DOWN_TWO:
1596 case LYK_DOWN_HALF:
1597 case LYK_NEXT_PAGE:
1598 case LYK_END:
1599 case LYK_VIEW_BOOKMARK:
1600 case LYK_COOKIE_JAR:
1601 #ifdef USE_CACHEJAR
1602 case LYK_CACHE_JAR:
1603 #endif
1604 case LYK_INDEX_SEARCH:
1605 case LYK_OPTIONS:
1606 mouse_link = -3; /* so LYgetch_for() passes it on - kw */
1607 }
1608 }
1609 if (retlac == LYK_DO_NOTHING ||
1610 retlac == LYK_REFRESH) {
1611 mouse_link = -1; /* mainloop should not change cur link - kw */
1612 }
1613 if (code == FOR_INPUT && retlac == LYK_DO_NOTHING) {
1614 repaint_main_statusline(FOR_INPUT);
1615 }
1616 return retlac;
1617 }
1618 #endif /* USE_MOUSE && (NCURSES || PDCURSES) */
1619
1620 #if defined(USE_KEYMAPS) && defined(USE_SLANG)
1621 /************************************************************************/
1622
1623 static int current_sl_modifier = 0;
1624
1625 /* We cannot guarantee the type for 'GetChar', and should not use a cast. */
myGetChar(void)1626 static int myGetChar(void)
1627 {
1628 int i = GetChar();
1629
1630 if (i == 0) /* trick to get NUL char through - kw */
1631 current_sl_modifier = LKC_ISLKC;
1632 return i;
1633 }
1634
LYgetch_for(int code)1635 static int LYgetch_for(int code)
1636 {
1637 SLang_Key_Type *key;
1638 int keysym;
1639
1640 current_sl_modifier = 0;
1641
1642 key = SLang_do_key(Keymap_List, myGetChar);
1643 if ((key == NULL) || (key->type != SLKEY_F_KEYSYM)) {
1644 #if defined(__WIN32__) || defined(__MINGW32__)
1645 if ((key == NULL) && (current_sl_modifier == LKC_ISLKC)) {
1646 key = SLang_do_key(Keymap_List, myGetChar);
1647 keysym = key->f.keysym;
1648 switch (keysym) {
1649 case 'H':
1650 keysym = UPARROW;
1651 break;
1652 case 'P':
1653 keysym = DNARROW;
1654 break;
1655 case 'M':
1656 keysym = RTARROW;
1657 break;
1658 case 'K':
1659 keysym = LTARROW;
1660 break;
1661 case 'R':
1662 keysym = INSERT_KEY;
1663 break;
1664 case 'S':
1665 keysym = REMOVE_KEY;
1666 break;
1667 case 'I':
1668 keysym = PGUP;
1669 break;
1670 case 'Q':
1671 keysym = PGDOWN;
1672 break;
1673 case 'G':
1674 keysym = HOME;
1675 break;
1676 case 'O':
1677 keysym = END_KEY;
1678 break;
1679 case ';':
1680 keysym = F1;
1681 break;
1682 }
1683 return (keysym);
1684 }
1685 #endif
1686
1687 return (current_sl_modifier ? 0 : DO_NOTHING);
1688 }
1689
1690 keysym = (int) key->f.keysym;
1691
1692 #if defined (USE_MOUSE)
1693 if (keysym == MOUSE_KEYSYM)
1694 return sl_read_mouse_event(code);
1695 #endif
1696
1697 if (keysym < 0)
1698 return 0;
1699
1700 if (keysym & (LKC_ISLECLAC | LKC_ISLAC))
1701 return (keysym);
1702
1703 current_sl_modifier = 0;
1704 if (LKC_HAS_ESC_MOD(keysym)) {
1705 current_sl_modifier = LKC_MOD2;
1706 keysym &= LKC_MASK;
1707 }
1708
1709 if (keysym + 1 >= KEYMAP_SIZE)
1710 return 0;
1711
1712 return (keysym | current_sl_modifier);
1713 }
1714
1715 /************************************************************************/
1716 #else /* NOT defined(USE_KEYMAPS) && defined(USE_SLANG) */
1717
1718 /*
1719 * LYgetch() translates some escape sequences and may fake noecho.
1720 */
1721 #define found_CSI(first,second) ((second) == '[' || (first) == 155)
1722 #define found_TLD(value) ((value) == '~')
1723
LYgetch_for(int code)1724 static int LYgetch_for(int code)
1725 {
1726 int a, b, c, d = -1;
1727 int current_modifier = 0;
1728 BOOLEAN done_esc = FALSE;
1729
1730 (void) code;
1731
1732 have_levent = 0;
1733
1734 re_read:
1735 #if !defined(UCX) || !defined(VAXC) /* errno not modifiable ? */
1736 if (errno == EINTR)
1737 set_errno(0); /* reset - kw */
1738 #endif /* UCX && VAXC */
1739 #ifndef USE_SLANG
1740 clearerr(stdin); /* needed here for ultrix and SOCKETSHR, but why? - FM */
1741 #endif /* !USE_SLANG */
1742 #if !defined(USE_SLANG) || defined(VMS) || defined(DJGPP_KEYHANDLER)
1743 c = GetChar();
1744 lynx_nl2crlf(FALSE);
1745 #else
1746 if (LYCursesON) {
1747 c = GetChar();
1748 lynx_nl2crlf(FALSE);
1749 } else {
1750 c = getchar();
1751 if (c == EOF && errno == EINTR) /* Ctrl-Z causes EINTR in getchar() */
1752 clearerr(stdin);
1753 if (feof(stdin) || ferror(stdin) || c == EOF) {
1754 #ifdef IGNORE_CTRL_C
1755 if (sigint)
1756 sigint = FALSE;
1757 #endif /* IGNORE_CTRL_C */
1758 CTRACE((tfp, "GETCH: Translate ^C to ^G.\n"));
1759 return (LYCharINTERRUPT2); /* use ^G to cancel whatever called us. */
1760 }
1761 }
1762 #endif /* !USE_SLANG || VMS */
1763
1764 CTRACE((tfp, "GETCH%d: Got %#x.\n", code, c));
1765 #ifdef MISC_EXP
1766 if (LYNoZapKey > 1 && errno != EINTR &&
1767 (c == EOF
1768 #ifdef USE_SLANG
1769 || (c == 0xFFFF)
1770 #endif
1771 )) {
1772
1773 CTRACE((tfp,
1774 "nozap: Got EOF, curses %s, stdin is %p, LYNoZapKey reduced from %d to 0.\n",
1775 LYCursesON ? "on" : "off", (void *) stdin, LYNoZapKey));
1776 LYNoZapKey = 0; /* 2 -> 0 */
1777 if (LYReopenInput() > 0) {
1778 if (LYCursesON) {
1779 stop_curses();
1780 start_curses();
1781 LYrefresh();
1782 }
1783 goto re_read;
1784 }
1785 }
1786 #endif /* MISC_EXP */
1787
1788 #ifdef USE_GETCHAR
1789 if (c == EOF && errno == EINTR) /* Ctrl-Z causes EINTR in getchar() */
1790 goto re_read;
1791 #else
1792 if (c == EOF && errno == EINTR) {
1793
1794 #if defined(HAVE_SIZECHANGE) || defined(USE_SLANG)
1795 CTRACE((tfp, "Got EOF with EINTR, recent_sizechange so far is %d\n",
1796 recent_sizechange));
1797 if (!recent_sizechange) { /* not yet detected by ourselves */
1798 size_change(0);
1799 CTRACE((tfp, "Now recent_sizechange is %d\n", recent_sizechange));
1800 }
1801 #else /* HAVE_SIZECHANGE || USE_SLANG */
1802 CTRACE((tfp, "Got EOF with EINTR, recent_sizechange is %d\n",
1803 recent_sizechange));
1804 #endif /* HAVE_SIZECHANGE || USE_SLANG */
1805 #if !defined(UCX) || !defined(VAXC) /* errno not modifiable ? */
1806 set_errno(0); /* reset - kw */
1807 #endif /* UCX && VAXC */
1808 return (DO_NOTHING);
1809 }
1810 #endif /* USE_GETCHAR */
1811
1812 #ifdef USE_SLANG
1813 if (c == 0xFFFF && LYCursesON) {
1814 #ifdef IGNORE_CTRL_C
1815 if (sigint) {
1816 sigint = FALSE;
1817 goto re_read;
1818 }
1819 #endif /* IGNORE_CTRL_C */
1820 return (LYCharINTERRUPT2); /* use ^G to cancel whatever called us. */
1821 }
1822 #else /* not USE_SLANG: */
1823 if (feof(stdin) || ferror(stdin) || c == EOF) {
1824 if (recent_sizechange)
1825 return (LYCharINTERRUPT2); /* use ^G to cancel whatever called us. */
1826 #ifdef IGNORE_CTRL_C
1827 if (sigint) {
1828 sigint = FALSE;
1829 /* clearerr(stdin); don't need here if stays above - FM */
1830 goto re_read;
1831 }
1832 #endif /* IGNORE_CTRL_C */
1833 #if !defined(USE_GETCHAR) && !defined(VMS) && !defined(NCURSES)
1834 if (c == ERR && errno == EINTR) /* may have been handled signal - kw */
1835 goto re_read;
1836 #endif /* USE_GETCHAR */
1837
1838 cleanup();
1839 exit_immediately(EXIT_SUCCESS);
1840 }
1841 #endif /* USE_SLANG */
1842
1843 if (!escape_bound
1844 && (c == CH_ESC || (csi_is_csi && c == UCH(CH_ESC_PAR)))) {
1845 /* handle escape sequence S/390 -- gil -- 2024 */
1846 done_esc = TRUE; /* Flag: we did it, not keypad() */
1847 b = GetChar();
1848
1849 if (b == '[' || b == 'O') {
1850 a = GetChar();
1851 } else {
1852 a = b;
1853 }
1854
1855 switch (a) {
1856 case 'A':
1857 c = UPARROW;
1858 break;
1859 case 'B':
1860 c = DNARROW;
1861 break;
1862 case 'C':
1863 c = RTARROW;
1864 break;
1865 case 'D':
1866 c = LTARROW;
1867 break;
1868 case 'q': /* vt100 application keypad 1 */
1869 c = END_KEY;
1870 break;
1871 case 'r': /* vt100 application keypad 2 */
1872 c = DNARROW;
1873 break;
1874 case 's': /* vt100 application keypad 3 */
1875 c = PGDOWN;
1876 break;
1877 case 't': /* vt100 application keypad 4 */
1878 c = LTARROW;
1879 break;
1880 case 'v': /* vt100 application keypad 6 */
1881 c = RTARROW;
1882 break;
1883 case 'w': /* vt100 application keypad 7 */
1884 c = HOME;
1885 break;
1886 case 'x': /* vt100 application keypad 8 */
1887 c = UPARROW;
1888 break;
1889 case 'y': /* vt100 application keypad 9 */
1890 c = PGUP;
1891 break;
1892 case 'M':
1893 #if defined(USE_SLANG) && defined(USE_MOUSE)
1894 if (found_CSI(c, b)) {
1895 c = sl_read_mouse_event(code);
1896 } else
1897 #endif
1898 c = '\n'; /* keypad enter on pc ncsa telnet */
1899 break;
1900
1901 case 'm':
1902 #ifdef VMS
1903 if (b != 'O')
1904 #endif /* VMS */
1905 c = '-'; /* keypad on pc ncsa telnet */
1906 break;
1907 case 'k':
1908 if (b == 'O')
1909 c = '+'; /* keypad + on my xterminal :) */
1910 else
1911 done_esc = FALSE; /* we have another look below - kw */
1912 break;
1913 case 'l':
1914 #ifdef VMS
1915 if (b != 'O')
1916 #endif /* VMS */
1917 c = '+'; /* keypad on pc ncsa telnet */
1918 break;
1919 case 'P':
1920 #ifdef VMS
1921 if (b != 'O')
1922 #endif /* VMS */
1923 c = F1;
1924 break;
1925 case 'u':
1926 #ifdef VMS
1927 if (b != 'O')
1928 #endif /* VMS */
1929 c = F1; /* macintosh help button */
1930 break;
1931 case 'p':
1932 #ifdef VMS
1933 if (b == 'O')
1934 #endif /* VMS */
1935 c = '0'; /* keypad 0 */
1936 break;
1937 case '1': /* VTxxx Find */
1938 if (found_CSI(c, b) && found_TLD(d = GetChar()))
1939 c = FIND_KEY;
1940 else
1941 done_esc = FALSE; /* we have another look below - kw */
1942 break;
1943 case '2':
1944 if (found_CSI(c, b)) {
1945 if (found_TLD(d = GetChar())) /* VTxxx Insert */
1946 c = INSERT_KEY;
1947 else if ((d == '8' ||
1948 d == '9') &&
1949 found_TLD(GetChar())) {
1950 if (d == '8') /* VTxxx Help */
1951 c = F1;
1952 else if (d == '9') /* VTxxx Do */
1953 c = DO_KEY;
1954 d = -1;
1955 }
1956 } else
1957 done_esc = FALSE; /* we have another look below - kw */
1958 break;
1959 case '3': /** VTxxx Delete **/
1960 if (found_CSI(c, b) && found_TLD(d = GetChar()))
1961 c = REMOVE_KEY;
1962 else
1963 done_esc = FALSE; /* we have another look below - kw */
1964 break;
1965 case '4': /** VTxxx Select **/
1966 if (found_CSI(c, b) && found_TLD(d = GetChar()))
1967 c = SELECT_KEY;
1968 else
1969 done_esc = FALSE; /* we have another look below - kw */
1970 break;
1971 case '5': /** VTxxx PrevScreen **/
1972 if (found_CSI(c, b) && found_TLD(d = GetChar()))
1973 c = PGUP;
1974 else
1975 done_esc = FALSE; /* we have another look below - kw */
1976 break;
1977 case '6': /** VTxxx NextScreen **/
1978 if (found_CSI(c, b) && found_TLD(d = GetChar()))
1979 c = PGDOWN;
1980 else
1981 done_esc = FALSE; /* we have another look below - kw */
1982 break;
1983 case '[': /** Linux F1-F5: ^[[[A etc. **/
1984 if (found_CSI(c, b)) {
1985 if ((d = GetChar()) == 'A')
1986 c = F1;
1987 break;
1988 }
1989 /* FALLTHRU */
1990 default:
1991 if (c == CH_ESC && a == b && !found_CSI(c, b)) {
1992 current_modifier = LKC_MOD2;
1993 c = a;
1994 /* We're not yet done if ESC + curses-keysym: */
1995 done_esc = (BOOL) ((a & ~0xFF) == 0);
1996 break;
1997 }
1998 CTRACE((tfp, "Unknown key sequence: %d:%d:%d\n", c, b, a));
1999 CTRACE_SLEEP(MessageSecs);
2000 break;
2001 }
2002 if (isdigit(a) && found_CSI(c, b) && d != -1 && !found_TLD(d))
2003 d = GetChar();
2004 if (!done_esc && (a & ~0xFF) == 0) {
2005 if (a == b && !found_CSI(c, b) && c == CH_ESC) {
2006 current_modifier = LKC_MOD2;
2007 c = a;
2008 done_esc = TRUE;
2009 } else {
2010 done_esc = TRUE;
2011 }
2012 }
2013 }
2014 #ifdef USE_KEYMAPS
2015 /* Extract a single code if two are merged: */
2016 if (c >= 0 && (c & LKC_ISLECLAC)) {
2017 if (!(code == FOR_INPUT || code == FOR_PROMPT))
2018 c = LKC2_TO_LKC(c);
2019 } else if (c >= 0 && (c & LKC_ISLKC)) {
2020 c &= ~LKC_ISLKC;
2021 done_esc = TRUE; /* already a lynxkeycode, skip keypad switches - kw */
2022 }
2023 if (c >= 0 && LKC_HAS_ESC_MOD(c)) {
2024 current_modifier = LKC_MOD2;
2025 c &= LKC_MASK;
2026 }
2027 if (c >= 0 && (c & (LKC_ISLECLAC | LKC_ISLAC))) {
2028 done_esc = TRUE; /* already a lynxactioncode, skip keypad switches - iz */
2029 }
2030 #endif
2031 if (done_esc) {
2032 /* don't do keypad() switches below, we already got it - kw */
2033 } else {
2034 #ifdef HAVE_KEYPAD
2035 /*
2036 * Convert keypad() mode keys into Lynx defined keys.
2037 */
2038 switch (c) {
2039 case KEY_DOWN: /* The four arrow keys ... */
2040 c = DNARROW;
2041 break;
2042 case KEY_UP:
2043 c = UPARROW;
2044 break;
2045 case KEY_LEFT:
2046 c = LTARROW;
2047 break;
2048 case KEY_RIGHT: /* ... */
2049 c = RTARROW;
2050 break;
2051 #if defined(PDCURSES) /* for NEC PC-9800 1998/08/30 (Sun) 21:50:35 */
2052 case KEY_C2:
2053 c = DNARROW;
2054 break;
2055 case KEY_A2:
2056 c = UPARROW;
2057 break;
2058 case KEY_B1:
2059 c = LTARROW;
2060 break;
2061 case KEY_B3:
2062 c = RTARROW;
2063 break;
2064 case PAD0: /* PC-9800 Ins */
2065 c = INSERT_KEY;
2066 break;
2067 case PADSTOP: /* PC-9800 DEL */
2068 c = REMOVE_KEY;
2069 break;
2070 #endif /* PDCURSES */
2071 case KEY_HOME: /* Home key (upward+left arrow) */
2072 c = HOME;
2073 break;
2074 case KEY_CLEAR: /* Clear screen */
2075 c = 18; /* CTRL-R */
2076 break;
2077 case KEY_NPAGE: /* Next page */
2078 c = PGDOWN;
2079 break;
2080 case KEY_PPAGE: /* Previous page */
2081 c = PGUP;
2082 break;
2083 case KEY_LL: /* home down or bottom (lower left) */
2084 c = END_KEY;
2085 break;
2086 #if defined(KEY_A1) && defined(KEY_C3)
2087 /* The keypad is arranged like this: */
2088 /* a1 up a3 */
2089 /* left b2 right */
2090 /* c1 down c3 */
2091 case KEY_A1: /* upper left of keypad */
2092 c = HOME;
2093 break;
2094 case KEY_A3: /* upper right of keypad */
2095 c = PGUP;
2096 break;
2097 case KEY_B2: /* center of keypad */
2098 c = DO_NOTHING;
2099 break;
2100 case KEY_C1: /* lower left of keypad */
2101 c = END_KEY;
2102 break;
2103 case KEY_C3: /* lower right of keypad */
2104 c = PGDOWN;
2105 break;
2106 #endif /* defined(KEY_A1) && defined(KEY_C3) */
2107 #ifdef KEY_ENTER
2108 case KEY_ENTER: /* enter/return */
2109 c = '\n';
2110 break;
2111 #endif /* KEY_ENTER */
2112 #ifdef PADENTER /* PDCURSES */
2113 case PADENTER:
2114 c = '\n';
2115 break;
2116 #endif /* PADENTER */
2117 #ifdef KEY_END
2118 case KEY_END: /* end key 001 */
2119 c = END_KEY;
2120 break;
2121 #endif /* KEY_END */
2122 #ifdef KEY_HELP
2123 case KEY_HELP: /* help key 001 */
2124 c = F1;
2125 break;
2126 #endif /* KEY_HELP */
2127 #ifdef KEY_BACKSPACE
2128 case KEY_BACKSPACE:
2129 c = CH_DEL; /* backspace key (delete, not Ctrl-H) S/390 -- gil -- 2041 */
2130 break;
2131 #endif /* KEY_BACKSPACE */
2132 case KEY_F(1):
2133 c = F1; /* VTxxx Help */
2134 break;
2135 #if defined(KEY_F) && !defined(__DJGPP__) && !defined(_WINDOWS)
2136 case KEY_F(16):
2137 c = DO_KEY; /* VTxxx Do */
2138 break;
2139 #endif /* KEY_F */
2140 #ifdef KEY_REDO
2141 case KEY_REDO: /* VTxxx Do */
2142 c = DO_KEY;
2143 break;
2144 #endif /* KEY_REDO */
2145 #ifdef KEY_FIND
2146 case KEY_FIND:
2147 c = FIND_KEY; /* VTxxx Find */
2148 break;
2149 #endif /* KEY_FIND */
2150 #ifdef KEY_SELECT
2151 case KEY_SELECT:
2152 c = SELECT_KEY; /* VTxxx Select */
2153 break;
2154 #endif /* KEY_SELECT */
2155 #ifdef KEY_IC
2156 case KEY_IC:
2157 c = INSERT_KEY; /* VTxxx Insert */
2158 break;
2159 #endif /* KEY_IC */
2160 #ifdef KEY_DC
2161 case KEY_DC:
2162 c = REMOVE_KEY; /* VTxxx Remove */
2163 break;
2164 #endif /* KEY_DC */
2165 #ifdef KEY_BTAB
2166 case KEY_BTAB:
2167 c = BACKTAB_KEY; /* Back tab, often Shift-Tab */
2168 break;
2169 #endif /* KEY_BTAB */
2170 #ifdef KEY_RESIZE
2171 case KEY_RESIZE: /* size change detected by ncurses */
2172 #if defined(HAVE_SIZECHANGE) || defined(USE_SLANG)
2173 /* Make call to detect new size, if that may be implemented.
2174 * The call may set recent_sizechange (except for USE_SLANG),
2175 * which will tell mainloop() to refresh. - kw
2176 */
2177 CTRACE((tfp, "Got KEY_RESIZE, recent_sizechange so far is %d\n",
2178 recent_sizechange));
2179 size_change(0);
2180 CTRACE((tfp, "Now recent_sizechange is %d\n", recent_sizechange));
2181 #else /* HAVE_SIZECHANGE || USE_SLANG */
2182 CTRACE((tfp, "Got KEY_RESIZE, recent_sizechange is %d\n",
2183 recent_sizechange));
2184 #endif /* HAVE_SIZECHANGE || USE_SLANG */
2185 if (!recent_sizechange) {
2186 #if 0 /* assumption seems flawed? */
2187 /* Not detected by us or already processed by us. It can
2188 * happens that ncurses lags behind us in detecting the change,
2189 * since its own SIGTSTP handler is not installed so detecting
2190 * happened *at the end* of the last refresh. Tell it to
2191 * refresh again... - kw
2192 */
2193 LYrefresh();
2194 #endif
2195 #if defined(NCURSES)
2196 /*
2197 * Work-around for scenario (Linux libc5) where we got a
2198 * recent sizechange before reading KEY_RESIZE. If we do
2199 * not reset the flag, we'll next get an EOF read, which
2200 * causes Lynx to exit.
2201 */
2202 recent_sizechange = TRUE;
2203 #endif
2204 /*
2205 * May be just the delayed effect of mainloop()'s call to
2206 * resizeterm(). Pretend we haven't read anything yet, don't
2207 * return. - kw
2208 */
2209 goto re_read;
2210 }
2211 /*
2212 * Yep, we agree there was a change. Return now so that the caller
2213 * can react to it. - kw
2214 */
2215 c = DO_NOTHING;
2216 break;
2217 #endif /* KEY_RESIZE */
2218
2219 /* The following maps PDCurses keys away from lynx reserved values */
2220 #if (defined(_WINDOWS) || defined(__DJGPP__)) && !defined(USE_SLANG)
2221 case KEY_F(2):
2222 c = 0x213;
2223 break;
2224 case KEY_F(3):
2225 c = 0x214;
2226 break;
2227 case KEY_F(4):
2228 c = 0x215;
2229 break;
2230 case KEY_F(5):
2231 c = 0x216;
2232 break;
2233 case KEY_F(6):
2234 c = 0x217;
2235 break;
2236 case KEY_F(7):
2237 c = 0x218;
2238 break;
2239 #endif /* PDCurses */
2240
2241 #if defined(USE_MOUSE)
2242 /********************************************************************/
2243
2244 #if defined(NCURSES) || defined(PDCURSES)
2245 case KEY_MOUSE:
2246 CTRACE((tfp, "KEY_MOUSE\n"));
2247 if (code == FOR_CHOICE) {
2248 c = MOUSE_KEY; /* Will be processed by the caller */
2249 }
2250 #if defined(NCURSES)
2251 else if (code == FOR_SINGLEKEY) {
2252 MEVENT event;
2253
2254 getmouse(&event); /* Completely ignore event - kw */
2255 c = DO_NOTHING;
2256 }
2257 #endif
2258 else {
2259 #if defined(NCURSES)
2260 MEVENT event;
2261 int err;
2262 int lac = LYK_UNKNOWN;
2263
2264 c = -1;
2265 mouse_link = -1;
2266 err = getmouse(&event);
2267 if (err != OK) {
2268 CTRACE((tfp, "Mouse error: no event available!\n"));
2269 return (code == FOR_PANEL ? 0 : DO_NOTHING);
2270 }
2271 levent = event; /* Allow setting pos in entry fields */
2272 if (event.bstate & BUTTON1_CLICKED) {
2273 c = set_clicked_link(event.x, event.y, code, 1);
2274 } else if (event.bstate & BUTTON1_DOUBLE_CLICKED) {
2275 c = set_clicked_link(event.x, event.y, code, 2);
2276 if (c == LAC_TO_LKC0(LYK_MOUSE_SUBMIT) &&
2277 code == FOR_INPUT)
2278 lac = LYK_MOUSE_SUBMIT;
2279 } else if (event.bstate & BUTTON3_CLICKED) {
2280 c = LAC_TO_LKC0(LYK_PREV_DOC);
2281 } else if (code == FOR_PROMPT
2282 /* Cannot ignore: see LYCurses.c */
2283 || (event.bstate &
2284 (BUTTON1_PRESSED | BUTTON1_RELEASED
2285 | BUTTON2_PRESSED | BUTTON2_RELEASED
2286 | BUTTON3_PRESSED | BUTTON3_RELEASED))) {
2287 /* Completely ignore - don't return anything, to
2288 avoid canceling the prompt - kw */
2289 goto re_read;
2290 } else if (event.bstate & BUTTON2_CLICKED) {
2291 int atlink;
2292
2293 c = set_clicked_link(event.x, event.y, code, 1);
2294 atlink = (c == LAC_TO_LKC0(LYK_ACTIVATE));
2295 if (!atlink)
2296 mouse_link = -1; /* Forget about approx stuff. */
2297
2298 lac = LYmouse_menu(event.x, event.y, atlink, code);
2299 if (lac == LYK_MOUSE_SUBMIT) {
2300 if (mouse_link == -1)
2301 lac = LYK_ACTIVATE;
2302 #ifdef TEXTFIELDS_MAY_NEED_ACTIVATION
2303 else if (mouse_link >= 0 &&
2304 textfields_need_activation &&
2305 links[mouse_link].type == WWW_FORM_LINK_TYPE &&
2306 F_TEXTLIKE(links[mouse_link].l_form->type))
2307 lac = LYK_ACTIVATE;
2308 #endif
2309 }
2310 if (lac == LYK_ACTIVATE && mouse_link == -1) {
2311 HTAlert(gettext("No link chosen"));
2312 lac = LYK_REFRESH;
2313 }
2314 c = LAC_TO_LKC(lac);
2315 #if 0 /* Probably not necessary any more - kw */
2316 lynx_force_repaint();
2317 LYrefresh();
2318 #endif
2319 }
2320 #if NCURSES_MOUSE_VERSION > 1
2321 else if (event.bstate & BUTTON4_PRESSED) {
2322 c = LAC_TO_LKC(LYK_UP_HALF);
2323 } else if (event.bstate & BUTTON5_PRESSED) {
2324 c = LAC_TO_LKC(LYK_DOWN_HALF);
2325 }
2326 #endif
2327 if (code == FOR_INPUT && mouse_link == -1 &&
2328 lac != LYK_REFRESH &&
2329 lac != LYK_MOUSE_SUBMIT) {
2330 ungetmouse(&event); /* Caller will process this. */
2331 wgetch(LYwin); /* ungetmouse puts KEY_MOUSE back */
2332 c = MOUSE_KEY;
2333 }
2334 #else /* pdcurses version */
2335
2336 #define H_CMD_AREA 6
2337 #define HIST_CMD_2 12
2338 #define V_CMD_AREA 1
2339
2340 int left = H_CMD_AREA;
2341 int right = (LYcolLimit - H_CMD_AREA - 1);
2342
2343 /* yes, I am assuming that my screen will be a certain width. */
2344
2345 int tick_count;
2346 char *p = NULL;
2347 char mouse_info[128];
2348 static int old_click = 0; /* [m Sec] */
2349
2350 c = -1;
2351 mouse_link = -1;
2352
2353 if (!system_is_NT) {
2354 tick_count = GetTickCount();
2355
2356 /* Guard Mouse button miss click */
2357 if ((tick_count - old_click) < 700) {
2358 c = DO_NOTHING;
2359 break;
2360 } else {
2361 old_click = tick_count;
2362 }
2363 }
2364 request_mouse_pos();
2365
2366 if (BUTTON_STATUS(1) & BUTTON_PRESSED) {
2367 if (MOUSE_Y_POS > (LYlines - V_CMD_AREA - 1)) {
2368 /* Screen BOTTOM */
2369 if (MOUSE_X_POS < left) {
2370 c = LTARROW;
2371 p = "<-";
2372 } else if (MOUSE_X_POS < HIST_CMD_2) {
2373 c = RTARROW;
2374 p = "->";
2375 } else if (MOUSE_X_POS > right) {
2376 c = 'z';
2377 p = "Cancel";
2378 } else {
2379 c = PGDOWN;
2380 p = "PGDOWN";
2381 }
2382 } else if (MOUSE_Y_POS < V_CMD_AREA) {
2383 /* Screen TOP */
2384 if (MOUSE_X_POS < left) {
2385 c = LTARROW;
2386 p = "<-";
2387 } else if (MOUSE_X_POS < HIST_CMD_2) {
2388 c = RTARROW;
2389 p = "->";
2390 } else if (MOUSE_X_POS > right) {
2391 c = 'z';
2392 p = "Cancel";
2393 } else {
2394 c = PGUP;
2395 p = "PGUP";
2396 }
2397 } else {
2398 c = set_clicked_link(MOUSE_X_POS,
2399 MOUSE_Y_POS,
2400 FOR_PANEL, 1);
2401 }
2402 }
2403 if (p && c != -1) {
2404 sprintf(mouse_info, "Mouse = 0x%x, [%s]", c, p);
2405 SetConsoleTitle(mouse_info);
2406 }
2407 #endif /* !(WIN_EX) */
2408 if ((c + 1) >= KEYMAP_SIZE && (c & LKC_ISLAC))
2409 return (c);
2410 }
2411 break;
2412 #endif /* NCURSES || PDCURSES */
2413
2414 /********************************************************************/
2415 #endif /* USE_MOUSE */
2416
2417 }
2418 #endif /* HAVE_KEYPAD */
2419 #ifdef DJGPP_KEYHANDLER
2420 switch (c) {
2421 case K_Down: /* The four arrow keys ... */
2422 case K_EDown:
2423 c = DNARROW;
2424 break;
2425 case K_Up:
2426 case K_EUp:
2427 c = UPARROW;
2428 break;
2429 case K_Left:
2430 case K_ELeft:
2431 c = LTARROW;
2432 break;
2433 case K_Right: /* ... */
2434 case K_ERight:
2435 c = RTARROW;
2436 break;
2437 case K_Home: /* Home key (upward+left arrow) */
2438 case K_EHome:
2439 c = HOME;
2440 break;
2441 case K_PageDown: /* Next page */
2442 case K_EPageDown:
2443 c = PGDOWN;
2444 break;
2445 case K_PageUp: /* Previous page */
2446 case K_EPageUp:
2447 c = PGUP;
2448 break;
2449 case K_End: /* home down or bottom (lower left) */
2450 case K_EEnd:
2451 c = END_KEY;
2452 break;
2453 case K_F1: /* F1 key */
2454 c = F1;
2455 break;
2456 case K_Insert: /* Insert key */
2457 case K_EInsert:
2458 c = INSERT_KEY;
2459 break;
2460 case K_Delete: /* Delete key */
2461 case K_EDelete:
2462 c = REMOVE_KEY;
2463 break;
2464 case K_Alt_Escape: /* Alt-Escape */
2465 c = 0x1a7;
2466 break;
2467 case K_Control_At: /* CTRL-@ */
2468 c = 0x1a8;
2469 break;
2470 case K_Alt_Backspace: /* Alt-Backspace */
2471 c = 0x1a9;
2472 break;
2473 case K_BackTab: /* BackTab */
2474 c = BACKTAB_KEY;
2475 break;
2476 }
2477 #endif /* DGJPP_KEYHANDLER */
2478 #if defined(USE_SLANG) && (defined(__DJGPP__) || defined(__CYGWIN__)) && !defined(DJGPP_KEYHANDLER) && !defined(USE_KEYMAPS)
2479 switch (c) {
2480 case SL_KEY_DOWN: /* The four arrow keys ... */
2481 c = DNARROW;
2482 break;
2483 case SL_KEY_UP:
2484 c = UPARROW;
2485 break;
2486 case SL_KEY_LEFT:
2487 c = LTARROW;
2488 break;
2489 case SL_KEY_RIGHT: /* ... */
2490 c = RTARROW;
2491 break;
2492 case SL_KEY_HOME: /* Home key (upward+left arrow) */
2493 case SL_KEY_A1: /* upper left of keypad */
2494 c = HOME;
2495 break;
2496 case SL_KEY_NPAGE: /* Next page */
2497 case SL_KEY_C3: /* lower right of keypad */
2498 c = PGDOWN;
2499 break;
2500 case SL_KEY_PPAGE: /* Previous page */
2501 case SL_KEY_A3: /* upper right of keypad */
2502 c = PGUP;
2503 break;
2504 case SL_KEY_END: /* home down or bottom (lower left) */
2505 case SL_KEY_C1: /* lower left of keypad */
2506 c = END_KEY;
2507 break;
2508 case SL_KEY_F(1): /* F1 key */
2509 c = F1;
2510 break;
2511 case SL_KEY_IC: /* Insert key */
2512 c = INSERT_KEY;
2513 break;
2514 case SL_KEY_DELETE: /* Delete key */
2515 c = REMOVE_KEY;
2516 break;
2517 }
2518 #endif /* USE_SLANG && __DJGPP__ && !DJGPP_KEYHANDLER && !USE_KEYMAPS */
2519 }
2520
2521 if (c & (LKC_ISLAC | LKC_ISLECLAC))
2522 return (c);
2523 if ((c + 1) >= KEYMAP_SIZE) {
2524 /*
2525 * Don't return raw values for KEYPAD symbols which we may have missed
2526 * in the switch above if they are obviously invalid when used as an
2527 * index into (e.g.) keypad[]. - KW
2528 */
2529 return (0);
2530 } else {
2531 return (c | current_modifier);
2532 }
2533 }
2534
2535 /************************************************************************/
2536 #endif /* NOT defined(USE_KEYMAPS) && defined(USE_SLANG) */
2537
LYgetch(void)2538 int LYgetch(void)
2539 {
2540 return LYReadCmdKey(FOR_PANEL);
2541 }
2542
2543 /*
2544 * Read a single keystroke, allows mouse-selection.
2545 */
LYgetch_choice(void)2546 int LYgetch_choice(void)
2547 {
2548 int ch = LYReadCmdKey(FOR_CHOICE);
2549
2550 if (ch == LYCharINTERRUPT1)
2551 ch = LYCharINTERRUPT2; /* treat ^C the same as ^G */
2552 return ch;
2553 }
2554
2555 /*
2556 * Read a single keystroke, allows mouse events.
2557 */
LYgetch_input(void)2558 int LYgetch_input(void)
2559 {
2560 int ch = LYReadCmdKey(FOR_INPUT);
2561
2562 if (ch == LYCharINTERRUPT1)
2563 ch = LYCharINTERRUPT2; /* treat ^C the same as ^G */
2564 return ch;
2565 }
2566
2567 /*
2568 * Read a single keystroke, ignoring case by translating it to uppercase.
2569 * Ignore mouse events, if any.
2570 */
LYgetch_single(void)2571 int LYgetch_single(void)
2572 {
2573 int ch = LYReadCmdKey(FOR_SINGLEKEY);
2574
2575 if (ch == LYCharINTERRUPT1)
2576 ch = LYCharINTERRUPT2; /* treat ^C the same as ^G */
2577 else if (ch > 0 && ch < 256)
2578 ch = TOUPPER(ch); /* will ignore case of result */
2579 return ch;
2580 }
2581
2582 /*
2583 * Convert a null-terminated string to lowercase
2584 */
LYLowerCase(char * arg_buffer)2585 void LYLowerCase(char *arg_buffer)
2586 {
2587 register unsigned char *buffer = (unsigned char *) arg_buffer;
2588 size_t i;
2589
2590 for (i = 0; buffer[i]; i++)
2591 #ifdef SUPPORT_MULTIBYTE_EDIT /* 1998/11/23 (Mon) 17:04:55 */
2592 {
2593 if ((buffer[i] & 0x80) != 0
2594 && buffer[i + 1] != 0) {
2595 if ((kanji_code == SJIS) && IS_SJIS_X0201KANA(UCH((buffer[i])))) {
2596 continue;
2597 }
2598 i++;
2599 } else {
2600 buffer[i] = UCH(TOLOWER(buffer[i]));
2601 }
2602 }
2603 #else
2604 buffer[i] = TOLOWER(buffer[i]);
2605 #endif
2606 }
2607
2608 /*
2609 * Convert a null-terminated string to uppercase
2610 */
LYUpperCase(char * arg_buffer)2611 void LYUpperCase(char *arg_buffer)
2612 {
2613 register unsigned char *buffer = (unsigned char *) arg_buffer;
2614 size_t i;
2615
2616 for (i = 0; buffer[i]; i++)
2617 #ifdef SUPPORT_MULTIBYTE_EDIT /* 1998/11/23 (Mon) 17:05:10 */
2618 {
2619 if ((buffer[i] & 0x80) != 0
2620 && buffer[i + 1] != 0) {
2621 if ((kanji_code == SJIS) && IS_SJIS_X0201KANA(UCH((buffer[i])))) {
2622 continue;
2623 }
2624 i++;
2625 } else {
2626 buffer[i] = UCH(TOUPPER(buffer[i]));
2627 }
2628 }
2629 #else
2630 buffer[i] = UCH(TOUPPER(buffer[i]));
2631 #endif
2632 }
2633
2634 /*
2635 * Remove newlines from a string, returning true if we removed any.
2636 */
LYRemoveNewlines(char * buffer)2637 BOOLEAN LYRemoveNewlines(char *buffer)
2638 {
2639 if (buffer != 0) {
2640 register char *buf = buffer;
2641
2642 for (; *buf && *buf != '\n' && *buf != '\r'; buf++) ;
2643 if (*buf) {
2644 /* runs very seldom */
2645 char *old = buf;
2646
2647 for (; *old; old++) {
2648 if (*old != '\n' && *old != '\r')
2649 *buf++ = *old;
2650 }
2651 *buf = '\0';
2652 return TRUE;
2653 }
2654 }
2655 return FALSE;
2656 }
2657
2658 /*
2659 * Remove leading/trailing whitespace from a string, reduce runs of embedded
2660 * whitespace to single blanks.
2661 */
LYReduceBlanks(char * buffer)2662 char *LYReduceBlanks(char *buffer)
2663 {
2664 if (non_empty(buffer)) {
2665 LYTrimLeading(buffer);
2666 LYTrimTrailing(buffer);
2667 convert_to_spaces(buffer, TRUE);
2668 }
2669 return buffer;
2670 }
2671
2672 /*
2673 * Remove ALL whitespace from a string (including embedded blanks), and returns
2674 * a pointer to the end of the trimmed string.
2675 */
LYRemoveBlanks(char * buffer)2676 char *LYRemoveBlanks(char *buffer)
2677 {
2678 if (buffer != 0) {
2679 register char *buf = buffer;
2680
2681 for (; *buf && !isspace(UCH(*buf)); buf++) ;
2682 if (*buf) {
2683 /* runs very seldom */
2684 char *old = buf;
2685
2686 for (; *old; old++) {
2687 if (!isspace(UCH(*old)))
2688 *buf++ = *old;
2689 }
2690 *buf = '\0';
2691 }
2692 return buf;
2693 }
2694 return NULL;
2695 }
2696
2697 /*
2698 * Skip whitespace
2699 */
LYSkipBlanks(char * buffer)2700 char *LYSkipBlanks(char *buffer)
2701 {
2702 while (isspace(UCH((*buffer))))
2703 buffer++;
2704 return buffer;
2705 }
2706
2707 /*
2708 * Skip non-whitespace
2709 */
LYSkipNonBlanks(char * buffer)2710 char *LYSkipNonBlanks(char *buffer)
2711 {
2712 while (*buffer != 0 && !isspace(UCH((*buffer))))
2713 buffer++;
2714 return buffer;
2715 }
2716
2717 /*
2718 * Skip const whitespace
2719 */
LYSkipCBlanks(const char * buffer)2720 const char *LYSkipCBlanks(const char *buffer)
2721 {
2722 while (isspace(UCH((*buffer))))
2723 buffer++;
2724 return buffer;
2725 }
2726
2727 /*
2728 * Skip const non-whitespace
2729 */
LYSkipCNonBlanks(const char * buffer)2730 const char *LYSkipCNonBlanks(const char *buffer)
2731 {
2732 while (*buffer != 0 && !isspace(UCH((*buffer))))
2733 buffer++;
2734 return buffer;
2735 }
2736
2737 /*
2738 * Trim leading blanks from a string
2739 */
LYTrimLeading(char * buffer)2740 void LYTrimLeading(char *buffer)
2741 {
2742 char *skipped = LYSkipBlanks(buffer);
2743
2744 while ((*buffer++ = *skipped++) != 0) ;
2745 }
2746
2747 /*
2748 * Trim trailing newline(s) from a string
2749 */
LYTrimNewline(char * buffer)2750 char *LYTrimNewline(char *buffer)
2751 {
2752 size_t i = strlen(buffer);
2753
2754 while (i != 0 && (buffer[i - 1] == '\n' || buffer[i - 1] == '\r'))
2755 buffer[--i] = 0;
2756 return buffer;
2757 }
2758
2759 /*
2760 * Trim trailing blanks from a string
2761 */
LYTrimTrailing(char * buffer)2762 void LYTrimTrailing(char *buffer)
2763 {
2764 size_t i = strlen(buffer);
2765
2766 while (i != 0 && isspace(UCH(buffer[i - 1])))
2767 buffer[--i] = 0;
2768 }
2769
2770 /* 1997/11/10 (Mon) 14:26:10, originally string_short() in LYExterns.c, but
2771 * moved here because LYExterns is not always configured.
2772 */
LYElideString(char * str,int cut_pos)2773 char *LYElideString(char *str,
2774 int cut_pos)
2775 {
2776 char buff[MAX_LINE], *s, *d;
2777 static char s_str[MAX_LINE];
2778 int len;
2779
2780 LYStrNCpy(buff, str, sizeof(buff) - 1);
2781 len = (int) strlen(buff);
2782 if (len > (LYcolLimit - 9)) {
2783 buff[cut_pos] = '.';
2784 buff[cut_pos + 1] = '.';
2785 for (s = (buff + len) - (LYcolLimit - 9) + cut_pos + 1,
2786 d = (buff + cut_pos) + 2;
2787 s >= buff &&
2788 d >= buff &&
2789 d < buff + LYcols &&
2790 (*d++ = *s++) != 0;) ;
2791 buff[LYcols] = 0;
2792 }
2793 strcpy(s_str, buff);
2794 return (s_str);
2795 }
2796
2797 /*
2798 * Trim a startfile, returning true if it looks like one of the Lynx tags.
2799 */
LYTrimStartfile(char * buffer)2800 BOOLEAN LYTrimStartfile(char *buffer)
2801 {
2802 LYTrimHead(buffer);
2803 if (isLYNXEXEC(buffer) ||
2804 isLYNXPROG(buffer)) {
2805 /*
2806 * The original implementations of these schemes expected white space
2807 * without hex escaping, and did not check for hex escaping, so we'll
2808 * continue to support that, until that code is redone in conformance
2809 * with SGML principles. - FM
2810 */
2811 HTUnEscapeSome(buffer, " \r\n\t");
2812 convert_to_spaces(buffer, TRUE);
2813 return TRUE;
2814 }
2815 return FALSE;
2816 }
2817
2818 /*
2819 * Escape unsafe characters in startfile, except for lynx internal URLs.
2820 */
LYEscapeStartfile(char ** buffer)2821 void LYEscapeStartfile(char **buffer)
2822 {
2823 if (!LYTrimStartfile(*buffer)) {
2824 char *escaped = HTEscapeUnsafe(*buffer);
2825
2826 StrAllocCopy(*buffer, escaped);
2827 FREE(escaped);
2828 }
2829 }
2830
2831 /*
2832 * Trim all blanks from startfile, except for lynx internal URLs.
2833 */
LYTrimAllStartfile(char * buffer)2834 void LYTrimAllStartfile(char *buffer)
2835 {
2836 if (!LYTrimStartfile(buffer)) {
2837 LYRemoveBlanks(buffer);
2838 }
2839 }
2840
2841 /*
2842 * Display the current value of the string and allow the user
2843 * to edit it.
2844 */
2845
2846 #define EDREC EditFieldData
2847
2848 /*
2849 * Shorthand to get rid of the "edit->suchandsos".
2850 */
2851 #define IsDirty edit->dirty
2852 #define IsHidden edit->hidden
2853 #define StartX edit->sx
2854 #define StartY edit->sy
2855 #define Buf edit->buffer
2856 #define Pos edit->pos /* current editing position (bytes) */
2857 #define StrLen edit->buffer_used /* length (bytes) */
2858 #define MaxLen edit->buffer_size
2859 #define BufLimit edit->buffer_limit
2860 #define DspWdth edit->dspwdth
2861 #define DspStart edit->xpan /* display-start (columns) */
2862 #define Margin edit->margin
2863 #define PanOn edit->panon
2864 #define PadChar edit->pad
2865 #ifdef ENHANCED_LINEEDIT
2866 #define Mark edit->mark
2867 #endif
2868 #define CurModif edit->current_modifiers
2869 #define Offs2Col edit->offset2col
2870
2871 #ifdef ENHANCED_LINEEDIT
2872 static bstring *killbuffer;
2873 #endif
2874
updateMargin(EDREC * edit)2875 static void updateMargin(EDREC * edit)
2876 {
2877 if ((int) MaxLen > DspWdth) { /* Need panning? */
2878 if (DspWdth > 4) /* Else "{}" take up precious screen space */
2879 PanOn = TRUE;
2880
2881 /*
2882 * Figure out margins. If too big, we do a lot of unnecessary
2883 * scrolling. If too small, user doesn't have sufficient look-ahead.
2884 * Let's say 25% for each margin, upper bound is 10 columns.
2885 */
2886 Margin = DspWdth / 4;
2887 if (Margin > 10)
2888 Margin = 10;
2889 }
2890 }
2891
2892 /*
2893 * Before using an array position, make sure that the array is long enough.
2894 * Reallocate if needed.
2895 */
ExtendEditor(EDREC * edit,int position)2896 static void ExtendEditor(EDREC * edit, int position)
2897 {
2898 size_t need = (size_t) (++position);
2899
2900 if (need >= MaxLen && (BufLimit == 0 || need < BufLimit)) {
2901 CTRACE((tfp, "ExtendEditor from %u to %u\n",
2902 (unsigned) MaxLen,
2903 (unsigned) need));
2904 Buf = typeRealloc(char, Buf, need);
2905 Offs2Col = typeRealloc(int, Offs2Col, need + 1);
2906
2907 MaxLen = need;
2908 updateMargin(edit);
2909 }
2910 }
2911
LYFinishEdit(EDREC * edit)2912 void LYFinishEdit(EDREC * edit)
2913 {
2914 CTRACE((tfp, "LYFinishEdit:%s\n", NonNull(Buf)));
2915
2916 FREE(Buf);
2917 FREE(Offs2Col);
2918 }
2919
LYSetupEdit(EDREC * edit,char * old_value,size_t buffer_limit,int display_limit)2920 void LYSetupEdit(EDREC * edit, char *old_value, size_t buffer_limit, int display_limit)
2921 {
2922 CTRACE((tfp, "LYSetupEdit buffer %u, display %d:%s\n",
2923 (unsigned) buffer_limit,
2924 display_limit,
2925 old_value));
2926
2927 BufLimit = buffer_limit;
2928 if (buffer_limit == 0)
2929 buffer_limit = strlen(old_value) + 1;
2930
2931 /*
2932 * Initialize edit record
2933 */
2934 LYGetYX(StartY, StartX);
2935 PadChar = ' ';
2936 IsDirty = TRUE;
2937 PanOn = FALSE;
2938 CurModif = 0;
2939
2940 MaxLen = buffer_limit;
2941 DspWdth = display_limit;
2942 Margin = 0;
2943 Pos = (int) strlen(old_value);
2944 #ifdef ENHANCED_LINEEDIT
2945 Mark = -1; /* pos=0, but do not show it yet */
2946 #endif
2947 DspStart = 0;
2948
2949 updateMargin(edit);
2950
2951 StrLen = strlen(old_value);
2952 Buf = typecallocn(char, MaxLen + 1);
2953
2954 if (Buf == 0)
2955 outofmem(__FILE__, "LYSetupEdit");
2956
2957 LYStrNCpy(Buf, old_value, buffer_limit);
2958 Offs2Col = typecallocn(int, MaxLen + 1);
2959
2960 if (Offs2Col == 0)
2961 outofmem(__FILE__, "LYSetupEdit");
2962 }
2963
2964 #ifdef SUPPORT_MULTIBYTE_EDIT
2965
2966 /*
2967 * MBCS positioning routines below are specific to SUPPORT_MULTIBYTE_EDIT code.
2968 * Currently they handle UTF-8 and (hopefully) CJK.
2969 * Current encoding is recognized using defines below.
2970 *
2971 * LYmbcs* functions don't look very convenient to use here...
2972 * Do we really need utf_flag as an argument?
2973 *
2974 * It is set (see IS_UTF8_TTY) for every invocation out there, and they use
2975 * HTCJK flag internally anyway. Something like LYmbcsstrnlen == mbcs_glyphs
2976 * would be useful to work with string slices -Sergej Kvachonok
2977 */
2978
2979 #define IS_UTF8_EXTRA(x) (((unsigned char)(x) & 0300) == 0200)
2980
2981 /*
2982 * Counts glyphs in a multibyte (sub)string s of length len.
2983 */
mbcs_glyphs(char * s,int len)2984 static int mbcs_glyphs(char *s, int len)
2985 {
2986 int glyphs = 0;
2987 int i;
2988
2989 if (IS_UTF8_TTY) {
2990 for (i = 0; s[i] && i < len; i++)
2991 if (!IS_UTF8_EXTRA(s[i]))
2992 glyphs++;
2993 } else if (IS_CJK_TTY) {
2994 for (i = 0; s[i] && i < len; i++, glyphs++)
2995 if (is8bits(s[i]))
2996 i++;
2997 } else {
2998 glyphs = len;
2999 }
3000 return glyphs;
3001 }
3002
3003 /*
3004 * Calculates offset in bytes of a glyph at cell position pos.
3005 */
mbcs_skip(char * s,int pos)3006 static int mbcs_skip(char *s, int pos)
3007 {
3008 int p, i;
3009
3010 if (IS_UTF8_TTY) {
3011 for (i = 0, p = 0; s[i]; i++) {
3012 if (!IS_UTF8_EXTRA(s[i]))
3013 p++;
3014 if (p > pos)
3015 break;
3016 }
3017 } else if (IS_CJK_TTY) {
3018 for (p = i = 0; s[i] && p < pos; p++, i++)
3019 if (is8bits(s[i]))
3020 i++;
3021 } else {
3022 i = pos;
3023 }
3024
3025 return i;
3026 }
3027
3028 /*
3029 * Given a string that would display (at least) the given number of cells,
3030 * determine the number of multibyte characters that comprised those cells.
3031 */
cell2char(char * s,int cells)3032 static int cell2char(char *s, int cells)
3033 {
3034 int result = 0;
3035 int len = (int) strlen(s);
3036 int pos;
3037 int have;
3038
3039 CTRACE_EDIT((tfp, "cell2char(%d) %d:%s\n", cells, len, s));
3040 /* FIXME - make this a binary search */
3041 if (len != 0) {
3042 for (pos = 0; pos <= len; ++pos) {
3043 have = LYstrExtent2(s, pos);
3044 CTRACE_EDIT((tfp, " %2d:%2d:%.*s\n", pos, have, pos, s));
3045 if (have >= cells) {
3046 break;
3047 }
3048 }
3049 if (pos > len)
3050 pos = len;
3051 } else {
3052 pos = 0;
3053 }
3054 result = mbcs_glyphs(s, pos);
3055 CTRACE_EDIT((tfp, "->%d\n", result));
3056 return result;
3057 }
3058
3059 #endif /* SUPPORT_MULTIBYTE_EDIT */
3060
3061 #ifdef EXP_KEYBOARD_LAYOUT
3062 static int map_active = 0;
3063
3064 #else
3065 #define map_active 0
3066 #endif
3067
LYEditInsert(EDREC * edit,unsigned const char * s,int len,int map GCC_UNUSED,int maxMessage)3068 int LYEditInsert(EDREC * edit, unsigned const char *s,
3069 int len,
3070 int map GCC_UNUSED,
3071 int maxMessage)
3072 {
3073 int length = (int) strlen(Buf);
3074 int remains = (int) MaxLen - (length + len);
3075 int edited = 0, overflow = 0;
3076
3077 /*
3078 * ch is (presumably) printable character.
3079 */
3080 if (remains < 0) {
3081 overflow = 1;
3082 len = 0;
3083 if ((int) MaxLen > length) /* Insert as much as we can */
3084 len = (int) MaxLen - length;
3085 else
3086 goto finish;
3087 }
3088 ExtendEditor(edit, length + len);
3089 Buf[length + len] = '\0';
3090 for (; length >= Pos; length--) /* Make room */
3091 Buf[length + len] = Buf[length];
3092 #ifdef EXP_KEYBOARD_LAYOUT
3093 if (map < 0)
3094 map = map_active;
3095 if (map && IS_UTF8_TTY) {
3096 int off = Pos;
3097 unsigned const char *e = s + len;
3098 char *tail = 0;
3099
3100 while (s < e) {
3101 char utfbuf[8];
3102 int l = 1;
3103
3104 utfbuf[0] = (char) *s;
3105 if (*s < 128 && LYKbLayouts[current_layout][*s]) {
3106 UCode_t ucode = LYKbLayouts[current_layout][*s];
3107
3108 if (ucode > 127) {
3109 if (UCConvertUniToUtf8(ucode, utfbuf)) {
3110 l = (int) strlen(utfbuf);
3111 remains -= l - 1;
3112 if (remains < 0) {
3113 if (tail)
3114 strcpy(Buf + off, tail);
3115 FREE(tail);
3116 len = off;
3117 overflow = 1;
3118 goto finish;
3119 }
3120 if (l > 1 && !tail)
3121 StrAllocCopy(tail, Buf + Pos + len);
3122 } else
3123 utfbuf[0] = '?';
3124 } else
3125 utfbuf[0] = (char) ucode;
3126 }
3127 StrNCpy(Buf + off, utfbuf, l);
3128 edited = 1;
3129 off += l;
3130 s++;
3131 }
3132 if (tail)
3133 strcpy(Buf + off, tail);
3134 len = off - Pos;
3135 FREE(tail);
3136 } else if (map) {
3137 unsigned const char *e = s + len;
3138 unsigned char *t = (unsigned char *) Buf + Pos;
3139
3140 while (s < e) {
3141 int ch;
3142
3143 if (*s < 128 && LYKbLayouts[current_layout][*s]) {
3144 ch = UCTransUniChar((UCode_t) LYKbLayouts[current_layout][*s],
3145 current_char_set);
3146 if (ch < 0)
3147 ch = '?';
3148 } else
3149 ch = *s;
3150 *t = UCH(ch);
3151 t++, s++;
3152 }
3153 edited = 1;
3154 } else
3155 #endif /* defined EXP_KEYBOARD_LAYOUT */
3156 {
3157 StrNCpy(Buf + Pos, (const char *) s, len);
3158 edited = 1;
3159 }
3160
3161 finish:
3162 Pos += len;
3163 StrLen += (size_t) len;
3164 if (edited)
3165 IsDirty = TRUE;
3166 if (overflow && maxMessage)
3167 _statusline(MAXLEN_REACHED_DEL_OR_MOV);
3168 #ifdef ENHANCED_LINEEDIT
3169 if (Mark > Pos)
3170 Mark += len;
3171 else if (Mark < -(1 + Pos))
3172 Mark -= len;
3173 if (Mark >= 0)
3174 Mark = -(1 + Mark); /* Disable it */
3175 #endif
3176 return edited;
3177 }
3178
3179 /* returns 0 character processed
3180 * -ch if action should be performed outside of line-editing mode
3181 * ch otherwise
3182 */
LYEdit1(EDREC * edit,int ch,int action,int maxMessage)3183 int LYEdit1(EDREC * edit, int ch,
3184 int action,
3185 int maxMessage)
3186 {
3187 int i;
3188 int length;
3189 unsigned char uch;
3190 int offset;
3191
3192 if ((int) MaxLen <= 0)
3193 return (0); /* Be defensive */
3194
3195 StrLen = strlen(&Buf[0]);
3196 length = (int) StrLen;
3197
3198 switch (action) {
3199 #ifdef EXP_KEYBOARD_LAYOUT
3200 case LYE_SWMAP:
3201 /*
3202 * Turn input character mapping on or off.
3203 */
3204 map_active = ~map_active;
3205 break;
3206 #endif
3207 #ifndef CJK_EX
3208 case LYE_AIX:
3209 /*
3210 * Hex 97.
3211 * Fall through as a character for CJK, or if this is a valid character
3212 * in the current display character set. Otherwise, we treat this as
3213 * LYE_ENTER.
3214 */
3215 if (!IS_CJK_TTY && LYlowest_eightbit[current_char_set] > 0x97)
3216 return (ch);
3217 /* FALLTHRU */
3218 #endif
3219 case LYE_CHAR:
3220 uch = UCH(ch);
3221 LYEditInsert(edit, &uch, 1, map_active, maxMessage);
3222 return 0; /* All changes already registered */
3223
3224 case LYE_C1CHAR:
3225 /*
3226 * ch is the second part (in most cases, a capital letter) of a 7-bit
3227 * replacement for a character in the 8-bit C1 control range.
3228 *
3229 * This is meant to undo transformations like 0x81 -> 0x1b 0x41 (ESC A)
3230 * etc. done by slang on Unix and possibly some comm programs. It's
3231 * an imperfect workaround that doesn't work for all such characters.
3232 */
3233 ch &= 0xFF;
3234 if (ch + 64 >= LYlowest_eightbit[current_char_set])
3235 ch += 64;
3236
3237 if (Pos <= ((int) MaxLen) && StrLen < MaxLen) {
3238 #ifdef ENHANCED_LINEEDIT
3239 if (Mark > Pos)
3240 Mark++;
3241 else if (Mark < -(1 + Pos))
3242 Mark--;
3243 if (Mark >= 0)
3244 Mark = -(1 + Mark); /* Disable it */
3245 #endif
3246 ExtendEditor(edit, length + 1);
3247 for (i = length; i >= Pos; i--) /* Make room */
3248 Buf[i + 1] = Buf[i];
3249 Buf[length + 1] = '\0';
3250 Buf[Pos] = (char) ch;
3251 Pos++;
3252 } else {
3253 if (maxMessage) {
3254 _statusline(MAXLEN_REACHED_DEL_OR_MOV);
3255 }
3256 return (ch);
3257 }
3258 break;
3259
3260 case LYE_BACKW:
3261 /*
3262 * Backword.
3263 * Definition of word is very naive: 1 or more a/n characters.
3264 */
3265 #ifndef SUPPORT_MULTIBYTE_EDIT
3266 while (Pos && !isalnum(UCH(Buf[Pos - 1])))
3267 Pos--;
3268 while (Pos && isalnum(UCH(Buf[Pos - 1])))
3269 Pos--;
3270 #else
3271 while (Pos && !(isalnum(UCH(Buf[Pos - 1])) || is8bits(Buf[Pos - 1])))
3272 Pos--;
3273 while (Pos && (isalnum(UCH(Buf[Pos - 1])) || is8bits(Buf[Pos - 1])))
3274 Pos--;
3275 #endif
3276 break;
3277
3278 case LYE_FORWW:
3279 /*
3280 * Word forward.
3281 */
3282 #ifndef SUPPORT_MULTIBYTE_EDIT
3283 while (isalnum(UCH(Buf[Pos])))
3284 Pos++; /* '\0' is not a/n */
3285 while (!isalnum(UCH(Buf[Pos])) && Buf[Pos])
3286 Pos++;
3287 #else
3288 while (isalnum(UCH(Buf[Pos])) || is8bits(Buf[Pos]))
3289 Pos++; /* '\0' is not a/n */
3290 while (!(isalnum(UCH(Buf[Pos])) || is8bits(Buf[Pos])) && Buf[Pos])
3291 Pos++;
3292 #endif
3293 break;
3294
3295 case LYE_ERASE:
3296 /*
3297 * Erase the line to start fresh.
3298 */
3299 Buf[0] = '\0';
3300 #ifdef ENHANCED_LINEEDIT
3301 Mark = -1; /* Do not show the mark */
3302 #endif
3303 /* fall through */
3304
3305 case LYE_BOL:
3306 /*
3307 * Go to first column.
3308 */
3309 Pos = 0;
3310 break;
3311
3312 case LYE_EOL:
3313 /*
3314 * Go to last column.
3315 */
3316 Pos = length;
3317 break;
3318
3319 case LYE_DELNW:
3320 /*
3321 * Delete next word.
3322 */
3323 offset = Pos;
3324 LYEdit1(edit, 0, LYE_FORWW, FALSE);
3325 offset = Pos - offset;
3326 Pos -= offset;
3327
3328 goto shrink; /* right below */
3329
3330 case LYE_DELPW:
3331 /*
3332 * Delete previous word.
3333 */
3334 offset = Pos;
3335 LYEdit1(edit, 0, LYE_BACKW, FALSE);
3336 offset -= Pos;
3337
3338 shrink:
3339 for (i = Pos; i < length - offset + 1; i++)
3340 Buf[i] = Buf[i + offset];
3341 #ifdef ENHANCED_LINEEDIT
3342 if (Mark >= 0)
3343 Mark = -(1 + Mark); /* Disable it */
3344 if (Mark <= -(1 + Pos + offset))
3345 Mark += offset; /* Shift it */
3346 if (-(1 + Pos + offset) < Mark && Mark < -(1 + Pos))
3347 Mark = -(1 + Pos); /* Set to the current position */
3348 #endif
3349
3350 break;
3351
3352 case LYE_DELBL:
3353 /*
3354 * Delete from current cursor position back to BOL.
3355 */
3356 for (i = Pos; i < length + 1; i++)
3357 Buf[i - Pos] = Buf[i];
3358
3359 #ifdef ENHANCED_LINEEDIT
3360 if (Mark >= 0)
3361 Mark = -(1 + Mark); /* Disable it */
3362 if (Mark <= -(1 + Pos))
3363 Mark += Pos; /* Shift it */
3364 else
3365 Mark = -1; /* Reset it */
3366 #endif
3367 Pos = 0;
3368 break;
3369
3370 case LYE_DELEL: /* @@@ */
3371 /*
3372 * Delete from current cursor position thru EOL.
3373 */
3374 Buf[Pos] = '\0';
3375 #ifdef ENHANCED_LINEEDIT
3376 if (Mark >= 0)
3377 Mark = -(1 + Mark); /* Disable it */
3378 if (Mark <= -(1 + Pos))
3379 Mark = -1; /* Reset it */
3380 #endif
3381 break;
3382
3383 case LYE_DELN:
3384 /*
3385 * Delete next character (I-beam style cursor), or current character
3386 * (box/underline style cursor).
3387 */
3388 if (Pos >= length)
3389 break;
3390 #ifndef SUPPORT_MULTIBYTE_EDIT
3391 Pos++;
3392 #else
3393 Pos += mbcs_skip(Buf + Pos, 1);
3394 #endif
3395 /* fall through - DO NOT RELOCATE the LYE_DELN case wrt LYE_DELP */
3396
3397 case LYE_DELP:
3398 /*
3399 * Delete preceding character.
3400 */
3401 if (length == 0 || Pos == 0)
3402 break;
3403
3404 #ifndef SUPPORT_MULTIBYTE_EDIT
3405 #ifdef ENHANCED_LINEEDIT
3406 if (Mark >= 0)
3407 Mark = -(1 + Mark); /* Disable it */
3408 if (Mark <= -(1 + Pos))
3409 Mark++;
3410 #endif
3411 Pos--;
3412 for (i = Pos; i < length; i++)
3413 Buf[i] = Buf[i + 1];
3414 #else /* SUPPORT_MULTIBYTE_EDIT */
3415 offset = Pos - mbcs_skip(Buf, mbcs_glyphs(Buf, Pos) - 1);
3416 Pos -= offset;
3417 for (i = Pos; i < length - offset + 1; i++)
3418 Buf[i] = Buf[i + offset];
3419
3420 #ifdef ENHANCED_LINEEDIT
3421 if (Mark >= 0)
3422 Mark = -(1 + Mark); /* Disable it */
3423 if (Mark <= -(1 + Pos))
3424 Mark += offset; /* Shift it */
3425 #endif
3426
3427 #endif /* SUPPORT_MULTIBYTE_EDIT */
3428 break;
3429
3430 case LYE_FORW_RL:
3431 case LYE_FORW:
3432 /*
3433 * Move cursor to the right.
3434 */
3435 #ifndef SUPPORT_MULTIBYTE_EDIT
3436 if (Pos < length)
3437 Pos++;
3438 #else
3439 if (Pos < length)
3440 Pos += mbcs_skip(Buf + Pos, 1);
3441 #endif
3442 else if (action == LYE_FORW_RL)
3443 return -ch;
3444 break;
3445
3446 case LYE_BACK_LL:
3447 case LYE_BACK:
3448 /*
3449 * Left-arrow move cursor to the left.
3450 */
3451 #ifndef SUPPORT_MULTIBYTE_EDIT
3452 if (Pos > 0)
3453 Pos--;
3454 #else
3455 if (Pos > 0)
3456 Pos = mbcs_skip(Buf, mbcs_glyphs(Buf, Pos) - 1);
3457 #endif
3458 else if (action == LYE_BACK_LL)
3459 return -ch;
3460 break;
3461
3462 #ifdef ENHANCED_LINEEDIT
3463 case LYE_TPOS:
3464 /*
3465 * Transpose characters - bash or ksh(emacs not gmacs) style
3466 */
3467
3468 #ifdef SUPPORT_MULTIBYTE_EDIT
3469 if (IS_UTF8_TTY || IS_CJK_TTY)
3470 break; /* Can't help it now */
3471 #endif
3472
3473 if (length <= 1 || Pos == 0)
3474 return (ch);
3475 if (Pos == length)
3476 Pos--;
3477 if (Mark < 0)
3478 Mark = -(1 + Mark); /* Temporary enable it */
3479 if (Mark == Pos || Mark == Pos + 1)
3480 Mark = Pos - 1;
3481 if (Mark >= 0)
3482 Mark = -(1 + Mark); /* Disable it */
3483 if (Buf[Pos - 1] == Buf[Pos]) {
3484 Pos++;
3485 break;
3486 }
3487 i = Buf[Pos - 1];
3488 Buf[Pos - 1] = Buf[Pos];
3489 Buf[Pos++] = (char) i;
3490 break;
3491
3492 case LYE_SETMARK:
3493 /*
3494 * primitive emacs-like set-mark-command
3495 */
3496 Mark = Pos;
3497 return (0);
3498
3499 case LYE_XPMARK:
3500 /*
3501 * emacs-like exchange-point-and-mark
3502 */
3503 if (Mark < 0)
3504 Mark = -(1 + Mark); /* Enable it */
3505 if (Mark == Pos)
3506 return (0);
3507 i = Pos;
3508 Pos = Mark;
3509 Mark = i;
3510 break;
3511
3512 case LYE_KILLREG:
3513 /*
3514 * primitive emacs-like kill-region
3515 */
3516 if (Mark < 0)
3517 Mark = -(1 + Mark); /* Enable it */
3518 if (Mark == Pos) {
3519 BStrFree(killbuffer);
3520 return (0);
3521 }
3522 if (Mark > Pos)
3523 LYEdit1(edit, 0, LYE_XPMARK, FALSE);
3524 {
3525 int reglen = Pos - Mark;
3526
3527 BStrCopy1(killbuffer, Buf + Mark, reglen);
3528 for (i = Mark; Buf[i + reglen]; i++)
3529 Buf[i] = Buf[i + reglen];
3530 Buf[i] = Buf[i + reglen]; /* terminate */
3531 Pos = Mark;
3532 }
3533 if (Mark >= 0)
3534 Mark = -(1 + Mark); /* Disable it */
3535 break;
3536
3537 case LYE_YANK:
3538 /*
3539 * primitive emacs-like yank
3540 */
3541 if (!killbuffer) {
3542 Mark = -(1 + Pos);
3543 return (0);
3544 } else {
3545 int yanklen = killbuffer->len;
3546
3547 if ((Pos + yanklen) <= (int) MaxLen &&
3548 StrLen + (size_t) yanklen <= MaxLen) {
3549
3550 ExtendEditor(edit, Pos + yanklen);
3551
3552 Mark = -(1 + Pos);
3553
3554 for (i = length; i >= Pos; i--) /* Make room */
3555 Buf[i + yanklen] = Buf[i];
3556 for (i = 0; i < yanklen; i++)
3557 Buf[Pos++] = killbuffer->str[i];
3558
3559 } else if (maxMessage) {
3560 _statusline(MAXLEN_REACHED_DEL_OR_MOV);
3561 }
3562 }
3563 break;
3564
3565 #endif /* ENHANCED_LINEEDIT */
3566
3567 case LYE_UPPER:
3568 LYUpperCase(Buf);
3569 break;
3570
3571 case LYE_LOWER:
3572 LYLowerCase(Buf);
3573 break;
3574
3575 default:
3576 return (ch);
3577 }
3578 IsDirty = TRUE;
3579 StrLen = strlen(&Buf[0]);
3580 return (0);
3581 }
3582
3583 /*
3584 * This function prompts for a choice or page number.
3585 * If a 'g' or 'p' suffix is included, that will be
3586 * loaded into c. Otherwise, c is zeroed. - FM & LE
3587 */
get_popup_number(const char * msg,int * c,int * rel)3588 int get_popup_number(const char *msg,
3589 int *c,
3590 int *rel)
3591 {
3592 bstring *temp = NULL;
3593 int result = 0;
3594
3595 /*
3596 * Load the c argument into the prompt buffer.
3597 */
3598 BStrCopy0(temp, "?");
3599 temp->str[0] = (char) *c;
3600
3601 _statusline(msg);
3602
3603 /*
3604 * Get the number, possibly with a suffix, from the user.
3605 */
3606 if (LYgetBString(&temp, VISIBLE, 0, NORECALL) < 0 || isBEmpty(temp)) {
3607 HTInfoMsg(CANCELLED);
3608 *c = '\0';
3609 *rel = '\0';
3610 } else {
3611 char *p = temp->str;
3612
3613 *rel = '\0';
3614 result = atoi(p);
3615 while (isdigit(UCH(*p)))
3616 ++p;
3617 switch (*p) {
3618 case '+':
3619 case '-':
3620 /* 123+ or 123- */
3621 *rel = *p++;
3622 *c = *p;
3623 break;
3624 default:
3625 *c = *p++;
3626 *rel = *p;
3627 break;
3628 case 0:
3629 break;
3630 }
3631
3632 /*
3633 * If we had a 'g' or 'p' suffix, load it into c. Otherwise, zero c. Then
3634 * return the number.
3635 */
3636 if (*p == 'g' || *p == 'G') {
3637 *c = 'g';
3638 } else if (*p == 'p' || *p == 'P') {
3639 *c = 'p';
3640 } else {
3641 *c = '\0';
3642 }
3643 if (*rel != '+' && *rel != '-')
3644 *rel = 0;
3645 }
3646 BStrFree(temp);
3647 return result;
3648 }
3649
3650 #ifdef USE_COLOR_STYLE
3651 # define TmpStyleOn(s) curses_style((s), STACK_ON)
3652 # define TmpStyleOff(s) curses_style((s), STACK_OFF)
3653 #else
3654 # define TmpStyleOn(s)
3655 # define TmpStyleOff(s)
3656 #endif /* defined USE_COLOR_STYLE */
3657
remember_column(EDREC * edit,int offset)3658 static void remember_column(EDREC * edit, int offset)
3659 {
3660 int y0, x0;
3661
3662 #if defined(USE_SLANG)
3663 y0 = 0;
3664 x0 = SLsmg_get_column();
3665 #elif defined(USE_CURSES_PADS)
3666 getyx(LYwin, y0, x0);
3667 #else
3668 getyx(stdscr, y0, x0);
3669 #endif
3670 Offs2Col[offset] = x0;
3671
3672 (void) y0;
3673 (void) x0;
3674 }
3675
fill_edited_line(int prompting GCC_UNUSED,int length,int ch)3676 static void fill_edited_line(int prompting GCC_UNUSED, int length, int ch)
3677 {
3678 int i;
3679
3680 TmpStyleOn(prompting ? s_prompt_edit_pad : s_aedit_pad);
3681
3682 for (i = 0; i < length; i++) {
3683 LYaddch(UCH(ch));
3684 }
3685
3686 TmpStyleOff(prompting ? s_prompt_edit_pad : s_aedit_pad);
3687 }
3688
3689 /*
3690 * Multibyte string display subroutine.
3691 * EDREC fields retain their values as byte offsets.
3692 * All external logic still works fine with byte values.
3693 */
LYRefreshEdit(EDREC * edit)3694 void LYRefreshEdit(EDREC * edit)
3695 {
3696 /* bytes and characters are not the same thing */
3697 #if defined(DEBUG_EDIT)
3698 int all_bytes;
3699 #endif
3700 int pos_bytes = Pos;
3701 int dpy_bytes;
3702 int lft_bytes; /* base of string which is displayed */
3703
3704 /* cells refer to display-columns on the screen */
3705 int all_cells; /* total of display-cells in Buf */
3706 int dpy_cells; /* number of cells which are displayed */
3707 int lft_cells; /* number of cells before display (on left) */
3708 int pos_cells; /* number of display-cells up to Pos */
3709
3710 #if defined(SUPPORT_MULTIBYTE_EDIT)
3711 int dpy_chars;
3712 int lft_chars;
3713
3714 #if defined(DEBUG_EDIT)
3715 int all_chars;
3716 int pos_chars;
3717 #endif
3718 #endif
3719
3720 /* other data */
3721 int i;
3722 int padsize;
3723 char *str;
3724 int lft_shift = 0;
3725 int rgt_shift = 0;
3726
3727 #ifdef USE_COLOR_STYLE
3728 int estyle;
3729 #endif
3730 int prompting = 0;
3731
3732 (void) pos_bytes;
3733
3734 /*
3735 * If we've made no changes, or if there is nothing to display, just leave.
3736 */
3737 if (!IsDirty || (DspWdth == 0))
3738 return;
3739
3740 CTRACE((tfp, "LYRefreshEdit:%s\n", Buf));
3741
3742 IsDirty = FALSE;
3743
3744 StrLen = strlen(&Buf[0]);
3745
3746 all_cells = LYstrCells(Buf);
3747 pos_cells = LYstrExtent2(Buf, Pos);
3748
3749 #if defined(SUPPORT_MULTIBYTE_EDIT) && defined(DEBUG_EDIT)
3750 all_bytes = (int) StrLen;
3751 lft_chars = mbcs_glyphs(Buf, DspStart);
3752 pos_chars = mbcs_glyphs(Buf, Pos);
3753 all_chars = mbcs_glyphs(Buf, all_bytes);
3754 #endif
3755
3756 /*
3757 * Now we have:
3758 * .--DspWdth---.
3759 * +---------+=============+-----------+
3760 * | |M M| | (M=margin)
3761 * +---------+=============+-----------+
3762 * 0 DspStart StrLen
3763 *
3764 * Insertion point can be anywhere between 0 and stringlength. Figure out
3765 * new display starting point.
3766 *
3767 * The first "if" below makes Lynx scroll several columns at a time when
3768 * extending the string. Looks awful, but that way we can keep up with
3769 * data entry at low baudrates.
3770 */
3771
3772 lft_bytes = DspStart;
3773 lft_cells = LYstrExtent2(Buf, DspStart);
3774
3775 if ((lft_cells + DspWdth) <= all_cells) {
3776 if (pos_cells >= (lft_cells + DspWdth) - Margin) {
3777 lft_cells = (pos_cells - DspWdth) + Margin;
3778 #ifdef SUPPORT_MULTIBYTE_EDIT
3779 lft_chars = cell2char(Buf, lft_cells);
3780 lft_bytes = mbcs_skip(Buf, lft_chars);
3781 #else
3782 lft_bytes = lft_cells;
3783 #endif /* SUPPORT_MULTIBYTE_EDIT */
3784 }
3785 }
3786
3787 if (pos_cells < lft_cells + Margin) {
3788 lft_cells = pos_cells - Margin;
3789 if (lft_cells < 0)
3790 lft_cells = 0;
3791 #ifdef SUPPORT_MULTIBYTE_EDIT
3792 lft_chars = cell2char(Buf, lft_cells);
3793 lft_bytes = mbcs_skip(Buf, lft_chars);
3794 #else
3795 lft_bytes = lft_cells;
3796 #endif /* SUPPORT_MULTIBYTE_EDIT */
3797 }
3798
3799 LYmove(StartY, StartX);
3800
3801 /*
3802 * Draw the left scrolling-indicator now, to avoid the complication of
3803 * overwriting part of a multicolumn character which may lie in the first
3804 * position.
3805 */
3806 if (PanOn && lft_cells) {
3807 CTRACE_EDIT((tfp, "Draw left scroll-indicator\n"));
3808 TmpStyleOn(prompting ? s_prompt_edit_arr : s_aedit_arr);
3809 LYmove(StartY, StartX);
3810 LYaddch(ACS_LARROW);
3811 TmpStyleOff(prompting ? s_prompt_edit_arr : s_aedit_arr);
3812 lft_shift = 1;
3813 }
3814
3815 str = &Buf[lft_bytes];
3816 DspStart = lft_bytes;
3817
3818 dpy_cells = all_cells - lft_cells;
3819 CTRACE_EDIT((tfp, "Comparing dpy_cells %d > (%d - %d)\n",
3820 dpy_cells, DspWdth, lft_shift));
3821 if (dpy_cells > (DspWdth - lft_shift)) {
3822 rgt_shift = 1;
3823 dpy_cells = (DspWdth - lft_shift - rgt_shift);
3824 }
3825 for (;;) {
3826 #ifdef SUPPORT_MULTIBYTE_EDIT
3827 dpy_chars = cell2char(str, dpy_cells);
3828 dpy_bytes = mbcs_skip(str, dpy_chars);
3829 #else
3830 dpy_bytes = dpy_cells;
3831 #endif /* SUPPORT_MULTIBYTE_EDIT */
3832 /*
3833 * The last character on the display may be multicolumn, and if we take
3834 * away a single cell for the right scroll-indicator, that would force
3835 * us to display fewer characters. Check for that, and recompute.
3836 */
3837 if (rgt_shift && *str) {
3838 int old_cells = dpy_cells;
3839
3840 dpy_cells = LYstrExtent2(str, dpy_bytes);
3841 if (dpy_cells > old_cells)
3842 dpy_cells = old_cells - 1;
3843
3844 CTRACE_EDIT((tfp, "Comparing cells %d vs %d\n", dpy_cells, old_cells));
3845 if (dpy_cells < old_cells) {
3846 CTRACE_EDIT((tfp, "Recomputing...\n"));
3847 continue;
3848 }
3849 }
3850 break;
3851 }
3852
3853 CTRACE_EDIT((tfp, "BYTES left %2d pos %2d dpy %2d all %2d\n",
3854 lft_bytes, pos_bytes, dpy_bytes, all_bytes));
3855 CTRACE_EDIT((tfp, "CELLS left %2d pos %2d dpy %2d all %2d\n",
3856 lft_cells, pos_cells, dpy_cells, all_cells));
3857 CTRACE_EDIT((tfp, "CHARS left %2d pos %2d dpy %2d all %2d\n",
3858 lft_chars, pos_chars, dpy_chars, all_chars));
3859
3860 #ifdef USE_COLOR_STYLE
3861 /*
3862 * If this is the last screen line, set attributes to normal, should only
3863 * be needed for color styles. The curses function may be used directly to
3864 * avoid complications. - kw
3865 */
3866 if (StartY == (LYlines - 1))
3867 prompting = 1;
3868 if (prompting) {
3869 estyle = s_prompt_edit;
3870 } else {
3871 estyle = s_aedit;
3872 }
3873 CTRACE2(TRACE_STYLE,
3874 (tfp, "STYLE.getstr: switching to <edit.%s>.\n",
3875 prompting ? "prompt" : "active"));
3876 if (estyle != NOSTYLE) {
3877 curses_style(estyle, STACK_ON);
3878 } else {
3879 (void) wattrset(LYwin, A_NORMAL); /* need to do something about colors? */
3880 }
3881 #endif
3882 if (IsHidden) {
3883 BOOL utf_flag = IS_UTF8_TTY;
3884 int cell = 0;
3885
3886 fill_edited_line(0, dpy_cells, '*');
3887
3888 i = 0;
3889 do {
3890 const char *last = str + i;
3891 const char *next = LYmbcs_skip_glyphs(last, 1, utf_flag);
3892 int j = (int) (next - str);
3893
3894 while (i < j) {
3895 Offs2Col[i++] = cell + StartX;
3896 }
3897 cell += LYstrExtent2(last, (int) (next - last));
3898 } while (i < dpy_bytes);
3899 Offs2Col[i] = cell + StartX;
3900 } else {
3901 #if defined(ENHANCED_LINEEDIT) && defined(USE_COLOR_STYLE)
3902 if (Mark >= 0 && DspStart > Mark)
3903 TmpStyleOn(prompting ? s_prompt_sel : s_aedit_sel);
3904 #endif
3905 remember_column(edit, 0);
3906 for (i = 0; i < dpy_bytes; i++) {
3907 #if defined(ENHANCED_LINEEDIT) && defined(USE_COLOR_STYLE)
3908 if (Mark >= 0 && ((DspStart + i == Mark && Pos > Mark)
3909 || (DspStart + i == Pos && Pos < Mark)))
3910 TmpStyleOn(prompting ? s_prompt_sel : s_aedit_sel);
3911 if (Mark >= 0 && ((DspStart + i == Mark && Pos < Mark)
3912 || (DspStart + i == Pos && Pos > Mark)))
3913 TmpStyleOff(prompting ? s_prompt_sel : s_aedit_sel);
3914 #endif
3915 if (str[i] == 1 || str[i] == 2 ||
3916 (UCH(str[i]) == 160 &&
3917 !(HTPassHighCtrlRaw || IS_CJK_TTY ||
3918 (LYCharSet_UC[current_char_set].enc != UCT_ENC_8859 &&
3919 !(LYCharSet_UC[current_char_set].like8859
3920 & UCT_R_8859SPECL))))) {
3921 LYaddch(' ');
3922 } else if (str[i] == '\t') {
3923 int col = Offs2Col[i] - StartX;
3924
3925 /*
3926 * Like LYwaddnstr(), expand tabs from the beginning of the
3927 * field.
3928 */
3929 while (++col % 8)
3930 LYaddch(' ');
3931 LYaddch(' ');
3932 } else {
3933 LYaddch(UCH(str[i]));
3934 }
3935 remember_column(edit, i + 1);
3936 }
3937 #if defined(ENHANCED_LINEEDIT) && defined(USE_COLOR_STYLE)
3938 if (Mark >= 0 &&
3939 ((DspStart + dpy_bytes <= Mark && DspStart + dpy_bytes > Pos)
3940 || (DspStart + dpy_bytes > Mark
3941 && DspStart + dpy_bytes <= Pos))) {
3942 TmpStyleOff(prompting ? s_prompt_sel : s_aedit_sel);
3943 }
3944 #endif
3945 }
3946
3947 /*
3948 * Erase rest of input area.
3949 */
3950 padsize = DspWdth - (Offs2Col[dpy_bytes] - StartX);
3951 fill_edited_line(prompting, padsize, PadChar);
3952
3953 /*
3954 * Scrolling indicators.
3955 */
3956 if (PanOn && dpy_bytes && rgt_shift) {
3957 CTRACE((tfp, "Draw right-scroller offset (%d + %d)\n",
3958 dpy_cells, lft_shift));
3959 TmpStyleOn(prompting ? s_prompt_edit_arr : s_aedit_arr);
3960 LYmove(StartY, StartX + dpy_cells + lft_shift);
3961 LYaddch(ACS_RARROW);
3962 TmpStyleOff(prompting ? s_prompt_edit_arr : s_aedit_arr);
3963 }
3964
3965 /*
3966 * Finally, move the cursor to the point where the next edit will occur.
3967 */
3968 LYmove(StartY, Offs2Col[Pos - DspStart]);
3969
3970 #ifdef USE_COLOR_STYLE
3971 if (estyle != NOSTYLE)
3972 curses_style(estyle, STACK_OFF);
3973 #endif
3974 LYrefresh();
3975 }
3976
reinsertEdit(EditFieldData * edit,char * result)3977 static void reinsertEdit(EditFieldData *edit, char *result)
3978 {
3979 if (result != 0) {
3980 LYEdit1(edit, '\0', LYE_ERASE, FALSE);
3981 while (*result != '\0') {
3982 LYLineEdit(edit, (int) (*result), FALSE);
3983 result++;
3984 }
3985 }
3986 }
3987
caselessCmpList(const void * a,const void * b)3988 static int caselessCmpList(const void *a,
3989 const void *b)
3990 {
3991 return strcasecomp(*(STRING2PTR) a, *(STRING2PTR) b);
3992 }
3993
normalCmpList(const void * a,const void * b)3994 static int normalCmpList(const void *a,
3995 const void *b)
3996 {
3997 return strcmp(*(STRING2PTR) a, *(STRING2PTR) b);
3998 }
3999
sortedList(HTList * list,int ignorecase)4000 static char **sortedList(HTList *list, int ignorecase)
4001 {
4002 size_t count = (unsigned) HTList_count(list);
4003 size_t j = 0;
4004 size_t k, jk;
4005 char **result = typecallocn(char *, count + 1);
4006
4007 if (result == 0)
4008 outofmem(__FILE__, "sortedList");
4009
4010 assert(result != 0);
4011
4012 while (!HTList_isEmpty(list))
4013 result[j++] = (char *) HTList_nextObject(list);
4014
4015 if (count > 1) {
4016 qsort((char *) result, count, sizeof(*result),
4017 ignorecase ? caselessCmpList : normalCmpList);
4018
4019 /* remove duplicate entries from the sorted index */
4020 for (j = 0; result[j] != 0; j++) {
4021 k = j;
4022 while (result[k] != 0
4023 && !strcmp(result[j], result[k])) {
4024 k++;
4025 }
4026 k--;
4027 if (j != k) {
4028 for (jk = j;; jk++) {
4029 result[jk] = result[jk + k - j];
4030 if (result[jk] == 0)
4031 break;
4032 }
4033 }
4034 }
4035 }
4036
4037 return result;
4038 }
4039
LYarrayLength(STRING2PTR list)4040 int LYarrayLength(STRING2PTR list)
4041 {
4042 int result = 0;
4043
4044 while (*list++ != 0)
4045 result++;
4046 return result;
4047 }
4048
LYarrayWidth(STRING2PTR list)4049 int LYarrayWidth(STRING2PTR list)
4050 {
4051 int result = 0;
4052 int check;
4053
4054 while (*list != 0) {
4055 check = (int) strlen(*list++);
4056 if (check > result)
4057 result = check;
4058 }
4059 return result;
4060 }
4061
FormatChoiceNum(char * dst,int num_choices,int choice,const char * value)4062 static void FormatChoiceNum(char *dst,
4063 int num_choices,
4064 int choice,
4065 const char *value)
4066 {
4067 if (num_choices >= 0) {
4068 int digits = (num_choices > 9) ? 2 : 1;
4069
4070 sprintf(dst, "%*d: %.*s",
4071 digits, (choice + 1),
4072 MAX_LINE - 9 - digits, value);
4073 } else {
4074 LYStrNCpy(dst, value, MAX_LINE - 1);
4075 }
4076 }
4077
options_width(STRING2PTR list)4078 static unsigned options_width(STRING2PTR list)
4079 {
4080 unsigned width = 0;
4081 int count = 0;
4082
4083 while (list[count] != 0) {
4084 unsigned ncells = (unsigned) LYstrCells(list[count]);
4085
4086 if (ncells > width) {
4087 width = ncells;
4088 }
4089 count++;
4090 }
4091 return width;
4092 }
4093
draw_option(WINDOW * win,int entry,int width,int reversed,int num_choices,int number,const char * value)4094 static void draw_option(WINDOW * win, int entry,
4095 int width,
4096 int reversed,
4097 int num_choices,
4098 int number,
4099 const char *value)
4100 {
4101 char Cnum[MAX_LINE];
4102
4103 (void) width;
4104
4105 FormatChoiceNum(Cnum, num_choices, number, "");
4106 #ifdef USE_SLANG
4107 SLsmg_gotorc(win->top_y + entry, (win->left_x + 2));
4108 LYaddstr(Cnum);
4109 if (reversed)
4110 SLsmg_set_color(2);
4111 SLsmg_write_nstring((SLFUTURE_CONST char *) value, (unsigned) win->width);
4112 if (reversed)
4113 SLsmg_set_color(0);
4114 #else
4115 wmove(win, entry, 1);
4116 LynxWChangeStyle(win, s_menu_entry, STACK_ON);
4117 waddch(win, ' ');
4118 LynxWChangeStyle(win, s_menu_entry, STACK_OFF);
4119 LynxWChangeStyle(win, s_menu_number, STACK_ON);
4120 waddstr(win, Cnum);
4121 LynxWChangeStyle(win, s_menu_number, STACK_OFF);
4122 #ifdef USE_COLOR_STYLE
4123 LynxWChangeStyle(win, reversed ? s_menu_active : s_menu_entry, STACK_ON);
4124 #else
4125 if (reversed)
4126 wstart_reverse(win);
4127 #endif
4128 LYpaddstr(win, width, value);
4129 #ifdef USE_COLOR_STYLE
4130 LynxWChangeStyle(win, reversed ? s_menu_active : s_menu_entry, STACK_OFF);
4131 #else
4132 if (reversed)
4133 wstop_reverse(win);
4134 #endif
4135 LynxWChangeStyle(win, s_menu_entry, STACK_ON);
4136 waddch(win, ' ');
4137 LynxWChangeStyle(win, s_menu_entry, STACK_OFF);
4138 #endif /* USE_SLANG */
4139 }
4140
4141 /*
4142 * This function offers the choices for values of an option via a popup window
4143 * which functions like that for selection of options in a form. - FM
4144 *
4145 * Also used for mouse popups with ncurses; this is indicated by for_mouse.
4146 */
LYhandlePopupList(int cur_choice,int ly,int lx,STRING2PTR choices,int width,int i_length,int disabled,int for_mouse)4147 int LYhandlePopupList(int cur_choice,
4148 int ly,
4149 int lx,
4150 STRING2PTR choices,
4151 int width,
4152 int i_length,
4153 int disabled,
4154 int for_mouse)
4155 {
4156 BOOLEAN numbered = (BOOLEAN) (keypad_mode != NUMBERS_AS_ARROWS);
4157 int c = 0, cmd = 0, i = 0, j = 0, rel = 0;
4158 int orig_choice;
4159 WINDOW *form_window;
4160 int num_choices = 0;
4161 int max_choices = 0;
4162 int top, bottom, length = -1;
4163 int window_offset = 0;
4164 int lines_to_show;
4165 char Cnum[64];
4166 int Lnum;
4167 int npages;
4168 static bstring *prev_target = NULL; /* Search string buffer */
4169 static bstring *next_target = NULL; /* Next search buffer */
4170 static BOOL first = TRUE;
4171 char *cp;
4172 int ch = 0;
4173 RecallType recall;
4174 int QueryTotal;
4175 int QueryNum;
4176 BOOLEAN FirstRecall = TRUE;
4177 BOOLEAN ReDraw = FALSE;
4178 int number;
4179 char buffer[MAX_LINE];
4180 const char *popup_status_msg = NULL;
4181 STRING2PTR Cptr = NULL;
4182
4183 #define CAN_SCROLL_DOWN 1
4184 #define CAN_SCROLL_UP 2
4185 #define CAN_SCROLL 4
4186 int can_scroll = 0, can_scroll_was = 0;
4187
4188 orig_choice = cur_choice;
4189 if (cur_choice < 0)
4190 cur_choice = 0;
4191
4192 /*
4193 * Initialize the search string buffer. - FM
4194 */
4195 if (first) {
4196 BStrCopy0(next_target, "");
4197 first = FALSE;
4198 }
4199 BStrCopy0(prev_target, "");
4200 QueryTotal = (search_queries ? HTList_count(search_queries) : 0);
4201 recall = ((QueryTotal >= 1) ? RECALL_URL : NORECALL);
4202 QueryNum = QueryTotal;
4203
4204 /*
4205 * Count the number of choices to be displayed, where num_choices ranges
4206 * from 0 to n, and set width to the longest choice string length. Also
4207 * set Lnum to the length for the highest choice number, then decrement
4208 * num_choices so as to be zero-based. The window width will be based on
4209 * the sum of width and Lnum. - FM
4210 */
4211 num_choices = LYarrayLength(choices) - 1;
4212 if (width <= 0)
4213 width = (int) options_width(choices);
4214 if (numbered) {
4215 sprintf(Cnum, "%d: ", num_choices);
4216 Lnum = (int) strlen(Cnum);
4217 max_choices = num_choices;
4218 } else {
4219 Lnum = 0;
4220 max_choices = -1;
4221 }
4222
4223 /*
4224 * Let's assume for the sake of sanity that ly is the number corresponding
4225 * to the line the choice is on.
4226 *
4227 * Let's also assume that cur_choice is the number of the item that should
4228 * be initially selected, as 0 being the first item.
4229 *
4230 * So what we have, is the top equal to the current screen line subtracting
4231 * the cur_choice + 1 (the one must be for the top line we will draw in a
4232 * box). If the top goes under 0, consider it 0.
4233 */
4234 top = ly - (cur_choice + 1);
4235 if (top < 0)
4236 top = 0;
4237
4238 /*
4239 * Check and see if we need to put the i_length parameter up to the number
4240 * of real choices.
4241 */
4242 if (i_length < 1) {
4243 i_length = num_choices;
4244 } else {
4245 /*
4246 * Otherwise, it is really one number too high.
4247 */
4248 i_length--;
4249 }
4250
4251 /*
4252 * The bottom is the value of the top plus the number of options to view
4253 * plus 3 (one for the top line, one for the bottom line, and one to offset
4254 * the 0 counted in the num_choices).
4255 */
4256 bottom = top + i_length + 3;
4257
4258 /*
4259 * Set lines_to_show based on the user_mode global.
4260 */
4261 if (user_mode == NOVICE_MODE)
4262 lines_to_show = LYlines - 4;
4263 else
4264 lines_to_show = LYlines - 2;
4265
4266 if (for_mouse && user_mode == NOVICE_MODE && lines_to_show > 2)
4267 lines_to_show--;
4268
4269 /*
4270 * Hmm... If the bottom goes beyond the number of lines available,
4271 */
4272 if (bottom > lines_to_show) {
4273 /*
4274 * Position the window at the top if we have more choices than will fit
4275 * in the window.
4276 */
4277 if ((i_length + 3) > lines_to_show) {
4278 top = 0;
4279 bottom = (top + (i_length + 3));
4280 if (bottom > lines_to_show)
4281 bottom = (lines_to_show + 1);
4282 } else {
4283 /*
4284 * Try to position the window so that the selected choice will
4285 * appear where the selection box currently is positioned. It
4286 * could end up too high, at this point, but we'll move it down
4287 * latter, if that has happened.
4288 */
4289 top = (lines_to_show + 1) - (i_length + 3);
4290 bottom = (lines_to_show + 1);
4291 }
4292 }
4293
4294 /*
4295 * This is really fun, when the length is 4, it means 0 to 4, or 5.
4296 */
4297 length = (bottom - top) - 2;
4298 if (length <= num_choices)
4299 can_scroll = CAN_SCROLL;
4300
4301 /*
4302 * Move the window down if it's too high.
4303 */
4304 if (bottom < ly + 2) {
4305 bottom = ly + 2;
4306 if (bottom > lines_to_show + 1)
4307 bottom = lines_to_show + 1;
4308 top = bottom - length - 2;
4309 }
4310
4311 if (for_mouse) {
4312 int check = (Lnum + (int) width + 4);
4313 int limit = LYcols;
4314
4315 /* shift horizontally to lie within screen width, if possible */
4316 if (check < limit) {
4317 if (lx - 1 + check > limit)
4318 lx = limit + 1 - check;
4319 else if (lx <= 0)
4320 lx = 1;
4321 }
4322 }
4323
4324 /*
4325 * Set up the overall window, including the boxing characters ('*'), if it
4326 * all fits. Otherwise, set up the widest window possible. - FM
4327 */
4328 width += Lnum;
4329 bottom -= top;
4330
4331 if (num_choices <= 0
4332 || cur_choice > num_choices
4333 || (form_window = LYstartPopup(&top,
4334 &lx,
4335 &bottom,
4336 &width)) == 0)
4337 return (orig_choice);
4338
4339 width -= Lnum;
4340 bottom += top;
4341
4342 /*
4343 * Clear the command line and write the popup statusline. - FM
4344 */
4345 if (disabled) {
4346 popup_status_msg = CHOICE_LIST_UNM_MSG;
4347 } else if (!for_mouse) {
4348 popup_status_msg = CHOICE_LIST_MESSAGE;
4349 #if defined(USE_MOUSE) && (defined(NCURSES) || defined(PDCURSES))
4350 } else {
4351 popup_status_msg =
4352 gettext("Left mouse button or return to select, arrow keys to scroll.");
4353 #endif
4354 }
4355 _statusline(popup_status_msg);
4356
4357 /*
4358 * Set up the window_offset for choices.
4359 * cur_choice ranges from 0...n
4360 * length ranges from 0...m
4361 */
4362 if (cur_choice >= length) {
4363 window_offset = cur_choice - length + 1;
4364 }
4365
4366 /*
4367 * Compute the number of popup window pages. - FM
4368 */
4369 npages = ((num_choices + 1) > length) ?
4370 (((num_choices + 1) + (length - 1)) / (length))
4371 : 1;
4372 /*
4373 * OH! I LOVE GOTOs! hack hack hack
4374 */
4375 redraw:
4376
4377 /*
4378 * Display the boxed choices.
4379 */
4380 for (i = 0; i <= num_choices; i++) {
4381 if (i >= window_offset && i - window_offset < length) {
4382 draw_option(form_window, ((i + 1) - window_offset), width, FALSE,
4383 max_choices, i, choices[i]);
4384 }
4385 }
4386 LYbox(form_window, !numbered);
4387 Cptr = NULL;
4388
4389 /*
4390 * Loop on user input.
4391 */
4392 while (cmd != LYK_ACTIVATE) {
4393 int row = ((i + 1) - window_offset);
4394
4395 /* Show scroll indicators. */
4396 if (can_scroll) {
4397 can_scroll = ((window_offset ? CAN_SCROLL_UP : 0)
4398 | (num_choices - window_offset >= length
4399 ? CAN_SCROLL_DOWN : 0));
4400 if (~can_scroll & can_scroll_was) { /* Need to redraw */
4401 LYbox(form_window, !numbered);
4402 can_scroll_was = 0;
4403 }
4404 if (can_scroll & ~can_scroll_was & CAN_SCROLL_UP) {
4405 wmove(form_window, 1, Lnum + width + 3);
4406 LynxWChangeStyle(form_window, s_menu_sb, STACK_ON);
4407 waddch(form_window, ACS_UARROW);
4408 LynxWChangeStyle(form_window, s_menu_sb, STACK_OFF);
4409 }
4410 if (can_scroll & ~can_scroll_was & CAN_SCROLL_DOWN) {
4411 wmove(form_window, length, Lnum + width + 3);
4412 LynxWChangeStyle(form_window, s_menu_sb, STACK_ON);
4413 waddch(form_window, ACS_DARROW);
4414 LynxWChangeStyle(form_window, s_menu_sb, STACK_OFF);
4415 }
4416 }
4417
4418 /*
4419 * Unreverse cur choice.
4420 */
4421 if (Cptr != NULL) {
4422 draw_option(form_window, row, width, FALSE,
4423 max_choices, i, Cptr[i]);
4424 }
4425 Cptr = choices;
4426 i = cur_choice;
4427 row = ((cur_choice + 1) - window_offset);
4428 draw_option(form_window, row, width, TRUE,
4429 max_choices, cur_choice, Cptr[cur_choice]);
4430 LYstowCursor(form_window, row, 1);
4431
4432 c = LYgetch_choice();
4433 if (term_options || LYCharIsINTERRUPT(c)) { /* Control-C or Control-G */
4434 cmd = LYK_QUIT;
4435 #ifndef USE_SLANG
4436 } else if (c == MOUSE_KEY) {
4437 if ((cmd = fancy_mouse(form_window, row, &cur_choice)) < 0)
4438 goto redraw;
4439 if (cmd == LYK_ACTIVATE)
4440 break;
4441 #endif
4442 } else {
4443 cmd = LKC_TO_LAC(keymap, c);
4444 }
4445 #ifdef VMS
4446 if (HadVMSInterrupt) {
4447 HadVMSInterrupt = FALSE;
4448 cmd = LYK_QUIT;
4449 }
4450 #endif /* VMS */
4451
4452 switch (cmd) {
4453 case LYK_F_LINK_NUM:
4454 c = '\0';
4455 /* FALLTHRU */
4456 case LYK_1: /* FALLTHRU */
4457 case LYK_2: /* FALLTHRU */
4458 case LYK_3: /* FALLTHRU */
4459 case LYK_4: /* FALLTHRU */
4460 case LYK_5: /* FALLTHRU */
4461 case LYK_6: /* FALLTHRU */
4462 case LYK_7: /* FALLTHRU */
4463 case LYK_8: /* FALLTHRU */
4464 case LYK_9:
4465 /*
4466 * Get a number from the user, possibly with a 'g' or 'p' suffix
4467 * (which will be loaded into c). - FM & LE
4468 */
4469 number = get_popup_number(SELECT_OPTION_NUMBER, &c, &rel);
4470
4471 /* handle + or - suffix */
4472 CTRACE((tfp, "got popup option number %d, ", number));
4473 CTRACE((tfp, "rel='%c', c='%c', cur_choice=%d\n",
4474 rel, c, cur_choice));
4475 if (c == 'p') {
4476 int curpage = ((cur_choice + 1) > length) ?
4477 (((cur_choice + 1) + (length - 1)) / (length))
4478 : 1;
4479
4480 CTRACE((tfp, " curpage=%d\n", curpage));
4481 if (rel == '+')
4482 number = curpage + number;
4483 else if (rel == '-')
4484 number = curpage - number;
4485 } else if (rel == '+') {
4486 number = cur_choice + number + 1;
4487 } else if (rel == '-') {
4488 number = cur_choice - number + 1;
4489 }
4490 if (rel)
4491 CTRACE((tfp, "new number=%d\n", number));
4492 /*
4493 * Check for a 'p' suffix. - FM
4494 */
4495 if (c == 'p') {
4496 /*
4497 * Treat 1 or less as the first page. - FM
4498 */
4499 if (number <= 1) {
4500 if (window_offset == 0) {
4501 HTUserMsg(ALREADY_AT_OPTION_BEGIN);
4502 _statusline(popup_status_msg);
4503 break;
4504 }
4505 window_offset = 0;
4506 cur_choice = 0;
4507 _statusline(popup_status_msg);
4508 goto redraw;
4509 }
4510
4511 /*
4512 * Treat a number equal to or greater than the number of pages
4513 * as the last page. - FM
4514 */
4515 if (number >= npages) {
4516 if (window_offset >= ((num_choices - length) + 1)) {
4517 HTUserMsg(ALREADY_AT_OPTION_END);
4518 _statusline(popup_status_msg);
4519 break;
4520 }
4521 window_offset = ((npages - 1) * length);
4522 if (window_offset > (num_choices - length)) {
4523 window_offset = (num_choices - length + 1);
4524 }
4525 if (cur_choice < window_offset)
4526 cur_choice = window_offset;
4527 _statusline(popup_status_msg);
4528 goto redraw;
4529 }
4530
4531 /*
4532 * We want an intermediate page. - FM
4533 */
4534 if (((number - 1) * length) == window_offset) {
4535 char *msg = 0;
4536
4537 HTSprintf0(&msg, ALREADY_AT_OPTION_PAGE, number);
4538 HTUserMsg(msg);
4539 FREE(msg);
4540 _statusline(popup_status_msg);
4541 break;
4542 }
4543 cur_choice = window_offset = ((number - 1) * length);
4544 _statusline(popup_status_msg);
4545 goto redraw;
4546
4547 }
4548
4549 /*
4550 * Check for a positive number, which signifies that a choice
4551 * should be sought. - FM
4552 */
4553 if (number > 0) {
4554 /*
4555 * Decrement the number so as to correspond with our cur_choice
4556 * values. - FM
4557 */
4558 number--;
4559
4560 /*
4561 * If the number is in range and had no legal suffix, select
4562 * the indicated choice. - FM
4563 */
4564 if (number <= num_choices && c == '\0') {
4565 cur_choice = number;
4566 cmd = LYK_ACTIVATE;
4567 break;
4568 }
4569
4570 /*
4571 * Verify that we had a 'g' suffix, and act on the number. -
4572 * FM
4573 */
4574 if (c == 'g') {
4575 if (cur_choice == number) {
4576 /*
4577 * The choice already is current. - FM
4578 */
4579 char *msg = 0;
4580
4581 HTSprintf0(&msg, OPTION_ALREADY_CURRENT, (number + 1));
4582 HTUserMsg(msg);
4583 FREE(msg);
4584 _statusline(popup_status_msg);
4585 break;
4586 }
4587
4588 if (number <= num_choices) {
4589 /*
4590 * The number is in range and had a 'g' suffix, so make
4591 * it the current option, scrolling if needed. - FM
4592 */
4593 j = (number - cur_choice);
4594 cur_choice = number;
4595 if ((j > 0) &&
4596 (cur_choice - window_offset) >= length) {
4597 window_offset += j;
4598 if (window_offset > (num_choices - length + 1))
4599 window_offset = (num_choices - length + 1);
4600 } else if ((cur_choice - window_offset) < 0) {
4601 window_offset -= abs(j);
4602 if (window_offset < 0)
4603 window_offset = 0;
4604 }
4605 _statusline(popup_status_msg);
4606 goto redraw;
4607 }
4608
4609 /*
4610 * Not in range. - FM
4611 */
4612 HTUserMsg(BAD_OPTION_NUM_ENTERED);
4613 }
4614 }
4615
4616 /*
4617 * Restore the popup statusline. - FM
4618 */
4619 _statusline(popup_status_msg);
4620 break;
4621
4622 case LYK_PREV_LINK:
4623 case LYK_LPOS_PREV_LINK:
4624 case LYK_FASTBACKW_LINK:
4625 case LYK_UP_LINK:
4626
4627 if (cur_choice > 0)
4628 cur_choice--;
4629
4630 /*
4631 * Scroll the window up if necessary.
4632 */
4633 if ((cur_choice - window_offset) < 0) {
4634 window_offset--;
4635 goto redraw;
4636 }
4637 break;
4638
4639 case LYK_NEXT_LINK:
4640 case LYK_LPOS_NEXT_LINK:
4641 case LYK_FASTFORW_LINK:
4642 case LYK_DOWN_LINK:
4643 if (cur_choice < num_choices)
4644 cur_choice++;
4645
4646 /*
4647 * Scroll the window down if necessary
4648 */
4649 if ((cur_choice - window_offset) >= length) {
4650 window_offset++;
4651 goto redraw;
4652 }
4653 break;
4654
4655 case LYK_NEXT_PAGE:
4656 /*
4657 * Okay, are we on the last page of the list? If not then,
4658 */
4659 if (window_offset != (num_choices - length + 1)) {
4660 /*
4661 * Modify the current choice to not be a coordinate in the
4662 * list, but a coordinate on the item selected in the window.
4663 */
4664 cur_choice -= window_offset;
4665
4666 /*
4667 * Page down the proper length for the list. If simply to far,
4668 * back up.
4669 */
4670 window_offset += length;
4671 if (window_offset > (num_choices - length)) {
4672 window_offset = (num_choices - length + 1);
4673 }
4674
4675 /*
4676 * Readjust the current selection to be a list coordinate
4677 * rather than window. Redraw this thing.
4678 */
4679 cur_choice += window_offset;
4680 goto redraw;
4681 } else if (cur_choice < num_choices) {
4682 /*
4683 * Already on last page of the list so just redraw it with the
4684 * last item selected.
4685 */
4686 cur_choice = num_choices;
4687 }
4688 break;
4689
4690 case LYK_PREV_PAGE:
4691 /*
4692 * Are we on the first page of the list? If not then,
4693 */
4694 if (window_offset != 0) {
4695 /*
4696 * Modify the current selection to not be a list coordinate,
4697 * but a window coordinate.
4698 */
4699 cur_choice -= window_offset;
4700
4701 /*
4702 * Page up the proper length. If too far, back up.
4703 */
4704 window_offset -= length;
4705 if (window_offset < 0) {
4706 window_offset = 0;
4707 }
4708
4709 /*
4710 * Readjust the current choice.
4711 */
4712 cur_choice += window_offset;
4713 goto redraw;
4714 } else if (cur_choice > 0) {
4715 /*
4716 * Already on the first page so just back up to the first item.
4717 */
4718 cur_choice = 0;
4719 }
4720 break;
4721
4722 case LYK_HOME:
4723 cur_choice = 0;
4724 if (window_offset > 0) {
4725 window_offset = 0;
4726 goto redraw;
4727 }
4728 break;
4729
4730 case LYK_END:
4731 cur_choice = num_choices;
4732 if (window_offset != (num_choices - length + 1)) {
4733 window_offset = (num_choices - length + 1);
4734 goto redraw;
4735 }
4736 break;
4737
4738 case LYK_DOWN_TWO:
4739 cur_choice += 2;
4740 if (cur_choice > num_choices)
4741 cur_choice = num_choices;
4742
4743 /*
4744 * Scroll the window down if necessary.
4745 */
4746 if ((cur_choice - window_offset) >= length) {
4747 window_offset += 2;
4748 if (window_offset > (num_choices - length + 1))
4749 window_offset = (num_choices - length + 1);
4750 goto redraw;
4751 }
4752 break;
4753
4754 case LYK_UP_TWO:
4755 cur_choice -= 2;
4756 if (cur_choice < 0)
4757 cur_choice = 0;
4758
4759 /*
4760 * Scroll the window up if necessary.
4761 */
4762 if ((cur_choice - window_offset) < 0) {
4763 window_offset -= 2;
4764 if (window_offset < 0)
4765 window_offset = 0;
4766 goto redraw;
4767 }
4768 break;
4769
4770 case LYK_DOWN_HALF:
4771 cur_choice += (length / 2);
4772 if (cur_choice > num_choices)
4773 cur_choice = num_choices;
4774
4775 /*
4776 * Scroll the window down if necessary.
4777 */
4778 if ((cur_choice - window_offset) >= length) {
4779 window_offset += (length / 2);
4780 if (window_offset > (num_choices - length + 1))
4781 window_offset = (num_choices - length + 1);
4782 goto redraw;
4783 }
4784 break;
4785
4786 case LYK_UP_HALF:
4787 cur_choice -= (length / 2);
4788 if (cur_choice < 0)
4789 cur_choice = 0;
4790
4791 /*
4792 * Scroll the window up if necessary.
4793 */
4794 if ((cur_choice - window_offset) < 0) {
4795 window_offset -= (length / 2);
4796 if (window_offset < 0)
4797 window_offset = 0;
4798 goto redraw;
4799 }
4800 break;
4801
4802 case LYK_REFRESH:
4803 lynx_force_repaint();
4804 LYrefresh();
4805 break;
4806
4807 case LYK_NEXT:
4808 if (recall && isBEmpty(next_target)) {
4809 /*
4810 * We got a 'n'ext command with no prior query specified within
4811 * the popup window. See if one was entered when the popup was
4812 * retracted, and if so, assume that's what's wanted. Note
4813 * that it will become the default within popups, unless
4814 * another is entered within a popup. If the within popup
4815 * default is to be changed at that point, use WHEREIS ('/')
4816 * and enter it, or the up- or down-arrow keys to seek any of
4817 * the previously entered queries, regardless of whether they
4818 * were entered within or outside of a popup window. - FM
4819 */
4820 if ((cp = (char *) HTList_objectAt(search_queries,
4821 0)) != NULL) {
4822 BStrCopy0(next_target, cp);
4823 QueryNum = 0;
4824 FirstRecall = FALSE;
4825 }
4826 }
4827 BStrCopy(prev_target, next_target);
4828 /* FALLTHRU */
4829 case LYK_WHEREIS:
4830 if (isBEmpty(prev_target)) {
4831 _statusline(ENTER_WHEREIS_QUERY);
4832 if ((ch = LYgetBString(&prev_target, VISIBLE, 0, recall)) < 0) {
4833 /*
4834 * User cancelled the search via ^G. - FM
4835 */
4836 HTInfoMsg(CANCELLED);
4837 goto restore_popup_statusline;
4838 }
4839 }
4840
4841 check_recall:
4842 if (isBEmpty(prev_target) &&
4843 !(recall && (ch == UPARROW || ch == DNARROW))) {
4844 /*
4845 * No entry. Simply break. - FM
4846 */
4847 HTInfoMsg(CANCELLED);
4848 goto restore_popup_statusline;
4849 }
4850
4851 if (recall && ch == UPARROW) {
4852 if (FirstRecall) {
4853 /*
4854 * Use the current string or last query in the list. - FM
4855 */
4856 FirstRecall = FALSE;
4857 if (!isBEmpty(next_target)) {
4858 for (QueryNum = (QueryTotal - 1);
4859 QueryNum > 0; QueryNum--) {
4860 if ((cp = (char *) HTList_objectAt(search_queries,
4861 QueryNum))
4862 != NULL &&
4863 !strcmp(next_target->str, cp)) {
4864 break;
4865 }
4866 }
4867 } else {
4868 QueryNum = 0;
4869 }
4870 } else {
4871 /*
4872 * Go back to the previous query in the list. - FM
4873 */
4874 QueryNum++;
4875 }
4876 if (QueryNum >= QueryTotal) {
4877 /*
4878 * Roll around to the last query in the list. - FM
4879 */
4880 QueryNum = 0;
4881 }
4882 if ((cp = (char *) HTList_objectAt(search_queries,
4883 QueryNum)) != NULL) {
4884 BStrCopy0(prev_target, cp);
4885 if (!isBEmpty(next_target) &&
4886 !strcmp(next_target->str, prev_target->str)) {
4887 _statusline(EDIT_CURRENT_QUERY);
4888 } else if ((!isBEmpty(next_target) && QueryTotal == 2) ||
4889 (isBEmpty(next_target) && QueryTotal == 1)) {
4890 _statusline(EDIT_THE_PREV_QUERY);
4891 } else {
4892 _statusline(EDIT_A_PREV_QUERY);
4893 }
4894 if ((ch = LYgetBString(&prev_target,
4895 VISIBLE, 0, recall)) < 0) {
4896 /*
4897 * User cancelled the search via ^G. - FM
4898 */
4899 HTInfoMsg(CANCELLED);
4900 goto restore_popup_statusline;
4901 }
4902 goto check_recall;
4903 }
4904 } else if (recall && ch == DNARROW) {
4905 if (FirstRecall) {
4906 /*
4907 * Use the current string or first query in the list. - FM
4908 */
4909 FirstRecall = FALSE;
4910 if (!isBEmpty(next_target)) {
4911 for (QueryNum = 0;
4912 QueryNum < (QueryTotal - 1); QueryNum++) {
4913 if ((cp = (char *) HTList_objectAt(search_queries,
4914 QueryNum))
4915 != NULL &&
4916 !strcmp(next_target->str, cp)) {
4917 break;
4918 }
4919 }
4920 } else {
4921 QueryNum = (QueryTotal - 1);
4922 }
4923 } else {
4924 /*
4925 * Advance to the next query in the list. - FM
4926 */
4927 QueryNum--;
4928 }
4929 if (QueryNum < 0) {
4930 /*
4931 * Roll around to the first query in the list. - FM
4932 */
4933 QueryNum = (QueryTotal - 1);
4934 }
4935 if ((cp = (char *) HTList_objectAt(search_queries,
4936 QueryNum)) != NULL) {
4937 BStrCopy0(prev_target, cp);
4938 if (isBEmpty(next_target) &&
4939 !strcmp(next_target->str, prev_target->str)) {
4940 _statusline(EDIT_CURRENT_QUERY);
4941 } else if ((!isBEmpty(next_target) && QueryTotal == 2) ||
4942 (isBEmpty(next_target) && QueryTotal == 1)) {
4943 _statusline(EDIT_THE_PREV_QUERY);
4944 } else {
4945 _statusline(EDIT_A_PREV_QUERY);
4946 }
4947 if ((ch = LYgetBString(&prev_target,
4948 VISIBLE, 0, recall)) < 0) {
4949 /*
4950 * User cancelled the search via ^G. - FM
4951 */
4952 HTInfoMsg(CANCELLED);
4953 goto restore_popup_statusline;
4954 }
4955 goto check_recall;
4956 }
4957 }
4958 /*
4959 * Replace the search string buffer with the new target. - FM
4960 */
4961 BStrCopy(next_target, prev_target);
4962 HTAddSearchQuery(next_target->str);
4963
4964 /*
4965 * Start search at the next choice. - FM
4966 */
4967 for (j = 1; Cptr[i + j] != NULL; j++) {
4968 FormatChoiceNum(buffer, max_choices, (i + j), Cptr[i + j]);
4969 if (LYcase_sensitive) {
4970 if (strstr(buffer, next_target->str) != NULL)
4971 break;
4972 } else {
4973 if (LYstrstr(buffer, next_target->str) != NULL)
4974 break;
4975 }
4976 }
4977 if (Cptr[i + j] != NULL) {
4978 /*
4979 * We have a hit, so make that choice the current. - FM
4980 */
4981 cur_choice += j;
4982 /*
4983 * Scroll the window down if necessary.
4984 */
4985 if ((cur_choice - window_offset) >= length) {
4986 window_offset += j;
4987 if (window_offset > (num_choices - length + 1))
4988 window_offset = (num_choices - length + 1);
4989 ReDraw = TRUE;
4990 }
4991 goto restore_popup_statusline;
4992 }
4993
4994 /*
4995 * If we started at the beginning, it can't be present. - FM
4996 */
4997 if (cur_choice == 0) {
4998 HTUserMsg2(STRING_NOT_FOUND, next_target->str);
4999 goto restore_popup_statusline;
5000 }
5001
5002 /*
5003 * Search from the beginning to the current choice. - FM
5004 */
5005 for (j = 0; j < cur_choice; j++) {
5006 FormatChoiceNum(buffer, max_choices, (j + 1), Cptr[j]);
5007 if (LYcase_sensitive) {
5008 if (strstr(buffer, next_target->str) != NULL)
5009 break;
5010 } else {
5011 if (LYstrstr(buffer, next_target->str) != NULL)
5012 break;
5013 }
5014 }
5015 if (j < cur_choice) {
5016 /*
5017 * We have a hit, so make that choice the current. - FM
5018 */
5019 j = (cur_choice - j);
5020 cur_choice -= j;
5021 /*
5022 * Scroll the window up if necessary.
5023 */
5024 if ((cur_choice - window_offset) < 0) {
5025 window_offset -= j;
5026 if (window_offset < 0)
5027 window_offset = 0;
5028 ReDraw = TRUE;
5029 }
5030 goto restore_popup_statusline;
5031 }
5032
5033 /*
5034 * Didn't find it in the preceding choices either. - FM
5035 */
5036 HTUserMsg2(STRING_NOT_FOUND, next_target->str);
5037
5038 restore_popup_statusline:
5039 /*
5040 * Restore the popup statusline and reset the search variables. -
5041 * FM
5042 */
5043 _statusline(popup_status_msg);
5044 BStrCopy0(prev_target, "");
5045 QueryTotal = (search_queries ? HTList_count(search_queries)
5046 : 0);
5047 recall = ((QueryTotal >= 1) ? RECALL_URL : NORECALL);
5048 QueryNum = QueryTotal;
5049 if (ReDraw == TRUE) {
5050 ReDraw = FALSE;
5051 goto redraw;
5052 }
5053 break;
5054
5055 case LYK_QUIT:
5056 case LYK_ABORT:
5057 case LYK_PREV_DOC:
5058 case LYK_INTERRUPT:
5059 cur_choice = orig_choice;
5060 cmd = LYK_ACTIVATE; /* to exit */
5061 break;
5062 }
5063 }
5064 LYstopPopup();
5065
5066 return (disabled ? orig_choice : cur_choice);
5067 }
5068
5069 /*
5070 * Allow the user to edit a string.
5071 */
LYgetBString(bstring ** inputline,int hidden,size_t max_cols,RecallType recall)5072 int LYgetBString(bstring **inputline,
5073 int hidden,
5074 size_t max_cols,
5075 RecallType recall)
5076 {
5077 int x, y;
5078 int ch;
5079 int xlec = -2;
5080 int last_xlec = -1;
5081 int last_xlkc = -1;
5082 EditFieldData MyEdit, *edit = &MyEdit;
5083
5084 #ifdef SUPPORT_MULTIBYTE_EDIT
5085 BOOL refresh_mb = TRUE;
5086 #endif /* SUPPORT_MULTIBYTE_EDIT */
5087
5088 CTRACE((tfp, "called LYgetBString hidden %d, recall %d\n", hidden, (int) recall));
5089
5090 LYGetYX(y, x); /* Use screen from cursor position to eol */
5091
5092 (void) y;
5093 (void) x;
5094
5095 if (*inputline == NULL) /* caller may not have initialized this */
5096 BStrCopy0(*inputline, "");
5097
5098 LYSetupEdit(edit, (*inputline)->str, max_cols, LYcolLimit - x);
5099 IsHidden = (BOOL) hidden;
5100 #ifdef FEPCTRL
5101 fep_on();
5102 #endif
5103
5104 for (;;) {
5105 again:
5106 #ifndef SUPPORT_MULTIBYTE_EDIT
5107 LYRefreshEdit(edit);
5108 #else /* SUPPORT_MULTIBYTE_EDIT */
5109 if (refresh_mb)
5110 LYRefreshEdit(edit);
5111 #endif /* SUPPORT_MULTIBYTE_EDIT */
5112 ch = LYReadCmdKey(FOR_PROMPT);
5113 #ifdef SUPPORT_MULTIBYTE_EDIT
5114 #ifdef CJK_EX /* for SJIS code */
5115 if (!refresh_mb
5116 && (EditBinding(ch) != LYE_CHAR))
5117 goto again;
5118 #else
5119 if (!refresh_mb
5120 && (EditBinding(ch) != LYE_CHAR)
5121 && (EditBinding(ch) != LYE_AIX))
5122 goto again;
5123 #endif
5124 #endif /* SUPPORT_MULTIBYTE_EDIT */
5125
5126 if (term_letter || term_options
5127 #ifdef VMS
5128 || HadVMSInterrupt
5129 #endif /* VMS */
5130 #ifndef DISABLE_NEWS
5131 || term_message
5132 #endif
5133 ) {
5134 #ifdef VMS
5135 HadVMSInterrupt = FALSE;
5136 #endif /* VMS */
5137 ch = LYCharINTERRUPT2;
5138 }
5139
5140 if (recall != NORECALL && (ch == UPARROW || ch == DNARROW)) {
5141 BStrCopy0(*inputline, Buf);
5142 LYAddToCloset(recall, Buf);
5143 CTRACE((tfp, "LYgetstr(%s) recall\n", (*inputline)->str));
5144 #ifdef FEPCTRL
5145 fep_off();
5146 #endif
5147 LYFinishEdit(edit);
5148 return (ch);
5149 }
5150 ch |= CurModif;
5151 CurModif = 0;
5152 if (last_xlkc != -1) {
5153 if (ch == last_xlkc)
5154 ch |= LKC_MOD3;
5155 last_xlkc = -1; /* consumed */
5156 }
5157 #ifndef WIN_EX
5158 if (LKC_TO_LAC(keymap, ch) == LYK_REFRESH)
5159 goto again;
5160 #endif
5161 last_xlec = xlec;
5162 xlec = EditBinding(ch);
5163 if ((xlec & LYE_DF) && !(xlec & LYE_FORM_LAC)) {
5164 last_xlkc = ch;
5165 xlec &= ~LYE_DF;
5166 } else {
5167 last_xlkc = -1;
5168 }
5169 switch (xlec) {
5170 case LYE_SETM1:
5171 /*
5172 * Set flag for modifier 1.
5173 */
5174 CurModif |= LKC_MOD1;
5175 break;
5176 case LYE_SETM2:
5177 /*
5178 * Set flag for modifier 2.
5179 */
5180 CurModif |= LKC_MOD2;
5181 break;
5182 case LYE_TAB:
5183 if (xlec == last_xlec && recall != NORECALL) {
5184 HTList *list = whichRecall(recall);
5185
5186 if (!HTList_isEmpty(list)) {
5187 char **data = sortedList(list, (BOOL) (recall == RECALL_CMD));
5188 int old_y, old_x;
5189 int cur_choice = 0;
5190 int num_options = LYarrayLength((STRING2PTR) data);
5191
5192 while (cur_choice < num_options
5193 && strcasecomp(data[cur_choice], Buf) < 0)
5194 cur_choice++;
5195
5196 LYGetYX(old_y, old_x);
5197 cur_choice = LYhandlePopupList(cur_choice,
5198 0,
5199 old_x,
5200 (STRING2PTR) data,
5201 -1,
5202 -1,
5203 FALSE,
5204 FALSE);
5205 if (cur_choice >= 0) {
5206 if (recall == RECALL_CMD)
5207 _statusline(": ");
5208 reinsertEdit(edit, data[cur_choice]);
5209 }
5210 LYmove(old_y, old_x);
5211 FREE(data);
5212 }
5213 } else {
5214 reinsertEdit(edit, LYFindInCloset(recall, Buf));
5215 }
5216 break;
5217
5218 #ifndef CJK_EX /* 1997/11/03 (Mon) 20:13:45 */
5219 case LYE_AIX:
5220 /*
5221 * Hex 97.
5222 * Treat as a character for CJK, or if this is a valid character in
5223 * the current display character set. Otherwise, we treat this as
5224 * LYE_ENTER.
5225 */
5226 if (ch != '\t' &&
5227 (IS_CJK_TTY ||
5228 LYlowest_eightbit[current_char_set] <= 0x97)) {
5229 LYLineEdit(edit, ch, FALSE);
5230 break;
5231 }
5232 /* FALLTHRU */
5233 #endif
5234 case LYE_ENTER:
5235 /*
5236 * Terminate the string and return.
5237 */
5238 BStrCopy0(*inputline, Buf);
5239 if (!hidden)
5240 LYAddToCloset(recall, Buf);
5241 CTRACE((tfp, "LYgetstr(%s) LYE_ENTER\n", (*inputline)->str));
5242 #ifdef FEPCTRL
5243 fep_off();
5244 #endif
5245 LYFinishEdit(edit);
5246 return (ch);
5247
5248 #ifdef CAN_CUT_AND_PASTE
5249 /* 1998/10/01 (Thu) 15:05:49 */
5250
5251 case LYE_PASTE:
5252 {
5253 unsigned char *s = (unsigned char *) get_clip_grab(), *e;
5254 int len;
5255
5256 if (!s)
5257 break;
5258 len = (int) strlen((const char *) s);
5259 e = s + len;
5260
5261 if (len > 0) {
5262 unsigned char *e1 = s;
5263
5264 while (e1 < e) {
5265 if (*e1 < ' ') { /* Stop here? */
5266 if (e1 > s)
5267 LYEditInsert(edit, s, (int) (e1 - s),
5268 map_active, TRUE);
5269 s = e1;
5270 if (*e1 == '\t') { /* Replace by space */
5271 LYEditInsert(edit,
5272 (unsigned const char *) " ",
5273 1,
5274 map_active,
5275 TRUE);
5276 s = ++e1;
5277 } else
5278 break;
5279 } else
5280 ++e1;
5281 }
5282 if (e1 > s)
5283 LYEditInsert(edit, s, (int) (e1 - s), map_active, TRUE);
5284 }
5285 get_clip_release();
5286 break;
5287 }
5288 #endif
5289
5290 case LYE_ABORT:
5291 /*
5292 * Control-C or Control-G aborts.
5293 */
5294 CTRACE((tfp, "LYgetstr LYE_ABORT\n"));
5295 #ifdef FEPCTRL
5296 fep_off();
5297 #endif
5298 LYFinishEdit(edit);
5299 BStrCopy0(*inputline, "");
5300 return (-1);
5301
5302 case LYE_STOP:
5303 /*
5304 * Deactivate.
5305 */
5306 CTRACE((tfp, "LYgetstr LYE_STOP\n"));
5307 #ifdef TEXTFIELDS_MAY_NEED_ACTIVATION
5308 textfields_need_activation = TRUE;
5309 LYFinishEdit(edit);
5310 BStrCopy0(*inputline, "");
5311 return (-1);
5312 #else
5313 #ifdef ENHANCED_LINEEDIT
5314 if (Mark >= 0)
5315 Mark = -(1 + Mark); /* Disable it */
5316 #endif
5317 break;
5318 #endif
5319
5320 case LYE_LKCMD:
5321 /*
5322 * Used only in form_getstr() for invoking the LYK_F_LINK_NUM
5323 * prompt when in form text fields. - FM
5324 */
5325 break;
5326
5327 case LYE_FORM_PASS:
5328 /*
5329 * Used in form_getstr() to end line editing and pass on the input
5330 * char/lynxkeycode. Here it is just ignored. - kw
5331 */
5332 break;
5333
5334 default:
5335 if (xlec & LYE_FORM_LAC) {
5336 /*
5337 * Used in form_getstr() to end line editing and pass on the
5338 * lynxkeycode already containing a lynxactioncode. Here it is
5339 * just ignored. - kw
5340 */
5341 break;
5342 }
5343 #ifndef SUPPORT_MULTIBYTE_EDIT
5344 LYLineEdit(edit, ch, FALSE);
5345 #else /* SUPPORT_MULTIBYTE_EDIT */
5346 if (LYLineEdit(edit, ch, FALSE) == 0) {
5347 if (refresh_mb && IS_CJK_TTY && (0x81 <= ch) && (ch <= 0xfe))
5348 refresh_mb = FALSE;
5349 else
5350 refresh_mb = TRUE;
5351 } else {
5352 if (!refresh_mb) {
5353 LYEdit1(edit, 0, LYE_DELP, FALSE);
5354 }
5355 }
5356 #endif /* SUPPORT_MULTIBYTE_EDIT */
5357 }
5358 }
5359 }
5360
5361 /*
5362 * Use this for fixed-buffer edits which have not been converted to use
5363 * LYgetBString().
5364 */
LYgetstr(char * inputline,int hidden,size_t bufsize,RecallType recall)5365 int LYgetstr(char *inputline, /* fixed-size buffer for input/output */
5366 int hidden, /* true to suppress from command-history */
5367 size_t bufsize, /* sizeof(inputline) */
5368 RecallType recall) /* type of command-history */
5369 {
5370 int ch;
5371 bstring *my_bstring = NULL;
5372
5373 BStrCopy0(my_bstring, inputline);
5374 if (my_bstring != 0) {
5375 ch = LYgetBString(&my_bstring, hidden, bufsize, recall);
5376 if (ch >= 0 && my_bstring != 0)
5377 LYStrNCpy(inputline, my_bstring->str, bufsize);
5378 BStrFree(my_bstring);
5379 } else {
5380 ch = -1;
5381 }
5382 return ch;
5383 }
5384
LYLineeditHelpURL(void)5385 const char *LYLineeditHelpURL(void)
5386 {
5387 static int lasthelp_lineedit = -1;
5388 static char helpbuf[LY_MAXPATH] = "\0";
5389 static char *phelp = &helpbuf[0];
5390
5391 if (lasthelp_lineedit == current_lineedit)
5392 return &helpbuf[0];
5393 if (lasthelp_lineedit == -1) {
5394 LYStrNCpy(helpbuf, helpfilepath, sizeof(helpbuf) - 1);
5395 phelp += strlen(helpbuf);
5396 }
5397 if (LYLineeditHelpURLs[current_lineedit] &&
5398 strlen(LYLineeditHelpURLs[current_lineedit]) &&
5399 (strlen(LYLineeditHelpURLs[current_lineedit]) <=
5400 sizeof(helpbuf) - (unsigned) (phelp - helpbuf))) {
5401 LYStrNCpy(phelp, LYLineeditHelpURLs[current_lineedit],
5402 (int) (sizeof(helpbuf) - (unsigned) (phelp - helpbuf) - 1));
5403 lasthelp_lineedit = current_lineedit;
5404 return (&helpbuf[0]);
5405 }
5406 return NULL;
5407 }
5408
5409 /*
5410 * Wrapper for sscanf to ensure that lynx can "always" read a POSIX float.
5411 * In some locales, the decimal point changes.
5412 */
LYscanFloat2(const char ** source,float * result)5413 int LYscanFloat2(const char **source, float *result)
5414 {
5415 int count = 0;
5416 char *temp;
5417 const char *src = *source;
5418
5419 src = LYSkipCBlanks(src);
5420 *result = 0.0;
5421 if (strchr(src, '.') != 0) {
5422 long frc_part = 0;
5423 float scale = 1.0;
5424
5425 if (*src != '.') {
5426 temp = NULL;
5427 frc_part = strtol(src, &temp, 10);
5428 *result = (float) frc_part;
5429 src = temp;
5430 }
5431 if (src != 0 && *src == '.') {
5432 ++src;
5433 if (isdigit(UCH(*src))) {
5434 temp = NULL;
5435 frc_part = strtol(src, &temp, 10);
5436 if (temp != 0) {
5437 int digits = (int) (temp - src);
5438
5439 while (digits-- > 0)
5440 scale *= (float) 10.0;
5441 *result += ((float) frc_part / scale);
5442 }
5443 src = temp;
5444 }
5445 }
5446 if (src != 0 && *src != '\0' && strchr(" \t+", *src) == 0) {
5447 char *extra = (char *) malloc(2 + strlen(src));
5448
5449 if (extra != 0) {
5450 extra[0] = '1';
5451 strcpy(extra + 1, src);
5452 if (sscanf(extra, "%f", &scale) == 1) {
5453 *result *= scale;
5454 }
5455 FREE(extra);
5456 src = LYSkipCNonBlanks(src);
5457 } else {
5458 src = 0;
5459 }
5460 }
5461 if (src != 0)
5462 count = 1;
5463 } else {
5464 count = sscanf(src, "%f", result);
5465 src = LYSkipCNonBlanks(src);
5466 }
5467 CTRACE2(TRACE_CFG,
5468 (tfp, "LYscanFloat \"%s\" -> %f (%s)\n",
5469 *source, *result,
5470 count ? "ok" : "error"));
5471 *source = src;
5472 return count;
5473 }
5474
LYscanFloat(const char * source,float * result)5475 int LYscanFloat(const char *source, float *result)
5476 {
5477 const char *temp = source;
5478
5479 return LYscanFloat2(&temp, result);
5480 }
5481
5482 /*
5483 * A replacement for 'strsep()'
5484 */
LYstrsep(char ** stringp,const char * delim)5485 char *LYstrsep(char **stringp,
5486 const char *delim)
5487 {
5488 char *tmp, *out;
5489
5490 if (isEmpty(stringp)) /* nothing to do? */
5491 return 0; /* then don't fall on our faces */
5492
5493 out = *stringp; /* save the start of the string */
5494 tmp = strpbrk(*stringp, delim);
5495 if (tmp) {
5496 *tmp = '\0'; /* terminate the substring with \0 */
5497 *stringp = ++tmp; /* point at the next substring */
5498 } else
5499 *stringp = 0; /* this was the last substring: */
5500 /* let caller see he's done */
5501 return out;
5502 }
5503
5504 /*
5505 * LYstrstr will find the first occurrence of the string pointed to by tarptr
5506 * in the string pointed to by chptr. It returns NULL if string not found. It
5507 * is a case insensitive search.
5508 */
LYstrstr(char * chptr,const char * tarptr)5509 char *LYstrstr(char *chptr,
5510 const char *tarptr)
5511 {
5512 int len = (int) strlen(tarptr);
5513
5514 for (; *chptr != '\0'; chptr++) {
5515 if (0 == UPPER8(*chptr, *tarptr)) {
5516 if (0 == strncasecomp8(chptr + 1, tarptr + 1, len - 1))
5517 return (chptr);
5518 }
5519 } /* end for */
5520
5521 return (NULL); /* string not found or initial chptr was empty */
5522 }
5523
5524 /*
5525 * LYno_attr_char_case_strstr will find the first occurrence of the
5526 * string pointed to by tarptr in the string pointed to by chptr.
5527 * It ignores the characters: LY_UNDERLINE_START_CHAR and
5528 * LY_UNDERLINE_END_CHAR
5529 * LY_BOLD_START_CHAR
5530 * LY_BOLD_END_CHAR
5531 * LY_SOFT_HYPHEN
5532 * if present in chptr.
5533 * It is a case insensitive search.
5534 */
LYno_attr_char_case_strstr(const char * chptr,const char * tarptr)5535 const char *LYno_attr_char_case_strstr(const char *chptr,
5536 const char *tarptr)
5537 {
5538 register const char *tmpchptr, *tmptarptr;
5539
5540 if (!chptr)
5541 return (NULL);
5542
5543 while (IsSpecialAttrChar(*chptr) && *chptr != '\0')
5544 chptr++;
5545
5546 for (; *chptr != '\0'; chptr++) {
5547 if (0 == UPPER8(*chptr, *tarptr)) {
5548 /*
5549 * See if they line up.
5550 */
5551 tmpchptr = chptr + 1;
5552 tmptarptr = tarptr + 1;
5553
5554 if (*tmptarptr == '\0') /* one char target */
5555 return (chptr);
5556
5557 while (1) {
5558 if (!IsSpecialAttrChar(*tmpchptr)) {
5559 if (0 != UPPER8(*tmpchptr, *tmptarptr))
5560 break;
5561 tmpchptr++;
5562 tmptarptr++;
5563 } else {
5564 tmpchptr++;
5565 }
5566 if (*tmptarptr == '\0')
5567 return (chptr);
5568 if (*tmpchptr == '\0')
5569 break;
5570 }
5571 }
5572 } /* end for */
5573
5574 return (NULL);
5575 }
5576
5577 /*
5578 * LYno_attr_char_strstr will find the first occurrence of the
5579 * string pointed to by tarptr in the string pointed to by chptr.
5580 * It ignores the characters: LY_UNDERLINE_START_CHAR and
5581 * LY_UNDERLINE_END_CHAR
5582 * LY_BOLD_START_CHAR
5583 * LY_BOLD_END_CHAR
5584 * LY_SOFT_HYPHEN
5585 * if present in chptr.
5586 * It is a case sensitive search.
5587 */
LYno_attr_char_strstr(const char * chptr,const char * tarptr)5588 const char *LYno_attr_char_strstr(const char *chptr,
5589 const char *tarptr)
5590 {
5591 register const char *tmpchptr, *tmptarptr;
5592
5593 if (!chptr)
5594 return (NULL);
5595
5596 while (IsSpecialAttrChar(*chptr) && *chptr != '\0')
5597 chptr++;
5598
5599 for (; *chptr != '\0'; chptr++) {
5600 if ((*chptr) == (*tarptr)) {
5601 /*
5602 * See if they line up.
5603 */
5604 tmpchptr = chptr + 1;
5605 tmptarptr = tarptr + 1;
5606
5607 if (*tmptarptr == '\0') /* one char target */
5608 return (chptr);
5609
5610 while (1) {
5611 if (!IsSpecialAttrChar(*tmpchptr)) {
5612 if ((*tmpchptr) != (*tmptarptr))
5613 break;
5614 tmpchptr++;
5615 tmptarptr++;
5616 } else {
5617 tmpchptr++;
5618 }
5619 if (*tmptarptr == '\0')
5620 return (chptr);
5621 if (*tmpchptr == '\0')
5622 break;
5623 }
5624 }
5625 } /* end for */
5626
5627 return (NULL);
5628 }
5629
5630 /*
5631 * LYno_attr_mbcs_case_strstr will find the first occurrence of the string
5632 * pointed to by tarptr in the string pointed to by chptr. It takes account of
5633 * MultiByte Character Sequences (UTF8). The physical lengths of the displayed
5634 * string up to the start and end (= next position after) of the target string
5635 * are returned in *nstartp and *nendp if the search is successful.
5636 *
5637 * These lengths count glyph cells if count_gcells is set. (Full-width
5638 * characters in CJK mode count as two.) Normally that's what we want. They
5639 * count actual glyphs if count_gcells is unset. (Full-width characters in CJK
5640 * mode count as one.)
5641 *
5642 * It ignores the characters: LY_UNDERLINE_START_CHAR and
5643 * LY_UNDERLINE_END_CHAR
5644 * LY_BOLD_START_CHAR
5645 * LY_BOLD_END_CHAR
5646 * LY_SOFT_HYPHEN
5647 * if present in chptr.
5648 * It assumes UTF8 if utf_flag is set.
5649 * It is a case insensitive search. - KW & FM
5650 */
LYno_attr_mbcs_case_strstr(const char * chptr,const char * tarptr,int utf_flag,int count_gcells,int * nstartp,int * nendp)5651 const char *LYno_attr_mbcs_case_strstr(const char *chptr,
5652 const char *tarptr,
5653 int utf_flag,
5654 int count_gcells,
5655 int *nstartp,
5656 int *nendp)
5657 {
5658 const char *tmpchptr;
5659 const char *tmptarptr;
5660 int len = 0;
5661 int offset;
5662
5663 if (!(chptr && tarptr))
5664 return (NULL);
5665
5666 /*
5667 * Skip initial IsSpecial chars. - FM
5668 */
5669 while (IsSpecialAttrChar(*chptr) && *chptr != '\0')
5670 chptr++;
5671
5672 /*
5673 * Seek a first target match. - FM
5674 */
5675 for (; *chptr != '\0'; chptr++) {
5676 if ((!utf_flag && IS_CJK_TTY && is8bits(*chptr) &&
5677 *chptr == *tarptr &&
5678 IsNormalChar(*(chptr + 1))) ||
5679 (0 == UPPER8(*chptr, *tarptr))) {
5680 int tarlen = 0;
5681
5682 offset = len;
5683 len++;
5684
5685 /*
5686 * See if they line up.
5687 */
5688 tmpchptr = (chptr + 1);
5689 tmptarptr = (tarptr + 1);
5690
5691 if (*tmptarptr == '\0') {
5692 /*
5693 * One char target.
5694 */
5695 if (nstartp)
5696 *nstartp = offset;
5697 if (nendp)
5698 *nendp = len;
5699 return (chptr);
5700 }
5701 if (!utf_flag && IS_CJK_TTY && is8bits(*chptr) &&
5702 *chptr == *tarptr &&
5703 IsNormalChar(*tmpchptr)) {
5704 /*
5705 * Check the CJK multibyte. - FM
5706 */
5707 if (*tmpchptr == *tmptarptr) {
5708 /*
5709 * It's a match. Advance to next char. - FM
5710 */
5711 tmpchptr++;
5712 tmptarptr++;
5713 if (count_gcells)
5714 tarlen++;
5715 if (*tmptarptr == '\0') {
5716 /*
5717 * One character match. - FM
5718 */
5719 if (nstartp)
5720 *nstartp = offset;
5721 if (nendp)
5722 *nendp = len + tarlen;
5723 return (chptr);
5724 }
5725 } else {
5726 /*
5727 * It's not a match, so go back to seeking a first target
5728 * match. - FM
5729 */
5730 chptr++;
5731 if (count_gcells)
5732 len++;
5733 continue;
5734 }
5735 }
5736 /*
5737 * See if the rest of the target matches. - FM
5738 */
5739 while (1) {
5740 if (!IsSpecialAttrChar(*tmpchptr)) {
5741 if (!utf_flag && IS_CJK_TTY && is8bits(*tmpchptr)) {
5742 if (*tmpchptr == *tmptarptr &&
5743 *(tmpchptr + 1) == *(tmptarptr + 1) &&
5744 !IsSpecialAttrChar(*(tmpchptr + 1))) {
5745 tmpchptr++;
5746 tmptarptr++;
5747 if (count_gcells)
5748 tarlen++;
5749 } else {
5750 break;
5751 }
5752 } else if (0 != UPPER8(*tmpchptr, *tmptarptr)) {
5753 break;
5754 }
5755
5756 if (!IS_UTF_EXTRA(*tmptarptr)) {
5757 tarlen++;
5758 }
5759 tmpchptr++;
5760 tmptarptr++;
5761
5762 } else {
5763 tmpchptr++;
5764 }
5765
5766 if (*tmptarptr == '\0') {
5767 if (nstartp)
5768 *nstartp = offset;
5769 if (nendp)
5770 *nendp = len + tarlen;
5771 return (chptr);
5772 }
5773 if (*tmpchptr == '\0')
5774 break;
5775 }
5776 } else if (!(IS_UTF_EXTRA(*chptr) ||
5777 IsSpecialAttrChar(*chptr))) {
5778 if (!utf_flag && IS_CJK_TTY && is8bits(*chptr) &&
5779 IsNormalChar(*(chptr + 1))) {
5780 chptr++;
5781 if (count_gcells)
5782 len++;
5783 }
5784 len++;
5785 }
5786 } /* end for */
5787
5788 return (NULL);
5789 }
5790
5791 /*
5792 * LYno_attr_mbcs_strstr will find the first occurrence of the string pointed
5793 * to by tarptr in the string pointed to by chptr.
5794 *
5795 * It takes account of CJK and MultiByte Character Sequences (UTF8). The
5796 * physical lengths of the displayed string up to the start and end (= next
5797 * position after) the target string are returned in *nstartp and *nendp if the
5798 * search is successful.
5799 *
5800 * These lengths count glyph cells if count_gcells is set. (Full-width
5801 * characters in CJK mode count as two.) Normally that's what we want. They
5802 * count actual glyphs if count_gcells is unset. (Full-width characters in CJK
5803 * mode count as one.)
5804 *
5805 * It ignores the characters: LY_UNDERLINE_START_CHAR and
5806 * LY_UNDERLINE_END_CHAR
5807 * LY_BOLD_START_CHAR
5808 * LY_BOLD_END_CHAR
5809 * LY_SOFT_HYPHEN
5810 * if present in chptr.
5811 * It assumes UTF8 if utf_flag is set.
5812 * It is a case sensitive search. - KW & FM
5813 */
LYno_attr_mbcs_strstr(const char * chptr,const char * tarptr,int utf_flag,int count_gcells,int * nstartp,int * nendp)5814 const char *LYno_attr_mbcs_strstr(const char *chptr,
5815 const char *tarptr,
5816 int utf_flag,
5817 int count_gcells,
5818 int *nstartp,
5819 int *nendp)
5820 {
5821 const char *tmpchptr;
5822 const char *tmptarptr;
5823 int len = 0;
5824 int offset;
5825
5826 if (!(chptr && tarptr))
5827 return (NULL);
5828
5829 /*
5830 * Skip initial IsSpecial chars. - FM
5831 */
5832 while (IsSpecialAttrChar(*chptr) && *chptr != '\0')
5833 chptr++;
5834
5835 /*
5836 * Seek a first target match. - FM
5837 */
5838 for (; *chptr != '\0'; chptr++) {
5839 if ((*chptr) == (*tarptr)) {
5840 int tarlen = 0;
5841
5842 offset = len;
5843 len++;
5844
5845 /*
5846 * See if they line up.
5847 */
5848 tmpchptr = (chptr + 1);
5849 tmptarptr = (tarptr + 1);
5850
5851 if (*tmptarptr == '\0') {
5852 /*
5853 * One char target.
5854 */
5855 if (nstartp)
5856 *nstartp = offset;
5857 if (nendp)
5858 *nendp = len;
5859 return (chptr);
5860 }
5861 if (!utf_flag && IS_CJK_TTY && is8bits(*chptr) &&
5862 IsNormalChar(*tmpchptr)) {
5863 /*
5864 * Check the CJK multibyte. - FM
5865 */
5866 if (*tmpchptr == *tmptarptr) {
5867 /*
5868 * It's a match. Advance to next char. - FM
5869 */
5870 tmpchptr++;
5871 tmptarptr++;
5872 if (count_gcells)
5873 tarlen++;
5874 if (*tmptarptr == '\0') {
5875 /*
5876 * One character match. - FM
5877 */
5878 if (nstartp)
5879 *nstartp = offset;
5880 if (nendp)
5881 *nendp = len + tarlen;
5882 return (chptr);
5883 }
5884 } else {
5885 /*
5886 * It's not a match, so go back to seeking a first target
5887 * match. - FM
5888 */
5889 chptr++;
5890 if (count_gcells)
5891 len++;
5892 continue;
5893 }
5894 }
5895 /*
5896 * See if the rest of the target matches. - FM
5897 */
5898 while (1) {
5899 if (!IsSpecialAttrChar(*tmpchptr)) {
5900 if (!utf_flag && IS_CJK_TTY && is8bits(*tmpchptr)) {
5901 if (*tmpchptr == *tmptarptr &&
5902 *(tmpchptr + 1) == *(tmptarptr + 1) &&
5903 !IsSpecialAttrChar(*(tmpchptr + 1))) {
5904 tmpchptr++;
5905 tmptarptr++;
5906 if (count_gcells)
5907 tarlen++;
5908 } else {
5909 break;
5910 }
5911 } else if ((*tmpchptr) != (*tmptarptr)) {
5912 break;
5913 }
5914
5915 if (!IS_UTF_EXTRA(*tmptarptr)) {
5916 tarlen++;
5917 }
5918 tmpchptr++;
5919 tmptarptr++;
5920 } else {
5921 tmpchptr++;
5922 }
5923
5924 if (*tmptarptr == '\0') {
5925 if (nstartp)
5926 *nstartp = offset;
5927 if (nendp)
5928 *nendp = len + tarlen;
5929 return (chptr);
5930 }
5931 if (*tmpchptr == '\0')
5932 break;
5933 }
5934 } else if (!(IS_UTF_EXTRA(*chptr) ||
5935 IsSpecialAttrChar(*chptr))) {
5936 if (!utf_flag && IS_CJK_TTY && is8bits(*chptr) &&
5937 IsNormalChar(*(chptr + 1))) {
5938 chptr++;
5939 if (count_gcells)
5940 len++;
5941 }
5942 len++;
5943 }
5944 } /* end for */
5945
5946 return (NULL);
5947 }
5948
5949 /*
5950 * Allocate a new copy of a string, and returns it.
5951 */
SNACopy(char ** dest,const char * src,int n)5952 char *SNACopy(char **dest,
5953 const char *src,
5954 int n)
5955 {
5956 FREE(*dest);
5957 if (src) {
5958 *dest = typeMallocn(char, (unsigned) n + 1);
5959
5960 if (*dest == NULL) {
5961 CTRACE((tfp, "Tried to malloc %d bytes\n", n));
5962 outofmem(__FILE__, "SNACopy");
5963 assert(*dest != NULL);
5964 }
5965 LYStrNCpy(*dest, src, n);
5966 }
5967 return *dest;
5968 }
5969
5970 /*
5971 * String Allocate and Concatenate.
5972 */
SNACat(char ** dest,const char * src,int n)5973 char *SNACat(char **dest,
5974 const char *src,
5975 int n)
5976 {
5977 if (non_empty(src)) {
5978 if (*dest) {
5979 int length = (int) strlen(*dest);
5980
5981 *dest = typeRealloc(char, *dest, length + n + 1);
5982
5983 if (*dest == NULL)
5984 outofmem(__FILE__, "SNACat");
5985 assert(*dest != NULL);
5986 LYStrNCpy(*dest + length, src, n);
5987 } else {
5988 *dest = typeMallocn(char, (unsigned) n + 1);
5989
5990 if (*dest == NULL)
5991 outofmem(__FILE__, "SNACat");
5992 assert(*dest != NULL);
5993 MemCpy(*dest, src, n);
5994 (*dest)[n] = '\0'; /* terminate */
5995 }
5996 }
5997 return *dest;
5998 }
5999
6000 #include <caselower.h>
6001
6002 /*
6003 * Returns lowercase equivalent for unicode,
6004 * transparent output if no equivalent found.
6005 */
UniToLowerCase(long upper)6006 static long UniToLowerCase(long upper)
6007 {
6008 size_t i, high, low;
6009 long diff = 0;
6010
6011 /*
6012 * Make check for sure.
6013 */
6014 if (upper <= 0)
6015 return (upper);
6016
6017 /*
6018 * Try unicode_to_lower_case[].
6019 */
6020 low = 0;
6021 high = TABLESIZE(unicode_to_lower_case);
6022 while (low < high) {
6023 /*
6024 * Binary search.
6025 */
6026 i = (low + (high - low) / 2);
6027 diff = (unicode_to_lower_case[i].upper - upper);
6028 if (diff < 0)
6029 low = i + 1;
6030 if (diff > 0)
6031 high = i;
6032 if (diff == 0)
6033 return (unicode_to_lower_case[i].lower);
6034 }
6035
6036 return (upper); /* if we came here */
6037 }
6038
6039 /*
6040 * UPPER8 ?
6041 * it was "TOUPPER(a) - TOUPPER(b)" in its previous life...
6042 *
6043 * It was realized that case-insensitive user search
6044 * got information about upper/lower mapping from TOUPPER
6045 * (precisely from "(TOUPPER(a) - TOUPPER(b))==0")
6046 * and depends on locale in its 8bit mapping. -
6047 * Usually fails with DOS/WINDOWS display charsets
6048 * as well as on non-UNIX systems.
6049 *
6050 * So use unicode case mapping.
6051 */
UPPER8(int ch1,int ch2)6052 int UPPER8(int ch1, int ch2)
6053 {
6054 /* if they are the same or one is a null characters return immediately. */
6055 if (ch1 == ch2)
6056 return 0;
6057 if (!ch2)
6058 return UCH(ch1);
6059 else if (!ch1)
6060 return -UCH(ch2);
6061
6062 /* case-insensitive match for us-ascii */
6063 if (UCH(TOASCII(ch1)) < 128 && UCH(TOASCII(ch2)) < 128)
6064 return (TOUPPER(ch1) - TOUPPER(ch2));
6065
6066 /* case-insensitive match for upper half */
6067 if (UCH(TOASCII(ch1)) > 127 && /* S/390 -- gil -- 2066 */
6068 UCH(TOASCII(ch2)) > 127) {
6069 if (DisplayCharsetMatchLocale)
6070 return (TOUPPER(ch1) - TOUPPER(ch2)); /* old-style */
6071 else {
6072 long uni_ch2 = UCTransToUni((char) ch2, current_char_set);
6073 long uni_ch1;
6074
6075 if (uni_ch2 < 0)
6076 return UCH(ch1);
6077 uni_ch1 = UCTransToUni((char) ch1, current_char_set);
6078 return (int) (UniToLowerCase(uni_ch1) - UniToLowerCase(uni_ch2));
6079 }
6080 }
6081
6082 return (-10); /* mismatch, if we come to here */
6083 }
6084
6085 /*
6086 * Replaces 'fgets()' calls into a fixed-size buffer with reads into a buffer
6087 * that is allocated. When an EOF or error is found, the buffer is freed
6088 * automatically.
6089 */
LYSafeGets(char ** src,FILE * fp)6090 char *LYSafeGets(char **src,
6091 FILE *fp)
6092 {
6093 char buffer[BUFSIZ];
6094 char *result = 0;
6095
6096 if (src != 0)
6097 result = *src;
6098 if (result != 0)
6099 *result = 0;
6100
6101 while (fgets(buffer, (int) sizeof(buffer), fp) != NULL) {
6102 if (*buffer)
6103 result = StrAllocCat(result, buffer);
6104 if (strchr(buffer, '\n') != 0)
6105 break;
6106 }
6107 if (ferror(fp)) {
6108 FREE(result);
6109 } else if (feof(fp) && result && *result == '\0') {
6110 /*
6111 * If the file ends in the middle of a line, return the partial line;
6112 * if another call is made after this, it will return NULL. - kw
6113 */
6114 FREE(result);
6115 }
6116 if (src != 0)
6117 *src = result;
6118 return result;
6119 }
6120
6121 #ifdef USE_CMD_LOGGING
6122 static FILE *cmd_logfile;
6123 static FILE *cmd_script;
6124
LYOpenCmdLogfile(int argc,char ** argv)6125 void LYOpenCmdLogfile(int argc,
6126 char **argv)
6127 {
6128 int n;
6129
6130 if (lynx_cmd_logfile != 0) {
6131 cmd_logfile = LYNewTxtFile(lynx_cmd_logfile);
6132 if (cmd_logfile != 0) {
6133 fprintf(cmd_logfile, "# Command logfile created by %s %s (%s)\n",
6134 LYNX_NAME, LYNX_VERSION, LYVersionDate());
6135 for (n = 0; n < argc; n++) {
6136 fprintf(cmd_logfile, "# Arg%d = %s\n", n, argv[n]);
6137 }
6138 }
6139 }
6140 }
6141
LYHaveCmdScript(void)6142 BOOL LYHaveCmdScript(void)
6143 {
6144 return (BOOL) (cmd_script != 0);
6145 }
6146
LYOpenCmdScript(void)6147 void LYOpenCmdScript(void)
6148 {
6149 if (lynx_cmd_script != 0) {
6150 cmd_script = fopen(lynx_cmd_script, TXT_R);
6151 CTRACE((tfp, "LYOpenCmdScript(%s) %s\n",
6152 lynx_cmd_script,
6153 cmd_script != 0 ? "SUCCESS" : "FAIL"));
6154 }
6155 }
6156
LYReadCmdKey(int mode)6157 int LYReadCmdKey(int mode)
6158 {
6159 int ch = -1;
6160
6161 if (cmd_script != 0) {
6162 char *buffer = 0;
6163 char *src;
6164 char *tmp;
6165
6166 while ((ch < 0) && LYSafeGets(&buffer, cmd_script) != 0) {
6167 LYTrimTrailing(buffer);
6168 src = LYSkipBlanks(buffer);
6169 tmp = LYSkipNonBlanks(src);
6170 switch ((unsigned) (tmp - src)) {
6171 case 4:
6172 if (!strncasecomp(src, "exit", 4))
6173 exit_immediately(0);
6174 break;
6175 case 3:
6176 if (!strncasecomp(src, "key", 3)) {
6177 ch = LYStringToKeycode(LYSkipBlanks(tmp));
6178 } else if (!strncasecomp(src, "set", 3)) {
6179 src = LYSkipBlanks(tmp);
6180 tmp = src;
6181 while (*tmp != '\0') {
6182 if (isspace(UCH(*tmp)) || *tmp == '=')
6183 break;
6184 ++tmp;
6185 }
6186 if (*tmp != '\0') {
6187 *tmp++ = '\0';
6188 tmp = LYSkipBlanks(tmp);
6189 }
6190 if (LYSetConfigValue(src, tmp)) {
6191 CTRACE((tfp, "LYSetConfigValue(%s, %s)\n", src, tmp));
6192 } else if (LYsetRcValue(src, tmp)) {
6193 CTRACE((tfp, "LYsetRcValue(%s, %s)\n", src, tmp));
6194 } else {
6195 CTRACE((tfp, "?? set ignored %s\n", src));
6196 }
6197 }
6198 break;
6199 }
6200 }
6201 if (feof(cmd_script)) {
6202 fclose(cmd_script);
6203 cmd_script = 0;
6204 }
6205 if (ch >= 0) {
6206 LYSleepReplay();
6207 LYrefresh();
6208 }
6209 FREE(buffer);
6210 } else {
6211 ch = LYgetch_for(mode);
6212 }
6213 CTRACE((tfp, "LYReadCmdKey(%d) ->%s (%#x)\n",
6214 mode, LYKeycodeToString(ch, TRUE), ch));
6215 LYWriteCmdKey(ch);
6216 return ch;
6217 }
6218
6219 /*
6220 * Write a LYKeymapCode 'ch' to the logfile.
6221 */
LYWriteCmdKey(int ch)6222 void LYWriteCmdKey(int ch)
6223 {
6224 if (cmd_logfile != 0) {
6225 fprintf(cmd_logfile, "key %s\n", LYKeycodeToString(ch, FALSE));
6226 }
6227 }
6228
LYCloseCmdLogfile(void)6229 void LYCloseCmdLogfile(void)
6230 {
6231 if (cmd_logfile != 0) {
6232 LYCloseOutput(cmd_logfile);
6233 cmd_logfile = 0;
6234 }
6235 if (cmd_script != 0) {
6236 LYCloseInput(cmd_script);
6237 cmd_script = 0;
6238 }
6239 FREE(lynx_cmd_logfile);
6240 FREE(lynx_cmd_script);
6241 }
6242 #endif /* USE_CMD_LOGGING */
6243