1 /* window.c -- windows in Info.
2 $Id: window.c,v 1.5 2006/07/17 16:12:36 espie 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
window_initialize_windows(int width,int height)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
window_new_screen_size(int width,int height)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 *
window_make_window(NODE * node)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
window_change_window_height(WINDOW * window,int amount)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
window_tile_windows(int style)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
window_toggle_wrap(WINDOW * window)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
window_set_node_of_window(WINDOW * window,NODE * node)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
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
window_delete_window(WINDOW * window)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
window_mark_chain(WINDOW * chain,int flag)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
window_unmark_chain(WINDOW * chain,int flag)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
character_width(int character,int hpos)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
string_width(char * string,int hpos)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
window_physical_lines(NODE * node)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
calculate_line_starts(WINDOW * window)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
recalculate_line_starts(WINDOW * window)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
window_adjust_pagetop(WINDOW * window)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
window_line_of_point(WINDOW * window)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
window_get_goal_column(WINDOW * window)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
window_get_cursor_column(WINDOW * window)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
window_chars_to_goal(char * line,int goal)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
window_make_modeline(WINDOW * window)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
window_goto_percentage(WINDOW * window,int percent)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
window_get_state(WINDOW * window,SEARCH_STATE * state)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
window_set_state(WINDOW * window,SEARCH_STATE * state)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
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
free_echo_area(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
window_clear_echo_area(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
window_message_in_echo_area(char * format,void * arg1,void * arg2)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
message_in_echo_area(char * format,void * arg1,void * arg2)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
unmessage_in_echo_area(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
message_buffer_resize(int length)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
build_message_buffer(char * format,void * arg1,void * arg2,void * arg3)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 *
build_message_node(char * format,void * arg1,void * arg2)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 *
message_buffer_to_node(void)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
initialize_message_buffer(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
printf_to_message_buffer(char * format,void * arg1,void * arg2,void * arg3)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
message_buffer_length_this_line(void)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
pad_to(int count,char * string)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 }
1555