/* $LynxId: LYStrings.c,v 1.273 2018/05/04 23:29:29 tom Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_MOUSE #include #endif #ifdef DJGPP_KEYHANDLER #include #include #endif /* DJGPP_KEYHANDLER */ #ifdef USE_COLOR_STYLE #include #include #endif #ifdef USE_SCROLLBAR #include #endif #ifdef USE_CMD_LOGGING #include #include #endif #include #include #if defined(WIN_EX) #undef BUTTON_CTRL #define BUTTON_CTRL 0 /* Quick hack */ #endif #ifdef DEBUG_EDIT #define CTRACE_EDIT(p) CTRACE(p) #else #define CTRACE_EDIT(p) /*nothing */ #endif #ifdef SUPPORT_MULTIBYTE_EDIT #define IsWordChar(c) (isalnum(UCH(c)) || is8bits(c)) #else #define IsWordChar(c) isalnum(UCH(c)) #endif /* * The edit_history lists allow the user to press tab when entering URL to get * the closest match in the closet */ #define LYClosetSize 100 static HTList *URL_edit_history; static HTList *MAIL_edit_history; /* If you want to add mouse support for some new platform, it's fairly * simple to do. Once you've determined the X and Y coordinates of * the mouse event, loop through the elements in the links[] array and * see if the coordinates fall within a highlighted link area. If so, * the code must set mouse_link to the index of the chosen link, * and return a key value that corresponds to LYK_ACTIVATE. The * LYK_ACTIVATE code in LYMainLoop.c will then check mouse_link * and activate that link. If the mouse event didn't fall within a * link, the code should just set mouse_link to -1 and return -1. --AMK */ /* The number of the link selected w/ the mouse (-1 if none) */ static int mouse_link = -1; static int have_levent; #if defined(USE_MOUSE) && defined(NCURSES) static MEVENT levent; #endif /* Return the value of mouse_link */ int peek_mouse_levent(void) { #if defined(USE_MOUSE) && defined(NCURSES) if (have_levent > 0) { ungetmouse(&levent); have_levent--; return 1; } #endif return 0; } /* Return the value of mouse_link, erasing it */ int get_mouse_link(void) { int t; t = mouse_link; mouse_link = -1; if (t < 0) t = -1; /* Backward compatibility. */ return t; } /* Return the value of mouse_link */ int peek_mouse_link(void) { return mouse_link; } int fancy_mouse(WINDOW * win, int row, int *position) { int cmd = LYK_DO_NOTHING; #ifdef USE_MOUSE /*********************************************************************/ #if defined(WIN_EX) && defined(PDCURSES) request_mouse_pos(); if (BUTTON_STATUS(1) && (MOUSE_X_POS >= getbegx(win) && MOUSE_X_POS < (getbegx(win) + getmaxx(win)))) { int mypos = MOUSE_Y_POS - getbegy(win); int delta = mypos - row; if (mypos + 1 == getmaxy(win)) { /* At the decorative border: scroll forward */ if (BUTTON_STATUS(1) & BUTTON1_TRIPLE_CLICKED) cmd = LYK_END; else if (BUTTON_STATUS(1) & BUTTON1_DOUBLE_CLICKED) cmd = LYK_NEXT_PAGE; else cmd = LYK_NEXT_LINK; } else if (mypos >= getmaxy(win)) { if (BUTTON_STATUS(1) & (BUTTON1_DOUBLE_CLICKED | BUTTON1_TRIPLE_CLICKED)) cmd = LYK_END; else cmd = LYK_NEXT_PAGE; } else if (mypos == 0) { /* At the decorative border: scroll back */ if (BUTTON_STATUS(1) & BUTTON1_TRIPLE_CLICKED) cmd = LYK_HOME; else if (BUTTON_STATUS(1) & BUTTON1_DOUBLE_CLICKED) cmd = LYK_PREV_PAGE; else cmd = LYK_PREV_LINK; } else if (mypos < 0) { if (BUTTON_STATUS(1) & (BUTTON1_DOUBLE_CLICKED | BUTTON1_TRIPLE_CLICKED)) cmd = LYK_HOME; else cmd = LYK_PREV_PAGE; #ifdef KNOW_HOW_TO_TOGGLE } else if (BUTTON_STATUS(1) & (BUTTON_CTRL)) { cur_selection += delta; cmd = LYX_TOGGLE; #endif } else if (BUTTON_STATUS(1) & (BUTTON_ALT | BUTTON_SHIFT | BUTTON_CTRL)) { /* Probably some unrelated activity, such as selecting some text. * Select, but do nothing else. */ *position += delta; cmd = -1; } else { /* No scrolling or overflow checks necessary. */ *position += delta; cmd = LYK_ACTIVATE; } } else if (BUTTON_STATUS(1) & (BUTTON3_CLICKED | BUTTON3_DOUBLE_CLICKED | BUTTON3_TRIPLE_CLICKED)) { cmd = LYK_QUIT; } #else #if defined(NCURSES) #define ButtonModifiers (BUTTON_ALT | BUTTON_SHIFT | BUTTON_CTRL) MEVENT event; getmouse(&event); if ((event.bstate & (BUTTON1_CLICKED | BUTTON1_DOUBLE_CLICKED | BUTTON1_TRIPLE_CLICKED))) { int mypos = event.y - getbegy(win); int delta = mypos - row; if ((event.x < getbegx(win) || event.x >= (getbegx(win) + getmaxx(win))) && !(event.bstate & ButtonModifiers)) return LYK_QUIT; /* User clicked outside, wants to quit? */ if (mypos + 1 == getmaxy(win)) { /* At the decorative border: scroll forward */ if (event.bstate & BUTTON1_TRIPLE_CLICKED) cmd = LYK_END; else if (event.bstate & BUTTON1_DOUBLE_CLICKED) cmd = LYK_NEXT_PAGE; else cmd = LYK_NEXT_LINK; } else if (mypos >= getmaxy(win)) { if (event.bstate & (BUTTON1_DOUBLE_CLICKED | BUTTON1_TRIPLE_CLICKED)) cmd = LYK_END; else cmd = LYK_NEXT_PAGE; } else if (mypos == 0) { /* At the decorative border: scroll back */ if (event.bstate & BUTTON1_TRIPLE_CLICKED) cmd = LYK_HOME; else if (event.bstate & BUTTON1_DOUBLE_CLICKED) cmd = LYK_PREV_PAGE; else cmd = LYK_PREV_LINK; } else if (mypos < 0) { if (event.bstate & (BUTTON1_DOUBLE_CLICKED | BUTTON1_TRIPLE_CLICKED)) cmd = LYK_HOME; else cmd = LYK_PREV_PAGE; #ifdef KNOW_HOW_TO_TOGGLE } else if (event.bstate & (BUTTON_CTRL)) { cur_selection += delta; cmd = LYX_TOGGLE; #endif } else if (event.x <= getbegx(win) + 1 || event.x >= getbegx(win) + getmaxx(win) - 2) { /* Click on left or right border for positioning without * immediate action: select, but do nothing else. * Actually, allow an error of one position inwards. - kw */ *position += delta; cmd = -1; } else if (event.bstate & ButtonModifiers) { /* Probably some unrelated activity, such as selecting some text. * Select, but do nothing else. */ /* Possibly this is never returned by ncurses, so this case * may be useless depending on situation (kind of mouse support * and library versions). - kw */ *position += delta; cmd = -1; } else { /* No scrolling or overflow checks necessary. */ *position += delta; cmd = LYK_ACTIVATE; } } else if (event.bstate & (BUTTON3_CLICKED | BUTTON3_DOUBLE_CLICKED | BUTTON3_TRIPLE_CLICKED)) { cmd = LYK_QUIT; } #endif /* NCURSES */ #endif /* PDCURSES */ /************************************************************************/ #endif /* USE_MOUSE */ (void) win; (void) row; (void) position; return cmd; } /* * Manage the collection of edit-histories */ static HTList *whichRecall(RecallType recall) { HTList **list; switch (recall) { case RECALL_CMD: return LYcommandList(); case RECALL_MAIL: list = &MAIL_edit_history; break; default: list = &URL_edit_history; break; } if (*list == 0) *list = HTList_new(); return *list; } /* * Remove the oldest item in the closet */ static void LYRemoveFromCloset(HTList *list) { void *data = HTList_removeFirstObject(list); if (data != 0) FREE(data); } void LYCloseCloset(RecallType recall) { HTList *list = whichRecall(recall); while (!HTList_isEmpty(list)) { LYRemoveFromCloset(list); } HTList_delete(list); /* should already be empty */ } /* * Strategy: We begin at the top and search downwards. We return the first * match, i.e., the newest since we search from the top. This should be made * more intelligent, but works for now. */ static char *LYFindInCloset(RecallType recall, char *base) { HTList *list = whichRecall(recall); char *data; size_t len = strlen(base); while (!HTList_isEmpty(list)) { data = (char *) HTList_nextObject(list); if (data != NULL && !StrNCmp(base, data, len)) return (data); } return (0); } static void LYAddToCloset(RecallType recall, char *str) { HTList *list = whichRecall(recall); char *data = NULL; StrAllocCopy(data, str); HTList_addObject(list, data); while (HTList_count(list) > LYClosetSize) LYRemoveFromCloset(list); } #ifdef USE_MOUSE static int XYdist(int x1, int y1, int x2, int y2, int dx2) { int xerr = 3 * (x2 - x1), yerr = 9 * (y2 - y1); if (xerr < 0) xerr = 3 * (x1 - x2 - dx2) + 1; /* pos after string not really in it */ if (xerr < 0) xerr = 0; if (yerr < 0) yerr = -yerr; if (!yerr) /* same line is good */ return (xerr > 0) ? (xerr * 2 - 1) : 0; if (xerr < 9 && yerr) /* x-dist of 3 cell better than y-dist of 1 cell */ yerr += (9 - xerr); return 2 * xerr + yerr; /* Subjective factor; ratio -> approx. 6 / 9 */ /* old: (IZ 1999-07-30) 3 2 2 2 1 1 1 XX XX XX XX XX 0 1 1 1 2 2 2 3 3 4\ 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3/ 4 4 5 4 4 4\ 3 3 3 3 3 3 3 3 3 3 3 3/ 4 4 4 5 5 6 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 6 5 now: (kw 1999-10-23) 41 35 29|23 17 11 5 XX XX XX XX XX 1 7 13 19 25|31 37 43 49 45 39 33\27 24 21 18 18 18 18 18 19 22 25 28/34 40 46 50 48 42 36 33 30\27 27 27 27 27 28/31 34 37 43 49 51 45 42 39 36 36 36 36 36 37 40 43 46 49 51 48 45 45 45 45 45 46 49 52 */ } /* Given X and Y coordinates of a mouse event, set mouse_link to the * index of the corresponding hyperlink, or set mouse_link to -1 if no * link matches the event. Returns -1 if no link matched the click, * or a keycode that must be returned from LYgetch() to activate the * link. */ static int set_clicked_link(int x, int y, int code, int clicks) { int left = 6; int right = LYcolLimit - 5; /* yes, I am assuming that my screen will be a certain width. */ int i; int c = -1; if (y == (LYlines - 1) || y == 0) { /* First or last row */ /* XXXX In fact # is not always at x==0? KANJI_CODE_OVERRIDE? */ int toolbar = (y == 0 && HText_hasToolbar(HTMainText)); mouse_link = -2; if (x == 0 && toolbar) /* On '#' */ c = LAC_TO_LKC0(LYK_TOOLBAR); #if defined(CAN_CUT_AND_PASTE) && defined(USE_COLOR_STYLE) else if (y == 0 && x == LYcolLimit && s_hot_paste != NOSTYLE) c = LAC_TO_LKC0(LYK_PASTE_URL); #endif else if (clicks > 1) { if (x < left + toolbar) c = (code == FOR_PROMPT && y) ? HOME_KEY : LAC_TO_LKC0(LYK_MAIN_MENU); else if (x > right) c = (code == FOR_PROMPT && y) ? END_KEY : LAC_TO_LKC0(LYK_VLINKS); else if (y) /* Last row */ c = LAC_TO_LKC0(LYK_END); else /* First row */ c = LAC_TO_LKC0(LYK_HOME); } else { if (x < left + toolbar) c = (code == FOR_PROMPT && y) ? LTARROW_KEY : ( #ifdef USE_COLOR_STYLE (s_forw_backw != NOSTYLE && x - toolbar >= 3) ? LAC_TO_LKC0(LYK_NEXT_DOC) : LAC_TO_LKC0(LYK_PREV_DOC) #else LAC_TO_LKC0(LYK_NEXT_DOC) #endif ); else if (x > right) c = (code == FOR_PROMPT && y) ? RTARROW_KEY : LAC_TO_LKC0(LYK_HISTORY); else if (y) /* Last row */ c = LAC_TO_LKC0(LYK_NEXT_PAGE); else /* First row */ c = LAC_TO_LKC0(LYK_PREV_PAGE); } #ifdef USE_SCROLLBAR } else if (x == (LYcols - 1) && LYShowScrollbar && LYsb_begin >= 0) { int h = display_lines - 2 * (LYsb_arrow != 0); mouse_link = -2; y -= 1 + (LYsb_arrow != 0); if (y < 0) return LAC_TO_LKC0(LYK_UP_TWO); if (y >= h) return LAC_TO_LKC0(LYK_DOWN_TWO); if (clicks >= 2) { double frac = (1. * y) / (h - 1); int l = HText_getNumOfLines() + 1; /* NOL() off by one? */ l -= display_lines; if (l > 0) LYSetNewline((int) (frac * l + 1 + 0.5)); return LYReverseKeymap(LYK_DO_NOTHING); } if (y < LYsb_begin) return LAC_TO_LKC0(LYK_PREV_PAGE); if (y >= LYsb_end) return LAC_TO_LKC0(LYK_NEXT_PAGE); mouse_link = -1; /* No action in edit fields */ #endif } else { int mouse_err = 29, /* subjctv-dist better than this for approx stuff */ cur_err; /* Loop over the links and see if we can get a match */ for (i = 0; i < nlinks; i++) { int len, lx = links[i].lx, is_text = 0; int count = 0; const char *text = LYGetHiliteStr(i, count); if (links[i].type == WWW_FORM_LINK_TYPE && F_TEXTLIKE(links[i].l_form->type)) is_text = 1; /* Check the first line of the link */ if (text != NULL) { if (is_text) len = links[i].l_form->size; else len = (int) LYstrCells(text); cur_err = XYdist(x, y, links[i].lx, links[i].ly, len); /* Check the second line */ while (cur_err > 0 && (text = LYGetHiliteStr(i, ++count)) != NULL) { /* Note that there is at most one hightext if is_text */ int cur_err_2 = XYdist(x, y, LYGetHilitePos(i, count), links[i].ly + count, (int) LYstrCells(text)); cur_err = HTMIN(cur_err, cur_err_2); } if (cur_err > 0 && is_text) cur_err--; /* a bit of preference for text fields, enter field if hit exactly at end - kw */ if (cur_err == 0) { int cury, curx; LYGetYX(cury, curx); /* double-click, if we care: submit text submit fields. - kw */ if (clicks > 1 && is_text && links[i].l_form->type == F_TEXT_SUBMIT_TYPE) { if (code != FOR_INPUT /* submit current input field directly */ || !(cury == y && (curx >= lx) && ((curx - lx) <= len))) { c = LAC_TO_LKC0(LYK_MOUSE_SUBMIT); mouse_link = i; } else { c = LAC_TO_LKC0(LYK_MOUSE_SUBMIT); mouse_link = -1; } mouse_err = 0; break; } if (code != FOR_INPUT /* Do not pick up the current input field */ || !((cury == y && (curx >= lx) && ((curx - lx) <= len)))) { if (is_text) { have_levent = 1; #if defined(TEXTFIELDS_MAY_NEED_ACTIVATION) && defined(INACTIVE_INPUT_STYLE_VH) if (x == links[i].lx && y == links[i].ly) textinput_redrawn = FALSE; #endif /* TEXTFIELDS_MAY_NEED_ACTIVATION && INACTIVE_INPUT_STYLE_VH */ } mouse_link = i; } else mouse_link = -1; mouse_err = 0; break; } else if (cur_err < mouse_err) { mouse_err = cur_err; mouse_link = i; } } } /* * If a link was hit, we must look for a key which will activate * LYK_ACTIVATE We expect to find LYK_ACTIVATE (it's usually mapped to * the Enter key). */ if (mouse_link >= 0) { if (mouse_err == 0) { if (c == -1) c = LAC_TO_LKC0(LYK_ACTIVATE); } else if (mouse_err >= 0) c = LAC_TO_LKC0(LYK_CHANGE_LINK); } else { if (2 * y > LYlines) { /* Bottom Half of the screen */ if (4 * y < 3 * LYlines) { c = LAC_TO_LKC0(LYK_DOWN_TWO); /* Third quarter */ } else c = LAC_TO_LKC0(LYK_DOWN_HALF); /* Fourth quarter */ } else { /* Upper Half of the screen */ if (4 * y < LYlines) { c = LAC_TO_LKC0(LYK_UP_HALF); /* First quarter */ } else c = LAC_TO_LKC0(LYK_UP_TWO); /* Second quarter */ } } } return c; } #endif /* USE_MOUSE */ /* * LYstrncpy() ensures that the copied strings end with a nul byte. * The nul is written to the n+1 position of the target. */ char *LYstrncpy(char *target, const char *source, int n) { char *val = target; int len; if (source == 0) source = ""; len = (int) strlen(source); if (n > 0) { if (n > len) n = len; (void) StrNCpy(target, source, n); } else { n = 0; } target[n] = '\0'; return val; } #define IS_NEW_GLYPH(ch) (utf_flag && (UCH(ch)&0xc0) != 0x80) #define IS_UTF_EXTRA(ch) (utf_flag && (UCH(ch)&0xc0) == 0x80) /* * LYmbcsstrncpy() terminates strings with a null byte. It takes account of * multibyte characters. The source string is copied until either end of string * or max number of either bytes or glyphs (mbcs sequences) (CJK or UTF8). The * utf_flag argument should be TRUE for UTF8. - KW & FM */ char *LYmbcsstrncpy(char *target, const char *source, int n_bytes, int n_glyphs, int utf_flag) { char *val = target; int i_bytes = 0, i_glyphs = 0; if (n_bytes < 0) n_bytes = 0; if (n_glyphs < 0) n_glyphs = 0; for (; *source != '\0' && i_bytes < n_bytes; i_bytes++) { if (IS_NEW_GLYPH(*source)) { if (i_glyphs++ >= n_glyphs) { *target = '\0'; return val; } } *(target++) = *(source++); } *target = '\0'; return val; } /* * LYmbcs_skip_glyphs() skips a given number of character positions in a string * and returns the resulting pointer. It takes account of UTF-8 encoded * characters. - KW */ const char *LYmbcs_skip_glyphs(const char *data, int n_glyphs, int utf_flag) { int i_glyphs = 0; if (n_glyphs < 0) n_glyphs = 0; if (non_empty(data)) { if (!utf_flag) { while (n_glyphs-- > 0) { if (!*++data) break; } } else { while (*data) { if (IS_NEW_GLYPH(*data)) { if (i_glyphs++ >= n_glyphs) { break; } } data++; } } } return data; } /* * LYmbcs_skip_cells() skips a given number of display positions in a string * and returns the resulting pointer. It takes account of UTF-8 encoded * characters. - TD */ const char *LYmbcs_skip_cells(const char *data, int n_cells, int utf_flag) { const char *result; int actual; int target = n_cells; do { result = LYmbcs_skip_glyphs(data, target--, utf_flag); actual = LYstrExtent2(data, (int) (result - data)); } while ((actual > 0) && (actual > n_cells)); return result; } /* * LYmbcsstrlen() returns the printable length of a string that might contain * IsSpecial or multibyte (CJK or UTF8) characters. - FM * * Counts glyph cells if count_gcells is set. (Full-width characters in CJK * mode count as two.) Counts character glyphs if count_gcells is unset. * (Full- width characters in CJK mode count as one.) - kw */ int LYmbcsstrlen(const char *str, int utf_flag, int count_gcells) { int i, j, len = 0; if (non_empty(str)) { #ifdef WIDEC_CURSES if (count_gcells) { len = LYstrCells(str); } else #endif { for (i = 0; str[i] != '\0'; i++) { if (!IsSpecialAttrChar(str[i])) { len++; if (IS_NEW_GLYPH(str[i])) { j = 0; while (IsNormalChar(str[(i + 1)]) && j < 5 && IS_UTF_EXTRA(str[(i + 1)])) { i++; j++; } } else if (!utf_flag && IS_CJK_TTY && !count_gcells && is8bits(str[i]) && IsNormalChar(str[(i + 1)])) { i++; } } } } } return (len); } #undef GetChar #ifdef USE_SLANG #if defined(VMS) #define GetChar() ttgetc() #elif defined(__DJGPP__) #define GetChar() getxkey() /* HTDos.c */ #elif defined(__CYGWIN__) #define GetChar SLkp_getkey #else #define GetChar (int)SLang_getkey #endif #else /* curses */ #if defined(DJGPP) #define GetChar() (djgpp_idle_loop(), wgetch(LYtopwindow())) #elif defined(NCURSES_VERSION) && defined(__BEOS__) #define GetChar() myGetCharNodelay() #elif defined(NCURSES) #define GetChar() wgetch(LYtopwindow()) #endif #endif #ifdef USE_CURSES_NODELAY /* PDCurses - until version 2.7 in 2005 - defined ERR as 0, unlike other * versions of curses. Generally both EOF and ERR are defined as -1's. * However, there is a special case (see HTCheckForInterrupt()) to handle a * case where no select() function is used in the win32 environment. * * HTCheckForInterrupt() uses nodelay() in this special case to check for * pending input. That normally returns ERR. But LYgetch_for() checks the * return value of this function for EOF (to handle some antique runtime * libraries which did not set the state for feof/ferror). Returning a zero * (0) is safer since normally that is not mapped to any commands, and will be * ignored by lynx. */ static int myGetCharNodelay(void) { int c = wgetch(LYwin); if (c == -1) c = 0; return c; } #else #define myGetCharNodelay() wgetch(LYwin) #endif #if !defined(GetChar) && defined(PDCURSES) && defined(PDC_BUILD) && PDC_BUILD >= 2401 /* PDCurses sends back key-modifiers that we don't use, but would waste time * upon, e.g., repainting the status line */ static int myGetChar(void) { int c; BOOL done = FALSE; do { switch (c = myGetCharNodelay()) { case KEY_SHIFT_L: case KEY_SHIFT_R: case KEY_CONTROL_L: case KEY_CONTROL_R: case KEY_ALT_L: case KEY_ALT_R: case KEY_RESIZE: break; default: done = TRUE; break; } } while (!done); return c; } #define GetChar() myGetChar() #endif #if !defined(GetChar) && defined(VMS) #define GetChar() ttgetc() #endif #if !defined(GetChar) #ifdef HAVE_KEYPAD #define GetChar() getch() #else #ifndef USE_GETCHAR #define USE_GETCHAR #endif /* !USE_GETCHAR */ #define GetChar() getchar() /* used to be "getc(stdin)" and "getch()" */ #endif /* HAVE_KEYPAD */ #endif /* !defined(GetChar) */ #if defined(USE_SLANG) && defined(USE_MOUSE) static int sl_parse_mouse_event(int *x, int *y, int *button) { /* "ESC [ M" has already been processed. There more characters are * expected: BUTTON X Y */ *button = (int) SLang_getkey(); switch (*button) { case 040: /* left button */ case 041: /* middle button */ case 042: /* right button */ *button -= 040; break; default: /* Hmmm.... */ SLang_flush_input(); return -1; } *x = (int) SLang_getkey(); if (*x == CH_ESC) /* Undo 7-bit replace for large x - kw */ *x = (int) SLang_getkey() + 64 - 33; else *x -= 33; *y = (int) SLang_getkey(); if (*y == CH_ESC) /* Undo 7-bit replace for large y - kw */ *y = (int) SLang_getkey() + 64 - 33; else *y -= 33; return 0; } static int sl_read_mouse_event(int code) { int mouse_x, mouse_y, button; mouse_link = -1; if (-1 != sl_parse_mouse_event(&mouse_x, &mouse_y, &button)) { if (button == 0) /* left */ return set_clicked_link(mouse_x, mouse_y, FOR_PANEL, 1); if (button == 1) /* middle */ return LYReverseKeymap(LYK_VIEW_BOOKMARK); if (button == 2) /* right */ { /* Right button: go back to prev document. * The problem is that we need to determine * what to return to achieve this. */ return LYReverseKeymap(LYK_PREV_DOC); } } if (code == FOR_INPUT || code == FOR_PROMPT) return DO_NOTHING; else return -1; } #endif /* USE_SLANG and USE_MOUSE */ static BOOLEAN csi_is_csi = TRUE; void ena_csi(int flag) { csi_is_csi = (BOOLEAN) flag; } #if defined(USE_KEYMAPS) #ifdef USE_SLANG #define define_key(string, code) \ SLkm_define_keysym ((SLFUTURE_CONST char*)(string), \ (unsigned) code, \ Keymap_List) #if SLANG_VERSION < 20000 #define expand_substring(target, first, last, final) \ (SLexpand_escaped_string(target, \ DeConst(first), \ DeConst(last), 1) static int SLang_get_error(void) { return SLang_Error; } #else int LY_Slang_UTF8_Mode = 0; #define expand_substring(target, first, last, final) \ (SLexpand_escaped_string(target, \ DeConst(first), \ DeConst(last), \ LY_Slang_UTF8_Mode), 1) #endif static SLKeyMap_List_Type *Keymap_List; /* This value should be larger than anything in LYStrings.h */ #define MOUSE_KEYSYM 0x0400 #endif /* * For ncurses, we use the predefined keysyms, since that lets us also reuse * the CSI logic and other special cases for VMS, NCSA telnet, etc. */ #ifdef USE_SLANG # ifdef VMS # define EXTERN_KEY(string,string1,lynx,curses) {string,lynx} # else # define EXTERN_KEY(string,string1,lynx,curses) {string,lynx},{string1,lynx} # endif # define INTERN_KEY(string,lynx,curses) {string,lynx,lynx} #else # define INTERN_KEY(string,lynx,curses) {string,curses,lynx} # define EXTERN_KEY(string,string1,lynx,curses) {string,curses,lynx} #endif typedef struct { const char *string; int value; LYExtraKeys internal; } Keysym_String_List; /* *INDENT-OFF* */ static Keysym_String_List Keysym_Strings [] = { INTERN_KEY( "UPARROW", UPARROW_KEY, KEY_UP ), INTERN_KEY( "DNARROW", DNARROW_KEY, KEY_DOWN ), INTERN_KEY( "RTARROW", RTARROW_KEY, KEY_RIGHT ), INTERN_KEY( "LTARROW", LTARROW_KEY, KEY_LEFT ), INTERN_KEY( "PGDOWN", PGDOWN_KEY, KEY_NPAGE ), INTERN_KEY( "PGUP", PGUP_KEY, KEY_PPAGE ), INTERN_KEY( "HOME", HOME_KEY, KEY_HOME ), INTERN_KEY( "END", END_KEY, KEY_END ), INTERN_KEY( "F1", F1_KEY, KEY_F(1) ), INTERN_KEY( "F2", F2_KEY, KEY_F(2) ), INTERN_KEY( "F3", F3_KEY, KEY_F(3) ), INTERN_KEY( "F4", F4_KEY, KEY_F(4) ), INTERN_KEY( "F5", F5_KEY, KEY_F(5) ), INTERN_KEY( "F6", F6_KEY, KEY_F(7) ), INTERN_KEY( "F7", F7_KEY, KEY_F(7) ), INTERN_KEY( "F8", F8_KEY, KEY_F(8) ), INTERN_KEY( "F9", F9_KEY, KEY_F(9) ), INTERN_KEY( "F10", F10_KEY, KEY_F(10) ), INTERN_KEY( "F11", F11_KEY, KEY_F(11) ), INTERN_KEY( "F12", F12_KEY, KEY_F(12) ), INTERN_KEY( "DO_KEY", DO_KEY, KEY_F(16) ), INTERN_KEY( "FIND_KEY", FIND_KEY, KEY_FIND ), INTERN_KEY( "SELECT_KEY", SELECT_KEY, KEY_SELECT ), INTERN_KEY( "INSERT_KEY", INSERT_KEY, KEY_IC ), INTERN_KEY( "REMOVE_KEY", REMOVE_KEY, KEY_DC ), INTERN_KEY( "DO_NOTHING", DO_NOTHING, DO_NOTHING|LKC_ISLKC ), INTERN_KEY( "BACKTAB_KEY", BACKTAB_KEY, BACKTAB_KEY ), INTERN_KEY( NULL, UNKNOWN_KEY, ERR ) }; /* *INDENT-ON* */ #ifdef NCURSES_VERSION /* * Ncurses stores the termcap/terminfo names in arrays sorted to match the * array of strings in the TERMTYPE struct. */ static int lookup_tiname(char *name, NCURSES_CONST char *const *names) { int code; for (code = 0; names[code] != 0; code++) if (!strcmp(names[code], name)) return code; return -1; } static const char *expand_tiname(const char *first, size_t len, char **result, char *final) { char name[BUFSIZ]; int code; TERMTYPE *tp = (TERMTYPE *) (cur_term); LYStrNCpy(name, first, len); if ((code = lookup_tiname(name, strnames)) >= 0 || (code = lookup_tiname(name, strfnames)) >= 0) { if (tp->Strings[code] != 0) { LYStrNCpy(*result, tp->Strings[code], (final - *result)); (*result) += strlen(*result); } } return first + len; } static const char *expand_tichar(const char *first, char **result, char *final) { int ch; int limit = 0; int radix = 0; int value = 0; const char *name = 0; switch (ch = *first++) { case 'E': case 'e': value = 27; break; case 'a': name = "bel"; break; case 'b': value = '\b'; break; case 'f': value = '\f'; break; case 'n': value = '\n'; break; case 'r': value = '\r'; break; case 't': value = '\t'; break; case 'v': value = '\v'; break; case 'd': radix = 10; limit = 3; break; case 'x': radix = 16; limit = 2; break; default: if (isdigit(ch)) { radix = 8; limit = 3; first--; } else { value = *first; } break; } if (radix != 0) { char *last = 0; char tmp[80]; LYStrNCpy(tmp, first, limit); value = (int) strtol(tmp, &last, radix); if (last != 0 && last != tmp) first += (last - tmp); } if (name != 0) { (void) expand_tiname(name, strlen(name), result, final); } else { **result = (char) value; (*result) += 1; } return first; } static BOOLEAN expand_substring(char *target, const char *first, const char *last, char *final) { int ch; while (first < last) { switch (ch = *first++) { case ESCAPE: first = expand_tichar(first, &target, final); break; case '^': ch = *first++; if (ch == LPAREN) { const char *s = StrChr(first, RPAREN); char *was = target; if (s == 0) s = first + strlen(first); first = expand_tiname(first, (size_t) (s - first), &target, final); if (target == was) return FALSE; if (*first) first++; } else if (ch == '?') { /* ASCII delete? */ *target++ = 127; } else if ((ch & 0x3f) < 0x20) { /* ASCII control char? */ *target++ = (char) (ch & 0x1f); } else { *target++ = '^'; first--; /* not legal... */ } break; case 0: /* convert nulls for terminfo */ ch = 0200; /* FALLTHRU */ default: *target++ = (char) ch; break; } } *target = '\0'; return TRUE; } #endif static void unescaped_char(const char *parse, int *keysym) { size_t len = strlen(parse); char buf[BUFSIZ]; if (len >= 3) { (void) expand_substring(buf, parse + 1, parse + len - 1, buf + sizeof(buf) - 1); if (strlen(buf) == 1) *keysym = *buf; } } static BOOLEAN unescape_string(char *source, char *target, char *final) { BOOLEAN ok = FALSE; if (*source == SQUOTE) { int keysym = -1; unescaped_char(source, &keysym); if (keysym >= 0) { target[0] = (char) keysym; target[1] = '\0'; ok = TRUE; } } else if (*source == DQUOTE) { if (expand_substring(target, source + 1, source + strlen(source) - 1, final)) ok = TRUE; (void) final; } return ok; } static Keysym_String_List *lookupKeysymByName(const char *name) { Keysym_String_List *k; Keysym_String_List *result = 0; k = Keysym_Strings; while (k->string != NULL) { if (0 == strcasecomp(k->string, name)) { result = k; break; } k++; } return result; } int map_string_to_keysym(const char *str, int *keysym, int internal) { int modifier = 0; *keysym = -1; if (strncasecomp(str, "LAC:", 4) == 0) { char *other = StrChr(str + 4, ':'); if (other) { int othersym = lecname_to_lec(other + 1); char buf[BUFSIZ]; if (othersym >= 0 && other - str - 4 < BUFSIZ) { LYStrNCpy(buf, str + 4, (other - str - 4)); *keysym = lacname_to_lac(buf); if (*keysym >= 0) { *keysym = LACLEC_TO_LKC0(*keysym, othersym); return (*keysym); } } } *keysym = lacname_to_lac(str + 4); if (*keysym >= 0) { *keysym = LAC_TO_LKC0(*keysym); return (*keysym); } } else if (strncasecomp(str, "Meta-", 5) == 0) { str += 5; modifier = LKC_MOD2; if (*str) { size_t len = strlen(str); if (len == 1) { return (*keysym = (UCH(str[0])) | modifier); } else if (len == 2 && str[0] == '^' && (isalpha(UCH(str[1])) || (TOASCII(str[1]) >= '@' && TOASCII(str[1]) <= '_'))) { return (*keysym = FROMASCII(UCH(str[1] & 0x1f)) | modifier); } else if (len == 2 && str[0] == '^' && str[1] == '?') { return (*keysym = CH_DEL | modifier); } if (*str == '^' || *str == '\\') { char buf[BUFSIZ]; (void) expand_substring(buf, str, str + HTMIN(len, 28), buf + sizeof(buf) - 1); if (strlen(buf) <= 1) return (*keysym = (UCH(buf[0])) | modifier); } } } else if (*str == SQUOTE) { unescaped_char(str, keysym); } else if (isdigit(UCH(*str))) { char *tmp; long value = strtol(str, &tmp, 0); if (!isalnum(UCH(*tmp))) { *keysym = (int) value; #ifndef USE_SLANG if (*keysym > 255) *keysym |= LKC_ISLKC; /* caller should remove this flag - kw */ #endif } } else { Keysym_String_List *k = lookupKeysymByName(str); if (k != 0) { *keysym = (internal ? k->internal : k->value); } } if (*keysym >= 0) *keysym |= modifier; return (*keysym); } LYExtraKeys LYnameToExtraKeys(const char *name) { Keysym_String_List *k = lookupKeysymByName(name); LYExtraKeys result = UNKNOWN_KEY; if (k != 0) result = k->internal; return result; } const char *LYextraKeysToName(LYExtraKeys code) { Keysym_String_List *k; const char *result = 0; k = Keysym_Strings; while (k->string != NULL) { if (k->internal == code) { result = k->string; break; } k++; } return result; } /* * Starting at a nonblank character, skip over a token, counting quoted and * escaped characters. */ static char *skip_keysym(char *parse) { int quoted = 0; int escaped = 0; while (*parse) { if (escaped) { escaped = 0; } else if (quoted) { if (*parse == ESCAPE) { escaped = 1; } else if (*parse == quoted) { quoted = 0; } } else if (*parse == ESCAPE) { escaped = 1; } else if (*parse == DQUOTE || *parse == SQUOTE) { quoted = *parse; } else if (isspace(UCH(*parse))) { break; } parse++; } return (quoted || escaped) ? 0 : parse; } /* * The first token is the string to define, the second is the name (of the * keysym) to define it to. */ #define MY_TRACE(p) CTRACE2(TRACE_CFG, p) static int setkey_cmd(char *parse) { char *s, *t; int keysym; char buf[BUFSIZ]; MY_TRACE((tfp, "KEYMAP(PA): in=%s", parse)); /* \n-terminated */ if ((s = skip_keysym(parse)) != 0) { if (isspace(UCH(*s))) { *s++ = '\0'; s = LYSkipBlanks(s); if ((t = skip_keysym(s)) == 0) { MY_TRACE((tfp, "KEYMAP(SKIP) no key expansion found\n")); return -1; } if (t != s) *t = '\0'; if (map_string_to_keysym(s, &keysym, FALSE) >= 0) { if (!unescape_string(parse, buf, buf + sizeof(buf) - 1)) { MY_TRACE((tfp, "KEYMAP(SKIP) could unescape key\n")); return 0; /* Trace the failure and continue. */ } if (LYTraceLogFP == 0) { MY_TRACE((tfp, "KEYMAP(DEF) keysym=%#x\n", keysym)); } else { MY_TRACE((tfp, "KEYMAP(DEF) keysym=%#x, seq='%s'\n", keysym, buf)); } return define_key(buf, keysym); } else { MY_TRACE((tfp, "KEYMAP(SKIP) could not map to keysym\n")); } } else { MY_TRACE((tfp, "KEYMAP(SKIP) junk after key description: '%s'\n", s)); } } else { MY_TRACE((tfp, "KEYMAP(SKIP) no key description\n")); } return -1; } #undef MY_TRACE static int unsetkey_cmd(char *parse) { char *s = skip_keysym(parse); if (s != parse) { *s = '\0'; #ifdef NCURSES_VERSION /* * This won't work with Slang. Remove the definition for the given * keysym. */ { int keysym; if (map_string_to_keysym(parse, &keysym, FALSE) >= 0) define_key((char *) 0, keysym); } #endif #ifdef USE_SLANG /* Slang implements this, for undefining the string which is associated * with a keysym (the reverse of what we normally want, but may * occasionally find useful). */ SLang_undefine_key(parse, Keymap_List); if (SLang_get_error()) return -1; #endif } return 0; } #ifdef FNAMES_8_3 #define FNAME_LYNX_KEYMAPS "_lynxkey.map" #else #define FNAME_LYNX_KEYMAPS ".lynx-keymaps" #endif /* FNAMES_8_3 */ static int read_keymap_file(void) { /* *INDENT-OFF* */ static struct { const char *name; int (*func) (char *s); } table[] = { { "setkey", setkey_cmd }, { "unsetkey", unsetkey_cmd }, }; /* *INDENT-ON* */ char *line = NULL; FILE *fp; char file[LY_MAXPATH]; int linenum; size_t n; LYAddPathToHome(file, sizeof(file), FNAME_LYNX_KEYMAPS); if ((fp = fopen(file, "r")) == 0) return 0; CTRACE((tfp, "read_keymap_file %s\n", file)); linenum = 0; while (LYSafeGets(&line, fp) != 0) { char *s = LYSkipBlanks(line); linenum++; if ((*s == 0) || (*s == '#')) continue; for (n = 0; n < TABLESIZE(table); n++) { size_t len = strlen(table[n].name); if (strlen(s) > len && !StrNCmp(s, table[n].name, len) && (*(table[n].func)) (LYSkipBlanks(s + len)) < 0) fprintf(stderr, FAILED_READING_KEYMAP, linenum, file); } } FREE(line); LYCloseInput(fp); return 0; } static void setup_vtXXX_keymap(void) { /* *INDENT-OFF* */ static Keysym_String_List table[] = { INTERN_KEY( "\033[A", UPARROW_KEY, KEY_UP ), INTERN_KEY( "\033OA", UPARROW_KEY, KEY_UP ), INTERN_KEY( "\033[B", DNARROW_KEY, KEY_DOWN ), INTERN_KEY( "\033OB", DNARROW_KEY, KEY_DOWN ), INTERN_KEY( "\033[C", RTARROW_KEY, KEY_RIGHT ), INTERN_KEY( "\033OC", RTARROW_KEY, KEY_RIGHT ), INTERN_KEY( "\033[D", LTARROW_KEY, KEY_LEFT ), INTERN_KEY( "\033OD", LTARROW_KEY, KEY_LEFT ), INTERN_KEY( "\033[1~", FIND_KEY, KEY_FIND ), INTERN_KEY( "\033[2~", INSERT_KEY, KEY_IC ), INTERN_KEY( "\033[3~", REMOVE_KEY, KEY_DC ), INTERN_KEY( "\033[4~", SELECT_KEY, KEY_SELECT ), INTERN_KEY( "\033[5~", PGUP_KEY, KEY_PPAGE ), INTERN_KEY( "\033[6~", PGDOWN_KEY, KEY_NPAGE ), INTERN_KEY( "\033[7~", HOME_KEY, KEY_HOME), INTERN_KEY( "\033[8~", END_KEY, KEY_END ), INTERN_KEY( "\033[11~", F1_KEY, KEY_F(1) ), INTERN_KEY( "\033[28~", F1_KEY, KEY_F(1) ), INTERN_KEY( "\033OP", F1_KEY, KEY_F(1) ), INTERN_KEY( "\033[OP", F1_KEY, KEY_F(1) ), INTERN_KEY( "\033[29~", DO_KEY, KEY_F(16) ), #if defined(USE_SLANG) #if defined(__WIN32__) || defined(__MINGW32__) INTERN_KEY( "\xE0H", UPARROW_KEY, KEY_UP ), INTERN_KEY( "\xE0P", DNARROW_KEY, KEY_DOWN ), INTERN_KEY( "\xE0M", RTARROW_KEY, KEY_RIGHT ), INTERN_KEY( "\xE0K", LTARROW_KEY, KEY_LEFT ), INTERN_KEY( "\xE0R", INSERT_KEY, KEY_IC ), INTERN_KEY( "\xE0S", REMOVE_KEY, KEY_DC ), INTERN_KEY( "\xE0I", PGUP_KEY, KEY_PPAGE ), INTERN_KEY( "\xE0Q", PGDOWN_KEY, KEY_NPAGE ), INTERN_KEY( "\xE0G", HOME_KEY, KEY_HOME), INTERN_KEY( "\xE0O", END_KEY, KEY_END ), #endif #if !defined(VMS) INTERN_KEY( "^(ku)", UPARROW_KEY, KEY_UP ), INTERN_KEY( "^(kd)", DNARROW_KEY, KEY_DOWN ), INTERN_KEY( "^(kr)", RTARROW_KEY, KEY_RIGHT ), INTERN_KEY( "^(kl)", LTARROW_KEY, KEY_LEFT ), INTERN_KEY( "^(@0)", FIND_KEY, KEY_FIND ), INTERN_KEY( "^(kI)", INSERT_KEY, KEY_IC ), INTERN_KEY( "^(kD)", REMOVE_KEY, KEY_DC ), INTERN_KEY( "^(*6)", SELECT_KEY, KEY_SELECT ), INTERN_KEY( "^(kP)", PGUP_KEY, KEY_PPAGE ), INTERN_KEY( "^(kN)", PGDOWN_KEY, KEY_NPAGE ), INTERN_KEY( "^(@7)", END_KEY, KEY_END ), INTERN_KEY( "^(kh)", HOME_KEY, KEY_HOME), INTERN_KEY( "^(k1)", F1_KEY, KEY_F(1) ), INTERN_KEY( "^(k2)", F2_KEY, KEY_F(2) ), INTERN_KEY( "^(k3)", F3_KEY, KEY_F(3) ), INTERN_KEY( "^(k4)", F4_KEY, KEY_F(4) ), INTERN_KEY( "^(k5)", F5_KEY, KEY_F(5) ), INTERN_KEY( "^(k6)", F6_KEY, KEY_F(6) ), INTERN_KEY( "^(k7)", F7_KEY, KEY_F(7) ), INTERN_KEY( "^(k8)", F8_KEY, KEY_F(8) ), INTERN_KEY( "^(k9)", F9_KEY, KEY_F(9) ), INTERN_KEY( "^(k;)", F10_KEY, KEY_F(10) ), INTERN_KEY( "^(F1)", F11_KEY, KEY_F(11) ), INTERN_KEY( "^(F2)", F12_KEY, KEY_F(12) ), INTERN_KEY( "^(F6)", DO_KEY, KEY_F(16) ), #endif /* !VMS */ #endif /* SLANG */ }; /* *INDENT-ON* */ size_t n; for (n = 0; n < TABLESIZE(table); n++) define_key(table[n].string, table[n].value); } int lynx_initialize_keymaps(void) { #ifdef USE_SLANG int i; char keybuf[2]; /* The escape sequences may contain embedded termcap strings. Make * sure the library is initialized for that. */ SLtt_get_terminfo(); if (NULL == (Keymap_List = SLang_create_keymap("Lynx", NULL))) return -1; keybuf[1] = 0; for (i = 1; i < 256; i++) { keybuf[0] = (char) i; define_key(keybuf, i); } setup_vtXXX_keymap(); define_key("\033[M", MOUSE_KEYSYM); if (SLang_get_error()) SLang_exit_error("Unable to initialize keymaps"); #else setup_vtXXX_keymap(); #endif return read_keymap_file(); } #endif /* USE_KEYMAPS */ #if defined(USE_MOUSE) && (defined(NCURSES)) static int LYmouse_menu(int x, int y, int atlink, int code) { #define ENT_ONLY_DOC 1 #define ENT_ONLY_LINK 2 /* *INDENT-OFF* */ static const struct { const char *txt; int action; unsigned int flag; } possible_entries[] = { {"Quit", LYK_ABORT, ENT_ONLY_DOC}, {"Home page", LYK_MAIN_MENU, ENT_ONLY_DOC}, {"Previous document", LYK_PREV_DOC, ENT_ONLY_DOC}, {"Beginning of document", LYK_HOME, ENT_ONLY_DOC}, {"Page up", LYK_PREV_PAGE, ENT_ONLY_DOC}, {"Half page up", LYK_UP_HALF, ENT_ONLY_DOC}, {"Two lines up", LYK_UP_TWO, ENT_ONLY_DOC}, {"History", LYK_HISTORY, ENT_ONLY_DOC}, {"Help", LYK_HELP, 0}, {"Do nothing (refresh)", LYK_REFRESH, 0}, {"Load again", LYK_RELOAD, ENT_ONLY_DOC}, {"Edit Doc URL and load", LYK_ECGOTO, ENT_ONLY_DOC}, {"Edit Link URL and load", LYK_ELGOTO, ENT_ONLY_LINK}, {"Show info", LYK_INFO, 0}, {"Search", LYK_WHEREIS, ENT_ONLY_DOC}, {"Print", LYK_PRINT, ENT_ONLY_DOC}, {"Two lines down", LYK_DOWN_TWO, ENT_ONLY_DOC}, {"Half page down", LYK_DOWN_HALF, ENT_ONLY_DOC}, {"Page down", LYK_NEXT_PAGE, ENT_ONLY_DOC}, {"End of document", LYK_END, ENT_ONLY_DOC}, {"Bookmarks", LYK_VIEW_BOOKMARK, ENT_ONLY_DOC}, {"Cookie jar", LYK_COOKIE_JAR, ENT_ONLY_DOC}, #ifdef USE_CACHEJAR {"Cache jar", LYK_CACHE_JAR, ENT_ONLY_DOC}, #endif {"Search index", LYK_INDEX_SEARCH, ENT_ONLY_DOC}, {"Set Options", LYK_OPTIONS, ENT_ONLY_DOC}, {"Activate this link", LYK_MOUSE_SUBMIT, ENT_ONLY_LINK}, {"Download", LYK_DOWNLOAD, ENT_ONLY_LINK} }; /* *INDENT-ON* */ #define TOTAL_MENUENTRIES TABLESIZE(possible_entries) const char *choices[TOTAL_MENUENTRIES + 1]; int actions[TOTAL_MENUENTRIES]; int c, c1, retlac; unsigned filter_out = (unsigned) (atlink ? ENT_ONLY_DOC : ENT_ONLY_LINK); c = c1 = 0; while (c < (int) TOTAL_MENUENTRIES) { if (!(possible_entries[c].flag & filter_out)) { choices[c1] = possible_entries[c].txt; actions[c1++] = possible_entries[c].action; } c++; } choices[c1] = NULL; /* Somehow the mouse is over the number instead of being over the name, so we decrease x. */ c = LYChoosePopup((atlink ? 2 : 10) - 1, y, (x > 5 ? x - 5 : 1), choices, c1, FALSE, TRUE); /* * LYhandlePopupList() wasn't really meant to be used outside of old-style * Options menu processing. One result of mis-using it here is that we * have to deal with side-effects regarding SIGINT signal handler and the * term_options global variable. - kw */ if (term_options) { retlac = LYK_DO_NOTHING; term_options = FALSE; } else { retlac = actions[c]; } if (code == FOR_INPUT && mouse_link == -1) { switch (retlac) { case LYK_ABORT: retlac = LYK_QUIT; /* a bit softer... */ /* fall through */ case LYK_MAIN_MENU: case LYK_PREV_DOC: case LYK_HOME: case LYK_PREV_PAGE: case LYK_UP_HALF: case LYK_UP_TWO: case LYK_HISTORY: case LYK_HELP: /* case LYK_REFRESH:*/ case LYK_RELOAD: case LYK_ECGOTO: case LYK_INFO: case LYK_WHEREIS: case LYK_PRINT: case LYK_DOWN_TWO: case LYK_DOWN_HALF: case LYK_NEXT_PAGE: case LYK_END: case LYK_VIEW_BOOKMARK: case LYK_COOKIE_JAR: #ifdef USE_CACHEJAR case LYK_CACHE_JAR: #endif case LYK_INDEX_SEARCH: case LYK_OPTIONS: mouse_link = -3; /* so LYgetch_for() passes it on - kw */ } } if (retlac == LYK_DO_NOTHING || retlac == LYK_REFRESH) { mouse_link = -1; /* mainloop should not change cur link - kw */ } if (code == FOR_INPUT && retlac == LYK_DO_NOTHING) { repaint_main_statusline(FOR_INPUT); } return retlac; } #endif /* USE_MOUSE && (NCURSES || PDCURSES) */ #if defined(USE_KEYMAPS) && defined(USE_SLANG) /************************************************************************/ static int current_sl_modifier = 0; /* We cannot guarantee the type for 'GetChar', and should not use a cast. */ static int myGetChar(void) { int i = GetChar(); if (i == 0) /* trick to get NUL char through - kw */ current_sl_modifier = LKC_ISLKC; return i; } static int LYgetch_for(int code) { SLang_Key_Type *key; int keysym; current_sl_modifier = 0; key = SLang_do_key(Keymap_List, myGetChar); if ((key == NULL) || (key->type != SLKEY_F_KEYSYM)) { #if defined(__WIN32__) || defined(__MINGW32__) if ((key == NULL) && (current_sl_modifier == LKC_ISLKC)) { key = SLang_do_key(Keymap_List, myGetChar); keysym = key->f.keysym; switch (keysym) { case 'H': keysym = UPARROW_KEY; break; case 'P': keysym = DNARROW_KEY; break; case 'M': keysym = RTARROW_KEY; break; case 'K': keysym = LTARROW_KEY; break; case 'R': keysym = INSERT_KEY; break; case 'S': keysym = REMOVE_KEY; break; case 'I': keysym = PGUP_KEY; break; case 'Q': keysym = PGDOWN_KEY; break; case 'G': keysym = HOME_KEY; break; case 'O': keysym = END_KEY; break; case ';': keysym = F1_KEY; break; } return (keysym); } #endif return (current_sl_modifier ? 0 : DO_NOTHING); } else { keysym = (int) key->f.keysym; #if defined (USE_MOUSE) if (keysym == MOUSE_KEYSYM) return sl_read_mouse_event(code); #endif if (keysym < 0) { return 0; } else if (keysym & (LKC_ISLECLAC | LKC_ISLAC)) { return (keysym); } else { current_sl_modifier = 0; if (LKC_HAS_ESC_MOD(keysym)) { current_sl_modifier = LKC_MOD2; keysym &= LKC_MASK; } if (keysym + 1 >= KEYMAP_SIZE) { return 0; } else { return (keysym | current_sl_modifier); } } } } /************************************************************************/ #else /* NOT defined(USE_KEYMAPS) && defined(USE_SLANG) */ /* * LYgetch() translates some escape sequences and may fake noecho. */ #define found_CSI(first,second) ((second) == '[' || (first) == 155) #define found_TLD(value) ((value) == '~') static int LYgetch_for(int code) { int a, b, c, d = -1; int current_modifier = 0; BOOLEAN done_esc = FALSE; (void) code; have_levent = 0; re_read: #if !defined(UCX) || !defined(VAXC) /* errno not modifiable ? */ if (errno == EINTR) set_errno(0); /* reset - kw */ #endif /* UCX && VAXC */ #ifndef USE_SLANG clearerr(stdin); /* needed here for ultrix and SOCKETSHR, but why? - FM */ #endif /* !USE_SLANG */ #if !defined(USE_SLANG) || defined(VMS) || defined(DJGPP_KEYHANDLER) c = GetChar(); lynx_nl2crlf(FALSE); #else if (LYCursesON) { c = GetChar(); lynx_nl2crlf(FALSE); } else { c = getchar(); if (c == EOF && errno == EINTR) /* Ctrl-Z causes EINTR in getchar() */ clearerr(stdin); if (feof(stdin) || ferror(stdin) || c == EOF) { #ifdef IGNORE_CTRL_C if (sigint) sigint = FALSE; #endif /* IGNORE_CTRL_C */ CTRACE((tfp, "GETCH: Translate ^C to ^G.\n")); return (LYCharINTERRUPT2); /* use ^G to cancel whatever called us. */ } } #endif /* !USE_SLANG || VMS */ CTRACE((tfp, "GETCH%d: Got %#x.\n", code, c)); if (LYNoZapKey > 1 && errno != EINTR && (c == EOF #ifdef USE_SLANG || (c == 0xFFFF) #endif )) { CTRACE((tfp, "nozap: Got EOF, curses %s, stdin is %p, LYNoZapKey reduced from %d to 0.\n", LYCursesON ? "on" : "off", (void *) stdin, LYNoZapKey)); LYNoZapKey = 0; /* 2 -> 0 */ if (LYReopenInput() > 0) { if (LYCursesON) { stop_curses(); start_curses(); LYrefresh(); } goto re_read; } } #ifdef USE_GETCHAR if (c == EOF && errno == EINTR) /* Ctrl-Z causes EINTR in getchar() */ goto re_read; #else if (c == EOF && errno == EINTR) { #if defined(HAVE_SIZECHANGE) || defined(USE_SLANG) CTRACE((tfp, "Got EOF with EINTR, recent_sizechange so far is %d\n", recent_sizechange)); if (!recent_sizechange) { /* not yet detected by ourselves */ size_change(0); CTRACE((tfp, "Now recent_sizechange is %d\n", recent_sizechange)); } #else /* HAVE_SIZECHANGE || USE_SLANG */ CTRACE((tfp, "Got EOF with EINTR, recent_sizechange is %d\n", recent_sizechange)); #endif /* HAVE_SIZECHANGE || USE_SLANG */ #if !defined(UCX) || !defined(VAXC) /* errno not modifiable ? */ set_errno(0); /* reset - kw */ #endif /* UCX && VAXC */ return (DO_NOTHING); } #endif /* USE_GETCHAR */ #ifdef USE_SLANG if (c == 0xFFFF && LYCursesON) { #ifdef IGNORE_CTRL_C if (sigint) { sigint = FALSE; goto re_read; } #endif /* IGNORE_CTRL_C */ return (LYCharINTERRUPT2); /* use ^G to cancel whatever called us. */ } #else /* not USE_SLANG: */ if (feof(stdin) || ferror(stdin) || c == EOF) { if (recent_sizechange) return (LYCharINTERRUPT2); /* use ^G to cancel whatever called us. */ #ifdef IGNORE_CTRL_C if (sigint) { sigint = FALSE; /* clearerr(stdin); don't need here if stays above - FM */ goto re_read; } #endif /* IGNORE_CTRL_C */ #if !defined(USE_GETCHAR) && !defined(VMS) && !defined(NCURSES) if (c == ERR && errno == EINTR) /* may have been handled signal - kw */ goto re_read; #endif /* USE_GETCHAR */ cleanup(); exit_immediately(EXIT_SUCCESS); } #endif /* USE_SLANG */ if (!escape_bound && (c == CH_ESC || (csi_is_csi && c == UCH(CH_ESC_PAR)))) { /* handle escape sequence S/390 -- gil -- 2024 */ done_esc = TRUE; /* Flag: we did it, not keypad() */ b = GetChar(); if (b == '[' || b == 'O') { a = GetChar(); } else { a = b; } switch (a) { case 'A': c = UPARROW_KEY; break; case 'B': c = DNARROW_KEY; break; case 'C': c = RTARROW_KEY; break; case 'D': c = LTARROW_KEY; break; case 'q': /* vt100 application keypad 1 */ c = END_KEY; break; case 'r': /* vt100 application keypad 2 */ c = DNARROW_KEY; break; case 's': /* vt100 application keypad 3 */ c = PGDOWN_KEY; break; case 't': /* vt100 application keypad 4 */ c = LTARROW_KEY; break; case 'v': /* vt100 application keypad 6 */ c = RTARROW_KEY; break; case 'w': /* vt100 application keypad 7 */ c = HOME_KEY; break; case 'x': /* vt100 application keypad 8 */ c = UPARROW_KEY; break; case 'y': /* vt100 application keypad 9 */ c = PGUP_KEY; break; case 'M': #if defined(USE_SLANG) && defined(USE_MOUSE) if (found_CSI(c, b)) { c = sl_read_mouse_event(code); } else #endif c = '\n'; /* keypad enter on pc ncsa telnet */ break; case 'm': #ifdef VMS if (b != 'O') #endif /* VMS */ c = '-'; /* keypad on pc ncsa telnet */ break; case 'k': if (b == 'O') c = '+'; /* keypad + on my xterminal :) */ else done_esc = FALSE; /* we have another look below - kw */ break; case 'l': #ifdef VMS if (b != 'O') #endif /* VMS */ c = '+'; /* keypad on pc ncsa telnet */ break; case 'P': #ifdef VMS if (b != 'O') #endif /* VMS */ c = F1_KEY; break; case 'u': #ifdef VMS if (b != 'O') #endif /* VMS */ c = F1_KEY; /* macintosh help button */ break; case 'p': #ifdef VMS if (b == 'O') #endif /* VMS */ c = '0'; /* keypad 0 */ break; case '1': /* VTxxx Find */ if (found_CSI(c, b) && found_TLD(d = GetChar())) c = FIND_KEY; else done_esc = FALSE; /* we have another look below - kw */ break; case '2': if (found_CSI(c, b)) { if (found_TLD(d = GetChar())) /* VTxxx Insert */ c = INSERT_KEY; else if ((d == '8' || d == '9') && found_TLD(GetChar())) { if (d == '8') /* VTxxx Help */ c = F1_KEY; else if (d == '9') /* VTxxx Do */ c = DO_KEY; d = -1; } } else done_esc = FALSE; /* we have another look below - kw */ break; case '3': /** VTxxx Delete **/ if (found_CSI(c, b) && found_TLD(d = GetChar())) c = REMOVE_KEY; else done_esc = FALSE; /* we have another look below - kw */ break; case '4': /** VTxxx Select **/ if (found_CSI(c, b) && found_TLD(d = GetChar())) c = SELECT_KEY; else done_esc = FALSE; /* we have another look below - kw */ break; case '5': /** VTxxx PrevScreen **/ if (found_CSI(c, b) && found_TLD(d = GetChar())) c = PGUP_KEY; else done_esc = FALSE; /* we have another look below - kw */ break; case '6': /** VTxxx NextScreen **/ if (found_CSI(c, b) && found_TLD(d = GetChar())) c = PGDOWN_KEY; else done_esc = FALSE; /* we have another look below - kw */ break; case '[': /** Linux F1-F5: ^[[[A etc. **/ if (found_CSI(c, b)) { if ((d = GetChar()) == 'A') c = F1_KEY; break; } /* FALLTHRU */ default: if (c == CH_ESC && a == b && !found_CSI(c, b)) { current_modifier = LKC_MOD2; c = a; /* We're not yet done if ESC + curses-keysym: */ done_esc = (BOOL) ((a & ~0xFF) == 0); break; } CTRACE((tfp, "Unknown key sequence: %d:%d:%d\n", c, b, a)); CTRACE_SLEEP(MessageSecs); break; } if (isdigit(a) && found_CSI(c, b) && d != -1 && !found_TLD(d)) d = GetChar(); if (!done_esc && (a & ~0xFF) == 0) { if (a == b && !found_CSI(c, b) && c == CH_ESC) { current_modifier = LKC_MOD2; c = a; done_esc = TRUE; } else { done_esc = TRUE; } } } #ifdef USE_KEYMAPS /* Extract a single code if two are merged: */ if (c >= 0 && (c & LKC_ISLECLAC)) { if (!(code == FOR_INPUT || code == FOR_PROMPT)) c = LKC2_TO_LKC(c); } else if (c >= 0 && (c & LKC_ISLKC)) { c &= ~LKC_ISLKC; done_esc = TRUE; /* already a lynxkeycode, skip keypad switches - kw */ } if (c >= 0 && LKC_HAS_ESC_MOD(c)) { current_modifier = LKC_MOD2; c &= LKC_MASK; } if (c >= 0 && (c & (LKC_ISLECLAC | LKC_ISLAC))) { done_esc = TRUE; /* already a lynxactioncode, skip keypad switches - iz */ } #endif if (done_esc) { /* don't do keypad() switches below, we already got it - kw */ } else { #ifdef HAVE_KEYPAD /* * Convert keypad() mode keys into Lynx defined keys. */ switch (c) { case KEY_DOWN: /* The four arrow keys ... */ c = DNARROW_KEY; break; case KEY_UP: c = UPARROW_KEY; break; case KEY_LEFT: c = LTARROW_KEY; break; case KEY_RIGHT: /* ... */ c = RTARROW_KEY; break; #if defined(PDCURSES) /* for NEC PC-9800 1998/08/30 (Sun) 21:50:35 */ case KEY_C2: c = DNARROW_KEY; break; case KEY_A2: c = UPARROW_KEY; break; case KEY_B1: c = LTARROW_KEY; break; case KEY_B3: c = RTARROW_KEY; break; case PAD0: /* PC-9800 Ins */ c = INSERT_KEY; break; case PADSTOP: /* PC-9800 DEL */ c = REMOVE_KEY; break; #endif /* PDCURSES */ case KEY_HOME: /* Home key (upward+left arrow) */ c = HOME_KEY; break; case KEY_CLEAR: /* Clear screen */ c = 18; /* CTRL-R */ break; case KEY_NPAGE: /* Next page */ c = PGDOWN_KEY; break; case KEY_PPAGE: /* Previous page */ c = PGUP_KEY; break; case KEY_LL: /* home down or bottom (lower left) */ c = END_KEY; break; #if defined(KEY_A1) && defined(KEY_C3) /* The keypad is arranged like this: */ /* a1 up a3 */ /* left b2 right */ /* c1 down c3 */ case KEY_A1: /* upper left of keypad */ c = HOME_KEY; break; case KEY_A3: /* upper right of keypad */ c = PGUP_KEY; break; case KEY_B2: /* center of keypad */ c = DO_NOTHING; break; case KEY_C1: /* lower left of keypad */ c = END_KEY; break; case KEY_C3: /* lower right of keypad */ c = PGDOWN_KEY; break; #endif /* defined(KEY_A1) && defined(KEY_C3) */ #ifdef KEY_ENTER case KEY_ENTER: /* enter/return */ c = '\n'; break; #endif /* KEY_ENTER */ #ifdef PADENTER /* PDCURSES */ case PADENTER: c = '\n'; break; #endif /* PADENTER */ #ifdef KEY_END case KEY_END: /* end key 001 */ c = END_KEY; break; #endif /* KEY_END */ #ifdef KEY_HELP case KEY_HELP: /* help key 001 */ c = F1_KEY; break; #endif /* KEY_HELP */ #ifdef KEY_BACKSPACE case KEY_BACKSPACE: c = CH_DEL; /* backspace key (delete, not Ctrl-H) S/390 -- gil -- 2041 */ break; #endif /* KEY_BACKSPACE */ case KEY_F(1): c = F1_KEY; /* VTxxx Help */ break; #if defined(KEY_F) && !defined(__DJGPP__) && !defined(_WINDOWS) case KEY_F(16): c = DO_KEY; /* VTxxx Do */ break; #endif /* KEY_F */ #ifdef KEY_REDO case KEY_REDO: /* VTxxx Do */ c = DO_KEY; break; #endif /* KEY_REDO */ #ifdef KEY_FIND case KEY_FIND: c = FIND_KEY; /* VTxxx Find */ break; #endif /* KEY_FIND */ #ifdef KEY_SELECT case KEY_SELECT: c = SELECT_KEY; /* VTxxx Select */ break; #endif /* KEY_SELECT */ #ifdef KEY_IC case KEY_IC: c = INSERT_KEY; /* VTxxx Insert */ break; #endif /* KEY_IC */ #ifdef KEY_DC case KEY_DC: c = REMOVE_KEY; /* VTxxx Remove */ break; #endif /* KEY_DC */ #ifdef KEY_BTAB case KEY_BTAB: c = BACKTAB_KEY; /* Back tab, often Shift-Tab */ break; #endif /* KEY_BTAB */ #ifdef KEY_RESIZE case KEY_RESIZE: /* size change detected by ncurses */ #if defined(HAVE_SIZECHANGE) || defined(USE_SLANG) /* Make call to detect new size, if that may be implemented. * The call may set recent_sizechange (except for USE_SLANG), * which will tell mainloop() to refresh. - kw */ CTRACE((tfp, "Got KEY_RESIZE, recent_sizechange so far is %d\n", recent_sizechange)); size_change(0); CTRACE((tfp, "Now recent_sizechange is %d\n", recent_sizechange)); #else /* HAVE_SIZECHANGE || USE_SLANG */ CTRACE((tfp, "Got KEY_RESIZE, recent_sizechange is %d\n", recent_sizechange)); #endif /* HAVE_SIZECHANGE || USE_SLANG */ if (!recent_sizechange) { #if defined(NCURSES) /* * Work-around for scenario (Linux libc5) where we got a * recent sizechange before reading KEY_RESIZE. If we do * not reset the flag, we'll next get an EOF read, which * causes Lynx to exit. */ recent_sizechange = TRUE; #endif /* * May be just the delayed effect of mainloop()'s call to * resizeterm(). Pretend we haven't read anything yet, don't * return. - kw */ goto re_read; } /* * Yep, we agree there was a change. Return now so that the caller * can react to it. - kw */ c = DO_NOTHING; break; #endif /* KEY_RESIZE */ /* The following maps PDCurses keys away from lynx reserved values */ #if (defined(_WINDOWS) || defined(__DJGPP__)) && !defined(USE_SLANG) case KEY_F(2): c = 0x213; break; case KEY_F(3): c = 0x214; break; case KEY_F(4): c = 0x215; break; case KEY_F(5): c = 0x216; break; case KEY_F(6): c = 0x217; break; case KEY_F(7): c = 0x218; break; #endif /* PDCurses */ #if defined(USE_MOUSE) /********************************************************************/ #if defined(NCURSES) || defined(PDCURSES) case KEY_MOUSE: CTRACE((tfp, "KEY_MOUSE\n")); if (code == FOR_CHOICE) { c = MOUSE_KEY; /* Will be processed by the caller */ } #if defined(NCURSES) else if (code == FOR_SINGLEKEY) { MEVENT event; getmouse(&event); /* Completely ignore event - kw */ c = DO_NOTHING; } #endif else { #if defined(NCURSES) MEVENT event; int err; int lac = LYK_UNKNOWN; c = -1; mouse_link = -1; err = getmouse(&event); if (err != OK) { CTRACE((tfp, "Mouse error: no event available!\n")); return (code == FOR_PANEL ? 0 : DO_NOTHING); } levent = event; /* Allow setting pos in entry fields */ if (event.bstate & BUTTON1_CLICKED) { c = set_clicked_link(event.x, event.y, code, 1); } else if (event.bstate & BUTTON1_DOUBLE_CLICKED) { c = set_clicked_link(event.x, event.y, code, 2); if (c == LAC_TO_LKC0(LYK_MOUSE_SUBMIT) && code == FOR_INPUT) lac = LYK_MOUSE_SUBMIT; } else if (event.bstate & BUTTON3_CLICKED) { c = LAC_TO_LKC0(LYK_PREV_DOC); } else if (code == FOR_PROMPT /* Cannot ignore: see LYCurses.c */ || (event.bstate & (BUTTON1_PRESSED | BUTTON1_RELEASED | BUTTON2_PRESSED | BUTTON2_RELEASED | BUTTON3_PRESSED | BUTTON3_RELEASED))) { /* Completely ignore - don't return anything, to avoid canceling the prompt - kw */ goto re_read; } else if (event.bstate & BUTTON2_CLICKED) { int atlink; c = set_clicked_link(event.x, event.y, code, 1); atlink = (c == LAC_TO_LKC0(LYK_ACTIVATE)); if (!atlink) mouse_link = -1; /* Forget about approx stuff. */ lac = LYmouse_menu(event.x, event.y, atlink, code); if (lac == LYK_MOUSE_SUBMIT) { if (mouse_link == -1) lac = LYK_ACTIVATE; #ifdef TEXTFIELDS_MAY_NEED_ACTIVATION else if (mouse_link >= 0 && textfields_need_activation && links[mouse_link].type == WWW_FORM_LINK_TYPE && F_TEXTLIKE(links[mouse_link].l_form->type)) lac = LYK_ACTIVATE; #endif } if (lac == LYK_ACTIVATE && mouse_link == -1) { HTAlert(gettext("No link chosen")); lac = LYK_REFRESH; } c = LAC_TO_LKC(lac); } #if NCURSES_MOUSE_VERSION > 1 else if (event.bstate & BUTTON4_PRESSED) { c = LAC_TO_LKC(LYK_UP_HALF); } else if (event.bstate & BUTTON5_PRESSED) { c = LAC_TO_LKC(LYK_DOWN_HALF); } #endif if (code == FOR_INPUT && mouse_link == -1 && lac != LYK_REFRESH && lac != LYK_MOUSE_SUBMIT) { ungetmouse(&event); /* Caller will process this. */ wgetch(LYwin); /* ungetmouse puts KEY_MOUSE back */ c = MOUSE_KEY; } #else /* pdcurses version */ #define H_CMD_AREA 6 #define HIST_CMD_2 12 #define V_CMD_AREA 1 int left = H_CMD_AREA; int right = (LYcolLimit - H_CMD_AREA - 1); /* yes, I am assuming that my screen will be a certain width. */ int tick_count; char *p = NULL; char mouse_info[128]; static int old_click = 0; /* [m Sec] */ c = -1; mouse_link = -1; if (!system_is_NT) { tick_count = GetTickCount(); /* Guard Mouse button miss click */ if ((tick_count - old_click) < 700) { c = DO_NOTHING; break; } else { old_click = tick_count; } } request_mouse_pos(); if (BUTTON_STATUS(1) & BUTTON_PRESSED) { if (MOUSE_Y_POS > (LYlines - V_CMD_AREA - 1)) { /* Screen BOTTOM */ if (MOUSE_X_POS < left) { c = LTARROW_KEY; p = "<-"; } else if (MOUSE_X_POS < HIST_CMD_2) { c = RTARROW_KEY; p = "->"; } else if (MOUSE_X_POS > right) { c = 'z'; p = "Cancel"; } else { c = PGDOWN_KEY; p = "PGDOWN"; } } else if (MOUSE_Y_POS < V_CMD_AREA) { /* Screen TOP */ if (MOUSE_X_POS < left) { c = LTARROW_KEY; p = "<-"; } else if (MOUSE_X_POS < HIST_CMD_2) { c = RTARROW_KEY; p = "->"; } else if (MOUSE_X_POS > right) { c = 'z'; p = "Cancel"; } else { c = PGUP_KEY; p = "PGUP"; } } else { c = set_clicked_link(MOUSE_X_POS, MOUSE_Y_POS, FOR_PANEL, 1); } } if (p && c != -1) { sprintf(mouse_info, "Mouse = 0x%x, [%s]", c, p); SetConsoleTitle(mouse_info); } #endif /* !(WIN_EX) */ if ((c + 1) >= KEYMAP_SIZE && (c & LKC_ISLAC)) return (c); } break; #endif /* NCURSES || PDCURSES */ /********************************************************************/ #endif /* USE_MOUSE */ } #endif /* HAVE_KEYPAD */ #ifdef DJGPP_KEYHANDLER switch (c) { case K_Down: /* The four arrow keys ... */ case K_EDown: c = DNARROW_KEY; break; case K_Up: case K_EUp: c = UPARROW_KEY; break; case K_Left: case K_ELeft: c = LTARROW_KEY; break; case K_Right: /* ... */ case K_ERight: c = RTARROW_KEY; break; case K_Home: /* Home key (upward+left arrow) */ case K_EHome: c = HOME_KEY; break; case K_PageDown: /* Next page */ case K_EPageDown: c = PGDOWN_KEY; break; case K_PageUp: /* Previous page */ case K_EPageUp: c = PGUP_KEY; break; case K_End: /* home down or bottom (lower left) */ case K_EEnd: c = END_KEY; break; case K_F1: /* F1 key */ c = F1_KEY; break; case K_Insert: /* Insert key */ case K_EInsert: c = INSERT_KEY; break; case K_Delete: /* Delete key */ case K_EDelete: c = REMOVE_KEY; break; case K_Alt_Escape: /* Alt-Escape */ c = 0x1a7; break; case K_Control_At: /* CTRL-@ */ c = 0x1a8; break; case K_Alt_Backspace: /* Alt-Backspace */ c = 0x1a9; break; case K_BackTab: /* BackTab */ c = BACKTAB_KEY; break; } #endif /* DGJPP_KEYHANDLER */ #if defined(USE_SLANG) && (defined(__DJGPP__) || defined(__CYGWIN__)) && !defined(DJGPP_KEYHANDLER) && !defined(USE_KEYMAPS) switch (c) { case SL_KEY_DOWN: /* The four arrow keys ... */ c = DNARROW_KEY; break; case SL_KEY_UP: c = UPARROW_KEY; break; case SL_KEY_LEFT: c = LTARROW_KEY; break; case SL_KEY_RIGHT: /* ... */ c = RTARROW_KEY; break; case SL_KEY_HOME: /* Home key (upward+left arrow) */ case SL_KEY_A1: /* upper left of keypad */ c = HOME_KEY; break; case SL_KEY_NPAGE: /* Next page */ case SL_KEY_C3: /* lower right of keypad */ c = PGDOWN_KEY; break; case SL_KEY_PPAGE: /* Previous page */ case SL_KEY_A3: /* upper right of keypad */ c = PGUP_KEY; break; case SL_KEY_END: /* home down or bottom (lower left) */ case SL_KEY_C1: /* lower left of keypad */ c = END_KEY; break; case SL_KEY_F(1): /* F1 key */ c = F1_KEY; break; case SL_KEY_IC: /* Insert key */ c = INSERT_KEY; break; case SL_KEY_DELETE: /* Delete key */ c = REMOVE_KEY; break; } #endif /* USE_SLANG && __DJGPP__ && !DJGPP_KEYHANDLER && !USE_KEYMAPS */ } if (c & (LKC_ISLAC | LKC_ISLECLAC)) { return (c); } else if ((c + 1) >= KEYMAP_SIZE) { /* * Don't return raw values for KEYPAD symbols which we may have missed * in the switch above if they are obviously invalid when used as an * index into (e.g.) keypad[]. - KW */ return (0); } else { return (c | current_modifier); } } /************************************************************************/ #endif /* NOT defined(USE_KEYMAPS) && defined(USE_SLANG) */ int LYgetch(void) { return LYReadCmdKey(FOR_PANEL); } /* * Read a single keystroke, allows mouse-selection. */ int LYgetch_choice(void) { int ch = LYReadCmdKey(FOR_CHOICE); if (ch == LYCharINTERRUPT1) ch = LYCharINTERRUPT2; /* treat ^C the same as ^G */ return ch; } /* * Read a single keystroke, allows mouse events. */ int LYgetch_input(void) { int ch = LYReadCmdKey(FOR_INPUT); if (ch == LYCharINTERRUPT1) ch = LYCharINTERRUPT2; /* treat ^C the same as ^G */ return ch; } /* * Read a single keystroke, ignoring case by translating it to uppercase. * Ignore mouse events, if any. */ int LYgetch_single(void) { int ch = LYReadCmdKey(FOR_SINGLEKEY); if (ch == LYCharINTERRUPT1) ch = LYCharINTERRUPT2; /* treat ^C the same as ^G */ else if (ch > 0 && ch < 256) ch = TOUPPER(ch); /* will ignore case of result */ return ch; } /* * Convert a null-terminated string to lowercase */ void LYLowerCase(char *arg_buffer) { register unsigned char *buffer = (unsigned char *) arg_buffer; size_t i; for (i = 0; buffer[i]; i++) { #ifdef SUPPORT_MULTIBYTE_EDIT /* 1998/11/23 (Mon) 17:04:55 */ if ((buffer[i] & 0x80) != 0 && buffer[i + 1] != 0) { if ((kanji_code == SJIS) && IS_SJIS_X0201KANA(UCH((buffer[i])))) { continue; } i++; } else { buffer[i] = UCH(TOLOWER(buffer[i])); } #else buffer[i] = TOLOWER(buffer[i]); #endif } } /* * Convert a null-terminated string to uppercase */ void LYUpperCase(char *arg_buffer) { register unsigned char *buffer = (unsigned char *) arg_buffer; size_t i; for (i = 0; buffer[i]; i++) { #ifdef SUPPORT_MULTIBYTE_EDIT /* 1998/11/23 (Mon) 17:05:10 */ if ((buffer[i] & 0x80) != 0 && buffer[i + 1] != 0) { if ((kanji_code == SJIS) && IS_SJIS_X0201KANA(UCH((buffer[i])))) { continue; } i++; } else { buffer[i] = UCH(TOUPPER(buffer[i])); } #else buffer[i] = UCH(TOUPPER(buffer[i])); #endif } } /* * Remove newlines from a string, returning true if we removed any. */ BOOLEAN LYRemoveNewlines(char *buffer) { BOOLEAN result = FALSE; if (buffer != 0) { register char *buf = buffer; for (; *buf && *buf != '\n' && *buf != '\r'; buf++) ; if (*buf) { /* runs very seldom */ char *old = buf; for (; *old; old++) { if (*old != '\n' && *old != '\r') *buf++ = *old; } *buf = '\0'; result = TRUE; } } return result; } /* * Remove leading/trailing whitespace from a string, reduce runs of embedded * whitespace to single blanks. */ char *LYReduceBlanks(char *buffer) { if (non_empty(buffer)) { LYTrimLeading(buffer); LYTrimTrailing(buffer); convert_to_spaces(buffer, TRUE); } return buffer; } /* * Remove ALL whitespace from a string (including embedded blanks), and returns * a pointer to the end of the trimmed string. */ char *LYRemoveBlanks(char *buffer) { char *result = NULL; if (buffer != 0) { register char *buf = buffer; for (; *buf && !isspace(UCH(*buf)); buf++) ; if (*buf) { /* runs very seldom */ char *old = buf; for (; *old; old++) { if (!isspace(UCH(*old))) *buf++ = *old; } *buf = '\0'; } result = buf; } return result; } /* * Skip whitespace */ char *LYSkipBlanks(char *buffer) { while (isspace(UCH((*buffer)))) buffer++; return buffer; } /* * Skip non-whitespace */ char *LYSkipNonBlanks(char *buffer) { while (*buffer != 0 && !isspace(UCH((*buffer)))) buffer++; return buffer; } /* * Skip const whitespace */ const char *LYSkipCBlanks(const char *buffer) { while (isspace(UCH((*buffer)))) buffer++; return buffer; } /* * Skip const non-whitespace */ const char *LYSkipCNonBlanks(const char *buffer) { while (*buffer != 0 && !isspace(UCH((*buffer)))) buffer++; return buffer; } /* * Trim leading blanks from a string */ void LYTrimLeading(char *buffer) { char *skipped = LYSkipBlanks(buffer); while ((*buffer++ = *skipped++) != 0) ; } /* * Trim trailing newline(s) from a string */ char *LYTrimNewline(char *buffer) { size_t i = strlen(buffer); while (i != 0 && (buffer[i - 1] == '\n' || buffer[i - 1] == '\r')) buffer[--i] = 0; return buffer; } /* * Trim trailing blanks from a string */ void LYTrimTrailing(char *buffer) { size_t i = strlen(buffer); while (i != 0 && isspace(UCH(buffer[i - 1]))) buffer[--i] = 0; } /* 1997/11/10 (Mon) 14:26:10, originally string_short() in LYExterns.c, but * moved here because LYExterns is not always configured. */ char *LYElideString(char *str, int cut_pos) { char buff[MAX_LINE], *s, *d; static char s_str[MAX_LINE]; int len; LYStrNCpy(buff, str, sizeof(buff) - 1); len = (int) strlen(buff); if (len > (LYcolLimit - 9)) { buff[cut_pos] = '.'; buff[cut_pos + 1] = '.'; for (s = (buff + len) - (LYcolLimit - 9) + cut_pos + 1, d = (buff + cut_pos) + 2; s >= buff && d >= buff && d < buff + LYcols && (*d++ = *s++) != 0;) ; buff[LYcols] = 0; } strcpy(s_str, buff); return (s_str); } /* * Trim a startfile, returning true if it looks like one of the Lynx tags. */ BOOLEAN LYTrimStartfile(char *buffer) { BOOLEAN result = FALSE; LYTrimHead(buffer); if (isLYNXEXEC(buffer) || isLYNXPROG(buffer)) { /* * The original implementations of these schemes expected white space * without hex escaping, and did not check for hex escaping, so we'll * continue to support that, until that code is redone in conformance * with SGML principles. - FM */ HTUnEscapeSome(buffer, " \r\n\t"); convert_to_spaces(buffer, TRUE); result = TRUE; } return result; } /* * Escape unsafe characters in startfile, except for lynx internal URLs. */ void LYEscapeStartfile(char **buffer) { if (!LYTrimStartfile(*buffer)) { char *escaped = HTEscapeUnsafe(*buffer); StrAllocCopy(*buffer, escaped); FREE(escaped); } } /* * Trim all blanks from startfile, except for lynx internal URLs. */ void LYTrimAllStartfile(char *buffer) { if (!LYTrimStartfile(buffer)) { LYRemoveBlanks(buffer); } } /* * Display the current value of the string and allow the user to edit it. */ /* * Shorthand to get rid of the "edit->suchandsos". */ #define IsDirty edit->efIsDirty #define IsHidden edit->efIsMasked #define StartX edit->efStartX #define StartY edit->efStartY #define Buffer edit->efBuffer #define EditAt edit->efEditAt /* current editing position (bytes) */ #define BufInUse edit->efBufInUse /* length (bytes) */ #define BufAlloc edit->efBufAlloc #define BufLimit edit->efBufLimit #define DpyWidth edit->efWidth #define DpyStart edit->efDpyStart /* display-start (columns) */ #define PanMargin edit->efPanMargin #define IsPanned edit->efIsPanned #define PadChar edit->efPadChar #ifdef ENHANCED_LINEEDIT #define EditMark edit->efEditMark #endif #define InputMods edit->efInputMods #define Offs2Col edit->efOffs2Col #define enableEditMark() \ if (EditMark < 0) \ EditMark = -(1 + EditMark) #define disableEditMark() \ if (EditMark >= 0) \ EditMark = -(1 + EditMark) #ifdef ENHANCED_LINEEDIT static bstring *killbuffer; #endif static void updateMargin(FieldEditor * edit) { if ((int) BufAlloc > DpyWidth) { /* Need panning? */ if (DpyWidth > 4) IsPanned = TRUE; /* * Figure out margins. If too big, we do a lot of unnecessary * scrolling. If too small, user doesn't have sufficient look-ahead. * Let's say 25% for each margin, upper bound is 10 columns. */ PanMargin = DpyWidth / 4; if (PanMargin > 10) PanMargin = 10; } } /* * Before using an array position, make sure that the array is long enough. * Reallocate if needed. */ static void ExtendEditor(FieldEditor * edit, int position) { size_t need = (size_t) (++position); if (need >= BufAlloc && (BufLimit == 0 || need < BufLimit)) { CTRACE((tfp, "ExtendEditor from %lu to %lu\n", (unsigned long) BufAlloc, (unsigned long) need)); Buffer = typeRealloc(char, Buffer, need); Offs2Col = typeRealloc(int, Offs2Col, need + 1); BufAlloc = need; updateMargin(edit); } } void LYFinishEdit(FieldEditor * edit) { CTRACE((tfp, "LYFinishEdit:%s\n", NonNull(Buffer))); FREE(Buffer); FREE(Offs2Col); } void LYSetupEdit(FieldEditor * edit, char *old_value, unsigned buffer_limit, int display_limit) { CTRACE((tfp, "LYSetupEdit buffer %lu, display %d:%s\n", (unsigned long) buffer_limit, display_limit, old_value)); BufLimit = buffer_limit; if (buffer_limit == 0) buffer_limit = (unsigned) strlen(old_value) + 1; /* * Initialize edit record */ LYGetYX(StartY, StartX); PadChar = ' '; IsDirty = TRUE; IsPanned = FALSE; InputMods = 0; BufAlloc = buffer_limit; DpyWidth = display_limit; PanMargin = 0; EditAt = (int) strlen(old_value); #ifdef ENHANCED_LINEEDIT EditMark = -1; /* pos=0, but do not show it yet */ #endif DpyStart = 0; updateMargin(edit); BufInUse = strlen(old_value); Buffer = typecallocn(char, BufAlloc + 1); if (Buffer == 0) outofmem(__FILE__, "LYSetupEdit"); LYStrNCpy(Buffer, old_value, buffer_limit); Offs2Col = typecallocn(int, BufAlloc + 1); if (Offs2Col == 0) outofmem(__FILE__, "LYSetupEdit"); } #ifdef SUPPORT_MULTIBYTE_EDIT /* * MBCS positioning routines below are specific to SUPPORT_MULTIBYTE_EDIT code. * Currently they handle UTF-8 and (hopefully) CJK. * Current encoding is recognized using defines below. * * LYmbcs* functions don't look very convenient to use here... * Do we really need utf_flag as an argument? * * It is set (see IS_UTF8_TTY) for every invocation out there, and they use * HTCJK flag internally anyway. Something like LYmbcsstrnlen == mbcs_glyphs * would be useful to work with string slices -Sergej Kvachonok */ #define IS_UTF8_EXTRA(x) (((unsigned char)(x) & 0300) == 0200) /* * Counts glyphs in a multibyte (sub)string s of length len. */ static int mbcs_glyphs(char *s, int len) { int glyphs = 0; int i; if (IS_UTF8_TTY) { for (i = 0; s[i] && i < len; i++) if (!IS_UTF8_EXTRA(s[i])) glyphs++; } else if (IS_CJK_TTY) { for (i = 0; s[i] && i < len; i++, glyphs++) if (is8bits(s[i])) i++; } else { glyphs = len; } return glyphs; } /* * Check if there are no continuation bytes in the multibyte (sub)string of * length len. */ static int mbcs_valid(char *s, int len, int limit) { int i; int result = FALSE; if (IS_UTF8_TTY) { for (i = 0; s[i] && i < limit; i++) { if (!IS_UTF8_EXTRA(s[i])) { if ((i + 1) == len) { result = TRUE; break; } } } } else if (IS_CJK_TTY) { for (i = 0; s[i] && i < limit; i++) { if (!is8bits(s[i])) { if ((i + 1) == len) { result = TRUE; break; } } } } else { result = TRUE; } return result; } /* * Calculates offset in bytes of a glyph at cell position pos. */ static int mbcs_skip(char *s, int pos) { int p, i; if (IS_UTF8_TTY) { for (i = 0, p = 0; s[i]; i++) { if (!IS_UTF8_EXTRA(s[i])) p++; if (p > pos) break; } } else if (IS_CJK_TTY) { for (p = i = 0; s[i] && p < pos; p++, i++) if (is8bits(s[i])) i++; } else { i = pos; } return i; } /* * Given a string that would display (at least) the given number of cells, * determine the number of multibyte characters that comprised those cells. */ static int cell2char(char *s, int cells) { int result = 0; int len = (int) strlen(s); int pos; int have; CTRACE_EDIT((tfp, "cell2char(%d) %d:%s\n", cells, len, s)); if (len != 0) { int best = -1; for (pos = 0; pos <= len; ++pos) { have = LYstrExtent2(s, pos); CTRACE_EDIT((tfp, " %2d:%2d:%.*s\n", pos, have, pos, s)); if (have >= cells) { if (cells <= 0) break; /* the best solution is the one with the most bytes */ best = pos; if (mbcs_valid(s, pos, len)) break; } } if (best >= 0) pos = best; if (pos > len) pos = len; } else { pos = 0; } result = mbcs_glyphs(s, pos); CTRACE_EDIT((tfp, "->%d\n", result)); return result; } #endif /* SUPPORT_MULTIBYTE_EDIT */ #ifdef EXP_KEYBOARD_LAYOUT static int map_active = 0; #else #define map_active 0 #endif int LYEditInsert(FieldEditor * edit, unsigned const char *s, int len, int map GCC_UNUSED, int maxMessage) { int length = (int) strlen(Buffer); int remains = (int) BufAlloc - (length + len); int edited = 0, overflow = 0; /* * ch is (presumably) printable character. */ if (remains < 0) { overflow = 1; len = 0; if ((int) BufAlloc > length) /* Insert as much as we can */ len = (int) BufAlloc - length; else goto finish; } ExtendEditor(edit, length + len); Buffer[length + len] = '\0'; for (; length >= EditAt; length--) /* Make room */ Buffer[length + len] = Buffer[length]; #ifdef EXP_KEYBOARD_LAYOUT if (map < 0) map = map_active; if (map && IS_UTF8_TTY) { int off = EditAt; unsigned const char *e = s + len; char *tail = 0; while (s < e) { char utfbuf[8]; int l = 1; utfbuf[0] = (char) *s; if (*s < 128 && LYKbLayouts[current_layout][*s]) { UCode_t ucode = LYKbLayouts[current_layout][*s]; if (ucode > 127) { if (UCConvertUniToUtf8(ucode, utfbuf)) { l = (int) strlen(utfbuf); remains -= l - 1; if (remains < 0) { if (tail) strcpy(Buffer + off, tail); FREE(tail); len = off; overflow = 1; goto finish; } if (l > 1 && !tail) StrAllocCopy(tail, Buffer + EditAt + len); } else utfbuf[0] = '?'; } else utfbuf[0] = (char) ucode; } StrNCpy(Buffer + off, utfbuf, l); edited = 1; off += l; s++; } if (tail) strcpy(Buffer + off, tail); len = off - EditAt; FREE(tail); } else if (map) { unsigned const char *e = s + len; unsigned char *t = (unsigned char *) Buffer + EditAt; while (s < e) { int ch; if (*s < 128 && LYKbLayouts[current_layout][*s]) { ch = UCTransUniChar((UCode_t) LYKbLayouts[current_layout][*s], current_char_set); if (ch < 0) ch = '?'; } else ch = *s; *t = UCH(ch); t++, s++; } edited = 1; } else #endif /* defined EXP_KEYBOARD_LAYOUT */ { StrNCpy(Buffer + EditAt, (const char *) s, len); edited = 1; } finish: EditAt += len; BufInUse += (size_t) len; if (edited) IsDirty = TRUE; if (overflow && maxMessage) _statusline(MAXLEN_REACHED_DEL_OR_MOV); #ifdef ENHANCED_LINEEDIT if (EditMark > EditAt) EditMark += len; else if (EditMark < -(1 + EditAt)) EditMark -= len; disableEditMark(); #endif return edited; } /* * Do one edit-operation, given the input 'ch' and working buffer 'edit'. * * If the input is processed, returns zero. * If the action should be performed outside of line-editing mode, return -ch. * Otherwise, e.g., returns 'ch'. */ int LYDoEdit(FieldEditor * edit, int ch, int action, int maxMessage) { int i; int length; unsigned char uch; int offset; if ((int) BufAlloc <= 0) return (0); /* Be defensive */ BufInUse = strlen(&Buffer[0]); length = (int) BufInUse; switch (action) { #ifdef EXP_KEYBOARD_LAYOUT case LYE_SWMAP: /* * Turn input character mapping on or off. */ map_active = ~map_active; break; #endif #ifndef CJK_EX case LYE_AIX: /* * Handle CJK characters, or as a valid character in the current * display character set. Otherwise, we treat this as LYE_ENTER. */ if (!IS_CJK_TTY && LYlowest_eightbit[current_char_set] > 0x97) return (ch); #endif /* FALLTHRU */ case LYE_CHAR: uch = UCH(ch); LYEditInsert(edit, &uch, 1, map_active, maxMessage); return 0; /* All changes already registered */ case LYE_C1CHAR: /* * ch is the second part (in most cases, a capital letter) of a 7-bit * replacement for a character in the 8-bit C1 control range. * * This is meant to undo transformations like 0x81 -> 0x1b 0x41 (ESC A) * etc., done by slang on Unix and possibly some comm programs. It's * an imperfect workaround that doesn't work for all such characters. */ ch &= 0xFF; if (ch + 64 >= LYlowest_eightbit[current_char_set]) ch += 64; if (EditAt <= ((int) BufAlloc) && BufInUse < BufAlloc) { #ifdef ENHANCED_LINEEDIT if (EditMark > EditAt) EditMark++; else if (EditMark < -(1 + EditAt)) EditMark--; disableEditMark(); #endif ExtendEditor(edit, length + 1); for (i = length; i >= EditAt; i--) /* Make room */ Buffer[i + 1] = Buffer[i]; Buffer[length + 1] = '\0'; Buffer[EditAt] = (char) ch; EditAt++; } else { if (maxMessage) { _statusline(MAXLEN_REACHED_DEL_OR_MOV); } return (ch); } break; case LYE_BACKW: /* go backward one word */ while (EditAt && !IsWordChar(Buffer[EditAt - 1])) EditAt--; while (EditAt && IsWordChar(UCH(Buffer[EditAt - 1]))) EditAt--; break; case LYE_FORWW: /* go forward one word */ while (IsWordChar(UCH(Buffer[EditAt]))) EditAt++; while (!IsWordChar(Buffer[EditAt]) && Buffer[EditAt]) EditAt++; break; case LYE_ERASE: /* erase the line */ Buffer[0] = '\0'; #ifdef ENHANCED_LINEEDIT EditMark = -1; /* Do not show the mark */ #endif /* FALLTHRU */ case LYE_BOL: /* go to beginning of line */ EditAt = 0; break; case LYE_EOL: /* go to end of line */ EditAt = length; break; case LYE_DELNW: /* delete next word */ offset = EditAt; LYDoEdit(edit, 0, LYE_FORWW, FALSE); offset = EditAt - offset; EditAt -= offset; goto shrink; /* right below */ case LYE_DELPW: /* delete previous word */ offset = EditAt; LYDoEdit(edit, 0, LYE_BACKW, FALSE); offset -= EditAt; shrink: for (i = EditAt; i < length - offset + 1; i++) Buffer[i] = Buffer[i + offset]; #ifdef ENHANCED_LINEEDIT disableEditMark(); if (EditMark <= -(1 + EditAt + offset)) EditMark += offset; /* Shift it */ if (-(1 + EditAt + offset) < EditMark && EditMark < -(1 + EditAt)) EditMark = -(1 + EditAt); /* Set to the current position */ #endif break; case LYE_DELBL: /* delete from cursor to beginning of line */ for (i = EditAt; i < length + 1; i++) Buffer[i - EditAt] = Buffer[i]; #ifdef ENHANCED_LINEEDIT disableEditMark(); if (EditMark <= -(1 + EditAt)) EditMark += EditAt; /* Shift it */ else EditMark = -1; /* Reset it */ #endif EditAt = 0; break; case LYE_DELEL: /* delete from cursor to end of line */ Buffer[EditAt] = '\0'; #ifdef ENHANCED_LINEEDIT disableEditMark(); if (EditMark <= -(1 + EditAt)) EditMark = -1; /* Reset it */ #endif break; case LYE_DELN: /* delete next character */ if (EditAt >= length) break; #ifndef SUPPORT_MULTIBYTE_EDIT EditAt++; #else EditAt += mbcs_skip(Buffer + EditAt, 1); #endif /* FALLTHRU */ case LYE_DELP: /* delete previous character */ if (length == 0 || EditAt == 0) break; #ifndef SUPPORT_MULTIBYTE_EDIT #ifdef ENHANCED_LINEEDIT disableEditMark(); if (EditMark <= -(1 + EditAt)) EditMark++; #endif EditAt--; for (i = EditAt; i < length; i++) Buffer[i] = Buffer[i + 1]; #else /* SUPPORT_MULTIBYTE_EDIT */ offset = EditAt - mbcs_skip(Buffer, mbcs_glyphs(Buffer, EditAt) - 1); EditAt -= offset; for (i = EditAt; i < length - offset + 1; i++) Buffer[i] = Buffer[i + offset]; #ifdef ENHANCED_LINEEDIT disableEditMark(); if (EditMark <= -(1 + EditAt)) EditMark += offset; /* Shift it */ #endif #endif /* SUPPORT_MULTIBYTE_EDIT */ break; case LYE_FORW_RL: case LYE_FORW: /* move cursor forward */ #ifndef SUPPORT_MULTIBYTE_EDIT if (EditAt < length) EditAt++; #else if (EditAt < length) EditAt += mbcs_skip(Buffer + EditAt, 1); #endif else if (action == LYE_FORW_RL) return -ch; break; case LYE_BACK_LL: case LYE_BACK: /* move cursor backward */ #ifndef SUPPORT_MULTIBYTE_EDIT if (EditAt > 0) EditAt--; #else if (EditAt > 0) EditAt = mbcs_skip(Buffer, mbcs_glyphs(Buffer, EditAt) - 1); #endif else if (action == LYE_BACK_LL) return -ch; break; #ifdef ENHANCED_LINEEDIT case LYE_TPOS: /* * Transpose characters - bash or ksh(emacs not gmacs) style */ #ifdef SUPPORT_MULTIBYTE_EDIT if (IS_UTF8_TTY || IS_CJK_TTY) break; /* Can't help it now */ #endif if (length <= 1 || EditAt == 0) return (ch); if (EditAt == length) EditAt--; enableEditMark(); if (EditMark == EditAt || EditMark == EditAt + 1) EditMark = EditAt - 1; disableEditMark(); if (Buffer[EditAt - 1] == Buffer[EditAt]) { EditAt++; break; } i = Buffer[EditAt - 1]; Buffer[EditAt - 1] = Buffer[EditAt]; Buffer[EditAt++] = (char) i; break; case LYE_SETMARK: /* Emacs-like set-mark-command */ EditMark = EditAt; return (0); case LYE_XPMARK: /* Emacs-like exchange-point-and-mark */ enableEditMark(); if (EditMark == EditAt) return (0); i = EditAt; EditAt = EditMark; EditMark = i; break; case LYE_KILLREG: /* Emacs-like kill-region */ enableEditMark(); if (EditMark == EditAt) { BStrFree(killbuffer); return (0); } if (EditMark > EditAt) LYDoEdit(edit, 0, LYE_XPMARK, FALSE); { int reglen = EditAt - EditMark; BStrCopy1(killbuffer, Buffer + EditMark, reglen); for (i = EditMark; Buffer[i + reglen]; i++) Buffer[i] = Buffer[i + reglen]; Buffer[i] = Buffer[i + reglen]; /* terminate */ EditAt = EditMark; } disableEditMark(); break; case LYE_YANK: /* Emacs-like yank */ if (!killbuffer) { EditMark = -(1 + EditAt); return (0); } else { int yanklen = killbuffer->len; if ((EditAt + yanklen) <= (int) BufAlloc && BufInUse + (size_t) yanklen <= BufAlloc) { ExtendEditor(edit, EditAt + yanklen); EditMark = -(1 + EditAt); for (i = length; i >= EditAt; i--) /* Make room */ Buffer[i + yanklen] = Buffer[i]; for (i = 0; i < yanklen; i++) Buffer[EditAt++] = killbuffer->str[i]; } else if (maxMessage) { _statusline(MAXLEN_REACHED_DEL_OR_MOV); } } break; #endif /* ENHANCED_LINEEDIT */ case LYE_UPPER: LYUpperCase(Buffer); break; case LYE_LOWER: LYLowerCase(Buffer); break; default: return (ch); } IsDirty = TRUE; BufInUse = strlen(&Buffer[0]); return (0); } /* * This function prompts for a choice or page number. * If a 'g' or 'p' suffix is included, that will be * loaded into c. Otherwise, c is zeroed. - FM & LE */ int get_popup_number(const char *msg, int *c, int *rel) { bstring *temp = NULL; int result = 0; /* * Load the c argument into the prompt buffer. */ BStrCopy0(temp, "?"); temp->str[0] = (char) *c; _statusline(msg); /* * Get the number, possibly with a suffix, from the user. */ if (LYgetBString(&temp, FALSE, 0, NORECALL) < 0 || isBEmpty(temp)) { HTInfoMsg(CANCELLED); *c = '\0'; *rel = '\0'; } else { char *p = temp->str; *rel = '\0'; result = atoi(p); while (isdigit(UCH(*p))) ++p; switch (*p) { case '+': case '-': /* 123+ or 123- */ *rel = *p++; *c = *p; break; default: *c = *p++; *rel = *p; break; case 0: break; } /* * If we had a 'g' or 'p' suffix, load it into c. Otherwise, zero c. Then * return the number. */ if (*p == 'g' || *p == 'G') { *c = 'g'; } else if (*p == 'p' || *p == 'P') { *c = 'p'; } else { *c = '\0'; } if (*rel != '+' && *rel != '-') *rel = 0; } BStrFree(temp); return result; } #ifdef USE_COLOR_STYLE # define TmpStyleOn(s) curses_style((s), STACK_ON) # define TmpStyleOff(s) curses_style((s), STACK_OFF) #else # define TmpStyleOn(s) # define TmpStyleOff(s) #endif /* defined USE_COLOR_STYLE */ static void remember_column(FieldEditor * edit, int offset) { int y0, x0; #if defined(USE_SLANG) y0 = 0; x0 = SLsmg_get_column(); #elif defined(USE_CURSES_PADS) getyx(LYwin, y0, x0); #else getyx(stdscr, y0, x0); #endif Offs2Col[offset] = x0; (void) y0; (void) x0; } static void fill_edited_line(int prompting GCC_UNUSED, int length, int ch) { int i; TmpStyleOn(prompting ? s_prompt_edit_pad : s_aedit_pad); for (i = 0; i < length; i++) { LYaddch(UCH(ch)); } TmpStyleOff(prompting ? s_prompt_edit_pad : s_aedit_pad); } /* * Multibyte string display subroutine. * FieldEditor fields retain their values as byte offsets. * All external logic still works fine with byte values. */ void LYRefreshEdit(FieldEditor * edit) { /* bytes and characters are not the same thing */ #if defined(DEBUG_EDIT) int all_bytes; #endif int pos_bytes = EditAt; int dpy_bytes; int lft_bytes; /* base of string which is displayed */ /* cells refer to display-columns on the screen */ int all_cells; /* total of display-cells in Buffer */ int dpy_cells; /* number of cells which are displayed */ int lft_cells; /* number of cells before display (on left) */ int pos_cells; /* number of display-cells up to EditAt */ #if defined(SUPPORT_MULTIBYTE_EDIT) int dpy_chars; int lft_chars; #if defined(DEBUG_EDIT) int all_chars; int pos_chars; #endif #endif /* other data */ int i; int padsize; char *str; int lft_shift = 0; int rgt_shift = 0; #ifdef USE_COLOR_STYLE int estyle; #endif int prompting = 0; (void) pos_bytes; /* * If we've made no changes, or if there is nothing to display, just leave. */ if (!IsDirty || (DpyWidth == 0)) return; CTRACE((tfp, "LYRefreshEdit:%s\n", Buffer)); IsDirty = FALSE; BufInUse = strlen(&Buffer[0]); all_cells = LYstrCells(Buffer); pos_cells = LYstrExtent2(Buffer, EditAt); #if defined(SUPPORT_MULTIBYTE_EDIT) && defined(DEBUG_EDIT) all_bytes = (int) BufInUse; lft_chars = mbcs_glyphs(Buffer, DpyStart); pos_chars = mbcs_glyphs(Buffer, EditAt); all_chars = mbcs_glyphs(Buffer, all_bytes); #endif /* * Now we have: * .--DpyWidth--. * +---------+=============+-----------+ * | |M M| | (M=PanMargin) * +---------+=============+-----------+ * 0 DpyStart BufInUse * * Insertion point can be anywhere between 0 and stringlength. Calculate * a new display starting point. * * First, make Lynx scroll several columns at a time as needed when * extending the string. Doing this helps with lowspeed connections. */ lft_bytes = DpyStart; lft_cells = LYstrExtent2(Buffer, DpyStart); if ((lft_cells + DpyWidth) <= all_cells) { if (pos_cells >= (lft_cells + DpyWidth) - PanMargin) { lft_cells = (pos_cells - DpyWidth) + PanMargin; #ifdef SUPPORT_MULTIBYTE_EDIT lft_chars = cell2char(Buffer, lft_cells); lft_bytes = mbcs_skip(Buffer, lft_chars); #else lft_bytes = lft_cells; #endif /* SUPPORT_MULTIBYTE_EDIT */ } } if (pos_cells < lft_cells + PanMargin) { lft_cells = pos_cells - PanMargin; if (lft_cells < 0) lft_cells = 0; #ifdef SUPPORT_MULTIBYTE_EDIT lft_chars = cell2char(Buffer, lft_cells); lft_bytes = mbcs_skip(Buffer, lft_chars); #else lft_bytes = lft_cells; #endif /* SUPPORT_MULTIBYTE_EDIT */ } LYmove(StartY, StartX); /* * Draw the left scrolling-indicator now, to avoid the complication of * overwriting part of a multicolumn character which may lie in the first * position. */ if (IsPanned && lft_cells) { CTRACE_EDIT((tfp, "Draw left scroll-indicator\n")); TmpStyleOn(prompting ? s_prompt_edit_arr : s_aedit_arr); LYmove(StartY, StartX); LYaddch(ACS_LARROW); TmpStyleOff(prompting ? s_prompt_edit_arr : s_aedit_arr); lft_shift = 1; } str = &Buffer[lft_bytes]; DpyStart = lft_bytes; dpy_cells = all_cells - lft_cells; CTRACE_EDIT((tfp, "Comparing dpy_cells %d > (%d - %d)\n", dpy_cells, DpyWidth, lft_shift)); if (dpy_cells > (DpyWidth - lft_shift)) { rgt_shift = 1; dpy_cells = (DpyWidth - lft_shift - rgt_shift); } for (;;) { #ifdef SUPPORT_MULTIBYTE_EDIT dpy_chars = cell2char(str, dpy_cells); dpy_bytes = mbcs_skip(str, dpy_chars); #else dpy_bytes = dpy_cells; #endif /* SUPPORT_MULTIBYTE_EDIT */ /* * The last character on the display may be multicolumn, and if we take * away a single cell for the right scroll-indicator, that would force * us to display fewer characters. Check for that, and recompute. */ if (rgt_shift && *str) { int old_cells = dpy_cells; dpy_cells = LYstrExtent2(str, dpy_bytes); if (dpy_cells > old_cells) dpy_cells = old_cells - 1; CTRACE_EDIT((tfp, "Comparing cells %d vs %d\n", dpy_cells, old_cells)); if (dpy_cells < old_cells) { CTRACE_EDIT((tfp, "Recomputing...\n")); continue; } } break; } CTRACE_EDIT((tfp, "BYTES left %2d pos %2d dpy %2d all %2d\n", lft_bytes, pos_bytes, dpy_bytes, all_bytes)); CTRACE_EDIT((tfp, "CELLS left %2d pos %2d dpy %2d all %2d\n", lft_cells, pos_cells, dpy_cells, all_cells)); CTRACE_EDIT((tfp, "CHARS left %2d pos %2d dpy %2d all %2d\n", lft_chars, pos_chars, dpy_chars, all_chars)); #ifdef USE_COLOR_STYLE /* * If this is the last screen line, set attributes to normal, should only * be needed for color styles. The curses function may be used directly to * avoid complications. - kw */ if (StartY == (LYlines - 1)) prompting = 1; if (prompting) { estyle = s_prompt_edit; } else { estyle = s_aedit; } CTRACE2(TRACE_STYLE, (tfp, "STYLE.getstr: switching to .\n", prompting ? "prompt" : "active")); if (estyle != NOSTYLE) { curses_style(estyle, STACK_ON); } else { (void) wattrset(LYwin, A_NORMAL); /* need to do something about colors? */ } #endif if (IsHidden) { BOOL utf_flag = IS_UTF8_TTY; int cell = 0; fill_edited_line(0, dpy_cells, '*'); i = 0; do { const char *last = str + i; const char *next = LYmbcs_skip_glyphs(last, 1, utf_flag); int j = (int) (next - str); while (i < j) { Offs2Col[i++] = cell + StartX; } cell += LYstrExtent2(last, (int) (next - last)); } while (i < dpy_bytes); Offs2Col[i] = cell + StartX; } else { #if defined(ENHANCED_LINEEDIT) && defined(USE_COLOR_STYLE) if (EditMark >= 0 && DpyStart > EditMark) TmpStyleOn(prompting ? s_prompt_sel : s_aedit_sel); #endif remember_column(edit, 0); for (i = 0; i < dpy_bytes; i++) { #if defined(ENHANCED_LINEEDIT) && defined(USE_COLOR_STYLE) if (EditMark >= 0 && ((DpyStart + i == EditMark && EditAt > EditMark) || (DpyStart + i == EditAt && EditAt < EditMark))) TmpStyleOn(prompting ? s_prompt_sel : s_aedit_sel); if (EditMark >= 0 && ((DpyStart + i == EditMark && EditAt < EditMark) || (DpyStart + i == EditAt && EditAt > EditMark))) TmpStyleOff(prompting ? s_prompt_sel : s_aedit_sel); #endif if (str[i] == 1 || str[i] == 2 || (UCH(str[i]) == 160 && !(HTPassHighCtrlRaw || IS_CJK_TTY || (LYCharSet_UC[current_char_set].enc != UCT_ENC_8859 && !(LYCharSet_UC[current_char_set].like8859 & UCT_R_8859SPECL))))) { LYaddch(' '); } else if (str[i] == '\t') { int col = Offs2Col[i] - StartX; /* * Like LYwaddnstr(), expand tabs from the beginning of the * field. */ while (++col % 8) LYaddch(' '); LYaddch(' '); } else { LYaddch(UCH(str[i])); } remember_column(edit, i + 1); } #if defined(ENHANCED_LINEEDIT) && defined(USE_COLOR_STYLE) if (EditMark >= 0 && ((DpyStart + dpy_bytes <= EditMark && DpyStart + dpy_bytes > EditAt) || (DpyStart + dpy_bytes > EditMark && DpyStart + dpy_bytes <= EditAt))) { TmpStyleOff(prompting ? s_prompt_sel : s_aedit_sel); } #endif } /* * Erase rest of input area. */ padsize = DpyWidth - (Offs2Col[dpy_bytes] - StartX); fill_edited_line(prompting, padsize, PadChar); /* * Scrolling indicators. */ if (IsPanned && dpy_bytes && rgt_shift) { CTRACE((tfp, "Draw right-scroller offset (%d + %d)\n", dpy_cells, lft_shift)); TmpStyleOn(prompting ? s_prompt_edit_arr : s_aedit_arr); LYmove(StartY, StartX + dpy_cells + lft_shift); LYaddch(ACS_RARROW); TmpStyleOff(prompting ? s_prompt_edit_arr : s_aedit_arr); } /* * Finally, move the cursor to the point where the next edit will occur. */ LYmove(StartY, Offs2Col[EditAt - DpyStart]); #ifdef USE_COLOR_STYLE if (estyle != NOSTYLE) curses_style(estyle, STACK_OFF); #endif LYrefresh(); } static void reinsertEdit(FieldEditor * edit, char *result) { if (result != 0) { LYDoEdit(edit, '\0', LYE_ERASE, FALSE); while (*result != '\0') { LYLineEdit(edit, (int) (*result), FALSE); result++; } } } static int caselessCmpList(const void *a, const void *b) { return strcasecomp(*(STRING2PTR) a, *(STRING2PTR) b); } static int normalCmpList(const void *a, const void *b) { return strcmp(*(STRING2PTR) a, *(STRING2PTR) b); } static char **sortedList(HTList *list, int ignorecase) { size_t count = (unsigned) HTList_count(list); size_t j = 0; size_t k, jk; char **result = typecallocn(char *, count + 1); if (result == 0) outofmem(__FILE__, "sortedList"); while (!HTList_isEmpty(list)) result[j++] = (char *) HTList_nextObject(list); if (count > 1) { qsort((char *) result, count, sizeof(*result), ignorecase ? caselessCmpList : normalCmpList); /* remove duplicate entries from the sorted index */ for (j = 0; result[j] != 0; j++) { k = j; while (result[k] != 0 && !strcmp(result[j], result[k])) { k++; } k--; if (j != k) { for (jk = j;; jk++) { result[jk] = result[jk + k - j]; if (result[jk] == 0) break; } } } } return result; } int LYarrayLength(STRING2PTR list) { int result = 0; while (*list++ != 0) result++; return result; } int LYarrayWidth(STRING2PTR list) { int result = 0; int check; while (*list != 0) { check = (int) strlen(*list++); if (check > result) result = check; } return result; } static void FormatChoiceNum(char *target, int num_choices, int choice, const char *value) { if (num_choices >= 0) { int digits = (num_choices > 9) ? 2 : 1; sprintf(target, "%*d: %.*s", digits, (choice + 1), MAX_LINE - 9 - digits, value); } else { LYStrNCpy(target, value, MAX_LINE - 1); } } static unsigned options_width(STRING2PTR list) { unsigned width = 0; int count = 0; while (list[count] != 0) { unsigned ncells = (unsigned) LYstrCells(list[count]); if (ncells > width) { width = ncells; } count++; } return width; } static void draw_option(WINDOW * win, int entry, int width, int reversed, int num_choices, int number, const char *value) { char Cnum[MAX_LINE]; (void) width; FormatChoiceNum(Cnum, num_choices, number, ""); #ifdef USE_SLANG SLsmg_gotorc(win->top_y + entry, (win->left_x + 2)); LYaddstr(Cnum); if (reversed) SLsmg_set_color(2); SLsmg_write_nstring((SLFUTURE_CONST char *) value, (unsigned) win->width); if (reversed) SLsmg_set_color(0); #else wmove(win, entry, 1); LynxWChangeStyle(win, s_menu_entry, STACK_ON); waddch(win, ' '); LynxWChangeStyle(win, s_menu_entry, STACK_OFF); LynxWChangeStyle(win, s_menu_number, STACK_ON); waddstr(win, Cnum); LynxWChangeStyle(win, s_menu_number, STACK_OFF); #ifdef USE_COLOR_STYLE LynxWChangeStyle(win, reversed ? s_menu_active : s_menu_entry, STACK_ON); #else if (reversed) wstart_reverse(win); #endif LYpaddstr(win, width, value); #ifdef USE_COLOR_STYLE LynxWChangeStyle(win, reversed ? s_menu_active : s_menu_entry, STACK_OFF); #else if (reversed) wstop_reverse(win); #endif LynxWChangeStyle(win, s_menu_entry, STACK_ON); waddch(win, ' '); LynxWChangeStyle(win, s_menu_entry, STACK_OFF); #endif /* USE_SLANG */ } static void show_popup_status(int cur_choice, STRING2PTR choices, int disabled, int for_mouse) { if (disabled) { _statusline(CHOICE_LIST_UNM_MSG); } else if (!for_mouse) { if (fields_are_named()) { char *status_msg = NULL; HTSprintf0(&status_msg, CHOICE_LIST_ADV_MSG, choices[cur_choice]); _statusline(status_msg); FREE(status_msg); } else { _statusline(CHOICE_LIST_MESSAGE); } #if defined(USE_MOUSE) && (defined(NCURSES) || defined(PDCURSES)) } else { _statusline(MOUSE_CHOICE_MESSAGE); #endif } } #define SHOW_POPUP_STATUS() show_popup_status(cur_choice, choices, disabled, for_mouse) /* * This function offers the choices for values of an option via a popup window * which functions like that for selection of options in a form. - FM * * Also used for mouse popups with ncurses; this is indicated by for_mouse. */ int LYhandlePopupList(int cur_choice, int ly, int lx, STRING2PTR choices, int width, int i_length, int disabled, int for_mouse) { BOOLEAN numbered = (BOOLEAN) (keypad_mode != NUMBERS_AS_ARROWS); int c = 0, cmd = 0, i = 0, j = 0, rel = 0; int orig_choice; WINDOW *form_window; int num_choices = 0; int max_choices = 0; int top, bottom, length = -1; int window_offset = 0; int lines_to_show; char Cnum[64]; int Lnum; int npages; static bstring *prev_target = NULL; /* Search string buffer */ static bstring *next_target = NULL; /* Next search buffer */ static BOOL first = TRUE; char *cp; int ch = 0; RecallType recall; int QueryTotal; int QueryNum; BOOLEAN FirstRecall = TRUE; BOOLEAN ReDraw = FALSE; int number; char buffer[MAX_LINE]; STRING2PTR Cptr = NULL; #define CAN_SCROLL_DOWN 1 #define CAN_SCROLL_UP 2 #define CAN_SCROLL 4 int can_scroll = 0, can_scroll_was = 0; orig_choice = cur_choice; if (cur_choice < 0) cur_choice = 0; /* * Initialize the search string buffer. - FM */ if (first) { BStrCopy0(next_target, ""); first = FALSE; } BStrCopy0(prev_target, ""); QueryTotal = (search_queries ? HTList_count(search_queries) : 0); recall = ((QueryTotal >= 1) ? RECALL_URL : NORECALL); QueryNum = QueryTotal; /* * Count the number of choices to be displayed, where num_choices ranges * from 0 to n, and set width to the longest choice string length. Also * set Lnum to the length for the highest choice number, then decrement * num_choices so as to be zero-based. The window width will be based on * the sum of width and Lnum. - FM */ num_choices = LYarrayLength(choices) - 1; if (width <= 0) width = (int) options_width(choices); if (numbered) { sprintf(Cnum, "%d: ", num_choices); Lnum = (int) strlen(Cnum); max_choices = num_choices; } else { Lnum = 0; max_choices = -1; } /* * Let's assume for the sake of sanity that ly is the number corresponding * to the line the choice is on. * * Let's also assume that cur_choice is the number of the item that should * be initially selected, as 0 being the first item. * * So what we have, is the top equal to the current screen line subtracting * the cur_choice + 1 (the one must be for the top line we will draw in a * box). If the top goes under 0, consider it 0. */ top = ly - (cur_choice + 1); if (top < 0) top = 0; /* * Check and see if we need to put the i_length parameter up to the number * of real choices. */ if (i_length < 1) { i_length = num_choices; } else { /* * Otherwise, it is really one number too high. */ i_length--; } /* * The bottom is the value of the top plus the number of options to view * plus 3 (one for the top line, one for the bottom line, and one to offset * the 0 counted in the num_choices). */ bottom = top + i_length + 3; /* * Set lines_to_show based on the user_mode global. */ if (user_mode == NOVICE_MODE) lines_to_show = LYlines - 4; else lines_to_show = LYlines - 2; if (for_mouse && user_mode == NOVICE_MODE && lines_to_show > 2) lines_to_show--; /* * Hmm... If the bottom goes beyond the number of lines available, */ if (bottom > lines_to_show) { /* * Position the window at the top if we have more choices than will fit * in the window. */ if ((i_length + 3) > lines_to_show) { top = 0; bottom = (top + (i_length + 3)); if (bottom > lines_to_show) bottom = (lines_to_show + 1); } else { /* * Try to position the window so that the selected choice will * appear where the selection box currently is positioned. It * could end up too high, at this point, but we'll move it down * latter, if that has happened. */ top = (lines_to_show + 1) - (i_length + 3); bottom = (lines_to_show + 1); } } /* * This is really fun, when the length is 4, it means 0 to 4, or 5. */ length = (bottom - top) - 2; if (length <= num_choices) can_scroll = CAN_SCROLL; /* * Move the window down if it's too high. */ if (bottom < ly + 2) { bottom = ly + 2; if (bottom > lines_to_show + 1) bottom = lines_to_show + 1; top = bottom - length - 2; } if (for_mouse) { int check = (Lnum + (int) width + 4); int limit = LYcols; /* shift horizontally to lie within screen width, if possible */ if (check < limit) { if (lx - 1 + check > limit) lx = limit + 1 - check; else if (lx <= 0) lx = 1; } } /* * Set up the overall window, including the boxing characters ('*'), if it * all fits. Otherwise, set up the widest window possible. - FM */ width += Lnum; bottom -= top; if (num_choices <= 0 || cur_choice > num_choices || (form_window = LYstartPopup(&top, &lx, &bottom, &width)) == 0) return (orig_choice); width -= Lnum; bottom += top; SHOW_POPUP_STATUS(); /* * Set up the window_offset for choices. * cur_choice ranges from 0...n * length ranges from 0...m */ if (cur_choice >= length) { window_offset = cur_choice - length + 1; } /* * Compute the number of popup window pages. - FM */ npages = ((num_choices + 1) > length) ? (((num_choices + 1) + (length - 1)) / (length)) : 1; /* * OH! I LOVE GOTOs! hack hack hack */ redraw: /* * Display the boxed choices. */ for (i = 0; i <= num_choices; i++) { if (i >= window_offset && i - window_offset < length) { draw_option(form_window, ((i + 1) - window_offset), width, FALSE, max_choices, i, choices[i]); } } LYbox(form_window, !numbered); Cptr = NULL; /* * Loop on user input. */ while (cmd != LYK_ACTIVATE) { int row = ((i + 1) - window_offset); /* Show scroll indicators. */ if (can_scroll) { can_scroll = ((window_offset ? CAN_SCROLL_UP : 0) | (num_choices - window_offset >= length ? CAN_SCROLL_DOWN : 0)); if (~can_scroll & can_scroll_was) { /* Need to redraw */ LYbox(form_window, !numbered); can_scroll_was = 0; } if (can_scroll & ~can_scroll_was & CAN_SCROLL_UP) { wmove(form_window, 1, Lnum + width + 3); LynxWChangeStyle(form_window, s_menu_sb, STACK_ON); waddch(form_window, ACS_UARROW); LynxWChangeStyle(form_window, s_menu_sb, STACK_OFF); } if (can_scroll & ~can_scroll_was & CAN_SCROLL_DOWN) { wmove(form_window, length, Lnum + width + 3); LynxWChangeStyle(form_window, s_menu_sb, STACK_ON); waddch(form_window, ACS_DARROW); LynxWChangeStyle(form_window, s_menu_sb, STACK_OFF); } } /* * Unreverse cur choice. */ if (Cptr != NULL) { draw_option(form_window, row, width, FALSE, max_choices, i, Cptr[i]); } Cptr = choices; i = cur_choice; row = ((cur_choice + 1) - window_offset); draw_option(form_window, row, width, TRUE, max_choices, cur_choice, Cptr[cur_choice]); LYstowCursor(form_window, row, 1); c = LYgetch_choice(); if (term_options || LYCharIsINTERRUPT(c)) { /* Control-C or Control-G */ cmd = LYK_QUIT; #ifndef USE_SLANG } else if (c == MOUSE_KEY) { if ((cmd = fancy_mouse(form_window, row, &cur_choice)) < 0) goto redraw; if (cmd == LYK_ACTIVATE) break; #endif } else { cmd = LKC_TO_LAC(keymap, c); } #ifdef VMS if (HadVMSInterrupt) { HadVMSInterrupt = FALSE; cmd = LYK_QUIT; } #endif /* VMS */ switch (cmd) { case LYK_F_LINK_NUM: c = '\0'; /* FALLTHRU */ case LYK_1: /* FALLTHRU */ case LYK_2: /* FALLTHRU */ case LYK_3: /* FALLTHRU */ case LYK_4: /* FALLTHRU */ case LYK_5: /* FALLTHRU */ case LYK_6: /* FALLTHRU */ case LYK_7: /* FALLTHRU */ case LYK_8: /* FALLTHRU */ case LYK_9: /* * Get a number from the user, possibly with a 'g' or 'p' suffix * (which will be loaded into c). - FM & LE */ number = get_popup_number(SELECT_OPTION_NUMBER, &c, &rel); /* handle + or - suffix */ CTRACE((tfp, "got popup option number %d, ", number)); CTRACE((tfp, "rel='%c', c='%c', cur_choice=%d\n", rel, c, cur_choice)); if (c == 'p') { int curpage = ((cur_choice + 1) > length) ? (((cur_choice + 1) + (length - 1)) / (length)) : 1; CTRACE((tfp, " curpage=%d\n", curpage)); if (rel == '+') number = curpage + number; else if (rel == '-') number = curpage - number; } else if (rel == '+') { number = cur_choice + number + 1; } else if (rel == '-') { number = cur_choice - number + 1; } if (rel) CTRACE((tfp, "new number=%d\n", number)); /* * Check for a 'p' suffix. - FM */ if (c == 'p') { /* * Treat 1 or less as the first page. - FM */ if (number <= 1) { if (window_offset == 0) { HTUserMsg(ALREADY_AT_OPTION_BEGIN); SHOW_POPUP_STATUS(); break; } window_offset = 0; cur_choice = 0; SHOW_POPUP_STATUS(); goto redraw; } /* * Treat a number equal to or greater than the number of pages * as the last page. - FM */ if (number >= npages) { if (window_offset >= ((num_choices - length) + 1)) { HTUserMsg(ALREADY_AT_OPTION_END); SHOW_POPUP_STATUS(); break; } window_offset = ((npages - 1) * length); if (window_offset > (num_choices - length)) { window_offset = (num_choices - length + 1); } if (cur_choice < window_offset) cur_choice = window_offset; SHOW_POPUP_STATUS(); goto redraw; } /* * We want an intermediate page. - FM */ if (((number - 1) * length) == window_offset) { char *msg = 0; HTSprintf0(&msg, ALREADY_AT_OPTION_PAGE, number); HTUserMsg(msg); FREE(msg); SHOW_POPUP_STATUS(); break; } cur_choice = window_offset = ((number - 1) * length); SHOW_POPUP_STATUS(); goto redraw; } /* * Check for a positive number, which signifies that a choice * should be sought. - FM */ if (number > 0) { /* * Decrement the number so as to correspond with our cur_choice * values. - FM */ number--; /* * If the number is in range and had no legal suffix, select * the indicated choice. - FM */ if (number <= num_choices && c == '\0') { cur_choice = number; cmd = LYK_ACTIVATE; break; } /* * Verify that we had a 'g' suffix, and act on the number. - * FM */ if (c == 'g') { if (cur_choice == number) { /* * The choice already is current. - FM */ char *msg = 0; HTSprintf0(&msg, OPTION_ALREADY_CURRENT, (number + 1)); HTUserMsg(msg); FREE(msg); SHOW_POPUP_STATUS(); break; } if (number <= num_choices) { /* * The number is in range and had a 'g' suffix, so make * it the current option, scrolling if needed. - FM */ j = (number - cur_choice); cur_choice = number; if ((j > 0) && (cur_choice - window_offset) >= length) { window_offset += j; if (window_offset > (num_choices - length + 1)) window_offset = (num_choices - length + 1); } else if ((cur_choice - window_offset) < 0) { window_offset -= abs(j); if (window_offset < 0) window_offset = 0; } SHOW_POPUP_STATUS(); goto redraw; } /* * Not in range. - FM */ HTUserMsg(BAD_OPTION_NUM_ENTERED); } } /* * Restore the popup statusline. - FM */ SHOW_POPUP_STATUS(); break; case LYK_PREV_LINK: case LYK_LPOS_PREV_LINK: case LYK_FASTBACKW_LINK: case LYK_UP_LINK: if (cur_choice > 0) cur_choice--; /* * Scroll the window up if necessary. */ if ((cur_choice - window_offset) < 0) { window_offset--; goto redraw; } break; case LYK_NEXT_LINK: case LYK_LPOS_NEXT_LINK: case LYK_FASTFORW_LINK: case LYK_DOWN_LINK: if (cur_choice < num_choices) cur_choice++; /* * Scroll the window down if necessary */ if ((cur_choice - window_offset) >= length) { window_offset++; goto redraw; } break; case LYK_NEXT_PAGE: /* * Okay, are we on the last page of the list? If not then, */ if (window_offset != (num_choices - length + 1)) { /* * Modify the current choice to not be a coordinate in the * list, but a coordinate on the item selected in the window. */ cur_choice -= window_offset; /* * Page down the proper length for the list. If simply to far, * back up. */ window_offset += length; if (window_offset > (num_choices - length)) { window_offset = (num_choices - length + 1); } /* * Readjust the current selection to be a list coordinate * rather than window. Redraw this thing. */ cur_choice += window_offset; goto redraw; } else if (cur_choice < num_choices) { /* * Already on last page of the list so just redraw it with the * last item selected. */ cur_choice = num_choices; } break; case LYK_PREV_PAGE: /* * Are we on the first page of the list? If not then, */ if (window_offset != 0) { /* * Modify the current selection to not be a list coordinate, * but a window coordinate. */ cur_choice -= window_offset; /* * Page up the proper length. If too far, back up. */ window_offset -= length; if (window_offset < 0) { window_offset = 0; } /* * Readjust the current choice. */ cur_choice += window_offset; goto redraw; } else if (cur_choice > 0) { /* * Already on the first page so just back up to the first item. */ cur_choice = 0; } break; case LYK_HOME: cur_choice = 0; if (window_offset > 0) { window_offset = 0; goto redraw; } break; case LYK_END: cur_choice = num_choices; if (window_offset != (num_choices - length + 1)) { window_offset = (num_choices - length + 1); goto redraw; } break; case LYK_DOWN_TWO: cur_choice += 2; if (cur_choice > num_choices) cur_choice = num_choices; /* * Scroll the window down if necessary. */ if ((cur_choice - window_offset) >= length) { window_offset += 2; if (window_offset > (num_choices - length + 1)) window_offset = (num_choices - length + 1); goto redraw; } break; case LYK_UP_TWO: cur_choice -= 2; if (cur_choice < 0) cur_choice = 0; /* * Scroll the window up if necessary. */ if ((cur_choice - window_offset) < 0) { window_offset -= 2; if (window_offset < 0) window_offset = 0; goto redraw; } break; case LYK_DOWN_HALF: cur_choice += (length / 2); if (cur_choice > num_choices) cur_choice = num_choices; /* * Scroll the window down if necessary. */ if ((cur_choice - window_offset) >= length) { window_offset += (length / 2); if (window_offset > (num_choices - length + 1)) window_offset = (num_choices - length + 1); goto redraw; } break; case LYK_UP_HALF: cur_choice -= (length / 2); if (cur_choice < 0) cur_choice = 0; /* * Scroll the window up if necessary. */ if ((cur_choice - window_offset) < 0) { window_offset -= (length / 2); if (window_offset < 0) window_offset = 0; goto redraw; } break; case LYK_REFRESH: lynx_force_repaint(); LYrefresh(); break; case LYK_NEXT: if (recall && isBEmpty(next_target)) { /* * We got a 'n'ext command with no prior query specified within * the popup window. See if one was entered when the popup was * retracted, and if so, assume that's what's wanted. Note * that it will become the default within popups, unless * another is entered within a popup. If the within popup * default is to be changed at that point, use WHEREIS ('/') * and enter it, or the up- or down-arrow keys to seek any of * the previously entered queries, regardless of whether they * were entered within or outside of a popup window. - FM */ if ((cp = (char *) HTList_objectAt(search_queries, 0)) != NULL) { BStrCopy0(next_target, cp); QueryNum = 0; FirstRecall = FALSE; } } BStrCopy(prev_target, next_target); /* FALLTHRU */ case LYK_WHEREIS: if (isBEmpty(prev_target)) { _statusline(ENTER_WHEREIS_QUERY); if ((ch = LYgetBString(&prev_target, FALSE, 0, recall)) < 0) { /* * User cancelled the search via ^G. - FM */ HTInfoMsg(CANCELLED); goto restore_popup_statusline; } } check_recall: if (isBEmpty(prev_target) && !(recall && (ch == UPARROW_KEY || ch == DNARROW_KEY))) { /* * No entry. Simply break. - FM */ HTInfoMsg(CANCELLED); goto restore_popup_statusline; } if (recall && ch == UPARROW_KEY) { if (FirstRecall) { /* * Use the current string or last query in the list. - FM */ FirstRecall = FALSE; if (!isBEmpty(next_target)) { for (QueryNum = (QueryTotal - 1); QueryNum > 0; QueryNum--) { if ((cp = (char *) HTList_objectAt(search_queries, QueryNum)) != NULL && !strcmp(next_target->str, cp)) { break; } } } else { QueryNum = 0; } } else { /* * Go back to the previous query in the list. - FM */ QueryNum++; } if (QueryNum >= QueryTotal) { /* * Roll around to the last query in the list. - FM */ QueryNum = 0; } if ((cp = (char *) HTList_objectAt(search_queries, QueryNum)) != NULL) { BStrCopy0(prev_target, cp); if (!isBEmpty(next_target) && !strcmp(next_target->str, prev_target->str)) { _statusline(EDIT_CURRENT_QUERY); } else if ((!isBEmpty(next_target) && QueryTotal == 2) || (isBEmpty(next_target) && QueryTotal == 1)) { _statusline(EDIT_THE_PREV_QUERY); } else { _statusline(EDIT_A_PREV_QUERY); } if ((ch = LYgetBString(&prev_target, FALSE, 0, recall)) < 0) { /* * User cancelled the search via ^G. - FM */ HTInfoMsg(CANCELLED); goto restore_popup_statusline; } goto check_recall; } } else if (recall && ch == DNARROW_KEY) { if (FirstRecall) { /* * Use the current string or first query in the list. - FM */ FirstRecall = FALSE; if (!isBEmpty(next_target)) { for (QueryNum = 0; QueryNum < (QueryTotal - 1); QueryNum++) { if ((cp = (char *) HTList_objectAt(search_queries, QueryNum)) != NULL && !strcmp(next_target->str, cp)) { break; } } } else { QueryNum = (QueryTotal - 1); } } else { /* * Advance to the next query in the list. - FM */ QueryNum--; } if (QueryNum < 0) { /* * Roll around to the first query in the list. - FM */ QueryNum = (QueryTotal - 1); } if ((cp = (char *) HTList_objectAt(search_queries, QueryNum)) != NULL) { BStrCopy0(prev_target, cp); if (isBEmpty(next_target) && !strcmp(next_target->str, prev_target->str)) { _statusline(EDIT_CURRENT_QUERY); } else if ((!isBEmpty(next_target) && QueryTotal == 2) || (isBEmpty(next_target) && QueryTotal == 1)) { _statusline(EDIT_THE_PREV_QUERY); } else { _statusline(EDIT_A_PREV_QUERY); } if ((ch = LYgetBString(&prev_target, FALSE, 0, recall)) < 0) { /* * User cancelled the search via ^G. - FM */ HTInfoMsg(CANCELLED); goto restore_popup_statusline; } goto check_recall; } } /* * Replace the search string buffer with the new target. - FM */ BStrCopy(next_target, prev_target); HTAddSearchQuery(next_target->str); /* * Start search at the next choice. - FM */ for (j = 1; Cptr[i + j] != NULL; j++) { FormatChoiceNum(buffer, max_choices, (i + j), Cptr[i + j]); if (LYcase_sensitive) { if (strstr(buffer, next_target->str) != NULL) break; } else { if (LYstrstr(buffer, next_target->str) != NULL) break; } } if (Cptr[i + j] != NULL) { /* * We have a hit, so make that choice the current. - FM */ cur_choice += j; /* * Scroll the window down if necessary. */ if ((cur_choice - window_offset) >= length) { window_offset += j; if (window_offset > (num_choices - length + 1)) window_offset = (num_choices - length + 1); ReDraw = TRUE; } goto restore_popup_statusline; } /* * If we started at the beginning, it can't be present. - FM */ if (cur_choice == 0) { HTUserMsg2(STRING_NOT_FOUND, next_target->str); goto restore_popup_statusline; } /* * Search from the beginning to the current choice. - FM */ for (j = 0; j < cur_choice; j++) { FormatChoiceNum(buffer, max_choices, (j + 1), Cptr[j]); if (LYcase_sensitive) { if (strstr(buffer, next_target->str) != NULL) break; } else { if (LYstrstr(buffer, next_target->str) != NULL) break; } } if (j < cur_choice) { /* * We have a hit, so make that choice the current. - FM */ j = (cur_choice - j); cur_choice -= j; /* * Scroll the window up if necessary. */ if ((cur_choice - window_offset) < 0) { window_offset -= j; if (window_offset < 0) window_offset = 0; ReDraw = TRUE; } goto restore_popup_statusline; } /* * Didn't find it in the preceding choices either. - FM */ HTUserMsg2(STRING_NOT_FOUND, next_target->str); restore_popup_statusline: /* * Restore the popup statusline and reset the search variables. - * FM */ SHOW_POPUP_STATUS(); BStrCopy0(prev_target, ""); QueryTotal = (search_queries ? HTList_count(search_queries) : 0); recall = ((QueryTotal >= 1) ? RECALL_URL : NORECALL); QueryNum = QueryTotal; if (ReDraw == TRUE) { ReDraw = FALSE; goto redraw; } break; case LYK_QUIT: case LYK_ABORT: case LYK_PREV_DOC: case LYK_INTERRUPT: cur_choice = orig_choice; cmd = LYK_ACTIVATE; /* to exit */ break; } } LYstopPopup(); return (disabled ? orig_choice : cur_choice); } /* * Allow the user to edit a string. */ int LYgetBString(bstring **inputline, int hidden, unsigned max_cols, RecallType recall) { int x, y; int ch; int xlec = -2; int last_xlec = -1; int last_xlkc = -1; FieldEditor MyEdit, *edit = &MyEdit; #ifdef SUPPORT_MULTIBYTE_EDIT BOOL refresh_mb = TRUE; #endif /* SUPPORT_MULTIBYTE_EDIT */ BOOL done = FALSE; int result = -1; CTRACE((tfp, "called LYgetBString hidden %d, recall %d\n", hidden, (int) recall)); LYGetYX(y, x); /* Use screen from cursor position to eol */ (void) y; (void) x; if (*inputline == NULL) /* caller may not have initialized this */ BStrCopy0(*inputline, ""); LYSetupEdit(edit, (*inputline)->str, max_cols, LYcolLimit - x); IsHidden = (BOOL) hidden; #ifdef FEPCTRL fep_on(); #endif while (!done) { beginning: #ifndef SUPPORT_MULTIBYTE_EDIT LYRefreshEdit(edit); #else /* SUPPORT_MULTIBYTE_EDIT */ if (refresh_mb) LYRefreshEdit(edit); #endif /* SUPPORT_MULTIBYTE_EDIT */ ch = LYReadCmdKey(FOR_PROMPT); #ifdef SUPPORT_MULTIBYTE_EDIT #ifdef CJK_EX /* for SJIS code */ if (!refresh_mb && (EditBinding(ch) != LYE_CHAR)) goto beginning; #else if (!refresh_mb && (EditBinding(ch) != LYE_CHAR) && (EditBinding(ch) != LYE_AIX)) goto beginning; #endif #endif /* SUPPORT_MULTIBYTE_EDIT */ if (term_letter || term_options #ifdef VMS || HadVMSInterrupt #endif /* VMS */ #ifndef DISABLE_NEWS || term_message #endif ) { #ifdef VMS HadVMSInterrupt = FALSE; #endif /* VMS */ ch = LYCharINTERRUPT2; } if (recall != NORECALL && (ch == UPARROW_KEY || ch == DNARROW_KEY)) { BStrCopy0(*inputline, Buffer); LYAddToCloset(recall, Buffer); CTRACE((tfp, "LYgetstr(%s) recall\n", (*inputline)->str)); #ifdef FEPCTRL fep_off(); #endif LYFinishEdit(edit); result = ch; done = TRUE; continue; } ch |= InputMods; InputMods = 0; if (last_xlkc != -1) { if (ch == last_xlkc) ch |= LKC_MOD3; last_xlkc = -1; /* consumed */ } #ifndef WIN_EX if (LKC_TO_LAC(keymap, ch) == LYK_REFRESH) goto beginning; #endif last_xlec = xlec; xlec = EditBinding(ch); if ((xlec & LYE_DF) && !(xlec & LYE_FORM_LAC)) { last_xlkc = ch; xlec &= ~LYE_DF; } else { last_xlkc = -1; } switch (xlec) { case LYE_SETM1: InputMods |= LKC_MOD1; break; case LYE_SETM2: InputMods |= LKC_MOD2; break; case LYE_TAB: if (xlec == last_xlec && recall != NORECALL) { HTList *list = whichRecall(recall); if (!HTList_isEmpty(list)) { char **data = sortedList(list, (BOOL) (recall == RECALL_CMD)); int old_y, old_x; int cur_choice = 0; int num_options = LYarrayLength((STRING2PTR) data); while (cur_choice < num_options && strcasecomp(data[cur_choice], Buffer) < 0) cur_choice++; LYGetYX(old_y, old_x); cur_choice = LYhandlePopupList(cur_choice, 0, old_x, (STRING2PTR) data, -1, -1, FALSE, FALSE); if (cur_choice >= 0) { if (recall == RECALL_CMD) _statusline(": "); reinsertEdit(edit, data[cur_choice]); } LYmove(old_y, old_x); FREE(data); } } else { reinsertEdit(edit, LYFindInCloset(recall, Buffer)); } break; #ifndef CJK_EX case LYE_AIX: /* * Handle CJK characters, or as a valid character in the current * display character set. Otherwise, we treat this as LYE_ENTER. */ if (ch != '\t' && (IS_CJK_TTY || LYlowest_eightbit[current_char_set] <= 0x97)) { LYLineEdit(edit, ch, FALSE); break; } #endif /* FALLTHRU */ case LYE_ENTER: BStrCopy0(*inputline, Buffer); if (!hidden) LYAddToCloset(recall, Buffer); CTRACE((tfp, "LYgetstr(%s) LYE_ENTER\n", (*inputline)->str)); #ifdef FEPCTRL fep_off(); #endif LYFinishEdit(edit); result = ch; done = TRUE; continue; #ifdef CAN_CUT_AND_PASTE case LYE_PASTE: { unsigned char *s = (unsigned char *) get_clip_grab(), *e; size_t len; if (!s) break; len = strlen((const char *) s); e = s + len; if (len != 0) { unsigned char *e1 = s; while (e1 < e) { if (*e1 < ' ') { /* Stop here? */ if (e1 > s) LYEditInsert(edit, s, (int) (e1 - s), map_active, TRUE); s = e1; if (*e1 == '\t') { /* Replace by space */ LYEditInsert(edit, (unsigned const char *) " ", 1, map_active, TRUE); s = ++e1; } else { break; } } else { ++e1; } } if (e1 > s) { LYEditInsert(edit, s, (int) (e1 - s), map_active, TRUE); } } get_clip_release(); break; } #endif case LYE_ABORT: CTRACE((tfp, "LYgetstr LYE_ABORT\n")); #ifdef FEPCTRL fep_off(); #endif LYFinishEdit(edit); BStrCopy0(*inputline, ""); done = TRUE; continue; case LYE_STOP: CTRACE((tfp, "LYgetstr LYE_STOP\n")); #ifdef TEXTFIELDS_MAY_NEED_ACTIVATION textfields_need_activation = TRUE; LYFinishEdit(edit); BStrCopy0(*inputline, ""); done = TRUE; continue; #else #ifdef ENHANCED_LINEEDIT disableEditMark(); #endif break; #endif case LYE_LKCMD: /* * Used only in form_getstr() for invoking the LYK_F_LINK_NUM * prompt when in form text fields. - FM */ break; case LYE_FORM_PASS: /* * Used in form_getstr() to end line editing and pass on the input * char/lynxkeycode. Here it is just ignored. - kw */ break; default: if (xlec & LYE_FORM_LAC) { /* * Used in form_getstr() to end line editing and pass on the * lynxkeycode already containing a lynxactioncode. Here it is * just ignored. - kw */ break; } #ifndef SUPPORT_MULTIBYTE_EDIT LYLineEdit(edit, ch, FALSE); #else /* SUPPORT_MULTIBYTE_EDIT */ if (LYLineEdit(edit, ch, FALSE) == 0) { if (refresh_mb && IS_CJK_TTY && (0x81 <= ch) && (ch <= 0xfe)) refresh_mb = FALSE; else refresh_mb = TRUE; } else { if (!refresh_mb) { LYDoEdit(edit, 0, LYE_DELP, FALSE); } } #endif /* SUPPORT_MULTIBYTE_EDIT */ } } return result; } /* * Use this for fixed-buffer edits which have not been converted to use * LYgetBString(). */ int LYgetstr(char *inputline, /* fixed-size buffer for input/output */ int hidden, /* true to suppress from command-history */ unsigned bufsize, /* sizeof(inputline) */ RecallType recall) /* type of command-history */ { int ch; bstring *my_bstring = NULL; BStrCopy0(my_bstring, inputline); if (my_bstring != 0) { ch = LYgetBString(&my_bstring, hidden, bufsize, recall); if (ch >= 0 && my_bstring != 0) LYStrNCpy(inputline, my_bstring->str, bufsize); BStrFree(my_bstring); } else { ch = -1; } return ch; } const char *LYLineeditHelpURL(void) { static int lasthelp_lineedit = -1; static char helpbuf[LY_MAXPATH] = "\0"; static char *phelp = &helpbuf[0]; const char *result = NULL; if (lasthelp_lineedit == current_lineedit) { result = helpbuf; } else { const char *source = LYLineeditHelpURLs[current_lineedit]; size_t available; if (lasthelp_lineedit == -1) { LYStrNCpy(helpbuf, helpfilepath, sizeof(helpbuf) - 1); phelp += strlen(helpbuf); } available = (sizeof(helpbuf) - (size_t) (phelp - helpbuf)); if (non_empty(source) && (strlen(source) <= available)) { LYStrNCpy(phelp, source, available); lasthelp_lineedit = current_lineedit; result = helpbuf; } } return result; } /* * Wrapper for sscanf to ensure that lynx can "always" read a POSIX float. * In some locales, the decimal point changes. */ int LYscanFloat2(const char **source, float *result) { int count = 0; char *temp; const char *src = *source; src = LYSkipCBlanks(src); *result = 0.0; if (StrChr(src, '.') != 0) { long frc_part = 0; float scale = 1.0; if (*src != '.') { temp = NULL; frc_part = strtol(src, &temp, 10); *result = (float) frc_part; src = temp; } if (src != 0 && *src == '.') { ++src; if (isdigit(UCH(*src))) { temp = NULL; frc_part = strtol(src, &temp, 10); if (temp != 0) { int digits = (int) (temp - src); while (digits-- > 0) scale *= (float) 10.0; *result += ((float) frc_part / scale); } src = temp; } } if (src != 0 && *src != '\0' && StrChr(" \t+", *src) == 0) { char *extra = (char *) malloc(2 + strlen(src)); if (extra != 0) { extra[0] = '1'; strcpy(extra + 1, src); if (sscanf(extra, "%f", &scale) == 1) { *result *= scale; } FREE(extra); src = LYSkipCNonBlanks(src); } else { src = 0; } } if (src != 0) count = 1; } else { count = sscanf(src, "%f", result); src = LYSkipCNonBlanks(src); } CTRACE2(TRACE_CFG, (tfp, "LYscanFloat \"%s\" -> %f (%s)\n", *source, *result, count ? "ok" : "error")); *source = src; return count; } int LYscanFloat(const char *source, float *result) { const char *temp = source; return LYscanFloat2(&temp, result); } /* * A replacement for 'strsep()' */ char *LYstrsep(char **stringp, const char *delim) { char *marker; char *result = 0; if (non_empty(stringp)) { result = *stringp; /* will return the old value */ marker = strpbrk(*stringp, delim); if (marker) { *marker = '\0'; /* terminate the substring */ *stringp = ++marker; /* point to the next substring */ } else { *stringp = 0; /* this was the last */ } } return result; } /* * LYstrstr finds the first occurrence of the string pointed to by needle * in the string pointed to by haystack. * * It returns NULL if the string is not found. * * It is a case insensitive search. */ char *LYstrstr(char *haystack, const char *needle) { int len = (int) strlen(needle); char *result = NULL; for (; *haystack != '\0'; haystack++) { if (0 == UPPER8(*haystack, *needle)) { if (0 == strncasecomp8(haystack + 1, needle + 1, len - 1)) { result = haystack; break; } } } return (result); } #define SkipSpecialChars(p) \ while (IsSpecialAttrChar(*p) && *p != '\0') \ p++ /* * LYno_attr_char_case_strstr finds the first occurrence of the * string pointed to by needle in the string pointed to by haystack. * * It ignores special characters, e.g., LY_UNDERLINE_START_CHAR in haystack. * * It is a case insensitive search. */ const char *LYno_attr_char_case_strstr(const char *haystack, const char *needle) { const char *refptr, *tstptr; const char *result = NULL; if (haystack != NULL && needle != NULL) { SkipSpecialChars(haystack); for (; *haystack != '\0' && (result == NULL); haystack++) { if (0 == UPPER8(*haystack, *needle)) { refptr = haystack + 1; tstptr = needle + 1; if (*tstptr == '\0') { result = haystack; break; } while (1) { if (!IsSpecialAttrChar(*refptr)) { if (0 != UPPER8(*refptr, *tstptr)) break; refptr++; tstptr++; } else { refptr++; } if (*tstptr == '\0') { result = haystack; break; } if (*refptr == '\0') break; } } } } return (result); } /* * LYno_attr_char_strstr finds the first occurrence of the * string pointed to by needle in the string pointed to by haystack. * It ignores special characters, e.g., LY_UNDERLINE_START_CHAR in haystack. * * It is a case sensitive search. */ const char *LYno_attr_char_strstr(const char *haystack, const char *needle) { const char *refptr, *tstptr; const char *result = NULL; if (haystack != NULL && needle != NULL) { SkipSpecialChars(haystack); for (; *haystack != '\0' && (result == NULL); haystack++) { if ((*haystack) == (*needle)) { refptr = haystack + 1; tstptr = needle + 1; if (*tstptr == '\0') { result = haystack; break; } while (1) { if (!IsSpecialAttrChar(*refptr)) { if ((*refptr) != (*tstptr)) break; refptr++; tstptr++; } else { refptr++; } if (*tstptr == '\0') { result = haystack; break; } else if (*refptr == '\0') { break; } } } } } return (result); } /* * LYno_attr_mbcs_case_strstr finds the first occurrence of the string pointed * to by needle in the string pointed to by haystack. It takes account of * MultiByte Character Sequences (UTF8). The physical lengths of the displayed * string up to the start and end (= next position after) of the target string * are returned in *nstartp and *nendp if the search is successful. * * These lengths count glyph cells if count_gcells is set. (Full-width * characters in CJK mode count as two.) Normally that's what we want. They * count actual glyphs if count_gcells is unset. (Full-width characters in CJK * mode count as one.) * * It ignores special characters, e.g., LY_UNDERLINE_START_CHAR in haystack. * * It assumes UTF8 if utf_flag is set. * * It is a case insensitive search. */ const char *LYno_attr_mbcs_case_strstr(const char *haystack, const char *needle, int utf_flag, int count_gcells, int *nstartp, int *nendp) { const char *refptr; const char *tstptr; int len = 0; int offset; const char *result = NULL; if (haystack != NULL && needle != NULL) { SkipSpecialChars(haystack); for (; *haystack != '\0' && (result == NULL); haystack++) { if ((!utf_flag && IS_CJK_TTY && is8bits(*haystack) && *haystack == *needle && IsNormalChar(*(haystack + 1))) || (0 == UPPER8(*haystack, *needle))) { int tarlen = 0; offset = len; len++; refptr = (haystack + 1); tstptr = (needle + 1); if (*tstptr == '\0') { if (nstartp) *nstartp = offset; if (nendp) *nendp = len; result = haystack; break; } if (!utf_flag && IS_CJK_TTY && is8bits(*haystack) && *haystack == *needle && IsNormalChar(*refptr)) { /* handle a CJK multibyte string */ if (*refptr == *tstptr) { refptr++; tstptr++; if (count_gcells) tarlen++; if (*tstptr == '\0') { if (nstartp) *nstartp = offset; if (nendp) *nendp = len + tarlen; result = haystack; break; } } else { /* not a match */ haystack++; if (count_gcells) len++; continue; } } /* compare the remainder of the string */ while (1) { if (!IsSpecialAttrChar(*refptr)) { if (!utf_flag && IS_CJK_TTY && is8bits(*refptr)) { if (*refptr == *tstptr && *(refptr + 1) == *(tstptr + 1) && !IsSpecialAttrChar(*(refptr + 1))) { refptr++; tstptr++; if (count_gcells) tarlen++; } else { break; } } else if (0 != UPPER8(*refptr, *tstptr)) { break; } if (!IS_UTF_EXTRA(*tstptr)) { tarlen++; } refptr++; tstptr++; } else { refptr++; } if (*tstptr == '\0') { if (nstartp) *nstartp = offset; if (nendp) *nendp = len + tarlen; result = haystack; break; } if (*refptr == '\0') break; } } else if (!(IS_UTF_EXTRA(*haystack) || IsSpecialAttrChar(*haystack))) { if (!utf_flag && IS_CJK_TTY && is8bits(*haystack) && IsNormalChar(*(haystack + 1))) { haystack++; if (count_gcells) len++; } len++; } } } return (result); } /* * LYno_attr_mbcs_strstr finds the first occurrence of the string pointed * to by needle in the string pointed to by haystack. * * It takes account of CJK and MultiByte Character Sequences (UTF8). The * physical lengths of the displayed string up to the start and end (= next * position after) the target string are returned in *nstartp and *nendp if the * search is successful. * * These lengths count glyph cells if count_gcells is set. (Full-width * characters in CJK mode count as two.) Normally that's what we want. They * count actual glyphs if count_gcells is unset. (Full-width characters in CJK * mode count as one.) * * It ignores special characters, e.g., LY_UNDERLINE_START_CHAR in haystack. * * It assumes UTF8 if utf_flag is set. * * It is a case sensitive search. */ const char *LYno_attr_mbcs_strstr(const char *haystack, const char *needle, int utf_flag, int count_gcells, int *nstartp, int *nendp) { const char *refptr; const char *tstptr; int len = 0; int offset; const char *result = NULL; if (haystack != NULL && needle != NULL) { SkipSpecialChars(haystack); for (; *haystack != '\0' && (result == NULL); haystack++) { if ((*haystack) == (*needle)) { int tarlen = 0; offset = len; len++; refptr = (haystack + 1); tstptr = (needle + 1); if (*tstptr == '\0') { if (nstartp) *nstartp = offset; if (nendp) *nendp = len; result = haystack; break; } else if (!utf_flag && IS_CJK_TTY && is8bits(*haystack) && IsNormalChar(*refptr)) { /* handle a CJK multibyte string */ if (*refptr == *tstptr) { /* found match */ refptr++; tstptr++; if (count_gcells) tarlen++; if (*tstptr == '\0') { if (nstartp) *nstartp = offset; if (nendp) *nendp = len + tarlen; result = haystack; break; } } else { /* not a match - restart comparison */ haystack++; if (count_gcells) len++; continue; } } /* compare the remainder of the string */ while (1) { if (!IsSpecialAttrChar(*refptr)) { if (!utf_flag && IS_CJK_TTY && is8bits(*refptr)) { if (*refptr == *tstptr && *(refptr + 1) == *(tstptr + 1) && !IsSpecialAttrChar(*(refptr + 1))) { refptr++; tstptr++; if (count_gcells) tarlen++; } else { break; } } else if ((*refptr) != (*tstptr)) { break; } if (!IS_UTF_EXTRA(*tstptr)) { tarlen++; } refptr++; tstptr++; } else { refptr++; } if (*tstptr == '\0') { if (nstartp) *nstartp = offset; if (nendp) *nendp = len + tarlen; result = haystack; break; } if (*refptr == '\0') break; } } else if (!(IS_UTF_EXTRA(*haystack) || IsSpecialAttrChar(*haystack))) { if (!utf_flag && IS_CJK_TTY && is8bits(*haystack) && IsNormalChar(*(haystack + 1))) { haystack++; if (count_gcells) len++; } len++; } } } return (result); } /* * Allocate and return a copy of a string. * see StrAllocCopy */ char *SNACopy(char **target, const char *source, size_t n) { FREE(*target); if (source) { *target = typeMallocn(char, n + 1); if (*target == NULL) { CTRACE((tfp, "Tried to malloc %lu bytes\n", (unsigned long) n)); outofmem(__FILE__, "SNACopy"); } LYStrNCpy(*target, source, n); } return *target; } /* * Combinate string allocation and concatenation. * see StrAllocCat */ char *SNACat(char **target, const char *source, size_t n) { if (non_empty(source)) { if (*target) { size_t length = strlen(*target); *target = typeRealloc(char, *target, length + n + 1); if (*target == NULL) outofmem(__FILE__, "SNACat"); LYStrNCpy(*target + length, source, n); } else { *target = typeMallocn(char, n + 1); if (*target == NULL) outofmem(__FILE__, "SNACat"); MemCpy(*target, source, n); (*target)[n] = '\0'; /* terminate */ } } return *target; } #include /* * Returns lowercase equivalent for unicode, * transparent output if no equivalent found. */ static long UniToLowerCase(long upper) { size_t i, high, low; long diff = 0; long result = upper; if (upper > 0) { /* * Try unicode_to_lower_case[]. */ low = 0; high = TABLESIZE(unicode_to_lower_case); while (low < high) { /* * Binary search. */ i = (low + (high - low) / 2); diff = (unicode_to_lower_case[i].upper - upper); if (diff < 0) { low = i + 1; } else if (diff > 0) { high = i; } else if (diff == 0) { result = unicode_to_lower_case[i].lower; break; } } } return result; } /* * UPPER8 ? * it was "TOUPPER(a) - TOUPPER(b)" in its previous life... * * It was realized that case-insensitive user search * got information about upper/lower mapping from TOUPPER * (precisely from "(TOUPPER(a) - TOUPPER(b))==0") * and depends on locale in its 8bit mapping. - * Usually fails with DOS/WINDOWS display charsets * as well as on non-UNIX systems. * * So use unicode case mapping. */ int UPPER8(int ch1, int ch2) { int result = 0; if (ch1 == ch2) { result = 0; } else if (!ch2) { result = UCH(ch1); } else if (!ch1) { result = -UCH(ch2); } else if (UCH(TOASCII(ch1)) < 128 && UCH(TOASCII(ch2)) < 128) { /* case-insensitive match for us-ascii */ result = (TOUPPER(ch1) - TOUPPER(ch2)); } else if (UCH(TOASCII(ch1)) > 127 && UCH(TOASCII(ch2)) > 127) { /* case-insensitive match for upper half */ if (DisplayCharsetMatchLocale) { result = (TOUPPER(ch1) - TOUPPER(ch2)); /* old-style */ } else { long uni_ch2 = UCTransToUni((char) ch2, current_char_set); long uni_ch1; if (uni_ch2 < 0) { result = UCH(ch1); } else { uni_ch1 = UCTransToUni((char) ch1, current_char_set); result = (int) (UniToLowerCase(uni_ch1) - UniToLowerCase(uni_ch2)); } } } else { result = -10; /* mismatch */ } return result; } /* * Replaces 'fgets()' calls into a fixed-size buffer with reads into a buffer * that is allocated. When an EOF or error is found, the buffer is freed * automatically. */ char *LYSafeGets(char **target, FILE *fp) { char buffer[BUFSIZ]; char *result = 0; if (target != 0) result = *target; if (result != 0) *result = 0; while (fgets(buffer, (int) sizeof(buffer), fp) != NULL) { if (*buffer) result = StrAllocCat(result, buffer); if (StrChr(buffer, '\n') != 0) break; } if (ferror(fp)) { FREE(result); } else if (feof(fp) && result && *result == '\0') { /* * If the file ends in the middle of a line, return the partial line; * if another call is made after this, it will return NULL. - kw */ FREE(result); } if (target != 0) *target = result; return result; } #ifdef USE_CMD_LOGGING static FILE *cmd_logfile; static FILE *cmd_script; void LYOpenCmdLogfile(int argc, char **argv) { int n; if (non_empty(lynx_cmd_logfile)) { cmd_logfile = LYNewTxtFile(lynx_cmd_logfile); if (cmd_logfile != 0) { fprintf(cmd_logfile, "# Command logfile created by %s %s (%s)\n", LYNX_NAME, LYNX_VERSION, LYVersionDate()); for (n = 0; n < argc; n++) { fprintf(cmd_logfile, "# Arg%d = %s\n", n, argv[n]); } } } } BOOL LYHaveCmdScript(void) { return (BOOL) (cmd_script != 0); } void LYOpenCmdScript(void) { if (non_empty(lynx_cmd_script)) { cmd_script = fopen(lynx_cmd_script, TXT_R); CTRACE((tfp, "LYOpenCmdScript(%s) %s\n", lynx_cmd_script, cmd_script != 0 ? "SUCCESS" : "FAIL")); } } int LYReadCmdKey(int mode) { int ch = -1; if (cmd_script != 0) { char *buffer = 0; char *src; char *tmp; while ((ch < 0) && LYSafeGets(&buffer, cmd_script) != 0) { LYTrimTrailing(buffer); src = LYSkipBlanks(buffer); tmp = LYSkipNonBlanks(src); switch ((unsigned) (tmp - src)) { case 4: if (!strncasecomp(src, "exit", 4)) exit_immediately(EXIT_SUCCESS); break; case 3: if (!strncasecomp(src, "key", 3)) { ch = LYStringToKeycode(LYSkipBlanks(tmp)); } else if (!strncasecomp(src, "set", 3)) { src = LYSkipBlanks(tmp); tmp = src; while (*tmp != '\0') { if (isspace(UCH(*tmp)) || *tmp == '=') break; ++tmp; } if (*tmp != '\0') { *tmp++ = '\0'; tmp = LYSkipBlanks(tmp); } if (LYSetConfigValue(src, tmp)) { CTRACE((tfp, "LYSetConfigValue(%s, %s)\n", src, tmp)); } else if (LYsetRcValue(src, tmp)) { CTRACE((tfp, "LYsetRcValue(%s, %s)\n", src, tmp)); } else { CTRACE((tfp, "?? set ignored %s\n", src)); } } break; } } if (feof(cmd_script)) { fclose(cmd_script); cmd_script = 0; } if (ch >= 0) { LYSleepReplay(); LYrefresh(); } FREE(buffer); } else { ch = LYgetch_for(mode); } CTRACE((tfp, "LYReadCmdKey(%d) ->%s (%#x)\n", mode, LYKeycodeToString(ch, TRUE), ch)); LYWriteCmdKey(ch); return ch; } /* * Write a LYKeymapCode 'ch' to the logfile. */ void LYWriteCmdKey(int ch) { if (cmd_logfile != 0) { fprintf(cmd_logfile, "key %s\n", LYKeycodeToString(ch, FALSE)); } } void LYCloseCmdLogfile(void) { if (cmd_logfile != 0) { LYCloseOutput(cmd_logfile); cmd_logfile = 0; } if (cmd_script != 0) { LYCloseInput(cmd_script); cmd_script = 0; } FREE(lynx_cmd_logfile); FREE(lynx_cmd_script); } #endif /* USE_CMD_LOGGING */