1 /*
2  * dsp_xm.c - Display results in a Motif window.
3  *
4  * Copyright (C) 1995, 1996 by Scott C. Gray
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, write to the Free Software
18  * Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
19  *
20  * You may contact the author :
21  *   e-mail:  gray@voicenet.com
22  *            grays@xtend-tech.com
23  *            gray@xenotropic.com
24  *
25  * Note: The Motif code was supplied by John Griffin
26  *       <belved!JOHNG@uunet.uu.net> and has been modified
27  *       slightly by Scott C. Gray.
28  */
29 #include <stdio.h>
30 #include <ctype.h>
31 #include "sqsh_config.h"
32 #include "sqsh_debug.h"
33 #include "sqsh_error.h"
34 #include "sqsh_expand.h"
35 #include "sqsh_global.h"
36 #include "sqsh_init.h"
37 #include "dsp.h"
38 
39 /*-- Current Version --*/
40 #if !defined(lint) && !defined(__LINT__)
41 static char RCS_Id[] = "$Id: dsp_x.c,v 1.8 2013/05/07 21:18:02 mwesdorp Exp $";
42 USE(RCS_Id)
43 #endif /* !defined(lint) */
44 
45 #if !defined(USE_X11)
46 
47 int dsp_x( output, cmd, flags, dsp_func )
48 	dsp_out_t   *output;
49 	CS_COMMAND  *cmd;
50 	int          flags;
51 	dsp_t       *dsp_func;
52 {
53 	fprintf( stderr, "dsp_x: X11 support not compiled into sqsh\n" );
54 	return DSP_FAIL;
55 }
56 
57 #else
58 
59 #include <X11/Intrinsic.h>
60 #include <X11/StringDefs.h>
61 
62 /*
63  * cb_data_t:
64  *
65  * This mostly useless data structure is used to pass around important
66  * information between callbacks.  Mainly, this is information about
67  * objects whose state can change according to callbacks.
68  */
69 typedef struct cb_data_st {
70 	int        c_fd;              /* File descriptor for communications */
71 	int        c_lines;           /* Number of lines displayed */
72 	int        c_lastpos;
73 	Widget     c_text;            /* Text widget */
74 	Widget     c_cmd;
75 	Widget     c_lbl;
76 	XtInputId  c_id;              /* Input id */
77 } cb_data_t;
78 
79 /*-- Prototypes --*/
80 static int dsp_x_init _ANSI_ARGS(( int, int, int ));
81 
82 /*
83  * dsp_x:
84  *
85  * Display routine to display the current result into an x window. This
86  * function is interesting in that it creates a pipe-line between itself
87  * and the dsp_horiz display style.
88  */
89 int dsp_x( output, cmd, flags, dsp_func )
90 	dsp_out_t   *output;
91 	CS_COMMAND  *cmd;
92 	int          flags;
93 	dsp_t       *dsp_func;
94 {
95 	int    fd_pair[2];               /* Pair of pipes to communicate */
96 	int    ret;                      /* Return value from display method */
97 	int    width;                    /* Width of screen */
98 	int    height;                   /* Height of screen */
99 	char   number[20];               /* Holder for number */
100 	char  *cp;
101 	int    orig_fd;
102 	int    fd;
103 	int    orig_width;
104 	int    i;
105 
106 	/*
107 	* First, we would like to figure out how large our X window
108 	* really is.
109 	*/
110 	orig_width = g_dsp_props.p_width;
111 	if (*g_dsp_props.p_xgeom == '\0')
112 	{
113 		width  = 80;
114 		height = 25;
115 	}
116 	else
117 	{
118 		i = 0;
119 		for (cp = g_dsp_props.p_xgeom; *cp != '\0' && isdigit( (int) *cp); ++cp)
120 		{
121 			number[i++] = *cp;
122 		}
123 		number[i] = '\0';
124 
125 		width = atoi(number);
126 
127 		if (*cp != '\0')
128 		{
129 			height = atoi( cp + 1 );
130 		}
131 		else
132 		{
133 			height = 25;
134 		}
135 
136 		if (width < 30)
137 		{
138 			width = 80;
139 		}
140 
141 		if (height < 10)
142 		{
143 			height = 25;
144 		}
145 	}
146 
147 	DBG(sqsh_debug(DEBUG_DISPLAY,"dsp_x: width = %d, height = %d\n",
148 		width, height);)
149 
150 	/*-- Create a pair of pipes to talk through --*/
151 	if (pipe(fd_pair) == -1)
152 	{
153 		fprintf( stderr, "dsp_x: Unable to create communications pipe\n" );
154 		return DSP_FAIL;
155 	}
156 
157 	switch (fork())
158 	{
159 		case   -1:          /* Error condition */
160 			fprintf( stderr, "dsp_x: Unable to create child process: %s\n",
161 				strerror(errno)  );
162 			close( fd_pair[0] );
163 			close( fd_pair[1] );
164 			return DSP_FAIL;
165 
166 		case    0:           /* Child process */
167 			/*
168 			 * Keep a handle on the file descriptor to be used to communicate
169 			 * with our parent (sqsh).  And close the other file descriptor
170 			 * that we no longer need.
171 			 */
172 			fd = fd_pair[0];
173 			close( fd_pair[1] );
174 			break;
175 
176 		default:             /* Parent process */
177 
178 			/*
179 			 * Close child's socket.
180 			 */
181 			close( fd_pair[0] );
182 
183 			/*
184 			 * Since we will be redirecting the file descriptor for
185 			 * output, we want to make sure it has flushed its contents
186 			 * before we begin.
187 			 */
188 			dsp_fflush( output );
189 
190 			/*
191 			 * Because we are going to replace the stdout for this process
192 			 * we want to make sure we can restore it before we return.
193 			 */
194 			orig_fd = dup( output->o_fd );
195 
196 			/*
197 			 * Now attach the remaining file descriptor to our output for
198 			 * the life of the selected display method.
199 			 */
200 			dup2( fd_pair[1], output->o_fd );
201 			close( fd_pair[1] );
202 
203 			/*
204 			 * Allow the display method to do whatever it does, however
205 			 * while it runs, the output will be piped into our child
206 			 * process that should be displaying it into an X window.
207 			 */
208 			g_dsp_props.p_width=2040;
209 			ret = dsp_func( output, cmd, flags );
210 			g_dsp_props.p_width=orig_width;
211 
212 			/*
213 			 * Now restore the file descriptors back to their original
214 			 * state.  After that, since output has most likely reached
215 			 * EOF, we need to clear this error condition before returning,
216 			 * otherwise OS's, like SunOS, will not be able to use it.
217 			 */
218 			dup2( orig_fd, output->o_fd );
219 			close( orig_fd );
220 
221 			return ret;
222 	}
223 
224 	dsp_x_init( fd, width, height );
225 	exit(0);
226 }
227 
228 /********************************************************************
229 **
230 **                              X11/MOTIF
231 **
232 ********************************************************************/
233 
234 #if defined(USE_MOTIF)
235 
236 /*-- Include the X headers --*/
237 #include <Xm/Command.h>
238 #include <Xm/Label.h>
239 #include <Xm/Form.h>
240 #include <Xm/ScrolledW.h>
241 #include <Xm/Text.h>
242 #include <Xm/RowColumn.h>
243 #include <Xm/PushB.h>
244 #include <Xm/PanedW.h>
245 #include <Xm/MainW.h>
246 
247 /*-- Prototypes --*/
248 static void dsp_x_input_cb( XtPointer, int*, XtInputId* );
249 static void dsp_x_ok_cb ( Widget, XtPointer, XtPointer );
250 
251 static int dsp_x_init( fd, width, height )
252 	int fd;
253 	int width;
254 	int height;
255 {
256 	int    nlines;
257 
258 	/*-- Widgets that make up our window --*/
259 	Widget       w_top;              /* Top level widget */
260 	Widget       w_main;             /* Top level widget */
261 	Widget       w_res_form;         /* Form constraing */
262 	Widget       w_res;              /* Text Widget */
263 	Widget       w_sql_form;
264 	Widget       w_sql_text;
265 	Widget       w_paned;
266 	Widget       w_btn_form;
267 	Widget       w_btn_ok;
268 	XmString     s_ok;
269 	cb_data_t    cd;                 /* Data for callbacks */
270 	XtInputId    id;                 /* Id of input source */
271 	Dimension    dim;
272 	int          argc;
273 	char        *argv[1];
274 	char        *cp;
275 	char        *xwin_title = NULL;
276 	varbuf_t    *exp_buf    = NULL;
277 
278 	/*
279 	 * At this point we are in the child process, the rest is pretty
280 	 * easy.  We simply open a window and simply place every line we
281 	 * receive from the parent through the fd file descriptor
282 	 * into a window.
283 	 * sqsh-2.1.7 - Pass on a window title through argv[0].
284 	 */
285 	env_get( g_env, "xwin_title", &xwin_title );
286 	if (xwin_title != NULL && *xwin_title != '\0')
287 	{
288 		exp_buf = varbuf_create( 64 );
289 
290 		if (exp_buf == NULL)
291 		{
292 			fprintf( stderr, "sqsh: %s\n", sqsh_get_errstr() );
293 			sqsh_exit( 255 );
294 		}
295 
296 		if (sqsh_expand( xwin_title, exp_buf, 0 ) == False)
297 		{
298 			fprintf( stderr, "sqsh: Error expanding $xwin_title: %s\n",
299 				sqsh_get_errstr() );
300 			sqsh_exit( 255 );
301 		}
302 		argc    = 1;
303 		argv[0] = varbuf_getstr( exp_buf );
304 	}
305 	else
306 	{
307 		argc    = 0;
308 		argv[0] = NULL;
309 	}
310 
311 	/*
312 	 * Calculate the number of lines in the SQL Text buffer.
313 	 * sqsh-2.1.7 : Set a maximum of 10 lines for the SQL buffer,
314 	 * otherwise it might eat up all the X window space in case of a
315 	 * large SQL batch.
316 	 */
317 	nlines = 0;
318 	for (cp = varbuf_getstr(g_sqlbuf); *cp != '\0' && nlines < 10; ++cp)
319 	{
320 		if (*cp == '\n')
321 		{
322 			++nlines;
323 		}
324 	}
325 
326 	w_top = XtInitialize( "sqsh", "Sqsh", NULL, 0, &argc, argv );
327 
328 	w_main =
329 		XtVaCreateManagedWidget("main", xmMainWindowWidgetClass, w_top,
330 			NULL);
331 
332 	w_paned =
333 		XtVaCreateManagedWidget("pane", xmPanedWindowWidgetClass, w_main,
334 			XmNallowResize,     TRUE,
335 			NULL);
336 
337 	w_sql_form =
338 		XtVaCreateManagedWidget("sql_form", xmScrolledWindowWidgetClass, w_paned,
339 			XmNallowResize,     TRUE,
340 			NULL);
341 
342 	w_sql_text =
343 		XtVaCreateManagedWidget( "sql_text", xmTextWidgetClass, w_sql_form,
344 	 		XmNscrollingPolicy, XmAUTOMATIC,
345 	 		XmNeditMode,        XmMULTI_LINE_EDIT,
346 	 		XmNeditable,        0,
347 	 		XmNcolumns,         width,
348 	 		XmNrows,            nlines,
349 	 		XmNresizeWidth,     1,
350 	 		NULL);
351 
352 	/*
353 	 * Populate the sql text box with the sql command being
354 	 * processed.
355 	 */
356 	XmTextSetString(w_sql_text, varbuf_getstr(g_sqlbuf) );
357 
358 	w_res_form =
359 		XtVaCreateManagedWidget("w_res_form", xmScrolledWindowWidgetClass,
360 			w_paned,
361 	 		NULL);
362 
363 	w_res =
364 		XtVaCreateManagedWidget( "text", xmTextWidgetClass, w_res_form,
365 			XmNscrollingPolicy, XmAUTOMATIC,
366 			XmNeditMode,        XmMULTI_LINE_EDIT,
367 			XmNeditable,        0,
368 			XmNrows,            height,
369 			XmNresizeWidth,     1,
370 			XmNcolumns,         width,
371 	 		NULL);
372 
373 	w_btn_form =
374 		XtVaCreateManagedWidget("btn_form", xmFormWidgetClass, w_paned,
375 			XmNrows,            1,
376 			XmNcolumns,         1,
377 			XmNresize,          0,
378 			NULL);
379 
380 	s_ok = XmStringCreateLocalized( "Done" );
381 
382 	w_btn_ok =
383 		XtVaCreateManagedWidget( "ok_button", xmPushButtonWidgetClass, w_btn_form,
384 			XmNlabelString,     s_ok,
385 			XmNleftAttachment,  XmATTACH_FORM,
386 			XmNrightAttachment, XmATTACH_FORM,
387 			XmNbottomAttachment, XmATTACH_FORM,
388 			XmNtopAttachment, XmATTACH_FORM,
389 			NULL );
390 
391 	XtAddCallback( w_btn_ok, XmNactivateCallback, dsp_x_ok_cb, &cd );
392 
393 	id = XtAddInput( fd, (XtPointer)XtInputReadMask,
394 		(XtInputCallbackProc)dsp_x_input_cb, (XtPointer)&cd );
395 
396 	/*
397 	 * We have already attached a cd_data_t to all of our callbacks, so
398 	 * now we need to populate it with enough info to get work done.
399 	 */
400 	cd.c_fd      = fd;
401 	cd.c_lines   = 0;
402 	cd.c_lastpos = 0;
403 	cd.c_text    = w_res;
404 	cd.c_id      = id;
405 
406 	XtRealizeWidget( w_top );
407  	/*
408  	 * Ok, We want to prevent the button from being resized. The easiest
409  	 * way to accomplish that is to set the max and the min.
410  	 */
411  	XtVaGetValues( w_btn_form,
412 		      XmNheight,       &dim,
413  		      NULL);
414 	XtVaSetValues( w_btn_form,
415  		      XmNpaneMaximum,  dim,
416  		      XmNpaneMinimum,  dim,
417  		      NULL);
418 	XtMainLoop();
419 
420 	if (exp_buf != NULL)
421 		varbuf_destroy( exp_buf );
422 	exit(0);
423 }
424 
425 /*
426  * dsp_x_input_cb():
427  *
428  * This function is automatically called whenever input is received
429  * from our parent process via a file descriptor.  It is responsible
430  * for appending any text received onto the end of the text widget.
431  */
432 static void dsp_x_input_cb (client_data, fd, id)
433 	XtPointer    client_data;
434 	int         *fd;
435 	XtInputId   *id;
436 {
437 	char          buffer[2048];     /* Read up to 2K of input */
438 	int           n;
439 	cb_data_t    *cd;
440 
441 	cd = (cb_data_t*)client_data;
442 
443 	/*
444 	 * Read input from the file descriptor.
445 	 */
446 	n = read( *fd, buffer, sizeof(buffer) - 1 );
447 
448 	/*
449 	 * If we hit an error or EOF, then close the file descriptor
450 	 * and remove it from being an X input source.
451 	 */
452 	if (n <= 0)
453 	{
454 		close( *fd );
455 		cd->c_fd = -1;
456 		XtVaSetValues(cd->c_text, XmNcursorPosition, 0,
457 		                          XmNtopCharacter,   0,
458 		                          NULL);
459 		XtRemoveInput( cd->c_id );
460 	}
461 	else
462 	{
463 		buffer[n] = '\0';
464 
465 		/*
466 		 * Append the new buffer to what we already have displayed
467 		 * in the text window.
468 		 */
469 		XmTextReplace( cd->c_text, cd->c_lastpos, cd->c_lastpos + n, buffer );
470 
471 		cd->c_lastpos += n;
472 	}
473 }
474 
475 static void dsp_x_ok_cb (w, client_data, call_data)
476 	Widget       w;
477 	XtPointer    client_data;
478 	XtPointer    call_data;
479 {
480 	exit(0);
481 }
482 
483 #else /* USE_MOTIF */
484 
485 /********************************************************************
486 **
487 **                              X11/ATHENA
488 **
489 ********************************************************************/
490 
491 /*-- Include the X headers --*/
492 #include <X11/Xaw/Command.h>
493 #include <X11/Xaw/Label.h>
494 #include <X11/Xaw/Form.h>
495 #include <X11/Xaw/Viewport.h>
496 #include <X11/Xaw/AsciiText.h>
497 #include <X11/Xaw/Text.h>
498 #include <X11/Xaw/TextSrc.h>
499 #include <X11/Xaw/TextSink.h>
500 #include <X11/Xaw/AsciiSrc.h>
501 #include <X11/Xaw/AsciiSink.h>
502 
503 /*-- Prototypes --*/
504 static void dsp_x_input_cb( XtPointer, int*, XtInputId* );
505 static void dsp_x_ok_cb ( Widget, XtPointer, XtPointer );
506 
507 static int dsp_x_init( fd, width, height )
508 	int fd;
509 	int width;
510 	int height;
511 {
512    Widget       w_top;              /* Top level widget */
513    Widget       w_form;             /* Form constraing */
514    Widget       w_text;             /* Text Widget */
515    Widget       w_cmd;              /* Command widget */
516    Widget       w_lbl;              /* Label widget */
517    cb_data_t    cd;                 /* Data for callbacks */
518    XtInputId    id;                 /* Id of input source */
519    int          text_width;
520    int          argc;
521    char        *argv[1];
522    char        *xwin_title = NULL;
523    varbuf_t    *exp_buf    = NULL;
524 
525    XFontStruct  *font = NULL; /* Font for text widget */
526 
527 	/*
528 	 * At this point we are in the child process, the rest is pretty
529 	 * easy.  We simply open a window and simply place every line we
530 	 * receive from the parent through the fd file descriptor
531 	 * into a window.
532 	 * sqsh-2.1.7 - Pass on a window title through argv[0].
533 	 */
534 	env_get( g_env, "xwin_title", &xwin_title );
535 	if (xwin_title != NULL && *xwin_title != '\0')
536 	{
537 		exp_buf = varbuf_create( 64 );
538 
539 		if (exp_buf == NULL)
540 		{
541 			fprintf( stderr, "sqsh: %s\n", sqsh_get_errstr() );
542 			sqsh_exit( 255 );
543 		}
544 
545 		if (sqsh_expand( xwin_title, exp_buf, 0 ) == False)
546 		{
547 			fprintf( stderr, "sqsh: Error expanding $xwin_title: %s\n",
548 				sqsh_get_errstr() );
549 			sqsh_exit( 255 );
550 		}
551 		argc    = 1;
552 		argv[0] = varbuf_getstr( exp_buf );
553 	}
554 	else
555 	{
556 		argc    = 0;
557 		argv[0] = NULL;
558 	}
559 
560 	/*-- Intialize our X Session --*/
561 	w_top = XtInitialize( "sqsh", "Sqsh", NULL, 0, &argc, argv );
562 
563 	w_form = XtCreateManagedWidget("form", formWidgetClass, w_top, NULL, 0);
564 
565 	w_text = XtVaCreateManagedWidget( "text", asciiTextWidgetClass, w_form,
566 		XtNdisplayCaret,      FALSE,
567 		XtNtop,               XtChainTop,
568 		XtNvertDistance,      5,
569 		XtNhorizDistance,     5,
570 		XtNleft,              XtChainLeft,
571 		XtNright,             XtChainRight,
572 		XtNwidth,             200,
573 		XtNheight,            100,
574 		XtNscrollHorizontal,  XawtextScrollAlways,
575 		XtNscrollVertical,    XawtextScrollAlways,
576 		XtNeditType,          XawtextAppend,
577 		NULL );
578 
579 	/*-- Now, get the AsciiText's Font structure --*/
580 	XtVaGetValues( w_text, XtNfont, &font, NULL );
581 
582 	if (font != NULL)
583 	{
584 		text_width = ((font->max_bounds.width + 1) * width) + 5;
585 
586 		XtVaSetValues( w_text,
587 			XtNwidth,       text_width,
588 			XtNheight,      ((font->max_bounds.ascent +
589 			                  font->max_bounds.descent) * height)+5,
590 			NULL );
591 	}
592 	else
593 	{
594 		/*-- Assume 7 pixel width font --*/
595 		text_width = (80 * 7) + 5;
596 	}
597 
598 	w_lbl = XtVaCreateManagedWidget( "lines", labelWidgetClass, w_form,
599 		XtNlabel,            "Lines: 0     ",
600 		XtNleft,             XtChainLeft,
601 		XtNbottom,           XtChainBottom,
602 		XtNjustify,          XtJustifyLeft,
603 		XtNfromVert,         w_text,
604 		NULL );
605 
606 	w_cmd = XtVaCreateManagedWidget( "cancel_button",commandWidgetClass,w_form,
607 		XtNlabel,           "Cancel",
608 		XtNleft,             XtRubber,
609 		XtNbottom,           XtChainBottom,
610 		XtNfromVert,         w_text,
611 		XtNresizable,        FALSE,
612 		XtNhorizDistance,    ((text_width/2) - 25),
613 		NULL );
614 
615 	/*
616 	 * The command button callback will receive a cb_data_t structure
617 	 * which will describe enough information to figure out how to
618 	 * cancel the current result set.
619 	 */
620 	XtAddCallback( w_cmd, XtNcallback, dsp_x_ok_cb, &cd );
621 
622 	id = XtAddInput( fd, (XtPointer)XtInputReadMask,
623 	                 (XtInputCallbackProc)dsp_x_input_cb, (XtPointer)&cd );
624 
625 	/*
626 	 * We have already attached a cd_data_t to all of our callbacks, so
627 	 * now we need to populate it with enough info to get work done.
628 	 */
629 	cd.c_fd    = fd;
630 	cd.c_lines = 0;
631 	cd.c_text  = w_text;
632 	cd.c_cmd   = w_cmd;
633 	cd.c_lbl   = w_lbl;
634 	cd.c_id    = id;
635 
636 	XtRealizeWidget( w_top );
637 	XtMainLoop();
638 
639 	if (exp_buf != NULL)
640 		varbuf_destroy( exp_buf );
641 	exit(0);
642 }
643 
644 static void dsp_x_ok_cb (w, client_data, call_data)
645 	Widget       w;
646 	XtPointer    client_data;
647 	XtPointer    call_data;
648 {
649 	cb_data_t    *cd;
650 	char          number[20];
651 
652 	cd = (cb_data_t*)client_data;
653 
654 	if (cd->c_fd != -1)
655 	{
656 		sprintf( number, "Lines: %d", cd->c_lines );
657 		XtVaSetValues( cd->c_cmd, XtNlabel, "Done", NULL );
658 		XtVaSetValues( cd->c_text, XtNeditType, XawtextRead, NULL );
659 		XtVaSetValues( cd->c_lbl, XtNlabel, number, NULL );
660 
661 		close( cd->c_fd );
662 		cd->c_fd = -1;
663 		XtRemoveInput( cd->c_id );
664 	}
665 	else
666 	{
667 		exit(0);
668 	}
669 }
670 
671 /*
672  * dsp_x_input_cb():
673  *
674  * This function is automatically called whenever input is received
675  * from our parent process via a file descriptor.  It is responsible
676  * for appending any text received onto the end of the text widget.
677  */
678 static void dsp_x_input_cb (client_data, fd, id)
679 	XtPointer    client_data;
680 	int         *fd;
681 	XtInputId   *id;
682 {
683 	static int    len = 0;          /* Total Length of text in widget */
684 	char          buffer[1024];     /* Read up to 1K of input */
685 	XawTextBlock  b;
686 	int           n;
687 	cb_data_t    *cd;
688 	char         *cp;
689 	char          number[20];
690 
691 	cd = (cb_data_t*)client_data;
692 
693 	/*-- Read input from file descriptor --*/
694 	n = read( *fd, buffer, sizeof(buffer) - 1 );
695 
696 	/*-- On error or EOF close connection --*/
697 	if (n <= 0)
698 	{
699 		sprintf( number, "Lines: %d", cd->c_lines );
700 		XtVaSetValues( cd->c_cmd, XtNlabel, "Done", NULL );
701 		XtVaSetValues( cd->c_text, XtNeditType, XawtextRead, NULL );
702 		XtVaSetValues( cd->c_lbl, XtNlabel, number, NULL );
703 
704 		close( *fd );
705 		cd->c_fd = -1;
706 		XtRemoveInput( cd->c_id );
707 	}
708 	else
709 	{
710 		/*-- Append data read to the end --*/
711 		b.firstPos = 0;
712 		b.length   = n;
713 		b.ptr      = buffer;
714 		b.format   = FMT8BIT;
715 
716 		cp = buffer;
717 		while ((cp = strchr( cp, '\n' )) != NULL)
718 		{
719 			++cd->c_lines;
720 
721 			if ((cd->c_lines % 100) == 0)
722 			{
723 				sprintf( number, "Lines: %d", cd->c_lines );
724 				XtVaSetValues( cd->c_lbl, XtNlabel, number, NULL );
725 			}
726 
727 			cp += 1;
728 		}
729 
730 		XawTextReplace( cd->c_text, len, len, &b );
731 
732 		len += n;
733 	}
734 }
735 
736 #endif /* !USE_MOTIF */
737 #endif /* USE_X11 */
738 
739