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 (at) 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 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 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 * 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 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 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 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 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 612 active window, make the next window in the chain be the active window. 613 If the active window is the next or previous window, choose that window 614 as the recipient of the extra space. Otherwise, prefer the next window. */ 615 void 616 window_delete_window (WINDOW *window) 617 { 618 WINDOW *next, *prev, *window_to_fix; 619 620 next = window->next; 621 prev = window->prev; 622 623 /* You cannot delete the only window or a permanent window. */ 624 if ((!next && !prev) || (window->flags & W_WindowIsPerm)) 625 return; 626 627 if (next) 628 next->prev = prev; 629 630 if (!prev) 631 windows = next; 632 else 633 prev->next = next; 634 635 if (window->line_starts) 636 free (window->line_starts); 637 638 if (window->modeline) 639 free (window->modeline); 640 641 if (window == active_window) 642 { 643 /* If there isn't a next window, then there must be a previous one, 644 since we cannot delete the last window. If there is a next window, 645 prefer to use that as the active window. */ 646 if (next) 647 active_window = next; 648 else 649 active_window = prev; 650 } 651 652 if (next && active_window == next) 653 window_to_fix = next; 654 else if (prev && active_window == prev) 655 window_to_fix = prev; 656 else if (next) 657 window_to_fix = next; 658 else if (prev) 659 window_to_fix = prev; 660 else 661 window_to_fix = windows; 662 663 if (window_to_fix->first_row > window->first_row) 664 { 665 int diff; 666 667 /* Try to adjust the visible part of the node so that as little 668 text as possible has to move. */ 669 diff = window_to_fix->first_row - window->first_row; 670 window_to_fix->first_row = window->first_row; 671 672 window_to_fix->pagetop -= diff; 673 if (window_to_fix->pagetop < 0) 674 window_to_fix->pagetop = 0; 675 } 676 677 /* The `+ 1' is to offset the difference between the first_row locations. 678 See the code in window_make_window (). */ 679 window_to_fix->height += window->height + 1; 680 window_to_fix->flags |= W_UpdateWindow; 681 682 free (window); 683 } 684 685 /* For every window in CHAIN, set the flags member to have FLAG set. */ 686 void 687 window_mark_chain (WINDOW *chain, int flag) 688 { 689 register WINDOW *win; 690 691 for (win = chain; win; win = win->next) 692 win->flags |= flag; 693 } 694 695 /* For every window in CHAIN, clear the flags member of FLAG. */ 696 void 697 window_unmark_chain (WINDOW *chain, int flag) 698 { 699 register WINDOW *win; 700 701 for (win = chain; win; win = win->next) 702 win->flags &= ~flag; 703 } 704 705 /* Return the number of characters it takes to display CHARACTER on the 706 screen at HPOS. */ 707 int 708 character_width (int character, int hpos) 709 { 710 int printable_limit = 127; 711 int width = 1; 712 713 if (ISO_Latin_p) 714 printable_limit = 255; 715 716 if (character > printable_limit) 717 width = 3; 718 else if (iscntrl (character)) 719 { 720 switch (character) 721 { 722 case '\r': 723 case '\n': 724 width = the_screen->width - hpos; 725 break; 726 case '\t': 727 width = ((hpos + 8) & 0xf8) - hpos; 728 break; 729 default: 730 width = 2; 731 } 732 } 733 else if (character == DEL) 734 width = 2; 735 736 return (width); 737 } 738 739 /* Return the number of characters it takes to display STRING on the screen 740 at HPOS. */ 741 int 742 string_width (char *string, int hpos) 743 { 744 register int i, width, this_char_width; 745 746 for (width = 0, i = 0; string[i]; i++) 747 { 748 /* Support ANSI escape sequences for -R. */ 749 if (raw_escapes_p 750 && string[i] == '\033' 751 && string[i+1] == '[' 752 && isdigit (string[i+2]) 753 && (string[i+3] == 'm' 754 || (isdigit (string[i+3]) && string[i+4] == 'm'))) 755 { 756 while (string[i] != 'm') 757 i++; 758 this_char_width = 0; 759 } 760 else 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. */ 770 int 771 window_physical_lines (NODE *node) 772 { 773 register int i, lines; 774 char *contents; 775 776 if (!node) 777 return (0); 778 779 contents = node->contents; 780 for (i = 0, lines = 1; i < node->nodelen; i++) 781 if (contents[i] == '\n') 782 lines++; 783 784 return (lines); 785 } 786 787 /* Calculate a list of line starts for the node belonging to WINDOW. The line 788 starts are pointers to the actual text within WINDOW->NODE. */ 789 void 790 calculate_line_starts (WINDOW *window) 791 { 792 register int i, hpos; 793 char **line_starts = NULL; 794 int line_starts_index = 0, line_starts_slots = 0; 795 int bump_index; 796 NODE *node; 797 798 window->line_starts = NULL; 799 window->line_count = 0; 800 node = window->node; 801 802 if (!node) 803 return; 804 805 /* Grovel the node starting at the top, and for each line calculate the 806 width of the characters appearing in that line. Add each line start 807 to our array. */ 808 i = 0; 809 hpos = 0; 810 bump_index = 0; 811 812 while (i < node->nodelen) 813 { 814 char *line = node->contents + i; 815 unsigned int cwidth, c; 816 817 add_pointer_to_array (line, line_starts_index, line_starts, 818 line_starts_slots, 100, char *); 819 if (bump_index) 820 { 821 i++; 822 bump_index = 0; 823 } 824 825 while (1) 826 { 827 /* The cast to unsigned char is for 8-bit characters, which 828 could be passed as negative integers to character_width 829 and wreak havoc on some naive implementations of iscntrl. */ 830 c = (unsigned char) node->contents[i]; 831 832 /* Support ANSI escape sequences for -R. */ 833 if (raw_escapes_p 834 && c == '\033' 835 && node->contents[i+1] == '[' 836 && isdigit (node->contents[i+2])) 837 { 838 if (node->contents[i+3] == 'm') 839 { 840 i += 3; 841 cwidth = 0; 842 } 843 else if (isdigit (node->contents[i+3]) 844 && node->contents[i+4] == 'm') 845 { 846 i += 4; 847 cwidth = 0; 848 } 849 else 850 cwidth = character_width (c, hpos); 851 } 852 else 853 cwidth = character_width (c, hpos); 854 855 /* If this character fits within this line, just do the next one. */ 856 if ((hpos + cwidth) < (unsigned int) window->width) 857 { 858 i++; 859 hpos += cwidth; 860 continue; 861 } 862 else 863 { 864 /* If this character would position the cursor at the start of 865 the next printed screen line, then do the next line. */ 866 if (c == '\n' || c == '\r' || c == '\t') 867 { 868 i++; 869 hpos = 0; 870 break; 871 } 872 else 873 { 874 /* This character passes the window width border. Postion 875 the cursor after the printed character, but remember this 876 line start as where this character is. A bit tricky. */ 877 878 /* If this window doesn't wrap lines, proceed to the next 879 physical line here. */ 880 if (window->flags & W_NoWrap) 881 { 882 hpos = 0; 883 while (i < node->nodelen && node->contents[i] != '\n') 884 i++; 885 886 if (node->contents[i] == '\n') 887 i++; 888 } 889 else 890 { 891 hpos = the_screen->width - hpos; 892 bump_index++; 893 } 894 break; 895 } 896 } 897 } 898 } 899 window->line_starts = line_starts; 900 window->line_count = line_starts_index; 901 } 902 903 /* Given WINDOW, recalculate the line starts for the node it displays. */ 904 void 905 recalculate_line_starts (WINDOW *window) 906 { 907 maybe_free (window->line_starts); 908 calculate_line_starts (window); 909 } 910 911 /* Global variable control redisplay of scrolled windows. If non-zero, it 912 is the desired number of lines to scroll the window in order to make 913 point visible. A user might set this to 1 for smooth scrolling. If 914 set to zero, the line containing point is centered within the window. */ 915 int window_scroll_step = 0; 916 917 /* Adjust the pagetop of WINDOW such that the cursor point will be visible. */ 918 void 919 window_adjust_pagetop (WINDOW *window) 920 { 921 register int line = 0; 922 char *contents; 923 924 if (!window->node) 925 return; 926 927 contents = window->node->contents; 928 929 /* Find the first printed line start which is after WINDOW->point. */ 930 for (line = 0; line < window->line_count; line++) 931 { 932 char *line_start; 933 934 line_start = window->line_starts[line]; 935 936 if ((line_start - contents) > window->point) 937 break; 938 } 939 940 /* The line index preceding the line start which is past point is the 941 one containing point. */ 942 line--; 943 944 /* If this line appears in the current displayable page, do nothing. 945 Otherwise, adjust the top of the page to make this line visible. */ 946 if ((line < window->pagetop) || 947 (line - window->pagetop > (window->height - 1))) 948 { 949 /* The user-settable variable "scroll-step" is used to attempt 950 to make point visible, iff it is non-zero. If that variable 951 is zero, then the line containing point is centered within 952 the window. */ 953 if (window_scroll_step < window->height) 954 { 955 if ((line < window->pagetop) && 956 ((window->pagetop - window_scroll_step) <= line)) 957 window->pagetop -= window_scroll_step; 958 else if ((line - window->pagetop > (window->height - 1)) && 959 ((line - (window->pagetop + window_scroll_step) 960 < window->height))) 961 window->pagetop += window_scroll_step; 962 else 963 window->pagetop = line - ((window->height - 1) / 2); 964 } 965 else 966 window->pagetop = line - ((window->height - 1) / 2); 967 968 if (window->pagetop < 0) 969 window->pagetop = 0; 970 window->flags |= W_UpdateWindow; 971 } 972 } 973 974 /* Return the index of the line containing point. */ 975 int 976 window_line_of_point (WINDOW *window) 977 { 978 register int i, start = 0; 979 980 /* Try to optimize. Check to see if point is past the pagetop for 981 this window, and if so, start searching forward from there. */ 982 if ((window->pagetop > -1 && window->pagetop < window->line_count) && 983 (window->line_starts[window->pagetop] - window->node->contents) 984 <= window->point) 985 start = window->pagetop; 986 987 for (i = start; i < window->line_count; i++) 988 { 989 if ((window->line_starts[i] - window->node->contents) > window->point) 990 break; 991 } 992 993 return (i - 1); 994 } 995 996 /* Get and return the goal column for this window. */ 997 int 998 window_get_goal_column (WINDOW *window) 999 { 1000 if (!window->node) 1001 return (-1); 1002 1003 if (window->goal_column != -1) 1004 return (window->goal_column); 1005 1006 /* Okay, do the work. Find the printed offset of the cursor 1007 in this window. */ 1008 return (window_get_cursor_column (window)); 1009 } 1010 1011 /* Get and return the printed column offset of the cursor in this window. */ 1012 int 1013 window_get_cursor_column (WINDOW *window) 1014 { 1015 int i, hpos, end; 1016 char *line; 1017 1018 i = window_line_of_point (window); 1019 1020 if (i < 0) 1021 return (-1); 1022 1023 line = window->line_starts[i]; 1024 end = window->point - (line - window->node->contents); 1025 1026 for (hpos = 0, i = 0; i < end; i++) 1027 { 1028 /* Support ANSI escape sequences for -R. */ 1029 if (raw_escapes_p 1030 && line[i] == '\033' 1031 && line[i+1] == '[' 1032 && isdigit (line[i+2])) 1033 { 1034 if (line[i+3] == 'm') 1035 i += 3; 1036 else if (isdigit (line[i+3]) && line[i+4] == 'm') 1037 i += 4; 1038 else 1039 hpos += character_width (line[i], hpos); 1040 } 1041 else 1042 hpos += character_width (line[i], hpos); 1043 } 1044 1045 return (hpos); 1046 } 1047 1048 /* Count the number of characters in LINE that precede the printed column 1049 offset of GOAL. */ 1050 int 1051 window_chars_to_goal (char *line, int goal) 1052 { 1053 register int i, check = 0, hpos; 1054 1055 for (hpos = 0, i = 0; line[i] != '\n'; i++) 1056 { 1057 /* Support ANSI escape sequences for -R. */ 1058 if (raw_escapes_p 1059 && line[i] == '\033' 1060 && line[i+1] == '[' 1061 && isdigit (line[i+2]) 1062 && (line[i+3] == 'm' 1063 || (isdigit (line[i+3]) && line[i+4] == 'm'))) 1064 while (line[i] != 'm') 1065 i++; 1066 else 1067 check = hpos + character_width (line[i], hpos); 1068 1069 if (check > goal) 1070 break; 1071 1072 hpos = check; 1073 } 1074 return (i); 1075 } 1076 1077 /* Create a modeline for WINDOW, and store it in window->modeline. */ 1078 void 1079 window_make_modeline (WINDOW *window) 1080 { 1081 register int i; 1082 char *modeline; 1083 char location_indicator[4]; 1084 int lines_remaining; 1085 1086 /* Only make modelines for those windows which have one. */ 1087 if (window->flags & W_InhibitMode) 1088 return; 1089 1090 /* Find the number of lines actually displayed in this window. */ 1091 lines_remaining = window->line_count - window->pagetop; 1092 1093 if (window->pagetop == 0) 1094 { 1095 if (lines_remaining <= window->height) 1096 strcpy (location_indicator, "All"); 1097 else 1098 strcpy (location_indicator, "Top"); 1099 } 1100 else 1101 { 1102 if (lines_remaining <= window->height) 1103 strcpy (location_indicator, "Bot"); 1104 else 1105 { 1106 float pt, lc; 1107 int percentage; 1108 1109 pt = (float)window->pagetop; 1110 lc = (float)window->line_count; 1111 1112 percentage = 100 * (pt / lc); 1113 1114 sprintf (location_indicator, "%2d%%", percentage); 1115 } 1116 } 1117 1118 /* Calculate the maximum size of the information to stick in MODELINE. */ 1119 { 1120 int modeline_len = 0; 1121 char *parent = NULL, *filename = "*no file*"; 1122 char *nodename = "*no node*"; 1123 const char *update_message = NULL; 1124 NODE *node = window->node; 1125 1126 if (node) 1127 { 1128 if (node->nodename) 1129 nodename = node->nodename; 1130 1131 if (node->parent) 1132 { 1133 parent = filename_non_directory (node->parent); 1134 modeline_len += strlen ("Subfile: ") + strlen (node->filename); 1135 } 1136 1137 if (node->filename) 1138 filename = filename_non_directory (node->filename); 1139 1140 if (node->flags & N_UpdateTags) 1141 update_message = _("--*** Tags out of Date ***"); 1142 } 1143 1144 if (update_message) 1145 modeline_len += strlen (update_message); 1146 modeline_len += strlen (filename); 1147 modeline_len += strlen (nodename); 1148 modeline_len += 4; /* strlen (location_indicator). */ 1149 1150 /* 10 for the decimal representation of the number of lines in this 1151 node, and the remainder of the text that can appear in the line. */ 1152 modeline_len += 10 + strlen (_("-----Info: (), lines ----, ")); 1153 modeline_len += window->width; 1154 1155 modeline = xmalloc (1 + modeline_len); 1156 1157 /* Special internal windows have no filename. */ 1158 if (!parent && !*filename) 1159 sprintf (modeline, _("-%s---Info: %s, %d lines --%s--"), 1160 (window->flags & W_NoWrap) ? "$" : "-", 1161 nodename, window->line_count, location_indicator); 1162 else 1163 sprintf (modeline, _("-%s%s-Info: (%s)%s, %d lines --%s--"), 1164 (window->flags & W_NoWrap) ? "$" : "-", 1165 (node && (node->flags & N_IsCompressed)) ? "zz" : "--", 1166 parent ? parent : filename, 1167 nodename, window->line_count, location_indicator); 1168 1169 if (parent) 1170 sprintf (modeline + strlen (modeline), _(" Subfile: %s"), filename); 1171 1172 if (update_message) 1173 sprintf (modeline + strlen (modeline), "%s", update_message); 1174 1175 i = strlen (modeline); 1176 1177 if (i >= window->width) 1178 modeline[window->width] = '\0'; 1179 else 1180 { 1181 while (i < window->width) 1182 modeline[i++] = '-'; 1183 modeline[i] = '\0'; 1184 } 1185 1186 strcpy (window->modeline, modeline); 1187 free (modeline); 1188 } 1189 } 1190 1191 /* Make WINDOW start displaying at PERCENT percentage of its node. */ 1192 void 1193 window_goto_percentage (WINDOW *window, int percent) 1194 { 1195 int desired_line; 1196 1197 if (!percent) 1198 desired_line = 0; 1199 else 1200 desired_line = 1201 (int) ((float)window->line_count * ((float)percent / 100.0)); 1202 1203 window->pagetop = desired_line; 1204 window->point = 1205 window->line_starts[window->pagetop] - window->node->contents; 1206 window->flags |= W_UpdateWindow; 1207 window_make_modeline (window); 1208 } 1209 1210 /* Get the state of WINDOW, and save it in STATE. */ 1211 void 1212 window_get_state (WINDOW *window, SEARCH_STATE *state) 1213 { 1214 state->node = window->node; 1215 state->pagetop = window->pagetop; 1216 state->point = window->point; 1217 } 1218 1219 /* Set the node, pagetop, and point of WINDOW. */ 1220 void 1221 window_set_state (WINDOW *window, SEARCH_STATE *state) 1222 { 1223 if (window->node != state->node) 1224 window_set_node_of_window (window, state->node); 1225 window->pagetop = state->pagetop; 1226 window->point = state->point; 1227 } 1228 1229 1230 /* Manipulating home-made nodes. */ 1232 1233 /* A place to buffer echo area messages. */ 1234 static NODE *echo_area_node = NULL; 1235 1236 /* Make the node of the_echo_area be an empty one. */ 1237 static void 1238 free_echo_area (void) 1239 { 1240 if (echo_area_node) 1241 { 1242 maybe_free (echo_area_node->contents); 1243 free (echo_area_node); 1244 } 1245 1246 echo_area_node = NULL; 1247 window_set_node_of_window (the_echo_area, echo_area_node); 1248 } 1249 1250 /* Clear the echo area, removing any message that is already present. 1251 The echo area is cleared immediately. */ 1252 void 1253 window_clear_echo_area (void) 1254 { 1255 free_echo_area (); 1256 display_update_one_window (the_echo_area); 1257 } 1258 1259 /* Make a message appear in the echo area, built from FORMAT, ARG1 and ARG2. 1260 The arguments are treated similar to printf () arguments, but not all of 1261 printf () hair is present. The message appears immediately. If there was 1262 already a message appearing in the echo area, it is removed. */ 1263 void 1264 window_message_in_echo_area (char *format, void *arg1, void *arg2) 1265 { 1266 free_echo_area (); 1267 echo_area_node = build_message_node (format, arg1, arg2); 1268 window_set_node_of_window (the_echo_area, echo_area_node); 1269 display_update_one_window (the_echo_area); 1270 } 1271 1272 /* Place a temporary message in the echo area built from FORMAT, ARG1 1273 and ARG2. The message appears immediately, but does not destroy 1274 any existing message. A future call to unmessage_in_echo_area () 1275 restores the old contents. */ 1276 static NODE **old_echo_area_nodes = NULL; 1277 static int old_echo_area_nodes_index = 0; 1278 static int old_echo_area_nodes_slots = 0; 1279 1280 void 1281 message_in_echo_area (char *format, void *arg1, void *arg2) 1282 { 1283 if (echo_area_node) 1284 { 1285 add_pointer_to_array (echo_area_node, old_echo_area_nodes_index, 1286 old_echo_area_nodes, old_echo_area_nodes_slots, 1287 4, NODE *); 1288 } 1289 echo_area_node = NULL; 1290 window_message_in_echo_area (format, arg1, arg2); 1291 } 1292 1293 void 1294 unmessage_in_echo_area (void) 1295 { 1296 free_echo_area (); 1297 1298 if (old_echo_area_nodes_index) 1299 echo_area_node = old_echo_area_nodes[--old_echo_area_nodes_index]; 1300 1301 window_set_node_of_window (the_echo_area, echo_area_node); 1302 display_update_one_window (the_echo_area); 1303 } 1304 1305 /* A place to build a message. */ 1306 static char *message_buffer = NULL; 1307 static int message_buffer_index = 0; 1308 static int message_buffer_size = 0; 1309 1310 /* Ensure that there is enough space to stuff LENGTH characters into 1311 MESSAGE_BUFFER. */ 1312 static void 1313 message_buffer_resize (int length) 1314 { 1315 if (!message_buffer) 1316 { 1317 message_buffer_size = length + 1; 1318 message_buffer = xmalloc (message_buffer_size); 1319 message_buffer_index = 0; 1320 } 1321 1322 while (message_buffer_size <= message_buffer_index + length) 1323 message_buffer = (char *) 1324 xrealloc (message_buffer, 1325 message_buffer_size += 100 + (2 * length)); 1326 } 1327 1328 /* Format MESSAGE_BUFFER with the results of printing FORMAT with ARG1 and 1329 ARG2. */ 1330 static void 1331 build_message_buffer (char *format, void *arg1, void *arg2, void *arg3) 1332 { 1333 register int i, len; 1334 void *args[3]; 1335 int arg_index = 0; 1336 1337 args[0] = arg1; 1338 args[1] = arg2; 1339 args[2] = arg3; 1340 1341 len = strlen (format); 1342 1343 message_buffer_resize (len); 1344 1345 for (i = 0; format[i]; i++) 1346 { 1347 if (format[i] != '%') 1348 { 1349 message_buffer[message_buffer_index++] = format[i]; 1350 len--; 1351 } 1352 else 1353 { 1354 char c; 1355 char *fmt_start = format + i; 1356 char *fmt; 1357 int fmt_len, formatted_len; 1358 int paramed = 0; 1359 1360 format_again: 1361 i++; 1362 while (format[i] && strchr ("-. +0123456789", format[i])) 1363 i++; 1364 c = format[i]; 1365 1366 if (c == '\0') 1367 abort (); 1368 1369 if (c == '$') { 1370 /* position parameter parameter */ 1371 /* better to use bprintf from bfox's metahtml? */ 1372 arg_index = atoi(fmt_start + 1) - 1; 1373 if (arg_index < 0) 1374 arg_index = 0; 1375 if (arg_index >= 2) 1376 arg_index = 1; 1377 paramed = 1; 1378 goto format_again; 1379 } 1380 1381 fmt_len = format + i - fmt_start + 1; 1382 fmt = (char *) xmalloc (fmt_len + 1); 1383 strncpy (fmt, fmt_start, fmt_len); 1384 fmt[fmt_len] = '\0'; 1385 1386 if (paramed) { 1387 /* removed positioned parameter */ 1388 char *p; 1389 for (p = fmt + 1; *p && *p != '$'; p++) { 1390 ; 1391 } 1392 strcpy(fmt + 1, p + 1); 1393 } 1394 1395 /* If we have "%-98s", maybe 98 calls for a longer string. */ 1396 if (fmt_len > 2) 1397 { 1398 int j; 1399 1400 for (j = fmt_len - 2; j >= 0; j--) 1401 if (isdigit (fmt[j]) || fmt[j] == '$') 1402 break; 1403 1404 formatted_len = atoi (fmt + j); 1405 } 1406 else 1407 formatted_len = c == 's' ? 0 : 1; /* %s can produce empty string */ 1408 1409 switch (c) 1410 { 1411 case '%': /* Insert a percent sign. */ 1412 message_buffer_resize (len + formatted_len); 1413 sprintf 1414 (message_buffer + message_buffer_index, fmt, "%"); 1415 message_buffer_index += formatted_len; 1416 break; 1417 1418 case 's': /* Insert the current arg as a string. */ 1419 { 1420 char *string; 1421 int string_len; 1422 1423 string = (char *)args[arg_index++]; 1424 string_len = strlen (string); 1425 1426 if (formatted_len > string_len) 1427 string_len = formatted_len; 1428 message_buffer_resize (len + string_len); 1429 sprintf 1430 (message_buffer + message_buffer_index, fmt, string); 1431 message_buffer_index += string_len; 1432 } 1433 break; 1434 1435 case 'd': /* Insert the current arg as an integer. */ 1436 { 1437 long long_val; 1438 int integer; 1439 1440 long_val = (long)args[arg_index++]; 1441 integer = (int)long_val; 1442 1443 message_buffer_resize (len + formatted_len > 32 1444 ? formatted_len : 32); 1445 sprintf 1446 (message_buffer + message_buffer_index, fmt, integer); 1447 message_buffer_index = strlen (message_buffer); 1448 } 1449 break; 1450 1451 case 'c': /* Insert the current arg as a character. */ 1452 { 1453 long long_val; 1454 int character; 1455 1456 long_val = (long)args[arg_index++]; 1457 character = (int)long_val; 1458 1459 message_buffer_resize (len + formatted_len); 1460 sprintf 1461 (message_buffer + message_buffer_index, fmt, character); 1462 message_buffer_index += formatted_len; 1463 } 1464 break; 1465 1466 default: 1467 abort (); 1468 } 1469 free (fmt); 1470 } 1471 } 1472 message_buffer[message_buffer_index] = '\0'; 1473 } 1474 1475 /* Build a new node which has FORMAT printed with ARG1 and ARG2 as the 1476 contents. */ 1477 NODE * 1478 build_message_node (char *format, void *arg1, void *arg2) 1479 { 1480 NODE *node; 1481 1482 message_buffer_index = 0; 1483 build_message_buffer (format, arg1, arg2, 0); 1484 1485 node = message_buffer_to_node (); 1486 return (node); 1487 } 1488 1489 /* Convert the contents of the message buffer to a node. */ 1490 NODE * 1491 message_buffer_to_node (void) 1492 { 1493 NODE *node; 1494 1495 node = xmalloc (sizeof (NODE)); 1496 node->filename = NULL; 1497 node->parent = NULL; 1498 node->nodename = NULL; 1499 node->flags = 0; 1500 node->display_pos =0; 1501 1502 /* Make sure that this buffer ends with a newline. */ 1503 node->nodelen = 1 + strlen (message_buffer); 1504 node->contents = xmalloc (1 + node->nodelen); 1505 strcpy (node->contents, message_buffer); 1506 node->contents[node->nodelen - 1] = '\n'; 1507 node->contents[node->nodelen] = '\0'; 1508 return (node); 1509 } 1510 1511 /* Useful functions can be called from outside of window.c. */ 1512 void 1513 initialize_message_buffer (void) 1514 { 1515 message_buffer_index = 0; 1516 } 1517 1518 /* Print FORMAT with ARG1,2 to the end of the current message buffer. */ 1519 void 1520 printf_to_message_buffer (char *format, void *arg1, void *arg2, void *arg3) 1521 { 1522 build_message_buffer (format, arg1, arg2, arg3); 1523 } 1524 1525 /* Return the current horizontal position of the "cursor" on the most 1526 recently output message buffer line. */ 1527 int 1528 message_buffer_length_this_line (void) 1529 { 1530 register int i; 1531 1532 if (!message_buffer_index) 1533 return (0); 1534 1535 for (i = message_buffer_index; i && message_buffer[i - 1] != '\n'; i--); 1536 1537 return (string_width (message_buffer + i, 0)); 1538 } 1539 1540 /* Pad STRING to COUNT characters by inserting blanks. */ 1541 int 1542 pad_to (int count, char *string) 1543 { 1544 register int i; 1545 1546 i = strlen (string); 1547 1548 if (i >= count) 1549 string[i++] = ' '; 1550 else 1551 { 1552 while (i < count) 1553 string[i++] = ' '; 1554 } 1555 string[i] = '\0'; 1556 1557 return (i); 1558 } 1559