PRINT.CPP
/* 
 * PRINT.CPP 
 * Patron Chapter 22 
 * 
 * Implementation of printing functions for both CPatronDoc 
 * and CPages classes.  These functions are here to keep clutter 
 * down in document.cpp and pages.cpp. 
 * 
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved 
 * 
 * Kraig Brockschmidt, Microsoft 
 * Internet  :  kraigb@microsoft.com 
 * Compuserve:  >INTERNET:kraigb@microsoft.com 
 */ 
 
#include "patron.h" 
 
static HWND g_hDlgPrint=NULL; 
static BOOL g_fCancelPrint=FALSE; 
 
 
/* 
 * CPatronDoc::Print 
 * 
 * Purpose: 
 *  Prints the current document. 
 * 
 * Parameters: 
 *  hWndFrame       HWND of the frame to use for dialog parents. 
 * 
 * Return Value: 
 *  BOOL            TRUE if printing happened, FALSE if it didn't 
 *                  start or didn't complete. 
 */ 
 
BOOL CPatronDoc::Print(HWND hWndFrame) 
    { 
    PRINTDLG        pd; 
    BOOL            fSuccess; 
 
    memset(&pd, 0, sizeof(PRINTDLG)); 
    pd.lStructSize=sizeof(PRINTDLG); 
    pd.hwndOwner  =hWndFrame; 
    pd.nCopies    =1; 
    pd.nFromPage  =(USHORT)-1; 
    pd.nToPage    =(USHORT)-1; 
    pd.nMinPage   =1; 
    pd.nMaxPage   =m_pPG->NumPagesGet(); 
 
    pd.lpfnPrintHook=PrintDlgHook; 
 
    //Get the current document printer settings 
    pd.hDevMode=m_pPG->DevModeGet(); 
 
    pd.Flags=PD_RETURNDC | PD_ALLPAGES | PD_COLLATE 
        | PD_HIDEPRINTTOFILE | PD_NOSELECTION | PD_ENABLEPRINTHOOK; 
 
    if (!PrintDlg(&pd)) 
        return FALSE; 
 
    if (NULL!=pd.hDevMode) 
        GlobalFree(pd.hDevMode); 
 
    if (NULL!=pd.hDevNames) 
        GlobalFree(pd.hDevNames); 
 
    //Go do the actual printing. 
    fSuccess=m_pPG->Print(pd.hDC, PSZ(IDS_DOCUMENTNAME), pd.Flags 
        , pd.nFromPage, pd.nToPage, pd.nCopies); 
 
    if (!fSuccess) 
        { 
        MessageBox(m_hWnd, PSZ(IDS_PRINTERROR) 
            , PSZ(IDS_DOCUMENTCAPTION), MB_OK); 
        } 
 
    return fSuccess; 
    } 
 
 
 
 
 
 
/* 
 * CPatronDoc::PrinterSetup 
 * 
 * Purpose: 
 *  Selects a new printer and options for this document. 
 * 
 * Parameters: 
 *  hWndFrame       HWND of the frame to use for dialog parents. 
 *  fDefault        BOOL to avoid any dialog and just use the 
 *                  default. 
 * 
 * Return Value: 
 *  UINT            Undefined 
 */ 
 
UINT CPatronDoc::PrinterSetup(HWND hWndFrame, BOOL fDefault) 
    { 
    PRINTDLG        pd; 
 
    //Attempt to get printer metrics for the default printer. 
    memset(&pd, 0, sizeof(PRINTDLG)); 
    pd.lStructSize=sizeof(PRINTDLG); 
 
    if (fDefault) 
        pd.Flags=PD_RETURNDEFAULT; 
    else 
        { 
        pd.hwndOwner=hWndFrame; 
        pd.Flags=PD_PRINTSETUP; 
 
        //Get the current document printer settings 
        pd.hDevMode=m_pPG->DevModeGet(); 
        } 
 
    if (!PrintDlg(&pd)) 
        return FALSE; 
 
    if (!m_pPG->DevModeSet(pd.hDevMode, pd.hDevNames)) 
        { 
        GlobalFree(pd.hDevNames); 
        GlobalFree(pd.hDevMode); 
        return FALSE; 
        } 
 
    FDirtySet(TRUE); 
    return 1; 
    } 
 
 
 
