Rename texinfo-4 directory to "texinfo" in vendor branch
[dragonfly.git] / contrib / texinfo / info / window.c
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. */
31 WINDOW *the_screen = NULL;
32
33 /* The window which describes the echo area. */
34 WINDOW *the_echo_area = NULL;
35
36 /* The list of windows in Info. */
37 WINDOW *windows = NULL;
38
39 /* Pointer to the active window in WINDOW_LIST. */
40 WINDOW *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. */
53 void
54 window_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. */
102 VFunction *window_deletion_notifier = NULL;
103
104 void
105 window_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.*/
262 WINDOW *
263 window_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. */
376 void
377 window_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. */
507 void
508 window_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. */
560 void
561 window_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. */
594 void
595 window_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 \f
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. */
612 void
613 window_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. */
683 void
684 window_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. */
693 void
694 window_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. */
704 int
705 character_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. */
738 int
739 string_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. */
767 int
768 window_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. */
786 void
787 calculate_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. */
901 void
902 recalculate_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. */
912 int window_scroll_step = 0;
913
914 /* Adjust the pagetop of WINDOW such that the cursor point will be visible. */
915 void
916 window_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. */
972 int
973 window_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. */
994 int
995 window_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. */
1009 int
1010 window_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. */
1047 int
1048 window_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. */
1075 void
1076 window_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. */
1189 void
1190 window_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. */
1208 void
1209 window_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. */
1217 void
1218 window_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 \f
1227 /* Manipulating home-made nodes.  */
1228
1229 /* A place to buffer echo area messages. */
1230 static NODE *echo_area_node = NULL;
1231
1232 /* Make the node of the_echo_area be an empty one. */
1233 static void
1234 free_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. */
1248 void
1249 window_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. */
1259 void
1260 window_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. */
1272 static NODE **old_echo_area_nodes = NULL;
1273 static int old_echo_area_nodes_index = 0;
1274 static int old_echo_area_nodes_slots = 0;
1275
1276 void
1277 message_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
1289 void
1290 unmessage_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. */
1302 static char *message_buffer = NULL;
1303 static int message_buffer_index = 0;
1304 static int message_buffer_size = 0;
1305
1306 /* Ensure that there is enough space to stuff LENGTH characters into
1307    MESSAGE_BUFFER. */
1308 static void
1309 message_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. */
1326 static void
1327 build_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. */
1473 NODE *
1474 build_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. */
1486 NODE *
1487 message_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. */
1508 void
1509 initialize_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. */
1515 void
1516 printf_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. */
1523 int
1524 message_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. */
1537 int
1538 pad_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 }