diff options
Diffstat (limited to 'texinfo/info/window.c')
-rw-r--r-- | texinfo/info/window.c | 1482 |
1 files changed, 1482 insertions, 0 deletions
diff --git a/texinfo/info/window.c b/texinfo/info/window.c new file mode 100644 index 0000000..304e89c --- /dev/null +++ b/texinfo/info/window.c @@ -0,0 +1,1482 @@ +/* window.c -- Windows in Info. */ + +/* This file is part of GNU Info, a program for reading online documentation + stored in Info format. + + Copyright (C) 1993 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + Written by Brian Fox (bfox@ai.mit.edu). */ + +#include <stdio.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> +#include "nodes.h" +#include "window.h" +#include "display.h" +#include "info-utils.h" +#include "infomap.h" + +/* The window which describes the screen. */ +WINDOW *the_screen = (WINDOW *)NULL; + +/* The window which describes the echo area. */ +WINDOW *the_echo_area = (WINDOW *)NULL; + +/* The list of windows in Info. */ +WINDOW *windows = (WINDOW *)NULL; + +/* Pointer to the active window in WINDOW_LIST. */ +WINDOW *active_window = (WINDOW *)NULL; + +/* The size of the echo area in Info. It never changes, irregardless of the + size of the screen. */ +#define ECHO_AREA_HEIGHT 1 + +/* Macro returns the amount of space that the echo area truly requires relative + to the entire screen. */ +#define echo_area_required (1 + the_echo_area->height) + +/* Initalize the window system by creating THE_SCREEN and THE_ECHO_AREA. + Create the first window ever. + You pass the dimensions of the total screen size. */ +void +window_initialize_windows (width, height) + int width, height; +{ + the_screen = (WINDOW *)xmalloc (sizeof (WINDOW)); + the_echo_area = (WINDOW *)xmalloc (sizeof (WINDOW)); + windows = (WINDOW *)xmalloc (sizeof (WINDOW)); + active_window = windows; + + zero_mem (the_screen, sizeof (WINDOW)); + zero_mem (the_echo_area, sizeof (WINDOW)); + zero_mem (active_window, sizeof (WINDOW)); + + /* None of these windows has a goal column yet. */ + the_echo_area->goal_column = -1; + active_window->goal_column = -1; + the_screen->goal_column = -1; + + /* The active and echo_area windows are visible. + The echo_area is permanent. + The screen is permanent. */ + active_window->flags = W_WindowVisible; + the_echo_area->flags = W_WindowIsPerm | W_InhibitMode | W_WindowVisible; + the_screen->flags = W_WindowIsPerm; + + /* The height of the echo area never changes. It is statically set right + here, and it must be at least 1 line for display. The size of the + initial window cannot be the same size as the screen, since the screen + includes the echo area. So, we make the height of the initial window + equal to the screen's displayable region minus the height of the echo + area. */ + the_echo_area->height = ECHO_AREA_HEIGHT; + active_window->height = the_screen->height - 1 - the_echo_area->height; + window_new_screen_size (width, height, (VFunction *)NULL); + + /* The echo area uses a different keymap than normal info windows. */ + the_echo_area->keymap = echo_area_keymap; + active_window->keymap = info_keymap; +} + +/* Given that the size of the screen has changed to WIDTH and HEIGHT + from whatever it was before (found in the_screen->height, ->width), + change the size (and possibly location) of each window in the screen. + If a window would become too small, call the function DELETER on it, + after deleting the window from our chain of windows. If DELETER is NULL, + nothing extra is done. The last window can never be deleted, but it can + become invisible. */ + +/* If non-null, a function to call with WINDOW as argument when the function + window_new_screen_size () has deleted WINDOW. */ +VFunction *window_deletion_notifier = (VFunction *)NULL; + +void +window_new_screen_size (width, height) + int width, height; +{ + register WINDOW *win; + int delta_height, delta_each, delta_leftover; + int numwins; + + /* If no change, do nothing. */ + if (width == the_screen->width && height == the_screen->height) + return; + + /* If the new window height is too small, make it be zero. */ + if (height < (WINDOW_MIN_SIZE + the_echo_area->height)) + height = 0; + if (width < 0) + width = 0; + + /* Find out how many windows will change. */ + for (numwins = 0, win = windows; win; win = win->next, numwins++); + + /* See if some windows will need to be deleted. This is the case if + the screen is getting smaller, and the available space divided by + the number of windows is less than WINDOW_MIN_SIZE. In that case, + delete some windows and try again until there is either enough + space to divy up among the windows, or until there is only one + window left. */ + while ((height - echo_area_required) / numwins <= WINDOW_MIN_SIZE) + { + /* If only one window, make the size of it be zero, and return + immediately. */ + if (!windows->next) + { + windows->height = 0; + maybe_free (windows->line_starts); + windows->line_starts = (char **)NULL; + windows->line_count = 0; + break; + } + + /* If we have some temporary windows, delete one of them. */ + for (win = windows; win; win = win->next) + if (win->flags & W_TempWindow) + break; + + /* Otherwise, delete the first window, and try again. */ + if (!win) + win = windows; + + if (window_deletion_notifier) + (*window_deletion_notifier) (win); + + window_delete_window (win); + numwins--; + } + + /* The screen has changed height and width. */ + delta_height = height - the_screen->height; /* This is how much. */ + the_screen->height = height; /* This is the new height. */ + the_screen->width = width; /* This is the new width. */ + + /* Set the start of the echo area. */ + the_echo_area->first_row = height - the_echo_area->height; + the_echo_area->width = width; + + /* Check to see if the screen can really be changed this way. */ + if ((!windows->next) && ((windows->height == 0) && (delta_height < 0))) + return; + + /* Divide the change in height among the available windows. */ + delta_each = delta_height / numwins; + delta_leftover = delta_height - (delta_each * numwins); + + /* Change the height of each window in the chain by delta_each. Change + the height of the last window in the chain by delta_each and by the + leftover amount of change. Change the width of each window to be + WIDTH. */ + for (win = windows; win; win = win->next) + { + if ((win->width != width) && ((win->flags & W_InhibitMode) == 0)) + { + win->width = width; + maybe_free (win->modeline); + win->modeline = (char *)xmalloc (1 + width); + } + + win->height += delta_each; + + /* If the previous height of this window was zero, it was the only + window, and it was not visible. Thus we need to compensate for + the echo_area. */ + if (win->height == delta_each) + win->height -= (1 + the_echo_area->height); + + /* If this is not the first window in the chain, then change the + first row of it. We cannot just add delta_each to the first row, + since this window's first row is the sum of the collective increases + that have gone before it. So we just add one to the location of the + previous window's modeline. */ + if (win->prev) + win->first_row = (win->prev->first_row + win->prev->height) + 1; + + /* The last window in the chain gets the extra space (or shrinkage). */ + if (!win->next) + win->height += delta_leftover; + + if (win->node) + recalculate_line_starts (win); + + win->flags |= W_UpdateWindow; + } + + /* If the screen got smaller, check over the windows just shrunk to + keep them within bounds. Some of the windows may have gotten smaller + than WINDOW_MIN_HEIGHT in which case some of the other windows are + larger than the available display space in the screen. Because of our + intial test above, we know that there is enough space for all of the + windows. */ + if ((delta_each < 0) && ((windows->height != 0) && windows->next)) + { + int avail; + + avail = the_screen->height - (numwins + the_echo_area->height); + win = windows; + + while (win) + { + if ((win->height < WINDOW_MIN_HEIGHT) || + (win->height > avail)) + { + WINDOW *lastwin; + + /* Split the space among the available windows. */ + delta_each = avail / numwins; + delta_leftover = avail - (delta_each * numwins); + + for (win = windows; win; win = win->next) + { + lastwin = win; + if (win->prev) + win->first_row = + (win->prev->first_row + win->prev->height) + 1; + win->height = delta_each; + } + + /* Give the leftover space (if any) to the last window. */ + lastwin->height += delta_leftover; + break; + } + else + win= win->next; + } + } +} + +/* Make a new window showing NODE, and return that window structure. + If NODE is passed as NULL, then show the node showing in the active + window. If the window could not be made return a NULL pointer. The + active window is not changed.*/ +WINDOW * +window_make_window (node) + NODE *node; +{ + WINDOW *window; + + if (!node) + node = active_window->node; + + /* If there isn't enough room to make another window, return now. */ + if ((active_window->height / 2) < WINDOW_MIN_SIZE) + return ((WINDOW *)NULL); + + /* Make and initialize the new window. + The fudging about with -1 and +1 is because the following window in the + chain cannot start at window->height, since that is where the modeline + for the previous window is displayed. The inverse adjustment is made + in window_delete_window (). */ + window = (WINDOW *)xmalloc (sizeof (WINDOW)); + window->width = the_screen->width; + window->height = (active_window->height / 2) - 1; +#if defined (SPLIT_BEFORE_ACTIVE) + window->first_row = active_window->first_row; +#else + window->first_row = active_window->first_row + + (active_window->height - window->height); +#endif + window->keymap = info_keymap; + window->goal_column = -1; + window->modeline = (char *)xmalloc (1 + window->width); + window->line_starts = (char **)NULL; + window->flags = W_UpdateWindow | W_WindowVisible; + window_set_node_of_window (window, node); + + /* Adjust the height of the old active window. */ + active_window->height -= (window->height + 1); +#if defined (SPLIT_BEFORE_ACTIVE) + active_window->first_row += (window->height + 1); +#endif + active_window->flags |= W_UpdateWindow; + + /* Readjust the new and old windows so that their modelines and contents + will be displayed correctly. */ +#if defined (NOTDEF) + /* We don't have to do this for WINDOW since window_set_node_of_window () + already did. */ + window_adjust_pagetop (window); + window_make_modeline (window); +#endif /* NOTDEF */ + + /* We do have to readjust the existing active window. */ + window_adjust_pagetop (active_window); + window_make_modeline (active_window); + +#if defined (SPLIT_BEFORE_ACTIVE) + /* This window is just before the active one. The active window gets + bumped down one. The active window is not changed. */ + window->next = active_window; + + window->prev = active_window->prev; + active_window->prev = window; + + if (window->prev) + window->prev->next = window; + else + windows = window; +#else + /* This window is just after the active one. Which window is active is + not changed. */ + window->prev = active_window; + window->next = active_window->next; + active_window->next = window; + if (window->next) + window->next->prev = window; +#endif /* !SPLIT_BEFORE_ACTIVE */ + return (window); +} + +/* These useful macros make it possible to read the code in + window_change_window_height (). */ +#define grow_me_shrinking_next(me, next, diff) \ + do { \ + me->height += diff; \ + next->height -= diff; \ + next->first_row += diff; \ + window_adjust_pagetop (next); \ + } while (0) + +#define grow_me_shrinking_prev(me, prev, diff) \ + do { \ + me->height += diff; \ + prev->height -= diff; \ + me->first_row -=diff; \ + window_adjust_pagetop (prev); \ + } while (0) + +#define shrink_me_growing_next(me, next, diff) \ + do { \ + me->height -= diff; \ + next->height += diff; \ + next->first_row -= diff; \ + window_adjust_pagetop (next); \ + } while (0) + +#define shrink_me_growing_prev(me, prev, diff) \ + do { \ + me->height -= diff; \ + prev->height += diff; \ + me->first_row += diff; \ + window_adjust_pagetop (prev); \ + } while (0) + +/* Change the height of WINDOW by AMOUNT. This also automagically adjusts + the previous and next windows in the chain. If there is only one user + window, then no change takes place. */ +void +window_change_window_height (window, amount) + WINDOW *window; + int amount; +{ + register WINDOW *win, *prev, *next; + + /* If there is only one window, or if the amount of change is zero, + return immediately. */ + if (!windows->next || amount == 0) + return; + + /* Find this window in our chain. */ + for (win = windows; win; win = win->next) + if (win == window) + break; + + /* If the window is isolated (i.e., doesn't appear in our window list, + then quit now. */ + if (!win) + return; + + /* Change the height of this window by AMOUNT, if that is possible. + It can be impossible if there isn't enough available room on the + screen, or if the resultant window would be too small. */ + + prev = window->prev; + next = window->next; + + /* WINDOW decreasing in size? */ + if (amount < 0) + { + int abs_amount = -amount; /* It is easier to deal with this way. */ + + /* If the resultant window would be too small, stop here. */ + if ((window->height - abs_amount) < WINDOW_MIN_HEIGHT) + return; + + /* If we have two neighboring windows, choose the smaller one to get + larger. */ + if (next && prev) + { + if (prev->height < next->height) + shrink_me_growing_prev (window, prev, abs_amount); + else + shrink_me_growing_next (window, next, abs_amount); + } + else if (next) + shrink_me_growing_next (window, next, abs_amount); + else + shrink_me_growing_prev (window, prev, abs_amount); + } + + /* WINDOW increasing in size? */ + if (amount > 0) + { + int total_avail, next_avail = 0, prev_avail = 0; + + if (next) + next_avail = next->height - WINDOW_MIN_SIZE; + + if (prev) + prev_avail = prev->height - WINDOW_MIN_SIZE; + + total_avail = next_avail + prev_avail; + + /* If there isn't enough space available to grow this window, give up. */ + if (amount > total_avail) + return; + + /* If there aren't two neighboring windows, or if one of the neighbors + is larger than the other one by at least AMOUNT, grow that one. */ + if ((next && !prev) || ((next_avail - amount) >= prev_avail)) + grow_me_shrinking_next (window, next, amount); + else if ((prev && !next) || ((prev_avail - amount) >= next_avail)) + grow_me_shrinking_prev (window, prev, amount); + else + { + int change; + + /* This window has two neighbors. They both must be shrunk in to + make enough space for WINDOW to grow. Make them both the same + size. */ + if (prev_avail > next_avail) + { + change = prev_avail - next_avail; + grow_me_shrinking_prev (window, prev, change); + amount -= change; + } + else + { + change = next_avail - prev_avail; + grow_me_shrinking_next (window, next, change); + amount -= change; + } + + /* Both neighbors are the same size. Split the difference in + AMOUNT between them. */ + while (amount) + { + window->height++; + amount--; + + /* Odd numbers grow next, even grow prev. */ + if (amount & 1) + { + prev->height--; + window->first_row--; + } + else + { + next->height--; + next->first_row++; + } + } + window_adjust_pagetop (prev); + window_adjust_pagetop (next); + } + } + if (prev) + prev->flags |= W_UpdateWindow; + + if (next) + next->flags |= W_UpdateWindow; + + window->flags |= W_UpdateWindow; + window_adjust_pagetop (window); +} + +/* Tile all of the windows currently displayed in the global variable + WINDOWS. If argument STYLE is TILE_INTERNALS, tile windows displaying + internal nodes as well, otherwise do not change the height of such + windows. */ +void +window_tile_windows (style) + int style; +{ + WINDOW *win, *last_adjusted; + int numwins, avail, per_win_height, leftover; + int do_internals; + + numwins = avail = 0; + do_internals = (style == TILE_INTERNALS); + + for (win = windows; win; win = win->next) + if (do_internals || !win->node || + (win->node->flags & N_IsInternal) == 0) + { + avail += win->height; + numwins++; + } + + if (numwins <= 1 || !the_screen->height) + return; + + /* Find the size for each window. Divide the size of the usable portion + of the screen by the number of windows. */ + per_win_height = avail / numwins; + leftover = avail - (per_win_height * numwins); + + last_adjusted = (WINDOW *)NULL; + for (win = windows; win; win = win->next) + { + if (do_internals || !win->node || + (win->node->flags & N_IsInternal) == 0) + { + last_adjusted = win; + win->height = per_win_height; + } + } + + if (last_adjusted) + last_adjusted->height += leftover; + + /* Readjust the first_row of every window in the chain. */ + for (win = windows; win; win = win->next) + { + if (win->prev) + win->first_row = win->prev->first_row + win->prev->height + 1; + + window_adjust_pagetop (win); + win->flags |= W_UpdateWindow; + } +} + +/* Toggle the state of line wrapping in WINDOW. This can do a bit of fancy + redisplay. */ +void +window_toggle_wrap (window) + WINDOW *window; +{ + if (window->flags & W_NoWrap) + window->flags &= ~W_NoWrap; + else + window->flags |= W_NoWrap; + + if (window != the_echo_area) + { + char **old_starts; + int old_lines, old_pagetop; + + old_starts = window->line_starts; + old_lines = window->line_count; + old_pagetop = window->pagetop; + + calculate_line_starts (window); + + /* Make sure that point appears within this window. */ + window_adjust_pagetop (window); + + /* If the pagetop hasn't changed maybe we can do some scrolling now + to speed up the display. Many of the line starts will be the same, + so scrolling here is a very good optimization.*/ + if (old_pagetop == window->pagetop) + display_scroll_line_starts + (window, old_pagetop, old_starts, old_lines); + maybe_free (old_starts); + } + window->flags |= W_UpdateWindow; +} + +/* Set WINDOW to display NODE. */ +void +window_set_node_of_window (window, node) + WINDOW *window; + NODE *node; +{ + window->node = node; + window->pagetop = 0; + window->point = 0; + recalculate_line_starts (window); + window->flags |= W_UpdateWindow; + window_adjust_pagetop (window); + window_make_modeline (window); +} + +/* Delete WINDOW from the list of known windows. If this window was the + active window, make the next window in the chain be the active window. + If the active window is the next or previous window, choose that window + as the recipient of the extra space. Otherwise, prefer the next window. */ +void +window_delete_window (window) + WINDOW *window; +{ + WINDOW *next, *prev, *window_to_fix; + + next = window->next; + prev = window->prev; + + /* You cannot delete the only window or a permanent window. */ + if ((!next && !prev) || (window->flags & W_WindowIsPerm)) + return; + + if (next) + next->prev = prev; + + if (!prev) + windows = next; + else + prev->next = next; + + if (window->line_starts) + free (window->line_starts); + + if (window->modeline) + free (window->modeline); + + if (window == active_window) + { + /* If there isn't a next window, then there must be a previous one, + since we cannot delete the last window. If there is a next window, + prefer to use that as the active window. */ + if (next) + active_window = next; + else + active_window = prev; + } + + if (next && active_window == next) + window_to_fix = next; + else if (prev && active_window == prev) + window_to_fix = prev; + else if (next) + window_to_fix = next; + else if (prev) + window_to_fix = prev; + else + window_to_fix = windows; + + if (window_to_fix->first_row > window->first_row) + { + int diff; + + /* Try to adjust the visible part of the node so that as little + text as possible has to move. */ + diff = window_to_fix->first_row - window->first_row; + window_to_fix->first_row = window->first_row; + + window_to_fix->pagetop -= diff; + if (window_to_fix->pagetop < 0) + window_to_fix->pagetop = 0; + } + + /* The `+ 1' is to offset the difference between the first_row locations. + See the code in window_make_window (). */ + window_to_fix->height += window->height + 1; + window_to_fix->flags |= W_UpdateWindow; + + free (window); +} + +/* For every window in CHAIN, set the flags member to have FLAG set. */ +void +window_mark_chain (chain, flag) + WINDOW *chain; + int flag; +{ + register WINDOW *win; + + for (win = chain; win; win = win->next) + win->flags |= flag; +} + +/* For every window in CHAIN, clear the flags member of FLAG. */ +void +window_unmark_chain (chain, flag) + WINDOW *chain; + int flag; +{ + register WINDOW *win; + + for (win = chain; win; win = win->next) + win->flags &= ~flag; +} + +/* Return the number of characters it takes to display CHARACTER on the + screen at HPOS. */ +int +character_width (character, hpos) + int character, hpos; +{ + int printable_limit = 127; + int width = 1; + + if (ISO_Latin_p) + printable_limit = 160; + + if (character > printable_limit) + width = 3; + else if (iscntrl (character)) + { + switch (character) + { + case '\r': + case '\n': + width = the_screen->width - hpos; + break; + case '\t': + width = ((hpos + 8) & 0xf8) - hpos; + break; + default: + width = 2; + } + } + else if (character == DEL) + width = 2; + + return (width); +} + +/* Return the number of characters it takes to display STRING on the screen + at HPOS. */ +int +string_width (string, hpos) + char *string; + int hpos; +{ + register int i, width, this_char_width; + + for (width = 0, i = 0; string[i]; i++) + { + this_char_width = character_width (string[i], hpos); + width += this_char_width; + hpos += this_char_width; + } + return (width); +} + +/* Quickly guess the approximate number of lines to that NODE would + take to display. This really only counts carriage returns. */ +int +window_physical_lines (node) + NODE *node; +{ + register int i, lines; + char *contents; + + if (!node) + return (0); + + contents = node->contents; + for (i = 0, lines = 1; i < node->nodelen; i++) + if (contents[i] == '\n') + lines++; + + return (lines); +} + +/* Calculate a list of line starts for the node belonging to WINDOW. The line + starts are pointers to the actual text within WINDOW->NODE. */ +void +calculate_line_starts (window) + WINDOW *window; +{ + register int i, hpos; + char **line_starts = (char **)NULL; + int line_starts_index = 0, line_starts_slots = 0; + int bump_index; + NODE *node; + + window->line_starts = (char **)NULL; + window->line_count = 0; + node = window->node; + + if (!node) + return; + + /* Grovel the node starting at the top, and for each line calculate the + width of the characters appearing in that line. Add each line start + to our array. */ + i = 0; + hpos = 0; + bump_index = 0; + + while (i < node->nodelen) + { + char *line = node->contents + i; + unsigned int cwidth, c; + + add_pointer_to_array (line, line_starts_index, line_starts, + line_starts_slots, 100, char *); + if (bump_index) + { + i++; + bump_index = 0; + } + + while (1) + { + c = node->contents[i]; + cwidth = character_width (c, hpos); + + /* If this character fits within this line, just do the next one. */ + if ((hpos + cwidth) < window->width) + { + i++; + hpos += cwidth; + continue; + } + else + { + /* If this character would position the cursor at the start of + the next printed screen line, then do the next line. */ + if (c == '\n' || c == '\r' || c == '\t') + { + i++; + hpos = 0; + break; + } + else + { + /* This character passes the window width border. Postion + the cursor after the printed character, but remember this + line start as where this character is. A bit tricky. */ + + /* If this window doesn't wrap lines, proceed to the next + physical line here. */ + if (window->flags & W_NoWrap) + { + hpos = 0; + while (i < node->nodelen && node->contents[i] != '\n') + i++; + + if (node->contents[i] == '\n') + i++; + } + else + { + hpos = the_screen->width - hpos; + bump_index++; + } + break; + } + } + } + } + window->line_starts = line_starts; + window->line_count = line_starts_index; +} + +/* Given WINDOW, recalculate the line starts for the node it displays. */ +void +recalculate_line_starts (window) + WINDOW *window; +{ + maybe_free (window->line_starts); + calculate_line_starts (window); +} + +/* Global variable control redisplay of scrolled windows. If non-zero, it + is the desired number of lines to scroll the window in order to make + point visible. A user might set this to 1 for smooth scrolling. If + set to zero, the line containing point is centered within the window. */ +int window_scroll_step = 0; + +/* Adjust the pagetop of WINDOW such that the cursor point will be visible. */ +void +window_adjust_pagetop (window) + WINDOW *window; +{ + register int line = 0; + char *contents; + + if (!window->node) + return; + + contents = window->node->contents; + + /* Find the first printed line start which is after WINDOW->point. */ + for (line = 0; line < window->line_count; line++) + { + char *line_start; + + line_start = window->line_starts[line]; + + if ((line_start - contents) > window->point) + break; + } + + /* The line index preceding the line start which is past point is the + one containing point. */ + line--; + + /* If this line appears in the current displayable page, do nothing. + Otherwise, adjust the top of the page to make this line visible. */ + if ((line < window->pagetop) || + (line - window->pagetop > (window->height - 1))) + { + /* The user-settable variable "scroll-step" is used to attempt + to make point visible, iff it is non-zero. If that variable + is zero, then the line containing point is centered within + the window. */ + if (window_scroll_step < window->height) + { + if ((line < window->pagetop) && + ((window->pagetop - window_scroll_step) <= line)) + window->pagetop -= window_scroll_step; + else if ((line - window->pagetop > (window->height - 1)) && + ((line - (window->pagetop + window_scroll_step) + < window->height))) + window->pagetop += window_scroll_step; + else + window->pagetop = line - ((window->height - 1) / 2); + } + else + window->pagetop = line - ((window->height - 1) / 2); + + if (window->pagetop < 0) + window->pagetop = 0; + window->flags |= W_UpdateWindow; + } +} + +/* Return the index of the line containing point. */ +int +window_line_of_point (window) + WINDOW *window; +{ + register int i, start = 0; + + /* Try to optimize. Check to see if point is past the pagetop for + this window, and if so, start searching forward from there. */ + if ((window->pagetop > -1 && window->pagetop < window->line_count) && + (window->line_starts[window->pagetop] - window->node->contents) + <= window->point) + start = window->pagetop; + + for (i = start; i < window->line_count; i++) + { + if ((window->line_starts[i] - window->node->contents) > window->point) + break; + } + + return (i - 1); +} + +/* Get and return the goal column for this window. */ +int +window_get_goal_column (window) + WINDOW *window; +{ + if (!window->node) + return (-1); + + if (window->goal_column != -1) + return (window->goal_column); + + /* Okay, do the work. Find the printed offset of the cursor + in this window. */ + return (window_get_cursor_column (window)); +} + +/* Get and return the printed column offset of the cursor in this window. */ +int +window_get_cursor_column (window) + WINDOW *window; +{ + int i, hpos, end; + char *line; + + i = window_line_of_point (window); + + if (i < 0) + return (-1); + + line = window->line_starts[i]; + end = window->point - (line - window->node->contents); + + for (hpos = 0, i = 0; i < end; i++) + hpos += character_width (line[i], hpos); + + return (hpos); +} + +/* Count the number of characters in LINE that precede the printed column + offset of GOAL. */ +int +window_chars_to_goal (line, goal) + char *line; + int goal; +{ + register int i, check, hpos; + + for (hpos = 0, i = 0; line[i] != '\n'; i++) + { + + check = hpos + character_width (line[i], hpos); + + if (check > goal) + break; + + hpos = check; + } + return (i); +} + +/* Create a modeline for WINDOW, and store it in window->modeline. */ +void +window_make_modeline (window) + WINDOW *window; +{ + register int i; + char *modeline; + char location_indicator[4]; + int lines_remaining; + + /* Only make modelines for those windows which have one. */ + if (window->flags & W_InhibitMode) + return; + + /* Find the number of lines actually displayed in this window. */ + lines_remaining = window->line_count - window->pagetop; + + if (window->pagetop == 0) + { + if (lines_remaining <= window->height) + strcpy (location_indicator, "All"); + else + strcpy (location_indicator, "Top"); + } + else + { + if (lines_remaining <= window->height) + strcpy (location_indicator, "Bot"); + else + { + float pt, lc; + int percentage; + + pt = (float)window->pagetop; + lc = (float)window->line_count; + + percentage = 100 * (pt / lc); + + sprintf (location_indicator, "%2d%%", percentage); + } + } + + /* Calculate the maximum size of the information to stick in MODELINE. */ + { + int modeline_len = 0; + char *parent = (char *)NULL, *filename = "*no file*"; + char *nodename = "*no node*"; + char *update_message = (char *)NULL; + NODE *node = window->node; + + if (node) + { + if (node->nodename) + nodename = node->nodename; + + if (node->parent) + { + parent = filename_non_directory (node->parent); + modeline_len += strlen ("Subfile: ") + strlen (node->filename); + } + + if (node->filename) + filename = filename_non_directory (node->filename); + + if (node->flags & N_UpdateTags) + update_message = "--*** Tags out of Date ***"; + } + + if (update_message) + modeline_len += strlen (update_message); + modeline_len += strlen (filename); + modeline_len += strlen (nodename); + modeline_len += 4; /* strlen (location_indicator). */ + + /* 10 for the decimal representation of the number of lines in this + node, and the remainder of the text that can appear in the line. */ + modeline_len += 10 + strlen ("-----Info: (), lines ----, "); + modeline_len += window->width; + + modeline = (char *)xmalloc (1 + modeline_len); + + /* Special internal windows have no filename. */ + if (!parent && !*filename) + sprintf (modeline, "-%s---Info: %s, %d lines --%s--", + (window->flags & W_NoWrap) ? "$" : "-", + nodename, window->line_count, location_indicator); + else + sprintf (modeline, "-%s%s-Info: (%s)%s, %d lines --%s--", + (window->flags & W_NoWrap) ? "$" : "-", + (node && (node->flags & N_IsCompressed)) ? "zz" : "--", + parent ? parent : filename, + nodename, window->line_count, location_indicator); + + if (parent) + sprintf (modeline + strlen (modeline), " Subfile: %s", filename); + + if (update_message) + sprintf (modeline + strlen (modeline), "%s", update_message); + + i = strlen (modeline); + + if (i >= window->width) + modeline[window->width] = '\0'; + else + { + while (i < window->width) + modeline[i++] = '-'; + modeline[i] = '\0'; + } + + strcpy (window->modeline, modeline); + free (modeline); + } +} + +/* Make WINDOW start displaying at PERCENT percentage of its node. */ +void +window_goto_percentage (window, percent) + WINDOW *window; + int percent; +{ + int desired_line; + + if (!percent) + desired_line = 0; + else + desired_line = + (int) ((float)window->line_count * ((float)percent / 100.0)); + + window->pagetop = desired_line; + window->point = + window->line_starts[window->pagetop] - window->node->contents; + window->flags |= W_UpdateWindow; + window_make_modeline (window); +} + +/* Get the state of WINDOW, and save it in STATE. */ +void +window_get_state (window, state) + WINDOW *window; + WINDOW_STATE *state; +{ + state->node = window->node; + state->pagetop = window->pagetop; + state->point = window->point; +} + +/* Set the node, pagetop, and point of WINDOW. */ +void +window_set_state (window, state) + WINDOW *window; + WINDOW_STATE *state; +{ + if (window->node != state->node) + window_set_node_of_window (window, state->node); + window->pagetop = state->pagetop; + window->point = state->point; +} + + +/* **************************************************************** */ +/* */ +/* Manipulating Home-Made Nodes */ +/* */ +/* **************************************************************** */ + +/* A place to buffer echo area messages. */ +static NODE *echo_area_node = (NODE *)NULL; + +/* Make the node of the_echo_area be an empty one. */ +static void +free_echo_area () +{ + if (echo_area_node) + { + maybe_free (echo_area_node->contents); + free (echo_area_node); + } + + echo_area_node = (NODE *)NULL; + window_set_node_of_window (the_echo_area, echo_area_node); +} + +/* Clear the echo area, removing any message that is already present. + The echo area is cleared immediately. */ +void +window_clear_echo_area () +{ + free_echo_area (); + display_update_one_window (the_echo_area); +} + +/* Make a message appear in the echo area, built from FORMAT, ARG1 and ARG2. + The arguments are treated similar to printf () arguments, but not all of + printf () hair is present. The message appears immediately. If there was + already a message appearing in the echo area, it is removed. */ +void +window_message_in_echo_area (format, arg1, arg2) + char *format; + void *arg1, *arg2; +{ + free_echo_area (); + echo_area_node = build_message_node (format, arg1, arg2); + window_set_node_of_window (the_echo_area, echo_area_node); + display_update_one_window (the_echo_area); +} + +/* Place a temporary message in the echo area built from FORMAT, ARG1 + and ARG2. The message appears immediately, but does not destroy + any existing message. A future call to unmessage_in_echo_area () + restores the old contents. */ +static NODE **old_echo_area_nodes = (NODE **)NULL; +static int old_echo_area_nodes_index = 0; +static int old_echo_area_nodes_slots = 0; + +void +message_in_echo_area (format, arg1, arg2) + char *format; + void *arg1, *arg2; +{ + if (echo_area_node) + { + add_pointer_to_array (echo_area_node, old_echo_area_nodes_index, + old_echo_area_nodes, old_echo_area_nodes_slots, + 4, NODE *); + } + echo_area_node = (NODE *)NULL; + window_message_in_echo_area (format, arg1, arg2); +} + +void +unmessage_in_echo_area () +{ + free_echo_area (); + + if (old_echo_area_nodes_index) + echo_area_node = old_echo_area_nodes[--old_echo_area_nodes_index]; + + window_set_node_of_window (the_echo_area, echo_area_node); + display_update_one_window (the_echo_area); +} + +/* A place to build a message. */ +static char *message_buffer = (char *)NULL; +static int message_buffer_index = 0; +static int message_buffer_size = 0; + +/* Ensure that there is enough space to stuff LENGTH characters into + MESSAGE_BUFFER. */ +static void +message_buffer_resize (length) + int length; +{ + if (!message_buffer) + { + message_buffer_size = length + 1; + message_buffer = (char *)xmalloc (message_buffer_size); + message_buffer_index = 0; + } + + while (message_buffer_size <= message_buffer_index + length) + message_buffer = (char *) + xrealloc (message_buffer, + message_buffer_size += 100 + (2 * length)); +} + +/* Format MESSAGE_BUFFER with the results of printing FORMAT with ARG1 and + ARG2. */ +static void +build_message_buffer (format, arg1, arg2) + char *format; + void *arg1, *arg2; +{ + register int i, len; + void *args[2]; + int arg_index = 0; + + args[0] = arg1; + args[1] = arg2; + + len = strlen (format); + + message_buffer_resize (len); + + for (i = 0; format[i]; i++) + { + if (format[i] != '%') + { + message_buffer[message_buffer_index++] = format[i]; + len--; + } + else + { + char c; + + c = format[++i]; + + switch (c) + { + case '%': /* Insert a percent sign. */ + message_buffer_resize (len + 1); + message_buffer[message_buffer_index++] = '%'; + break; + + case 's': /* Insert the current arg as a string. */ + { + char *string; + int string_len; + + string = (char *)args[arg_index++]; + string_len = strlen (string); + + message_buffer_resize (len + string_len); + sprintf + (message_buffer + message_buffer_index, "%s", string); + message_buffer_index += string_len; + } + break; + + case 'd': /* Insert the current arg as an integer. */ + { + long long_val; + int integer; + + long_val = (long)args[arg_index++]; + integer = (int)long_val; + + message_buffer_resize (len + 32); + sprintf + (message_buffer + message_buffer_index, "%d", integer); + message_buffer_index = strlen (message_buffer); + } + break; + + case 'c': /* Insert the current arg as a character. */ + { + long long_val; + int character; + + long_val = (long)args[arg_index++]; + character = (int)long_val; + + message_buffer_resize (len + 1); + message_buffer[message_buffer_index++] = character; + } + break; + + default: + abort (); + } + } + } + message_buffer[message_buffer_index] = '\0'; +} + +/* Build a new node which has FORMAT printed with ARG1 and ARG2 as the + contents. */ +NODE * +build_message_node (format, arg1, arg2) + char *format; + void *arg1, *arg2; +{ + NODE *node; + + message_buffer_index = 0; + build_message_buffer (format, arg1, arg2); + + node = message_buffer_to_node (); + return (node); +} + +/* Convert the contents of the message buffer to a node. */ +NODE * +message_buffer_to_node () +{ + NODE *node; + + node = (NODE *)xmalloc (sizeof (NODE)); + node->filename = (char *)NULL; + node->parent = (char *)NULL; + node->nodename = (char *)NULL; + node->flags = 0; + + /* Make sure that this buffer ends with a newline. */ + node->nodelen = 1 + strlen (message_buffer); + node->contents = (char *)xmalloc (1 + node->nodelen); + strcpy (node->contents, message_buffer); + node->contents[node->nodelen - 1] = '\n'; + node->contents[node->nodelen] = '\0'; + return (node); +} + +/* Useful functions can be called from outside of window.c. */ +void +initialize_message_buffer () +{ + message_buffer_index = 0; +} + +/* Print FORMAT with ARG1,2 to the end of the current message buffer. */ +void +printf_to_message_buffer (format, arg1, arg2) + char *format; + void *arg1, *arg2; +{ + build_message_buffer (format, arg1, arg2); +} + +/* Return the current horizontal position of the "cursor" on the most + recently output message buffer line. */ +int +message_buffer_length_this_line () +{ + register int i; + + if (!message_buffer_index) + return (0); + + for (i = message_buffer_index; i && message_buffer[i - 1] != '\n'; i--); + + return (string_width (message_buffer + i, 0)); +} + +/* Pad STRING to COUNT characters by inserting blanks. */ +int +pad_to (count, string) + int count; + char *string; +{ + register int i; + + i = strlen (string); + + if (i >= count) + string[i++] = ' '; + else + { + while (i < count) + string[i++] = ' '; + } + string[i] = '\0'; + + return (i); +} |