/* $Header: Xtextlib.c,v 10.4 86/02/01 15:42:32 tony Rel $ */
/* Library of routines for creating a simple text output window.
* Routines in the library are:
* TextCreate Creates a new instance of a text window
* TextDestroy Destroys the window
* TextClear Clears a text window
* TextRedisplay Redisplays the window
* TextEvent Handles exposure and unmapping events
* TextPutString Displays a string in a text window
* TextPutChar Displays a character in a text window
* TextPrintf Does a printf in a text window
* All these routines pass around a pointer to a TextWindow data structure:
* typedef struct _TextWindow {
* Window w; Window to use
* FontInfo *font; Font to use for text
* short num_lines; Number of lines in the window
* short num_chars; The length of each line
* short mapped; Whether or not the window is mapped
* short height; Height of window in pixels
* short width; Width of window in pixels
* short first_line; The index of the first line
* char **lines; Ptr to array of text lines
* short *line_length; Ptr to array of line lengths (in pixels)
* short *line_chars; Ptr to array of line lengths in chars
* short last_line; Which line is the last
* short last_char; Length of the last line
* short next_x; X-coord for next character
* short next_y; Y-coord for next character
* unsigned int eventmask; List of events we're interested in
* char *scroll_history; Ptr to list of scroll amounts
* short scroll_count; Number of outstanding scrolls
* short scroll_start; Where in the history the history starts
* short old_scrolls; Number of ignorable outstanding scrolls
* short fastscroll; Whether or not to use fast scrolling
* Applications should not modify anything in this data structure, obviously!
* They may, however, have reason to get information out of it. (Such as the
* window id for mapping).
* Information about the first line of the window is stored in the array
* entries subscripted by [first_line]; the arrays wrap back up at the end.
* Last_char should always be the same as line_chars[last_line].
* Similarly, next_x should always be the same as line_length[last_line];
* The only complicated thing about these procedures is the way they keep
* track of scrolling. When a scroll is done, X sends ExposeRegions for
* every region that needs to be patched up and then an ExposeCopy event.
* The ExposeCopy comes even if there were no regions. The only problem
* is that more scrolls may have been done in the meantime. So we keep a
* history of how much cumulative scrolling has been done in the
* scroll_history list. scroll_start tells which one to start with, and
* scroll_count tells how many there are (they wrap around). The list is
* num_lines long since anything that's scrolled away longer ago than that
* has scrolled off the screen. The old_scrolls field gets set whenever the
* screen is fully updated for some reason or other; it means that that
* many ExposeCopy events can be completely ignored since the screen has
/* Define the width of the left margin */
char *calloc(), *malloc(), *realloc();
/* The following variable is sometimes set by TextPutString to temporarily
disable screen updating. */
static int dont_update
= FALSE
;
/* TextCreate creates a new window which will use the
* specified font. The window is height lines high and width
* characters wide. Note that since a variable-width font may be
* used, the width is calculated using the average width of the font.
* Colors are used as specified.
TextWindow
*TextCreate (width
, height
, x
, y
, parent
, fontname
,
bwidth
, fgpixel
, bgpixel
, bordercolor
, fastscroll
)
int height
, width
, x
, y
, bwidth
, fastscroll
;
if ((t
= (TextWindow
*) malloc(sizeof(TextWindow
))) ==
if ((f
= t
->font
= XOpenFont(fontname
)) == NULL
) {
if ((bgpixmap
= XMakeTile(bgpixel
)) == NULL
) {
t
->width
= width
* f
->width
+ mar_width
;
t
->height
= height
* f
->height
;
t
->w
= XCreateWindow (parent
, x
, y
, t
->width
, t
->height
,
bwidth
, bordercolor
, bgpixmap
);
t
->eventmask
= ExposeRegion
| ExposeCopy
| UnmapWindow
;
/* (ExposeRegion automatically selects ExposeWindow) */
XSelectInput (t
->w
, t
->eventmask
);
XSetResizeHint (t
->w
, mar_width
, 0, f
->width
, f
->height
);
t
->fastscroll
= fastscroll
;
if ((t
->lines
= (char **)
calloc (height
, sizeof (char *))) == NULL
) {
if ((t
->line_length
= (short *)
calloc (height
, sizeof (short))) == NULL
) {
if ((t
->line_chars
= (short *)
calloc (height
, sizeof (short))) == NULL
) {
for (i
= 0; i
< height
; i
++) {
if ((t
->lines
[i
] = (char *)
calloc (width
+1, sizeof (char))) == NULL
) {
if ((t
->scroll_history
= calloc(height
, sizeof (char))) == NULL
) {
t
->scroll_count
= t
->scroll_start
= t
->old_scrolls
= 0;
/* Free all the storage associated with a textwindow */
/* Free things in the order we allocated them. If something doesn't
exist, don't free it!) */
if (t
->font
->fixedwidth
== 0) free(t
->font
->widths
);
if (t
->w
) XDestroyWindow(t
->w
);
for (i
= 0; i
< t
->num_lines
; i
++) {
if (t
->lines
[i
]) free(t
->lines
[i
]);
if (t
->line_length
) free (t
->line_length
);
if (t
->line_chars
) free (t
->line_chars
);
if (t
->scroll_history
) free (t
->scroll_history
);
/* And finally the data structure itself! */
/* Clear out a text window and redisplay */
for (i
= 0; i
< t
->num_lines
; i
++) {
t
->line_length
[i
] = mar_width
; /* Allow a left margin */
t
->next_x
= mar_width
; /* Allow a left margin */
/* Redisplays a text window */
/* Clear the border area */
XPixSet(t
->w
, 0, 0, mar_width
, t
->height
, t
->bgpixel
);
Redisplay_lines(t
, 0, t
->num_lines
- 1);
/* Any outstanding copies from scrolls can now be ignored */
t
->old_scrolls
= t
->scroll_count
;
t
->scroll_count
= t
->scroll_start
= 0;
Redisplay_lines(t
, start
, finish
)
register int i
, j
, y
, height
= t
->font
->height
, x
, width
;
if (start
< 0) start
= 0;
j
= start
+ t
->first_line
;
for (i
= start
; i
<= finish
; i
++) {
if (j
>= t
->num_lines
) j
= 0;
XText (t
->w
, mar_width
, y
, t
->lines
[j
], t
->line_chars
[j
],
t
->font
->id
, t
->fgpixel
, t
->bgpixel
);
if (width
> 0) XPixSet(t
->w
, x
, y
, width
, height
, t
->bgpixel
);
/* Handles an event. If it's not an event it knows how to deal with,
returns TRUE, otherwise FALSE. */
XExposeEvent
*ee
= (XExposeEvent
*) e
;
if (ee
->height
!= t
->height
|| ee
->width
!= t
->width
) {
Change_text_window_size(t
, ee
->height
/ t
->font
->height
,
ee
->width
/ t
->font
->width
);
/* If there have been more scrolls than there are lines,
this stuff has already scrolled off! */
if (t
->scroll_count
> t
->num_lines
) return FALSE
;
/* If this is for an old scroll, ignore it */
if (ee
->detail
== ExposeCopy
&& t
->old_scrolls
) return FALSE
;
if (t
->scroll_count
> 0) {
offset
= t
->scroll_history
[t
->scroll_start
];
Redisplay_lines(t
, ee
->y
/ t
->font
->height
- offset
,
(ee
->y
+ ee
->height
- 1) / t
->font
->height
- offset
);
case ExposeCopy
: /* We've finished the events for one scroll */
/* If there are old scrolls, just decrement the count and
if (t
->scroll_count
< t
->num_lines
) {
if (t
->scroll_start
>= t
->num_lines
) t
->scroll_start
= 0;
Change_text_window_size (t
, new_h
, new_w
)
register int new_h
, new_w
;
Normalize(t
); /* Rearrange lines so that first_line = 0 */
/* First free up any now extraneous lines */
for (i
= new_h
; i
< t
->num_lines
; i
++) free(t
->lines
[i
]);
if ((t
->lines
= (char **)
realloc(t
->lines
, new_h
* sizeof (char *))) == NULL
) {
if ((t
->line_length
= (short *)
realloc(t
->line_length
, new_h
* sizeof (short))) == NULL
) {
if ((t
->line_chars
= (short *)
realloc(t
->line_chars
, new_h
* sizeof (short))) == NULL
) {
if ((t
->scroll_history
= realloc(t
->scroll_history
, new_h
)) == NULL
) {
for (i
= 0; i
< new_h
; i
++) {
if ((curline
= t
->lines
[i
] =
realloc(t
->lines
[i
], new_w
+ 1)) == NULL
) {
if (t
->line_chars
[i
] > new_w
) {
t
->line_chars
[i
] = new_w
;
curline
[new_w
] = '\0'; /* Truncate the line */
t
->line_length
[i
] = mar_width
+
XStringWidth (curline
, t
->font
, 0, 0);
if ((t
->lines
[i
] = malloc(new_w
+1)) == NULL
) {
t
->line_length
[i
] = mar_width
;
if (t
->last_line
>= new_h
) {
t
->last_line
= new_h
- 1;
t
->last_char
= t
->line_chars
[t
->last_line
];
t
->next_x
= t
->line_length
[t
->last_line
];
t
->next_y
= t
->last_line
* t
->font
->height
;
} else if (t
->last_char
> new_w
) {
t
->last_char
= t
->line_chars
[t
->last_line
];
t
->next_x
= t
->line_length
[t
->last_line
];
t
->height
= new_h
* t
->font
->height
;
t
->width
= new_w
* t
->font
->width
+ mar_width
;
/* Routine to re-arrange the lines in a window structure so that first_line
if (t
->first_line
== 0) return;
t
->last_line
-= t
->first_line
;
if (t
->last_line
< 0) t
->last_line
+= t
->num_lines
;
Spin_lines(t
, 0, t
->num_lines
-1, t
->first_line
);
/* Spin lines rotates the m through n lines of the arrays
forward offset places. For example, 012345 spun forward 2 is 234501.
It's straightforward to spin the first part of the arrays; and we
call Spin_lines recursively to do the last offset elements */
/* Actually, it's tail-recursive, so I just use a loop. But I can
Spin_lines(t
, m
, n
, offset
)
register int temp
; /* Temporaries */
if (offset
== 0 || offset
> n
-m
) return;
for (i
= m
; i
<= n
-offset
; i
++) {
temp
= t
->line_length
[i
];
t
->line_length
[i
] = t
->line_length
[offset
+i
];
t
->line_length
[offset
+i
] = temp
;
t
->line_chars
[i
] = t
->line_chars
[offset
+i
];
t
->line_chars
[offset
+i
] = temp
;
t
->lines
[i
] = t
->lines
[offset
+i
];
t
->lines
[offset
+i
] = tempc
;
/* Spin_lines(t, n-offset+1, n, offset - ((n-m+1) % offset)); */
offset
-= (n
- temp
+ 1) % offset
;
/* Routine to put a string in a text window. If fastscroll is
set in the TextWindow structure, a single block scroll is done instead
of scrolling at each newline. */
#define verybig 10000 /* Amount to scroll if we should refresh instead */
int jump
= t
->fastscroll
; /* Whether to do jump scrolling */
if (jump
) jump
= Count_lines (t
, str
, &newlines
, &scroll
);
while (*ch
!= '\0' && *ch
!= '\n') ch
++;
if (jump
&& newlines
== scroll
) {
Clear_lines (t
, newlines
);
dont_update
= TRUE
; /* Stop updating now */
if (scroll
!= verybig
) Scroll_text_window (t
, scroll
);
/* Count the number of lines in str, calculate how much scrolling
will be needed, and return whether this amount is positive */
int Count_lines (t
, str
, newlines
, scroll
)
register int num_lines
= 0;
register int lines_left
, height
= t
->num_lines
;
if (*str
++ == '\n') num_lines
++;
if (num_lines
<= 1) return FALSE
; /* Don't bother jump scrolling */
/* Would this fill the screen? */
if (num_lines
>= height
) {
/* Calculate the number of lines left in the window */
lines_left
= height
- (t
->last_line
- t
->first_line
+ 1);
if (lines_left
>= height
) lines_left
-= height
;
/* Figure out how many lines to scroll */
if (num_lines
<= 0) return FALSE
; /* Enough room already */
/* Clear a number of lines in the window data structure */
register int i
, start
= t
->first_line
;
register int height
= t
->num_lines
;
/* If this would fill the screen, clear it instead */
if (scroll
>= t
->height
) {
if (t
->first_line
>= height
) t
->first_line
-= height
;
/* Now clear the blank lines */
for (i
= 0; i
< scroll
; i
++) {
t
->lines
[start
][0] = '\0';
t
->line_chars
[start
] = 0;
t
->line_length
[start
] = mar_width
; /* Allow a left margin */
if (start
>= height
) start
= 0;
/* Store the characters of a string in the window and update the screen,
but only if dont_update isn't set */
register char *curline
= t
->lines
[t
->last_line
];
register int curchar
= t
->last_char
;
register int x
= t
->next_x
;
register FontInfo
*f
= t
->font
;
int start_x
= t
->next_x
, start
= curchar
,
minch
= f
->firstchar
, maxch
= f
->lastchar
;
/* First store the characters in the line */
while (*ch
!= '\0' && curchar
< t
->num_chars
) {
if (*ch
>= minch
&& *ch
<= maxch
) {
x
+= f
->fixedwidth
? f
->width
: f
->widths
[*ch
- minch
];
t
->line_chars
[t
->last_line
] = t
->last_char
= curchar
;
t
->line_length
[t
->last_line
] = t
->next_x
= x
;
if (dont_update
|| !t
->mapped
) return;
/* And then update the screen */
if (start
< t
->num_chars
) {
XText (t
->w
, start_x
, t
->next_y
, str
, curchar
-start
,
f
->id
, t
->fgpixel
, t
->bgpixel
);
/* Textputchar displays a character in the text window. It
* responds to \n as a special character and just displays anything else.
register int i
, height
= t
->num_lines
;
register char *curline
= t
->lines
[t
->last_line
];
register FontInfo
*f
= t
->font
;
if (t
->last_line
== t
->first_line
- 1 ||
(t
->last_line
== height
- 1 && t
->first_line
== 0)) {
/* The screen is full...clear out the first line */
t
->lines
[t
->first_line
][0] = '\0';
t
->line_chars
[t
->first_line
] = 0;
t
->line_length
[t
->first_line
] = mar_width
;
t
->first_line
++; /* And advance it */
if (t
->first_line
== height
) t
->first_line
= 0;
if (!dont_update
&& t
->mapped
) Scroll_text_window (t
, 1);
} else if (!dont_update
) t
->next_y
+= f
->height
;
if (t
->last_line
== height
) t
->last_line
= 0;
default: /* Just insert the character */
t
->line_chars
[t
->last_line
]++;
if (t
->last_char
> t
->num_chars
) break;
curline
[t
->last_char
] = ch
;
curline
[t
->last_char
+1] = '\0';
if (!dont_update
&& t
->mapped
) {
XText(t
->w
, t
->next_x
, t
->next_y
, &ch
, 1,
f
->id
, t
->fgpixel
, t
->bgpixel
);
if (ch
<= f
->firstchar
&& ch
>= f
->lastchar
) {
t
->line_length
[t
->last_line
] = t
->next_x
+=
(f
->fixedwidth
? f
->width
:
f
->widths
[ch
- f
->lastchar
]);
/* This procedure moves the contents of a text window up n lines.
Scroll_text_window (t
, n
)
register int i
, y
, x
, width
, j
;
int height
= t
->font
->height
;
int scrollsize
= n
* height
;
/* First shift up the contents */
XMoveArea(t
->w
, 0, scrollsize
, 0, 0, t
->width
, t
->height
-scrollsize
);
/* Now redisplay the bottom n lines */
y
= height
* (t
->num_lines
- n
);
if (i
< 0) i
+= t
->num_lines
;
for (j
= 0; j
< n
; j
++) {
XText (t
->w
, mar_width
, y
, t
->lines
[i
], t
->line_chars
[i
],
t
->font
->id
, t
->fgpixel
, t
->bgpixel
);
if (width
> 0) XPixSet(t
->w
, x
, y
, width
, height
, t
->bgpixel
);
if (i
== t
->num_lines
) i
= 0;
/* Add the current scroll to all values in the scroll history,
then add a new entry at the end (the history wraps!) */
for (j
= 0; j
< t
->scroll_count
; j
++) {
t
->scroll_history
[i
] += n
;
if (i
>= t
->num_lines
) i
= 0;
t
->scroll_history
[i
] = n
;
if (t
->scroll_count
> t
->num_lines
) t
->scroll_start
++; /* trash one */
#define TEXT_BUFSIZE 2048
TextPrintf(t
, format
, args
)
char buffer
[TEXT_BUFSIZE
+1];
_strbuf
._flag
= _IOWRT
+_IOSTRG
;
_strbuf
._cnt
= TEXT_BUFSIZE
;
_doprnt(format
, &args
, &_strbuf
);
_strbuf
._cnt
++; /* Be sure there's room for the \0 */
TextPutString(t
, buffer
);