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