window.c revision 93139
1/* window.c -- windows in Info.
2   $Id: window.c,v 1.15 2002/01/19 01:08:20 karl Exp $
3
4   Copyright (C) 1993, 97, 98, 2001, 02 Free Software Foundation, Inc.
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 2, or (at your option)
9   any later version.
10
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with this program; if not, write to the Free Software
18   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20   Written by Brian Fox (bfox@ai.mit.edu). */
21
22#include "info.h"
23#include "nodes.h"
24#include "window.h"
25#include "display.h"
26#include "info-utils.h"
27#include "infomap.h"
28
29/* The window which describes the screen. */
30WINDOW *the_screen = NULL;
31
32/* The window which describes the echo area. */
33WINDOW *the_echo_area = NULL;
34
35/* The list of windows in Info. */
36WINDOW *windows = NULL;
37
38/* Pointer to the active window in WINDOW_LIST. */
39WINDOW *active_window = NULL;
40
41/* The size of the echo area in Info.  It never changes, irregardless of the
42   size of the screen. */
43#define ECHO_AREA_HEIGHT 1
44
45/* Macro returns the amount of space that the echo area truly requires relative
46   to the entire screen. */
47#define echo_area_required (1 + the_echo_area->height)
48
49/* Initalize the window system by creating THE_SCREEN and THE_ECHO_AREA.
50   Create the first window ever.
51   You pass the dimensions of the total screen size. */
52void
53window_initialize_windows (width, height)
54     int width, height;
55{
56  the_screen = xmalloc (sizeof (WINDOW));
57  the_echo_area = xmalloc (sizeof (WINDOW));
58  windows = xmalloc (sizeof (WINDOW));
59  active_window = windows;
60
61  zero_mem (the_screen, sizeof (WINDOW));
62  zero_mem (the_echo_area, sizeof (WINDOW));
63  zero_mem (active_window, sizeof (WINDOW));
64
65  /* None of these windows has a goal column yet. */
66  the_echo_area->goal_column = -1;
67  active_window->goal_column = -1;
68  the_screen->goal_column = -1;
69
70  /* The active and echo_area windows are visible.
71     The echo_area is permanent.
72     The screen is permanent. */
73  active_window->flags = W_WindowVisible;
74  the_echo_area->flags = W_WindowIsPerm | W_InhibitMode | W_WindowVisible;
75  the_screen->flags    = W_WindowIsPerm;
76
77  /* The height of the echo area never changes.  It is statically set right
78     here, and it must be at least 1 line for display.  The size of the
79     initial window cannot be the same size as the screen, since the screen
80     includes the echo area.  So, we make the height of the initial window
81     equal to the screen's displayable region minus the height of the echo
82     area. */
83  the_echo_area->height = ECHO_AREA_HEIGHT;
84  active_window->height = the_screen->height - 1 - the_echo_area->height;
85  window_new_screen_size (width, height, NULL);
86
87  /* The echo area uses a different keymap than normal info windows. */
88  the_echo_area->keymap = echo_area_keymap;
89  active_window->keymap = info_keymap;
90}
91
92/* Given that the size of the screen has changed to WIDTH and HEIGHT
93   from whatever it was before (found in the_screen->height, ->width),
94   change the size (and possibly location) of each window in the screen.
95   If a window would become too small, call the function DELETER on it,
96   after deleting the window from our chain of windows.  If DELETER is NULL,
97   nothing extra is done.  The last window can never be deleted, but it can
98   become invisible. */
99
100/* If non-null, a function to call with WINDOW as argument when the function
101   window_new_screen_size () has deleted WINDOW. */
102VFunction *window_deletion_notifier = NULL;
103
104void
105window_new_screen_size (width, height)
106     int width, height;
107{
108  register WINDOW *win;
109  int delta_height, delta_each, delta_leftover;
110  int numwins;
111
112  /* If no change, do nothing. */
113  if (width == the_screen->width && height == the_screen->height)
114    return;
115
116  /* If the new window height is too small, make it be zero. */
117  if (height < (WINDOW_MIN_SIZE + the_echo_area->height))
118    height = 0;
119  if (width < 0)
120    width = 0;
121
122  /* Find out how many windows will change. */
123  for (numwins = 0, win = windows; win; win = win->next, numwins++);
124
125  /* See if some windows will need to be deleted.  This is the case if
126     the screen is getting smaller, and the available space divided by
127     the number of windows is less than WINDOW_MIN_SIZE.  In that case,
128     delete some windows and try again until there is either enough
129     space to divy up among the windows, or until there is only one
130     window left. */
131  while ((height - echo_area_required) / numwins <= WINDOW_MIN_SIZE)
132    {
133      /* If only one window, make the size of it be zero, and return
134         immediately. */
135      if (!windows->next)
136        {
137          windows->height = 0;
138          maybe_free (windows->line_starts);
139          windows->line_starts = NULL;
140          windows->line_count = 0;
141          break;
142        }
143
144      /* If we have some temporary windows, delete one of them. */
145      for (win = windows; win; win = win->next)
146        if (win->flags & W_TempWindow)
147          break;
148
149      /* Otherwise, delete the first window, and try again. */
150      if (!win)
151        win = windows;
152
153      if (window_deletion_notifier)
154        (*window_deletion_notifier) (win);
155
156      window_delete_window (win);
157      numwins--;
158    }
159
160  /* The screen has changed height and width. */
161  delta_height = height - the_screen->height;   /* This is how much. */
162  the_screen->height = height;                  /* This is the new height. */
163  the_screen->width = width;                    /* This is the new width. */
164
165  /* Set the start of the echo area. */
166  the_echo_area->first_row = height - the_echo_area->height;
167  the_echo_area->width = width;
168
169  /* Check to see if the screen can really be changed this way. */
170  if ((!windows->next) && ((windows->height == 0) && (delta_height < 0)))
171    return;
172
173  /* Divide the change in height among the available windows. */
174  delta_each = delta_height / numwins;
175  delta_leftover = delta_height - (delta_each * numwins);
176
177  /* Change the height of each window in the chain by delta_each.  Change
178     the height of the last window in the chain by delta_each and by the
179     leftover amount of change.  Change the width of each window to be
180     WIDTH. */
181  for (win = windows; win; win = win->next)
182    {
183      if ((win->width != width) && ((win->flags & W_InhibitMode) == 0))
184        {
185          win->width = width;
186          maybe_free (win->modeline);
187          win->modeline = xmalloc (1 + width);
188        }
189
190      win->height += delta_each;
191
192      /* If the previous height of this window was zero, it was the only
193         window, and it was not visible.  Thus we need to compensate for
194         the echo_area. */
195      if (win->height == delta_each)
196        win->height -= (1 + the_echo_area->height);
197
198      /* If this is not the first window in the chain, then change the
199         first row of it.  We cannot just add delta_each to the first row,
200         since this window's first row is the sum of the collective increases
201         that have gone before it.  So we just add one to the location of the
202         previous window's modeline. */
203      if (win->prev)
204        win->first_row = (win->prev->first_row + win->prev->height) + 1;
205
206      /* The last window in the chain gets the extra space (or shrinkage). */
207      if (!win->next)
208        win->height += delta_leftover;
209
210      if (win->node)
211        recalculate_line_starts (win);
212
213      win->flags |= W_UpdateWindow;
214    }
215
216  /* If the screen got smaller, check over the windows just shrunk to
217     keep them within bounds.  Some of the windows may have gotten smaller
218     than WINDOW_MIN_HEIGHT in which case some of the other windows are
219     larger than the available display space in the screen.  Because of our
220     intial test above, we know that there is enough space for all of the
221     windows. */
222  if ((delta_each < 0) && ((windows->height != 0) && windows->next))
223    {
224      int avail;
225
226      avail = the_screen->height - (numwins + the_echo_area->height);
227      win = windows;
228
229      while (win)
230        {
231          if ((win->height < WINDOW_MIN_HEIGHT) ||
232              (win->height > avail))
233            {
234              WINDOW *lastwin;
235
236              /* Split the space among the available windows. */
237              delta_each = avail / numwins;
238              delta_leftover = avail - (delta_each * numwins);
239
240              for (win = windows; win; win = win->next)
241                {
242                  lastwin = win;
243                  if (win->prev)
244                    win->first_row =
245                      (win->prev->first_row + win->prev->height) + 1;
246                  win->height = delta_each;
247                }
248
249              /* Give the leftover space (if any) to the last window. */
250              lastwin->height += delta_leftover;
251              break;
252            }
253          else
254            win= win->next;
255        }
256    }
257}
258
259/* Make a new window showing NODE, and return that window structure.
260   If NODE is passed as NULL, then show the node showing in the active
261   window.  If the window could not be made return a NULL pointer.  The
262   active window is not changed.*/
263WINDOW *
264window_make_window (node)
265     NODE *node;
266{
267  WINDOW *window;
268
269  if (!node)
270    node = active_window->node;
271
272  /* If there isn't enough room to make another window, return now. */
273  if ((active_window->height / 2) < WINDOW_MIN_SIZE)
274    return (NULL);
275
276  /* Make and initialize the new window.
277     The fudging about with -1 and +1 is because the following window in the
278     chain cannot start at window->height, since that is where the modeline
279     for the previous window is displayed.  The inverse adjustment is made
280     in window_delete_window (). */
281  window = xmalloc (sizeof (WINDOW));
282  window->width = the_screen->width;
283  window->height = (active_window->height / 2) - 1;
284#if defined (SPLIT_BEFORE_ACTIVE)
285  window->first_row = active_window->first_row;
286#else
287  window->first_row = active_window->first_row +
288    (active_window->height - window->height);
289#endif
290  window->keymap = info_keymap;
291  window->goal_column = -1;
292  window->modeline = xmalloc (1 + window->width);
293  window->line_starts = NULL;
294  window->flags = W_UpdateWindow | W_WindowVisible;
295  window_set_node_of_window (window, node);
296
297  /* Adjust the height of the old active window. */
298  active_window->height -= (window->height + 1);
299#if defined (SPLIT_BEFORE_ACTIVE)
300  active_window->first_row += (window->height + 1);
301#endif
302  active_window->flags |= W_UpdateWindow;
303
304  /* Readjust the new and old windows so that their modelines and contents
305     will be displayed correctly. */
306#if defined (NOTDEF)
307  /* We don't have to do this for WINDOW since window_set_node_of_window ()
308     already did. */
309  window_adjust_pagetop (window);
310  window_make_modeline (window);
311#endif /* NOTDEF */
312
313  /* We do have to readjust the existing active window. */
314  window_adjust_pagetop (active_window);
315  window_make_modeline (active_window);
316
317#if defined (SPLIT_BEFORE_ACTIVE)
318  /* This window is just before the active one.  The active window gets
319     bumped down one.  The active window is not changed. */
320  window->next = active_window;
321
322  window->prev = active_window->prev;
323  active_window->prev = window;
324
325  if (window->prev)
326    window->prev->next = window;
327  else
328    windows = window;
329#else
330  /* This window is just after the active one.  Which window is active is
331     not changed. */
332  window->prev = active_window;
333  window->next = active_window->next;
334  active_window->next = window;
335  if (window->next)
336    window->next->prev = window;
337#endif /* !SPLIT_BEFORE_ACTIVE */
338  return (window);
339}
340
341/* These useful macros make it possible to read the code in
342   window_change_window_height (). */
343#define grow_me_shrinking_next(me, next, diff) \
344  do { \
345    me->height += diff; \
346    next->height -= diff; \
347    next->first_row += diff; \
348    window_adjust_pagetop (next); \
349  } while (0)
350
351#define grow_me_shrinking_prev(me, prev, diff) \
352  do { \
353    me->height += diff; \
354    prev->height -= diff; \
355    me->first_row -=diff; \
356    window_adjust_pagetop (prev); \
357  } while (0)
358
359#define shrink_me_growing_next(me, next, diff) \
360  do { \
361    me->height -= diff; \
362    next->height += diff; \
363    next->first_row -= diff; \
364    window_adjust_pagetop (next); \
365  } while (0)
366
367#define shrink_me_growing_prev(me, prev, diff) \
368  do { \
369    me->height -= diff; \
370    prev->height += diff; \
371    me->first_row += diff; \
372    window_adjust_pagetop (prev); \
373  } while (0)
374
375/* Change the height of WINDOW by AMOUNT.  This also automagically adjusts
376   the previous and next windows in the chain.  If there is only one user
377   window, then no change takes place. */
378void
379window_change_window_height (window, amount)
380     WINDOW *window;
381     int amount;
382{
383  register WINDOW *win, *prev, *next;
384
385  /* If there is only one window, or if the amount of change is zero,
386     return immediately. */
387  if (!windows->next || amount == 0)
388    return;
389
390  /* Find this window in our chain. */
391  for (win = windows; win; win = win->next)
392    if (win == window)
393      break;
394
395  /* If the window is isolated (i.e., doesn't appear in our window list,
396     then quit now. */
397  if (!win)
398    return;
399
400  /* Change the height of this window by AMOUNT, if that is possible.
401     It can be impossible if there isn't enough available room on the
402     screen, or if the resultant window would be too small. */
403
404    prev = window->prev;
405    next = window->next;
406
407  /* WINDOW decreasing in size? */
408  if (amount < 0)
409    {
410      int abs_amount = -amount; /* It is easier to deal with this way. */
411
412      /* If the resultant window would be too small, stop here. */
413      if ((window->height - abs_amount) < WINDOW_MIN_HEIGHT)
414        return;
415
416      /* If we have two neighboring windows, choose the smaller one to get
417         larger. */
418      if (next && prev)
419        {
420          if (prev->height < next->height)
421            shrink_me_growing_prev (window, prev, abs_amount);
422          else
423            shrink_me_growing_next (window, next, abs_amount);
424        }
425      else if (next)
426        shrink_me_growing_next (window, next, abs_amount);
427      else
428        shrink_me_growing_prev (window, prev, abs_amount);
429    }
430
431  /* WINDOW increasing in size? */
432  if (amount > 0)
433    {
434      int total_avail, next_avail = 0, prev_avail = 0;
435
436      if (next)
437        next_avail = next->height - WINDOW_MIN_SIZE;
438
439      if (prev)
440        prev_avail = prev->height - WINDOW_MIN_SIZE;
441
442      total_avail = next_avail + prev_avail;
443
444      /* If there isn't enough space available to grow this window, give up. */
445      if (amount > total_avail)
446        return;
447
448      /* If there aren't two neighboring windows, or if one of the neighbors
449         is larger than the other one by at least AMOUNT, grow that one. */
450      if ((next && !prev) || ((next_avail - amount) >= prev_avail))
451        grow_me_shrinking_next (window, next, amount);
452      else if ((prev && !next) || ((prev_avail - amount) >= next_avail))
453        grow_me_shrinking_prev (window, prev, amount);
454      else
455        {
456          int change;
457
458          /* This window has two neighbors.  They both must be shrunk in to
459             make enough space for WINDOW to grow.  Make them both the same
460             size. */
461          if (prev_avail > next_avail)
462            {
463              change = prev_avail - next_avail;
464              grow_me_shrinking_prev (window, prev, change);
465              amount -= change;
466            }
467          else
468            {
469              change = next_avail - prev_avail;
470              grow_me_shrinking_next (window, next, change);
471              amount -= change;
472            }
473
474          /* Both neighbors are the same size.  Split the difference in
475             AMOUNT between them. */
476          while (amount)
477            {
478              window->height++;
479              amount--;
480
481              /* Odd numbers grow next, even grow prev. */
482              if (amount & 1)
483                {
484                  prev->height--;
485                  window->first_row--;
486                }
487              else
488                {
489                  next->height--;
490                  next->first_row++;
491                }
492            }
493          window_adjust_pagetop (prev);
494          window_adjust_pagetop (next);
495        }
496    }
497  if (prev)
498    prev->flags |= W_UpdateWindow;
499
500  if (next)
501    next->flags |= W_UpdateWindow;
502
503  window->flags |= W_UpdateWindow;
504  window_adjust_pagetop (window);
505}
506
507/* Tile all of the windows currently displayed in the global variable
508   WINDOWS.  If argument STYLE is TILE_INTERNALS, tile windows displaying
509   internal nodes as well, otherwise do not change the height of such
510   windows. */
511void
512window_tile_windows (style)
513     int style;
514{
515  WINDOW *win, *last_adjusted;
516  int numwins, avail, per_win_height, leftover;
517  int do_internals;
518
519  numwins = avail = 0;
520  do_internals = (style == TILE_INTERNALS);
521
522  for (win = windows; win; win = win->next)
523    if (do_internals || !win->node ||
524        (win->node->flags & N_IsInternal) == 0)
525      {
526        avail += win->height;
527        numwins++;
528      }
529
530  if (numwins <= 1 || !the_screen->height)
531    return;
532
533  /* Find the size for each window.  Divide the size of the usable portion
534     of the screen by the number of windows. */
535  per_win_height = avail / numwins;
536  leftover = avail - (per_win_height * numwins);
537
538  last_adjusted = NULL;
539  for (win = windows; win; win = win->next)
540    {
541      if (do_internals || !win->node ||
542          (win->node->flags & N_IsInternal) == 0)
543        {
544          last_adjusted = win;
545          win->height = per_win_height;
546        }
547    }
548
549  if (last_adjusted)
550    last_adjusted->height += leftover;
551
552  /* Readjust the first_row of every window in the chain. */
553  for (win = windows; win; win = win->next)
554    {
555      if (win->prev)
556        win->first_row = win->prev->first_row + win->prev->height + 1;
557
558      window_adjust_pagetop (win);
559      win->flags |= W_UpdateWindow;
560    }
561}
562
563/* Toggle the state of line wrapping in WINDOW.  This can do a bit of fancy
564   redisplay. */
565void
566window_toggle_wrap (window)
567     WINDOW *window;
568{
569  if (window->flags & W_NoWrap)
570    window->flags &= ~W_NoWrap;
571  else
572    window->flags |= W_NoWrap;
573
574  if (window != the_echo_area)
575    {
576      char **old_starts;
577      int old_lines, old_pagetop;
578
579      old_starts = window->line_starts;
580      old_lines = window->line_count;
581      old_pagetop = window->pagetop;
582
583      calculate_line_starts (window);
584
585      /* Make sure that point appears within this window. */
586      window_adjust_pagetop (window);
587
588      /* If the pagetop hasn't changed maybe we can do some scrolling now
589         to speed up the display.  Many of the line starts will be the same,
590         so scrolling here is a very good optimization.*/
591      if (old_pagetop == window->pagetop)
592        display_scroll_line_starts
593          (window, old_pagetop, old_starts, old_lines);
594      maybe_free (old_starts);
595    }
596  window->flags |= W_UpdateWindow;
597}
598
599/* Set WINDOW to display NODE. */
600void
601window_set_node_of_window (window, node)
602     WINDOW *window;
603     NODE *node;
604{
605  window->node = node;
606  window->pagetop = 0;
607  window->point = 0;
608  recalculate_line_starts (window);
609  window->flags |= W_UpdateWindow;
610  /* The display_pos member is nonzero if we're displaying an anchor.  */
611  window->point = node ? node->display_pos : 0;
612  window_adjust_pagetop (window);
613  window_make_modeline (window);
614}
615
616/* Delete WINDOW from the list of known windows.  If this window was the
617   active window, make the next window in the chain be the active window.
618   If the active window is the next or previous window, choose that window
619   as the recipient of the extra space.  Otherwise, prefer the next window. */
620void
621window_delete_window (window)
622     WINDOW *window;
623{
624  WINDOW *next, *prev, *window_to_fix;
625
626  next = window->next;
627  prev = window->prev;
628
629  /* You cannot delete the only window or a permanent window. */
630  if ((!next && !prev) || (window->flags & W_WindowIsPerm))
631    return;
632
633  if (next)
634    next->prev = prev;
635
636  if (!prev)
637    windows = next;
638  else
639    prev->next = next;
640
641  if (window->line_starts)
642    free (window->line_starts);
643
644  if (window->modeline)
645    free (window->modeline);
646
647  if (window == active_window)
648    {
649      /* If there isn't a next window, then there must be a previous one,
650         since we cannot delete the last window.  If there is a next window,
651         prefer to use that as the active window. */
652      if (next)
653        active_window = next;
654      else
655        active_window = prev;
656    }
657
658  if (next && active_window == next)
659    window_to_fix = next;
660  else if (prev && active_window == prev)
661    window_to_fix = prev;
662  else if (next)
663    window_to_fix = next;
664  else if (prev)
665    window_to_fix = prev;
666  else
667    window_to_fix = windows;
668
669  if (window_to_fix->first_row > window->first_row)
670    {
671      int diff;
672
673      /* Try to adjust the visible part of the node so that as little
674         text as possible has to move. */
675      diff = window_to_fix->first_row - window->first_row;
676      window_to_fix->first_row = window->first_row;
677
678      window_to_fix->pagetop -= diff;
679      if (window_to_fix->pagetop < 0)
680        window_to_fix->pagetop = 0;
681    }
682
683  /* The `+ 1' is to offset the difference between the first_row locations.
684     See the code in window_make_window (). */
685  window_to_fix->height += window->height + 1;
686  window_to_fix->flags |= W_UpdateWindow;
687
688  free (window);
689}
690
691/* For every window in CHAIN, set the flags member to have FLAG set. */
692void
693window_mark_chain (chain, flag)
694     WINDOW *chain;
695     int flag;
696{
697  register WINDOW *win;
698
699  for (win = chain; win; win = win->next)
700    win->flags |= flag;
701}
702
703/* For every window in CHAIN, clear the flags member of FLAG. */
704void
705window_unmark_chain (chain, flag)
706     WINDOW *chain;
707     int flag;
708{
709  register WINDOW *win;
710
711  for (win = chain; win; win = win->next)
712    win->flags &= ~flag;
713}
714
715/* Return the number of characters it takes to display CHARACTER on the
716   screen at HPOS. */
717int
718character_width (character, hpos)
719     int character, hpos;
720{
721  int printable_limit = 127;
722  int width = 1;
723
724  if (ISO_Latin_p)
725    printable_limit = 255;
726
727  if (character > printable_limit)
728    width = 3;
729  else if (iscntrl (character))
730    {
731      switch (character)
732        {
733        case '\r':
734        case '\n':
735          width = the_screen->width - hpos;
736          break;
737        case '\t':
738          width = ((hpos + 8) & 0xf8) - hpos;
739          break;
740        default:
741          width = 2;
742        }
743    }
744  else if (character == DEL)
745    width = 2;
746
747  return (width);
748}
749
750/* Return the number of characters it takes to display STRING on the screen
751   at HPOS. */
752int
753string_width (string, hpos)
754     char *string;
755     int hpos;
756{
757  register int i, width, this_char_width;
758
759  for (width = 0, i = 0; string[i]; i++)
760    {
761      this_char_width = character_width (string[i], hpos);
762      width += this_char_width;
763      hpos += this_char_width;
764    }
765  return (width);
766}
767
768/* Quickly guess the approximate number of lines that NODE would
769   take to display.  This really only counts carriage returns. */
770int
771window_physical_lines (node)
772     NODE *node;
773{
774  register int i, lines;
775  char *contents;
776
777  if (!node)
778    return (0);
779
780  contents = node->contents;
781  for (i = 0, lines = 1; i < node->nodelen; i++)
782    if (contents[i] == '\n')
783      lines++;
784
785  return (lines);
786}
787
788/* Calculate a list of line starts for the node belonging to WINDOW.  The line
789   starts are pointers to the actual text within WINDOW->NODE. */
790void
791calculate_line_starts (window)
792     WINDOW *window;
793{
794  register int i, hpos;
795  char **line_starts = NULL;
796  int line_starts_index = 0, line_starts_slots = 0;
797  int bump_index;
798  NODE *node;
799
800  window->line_starts = NULL;
801  window->line_count = 0;
802  node = window->node;
803
804  if (!node)
805    return;
806
807  /* Grovel the node starting at the top, and for each line calculate the
808     width of the characters appearing in that line.  Add each line start
809     to our array. */
810  i = 0;
811  hpos = 0;
812  bump_index = 0;
813
814  while (i < node->nodelen)
815    {
816      char *line = node->contents + i;
817      unsigned int cwidth, c;
818
819      add_pointer_to_array (line, line_starts_index, line_starts,
820                            line_starts_slots, 100, char *);
821      if (bump_index)
822        {
823          i++;
824          bump_index = 0;
825        }
826
827      while (1)
828        {
829	  /* The cast to unsigned char is for 8-bit characters, which
830	     could be passed as negative integers to character_width
831	     and wreak havoc on some naive implementations of iscntrl.  */
832          c = (unsigned char) node->contents[i];
833          cwidth = character_width (c, hpos);
834
835          /* If this character fits within this line, just do the next one. */
836          if ((hpos + cwidth) < window->width)
837            {
838              i++;
839              hpos += cwidth;
840              continue;
841            }
842          else
843            {
844              /* If this character would position the cursor at the start of
845                 the next printed screen line, then do the next line. */
846              if (c == '\n' || c == '\r' || c == '\t')
847                {
848                  i++;
849                  hpos = 0;
850                  break;
851                }
852              else
853                {
854                  /* This character passes the window width border.  Postion
855                     the cursor after the printed character, but remember this
856                     line start as where this character is.  A bit tricky. */
857
858                  /* If this window doesn't wrap lines, proceed to the next
859                     physical line here. */
860                  if (window->flags & W_NoWrap)
861                    {
862                      hpos = 0;
863                      while (i < node->nodelen && node->contents[i] != '\n')
864                        i++;
865
866                      if (node->contents[i] == '\n')
867                        i++;
868                    }
869                  else
870                    {
871                      hpos = the_screen->width - hpos;
872                      bump_index++;
873                    }
874                  break;
875                }
876            }
877        }
878    }
879  window->line_starts = line_starts;
880  window->line_count = line_starts_index;
881}
882
883/* Given WINDOW, recalculate the line starts for the node it displays. */
884void
885recalculate_line_starts (window)
886     WINDOW *window;
887{
888  maybe_free (window->line_starts);
889  calculate_line_starts (window);
890}
891
892/* Global variable control redisplay of scrolled windows.  If non-zero, it
893   is the desired number of lines to scroll the window in order to make
894   point visible.  A user might set this to 1 for smooth scrolling.  If
895   set to zero, the line containing point is centered within the window. */
896int window_scroll_step = 0;
897
898/* Adjust the pagetop of WINDOW such that the cursor point will be visible. */
899void
900window_adjust_pagetop (window)
901     WINDOW *window;
902{
903  register int line = 0;
904  char *contents;
905
906  if (!window->node)
907    return;
908
909  contents = window->node->contents;
910
911  /* Find the first printed line start which is after WINDOW->point. */
912  for (line = 0; line < window->line_count; line++)
913    {
914      char *line_start;
915
916      line_start = window->line_starts[line];
917
918      if ((line_start - contents) > window->point)
919        break;
920    }
921
922  /* The line index preceding the line start which is past point is the
923     one containing point. */
924  line--;
925
926  /* If this line appears in the current displayable page, do nothing.
927     Otherwise, adjust the top of the page to make this line visible. */
928  if ((line < window->pagetop) ||
929      (line - window->pagetop > (window->height - 1)))
930    {
931      /* The user-settable variable "scroll-step" is used to attempt
932         to make point visible, iff it is non-zero.  If that variable
933         is zero, then the line containing point is centered within
934         the window. */
935      if (window_scroll_step < window->height)
936        {
937          if ((line < window->pagetop) &&
938              ((window->pagetop - window_scroll_step) <= line))
939            window->pagetop -= window_scroll_step;
940          else if ((line - window->pagetop > (window->height - 1)) &&
941                   ((line - (window->pagetop + window_scroll_step)
942                     < window->height)))
943            window->pagetop += window_scroll_step;
944          else
945            window->pagetop = line - ((window->height - 1) / 2);
946        }
947      else
948        window->pagetop = line - ((window->height - 1) / 2);
949
950      if (window->pagetop < 0)
951        window->pagetop = 0;
952      window->flags |= W_UpdateWindow;
953    }
954}
955
956/* Return the index of the line containing point. */
957int
958window_line_of_point (window)
959     WINDOW *window;
960{
961  register int i, start = 0;
962
963  /* Try to optimize.  Check to see if point is past the pagetop for
964     this window, and if so, start searching forward from there. */
965  if ((window->pagetop > -1 && window->pagetop < window->line_count) &&
966      (window->line_starts[window->pagetop] - window->node->contents)
967      <= window->point)
968    start = window->pagetop;
969
970  for (i = start; i < window->line_count; i++)
971    {
972      if ((window->line_starts[i] - window->node->contents) > window->point)
973        break;
974    }
975
976  return (i - 1);
977}
978
979/* Get and return the goal column for this window. */
980int
981window_get_goal_column (window)
982     WINDOW *window;
983{
984  if (!window->node)
985    return (-1);
986
987  if (window->goal_column != -1)
988    return (window->goal_column);
989
990  /* Okay, do the work.  Find the printed offset of the cursor
991     in this window. */
992  return (window_get_cursor_column (window));
993}
994
995/* Get and return the printed column offset of the cursor in this window. */
996int
997window_get_cursor_column (window)
998     WINDOW *window;
999{
1000  int i, hpos, end;
1001  char *line;
1002
1003  i = window_line_of_point (window);
1004
1005  if (i < 0)
1006    return (-1);
1007
1008  line = window->line_starts[i];
1009  end = window->point - (line - window->node->contents);
1010
1011  for (hpos = 0, i = 0; i < end; i++)
1012    hpos += character_width (line[i], hpos);
1013
1014  return (hpos);
1015}
1016
1017/* Count the number of characters in LINE that precede the printed column
1018   offset of GOAL. */
1019int
1020window_chars_to_goal (line, goal)
1021     char *line;
1022     int goal;
1023{
1024  register int i, check, hpos;
1025
1026  for (hpos = 0, i = 0; line[i] != '\n'; i++)
1027    {
1028
1029      check = hpos + character_width (line[i], hpos);
1030
1031      if (check > goal)
1032        break;
1033
1034      hpos = check;
1035    }
1036  return (i);
1037}
1038
1039/* Create a modeline for WINDOW, and store it in window->modeline. */
1040void
1041window_make_modeline (window)
1042     WINDOW *window;
1043{
1044  register int i;
1045  char *modeline;
1046  char location_indicator[4];
1047  int lines_remaining;
1048
1049  /* Only make modelines for those windows which have one. */
1050  if (window->flags & W_InhibitMode)
1051    return;
1052
1053  /* Find the number of lines actually displayed in this window. */
1054  lines_remaining = window->line_count - window->pagetop;
1055
1056  if (window->pagetop == 0)
1057    {
1058      if (lines_remaining <= window->height)
1059        strcpy (location_indicator, "All");
1060      else
1061        strcpy (location_indicator, "Top");
1062    }
1063  else
1064    {
1065      if (lines_remaining <= window->height)
1066        strcpy (location_indicator, "Bot");
1067      else
1068        {
1069          float pt, lc;
1070          int percentage;
1071
1072          pt = (float)window->pagetop;
1073          lc = (float)window->line_count;
1074
1075          percentage = 100 * (pt / lc);
1076
1077          sprintf (location_indicator, "%2d%%", percentage);
1078        }
1079    }
1080
1081  /* Calculate the maximum size of the information to stick in MODELINE. */
1082  {
1083    int modeline_len = 0;
1084    char *parent = NULL, *filename = "*no file*";
1085    char *nodename = "*no node*";
1086    char *update_message = NULL;
1087    NODE *node = window->node;
1088
1089    if (node)
1090      {
1091        if (node->nodename)
1092          nodename = node->nodename;
1093
1094        if (node->parent)
1095          {
1096            parent = filename_non_directory (node->parent);
1097            modeline_len += strlen ("Subfile: ") + strlen (node->filename);
1098          }
1099
1100        if (node->filename)
1101          filename = filename_non_directory (node->filename);
1102
1103        if (node->flags & N_UpdateTags)
1104          update_message = _("--*** Tags out of Date ***");
1105      }
1106
1107    if (update_message)
1108      modeline_len += strlen (update_message);
1109    modeline_len += strlen (filename);
1110    modeline_len += strlen (nodename);
1111    modeline_len += 4;          /* strlen (location_indicator). */
1112
1113    /* 10 for the decimal representation of the number of lines in this
1114       node, and the remainder of the text that can appear in the line. */
1115    modeline_len += 10 + strlen (_("-----Info: (), lines ----, "));
1116    modeline_len += window->width;
1117
1118    modeline = xmalloc (1 + modeline_len);
1119
1120    /* Special internal windows have no filename. */
1121    if (!parent && !*filename)
1122      sprintf (modeline, _("-%s---Info: %s, %d lines --%s--"),
1123               (window->flags & W_NoWrap) ? "$" : "-",
1124               nodename, window->line_count, location_indicator);
1125    else
1126      sprintf (modeline, _("-%s%s-Info: (%s)%s, %d lines --%s--"),
1127               (window->flags & W_NoWrap) ? "$" : "-",
1128               (node && (node->flags & N_IsCompressed)) ? "zz" : "--",
1129               parent ? parent : filename,
1130               nodename, window->line_count, location_indicator);
1131
1132    if (parent)
1133      sprintf (modeline + strlen (modeline), _(" Subfile: %s"), filename);
1134
1135    if (update_message)
1136      sprintf (modeline + strlen (modeline), "%s", update_message);
1137
1138    i = strlen (modeline);
1139
1140    if (i >= window->width)
1141      modeline[window->width] = '\0';
1142    else
1143      {
1144        while (i < window->width)
1145          modeline[i++] = '-';
1146        modeline[i] = '\0';
1147      }
1148
1149    strcpy (window->modeline, modeline);
1150    free (modeline);
1151  }
1152}
1153
1154/* Make WINDOW start displaying at PERCENT percentage of its node. */
1155void
1156window_goto_percentage (window, percent)
1157     WINDOW *window;
1158     int percent;
1159{
1160  int desired_line;
1161
1162  if (!percent)
1163    desired_line = 0;
1164  else
1165    desired_line =
1166      (int) ((float)window->line_count * ((float)percent / 100.0));
1167
1168  window->pagetop = desired_line;
1169  window->point =
1170    window->line_starts[window->pagetop] - window->node->contents;
1171  window->flags |= W_UpdateWindow;
1172  window_make_modeline (window);
1173}
1174
1175/* Get the state of WINDOW, and save it in STATE. */
1176void
1177window_get_state (window, state)
1178     WINDOW *window;
1179     WINDOW_STATE *state;
1180{
1181  state->node = window->node;
1182  state->pagetop = window->pagetop;
1183  state->point = window->point;
1184}
1185
1186/* Set the node, pagetop, and point of WINDOW. */
1187void
1188window_set_state (window, state)
1189     WINDOW *window;
1190     WINDOW_STATE *state;
1191{
1192  if (window->node != state->node)
1193    window_set_node_of_window (window, state->node);
1194  window->pagetop = state->pagetop;
1195  window->point = state->point;
1196}
1197
1198
1199/* Manipulating home-made nodes.  */
1200
1201/* A place to buffer echo area messages. */
1202static NODE *echo_area_node = NULL;
1203
1204/* Make the node of the_echo_area be an empty one. */
1205static void
1206free_echo_area ()
1207{
1208  if (echo_area_node)
1209    {
1210      maybe_free (echo_area_node->contents);
1211      free (echo_area_node);
1212    }
1213
1214  echo_area_node = NULL;
1215  window_set_node_of_window (the_echo_area, echo_area_node);
1216}
1217
1218/* Clear the echo area, removing any message that is already present.
1219   The echo area is cleared immediately. */
1220void
1221window_clear_echo_area ()
1222{
1223  free_echo_area ();
1224  display_update_one_window (the_echo_area);
1225}
1226
1227/* Make a message appear in the echo area, built from FORMAT, ARG1 and ARG2.
1228   The arguments are treated similar to printf () arguments, but not all of
1229   printf () hair is present.  The message appears immediately.  If there was
1230   already a message appearing in the echo area, it is removed. */
1231void
1232window_message_in_echo_area (format, arg1, arg2)
1233     char *format;
1234     void *arg1, *arg2;
1235{
1236  free_echo_area ();
1237  echo_area_node = build_message_node (format, arg1, arg2);
1238  window_set_node_of_window (the_echo_area, echo_area_node);
1239  display_update_one_window (the_echo_area);
1240}
1241
1242/* Place a temporary message in the echo area built from FORMAT, ARG1
1243   and ARG2.  The message appears immediately, but does not destroy
1244   any existing message.  A future call to unmessage_in_echo_area ()
1245   restores the old contents. */
1246static NODE **old_echo_area_nodes = NULL;
1247static int old_echo_area_nodes_index = 0;
1248static int old_echo_area_nodes_slots = 0;
1249
1250void
1251message_in_echo_area (format, arg1, arg2)
1252     char *format;
1253     void *arg1, *arg2;
1254{
1255  if (echo_area_node)
1256    {
1257      add_pointer_to_array (echo_area_node, old_echo_area_nodes_index,
1258                            old_echo_area_nodes, old_echo_area_nodes_slots,
1259                            4, NODE *);
1260    }
1261  echo_area_node = NULL;
1262  window_message_in_echo_area (format, arg1, arg2);
1263}
1264
1265void
1266unmessage_in_echo_area ()
1267{
1268  free_echo_area ();
1269
1270  if (old_echo_area_nodes_index)
1271    echo_area_node = old_echo_area_nodes[--old_echo_area_nodes_index];
1272
1273  window_set_node_of_window (the_echo_area, echo_area_node);
1274  display_update_one_window (the_echo_area);
1275}
1276
1277/* A place to build a message. */
1278static char *message_buffer = NULL;
1279static int message_buffer_index = 0;
1280static int message_buffer_size = 0;
1281
1282/* Ensure that there is enough space to stuff LENGTH characters into
1283   MESSAGE_BUFFER. */
1284static void
1285message_buffer_resize (length)
1286     int length;
1287{
1288  if (!message_buffer)
1289    {
1290      message_buffer_size = length + 1;
1291      message_buffer = xmalloc (message_buffer_size);
1292      message_buffer_index = 0;
1293    }
1294
1295  while (message_buffer_size <= message_buffer_index + length)
1296    message_buffer = (char *)
1297      xrealloc (message_buffer,
1298                message_buffer_size += 100 + (2 * length));
1299}
1300
1301/* Format MESSAGE_BUFFER with the results of printing FORMAT with ARG1 and
1302   ARG2. */
1303static void
1304build_message_buffer (format, arg1, arg2, arg3)
1305     char *format;
1306     void *arg1, *arg2, *arg3;
1307{
1308  register int i, len;
1309  void *args[3];
1310  int arg_index = 0;
1311
1312  args[0] = arg1;
1313  args[1] = arg2;
1314  args[2] = arg3;
1315
1316  len = strlen (format);
1317
1318  message_buffer_resize (len);
1319
1320  for (i = 0; format[i]; i++)
1321    {
1322      if (format[i] != '%')
1323        {
1324          message_buffer[message_buffer_index++] = format[i];
1325          len--;
1326        }
1327      else
1328        {
1329          char c;
1330          char *fmt_start = format + i;
1331          char *fmt;
1332          int fmt_len, formatted_len;
1333	  int paramed = 0;
1334
1335	format_again:
1336          i++;
1337          while (format[i] && strchr ("-. +0123456789", format[i]))
1338            i++;
1339          c = format[i];
1340
1341          if (c == '\0')
1342            abort ();
1343
1344	  if (c == '$') {
1345	    /* position parameter parameter */
1346	    /* better to use bprintf from bfox's metahtml? */
1347	    arg_index = atoi(fmt_start + 1) - 1;
1348	    if (arg_index < 0)
1349	      arg_index = 0;
1350	    if (arg_index >= 2)
1351	      arg_index = 1;
1352	    paramed = 1;
1353	    goto format_again;
1354	  }
1355
1356          fmt_len = format + i - fmt_start + 1;
1357          fmt = (char *) xmalloc (fmt_len + 1);
1358          strncpy (fmt, fmt_start, fmt_len);
1359          fmt[fmt_len] = '\0';
1360
1361	  if (paramed) {
1362	    /* removed positioned parameter */
1363	    char *p;
1364	    for (p = fmt + 1; *p && *p != '$'; p++) {
1365	      ;
1366	    }
1367	    strcpy(fmt + 1, p + 1);
1368	  }
1369
1370          /* If we have "%-98s", maybe 98 calls for a longer string.  */
1371          if (fmt_len > 2)
1372            {
1373              int j;
1374
1375              for (j = fmt_len - 2; j >= 0; j--)
1376                if (isdigit (fmt[j]) || fmt[j] == '$')
1377                  break;
1378
1379              formatted_len = atoi (fmt + j);
1380            }
1381          else
1382            formatted_len = c == 's' ? 0 : 1; /* %s can produce empty string */
1383
1384          switch (c)
1385            {
1386            case '%':           /* Insert a percent sign. */
1387              message_buffer_resize (len + formatted_len);
1388              sprintf
1389                (message_buffer + message_buffer_index, fmt, "%");
1390              message_buffer_index += formatted_len;
1391              break;
1392
1393            case 's':           /* Insert the current arg as a string. */
1394              {
1395                char *string;
1396                int string_len;
1397
1398                string = (char *)args[arg_index++];
1399                string_len = strlen (string);
1400
1401                if (formatted_len > string_len)
1402                  string_len = formatted_len;
1403                message_buffer_resize (len + string_len);
1404                sprintf
1405                  (message_buffer + message_buffer_index, fmt, string);
1406                message_buffer_index += string_len;
1407              }
1408              break;
1409
1410            case 'd':           /* Insert the current arg as an integer. */
1411              {
1412                long long_val;
1413                int integer;
1414
1415                long_val = (long)args[arg_index++];
1416                integer = (int)long_val;
1417
1418                message_buffer_resize (len + formatted_len > 32
1419                                       ? formatted_len : 32);
1420                sprintf
1421                  (message_buffer + message_buffer_index, fmt, integer);
1422                message_buffer_index = strlen (message_buffer);
1423              }
1424              break;
1425
1426            case 'c':           /* Insert the current arg as a character. */
1427              {
1428                long long_val;
1429                int character;
1430
1431                long_val = (long)args[arg_index++];
1432                character = (int)long_val;
1433
1434                message_buffer_resize (len + formatted_len);
1435                sprintf
1436                  (message_buffer + message_buffer_index, fmt, character);
1437                message_buffer_index += formatted_len;
1438              }
1439              break;
1440
1441            default:
1442              abort ();
1443            }
1444          free (fmt);
1445        }
1446    }
1447  message_buffer[message_buffer_index] = '\0';
1448}
1449
1450/* Build a new node which has FORMAT printed with ARG1 and ARG2 as the
1451   contents. */
1452NODE *
1453build_message_node (format, arg1, arg2)
1454     char *format;
1455     void *arg1, *arg2;
1456{
1457  NODE *node;
1458
1459  message_buffer_index = 0;
1460  build_message_buffer (format, arg1, arg2, 0);
1461
1462  node = message_buffer_to_node ();
1463  return (node);
1464}
1465
1466/* Convert the contents of the message buffer to a node. */
1467NODE *
1468message_buffer_to_node ()
1469{
1470  NODE *node;
1471
1472  node = xmalloc (sizeof (NODE));
1473  node->filename = NULL;
1474  node->parent = NULL;
1475  node->nodename = NULL;
1476  node->flags = 0;
1477  node->display_pos =0;
1478
1479  /* Make sure that this buffer ends with a newline. */
1480  node->nodelen = 1 + strlen (message_buffer);
1481  node->contents = xmalloc (1 + node->nodelen);
1482  strcpy (node->contents, message_buffer);
1483  node->contents[node->nodelen - 1] = '\n';
1484  node->contents[node->nodelen] = '\0';
1485  return (node);
1486}
1487
1488/* Useful functions can be called from outside of window.c. */
1489void
1490initialize_message_buffer ()
1491{
1492  message_buffer_index = 0;
1493}
1494
1495/* Print FORMAT with ARG1,2 to the end of the current message buffer. */
1496void
1497printf_to_message_buffer (format, arg1, arg2, arg3)
1498     char *format;
1499     void *arg1, *arg2, *arg3;
1500{
1501  build_message_buffer (format, arg1, arg2, arg3);
1502}
1503
1504/* Return the current horizontal position of the "cursor" on the most
1505   recently output message buffer line. */
1506int
1507message_buffer_length_this_line ()
1508{
1509  register int i;
1510
1511  if (!message_buffer_index)
1512    return (0);
1513
1514  for (i = message_buffer_index; i && message_buffer[i - 1] != '\n'; i--);
1515
1516  return (string_width (message_buffer + i, 0));
1517}
1518
1519/* Pad STRING to COUNT characters by inserting blanks. */
1520int
1521pad_to (count, string)
1522     int count;
1523     char *string;
1524{
1525  register int i;
1526
1527  i = strlen (string);
1528
1529  if (i >= count)
1530    string[i++] = ' ';
1531  else
1532    {
1533      while (i < count)
1534        string[i++] = ' ';
1535    }
1536  string[i] = '\0';
1537
1538  return (i);
1539}
1540