[comp.windows.ms.programmer] wfprintf - a windows fprintf implementation

alen@crash.cts.com (Alen Shapiro) (12/08/90)

(extract from the readme)...
This is wfprintf(), it is packaged with a working example of its use,
(I wonder where I got that idea). Just unshar it on a friendly Unix
box, xmodem it to an empty PC directory, type nmk<CR> and
run the resulting wfprintf.exe under windows3.0.

Fits fairly easily into existing applications (2 hooks - see the readme).

--alen@crash.cts.com

Use it however you like, copy, modify, break it, for profit or not.

----------------8<-----8=------8<-----cut---here------------8<---------
#!/bin/sh
# this is a shar archive, run it through /bin/sh
# in an otherwise empty directory
#
echo extracting wfprintf.c
cat > wfprintf.c << 'MyEof'
/* wfprintf package for Windows 3.0 (c) 1990 Alen Shapiro and Carl Lambert
 * Delphi Management Inc. Permission is hereby given for the copying, use and
 * modification of this package as needed, for any purpose, providing
 * the following copyright notice is included on this or any derived library. 
 * If you improve the package, please mail diffs and an explanation to
 * alen%shappy@crash.cts.com. If improvements are incorporated, your name
 * will appear with the original authors on the (c) notice.
 * This is not public domain but it's pretty close. Hope it comes in handy.
 * No warranty expressed or implied - WARNING, "as is" software.
 */

static char *SCCSId = "wfprintf v1.0 (c) 1990 Alen Shapiro & Carl Lambert, Delphi Management Inc.";

/* fprintf implementation to a Windows 3.0 window, up to WF_HORZ proportional
 * characters per line, WF_VERT lines per window. Resize the window at your
 * peril. Could be made resizable with some event catchers and a additional
 * textmetric calls (any takers?). Is there a better way to do the variable
 * number of args type interface demanded by fprintf?
 * Someone wanna implement the ANSI control sequences :-) ?
 *
 * BTW - there is a much better way to do this - take the struct scr
 * below and allocate an array of them (NWINS), build a wfopen function
 * that returns a pointer to the next unused element in the array, mark
 * it used, allocates "screen" space and gets font/window info. build a
 * wfclose function to deallocate the space and mark the element unused
 * one again. The return value from wfopen could be passed as the first
 * arg to wfprintf and the stupid tests to stderr/stdout could be removed.
 * What a lot of work for debugging print capability!!. Wrefresh would
 * have to refresh all used elements in this hypothetical wio array.
 */
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "wfprintf.h"

extern HWND htxt_wnd;

struct scr {
	TEXTMETRIC  textmetric;	/* current font-info */
	int			lpbuf[(int)(WF_TILDE-WF_SPACE)];	/* buffer for printable char widths */
	char		**screen;	/* screen refresh array */
	HWND		txt_wnd;	/* handle of text window */
	RECT		winrect;	/* text window bounds rect */
};

static struct scr scr;

/* calloc that is in-line callable and checks for errors
 */
char *
ccalloc(no, sz)
	unsigned no, sz; {
	char *addr = calloc(no, sz);

	if(addr == (char *)NULL) {
		char msg[100];

		sprintf(msg, "unable to calloc %u chunk(s) of %u byte(s)", no, sz);
		MessageBox(scr.txt_wnd, msg, NULL, MB_OK|MB_ICONHAND);
		exit(1);
	}
}

/* in line callable getcd()
 */
HDC
ggetdc(hwin)
	HWND hwin; {
	HDC handl = GetDC(hwin);

	if(handl == (HDC)NULL) {
		MessageBox(scr.txt_wnd, "GetDC failure - expect catastrophic badness :-(",NULL,MB_OK | MB_ICONHAND);
	}
	return(handl);
}

/* refresh the whole screen from memory, called from event
 * loop whenever the text window is activated
 */
