Now we're ready to add a printing facility to the POPPAD series of programs and declare POPPAD finished. You'll need the various POPPAD files from Chapter 10, plus the three new files in Figure 15-9.
POPPAD.MAK
#----------------------
# POPPAD.MAK make file
#----------------------
poppad.exe : poppad.obj poppadf.obj poppadp.obj \
filedlg.obj poppad.def poppad.res
link poppad poppadf poppadp filedlg, poppad.exe /align:16, \
NUL, /nod slibcew libw, poppad
rc poppad.res poppad.exe
poppad.obj : poppad.c poppad.h
cl -c -Gsw -Ow -W2 -Zp poppad.c
poppadf.obj : poppadf.c
cl -c -Gsw -Ow -W2 -Zp poppadf.c
poppadp.obj : poppadp.c
cl -c -Gsw -Ow -W2 -Zp poppadp.c
filedlg.obj : filedlg.c filedlg.h
cl -c -Gsw -Ow -W2 -Zp filedlg.c
poppad.res : poppad.rc poppad.h poppad.ico filedlg.dlg filedlg.h
rc -r poppad.rc
POPPADP.C
/*----------------------------------------------
POPPADP.C -- Popup Editor Printing Functions
(c) Charles Petzold, 1990
----------------------------------------------*/
#include <windows.h>
#include <string.h>
#include "filedlg.h" // for IDD_FNAME definition
extern char szAppName [] ; // in POPPAD.C
BOOL bUserAbort ;
HWND hDlgPrint ;
BOOL FAR PASCAL PrintDlgProc (HWND hDlg, WORD message, WORD wParam, LONG lParam)
{
switch (message)
{
case WM_INITDIALOG :
EnableMenuItem (GetSystemMenu (hDlg, FALSE), SC_CLOSE,
MF_GRAYED) ;
return TRUE ;
case WM_COMMAND :
bUserAbort = TRUE ;
EnableWindow (GetParent (hDlg), TRUE) ;
DestroyWindow (hDlg) ;
hDlgPrint = 0 ;
return TRUE ;
}
return FALSE ;
}
BOOL FAR PASCAL AbortProc (HDC hPrinterDC, short nCode)
{
MSG msg ;
while (!bUserAbort && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
{
if (!hDlgPrint || !IsDialogMessage (hDlgPrint, &msg))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
}
return !bUserAbort ;
}
HDC GetPrinterDC (void)
{
static char szPrinter [80] ;
char *szDevice, *szDriver, *szOutput ;
GetProfileString ("windows", "device", ",,,", szPrinter, 80) ;
if ((szDevice = strtok (szPrinter, "," )) &&
(szDriver = strtok (NULL, ", ")) &&
(szOutput = strtok (NULL, ", ")))
return CreateDC (szDriver, szDevice, szOutput, NULL) ;
return 0 ;
}
BOOL PrintFile (HANDLE hInst, HWND hwnd, HWND hwndEdit, char *szFileName)
{
BOOL bError = FALSE ;
char szMsg [40] ;
FARPROC lpfnAbortProc, lpfnPrintDlgProc ;
HDC hdcPrn ;
NPSTR psBuffer ;
RECT rect ;
short yChar, nCharsPerLine, nLinesPerPage,
nTotalLines, nTotalPages, nPage, nLine, nLineNum = 0 ;
TEXTMETRIC tm ;
if (0 == (nTotalLines = (short) SendMessage (hwndEdit,
EM_GETLINECOUNT, 0, 0L)))
return FALSE ;
if (NULL == (hdcPrn = GetPrinterDC ()))
return TRUE ;
GetTextMetrics (hdcPrn, &tm) ;
yChar = tm.tmHeight + tm.tmExternalLeading ;
nCharsPerLine = GetDeviceCaps (hdcPrn, HORZRES) / tm.tmAveCharWidth ;
nLinesPerPage = GetDeviceCaps (hdcPrn, VERTRES) / yChar ;
nTotalPages = (nTotalLines + nLinesPerPage - 1) / nLinesPerPage ;
psBuffer = (NPSTR) LocalAlloc (LPTR, nCharsPerLine) ;
EnableWindow (hwnd, FALSE) ;
bUserAbort = FALSE ;
lpfnPrintDlgProc = MakeProcInstance (PrintDlgProc, hInst) ;
hDlgPrint = CreateDialog (hInst, "PrintDlgBox", hwnd, lpfnPrintDlgProc) ;
SetDlgItemText (hDlgPrint, IDD_FNAME, szFileName) ;
lpfnAbortProc = MakeProcInstance (AbortProc, hInst) ;
Escape (hdcPrn, SETABORTPROC, 0, (LPSTR) lpfnAbortProc, NULL) ;
strcat (strcat (strcpy (szMsg, szAppName), " - "), szFileName) ;
if (Escape (hdcPrn, STARTDOC, strlen (szMsg), szMsg, NULL) > 0)
{
for (nPage = 0 ; nPage < nTotalPages ; nPage++)
{
for (nLine = 0 ; nLine < nLinesPerPage &&
nLineNum < nTotalLines ; nLine++, nLineNum++)
{
*(short *) psBuffer = nCharsPerLine ;
TextOut (hdcPrn, 0, yChar * nLine, psBuffer,
(short) SendMessage (hwndEdit, EM_GETLINE,
nLineNum, (LONG) (LPSTR) psBuffer)) ;
}
if (Escape (hdcPrn, NEWFRAME, 0, NULL, (LPSTR) &rect) < 0)
{
bError = TRUE ;
break ;
}
if (bUserAbort)
break ;
}
}
else
bError = TRUE ;
if (!bError)
Escape (hdcPrn, ENDDOC, 0, NULL, NULL) ;
if (!bUserAbort)
{
EnableWindow (hwnd, TRUE) ;
DestroyWindow (hDlgPrint) ;
}
if (bError || bUserAbort)
{
strcat (strcpy (szMsg, "Could not print: "), szFileName) ;
MessageBox (hwnd, szMsg, szAppName, MB_OK | MB_ICONEXCLAMATION) ;
}
LocalFree ((LOCALHANDLE) psBuffer) ;
FreeProcInstance (lpfnPrintDlgProc) ;
FreeProcInstance (lpfnAbortProc) ;
DeleteDC (hdcPrn) ;
return bError || bUserAbort ;
}
POPPAD.DEF
;-----------------------------------
; POPPAD.DEF module definition file
;-----------------------------------
NAME POPPAD
DESCRIPTION 'Popup Editor (c) Charles Petzold, 1990'
EXETYPE WINDOWS
STUB 'WINSTUB.EXE'
CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD MOVEABLE MULTIPLE
HEAPSIZE 1024
STACKSIZE 8192
EXPORTS WndProc
AboutDlgProc
FileOpenDlgProc
FileSaveDlgProc
PrintDlgProc
AbortProc
POPPADP.C is structurally similar to PRINT3.C except that it is able to print multiple pages. The PrintFile routine performs some calculations to determine the number of characters it can fit on a line and the number of lines it can fit on a page. This process involves calls to GetDeviceCaps to determine the resolution of the page and to GetTextMetrics for the dimensions of a character.
The program obtains the total number of lines in the document (the variable nTotalLines) by sending an EM_GETLINECOUNT message to the edit control. A buffer for holding the contents of each line is allocated from local memory. For each line, the first word of this buffer is set to the number of characters in the line. Sending the edit control an EM_GETLINE message copies a line into the buffer; the line is then sent to the printer device context using TextOut.
The program breaks from the for loop incrementing the page number if the NEWFRAME Escape call returns an error or if bUserAbort is TRUE. Although the NEWFRAME call will return before GDI finishes the call if the return value of the abort procedure is FALSE, it doesn't return an error. For this reason, bUserAbort is tested explicitly before the next page is started. If no error is reported, the ENDDOC Escape call is made:
if (!bError)
Escape (hdcPrn, ENDDOC, 0, NULL, NULL) ;
You might want to experiment with POPPAD by printing a multipage file. The file being printed first shows up in the Print Manager's client area after GDI has finished processing the first NEWFRAME Escape call. At that time, the Print Manager starts sending the file to the printer. If you then cancel the print job from POPPAD, the Print Manager aborts the printing also—that's a result of returning FALSE from the abort procedure. Once the file appears in the Print Manager's client area, you can also cancel the printing by selecting Terminate from the Queue menu. In that case, the NEWFRAME Escape call in progress in POPPAD returns an SP_USERABORT error (equal to -3).
Programmers new to Windows often become inordinately obsessed with the ABORTDOC Escape function. This function is rarely used in printing that also uses the NEWFRAME Escape function. As you can see in POPPAD, a user can cancel a print job at almost any time, either through POPPAD's printing dialog box or through the Print Manager. Neither requires that the program use the ABORTDOC Escape function. The only time that ABORTDOC would be allowed in POPPAD is between the STARTDOC Escape call and the first NEWFRAME Escape call, but that code goes so quickly that ABORTDOC isn't necessary.
Figure 15-10 shows the correct sequence of Escape calls for printing a multipage document. The best place to check for a bUserAbort value of TRUE is after each NEWFRAME Escape call. The ENDDOC Escape function is used only when the previous Escape calls have proceeded without error. In fact, once you get an error from any Escape call, the show is over, and you can go home.