/* 
 * PrintDlgHook 
 * 
 * Purpose: 
 *  Callback hook for the Print Dialog so we can hide the Setup 
 *  button.  Patron only allows Setup before anything exists on 
 *  the page, and is not written to handle setup at Print time. 
 */ 
 
UINT CALLBACK PrintDlgHook(HWND hDlg, UINT iMsg, WPARAM wParam 
    , LPARAM lParam) 
    { 
    if (WM_INITDIALOG==iMsg) 
        { 
        HWND        hWnd; 
 
        hWnd=GetDlgItem(hDlg, psh1); 
        ShowWindow(hWnd, SW_HIDE); 
        return TRUE; 
        } 
 
    return FALSE; 
    } 
 
 
 
 
/* 
 * CPatronDoc::FQueryPrinterSetup 
 * 
 * Purpose: 
 *  Returns whether or not the Printer Setup menu item can be 
 *  enabled.  Once you create a tenant in any page, Printer Setup 
 *  is voided simply to keep this sample simple, that is, we don't 
 *  have to worry about reorganizing potentially large amounts 
 *  of layout after we start plopping down objects. 
 * 
 * Parameters: 
 *  None 
 * 
 * Return Value: 
 *  BOOL            TRUE to enable the menu, FALSE otherwise. 
 */ 
 
BOOL CPatronDoc::FQueryPrinterSetup(void) 
    { 
    return m_fPrintSetup; 
    } 
 
 
 
 
/* 
 * CPages::DevModeSet 
 * 
 * Purpose: 
 *  Provides the Pages with the current printer information. 
 * 
 * Parameters: 
 *  hDevMode        HGLOBAL to the memory containing the DEVMODE. 
 *                  This function assumes responsibility for this 
 *                  handle. 
 *  hDevNames       HGLOBAL providing the driver name and device 
 *                  name from which we can create a DC for 
 *                  information. 
 * 
 * Return Value: 
 *  BOOL            TRUE if we could accept this configuration, 
 *                  FALSE otherwise.  If we return TRUE we also 
 *                  delete the old memory we hold. 
 */ 
 
BOOL CPages::DevModeSet(HGLOBAL hDevMode, HGLOBAL hDevNames) 
    { 
    LPDEVNAMES      pdn; 
    LPTSTR          psz; 
    HGLOBAL         hMem; 
    PDEVICECONFIG   pdc; 
    LPDEVMODE       pdm; 
    LPSTREAM        pIStream; 
    HRESULT         hr; 
    ULONG           cbDevMode, cbWrite; 
    BOOL            fRet=FALSE; 
 
    if (NULL==hDevMode || NULL==hDevNames) 
        return FALSE; 
 
    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT 
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream); 
 
    if (FAILED(hr)) 
        return FALSE; 
 
    /* 
     * DEVMODE is variable length--total length in hDevMode, so the 
     * amount to write is that plus string space.  We subtract 
     * sizeof(DEVMODE) as that is already included from GlobalSize. 
     */ 
    cbDevMode=GlobalSize(hDevMode); 
    cbWrite=cbDevMode+sizeof(DEVICECONFIG)-sizeof(DEVMODE); 
 
    hMem=GlobalAlloc(GHND, cbWrite); 
 
    if (NULL==hMem) 
        { 
        pIStream->Release(); 
        return FALSE; 
        } 
 
    pdc=(PDEVICECONFIG)GlobalLock(hMem);    //This always works 
    pdm=(LPDEVMODE)GlobalLock(hDevMode);    //This might not 
 
    if (NULL!=pdm) 
        { 
        pdc->cb=cbWrite; 
        pdc->cbDevMode=cbDevMode; 
        memcpy(&pdc->dm, pdm, (int)cbDevMode); 
        GlobalUnlock(hDevMode); 
 
        psz=(LPTSTR)GlobalLock(hDevNames); 
 
        if (NULL!=psz) 
            { 
            pdn=(LPDEVNAMES)psz; 
            lstrcpy(pdc->szDriver, psz+pdn->wDriverOffset); 
            lstrcpy(pdc->szDevice, psz+pdn->wDeviceOffset); 
            lstrcpy(pdc->szPort,   psz+pdn->wOutputOffset); 
 
            pIStream->Write(pdc, cbWrite, &cbWrite); 
            GlobalUnlock(hDevNames); 
            fRet=TRUE; 
            } 
        } 
 
    GlobalUnlock(hMem); 
    GlobalFree(hMem); 
 
    pIStream->Release(); 
 
    if (!fRet) 
        return FALSE; 
 
    GlobalFree(hDevNames); 
    GlobalFree(hDevMode); 
 
    return ConfigureForDevice(); 
    } 
 
 
 
 
