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