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