1/* display.c -- How to display Info windows.
2   $Id: display.c,v 1.1 2004/10/28 18:14:09 zooey Exp $
3
4   Copyright (C) 1993, 97 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 "display.h"
24
25extern int info_any_buffered_input_p (); /* Found in session.c. */
26
27static void free_display ();
28static DISPLAY_LINE **make_display ();
29
30/* An array of display lines which tell us what is currently visible on
31   the display.  */
32DISPLAY_LINE **the_display = (DISPLAY_LINE **)NULL;
33
34/* Non-zero means do no output. */
35int display_inhibited = 0;
36
37/* Initialize THE_DISPLAY to WIDTH and HEIGHT, with nothing in it. */
38void
39display_initialize_display (width, height)
40     int width, height;
41{
42  free_display (the_display);
43  the_display = make_display (width, height);
44  display_clear_display (the_display);
45}
46
47/* Clear all of the lines in DISPLAY making the screen blank. */
48void
49display_clear_display (display)
50     DISPLAY_LINE **display;
51{
52  register int i;
53  register DISPLAY_LINE *display_line;
54
55  for (i = 0; (display_line = display[i]); i++)
56    {
57      display[i]->text[0] = '\0';
58      display[i]->textlen = 0;
59      display[i]->inverse = 0;
60    }
61}
62
63/* Non-zero if we didn't completely redisplay a window. */
64int display_was_interrupted_p = 0;
65
66/* Update the windows pointed to by WINDOW in the_display.  This actually
67   writes the text on the screen. */
68void
69display_update_display (window)
70     WINDOW *window;
71{
72  register WINDOW *win;
73
74  display_was_interrupted_p = 0;
75
76  /* For every window in the list, check contents against the display. */
77  for (win = window; win; win = win->next)
78    {
79      /* Only re-display visible windows which need updating. */
80      if (((win->flags & W_WindowVisible) == 0) ||
81          ((win->flags & W_UpdateWindow) == 0) ||
82          (win->height == 0))
83        continue;
84
85      display_update_one_window (win);
86      if (display_was_interrupted_p)
87        break;
88    }
89
90  /* Always update the echo area. */
91  display_update_one_window (the_echo_area);
92}
93
94/* Display WIN on the_display.  Unlike display_update_display (), this
95   function only does one window. */
96void
97display_update_one_window (win)
98     WINDOW *win;
99{
100  register char *nodetext;      /* Current character to display. */
101  register char *last_node_char; /* Position of the last character in node. */
102  register int i;               /* General use index. */
103  char *printed_line;           /* Buffer for a printed line. */
104  int pl_index = 0;             /* Index into PRINTED_LINE. */
105  int line_index = 0;           /* Number of lines done so far. */
106  DISPLAY_LINE **display = the_display;
107
108  /* If display is inhibited, that counts as an interrupted display. */
109  if (display_inhibited)
110    display_was_interrupted_p = 1;
111
112  /* If the window has no height, or display is inhibited, quit now. */
113  if (!win->height || display_inhibited)
114    return;
115
116  /* If the window's first row doesn't appear in the_screen, then it
117     cannot be displayed.  This can happen when the_echo_area is the
118     window to be displayed, and the screen has shrunk to less than one
119     line. */
120  if ((win->first_row < 0) || (win->first_row > the_screen->height))
121    return;
122
123  /* Print each line in the window into our local buffer, and then
124     check the contents of that buffer against the display.  If they
125     differ, update the display. */
126  printed_line = (char *)xmalloc (1 + win->width);
127
128  if (!win->node || !win->line_starts)
129    goto done_with_node_display;
130
131  nodetext = win->line_starts[win->pagetop];
132  last_node_char = win->node->contents + win->node->nodelen;
133
134  for (; nodetext < last_node_char; nodetext++)
135    {
136      char *rep, *rep_carried_over, rep_temp[2];
137      int replen;
138
139      if (isprint (*nodetext))
140        {
141          rep_temp[0] = *nodetext;
142          replen = 1;
143          rep_temp[1] = '\0';
144          rep = rep_temp;
145        }
146      else
147        {
148          if (*nodetext == '\r' || *nodetext == '\n')
149            {
150              replen = win->width - pl_index;
151            }
152          else
153            {
154              rep = printed_representation (*nodetext, pl_index);
155              replen = strlen (rep);
156            }
157        }
158
159      /* If this character can be printed without passing the width of
160         the line, then stuff it into the line. */
161      if (replen + pl_index < win->width)
162        {
163          /* Optimize if possible. */
164          if (replen == 1)
165            {
166              printed_line[pl_index++] = *rep;
167            }
168          else
169            {
170              for (i = 0; i < replen; i++)
171                printed_line[pl_index++] = rep[i];
172            }
173        }
174      else
175        {
176          DISPLAY_LINE *entry;
177
178          /* If this character cannot be printed in this line, we have
179             found the end of this line as it would appear on the screen.
180             Carefully print the end of the line, and then compare. */
181          if (*nodetext == '\n' || *nodetext == '\r' || *nodetext == '\t')
182            {
183              printed_line[pl_index] = '\0';
184              rep_carried_over = (char *)NULL;
185            }
186          else
187            {
188              /* The printed representation of this character extends into
189                 the next line.  Remember the offset of the last character
190                 printed out of REP so that we can carry the character over
191                 to the next line. */
192              for (i = 0; pl_index < (win->width - 1);)
193                printed_line[pl_index++] = rep[i++];
194
195              rep_carried_over = rep + i;
196
197              /* If printing the last character in this window couldn't
198                 possibly cause the screen to scroll, place a backslash
199                 in the rightmost column. */
200              if (1 + line_index + win->first_row < the_screen->height)
201                {
202                  if (win->flags & W_NoWrap)
203                    printed_line[pl_index++] = '$';
204                  else
205                    printed_line[pl_index++] = '\\';
206                }
207              printed_line[pl_index] = '\0';
208            }
209
210          /* We have the exact line as it should appear on the screen.
211             Check to see if this line matches the one already appearing
212             on the screen. */
213          entry = display[line_index + win->first_row];
214
215          /* If the screen line is inversed, then we have to clear
216             the line from the screen first.  Why, I don't know. */
217          if (entry->inverse)
218            {
219              terminal_goto_xy (0, line_index + win->first_row);
220              terminal_clear_to_eol ();
221              entry->inverse = 0;
222              entry->text[0] = '\0';
223              entry->textlen = 0;
224            }
225
226          /* Find the offset where these lines differ. */
227          for (i = 0; i < pl_index; i++)
228            if (printed_line[i] != entry->text[i])
229              break;
230
231          /* If the lines are not the same length, or if they differed
232             at all, we must do some redrawing. */
233          if ((i != pl_index) || (pl_index != entry->textlen))
234            {
235              /* Move to the proper point on the terminal. */
236              terminal_goto_xy (i, line_index + win->first_row);
237
238              /* If there is any text to print, print it. */
239              if (i != pl_index)
240                terminal_put_text (printed_line + i);
241
242              /* If the printed text didn't extend all the way to the edge
243                 of the window, and text was appearing between here and the
244                 edge of the window, clear from here to the end of the line. */
245              if ((pl_index < win->width && pl_index < entry->textlen) ||
246                  (entry->inverse))
247                terminal_clear_to_eol ();
248
249              fflush (stdout);
250
251              /* Update the display text buffer. */
252              strcpy (entry->text + i, printed_line + i);
253              entry->textlen = pl_index;
254
255              /* Lines showing node text are not in inverse.  Only modelines
256                 have that distinction. */
257              entry->inverse = 0;
258            }
259
260          /* We have done at least one line.  Increment our screen line
261             index, and check against the bottom of the window. */
262          if (++line_index == win->height)
263            break;
264
265          /* A line has been displayed, and the screen reflects that state.
266             If there is typeahead pending, then let that typeahead be read
267             now, instead of continuing with the display. */
268          if (info_any_buffered_input_p ())
269            {
270              free (printed_line);
271              display_was_interrupted_p = 1;
272              return;
273            }
274
275          /* Reset PL_INDEX to the start of the line. */
276          pl_index = 0;
277
278          /* If there are characters from REP left to print, stuff them
279             into the buffer now. */
280          if (rep_carried_over)
281            for (; rep[pl_index]; pl_index++)
282              printed_line[pl_index] = rep[pl_index];
283
284          /* If this window has chosen not to wrap lines, skip to the end
285             of the physical line in the buffer, and start a new line here. */
286          if (pl_index && (win->flags & W_NoWrap))
287            {
288              char *begin;
289
290              pl_index = 0;
291              printed_line[0] = '\0';
292
293              begin = nodetext;
294
295              while ((nodetext < last_node_char) && (*nodetext != '\n'))
296                nodetext++;
297            }
298        }
299    }
300
301 done_with_node_display:
302  /* We have reached the end of the node or the end of the window.  If it
303     is the end of the node, then clear the lines of the window from here
304     to the end of the window. */
305  for (; line_index < win->height; line_index++)
306    {
307      DISPLAY_LINE *entry = display[line_index + win->first_row];
308
309      /* If this line has text on it then make it go away. */
310      if (entry && entry->textlen)
311        {
312          entry->textlen = 0;
313          entry->text[0] = '\0';
314
315          terminal_goto_xy (0, line_index + win->first_row);
316          terminal_clear_to_eol ();
317        }
318    }
319
320  /* Finally, if this window has a modeline it might need to be redisplayed.
321     Check the window's modeline against the one in the display, and update
322     if necessary. */
323  if ((win->flags & W_InhibitMode) == 0)
324    {
325      window_make_modeline (win);
326      line_index = win->first_row + win->height;
327
328      /* This display line must both be in inverse, and have the same
329         contents. */
330      if ((!display[line_index]->inverse) ||
331          (strcmp (display[line_index]->text, win->modeline) != 0))
332        {
333          terminal_goto_xy (0, line_index);
334          terminal_begin_inverse ();
335          terminal_put_text (win->modeline);
336          terminal_end_inverse ();
337          strcpy (display[line_index]->text, win->modeline);
338          display[line_index]->inverse = 1;
339          display[line_index]->textlen = strlen (win->modeline);
340          fflush (stdout);
341        }
342    }
343
344  /* Okay, this window doesn't need updating anymore. */
345  win->flags &= ~W_UpdateWindow;
346  free (printed_line);
347  fflush (stdout);
348}
349
350/* Scroll the region of the_display starting at START, ending at END, and
351   moving the lines AMOUNT lines.  If AMOUNT is less than zero, the lines
352   are moved up in the screen, otherwise down.  Actually, it is possible
353   for no scrolling to take place in the case that the terminal doesn't
354   support it.  This doesn't matter to us. */
355void
356display_scroll_display (start, end, amount)
357     int start, end, amount;
358{
359  register int i, last;
360  DISPLAY_LINE *temp;
361
362  /* If this terminal cannot do scrolling, give up now. */
363  if (!terminal_can_scroll)
364    return;
365
366  /* If there isn't anything displayed on the screen because it is too
367     small, quit now. */
368  if (!the_display[0])
369    return;
370
371  /* If there is typeahead pending, then don't actually do any scrolling. */
372  if (info_any_buffered_input_p ())
373    return;
374
375  /* Do it on the screen. */
376  terminal_scroll_terminal (start, end, amount);
377
378  /* Now do it in the display buffer so our contents match the screen. */
379  if (amount > 0)
380    {
381      last = end + amount;
382
383      /* Shift the lines to scroll right into place. */
384      for (i = 0; i < (end - start); i++)
385        {
386          temp = the_display[last - i];
387          the_display[last - i] = the_display[end - i];
388          the_display[end - i] = temp;
389        }
390
391      /* The lines have been shifted down in the buffer.  Clear all of the
392         lines that were vacated. */
393      for (i = start; i != (start + amount); i++)
394        {
395          the_display[i]->text[0] = '\0';
396          the_display[i]->textlen = 0;
397          the_display[i]->inverse = 0;
398        }
399    }
400
401  if (amount < 0)
402    {
403      last = start + amount;
404      for (i = 0; i < (end - start); i++)
405        {
406          temp = the_display[last + i];
407          the_display[last + i] = the_display[start + i];
408          the_display[start + i] = temp;
409        }
410
411      /* The lines have been shifted up in the buffer.  Clear all of the
412         lines that are left over. */
413      for (i = end + amount; i != end; i++)
414        {
415          the_display[i]->text[0] = '\0';
416          the_display[i]->textlen = 0;
417          the_display[i]->inverse = 0;
418        }
419    }
420}
421
422/* Try to scroll lines in WINDOW.  OLD_PAGETOP is the pagetop of WINDOW before
423   having had its line starts recalculated.  OLD_STARTS is the list of line
424   starts that used to appear in this window.  OLD_COUNT is the number of lines
425   that appear in the OLD_STARTS array. */
426void
427display_scroll_line_starts (window, old_pagetop, old_starts, old_count)
428     WINDOW *window;
429     int old_pagetop, old_count;
430     char **old_starts;
431{
432  register int i, old, new;     /* Indices into the line starts arrays. */
433  int last_new, last_old;       /* Index of the last visible line. */
434  int old_first, new_first;     /* Index of the first changed line. */
435  int unchanged_at_top = 0;
436  int already_scrolled = 0;
437
438  /* Locate the first line which was displayed on the old window. */
439  old_first = old_pagetop;
440  new_first = window->pagetop;
441
442  /* Find the last line currently visible in this window. */
443  last_new = window->pagetop + (window->height - 1);
444  if (last_new > window->line_count)
445    last_new = window->line_count - 1;
446
447  /* Find the last line which used to be currently visible in this window. */
448  last_old = old_pagetop + (window->height - 1);
449  if (last_old > old_count)
450    last_old = old_count - 1;
451
452  for (old = old_first, new = new_first;
453       old < last_old && new < last_new;
454       old++, new++)
455    if (old_starts[old] != window->line_starts[new])
456      break;
457    else
458      unchanged_at_top++;
459
460  /* Loop through the old lines looking for a match in the new lines. */
461  for (old = old_first + unchanged_at_top; old < last_old; old++)
462    {
463      for (new = new_first; new < last_new; new++)
464        if (old_starts[old] == window->line_starts[new])
465          {
466            /* Find the extent of the matching lines. */
467            for (i = 0; (old + i) < last_old; i++)
468              if (old_starts[old + i] != window->line_starts[new + i])
469                break;
470
471            /* Scroll these lines if there are enough of them. */
472            {
473              int start, end, amount;
474
475              start = (window->first_row
476                       + ((old + already_scrolled) - old_pagetop));
477              amount = new - (old + already_scrolled);
478              end = window->first_row + window->height;
479
480              /* If we are shifting the block of lines down, then the last
481                 AMOUNT lines will become invisible.  Thus, don't bother
482                 scrolling them. */
483              if (amount > 0)
484                end -= amount;
485
486              if ((end - start) > 0)
487                {
488                  display_scroll_display (start, end, amount);
489
490                  /* Some lines have been scrolled.  Simulate the scrolling
491                     by offsetting the value of the old index. */
492                  old += i;
493                  already_scrolled += amount;
494                }
495            }
496          }
497    }
498}
499
500/* Move the screen cursor to directly over the current character in WINDOW. */
501void
502display_cursor_at_point (window)
503     WINDOW *window;
504{
505  int vpos, hpos;
506
507  vpos = window_line_of_point (window) - window->pagetop + window->first_row;
508  hpos = window_get_cursor_column (window);
509  terminal_goto_xy (hpos, vpos);
510  fflush (stdout);
511}
512
513/* **************************************************************** */
514/*                                                                  */
515/*                   Functions Static to this File                  */
516/*                                                                  */
517/* **************************************************************** */
518
519/* Make a DISPLAY_LINE ** with width and height. */
520static DISPLAY_LINE **
521make_display (width, height)
522     int width, height;
523{
524  register int i;
525  DISPLAY_LINE **display;
526
527  display = (DISPLAY_LINE **)xmalloc ((1 + height) * sizeof (DISPLAY_LINE *));
528
529  for (i = 0; i < height; i++)
530    {
531      display[i] = (DISPLAY_LINE *)xmalloc (sizeof (DISPLAY_LINE));
532      display[i]->text = (char *)xmalloc (1 + width);
533      display[i]->textlen = 0;
534      display[i]->inverse = 0;
535    }
536  display[i] = (DISPLAY_LINE *)NULL;
537  return (display);
538}
539
540/* Free the storage allocated to DISPLAY. */
541static void
542free_display (display)
543     DISPLAY_LINE **display;
544{
545  register int i;
546  register DISPLAY_LINE *display_line;
547
548  if (!display)
549    return;
550
551  for (i = 0; (display_line = display[i]); i++)
552    {
553      free (display_line->text);
554      free (display_line);
555    }
556  free (display);
557}
558