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