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