| /* footnotes.c -- Some functions for manipulating footnotes. |
| |
| Copyright 1993-2022 Free Software Foundation, Inc. |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/>. |
| |
| Originally written by Brian Fox. */ |
| |
| #include "info.h" |
| #include "session.h" |
| #include "scan.h" |
| #include "util.h" |
| #include "footnotes.h" |
| |
| /* Nonzero means attempt to show footnotes when displaying a new window. */ |
| int auto_footnotes_p = 0; |
| |
| static char *footnote_nodename = "*Footnotes*"; |
| |
| /* Find the window currently showing footnotes. */ |
| static WINDOW * |
| find_footnotes_window (void) |
| { |
| WINDOW *win; |
| |
| /* Try to find an existing window first. */ |
| for (win = windows; win; win = win->next) |
| if (internal_info_node_p (win->node) && |
| (strcmp (win->node->nodename, footnote_nodename) == 0)) |
| break; |
| |
| return win; |
| } |
| |
| /* Manufacture a node containing the footnotes of this node, and |
| return the manufactured node. If NODE has no footnotes, return a |
| NULL pointer. */ |
| NODE * |
| make_footnotes_node (NODE *node) |
| { |
| NODE *fn_node, *footnotes_node = NULL, *result = NULL; |
| long fn_start = -1; |
| char *fnptr; |
| |
| /* Make the initial assumption that the footnotes appear as simple |
| text within this windows node. */ |
| fn_node = node; |
| |
| /* See if this node contains the magic footnote label. */ |
| { |
| char saved = node->contents[node->nodelen]; |
| node->contents[node->nodelen] = '\0'; |
| fnptr = strstr (node->contents, FOOTNOTE_LABEL); |
| node->contents[node->nodelen] = saved; |
| } |
| if (fnptr) |
| fn_start = fnptr - node->contents; |
| |
| /* If it doesn't, check to see if it has an associated footnotes node. */ |
| if (!fnptr) |
| { |
| REFERENCE **refs; |
| |
| refs = node->references; |
| |
| if (refs) |
| { |
| register int i; |
| char *refname; |
| int reflen = strlen ("-Footnotes") + strlen (node->nodename); |
| |
| refname = xmalloc (reflen + 1); |
| |
| strcpy (refname, node->nodename); |
| strcat (refname, "-Footnotes"); |
| |
| for (i = 0; refs[i]; i++) |
| if (refs[i]->type == REFERENCE_XREF |
| && (refs[i]->nodename != NULL) |
| /* Support both the older "foo-Footnotes" and the new |
| style "foo-Footnote-NN" references. */ |
| && (strcmp (refs[i]->nodename, refname) == 0 || |
| (strncmp (refs[i]->nodename, refname, reflen - 1) == 0 && |
| refs[i]->nodename[reflen - 1] == '-' && |
| isdigit (refs[i]->nodename[reflen])))) |
| { |
| footnotes_node = info_get_node (node->fullpath, refname); |
| if (footnotes_node) |
| { |
| fn_node = footnotes_node; |
| fn_start = 0; |
| } |
| break; |
| } |
| |
| free (refname); |
| } |
| } |
| |
| /* If we never found the start of a footnotes area, quit now. */ |
| if (fn_start == -1) |
| return NULL; |
| |
| /* Make the new node. */ |
| result = info_create_node (); |
| |
| /* Get the size of the footnotes appearing within this node. */ |
| { |
| char *header; |
| long text_start = fn_start; |
| |
| xasprintf (&header, |
| "*** Footnotes appearing in the node '%s' ***\n", |
| node->nodename); |
| |
| /* Move the start of the displayed text to right after the first line. |
| This effectively skips either "---- footno...", or "File: foo...". */ |
| while (text_start < fn_node->nodelen) |
| if (fn_node->contents[text_start++] == '\n') |
| break; |
| |
| result->nodelen = strlen (header) + fn_node->nodelen - text_start; |
| |
| /* Set the contents of this node. */ |
| result->contents = xmalloc (1 + result->nodelen); |
| sprintf (result->contents, "%s", header); |
| memcpy (result->contents + strlen (header), |
| fn_node->contents + text_start, fn_node->nodelen - text_start); |
| result->contents[strlen (header) + fn_node->nodelen - text_start] = '\0'; |
| |
| /* Copy and adjust references that appear in footnotes section. */ |
| { |
| REFERENCE **ref = fn_node->references; |
| |
| for (; *ref; ref++) |
| { |
| if ((*ref)->start > text_start) |
| break; |
| } |
| |
| result->references = info_copy_references (ref); |
| |
| for (ref = result->references; *ref; ref++) |
| { |
| (*ref)->start -= text_start - strlen (header); |
| (*ref)->end -= text_start - strlen (header); |
| } |
| } |
| |
| result->nodename = xstrdup (footnote_nodename); |
| result->flags |= N_IsInternal | N_WasRewritten; |
| |
| /* Needed in case the user follows a reference in the footnotes window. */ |
| result->fullpath = fn_node->fullpath; |
| result->subfile = fn_node->subfile; |
| |
| free (header); |
| } |
| |
| free_history_node (footnotes_node); |
| return result; |
| } |
| |
| /* Create or delete the footnotes window depending on whether footnotes |
| exist in WINDOW's node or not. Returns FN_FOUND if footnotes were found |
| and displayed. Returns FN_UNFOUND if there were no footnotes found |
| in WINDOW's node. Returns FN_UNABLE if there were footnotes, but the |
| window to show them couldn't be made. */ |
| int |
| info_get_or_remove_footnotes (WINDOW *window) |
| { |
| WINDOW *fn_win; |
| NODE *new_footnotes = 0; |
| |
| fn_win = find_footnotes_window (); |
| |
| /* If we are in the footnotes window, change nothing. */ |
| if (fn_win == window) |
| return FN_FOUND; |
| |
| /* Don't display footnotes for the "*" node (entire contents of file) or |
| for nodes without a name like completion windows. */ |
| if (window->node->nodename && strcmp ("*", window->node->nodename)) |
| /* Try to find footnotes for this window's node. */ |
| new_footnotes = make_footnotes_node (window->node); |
| |
| if (!new_footnotes) |
| { |
| /* If there was a window showing footnotes, and there are no footnotes |
| for the current window, delete the old footnote window. */ |
| if (fn_win && windows->next) |
| info_delete_window_internal (fn_win); |
| return FN_UNFOUND; |
| } |
| |
| /* If there is no window around showing footnotes, try |
| to make a new window. */ |
| if (!fn_win) |
| { |
| WINDOW *old_active; |
| WINDOW *last, *win; |
| |
| /* Always make this window be the last one appearing in the list. Find |
| the last window in the chain. */ |
| for (win = windows, last = windows; win; last = win, win = win->next); |
| |
| /* Try to split this window, and make the split window the one to |
| contain the footnotes. */ |
| old_active = active_window; |
| active_window = last; |
| fn_win = window_make_window (); |
| active_window = old_active; |
| |
| /* If we are hacking automatic footnotes, and there are footnotes |
| but we couldn't display them, print a message to that effect. */ |
| if (!fn_win) |
| { |
| if (auto_footnotes_p) |
| info_error (_("Footnotes could not be displayed")); |
| return FN_UNABLE; |
| } |
| } |
| |
| /* Note that info_set_node_of_window calls this function |
| (info_get_or_remove_footnotes), but we do not recurse indefinitely |
| because we check if we are in the footnote window above. */ |
| info_set_node_of_window (fn_win, new_footnotes); |
| fn_win->flags |= W_TempWindow; |
| |
| /* Make the height be the number of lines appearing in the footnotes. */ |
| if (new_footnotes) |
| window_change_window_height (fn_win, fn_win->line_count - fn_win->height); |
| |
| return FN_FOUND; |
| } |
| |
| /* Show the footnotes associated with this node in another window. */ |
| DECLARE_INFO_COMMAND (info_show_footnotes, |
| _("Show the footnotes associated with this node in another window")) |
| { |
| /* Make the window go away if it is already showing. */ |
| WINDOW *fn_win = find_footnotes_window (); |
| |
| /* If there is an old footnotes window, and it isn't the only window |
| on the screen, delete it. */ |
| if (fn_win && windows->next) |
| { |
| info_delete_window_internal (fn_win); |
| return; |
| } |
| |
| |
| switch (info_get_or_remove_footnotes (window)) |
| { |
| case FN_UNFOUND: |
| info_error ("%s", msg_no_foot_node); |
| break; |
| |
| case FN_UNABLE: |
| info_error ("%s", msg_win_too_small); |
| break; |
| } |
| } |