void
wrefresh() {
	int i, y=0;
	HDC hdc;
	char **sptr;

	if(scr.screen != (char **)NULL) {	/* in case refresh before first print */
		InvalidateRgn(scr.txt_wnd, NULL, WF_ERASE_BK);
		UpdateWindow(scr.txt_wnd);
		hdc = ggetdc(scr.txt_wnd);
		for(sptr=scr.screen, i=0 ; i < WF_VERT ; i++, sptr++) {
			TextOut(hdc, 0, y, *sptr, (size_t)strlen(*sptr));
			y += scr.textmetric.tmExternalLeading + scr.textmetric.tmHeight;
		}
		ReleaseDC(scr.txt_wnd, hdc);
	}
}

/* rotate the addresses of the copies of screen lines
 * kept in memory so that the top line is lost and becomes
 * the store used for new characters added to the bottom of the screen
 */
static void
wshift(screen)
	char **screen; {
	int	i;
	char *tmpline = *screen;

	/* NULL out the old "top line" */
	*tmpline = (char)NULL;

	/* shift "copy of screen" lines up one */
	for(i=0 ; i < WF_VERT-1 ; i++)
		*screen++ = *(screen+1);

	/* old "top line" becomes new "last line", ready to accept
	 * fresh characters
	 */
	*screen = tmpline;
}

/* scroll the window up one line height (according to font
 * details). Keep memory copy of screen chars in step by
 * calling wshift(). Reset global pixel position and logical char
 * position (*pposx resp. *xptr) to left of window.
 */
static void
wscroll(pposx, xptr, yptr, pscr)
	int *pposx, *xptr, *yptr;
	struct scr *pscr; {

	pscr->screen[*yptr][*xptr] = (char)NULL;
	wshift(pscr->screen);
	ScrollWindow(pscr->txt_wnd, 0, -(pscr->textmetric.tmExternalLeading +
							pscr->textmetric.tmHeight), NULL, NULL);
	UpdateWindow(pscr->txt_wnd);

	/* LOOK OUT - lots of global side effects */
	*xptr = *pposx = 0;
	*yptr = WF_VERT-1;
}

/* output a character to the current screen pixel-x position (*pposx)
 * and the current pixel-y position (derived from font-info and y-index
 * (*yptr). Advance pixe-x position by the width of the current character
 * if line-wrap is indicated then call wscroll() to deal with both local and
 * screen scrolling.
 */
static void
woutchar(ch, pposx, xptr, yptr, pscr)
	char ch;
	int *pposx, *xptr, *yptr;
	struct scr *pscr; {
	int chwidth;
	HDC hdc;
	int winwidth = pscr->winrect.right - pscr->winrect.left;
	char *cptr; /* can't set it here in case of wscroll */

	if(ch >= WF_SPACE && ch <= WF_TILDE)
		chwidth = pscr->lpbuf[(int)(ch-WF_SPACE)];
	else
		chwidth = WF_DEFAULTWIDTH;
	if(chwidth <= 0)
		chwidth = WF_DEFAULTWIDTH;

	if(((*pposx+chwidth) >= (winwidth-WF_WINBORDER)) || (*xptr >= (WF_HORZ-1)))
		wscroll(pposx, xptr, yptr, pscr);

	/* output a char to the current screen location [*xptr][*yptr] */
	hdc = ggetdc(pscr->txt_wnd);
	TextOut(hdc, *pposx, *yptr * (pscr->textmetric.tmExternalLeading + pscr->textmetric.tmHeight), &ch, 1);
	ReleaseDC(pscr->txt_wnd, hdc);

	*pposx += chwidth;
	cptr = &(pscr->screen[*yptr][(*xptr)++]); /* LOOK OUT...global side-effect */
	*cptr++ = ch;
	*cptr = (char)NULL; /* keep string terminated */
}

/* output a chunk of text, expand tabs, optionally terminate the line
 * by scrolling (if(nlflag)) . Host point for screen position statics.
 * calls woutchar() and wscroll() to do the dirty work, passing pointers
 * to the statics and globals that describe the state of the screen.
 */
