footnotes.c   [plain text]


/* footnotes.c -- Some functions for manipulating footnotes.
   $Id: footnotes.c,v 1.2 2003/07/25 18:37:06 jkh Exp $

   Copyright (C) 1993, 1997, 1998, 1999, 2002 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 2, 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, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   Written by Brian Fox (bfox@ai.mit.edu). */

#include "info.h"

/* Nonzero means attempt to show footnotes when displaying a new window. */
int auto_footnotes_p = 0;

static char *footnote_nodename = "*Footnotes*";

#define FOOTNOTE_HEADER_FORMAT \
   "*** Footnotes appearing in the node \"%s\" ***\n"

/* Find the window currently showing footnotes. */
static WINDOW *
find_footnotes_window ()
{
  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;
{
  NODE *fn_node, *result = (NODE *)NULL;
  long fn_start;

  /* 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. */
  fn_start =
    info_search_in_node (FOOTNOTE_LABEL, node, 0, (WINDOW *)NULL, 1, 0);

  /* If it doesn't, check to see if it has an associated footnotes node. */
  if (fn_start == -1)
    {
      REFERENCE **refs;

      refs = info_xrefs_of_node (node);

      if (refs)
        {
          register int i;
          char *refname;
          int reflen = strlen ("-Footnotes") + strlen (node->nodename);

          refname = (char *)xmalloc (reflen + 1);

          strcpy (refname, node->nodename);
          strcat (refname, "-Footnotes");

          for (i = 0; refs[i]; i++)
            if ((refs[i]->nodename != (char *)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]))))
              {
                char *filename;

                filename = node->parent;
                if (!filename)
                  filename = node->filename;

                fn_node = info_get_node (filename, refname);

                if (fn_node)
                  fn_start = 0;

                break;
              }

          free (refname);
          info_free_references (refs);
        }
    }

  /* If we never found the start of a footnotes area, quit now. */
  if (fn_start == -1)
    return ((NODE *)NULL);

  /* Make the new node. */
  result = (NODE *)xmalloc (sizeof (NODE));
  result->flags = 0;
  result->display_pos = 0;

  /* Get the size of the footnotes appearing within this node. */
  {
    char *header;
    long text_start = fn_start;

    header = (char *)xmalloc
      (1 + strlen (node->nodename) + strlen (FOOTNOTE_HEADER_FORMAT));
    sprintf (header, FOOTNOTE_HEADER_FORMAT, 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 = (char *)xmalloc (1 + result->nodelen);
    sprintf (result->contents, "%s", header);
    memcpy (result->contents + strlen (header),
            fn_node->contents + text_start, fn_node->nodelen - text_start);

    name_internal_node (result, footnote_nodename);
    free (header);
  }

#if defined (NOTDEF)
  /* If the footnotes were gleaned from the node that we were called with,
     shorten the calling node's display length. */
  if (fn_node == node)
    narrow_node (node, 0, fn_start);
#endif /* NOTDEF */

  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;
{
  WINDOW *fn_win;
  NODE *new_footnotes;

  fn_win = find_footnotes_window ();

  /* If we are in the footnotes window, change nothing. */
  if (fn_win == window)
    return (FN_FOUND);

  /* Try to find footnotes for this window's node. */
  new_footnotes = make_footnotes_node (window->node);

  /* If there was a window showing footnotes, and there are no footnotes
     for the current window, delete the old footnote window. */
  if (fn_win && !new_footnotes)
    {
      if (windows->next)
        info_delete_window_internal (fn_win);
    }

  /* If there are footnotes for this window's node, but no window around
     showing footnotes, try to make a new window. */
  if (new_footnotes && !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 (new_footnotes);
      active_window = old_active;

      if (!fn_win)
        {
          free (new_footnotes->contents);
          free (new_footnotes);

          /* If we are hacking automatic footnotes, and there are footnotes
             but we couldn't display them, print a message to that effect. */
          if (auto_footnotes_p)
            inform_in_echo_area (_("Footnotes could not be displayed"));
          return (FN_UNABLE);
        }
    }

  /* If there are footnotes, and there is a window to display them,
     make that window be the number of lines appearing in the footnotes. */
  if (new_footnotes && fn_win)
    {
      window_set_node_of_window (fn_win, new_footnotes);

      window_change_window_height
        (fn_win, fn_win->line_count - fn_win->height);

      remember_window_and_node (fn_win, new_footnotes);
      add_gcable_pointer (new_footnotes->contents);
    }

  if (!new_footnotes)
    return (FN_UNFOUND);
  else
    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"))
{
  /* A negative argument means just make the window go away. */
  if (count < 0)
    {
      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);
    }
  else
    {
      int result;

      result = info_get_or_remove_footnotes (window);

      switch (result)
        {
        case FN_UNFOUND:
          info_error (msg_no_foot_node);
          break;

        case FN_UNABLE:
          info_error (msg_win_too_small);
          break;
        }
    }
}