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