static void
woutline(str, nlflag, pscr)
	char *str;
	int nlflag;
	struct scr *pscr; {
	int len = strlen(str), i, j;
	static int x = 0, lx = 0, y = WF_VERT-1;
	static int posx = 0;
	
	if(str == (char *)NULL)
		return;
	for(i=0 ; i < len ; i++, str++) {
		if(*str == WF_TAB) {
			int fill = WF_TABSTOP-(lx%WF_TABSTOP);
			for(j=0 ; j < (fill == 0 ? WF_TABSTOP : fill) ; j++) {
				woutchar(WF_SPACE, &posx, &x, &y, pscr);
				lx++;
			}
		}
		else {
			woutchar(*str, &posx, &x, &y, pscr);
			lx++;
		}
	}
	if(nlflag == WF_SCROLL) {
		wscroll(&posx, &x, &y, pscr);
		lx = 0;
	}
}

/* chop up a string destined for the screen into line chunks, the last such chunk
 * may not be NL terminated. Called recursively to deal with imbedded NL (sorry -
 * I couldn't help it...this way was too cute to make iterative).
 */
static void
woutstr(cptr, pscr)
	char *cptr;
	struct scr *pscr; {
	char *nlptr;

	if(cptr == (char *)NULL)
		return;
	if((nlptr=strchr(cptr, WF_NL)) != (char *)NULL) {
		*nlptr = (char)NULL;
		woutline(cptr, WF_SCROLL, pscr);
		woutstr(nlptr+1, pscr);
	}
	else {
		woutline(cptr, WF_NOSCROLL, pscr);
	}
}

/* called in place of fprintf(std{out,err}) to direct output to a windows
 * text window. Scroll bars not implemented, proportional fonts are dealt
 * with, lines autowrap if they can't be fitted....Very basic package. Max
 * line length = WF_LINE (#defined). See the setup required in main(). NOTE
 * stdout and stderr are merged into the same window, should not be difficult
 * to parameterize calls to split the output (any takers?). Also, stdin,
 * stdout, stderr etc. streams are still legal and available. Windows
 * fopen() WILL assign these tokens to real files that may be the first
 * to be opened (first fopen() will be stdin, second stdout etc.). This
 * wreaks havock with my original idea to #define fprintf wfprintf and then
 * call the real fprintf internal to wfprintf for non screen streams.
 * Blasted MS do it again!!. I left the calling sequence intact though
 * just in case some bright spark figured out how to use the preprocessor
 * to redirect fprintf->wfprintf sensibly. For now though, all
 * fprintf(std*) calls need to be manually changed to wfprintf(std*)
 * in the application source.
 * WARNING...I've noticed that long doubles do not print right, perhaps
 * they are stacked differently (or perhaps I only tried them at the end
 * of the argument list). Anyway, be careful and check the screen results
 * as you test, whenever you are able.
 */
void
wfprintf(stream, fwin, a, b, c, d, e, f, h, i, j, k, l, m, n, o, p, q, r)
	FILE *stream;
	char *fwin;
	void *a, *b, *c, *d, *e, *f, *h, *i, *j, *k, *l, *m, *n, *o, *p, *q, *r; {
	char line[WF_LINE];
	HDC hdc;
	int wi;

	if(scr.screen == (char **)NULL) { /* first time through */
		scr.txt_wnd = htxt_wnd;
		GetWindowRect(scr.txt_wnd, &scr.winrect);
		hdc = ggetdc(scr.txt_wnd);
		GetTextMetrics(hdc, &(scr.textmetric));

		if(GetCharWidth(hdc, (WORD)WF_SPACE, (WORD)WF_TILDE, scr.lpbuf) == (BOOL)0) {
			ReleaseDC(scr.txt_wnd, hdc);
			MessageBox(scr.txt_wnd, "GetCharWidth() failed - using defaults" ,NULL,MB_OK | MB_ICONHAND);
		}
		else
			ReleaseDC(scr.txt_wnd, hdc);

		/* do this last cos it is the clue that wrefresh() uses
		 * to determine if there is a "screen" to refresh
		 */
		scr.screen = (char **)ccalloc((unsigned)WF_VERT, (unsigned)(sizeof(char *)));
		for(wi=0 ; wi < WF_VERT ; wi++)
			scr.screen[wi] = (char *)ccalloc((unsigned)WF_HORZ, (unsigned)(sizeof(char)));
	}

	if(stream == stdout || stream == stderr) { /* streams just serve as tokens */
		sprintf(line, fwin, a, b, c, d, e, f, h, i, j, k, l, m, n, o, p, q, r);
		woutstr(line, &scr);
	}
	else {
		MessageBox(scr.txt_wnd, "wfprintf to bad stream!!" ,NULL,MB_OK | MB_ICONHAND);
	}
}
MyEof
echo extracting wmain.c
cat > wmain.c << 'MyEof'
#include <windows.h>
#include <stdio.h>
#include "wfprintf.h"

