diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:37:15 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:37:15 +0000 |
commit | ae5d181b854d3ccb373b6bc01b4869e44ff4d87a (patch) | |
tree | 91f59efb48c56a84cc798e012fccb667b63d3fee /src/LYBookmark.c | |
parent | Initial commit. (diff) | |
download | lynx-upstream.tar.xz lynx-upstream.zip |
Adding upstream version 2.9.0dev.12.upstream/2.9.0dev.12upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/LYBookmark.c | 1169 |
1 files changed, 1169 insertions, 0 deletions
diff --git a/src/LYBookmark.c b/src/LYBookmark.c new file mode 100644 index 0000000..ee88cf9 --- /dev/null +++ b/src/LYBookmark.c @@ -0,0 +1,1169 @@ +/* + * $LynxId: LYBookmark.c,v 1.88 2023/01/07 16:09:53 tom Exp $ + */ +#include <HTUtils.h> +#include <HTAlert.h> +#include <HTFile.h> +#include <LYUtils.h> +#include <LYStrings.h> +#include <LYBookmark.h> +#include <LYGlobalDefs.h> +#include <LYClean.h> +#include <LYKeymap.h> +#include <LYCharUtils.h> /* need for META charset */ +#include <UCAux.h> +#include <LYCharSets.h> /* need for LYHaveCJKCharacterSet */ +#include <LYCurses.h> +#include <GridText.h> +#include <HTCJK.h> + +#ifdef _WINDOWS +#include <io.h> /* for _chsize() */ +#endif + +#ifdef VMS +#include <nam.h> +#endif /* VMS */ + +#include <LYLeaks.h> + +char *MBM_A_subbookmark[MBM_V_MAXFILES + 1]; +char *MBM_A_subdescript[MBM_V_MAXFILES + 1]; + +static BOOLEAN is_mosaic_hotlist = FALSE; +static const char *convert_mosaic_bookmark_file(const char *filename_buffer); + +unsigned LYindex2MBM(int n) +{ + static char MBMcodes[MBM_V_MAXFILES + 2] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + return n >= 0 && n <= MBM_V_MAXFILES ? UCH(MBMcodes[n]) : UCH('?'); +} + +int LYMBM2index(int ch) +{ + if ((ch = TOUPPER(ch)) > 0) { + const char *letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const char *result = StrChr(letters, ch); + + if (result != 0 + && (result - letters) <= MBM_V_MAXFILES) + return (int) (result - letters); + } + return -1; +} + +static void show_bookmark_not_defined(void) +{ + char *string_buffer = 0; + + HTSprintf0(&string_buffer, + BOOKMARK_FILE_NOT_DEFINED, + key_for_func(LYK_OPTIONS)); + LYMBM_statusline(string_buffer); + FREE(string_buffer); +} + +/* + * Tries to open a bookmark file for reading, which may be the default, or + * based on offering the user a choice from the MBM_A_subbookmark[] array. If + * successful the file is closed, and the filename in system path specs is + * returned, the URL is allocated into *URL, and the MBM_A_subbookmark[] + * filepath is allocated into the BookmarkPage global. Returns a zero-length + * pointer to flag a cancel, or a space to flag an undefined selection, without + * allocating into *URL or BookmarkPage. Returns NULL with allocating into + * BookmarkPage but not *URL is the selection is valid but the file doesn't yet + * exist. - FM + */ +const char *get_bookmark_filename(char **URL) +{ + static char filename_buffer[LY_MAXPATH]; + char *string_buffer = 0; + FILE *fp; + int MBM_tmp; + + /* + * Multi_Bookmarks support. - FMG & FM + * Let user select a bookmark file. + */ + MBM_tmp = select_multi_bookmarks(); + if (MBM_tmp == -2) + /* + * Zero-length pointer flags a cancel. - FM + */ + return (""); + if (MBM_tmp == -1) { + show_bookmark_not_defined(); + /* + * Space flags an undefined selection. - FMG + */ + return (" "); + } else { + /* + * Save the filepath as a global. The system path will be loaded into + * to the (static) filename_buffer as the return value, the URL will be + * allocated into *URL, and we also need the filepath available to + * calling functions. This is all pitifully non-reentrant, a la the + * original Lynx, and should be redesigned someday. - FM + */ + StrAllocCopy(BookmarkPage, MBM_A_subbookmark[MBM_tmp]); + } + + /* + * Seek it in the home path. - FM + */ + LYAddPathToHome(filename_buffer, + sizeof(filename_buffer), + BookmarkPage); + CTRACE((tfp, "\nget_bookmark_filename: SEEKING %s\n AS %s\n\n", + BookmarkPage, filename_buffer)); + if ((fp = fopen(filename_buffer, TXT_R)) != NULL) { + /* + * We now have the file open. + * Check if it is a mosaic hotlist. + */ + if (LYSafeGets(&string_buffer, fp) != 0 + && *LYTrimNewline(string_buffer) != '\0' + && !StrNCmp(string_buffer, "ncsa-xmosaic-hotlist-format-1", 29)) { + const char *newname; + + /* + * It is a mosaic hotlist file. + */ + is_mosaic_hotlist = TRUE; + newname = convert_mosaic_bookmark_file(filename_buffer); + LYLocalFileToURL(URL, newname); + } else { + is_mosaic_hotlist = FALSE; + LYLocalFileToURL(URL, filename_buffer); + } + FREE(string_buffer); + LYCloseInput(fp); + + return (filename_buffer); /* bookmark file exists */ + } + return (NULL); + +} /* big end */ + +/* + * Converts a Mosaic hotlist file into an HTML file for handling as a Lynx + * bookmark file. - FM + */ +static const char *convert_mosaic_bookmark_file(const char *filename_buffer) +{ + static char newfile[LY_MAXPATH]; + FILE *fp, *nfp; + char *buf = NULL; + int line = -2; + + (void) LYRemoveTemp(newfile); + if ((nfp = LYOpenTemp(newfile, HTML_SUFFIX, "w")) == NULL) { + LYMBM_statusline(NO_TEMP_FOR_HOTLIST); + LYSleepAlert(); + return (""); + } + + if ((fp = fopen(filename_buffer, TXT_R)) == NULL) + return (""); /* should always open */ + + fprintf(nfp, "<head>\n<title>%s</title>\n</head>\n", MOSAIC_BOOKMARK_TITLE); + fprintf(nfp, "%s\n\n<p>\n<ol>\n", gettext("\ + This file is an HTML representation of the X Mosaic hotlist file.\n\ + Outdated or invalid links may be removed by using the\n\ + remove bookmark command, it is usually the 'R' key but may have\n\ + been remapped by you or your system administrator.")); + + while ((LYSafeGets(&buf, fp)) != NULL) { + if (line >= 0) { + LYTrimNewline(buf); + if ((line % 2) == 0) { /* even lines */ + if (*buf != '\0') { + strtok(buf, " "); /* kill everything after the space */ + fprintf(nfp, "<li><a href=\"%s\">", buf); /* the URL */ + } + } else { /* odd lines */ + fprintf(nfp, "%s</a></li>\n", buf); /* the title */ + } + } + /* else - ignore the line (this gets rid of first two lines) */ + line++; + } + LYCloseTempFP(nfp); + LYCloseInput(fp); + return (newfile); +} + +static BOOLEAN havevisible(const char *Title); +static BOOLEAN have8bit(const char *Title); +static char *title_convert8bit(const char *Title); + +#if defined(_WINDOWS) && !defined(ftruncate) +#define ftruncate(fd, len) _chsize(fd, len) +#endif + +/* + * Adds a link to a bookmark file, creating the file if it doesn't already + * exist, and making sure that no_cache is set for a pre-existing, cached file, + * so that the change will be evident on return to to that file. - FM + */ +void save_bookmark_link(const char *address, + const char *title) +{ + FILE *fp; + BOOLEAN first_time = FALSE; + const char *filename; + char *bookmark_URL = NULL; + char filename_buffer[LY_MAXPATH]; + char *Address = NULL; + char *Title = NULL; + int i, c; + bstring *string_data = NULL; + bstring *tmp_data = NULL; + DocAddress WWWDoc; + HTParentAnchor *tmpanchor; + HText *text; + + /* + * Make sure we were passed something to save. - FM + */ + if (isEmpty(address)) { + HTAlert(MALFORMED_ADDRESS); + return; + } + + /* + * Offer a choice of bookmark files, or get the default. - FMG + */ + filename = get_bookmark_filename(&bookmark_URL); + + /* + * If filename is NULL, must create a new file. If filename is a space, an + * invalid bookmark file was selected, or if zero-length, the user + * cancelled. Ignore request in both cases. Otherwise, make a copy before + * anything might change the static get_bookmark_filename() buffer. - FM + */ + if (filename == NULL) { + first_time = TRUE; + filename_buffer[0] = '\0'; + } else { + if (*filename == '\0' || !strcmp(filename, " ")) { + FREE(bookmark_URL); + return; + } + LYStrNCpy(filename_buffer, filename, sizeof(filename_buffer) - 1); + } + + /* + * If BookmarkPage is NULL, something went wrong, so ignore the request. - + * FM + */ + if (isEmpty(BookmarkPage)) { + FREE(bookmark_URL); + return; + } + + /* + * If the link will be added to the same bookmark file, get confirmation. + * - FM + */ + if (LYMultiBookmarks != MBM_OFF) { + const char *url = HTLoadedDocumentURL(); + const char *page = ((*BookmarkPage == '.') + ? (BookmarkPage + 1) + : BookmarkPage); + + if (strstr(url, page) != NULL) { + LYMBM_statusline(MULTIBOOKMARKS_SELF); + c = LYgetch_single(); + if (c != 'L') { + FREE(bookmark_URL); + return; + } + } + } + + /* + * Allow user to change the title. - FM + */ + do { + if (HTCJK == JAPANESE) { + switch (kanji_code) { + case EUC: + BStrAlloc(tmp_data, MAX_LINE + 2 * (int) strlen(title)); + TO_EUC((const unsigned char *) title, (unsigned char *) tmp_data->str); + break; + case SJIS: + BStrAlloc(tmp_data, MAX_LINE + (int) strlen(title)); + TO_SJIS((const unsigned char *) title, (unsigned char *) tmp_data->str); + break; + default: + break; + } + BStrCopy0(string_data, tmp_data ? tmp_data->str : title); + } else { + BStrCopy0(string_data, title); + } + LYReduceBlanks(string_data->str); + LYMBM_statusline(TITLE_PROMPT); + LYgetBString(&string_data, FALSE, 0, NORECALL); + if (isBEmpty(string_data)) { + LYMBM_statusline(CANCELLED); + LYSleepMsg(); + FREE(bookmark_URL); + BStrFree(tmp_data); + return; + } + } while (!havevisible(string_data->str)); + + /* + * Create the Title with any left-angle-brackets converted to < entities + * and any ampersands converted to & entities. - FM + * + * Convert 8-bit letters to &#xUUUU to avoid dependencies from display + * character set which may need changing. Do NOT convert any 8-bit chars + * if we have CJK display. - LP + */ + LYformTitle(&Title, string_data->str); + LYEntify(&Title, TRUE); + if (UCSaveBookmarksInUnicode && + have8bit(Title) && (!LYHaveCJKCharacterSet)) { + char *p = title_convert8bit(Title); + + if (p != 0) { + FREE(Title); + Title = p; + } + } + + /* + * Create the bookmark file, if it doesn't exist already, Otherwise, open + * the pre-existing bookmark file. - FM + */ + SetDefaultMode(O_TEXT); + if (first_time) { + /* + * Seek it in the home path. - FM + */ + LYAddPathToHome(filename_buffer, + sizeof(filename_buffer), + BookmarkPage); + } + CTRACE((tfp, "\nsave_bookmark_link: SEEKING %s\n AS %s\n\n", + BookmarkPage, filename_buffer)); + if ((fp = fopen(filename_buffer, (first_time ? TXT_W : TXT_A))) == NULL) { + LYMBM_statusline(BOOKMARK_OPEN_FAILED); + LYSleepAlert(); + FREE(Title); + FREE(bookmark_URL); + BStrFree(tmp_data); + return; + } + + /* + * Convert all ampersands in the address to & entities. - FM + */ + StrAllocCopy(Address, address); + LYEntify(&Address, FALSE); + + if (!first_time) { + BOOLEAN empty_file = TRUE; + FILE *bp = tmpfile(); + char *buffer = NULL; + + rewind(fp); + while (LYSafeGets(&buffer, fp)) { + empty_file = FALSE; + if (LYstrstr(buffer, "</ol>")) + break; + fprintf(bp, "%s", buffer); + } + + fflush(bp); + rewind(bp); + + rewind(fp); + ftruncate(fileno(fp), 0); + + while (LYSafeGets(&buffer, bp)) { + fprintf(fp, "%s", buffer); + } + fclose(bp); + + if (empty_file) + first_time = TRUE; + } + + /* + * If we created a new bookmark file, write the headers. - FM + * Once and forever... + */ + if (first_time) { + fprintf(fp, "%s\n", LYNX_DOCTYPE); + fprintf(fp, "<html>\n"); + fprintf(fp, "<head>\n"); +#if defined(SH_EX) && !defined(_WINDOWS) /* 1997/12/11 (Thu) 19:13:40 */ + if (HTCJK != JAPANESE) + LYAddMETAcharsetToFD(fp, -1); + else + fprintf(fp, "<meta %s %s>\n", + "http-equiv=\"content-type\"", + "content=\"" STR_HTML ";charset=iso-2022-jp\""); +#else + LYAddMETAcharsetToFD(fp, -1); +#endif /* !_WINDOWS */ + fprintf(fp, "<title>%s</title>\n</head>\n", BOOKMARK_TITLE); + fprintf(fp, "<body>\n"); +#ifdef _WINDOWS + fprintf(fp, "<p>%s", + gettext(" You can delete links by the 'R' key<br>\n<ol>\n")); +#else + fprintf(fp, "<p>%s<br>\n%s\n\n<!--\n%s\n--></p>\n\n<ol>\n", + gettext("\ + You can delete links using the remove bookmark command. It is usually\n\ + the 'R' key but may have been remapped by you or your system\n\ + administrator."), + gettext("\ + This file also may be edited with a standard text editor to delete\n\ + outdated or invalid links, or to change their order."), + gettext("\ +Note: if you edit this file manually\n\ + you should not change the format within the lines\n\ + or add other HTML markup.\n\ + Make sure any bookmark link is saved as a single line.")); +#endif /* _WINDOWS */ + } + + /* + * Add the bookmark link, in Mosaic hotlist or Lynx format. - FM + */ + if (is_mosaic_hotlist) { + time_t NowTime = time(NULL); + char *TimeString = (char *) ctime(&NowTime); + + /* + * TimeString has a \n at the end. + */ + fprintf(fp, "%s %s%s\n", Address, TimeString, Title); + } else { + fprintf(fp, "<li><a href=\"%s\">%s</a></li>\n", Address, Title); + fprintf(fp, "</ol></body></html>\n"); + } + LYCloseOutput(fp); + + SetDefaultMode(O_BINARY); + /* + * If this is a cached bookmark file, set nocache for it so we'll see the + * new bookmark link when that cache is retrieved. - FM + */ + if (!first_time && nhist > 0 && bookmark_URL) { + for (i = 0; i < nhist; i++) { + if (HDOC(i).bookmark && + !strcmp(HDOC(i).address, bookmark_URL)) { + WWWDoc.address = HDOC(i).address; + WWWDoc.post_data = NULL; + WWWDoc.post_content_type = NULL; + WWWDoc.bookmark = HDOC(i).bookmark; + WWWDoc.isHEAD = FALSE; + WWWDoc.safe = FALSE; + tmpanchor = HTAnchor_findAddress(&WWWDoc); + if ((text = (HText *) HTAnchor_document(tmpanchor)) != NULL) { + HText_setNoCache(text); + } + break; + } + } + } + + /* + * Clean up and report success. + */ + BStrFree(string_data); + BStrFree(tmp_data); + FREE(Title); + FREE(Address); + FREE(bookmark_URL); + LYMBM_statusline(OPERATION_DONE); + LYSleepMsg(); +} + +/* + * Remove a link from a bookmark file. The calling function is expected to + * have used get_filename_link(), pass us the link number as cur, the + * MBM_A_subbookmark[] string as cur_bookmark_page, and to have set up no_cache + * itself. - FM + */ +void remove_bookmark_link(int cur, + char *cur_bookmark_page) +{ + FILE *fp, *nfp; + char *buf = NULL; + int n; + +#ifdef VMS + char filename_buffer[NAM$C_MAXRSS + 12]; + char newfile[NAM$C_MAXRSS + 12]; + +#define keep_tempfile FALSE +#else + char filename_buffer[LY_MAXPATH]; + char newfile[LY_MAXPATH]; + BOOLEAN keep_tempfile = FALSE; + +#ifdef UNIX + struct stat stat_buf; + BOOLEAN regular = FALSE; +#endif /* UNIX */ +#endif /* VMS */ + char homepath[LY_MAXPATH]; + + CTRACE((tfp, "remove_bookmark_link: deleting link number: %d\n", cur)); + + if (!cur_bookmark_page) + return; + LYAddPathToHome(filename_buffer, + sizeof(filename_buffer), + cur_bookmark_page); + CTRACE((tfp, "\nremove_bookmark_link: SEEKING %s\n AS %s\n\n", + cur_bookmark_page, filename_buffer)); + if ((fp = fopen(filename_buffer, TXT_R)) == NULL) { + HTAlert(BOOKMARK_OPEN_FAILED_FOR_DEL); + return; + } + + LYAddPathToHome(homepath, sizeof(homepath), ""); + if ((nfp = LYOpenScratch(newfile, homepath)) == 0) { + LYCloseInput(fp); + HTAlert(BOOKSCRA_OPEN_FAILED_FOR_DEL); + return; + } +#ifdef UNIX + /* + * Explicitly preserve bookmark file mode on Unix. - DSL + */ + if (stat(filename_buffer, &stat_buf) == 0) { + regular = (BOOLEAN) (S_ISREG(stat_buf.st_mode) && stat_buf.st_nlink == 1); + (void) chmod(newfile, HIDE_CHMOD); + if ((nfp = LYReopenTemp(newfile)) == NULL) { + (void) LYCloseInput(fp); + HTAlert(BOOKTEMP_REOPEN_FAIL_FOR_DEL); + return; + } + } +#endif /* UNIX */ + + if (is_mosaic_hotlist) { + int del_line = cur * 2; /* two lines per entry */ + + n = -3; /* skip past cookie and name lines */ + while (LYSafeGets(&buf, fp) != NULL) { + n++; + if (n == del_line || n == del_line + 1) + continue; /* remove two lines */ + if (fputs(buf, nfp) == EOF) + goto failure; + } + + } else { + char *cp; + BOOLEAN retain; + int seen; + + n = -1; + while (LYSafeGets(&buf, fp) != NULL) { + int keep_ol = FALSE; + + retain = TRUE; + seen = 0; + cp = buf; + if ((cur == 0) && LYstrstr(cp, "<ol><li>")) + keep_ol = TRUE; /* Do not erase, this corrects a bug in an + older version */ + while (n < cur && (cp = LYstrstr(cp, "<a href="))) { + seen++; + if (++n == cur) { + if (seen != 1 || !LYstrstr(buf, "</a>") || + LYstrstr((cp + 1), "<a href=")) { + HTAlert(BOOKMARK_LINK_NOT_ONE_LINE); + goto failure; + } + CTRACE((tfp, "remove_bookmark_link: skipping link %d\n", n)); + if (keep_ol) + fprintf(nfp, "<ol>\n"); + retain = FALSE; + } + cp += 8; + } + if (retain && fputs(buf, nfp) == EOF) + goto failure; + } + } + + FREE(buf); + CTRACE((tfp, "remove_bookmark_link: files: %s %s\n", + newfile, filename_buffer)); + + LYCloseInput(fp); + fp = NULL; + if (fflush(nfp) == EOF) { + CTRACE((tfp, "fflush(nfp): %s", LYStrerror(errno))); + goto failure; + } + LYCloseTempFP(nfp); + nfp = NULL; +#if defined(DOSPATH) || defined(__EMX__) + remove(filename_buffer); +#endif /* DOSPATH */ + +#ifdef UNIX + /* + * By copying onto the bookmark file, rather than renaming it, we can + * preserve the original ownership of the file, provided that it is + * writable by the current process. + * + * Changed to copy 1998-04-26 -- gil + * + * But if the copy fails, for example because the filesystem is full, we + * are left with a corrupt bookmark file. Changed back to use the previous + * mechanism [try rename(), then mv for EXDEV], except in usual cases (not + * a regular file e.g., symbolic link, or has hard links). This will let + * bookmarks survive a filesystem full condition in the "normal" case + * (bookmark is on same filesystem as home directory, is a regular file, + * has no additional hard links). + * + * If we first tried LYCopyFile, and that fails, also fall back to trying + * the other stuff. That gives a chance to recover in case the LYCopyFile + * left a corrupt target file. + * + * If there is an error, and that error may mean that the bookmark file has + * been corrupted, don't remove the temporary newfile (which should always + * be uncorrupted) in place, it may still be used to recover manually. If + * this applies, produce an additional message to that effect. The temp + * file will still be removed by normal program exit cleanup. - kw + * 1999-11-12 + */ + if (!regular) { + if (LYCopyFile(newfile, filename_buffer) == 0) { + (void) LYRemoveTemp(newfile); + return; + } + LYSleepAlert(); /* give a chance to see error from cp - kw */ + HTUserMsg(BOOKTEMP_COPY_FAIL); + keep_tempfile = TRUE; + } +#endif /* UNIX */ + + if (rename(newfile, filename_buffer) != -1) { +#ifdef MULTI_USER_UNIX + if (regular) + chmod(filename_buffer, stat_buf.st_mode & 07777); +#endif + HTSYS_purge(filename_buffer); + return; + } else { +#ifndef VMS + /* + * Rename won't work across file systems. Check if this is the case + * and do something appropriate. Used to be ODD_RENAME + */ +#if defined(_WINDOWS) || defined(WIN_EX) +#if defined(WIN_EX) + if (GetLastError() == ERROR_NOT_SAME_DEVICE) +#else /* !_WIN_EX */ + if (errno == ENOTSAM) +#endif /* _WIN_EX */ + { + if (rename(newfile, filename_buffer) != 0) { + if (LYCopyFile(newfile, filename_buffer) == 0) + remove(newfile); + } + } +#else + if (errno == EXDEV) { + static const char MV_FMT[] = "%s %s %s"; + char *buffer = 0; + const char *program; + + if ((program = HTGetProgramPath(ppMV)) != NULL) { + HTAddParam(&buffer, MV_FMT, 1, program); + HTAddParam(&buffer, MV_FMT, 2, newfile); + HTAddParam(&buffer, MV_FMT, 3, filename_buffer); + HTEndParam(&buffer, MV_FMT, 3); + if (LYSystem(buffer) == 0) { +#ifdef MULTI_USER_UNIX + if (regular) + chmod(filename_buffer, stat_buf.st_mode & 07777); +#endif + FREE(buffer); + return; + } + } + FREE(buffer); + keep_tempfile = TRUE; + goto failure; + } + CTRACE((tfp, "rename(): %s", LYStrerror(errno))); +#endif /* _WINDOWS */ +#endif /* !VMS */ + +#ifdef VMS + HTAlert(ERROR_RENAMING_SCRA); +#else + HTAlert(ERROR_RENAMING_TEMP); +#endif /* VMS */ + if (TRACE) + perror("renaming the file"); + } + + failure: + FREE(buf); + HTAlert(BOOKMARK_DEL_FAILED); + if (nfp) + LYCloseTempFP(nfp); + if (fp != NULL) + LYCloseInput(fp); + if (keep_tempfile) { + HTUserMsg2(gettext("File may be recoverable from %s during this session"), + newfile); + } else { + (void) LYRemoveTemp(newfile); + } +} + +/* + * Allows user to select sub-bookmarks files. - FMG & FM + */ +int select_multi_bookmarks(void) +{ + int c; + + /* + * If not enabled, pick the "default" (0). + */ + if (LYMultiBookmarks == MBM_OFF || LYHaveSubBookmarks() == FALSE) { + if (MBM_A_subbookmark[0]) /* If it exists! */ + return (0); + else + return (-1); + } + + /* + * For ADVANCED users, we can just mess with the status line to save the 2 + * redraws of the screen, if LYMBMAdvnced is TRUE. '=' will still show the + * screen and let them do it the "long" way. + */ + if (LYMultiBookmarks == MBM_ADVANCED && (user_mode == ADVANCED_MODE)) { + LYMBM_statusline(MULTIBOOKMARKS_SELECT); + get_advanced_choice: + c = LYgetch(); +#ifdef VMS + if (HadVMSInterrupt) { + HadVMSInterrupt = FALSE; + c = LYCharINTERRUPT2; + } +#endif /* VMS */ + if (LYisNonAlnumKeyname(c, LYK_PREV_DOC) || LYCharIsINTERRUPT_HARD(c)) { + /* + * Treat left-arrow, ^G, or ^C as cancel. + */ + return (-2); + } + if (LYisNonAlnumKeyname(c, LYK_REFRESH)) { + /* + * Refresh the screen. + */ + lynx_force_repaint(); + LYrefresh(); + goto get_advanced_choice; + } + if (LYisNonAlnumKeyname(c, LYK_ACTIVATE)) { + /* + * Assume default bookmark file on ENTER or right-arrow. + */ + return (MBM_A_subbookmark[0] ? 0 : -1); + } + switch (c) { + case '=': + /* + * Get the choice via the menu. + */ + return (select_menu_multi_bookmarks()); + + default: + /* + * Convert to an array index, act on it if valid. + * Otherwise, get another keystroke. + */ + if ((c = LYMBM2index(c)) < 0) { + goto get_advanced_choice; + } + } + /* + * See if we have a bookmark like that. + */ + return (MBM_A_subbookmark[c] ? c : -1); + } else { + /* + * Get the choice via the menu. + */ + return (select_menu_multi_bookmarks()); + } +} + +/* + * Allows user to select sub-bookmarks files. - FMG & FM + */ +int select_menu_multi_bookmarks(void) +{ + int c, d, MBM_tmp_count, MBM_allow; + int MBM_screens, MBM_from, MBM_to, MBM_current; + + /* + * If not enabled, pick the "default" (0). + */ + if (LYMultiBookmarks == MBM_OFF) + return (0); + + /* + * Filip M. Gieszczykiewicz (filipg@paranoia.com) & FM + * --------------------------------------------------- + * MBM_A_subbookmark[n] - Hold values of the respective "multi_bookmarkn" + * in the lynxrc file. + * + * MBM_A_subdescript[n] - Hold description entries in the lynxrc file. + * + * Note: MBM_A_subbookmark[0] is defined to be same value as + * "bookmark_file" in the lynxrc file and/or the startup + * "bookmark_page". + * + * We make the display of bookmarks depend on rows we have available. + * + * We load BookmarkPage with the valid MBM_A_subbookmark[n] via + * get_bookmark_filename(). Otherwise, that function returns a zero-length + * string to indicate a cancel, a single space to indicate an invalid + * choice, or NULL to indicate an inaccessible file. + */ + MBM_allow = (LYlines - 7); /* We need 7 for header and footer */ + /* + * Screen big enough? + */ + if (MBM_allow <= 0) { + /* + * Too small. + */ + HTAlert(MULTIBOOKMARKS_SMALL); + return (-2); + } + + MBM_screens = (MBM_V_MAXFILES / MBM_allow) + 1; /* int rounds off low. */ + + MBM_current = 1; /* Gotta start somewhere :-) */ + + for (;;) { + MBM_from = MBM_allow * MBM_current - MBM_allow; + if (MBM_from < 0) + MBM_from = 0; /* 0 is default bookmark... */ + if (MBM_current != 1) + MBM_from++; + + MBM_to = (MBM_allow * MBM_current); + if (MBM_to > MBM_V_MAXFILES) + MBM_to = MBM_V_MAXFILES; + + /* + * Display menu of bookmarks. NOTE that we avoid printw()'s to + * increase the chances that any non-ASCII or multibyte/CJK characters + * will be handled properly. - FM + */ + LYclear(); + LYmove(1, 5); + lynx_start_h1_color(); + if (MBM_screens > 1) { + char *shead_buffer = 0; + + HTSprintf0(&shead_buffer, + MULTIBOOKMARKS_SHEAD_MASK, MBM_current, MBM_screens); + LYaddstr(shead_buffer); + FREE(shead_buffer); + } else { + LYaddstr(MULTIBOOKMARKS_SHEAD); + } + + lynx_stop_h1_color(); + + MBM_tmp_count = 0; + for (c = MBM_from; c <= MBM_to; c++) { + LYmove(3 + MBM_tmp_count, 5); + LYaddch(UCH(LYindex2MBM(c))); + LYaddstr(" : "); + if (MBM_A_subdescript[c]) + LYaddstr(MBM_A_subdescript[c]); + LYmove(3 + MBM_tmp_count, 36); + LYaddch('('); + if (MBM_A_subbookmark[c]) + LYaddstr(MBM_A_subbookmark[c]); + LYaddch(')'); + MBM_tmp_count++; + } + + /* + * Don't need to show it if it all fits on one screen! + */ + if (MBM_screens > 1) { + LYmove(LYlines - 2, 0); + LYaddstr("'"); + lynx_start_bold(); + LYaddstr("["); + lynx_stop_bold(); + LYaddstr("' "); + LYaddstr(PREVIOUS); + LYaddstr(", '"); + lynx_start_bold(); + LYaddstr("]"); + lynx_stop_bold(); + LYaddstr("' "); + LYaddstr(NEXT_SCREEN); + } + + LYMBM_statusline(MULTIBOOKMARKS_SAVE); + + for (;;) { + c = LYgetch(); +#ifdef VMS + if (HadVMSInterrupt) { + HadVMSInterrupt = FALSE; + c = 7; + } +#endif /* VMS */ + + if ((d = LYMBM2index(c)) >= 0) { + /* + * See if we have a bookmark like that. + */ + if (non_empty(MBM_A_subbookmark[d])) + return (d); + + show_bookmark_not_defined(); + LYMBM_statusline(MULTIBOOKMARKS_SAVE); + } else if (LYisNonAlnumKeyname(c, LYK_PREV_DOC) || + c == 7 || c == 3) { + /* + * Treat left-arrow, ^G, or ^C as cancel. + */ + return (-2); + } else if (LYisNonAlnumKeyname(c, LYK_REFRESH)) { + /* + * Refresh the screen. + */ + lynx_force_repaint(); + LYrefresh(); + } else if (LYisNonAlnumKeyname(c, LYK_ACTIVATE)) { + /* + * Assume default bookmark file on ENTER or right-arrow. + */ + return (MBM_A_subbookmark[0] ? 0 : -1); + } else if ((c == ']' || LYisNonAlnumKeyname(c, LYK_NEXT_PAGE)) && + MBM_screens > 1) { + /* + * Next range, if available. + */ + if (++MBM_current > MBM_screens) + MBM_current = 1; + break; + } + + else if ((c == '[' || LYisNonAlnumKeyname(c, LYK_PREV_PAGE)) && + MBM_screens > 1) { + /* + * Previous range, if available. + */ + if (--MBM_current <= 0) + MBM_current = MBM_screens; + break; + } + } + } +} + +/* + * This function returns TRUE if we have sub-bookmarks defined. Otherwise + * (i.e., only the default bookmark file is defined), it returns FALSE. - FM + */ +BOOLEAN LYHaveSubBookmarks(void) +{ + int i; + + for (i = 1; i < MBM_V_MAXFILES; i++) { + if (non_empty(MBM_A_subbookmark[i])) + return (TRUE); + } + + return (FALSE); +} + +/* + * This function passes a string to _statusline(), making sure it is at the + * bottom of the screen if LYMultiBookmarks is not MBM_OFF, otherwise, letting + * it go to the normal statusline position based on the current user mode. We + * want to use _statusline() so that any multibyte/CJK characters in the string + * will be handled properly. - FM + */ +void LYMBM_statusline(const char *text) +{ + if (LYMultiBookmarks != MBM_OFF && user_mode == NOVICE_MODE) { + LYStatusLine = (LYlines - 1); + _statusline(text); + LYStatusLine = -1; + } else { + _statusline(text); + } +} + +/* + * Check whether we have any visible (non-blank) chars. + */ +static BOOLEAN havevisible(const char *Title) +{ + BOOLEAN result = FALSE; + const char *p = Title; + unsigned char c; + long unicode; + + for (; *p; p++) { + c = UCH(TOASCII(*p)); + if (c > 32 && c < 127) { + result = TRUE; + break; + } + if (c <= 32 || c == 127) + continue; + if (LYHaveCJKCharacterSet || !UCCanUniTranslateFrom(current_char_set)) { + result = TRUE; + break; + } + unicode = UCTransToUni(*p, current_char_set); + if (unicode == ucNeedMore) + continue; + if (unicode > 32 && unicode < 127) { + result = TRUE; + break; + } + if (unicode <= 32 || unicode == 0xa0 || unicode == 0xad) + continue; + if (unicode < 0x2000 || unicode >= 0x200f) { + result = TRUE; + break; + } + } + return (result); +} + +/* + * Check whether string have 8 bit chars. + */ +static BOOLEAN have8bit(const char *Title) +{ + const char *p = Title; + + for (; *p; p++) { + if (UCH(*p) > 127) + return (TRUE); + } + return (FALSE); /* if we came here */ +} + +/* + * Ok, title have 8-bit characters and they are in display charset. Bookmarks + * is a permanent file. To avoid dependencies from display character set which + * may be changed with time we store 8-bit characters as numeric character + * reference (NCR), so where the character encoded as unicode number in form of + * &#xUUUU; + * + * To make bookmarks more readable for human (&#xUUUU certainly not) we add a + * comment with '7-bit approximation' from the converted string. This is a + * valid HTML and bookmarks code. + * + * We do not want use META charset tag in bookmarks file: it will never be + * changed later :-( + * + * NCR's translation is part of I18N and HTML4.0 supported starting with Lynx + * 2.7.2, Netscape 4.0 and MSIE 4.0. Older versions fail. + */ +static char *title_convert8bit(const char *Title) +{ + const char *p = Title; + char *p0; + char *q; + char *comment = NULL; + char *ncr = NULL; + char *buf = NULL; + int charset_in = current_char_set; + int charset_out = UCGetLYhndl_byMIME("us-ascii"); + + for (; *p; p++) { + char temp[2]; + + LYStrNCpy(temp, p, sizeof(temp) - 1); + if (UCH(*temp) <= 127) { + StrAllocCat(comment, temp); + StrAllocCat(ncr, temp); + } else if (charset_out >= 0) { + long unicode; + char replace_buf[32]; + + if (UCTransCharStr(replace_buf, (int) sizeof(replace_buf), *temp, + charset_in, charset_out, YES) > 0) + StrAllocCat(comment, replace_buf); + + unicode = UCTransToUni(*temp, charset_in); + + StrAllocCat(ncr, "&#"); + sprintf(replace_buf, "%ld", unicode); + StrAllocCat(ncr, replace_buf); + StrAllocCat(ncr, ";"); + } + } + + if (comment != NULL) { + /* + * Cleanup comment, collapse multiple dashes into one dash, skip '>'. + */ + for (q = p0 = comment; *p0; p0++) { + if (UCH(TOASCII(*p0)) >= 32 && + *p0 != '>' && + (q == comment || *p0 != '-' || *(q - 1) != '-')) { + *q++ = *p0; + } + } + *q = '\0'; + + /* + * valid bookmark should be a single line (no linebreaks!). + */ + StrAllocCat(buf, "<!-- "); + StrAllocCat(buf, comment); + StrAllocCat(buf, " -->"); + StrAllocCat(buf, ncr); + + FREE(comment); + } + FREE(ncr); + return (buf); +} + +/* + * Since this is the "Default Bookmark File", we save it as a global, and as + * the first MBM_A_subbookmark entry. + */ +void set_default_bookmark_page(char *value) +{ + if (value != 0) { + if (bookmark_page == NULL + || strcmp(bookmark_page, value)) { + StrAllocCopy(bookmark_page, value); + } + StrAllocCopy(BookmarkPage, bookmark_page); + StrAllocCopy(MBM_A_subbookmark[0], bookmark_page); + StrAllocCopy(MBM_A_subdescript[0], MULTIBOOKMARKS_DEFAULT); + } +} |