/* 
 * CPages::DevModeGet 
 * 
 * Purpose: 
 *  Retrieves a copy of the current DEVMODE structure for this 
 *  Pages window.  The caller is responsible for this memory. 
 * 
 * Parameters: 
 *  None 
 * 
 * Return Value: 
 *  HGLOBAL         Handle to the memory containing the DEVMODE 
 *                  structure. 
 */ 
 
HGLOBAL CPages::DevModeGet(void) 
    { 
    HGLOBAL         hMem; 
    LPVOID          pv; 
    ULONG           cbDevMode, cbRead; 
    LARGE_INTEGER   li; 
    LPSTREAM        pIStream; 
    HRESULT         hr; 
 
    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT 
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream); 
 
    if (FAILED(hr)) 
        return FALSE; 
 
    //Read how much to allocate for the DEVMODE structure 
    LISet32(li, CBSEEKOFFSETCBDEVMODE); 
    pIStream->Seek(li, STREAM_SEEK_SET, NULL); 
    pIStream->Read(&cbDevMode, sizeof(ULONG), &cbRead); 
 
    hMem=GlobalAlloc(GHND, cbDevMode); 
 
    if (NULL!=hMem) 
        { 
        pv=(LPVOID)GlobalLock(hMem); 
        pIStream->Read(pv, cbDevMode, &cbRead); 
        GlobalUnlock(hMem); 
        } 
 
    pIStream->Release(); 
    return hMem; 
    } 
 
 
 
 
 
 
 
/* 
 * CPages::DevReadConfig 
 * 
 * Purpose: 
 *  Public function to read the current device configuration and 
 *  optionally return an information context for it. 
 * 
 * 
 * Parameters: 
 *  ppcd            PCOMBINEDEVICE * in which to return a pointer 
 *                  to an allocated structure that has all the 
 *                  device information we want.  Ignored if NULL. 
 *                  This is allocated with the task allocator. 
 *  phDC            HDC * in which to return the information 
 *                  context.  If NULL, no IC is created.  Caller 
 *                  becomes responsible for the returned IC. 
 * 
 * Return Value: 
 *  BOOL            TRUE if successful, FALSE otherwise. 
 */ 
 