HANDLE hInst;
HWND htxt_wnd;

char *mess[] = {
	"\nmumble\n",
	"\nstrike 2\n",
	"\nyer outa there\n",
	"\nforrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrre\n",
	"\nlook out I'm gonna be.......\n",
	"\n.....SIX - HUUUUUUEEEEEEYYYYYYYYY\n",
	"\ngross eh - no that's more than seven\n",
	"\nI've lost count - it must be something I eight\n",
	"\n9 U C V F N 10 E M N X 4 T\n",
	"\n10 jars of gelignite hanging on a wall...10 jars of gelignite hangin on a wall...and if one jar of gelignite should accidently fall\n",
	"\nthere'll be no jars of gelignite and no blinking wall\n",
	(char *)NULL
};

BOOL FAR PASCAL
About(hDlg, message, wParam, lParam)
	HWND hDlg;
	unsigned message;
	WORD wParam;
	LONG lParam; {

    switch(message) {
	  case WM_INITDIALOG:
		return(TRUE);

	  case WM_COMMAND:
		if(wParam == IDOK || wParam == IDCANCEL) {
			EndDialog(hDlg, TRUE);
			return (TRUE);
	    }
	    break;
    }
    return (FALSE);
}

long FAR PASCAL
t_handler(hWnd, message, wParam, lParam)
	HWND hWnd;		 
	unsigned message;
	WORD wParam;
	LONG lParam; {
	FARPROC lpProcAbout;
	int i;
	static int acount = 0;

	 switch(message){
	  case WM_ACTIVATE:
		wrefresh();
		break; 
	  case WM_COMMAND:
		switch (wParam) {
		  case IDM_EXIT:
			DestroyWindow(hWnd);
			break;
		  case IDM_ABOUT:
			wfprintf(stderr, "how \"ABOUT\" this - I've been called %d time%s%s\n", acount+1, (acount+1 == 1 ? "" : "s"), (acount+1 == 2 ? " (that's twice)" : ""));
			for(i=0 ; i < acount+1 ; i++)
				wfprintf(stderr, "abcdefghijklmnopqrstuvwxyz\t1234567890");
			wfprintf(stderr, mess[acount++]);
			if(mess[acount] == (char *)NULL)
				acount = 0;
			lpProcAbout = MakeProcInstance(About, hInst);
			DialogBox(hInst, "AboutBox", hWnd, lpProcAbout);
			FreeProcInstance(lpProcAbout);
			break;
		}
		break;
	  case WM_DESTROY:
	    PostQuitMessage(0);
	    break;
	  default:
		/* don't enable the following line or you'll get a message
		 * before the windows have been properly set up
		 */
#ifdef DONT_DO_THIS
		wfprintf(stderr, "message =  (%d)\n", message);
#endif /* DONT_DO_THIS */
	    return(DefWindowProc(hWnd, message, wParam, lParam));
    }
    return (NULL);
}

BOOL
InitTxtWnd(hInstance)
	HANDLE hInstance; {
	WNDCLASS  wc;

	wc.style = NULL;
	wc.lpfnWndProc = t_handler;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName =  "MwfprintfMenu";
	wc.lpszClassName = "MwfprintfWClass";

	return (RegisterClass(&wc));
}

