/***************************************************************************** * * PRINT.C * * Copyright (C) Microsoft Corporation 1990. * All Rights reserved. * ****************************************************************************** * * Module Intent * * Code to print help topics. * *****************************************************************************/ #include "help.h" #pragma hdrstop #include #include "inc\printset.h" #define MAX_PRINTING 100 #define fPrintInsert 0 #define fPrintRegular 1 typedef BOOL (APIENTRY* DLGPRINTPROC)(LPPRINTDLG); typedef DWORD (APIENTRY* COMMDLGEXTENDEDERROR)(VOID); BOOL (APIENTRY* pPrintDlg)(LPPRINTDLG); DWORD (APIENTRY* pCommDlgExtendedError)(void); // Prototypes INT EXPORT AbortPrint(HDC, INT); BOOL EXPORT AbortPrintDlg(HWND, UINT, WPARAM, LPARAM); static int STDCALL DyPrintLayoutHde(HDE, BOOL, RECT, int, int *); void STDCALL ReportComDlgError(DWORD Error); // Global Variables: BOOL fQuitHelp; // Set to TRUE if we should quit help static HDC hdcPrint; static BOOL fStartPage; #ifndef NO_PRAGMAS #pragma data_seg(".text", "CODE") #endif const char txtPrintDlg[] = "PrintDlgA"; const char txtCommDlgError[] = "CommDlgExtendedError"; #ifndef NO_PRAGMAS #pragma data_seg() #endif PRINTDLG* ppd; extern int cxAspect, cyAspect, cBitCount, cPlanes; // used in bitmap.c HDC STDCALL HdcGetPrinter(void) { #ifdef _DEBUG LPSTR qchPrinter, qchDriver, qchPort; #endif HDC hdc; PDEVMODE lpDevMode; LPDEVNAMES lpDevNames; if (!InitPrintDialogStruct(ahwnd[iCurWindow].hwndParent)) return NULL; // Let the user know where we are printing to, and give them the option // of changing things like DPI if the driver supports it. ASSERT(pPrintDlg); #ifdef _DEBUG ppd->Flags = PD_NOPAGENUMS | PD_NOSELECTION | PD_USEDEVMODECOPIES; #else ppd->Flags = PD_NOPAGENUMS | PD_NOSELECTION | PD_DISABLEPRINTTOFILE | PD_USEDEVMODECOPIES; #endif for (;;) { DWORD result = pPrintDlg(ppd); if (result == 0 && !PrintDlgFailed()) // did the user cancel? return NULL; else break; } /* * Commdlg is around and we did get DevNames info. Use it in creating * our print DC. */ if (ppd->hDevNames) { if (ppd->hDevMode) lpDevMode = (DEVMODE*) GlobalLock(ppd->hDevMode); lpDevNames = (LPDEVNAMES) GlobalLock(ppd->hDevNames); } else { Error(wERRS_NOPRINTSETUP, wERRA_RETURN); return NULL; } #ifdef _DEBUG qchDriver = (LPSTR) lpDevNames + lpDevNames->wDriverOffset; qchPrinter = (LPSTR) lpDevNames + lpDevNames->wDeviceOffset; qchPort = (LPSTR) lpDevNames + lpDevNames->wOutputOffset; #endif hdc = CreateDC((LPSTR) lpDevNames + lpDevNames->wDriverOffset, (LPSTR) lpDevNames + lpDevNames->wDeviceOffset, (LPSTR) lpDevNames + lpDevNames->wOutputOffset, lpDevMode); GlobalUnlock(ppd->hDevNames); GlobalUnlock(ppd->hDevMode); if (!hdc) Error(wERRS_OOM, wERRA_RETURN); return hdc; } /* * AbortPrintDlg() printing dialog proc (has a cancle button on it) * * Procedure: * * globals used: * fAbortPrint -- sets this when the user hits cancel. * hdlgPrint -- set to NULL when dialog has been ended. */ BOOL EXPORT AbortPrintDlg(HWND hwndDlg, UINT wMsg, WPARAM wParam, LPARAM lParam) { switch (wMsg) { case WM_COMMAND: EnableWindows(); DestroyWindow(hwndDlg); break; case WM_DESTROY: hdlgPrint = NULL; fAbortPrint = TRUE; break; case WM_INITDIALOG: ChangeDlgFont(hwndDlg); break; default: return FALSE; } return TRUE; } /* * AbortProc() printing abort procedure * * globals used: * fAbortPrint -- indicates the user hit CANCEL on the print dialog * hdlgPrint -- handle of the print dialog * */ int CALLBACK AbortPrint(HDC hdc, int wCode) { MSG msg; while (!fAbortPrint && PeekMessage(&msg, (HWND) 0, 0, 0, PM_REMOVE)) { if (!hdlgPrint || !IsDialogMessage(hdlgPrint, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return !fAbortPrint; } /*************** ** ** void STDCALL PrintHde( hde ) ** ** purpose ** Main entry point into printing. This will cause the current ** topic, passed in the hde, to be printed. ** ** arguments ** HDE hde -- handle to the current topic to be printed. ** ** return value ** none ** ** notes ** This function is not re-entrant. ** ***************/ BOOL STDCALL PrintHde(HDE hdeTopic) { HDE hde; QDE qde; char rgchDialogText[MAX_PRINTING]; RECT rct; PT pt; int spErr; // Spooler error int dyTopOfPage; TLP tlp; DOCINFO di; if (fQuitHelp) return FALSE; WaitCursor(); if (hwndNote) SetWindowPos(hwndNote, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); /*********************************************************************** * * CAUTION: While the hourglass is up, we must not call any * code that can yield control, or wCursor may become invalid. * This includes Error() and PeekMessage(). * ************************************************************************/ if (fMultiPrinting) { if ((hde = HdeCreate(NULL, hdeTopic, dePrint)) == NULL) { spErr = 1; // The appropriate error message has already goto PrintError; // been displayed. } } else { // Set things up for an error condition: ASSERT(hdlgPrint == NULL); hde = NULL; if ((hdcPrint = HdcGetPrinter()) == NULL || (hde = HdeCreate(NULL, hdeTopic, dePrint)) == NULL) { spErr = 1; // The appropriate error message has already goto PrintError; // been displayed. } } qde = QdeFromGh(hde); qde->hdc = hdcPrint; cxAspect = qde->wXAspectMul = GetDeviceCaps(hdcPrint, LOGPIXELSX); cyAspect = qde->wYAspectMul = GetDeviceCaps(hdcPrint, LOGPIXELSY); cBitCount = GetDeviceCaps(hdcPrint, BITSPIXEL); cPlanes = GetDeviceCaps(hdcPrint, PLANES); // Review: We copy the hwnd from hdeTopic because our current hde does // not have one. Way down in the layout code, if the topic has an // annotation, the hwnd will be used, (PtAnnoLim()) and therefore must // be present. Is there a better way for PtAnnoLim to do it's job? Is this // the right HWND to use? if not, what? qde->hwnd = QdeFromGh(hdeTopic)->hwnd; /* * Review -- If hTitle is nil, then it could be because key elements * in the DE have not yet been initized. Therefore, we will make sure * these elements get initialized here. This may be considered a hack. */ if (qde->top.hTitle == NULL) { rct.top = rct.left = 0; rct.right = rct.bottom = 100; SetSizeHdeQrct(hde, &rct, TRUE); } if (!fMultiPrinting) { // Set up dialog for abort function hdlgPrint = CreateDialog(hInsNow, MAKEINTRESOURCE(ABORTPRINTDLG), ahwnd[iCurWindow].hwndParent, AbortPrintDlg); if (hdlgPrint == NULL) { spErr = SP_OUTOFMEMORY; goto PrintError; } fAbortPrint = FALSE; } DisableWindows(); // Set text fields in dialog box. { char szTitle[MAX_TOPIC_TITLE]; /* * The "default" dialog box text is really the template for the * final text. */ GetCurrentTitleQde(qde, szTitle, sizeof(szTitle)); ASSERT(!IsEmptyString(szTitle)); wsprintf(rgchDialogText, GetStringResource(sidPrintText), szTitle); ASSERT(lstrlen(rgchDialogText) < sizeof(rgchDialogText)); SetDlgItemText(hdlgPrint, ABORTPRINTTEXT, rgchDialogText); } ShowWindow(hdlgPrint, SW_SHOW); UpdateWindow(hdlgPrint); /* * Set up printing rectangle, using one inch margins (assuming we're in * MM_TEXT mode, where 1 logical unit = 1 pixel.) */ Escape(qde->hdc, GETPHYSPAGESIZE, 0, NULL, (LPSTR) &pt); ASSERT(GetMapMode(qde->hdc) == MM_TEXT); rct.left = GetDeviceCaps(qde->hdc, LOGPIXELSX); rct.right = pt.x - rct.left; ASSERT(rct.right > rct.left); rct.top = dyTopOfPage = GetDeviceCaps(qde->hdc, LOGPIXELSY); rct.bottom = pt.y - rct.top; ASSERT(rct.bottom > rct.top); qde->rct = rct; RemoveWaitCursor(); /* * Print document. For the STARTDOC call, we need to create a unique * string. */ SetAbortProc(qde->hdc, AbortPrint); ZeroMemory(&di, sizeof(DOCINFO)); di.cbSize = sizeof(DOCINFO); di.lpszDocName = rgchDialogText; spErr = StartDoc(qde->hdc, &di); fStartPage = FALSE; // Non-scrolling region support if (FTopicHasNSR(hde) && spErr > 0 && !fAbortPrint) { HSGC hsgc; /* * HACK ALERT! Currently we have no way, given a DE, to tell which * layout to use, other than the DE type or the value of * qde->top.vaCurr. Because we do a jump here, we rely on the latter. */ GetTLPNSRStartHde(hde, &tlp); JumpTLP(hde, tlp); rct.top += DyPrintLayoutHde(hde, fPrintInsert, rct, dyTopOfPage, &spErr); #if 0 /* Draw a line across bottom of NSR */ hsgc = (HSGC) HsgcFromQde(qde); FSetPen(hsgc, 1, coBLACK, coBLACK, wOPAQUE, roCOPY, wPenSolid); GotoXY(hsgc, rct.left, rct.top); DrawTo(hsgc, rct.right, rct.top); FreeHsgc(hsgc); #else Unreferenced(hsgc); #endif /* 0 */ ++rct.top; SetSizeHdeQrct(hde, &rct, FALSE); } if (spErr > 0 && !fAbortPrint) { if (FTopicHasSR(hde)) { GetTLPTopicStartHde(hde, &tlp); JumpTLP(hde, tlp); // NOTE: DyPrintLayoutHde may modify the DE rect. DyPrintLayoutHde(hde, fPrintRegular, rct, dyTopOfPage, &spErr); } else { /* * Special Case: There is no scrolling region, so force out * whatever we inserted for the non-scrolling region above. */ ASSERT(FTopicHasNSR(hde)); spErr = EndPage(qde->hdc); } } if (spErr > 0 && !fAbortPrint) EndDoc(qde->hdc); PrintError: if (!fMultiPrinting) { if (hdcPrint != NULL) { DeleteDC(hdcPrint); hdcPrint = NULL; } if (hde != NULL) DestroyHde(hde); if (hdlgPrint != NULL) SendMessage(hdlgPrint, WM_COMMAND, IDCANCEL, 0L); if (hwndNote) SetWindowPos(hwndNote, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); } ASSERT(hdeTopic); if (hdeTopic) { cxAspect = GetDeviceCaps(QdeFromGh(hdeTopic)->hdc, LOGPIXELSX); cyAspect = GetDeviceCaps(QdeFromGh(hdeTopic)->hdc, LOGPIXELSY); cBitCount = GetDeviceCaps(QdeFromGh(hdeTopic)->hdc, BITSPIXEL); cPlanes = GetDeviceCaps(QdeFromGh(hdeTopic)->hdc, PLANES); } // This was possibly set, if print was called by macro ClearMacroFlag(); if (fQuitHelp) DestroyHelp(); else { if (fMultiPrinting && (spErr < 0 || fAbortPrint)) { fMultiPrinting = FALSE; EnableWindows(); if (spErr != SP_ERROR && spErr != SP_OUTOFMEMORY) return FALSE; } switch (spErr) { case SP_ERROR: if (!fAbortPrint) Error(wERRS_PRINT_ERROR, wERRA_RETURN); return FALSE; case SP_OUTOFMEMORY: Error(wERRS_OOM, wERRA_RETURN); return FALSE; default: if (fMultiPrinting) EnableWindows(); return TRUE; } } } static int STDCALL DyPrintLayoutHde(HDE hde, BOOL fMode, RECT rct, int dyTopOfPage, int* qspErr) { QDE qde; POINT pt; POINT dpt; int spErr; int dyExtra = 0; BOOL fHaveFullPage; ASSERT(hde); qde = QdeFromGh(hde); ASSERT(qspErr); spErr = *qspErr; while (spErr > 0 && !fAbortPrint) { if (!fStartPage) { spErr = StartPage(qde->hdc); if (spErr <= 0) break; fStartPage = TRUE; } if (fMode == fPrintInsert) { pt.x = 0; /* * NOTE: For NSRs longer than one page, this may cause the * last frame on a page to be duplicated on the next page. We * don't expect or care about NSRs longer than one page. */ pt.y = min(qde->rct.bottom - qde->rct.top, DyGetLayoutHeightHde(hde)); } else { pt.x = 0; pt.y = DyCleanLayoutHeight(qde); // Adjust pt.y if it is too small or negative if (pt.y < (qde->rct.bottom - qde->rct.top) / 2) pt.y = qde->rct.bottom - qde->rct.top; } dyExtra = pt.y; // Draw appropriate rectangle rct.bottom = rct.top + pt.y - 1; /* * fHaveFullPage handles the case where our insertion has * completely filled one page. In this case, we need to force a new * page. */ fHaveFullPage = (rct.bottom + 1 == qde->rct.bottom); /* NOTE: * Previously, we have been clipping our output to rct. * Unfortunately, clipping rectangles are scaled by the printer's * scaling factor (even though nothing else is), and trying to get * that scaling factor was causing problems with the rest of the * print job. Since DyCleanLayoutHeight should take care of most * clipping problems, I have decided to blow off clipping. */ RefreshHde(hde, &rct); if (fMode == fPrintRegular || fHaveFullPage) { spErr = EndPage(qde->hdc); fStartPage = FALSE; if (spErr <= 0) break; } /* * If our initial rectangle began smaller than a page height, * (because of a previous insertion), then reset to full page height * to prepare for next page. */ if (rct.top != dyTopOfPage) { rct.top = dyTopOfPage; SetSizeHdeQrct( hde, &rct, TRUE); } // Change pt.y to a negative value to scroll down. pt.y = -pt.y; dpt = DptScrollLayout(qde, pt); if (dpt.y != pt.y) break; // Reached end of topic } *qspErr = spErr; return dyExtra; } BOOL STDCALL InitMPrint(void) { if (hwndNote || fHelp == POPUP_HELP) return FALSE; // no multiple printing from a popup if ((hdcPrint = HdcGetPrinter()) == NULL) return FALSE; // Set up dialog for abort function fAbortPrint = FALSE; fMultiPrinting = TRUE; hdlgPrint = CreateDialog(hInsNow, MAKEINTRESOURCE(ABORTPRINTDLG), ahwnd[MAIN_HWND].hwndParent, AbortPrintDlg); // REVIEW: does this do any good? SetWindowPos(hdlgPrint, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); if (!hdlgPrint) { EndMPrint(); return FALSE; } return TRUE; } void STDCALL EndMPrint(void) { if (hdlgPrint) SendMessage(hdlgPrint, WM_COMMAND, IDCANCEL, 0L); if (hdcPrint) { DeleteDC(hdcPrint); hdcPrint = NULL; } fMultiPrinting = FALSE; } BOOL STDCALL MPrintHash(PCSTR pszFile, HASH hash) { if (!fMultiPrinting || fAbortPrint || fQuitHelp) return FALSE; FJumpHash(pszFile, hash); FlushMessageQueue(0); return PrintHde(HdeGetEnv()); } BOOL STDCALL MPrintId(PCSTR pszFile, PCSTR pszCtx) { if (!fMultiPrinting || fAbortPrint || fQuitHelp) return FALSE; if (IsEmptyString(pszCtx)) // empty means pszFile is the topic id FJumpId(txtZeroLength, pszFile); else FJumpId(pszFile, pszCtx); FlushMessageQueue(0); return PrintHde(HdeGetEnv()); } #ifdef _DEBUG /*************************************************************************** FUNCTION: ReportComDlgError PURPOSE: For debug version, specify what the error constant is. For non-debug version, report an internal error. RETURNS: COMMENTS: MODIFICATION DATES: 16-Sep-1991 [ralphw] ***************************************************************************/ #include static PSTR apszCDErr[] = { "CDERR_GENERALCODES", "CDERR_STRUCTSIZE", "CDERR_INITIALIZATION", "CDERR_NOTEMPLATE", "CDERR_NOHINSTANCE", "CDERR_LOADSTRFAILURE", "CDERR_FINDRESFAILURE", "CDERR_LOADRESFAILURE", "CDERR_LOCKRESFAILURE", "CDERR_MEMALLOCFAILURE", "CDERR_MEMLOCKFAILURE", "CDERR_NOHOOK" }; static PSTR apszFNErr[] = { "FNERR_FILENAMECODES", "FNERR_SUBCLASSFAILURE", "FNERR_INVALIDFILENAME", "FNERR_BUFFERTOOSMALL" }; /*************************************************************************** FUNCTION: ReportComDlgError PURPOSE: Display the constant name of the common dialog box error RETURNS: COMMENTS: MODIFICATION DATES: 16-Sep-1991 [ralphw] ***************************************************************************/ void STDCALL ReportComDlgError(DWORD err) { char szMsg[100]; strcpy(szMsg, "Common Dialog Box error: "); if (err >= CDERR_GENERALCODES && err <= PDERR_PRINTERCODES) strcat(szMsg, apszCDErr[err]); else if (err >= FNERR_FILENAMECODES && err <= FRERR_FINDREPLACECODES) strcat(szMsg, apszFNErr[err - FNERR_FILENAMECODES]); else wsprintf(szMsg + strlen(szMsg), "%lu", err); AuthorMsg(szMsg, NULL); return; } #endif BOOL STDCALL InitPrintDialogStruct(HWND hwnd) { HLIBMOD hmodule; if (!ppd) { ppd = (PRINTDLG*) lcCalloc(sizeof(PRINTDLG)); if (!ppd) { Error(wERRS_OOM, wERRA_RETURN); return FALSE; } ppd->lStructSize = sizeof(PRINTDLG); ppd->hInstance = hInsNow; } ppd->hwndOwner = hwnd; /* * Check if we have picked up a DEVNAMES structure previously from * commdlg during print setup (or here). */ if (!ppd->hDevMode) { if (!pPrintDlg) { if ((hmodule = HFindDLL(txtCommDlg, FALSE))) { pPrintDlg = (DLGPRINTPROC) GetProcAddress(hmodule, txtPrintDlg); pCommDlgExtendedError = (COMMDLGEXTENDEDERROR) GetProcAddress(hmodule, txtCommDlgError); } else { Error(wERRS_MISSINGCOMMDLG, wERRA_RETURN); return NULL; } if (!pPrintDlg || !pCommDlgExtendedError) { Error(wERRS_CORRUPTCOMMDLG, wERRA_RETURN); return NULL; } } ppd->Flags = PD_RETURNDEFAULT | PD_PRINTSETUP; pPrintDlg(ppd); if (!ppd->hDevNames) { Error(wERRS_NOPRINTSETUP, wERRA_RETURN); return FALSE; } } return TRUE; } /*************************************************************************** FUNCTION: PrintDlgFailed PURPOSE: Determins cause of PrintDlg returning 0 PARAMETERS: void RETURNS: TRUE to call the dialog again COMMENTS: MODIFICATION DATES: 29-Sep-1994 [ralphw] ***************************************************************************/ BOOL STDCALL PrintDlgFailed(void) { DWORD errno = pCommDlgExtendedError(); if (errno) { /* * There are quite a number of extended errors from commdlg * and it's not clear what all will come back from Printer Setup. * Many of them have to do with commdlg encountering resource * limits. We punt on these and report a Print Setup error msg. * These two have to do with someone changing the printer * settings on us while we're running. The error handling here * was drawn from the win 3.1 notepad.exe sources. */ if (errno == PDERR_PRINTERNOTFOUND || errno == PDERR_DNDMMISMATCH) { if (ppd->hDevMode) { GlobalFree(ppd->hDevMode); ppd->hDevMode = NULL; } if (ppd->hDevNames) { GlobalFree(ppd->hDevNames); ppd->hDevNames = NULL; } return TRUE; } Error(wERRS_NOPRINTSETUP, wERRA_RETURN); } return FALSE; }