BOOL CPages::DevReadConfig(PCOMBINEDEVICE *ppcd, HDC *phDC) 
    { 
    HRESULT         hr; 
    LPSTREAM        pIStream; 
    LPMALLOC        pIMalloc; 
    PCOMBINEDEVICE  pcd; 
    ULONG           cb, cbRead; 
    LARGE_INTEGER   li; 
 
    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT 
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream); 
 
    if (FAILED(hr)) 
        return FALSE; 
 
    /* 
     * Allocate the COMBINEDEVICE structure including the variable 
     * information past the DEVMODE part. 
     */ 
 
    hr=CoGetMalloc(MEMCTX_TASK, &pIMalloc); 
 
    if (FAILED(hr)) 
        { 
        pIStream->Release(); 
        return FALSE; 
        } 
 
    /* 
     * Read size of the DEVICECONFIG structure including variable 
     * portion of DEVMODE.  We need to load all this information 
     * for CreateIC.  To this size we'll add the size of 
     * DVTARGETDEVICE in order to allocate a COMBINEDEVICE. 
     */ 
 
    pIStream->Read(&cb, sizeof(DWORD), &cbRead); 
 
    pcd=(PCOMBINEDEVICE)pIMalloc->Alloc(cb+sizeof(DVTARGETDEVICE)); 
 
    if (NULL==pcd) 
        { 
        pIMalloc->Release(); 
        pIStream->Release(); 
        return FALSE; 
        } 
 
    //Now get the real information. 
    LISet32(li, 0); 
    pIStream->Seek(li, STREAM_SEEK_SET, NULL); 
    pIStream->Read(&(pcd->dc), cb, &cbRead); 
    pIStream->Release(); 
 
 
    /* 
     * If requested, complete the DVTARGETDEVICE structure in 
     * pcd and store pcd in *ppcd for return. 
     */ 
 
    if (NULL!=ppcd) 
        { 
        WORD    cb=sizeof(DVTARGETDEVICE); 
 
        pcd->td.tdSize=cb; 
        pcd->td.tdExtDevmodeOffset=cb; 
        pcd->td.tdDriverNameOffset=cb+sizeof(DEVMODE); 
        pcd->td.tdDeviceNameOffset=cb+sizeof(DEVMODE) 
            +(CCHDEVICENAME*sizeof(TCHAR)); 
        pcd->td.tdPortNameOffset  =cb+sizeof(DEVMODE) 
            +(CCHDEVICENAME*2*sizeof(TCHAR)); 
 
        *ppcd=pcd; 
        } 
 
    //Create an IC if requested. 
    if (NULL!=phDC) 
        { 
        //Get the DC then configure 
        *phDC=CreateIC(pcd->dc.szDriver, pcd->dc.szDevice 
            , pcd->dc.szPort, &(pcd->dc.dm)); 
 
        if (NULL==*phDC) 
            return FALSE; 
        } 
 
    //pcd is a temporary allocation in this case 
    if (NULL==ppcd) 
        pIMalloc->Free(pcd); 
 
    pIMalloc->Release(); 
    return (NULL!=*phDC); 
    } 
 
 
 
 
 
/* 
 * CPages::ConfigureForDevice 
 * 
 * Purpose: 
 *  Recalculates our drawing configuration based on the contents of 
 *  an hDC.  If no HDC is given we use the contents of our DevMode 
 *  stream. 
 * 
 * Parameters: 
 *  None 
 * 
 * Return Value: 
 *  BOOL            TRUE if successful, FALSE otherwise. 
 */ 
 
BOOL CPages::ConfigureForDevice(void) 
    { 
    POINT           ptOffset, ptPaper; 
    RECT            rc; 
    HDC             hDC; 
    CHourglass      hg; 
 
    if (!DevReadConfig(NULL, &hDC)) 
        return FALSE; 
 
    //Get usable page dimensions:  already sensitive to orientation 
    m_cx=GetDeviceCaps(hDC, HORZSIZE)*10-16; //*10: mm to LOMETRIC 
    m_cy=GetDeviceCaps(hDC, VERTSIZE)*10-16; //-16: for driver bugs. 
 
    //Calculate the printer-limited margins on sides in LOMETRIC. 
    Escape(hDC, GETPRINTINGOFFSET, NULL, NULL, &ptOffset); 
    Escape(hDC, GETPHYSPAGESIZE,   NULL, NULL, &ptPaper); 
 
    SetRect(&rc, ptOffset.x, ptOffset.y, ptPaper.x, ptPaper.y); 
    SetMapMode(hDC, MM_LOMETRIC); 
    RectConvertMappings(&rc, hDC, FALSE); 
 
    //Left and top margins are the printing offset. 
    m_xMarginLeft= rc.left+8;   //+8 to match -16 above 
    m_yMarginTop =-rc.top+8;    //LOMETRIC makes this negative. 
 
    //Right is (paper width)-(usable width)-(left margin) 
    m_xMarginRight =rc.right-m_cx-m_xMarginLeft; 
 
    //Bottom is (paper height)-(usable height)-(top margin)+1 
    m_yMarginBottom=-rc.bottom-m_cy-m_yMarginTop+1; 
 
    UpdateScrollRanges(); 
 
    DeleteDC(hDC); 
    return TRUE; 
    } 
 
 
 
 