BOOL
InitInstance(hInstance, nCmdShow)
	HANDLE	hInstance;
	int		nCmdShow; {
 
    hInst = hInstance;
    htxt_wnd = CreateWindow("MwfprintfWClass", "Wfprintf Demo Application",
		WS_OVERLAPPEDWINDOW, 0, 0, 622, 470, NULL, NULL, hInstance, NULL);  
    if(!htxt_wnd)
        return (FALSE);
    ShowWindow(htxt_wnd, nCmdShow);
    UpdateWindow(htxt_wnd);
}

int PASCAL
WinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow)
	HANDLE hInstance;
	HANDLE hPrevInstance;
	LPSTR lpCmdLine;
	int nCmdShow; {
    MSG msg;

    /* Perform initializations that apply to a specific instance */

	if(!InitTxtWnd(hInstance))
		return(FALSE);
    if(!InitInstance(hInstance, nCmdShow))
        return (FALSE);

	MessageBox(htxt_wnd, "starting wfprintf test - hit the File:About menu", NULL, MB_OK|MB_ICONHAND);
    /* Acquire and dispatch messages until a WM_QUIT message is received. */
	wfprintf(stderr, "Now you've done it, you activated the OK button!!\n\n");

    while (GetMessage(&msg, NULL, NULL, NULL)) {
		TranslateMessage(&msg);	   /* Translates virtual key codes	     */
		DispatchMessage(&msg);	   /* Dispatches message to window	     */
    }

    return (msg.wParam);	   /* Returns the value from PostQuitMessage */
}
MyEof
echo extracting wfprintf.h
cat > wfprintf.h << 'MyEof'
/* what a complicated little program!! */

#define WF_HORZ				200	/* max chars per window width */
#define WF_VERT				26	/* window height in font lines */
#define WF_INSERT			(VERT-1)	/* where to insert new lines of text */
#define WF_SPACE			((unsigned char)(' '))
#define WF_TILDE			((unsigned char)('~'))
#define WF_WINBORDER		7

#define WF_LINE				512
#define WF_ERASE_BK			1
#define WF_DEFAULTWIDTH		16
#define WF_NL				'\n'
#define WF_NOSCROLL			0
#define WF_SCROLL			1
#define WF_TAB				'\t'
#define WF_TABSTOP			8

/* monkey see, monkey do */
#ifdef TEST_PROG
# define IDM_DEXIT			99
# define IDM_EXIT			101
# define IDM_ABOUT			102
# define ID_ABOUT			100

# define IDM_UNDO			200
# define IDM_CUT			201
# define IDM_COPY			202
# define IDM_PASTE			203
# define IDM_CLEAR			204
#endif	/*	TEST_PROG	*/
MyEof
echo extracting makefile
cat > makefile << 'MyEof'
DOSMODEL=L

# when using wfprintf.obj without the test program wmain.obj
# uncomment the following line and comment the one after it
# DEF = 
DEF = -DTEST_PROG
CFLAGS = -qc $(DEF) -FPi -Ge -Gw -Oas -Zpe -A$(DOSMODEL)

SOURCES= wfprintf.c wmain.c
INCLUDES = wfprintf.h
XTRAS = makefile wfprintf.def wfprintf.rc readme
OBJECTS = wmain.obj wfprintf.obj

wfprintf.exe: wfprintf.def $(OBJECTS)
	link /NOD $(OBJECTS), wfprintf, wfprintf, libw llibcew, \
		wfprintf.def
	rc wfprintf.res

wfprintf.exe: wfprintf.res
	rc wfprintf.res

wfprintf.res: wfprintf.rc $(INCLUDES)
	rc -r $(DEF) wfprintf.rc

wfprintf.obj: wfprintf.c $(INCLUDES)

clean:
	del *.obj
	del wfprintf.res
	del wfprintf.exe
	del tags
	del *.map

tags:
	ctags *.[ch]

