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