/* 
 * CPages::Print 
 * 
 * Purpose: 
 *  Prints a specified range of pages to a given hDC.  Repeats for 
 *  a given number of copies. 
 * 
 * Parameters: 
 *  hDC             HDC to which we print. 
 *  pszDoc          LPTSTR providing the document name. 
 *  dwFlags         DWORD flags from PrintDlg 
 *  iPageStart      UINT starting page index (one based) 
 *  iPageEnd        UINT ending page index (one based).  Includes 
 *                  this page. 
 *  cCopies         UINT number of copies to print.  If PD_COLLATE 
 *                  in dwFlags is set, we print multiple copies of 
 *                  each page as we cycle through.  Otherwise we 
 *                  cycle multiple times. 
 * 
 * Return Value: 
 *  None 
 */ 
 
BOOL CPages::Print(HDC hDC, LPTSTR pszDoc, DWORD dwFlags 
    , UINT iPageStart, UINT iPageEnd, UINT cCopies) 
    { 
    BOOL        fError=FALSE; 
    int         iPage, iPageInc; 
    int         iUserPage, cPages; 
    UINT        iRepeat, cRepeat; 
    UINT        iCycle, cCycles; 
    UINT        iPageHold=m_iPageCur; 
    HWND        hWndT, hWndTop=NULL; 
    DOCINFO     di; 
    PCDocument  pDoc; 
 
    //Validate hDC and page ranges 
    if (NULL==hDC) 
        return FALSE; 
 
    if ((PD_PAGENUMS & dwFlags)) 
        { 
        if (-1==iPageStart) 
            iPageStart=0; 
        else 
            iPageStart--;   //Switch to zero offset. 
 
        if (-1==iPageEnd) 
            iPageEnd=m_cPages-1; 
        else 
            iPageEnd--;     //Switch to zero offset. 
        } 
    else //Can't test PD_ALLPAGES with & since it's defined as 0L 
        { 
        iPageStart=0; 
        iPageEnd=m_cPages-1; 
        } 
 
    //Arrange cycles and repeats depending on cCopies and collating 
    if (PD_COLLATE & dwFlags) 
        { 
        cCycles=cCopies; 
        cRepeat=1; 
        } 
    else 
        { 
        cCycles=1; 
        cRepeat=cCopies; 
        } 
 
    //Disable the frame window to prevent reentrancy while printing. 
    hWndT=GetParent(m_hWnd); 
    pDoc=(PCDocument)SendMessage(hWndT, DOCM_PDOCUMENT, 0, 0L); 
 
    if (NULL!=pDoc) 
        { 
        PCFrame pFR; 
 
        pFR=pDoc->FrameGet(); 
        hWndTop=pFR->Window(); 
        EnableWindow(hWndTop, FALSE); 
        } 
 
    SetAbortProc(hDC, AbortProc); 
    g_fCancelPrint=FALSE; 
 
    //If these don't work then we'll just live without a dialog. 
    g_hDlgPrint=CreateDialog(m_hInst, MAKEINTRESOURCE(IDD_PRINTING) 
        , hWndTop, PrintDlgProc); 
 
    //Increment for either direction. 
    iPageInc=(iPageStart > iPageEnd) ? -1 : 1; 
 
    //Initial entries in dialog box. 
    cPages=1+((int)(iPageEnd-iPageStart)*iPageInc); 
 
    SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE, 1, (LPARAM)cPages); 
    SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, 1, (LPARAM)cRepeat); 
 
    di.cbSize=sizeof(DOCINFO); 
    di.lpszDocName=pszDoc; 
    di.lpszOutput=NULL; 
 
    if (StartDoc(hDC, &di) > 0) 
        { 
        /* 
         * Iterate over the pages, repeating each page depending on 
         * the copies we want and if we have collate enabled. 
         */ 
 
        for (iCycle=1; iCycle <= cCycles; iCycle++) 
            { 
            if (PD_COLLATE & dwFlags) 
                { 
                SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, iCycle 
                    , (LPARAM)cCycles); 
                } 
 
            //iPageInc controls direction 
            for (iPage=iPageStart; ; iPage+=iPageInc) 
                { 
                iUserPage=1+((iPage-(int)iPageStart)*iPageInc); 
 
                SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE 
                    , iUserPage, (LPARAM)cPages); 
 
                m_iPageCur=iPage;   //We restore this later. 
 
                for (iRepeat=1; iRepeat <= cRepeat; iRepeat++) 
                    { 
                    if (!(PD_COLLATE & dwFlags)) 
                        { 
                        SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE 
                            , iRepeat, (LPARAM)cRepeat); 
                        } 
 
                    StartPage(hDC); 
                    Draw(hDC, TRUE, TRUE); 
 
                    if (EndPage(hDC) < 0) 
                        fError=TRUE; 
 
                    if (fError || g_fCancelPrint) 
                        break; 
                    } 
 
                if (fError || g_fCancelPrint) 
                    break; 
 
                //If we just printed the last page, time to quit. 
                if (iPage==(int)iPageEnd) 
                    break; 
                } 
 
            if (fError || g_fCancelPrint) 
                break; 
            } 
 
        if (!fError) 
            EndDoc(hDC); 
        else 
            AbortDoc(hDC); 
        } 
    else 
        fError=TRUE; 
 
    //Set the page back to what it was before all this started. 
    m_iPageCur=iPageHold; 
 
    EnableWindow(hWndTop, TRUE); 
    SetFocus(hWndTop); 
    DestroyWindow(g_hDlgPrint); 
    DeleteDC(hDC); 
    return !fError; 
    } 
 
 
 
 
 
 