wfprintf.shar: $(SOURCES) $(INCLUDES) $(XTRAS)
	shar $(SOURCES) $(INCLUDES) $(XTRAS) > wfprintf.shar
MyEof
echo extracting wfprintf.def
cat > wfprintf.def << 'MyEof'

NAME	    wfprintf

DESCRIPTION 'test prog for wfprintf library'

EXETYPE     WINDOWS

STUB	    'WINSTUB.EXE'

CODE	    PRELOAD MOVEABLE
DATA	    PRELOAD MOVEABLE MULTIPLE

HEAPSIZE    22000
STACKSIZE   5120

EXPORTS
	t_handler	@1
	About		@2


MyEof
echo extracting wfprintf.rc
cat > wfprintf.rc << 'MyEof'
#include <windows.h>
#include "wfprintf.h"

Dummy MENU
BEGIN
    POPUP        "&File"
    BEGIN
	MENUITEM   "E&xit",		 IDM_DEXIT
    END
END

MwfprintfMenu MENU
BEGIN
    POPUP        "&File"
    BEGIN
	MENUITEM   "E&xit",		 IDM_EXIT
	MENUITEM   SEPARATOR
	MENUITEM   "&About Wfprintf...", IDM_ABOUT
    END

    POPUP        "&Edit"
    BEGIN
        MENUITEM    "&Undo\tAlt+BkSp",     IDM_UNDO   ,GRAYED
        MENUITEM    SEPARATOR
		MENUITEM    "Cu&t\tShift+Del",     IDM_CUT    ,GRAYED
		MENUITEM    "&Copy\tCtrl+Ins",     IDM_COPY   ,GRAYED
		MENUITEM    "&Paste\tShift+Ins",   IDM_PASTE  ,GRAYED
		MENUITEM    "&Delete\tDel",        IDM_CLEAR  ,GRAYED
	END
END

AboutBox DIALOG 22, 17, 144, 75
STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
CAPTION "About wfprintf"
BEGIN
    CTEXT "Delphi Management's"     -1,       0,  5, 144,  8
	CTEXT "wfprintf demo for windows"  -1,       0, 14, 144,  8
	CTEXT "hope this helps :-)"           -1,       0, 34, 144,  8
	DEFPUSHBUTTON "OK"          IDOK,      53, 59,  32, 14,      WS_GROUP
END


MyEof
echo extracting readme
cat > readme << 'MyEof'
Well, you asked for it (well, a lot of you did anyway), so here it is,
the biggest waste of time and effort since the invention of the RS232
protocol (but that's another story)...I mean why invent a standard and
then give Sun Microsystems an excuse not to fully implement flow
control on their SPARCstations....but that really is another story.

This is wfprintf(), it is packaged with a working example of its use,
(I wonder where I got that idea). Just unshar it on a friendly Unix
box, xmodem it to an empty directory on your PC, type nmk<CR> and
run the resulting wfprintf.exe under windows 3.0.

Wfprintf.obj is a fairly well isolated library, there are 2 hooks
to the outside world;

 1) wrefresh() which should be called from your callbacks under
    WM_ACTIVATE (it should probably be called when a child window
    is dragged as well (try dragging the about box and see what a
    large eraser looks like - gee I've emulated one of the functions
    of PCpaint :-))),

 2) the extern HWND htxt_wnd should refer to your text window handle
    returned by CreateWindow (see wmain.c:InitInstance()).

See the comments that head up wfprintf.c for some hints about how
this could have been done better.

The software is provided "as-is", there is little chance that I'll
have time to enhance it but, if you could send me diffs, I'd like to
try and keep a current "up-to-date" version which I'll repost as
necessary. Use it commercially or not, I only ask that you keep
the copyright message in the source to wfprintf intact...it will grow
as people contribute (as will the version number).

Good luck and happy wfprintfing

--alen The Lisa Slayer - (learning how to turn a SPARC into a Flame)
 alen%shappy.uucp@crash.cts.com (a mac+ uucp host - what a concept!!)
 alen@crash.cts.com

MyEof