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