source: trunk/texinfo/info/window.c@ 3003

Last change on this file since 3003 was 2617, checked in by bird, 19 years ago

GNU Texinfo 4.8

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