1 /* footnotes.c -- Some functions for manipulating footnotes.
2 
3    Copyright 1993-2019 Free Software Foundation, Inc.
4 
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 
18    Originally written by Brian Fox. */
19 
20 #include "info.h"
21 #include "session.h"
22 #include "info-utils.h"
23 #include "footnotes.h"
24 
25 /* Nonzero means attempt to show footnotes when displaying a new window. */
26 int auto_footnotes_p = 0;
27 
28 static char *footnote_nodename = "*Footnotes*";
29 
30 /* Find the window currently showing footnotes. */
31 static WINDOW *
find_footnotes_window(void)32 find_footnotes_window (void)
33 {
34   WINDOW *win;
35 
36   /* Try to find an existing window first. */
37   for (win = windows; win; win = win->next)
38     if (internal_info_node_p (win->node) &&
39         (strcmp (win->node->nodename, footnote_nodename) == 0))
40       break;
41 
42   return win;
43 }
44 
45 /* Manufacture a node containing the footnotes of this node, and
46    return the manufactured node.  If NODE has no footnotes, return a
47    NULL pointer. */
48 NODE *
make_footnotes_node(NODE * node)49 make_footnotes_node (NODE *node)
50 {
51   NODE *fn_node, *footnotes_node = NULL, *result = NULL;
52   long fn_start = -1;
53   char *fnptr;
54 
55   /* Make the initial assumption that the footnotes appear as simple
56      text within this windows node. */
57   fn_node = node;
58 
59   /* See if this node contains the magic footnote label. */
60     {
61       char saved = node->contents[node->nodelen];
62       node->contents[node->nodelen] = '\0';
63       fnptr = strstr (node->contents, FOOTNOTE_LABEL);
64       node->contents[node->nodelen] = saved;
65     }
66   if (fnptr)
67     fn_start = fnptr - node->contents;
68 
69   /* If it doesn't, check to see if it has an associated footnotes node. */
70   if (!fnptr)
71     {
72       REFERENCE **refs;
73 
74       refs = node->references;
75 
76       if (refs)
77         {
78           register int i;
79           char *refname;
80           int reflen = strlen ("-Footnotes") + strlen (node->nodename);
81 
82           refname = xmalloc (reflen + 1);
83 
84           strcpy (refname, node->nodename);
85           strcat (refname, "-Footnotes");
86 
87           for (i = 0; refs[i]; i++)
88             if (refs[i]->type == REFERENCE_XREF
89                 && (refs[i]->nodename != NULL)
90                 /* Support both the older "foo-Footnotes" and the new
91                    style "foo-Footnote-NN" references.  */
92                 && (strcmp (refs[i]->nodename, refname) == 0 ||
93                  (strncmp (refs[i]->nodename, refname, reflen - 1) == 0 &&
94                   refs[i]->nodename[reflen - 1] == '-' &&
95                   isdigit (refs[i]->nodename[reflen]))))
96               {
97                 footnotes_node = info_get_node (node->fullpath, refname);
98                 if (footnotes_node)
99                   {
100                     fn_node = footnotes_node;
101                     fn_start = 0;
102                   }
103                 break;
104               }
105 
106           free (refname);
107         }
108     }
109 
110   /* If we never found the start of a footnotes area, quit now. */
111   if (fn_start == -1)
112     return NULL;
113 
114   /* Make the new node. */
115   result = info_create_node ();
116 
117   /* Get the size of the footnotes appearing within this node. */
118   {
119     char *header;
120     long text_start = fn_start;
121 
122     asprintf (&header,
123               "*** Footnotes appearing in the node '%s' ***\n",
124               node->nodename);
125 
126     /* Move the start of the displayed text to right after the first line.
127        This effectively skips either "---- footno...", or "File: foo...". */
128     while (text_start < fn_node->nodelen)
129       if (fn_node->contents[text_start++] == '\n')
130         break;
131 
132     result->nodelen = strlen (header) + fn_node->nodelen - text_start;
133 
134     /* Set the contents of this node. */
135     result->contents = xmalloc (1 + result->nodelen);
136     sprintf (result->contents, "%s", header);
137     memcpy (result->contents + strlen (header),
138             fn_node->contents + text_start, fn_node->nodelen - text_start);
139     result->contents[strlen (header) + fn_node->nodelen - text_start] = '\0';
140 
141    /* Copy and adjust references that appear in footnotes section. */
142     {
143       REFERENCE **ref = fn_node->references;
144 
145       for (; *ref; ref++)
146         {
147           if ((*ref)->start > text_start)
148             break;
149         }
150 
151       result->references = info_copy_references (ref);
152 
153       for (ref = result->references; *ref; ref++)
154         {
155           (*ref)->start -= text_start - strlen (header);
156           (*ref)->end -= text_start - strlen (header);
157         }
158     }
159 
160     result->nodename = xstrdup (footnote_nodename);
161     result->flags |= N_IsInternal | N_WasRewritten;
162 
163     /* Needed in case the user follows a reference in the footnotes window. */
164     result->fullpath = fn_node->fullpath;
165     result->subfile = fn_node->subfile;
166 
167     free (header);
168   }
169 
170   free_history_node (footnotes_node);
171   return result;
172 }
173 
174 /* Create or delete the footnotes window depending on whether footnotes
175    exist in WINDOW's node or not.  Returns FN_FOUND if footnotes were found
176    and displayed.  Returns FN_UNFOUND if there were no footnotes found
177    in WINDOW's node.  Returns FN_UNABLE if there were footnotes, but the
178    window to show them couldn't be made. */
179 int
info_get_or_remove_footnotes(WINDOW * window)180 info_get_or_remove_footnotes (WINDOW *window)
181 {
182   WINDOW *fn_win;
183   NODE *new_footnotes = 0;
184 
185   fn_win = find_footnotes_window ();
186 
187   /* If we are in the footnotes window, change nothing. */
188   if (fn_win == window)
189     return FN_FOUND;
190 
191   /* Don't display footnotes for the "*" node (entire contents of file) or
192      for nodes without a name like completion windows. */
193   if (window->node->nodename && strcmp ("*", window->node->nodename))
194     /* Try to find footnotes for this window's node. */
195     new_footnotes = make_footnotes_node (window->node);
196 
197   if (!new_footnotes)
198     {
199       /* If there was a window showing footnotes, and there are no footnotes
200          for the current window, delete the old footnote window. */
201       if (fn_win && windows->next)
202         info_delete_window_internal (fn_win);
203       return FN_UNFOUND;
204     }
205 
206   /* If there is no window around showing footnotes, try
207      to make a new window. */
208   if (!fn_win)
209     {
210       WINDOW *old_active;
211       WINDOW *last, *win;
212 
213       /* Always make this window be the last one appearing in the list.  Find
214          the last window in the chain. */
215       for (win = windows, last = windows; win; last = win, win = win->next);
216 
217       /* Try to split this window, and make the split window the one to
218          contain the footnotes. */
219       old_active = active_window;
220       active_window = last;
221       fn_win = window_make_window ();
222       active_window = old_active;
223 
224       /* If we are hacking automatic footnotes, and there are footnotes
225          but we couldn't display them, print a message to that effect. */
226       if (!fn_win)
227         {
228           if (auto_footnotes_p)
229             info_error (_("Footnotes could not be displayed"));
230           return FN_UNABLE;
231         }
232     }
233 
234   /* Note that info_set_node_of_window calls this function
235      (info_get_or_remove_footnotes), but we do not recurse indefinitely
236      because we check if we are in the footnote window above. */
237   info_set_node_of_window (fn_win, new_footnotes);
238   fn_win->flags |= W_TempWindow;
239 
240   /* Make the height be the number of lines appearing in the footnotes. */
241   if (new_footnotes)
242     window_change_window_height (fn_win, fn_win->line_count - fn_win->height);
243 
244   return FN_FOUND;
245 }
246 
247 /* Show the footnotes associated with this node in another window. */
248 DECLARE_INFO_COMMAND (info_show_footnotes,
249    _("Show the footnotes associated with this node in another window"))
250 {
251   /* A negative argument means just make the window go away. */
252   if (count < 0)
253     {
254       WINDOW *fn_win = find_footnotes_window ();
255 
256       /* If there is an old footnotes window, and it isn't the only window
257          on the screen, delete it. */
258       if (fn_win && windows->next)
259         info_delete_window_internal (fn_win);
260     }
261   else
262     {
263       int result;
264 
265       result = info_get_or_remove_footnotes (window);
266 
267       switch (result)
268         {
269         case FN_UNFOUND:
270           info_error ("%s", msg_no_foot_node);
271           break;
272 
273         case FN_UNABLE:
274           info_error ("%s", msg_win_too_small);
275           break;
276         }
277     }
278 }
279