/* 
 * AbortProc 
 * 
 * Purpose: 
 *  Abort procedure for printing the pages. 
 * 
 * Parameters: 
 *  hDC             HDC on which printing is happening. 
 *  iErr            int error code. 
 * 
 * Return Value: 
 *  BOOL            TRUE to continue the print job, FALSE otherwise. 
 */ 
 
BOOL APIENTRY AbortProc(HDC hDC, int iErr) 
    { 
    MSG     msg; 
 
    while (!g_fCancelPrint 
        && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) 
        { 
        if (NULL==g_hDlgPrint 
            || !IsDialogMessage(g_hDlgPrint, &msg)) 
            { 
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
            } 
        } 
 
    return !g_fCancelPrint; 
    } 
 
 
 
 
/* 
 * PrintDlgProc 
 * 
 * Purpose: 
 *  Modeless dialog procedure for the dialog displayed while Patron 
 *  is printing pages. 
 */ 
 
BOOL APIENTRY PrintDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam 
    , LPARAM lParam) 
    { 
    TCHAR           szFormat[40]; 
    TCHAR           szOutput[80]; 
 
    switch (iMsg) 
        { 
        case WM_INITDIALOG: 
            EnableMenuItem(GetSystemMenu(hDlg, FALSE), SC_CLOSE 
                , MF_GRAYED); 
            return TRUE; 
 
        case WM_COMMAND: 
            //Cancel button was pressed. 
            g_fCancelPrint=TRUE; 
            ShowWindow(hDlg, SW_HIDE); 
            return TRUE; 
 
        case PRINTM_PAGEUPDATE: 
            GetDlgItemText(hDlg, ID_PAGESTRING, szFormat 
                , sizeof(szFormat)); 
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam); 
            SetDlgItemText(hDlg, ID_CURRENTPAGE, szOutput); 
            return TRUE; 
 
        case PRINTM_COPYUPDATE: 
            GetDlgItemText(hDlg, ID_COPYSTRING, szFormat 
                , sizeof(szFormat)); 
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam); 
            SetDlgItemText(hDlg, ID_CURRENTCOPY, szOutput); 
            return TRUE; 
        } 
 
    return FALSE; 
    }