// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1995 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.

#include "stdafx.h"

#ifdef AFX_PRINT_SEG
#pragma code_seg(AFX_PRINT_SEG)
#endif

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

BOOL CALLBACK _AfxPreviewCloseProc(CFrameWnd* pFrameWnd);

/////////////////////////////////////////////////////////////////////////////
// CPrintPreviewState helper structure

CPrintPreviewState::CPrintPreviewState()
{
	// set defaults
	nIDMainPane = AFX_IDW_PANE_FIRST;
	dwStates = AFX_CONTROLBAR_MASK(AFX_IDW_STATUS_BAR);
						// status bar visible if available
	lpfnCloseProc = _AfxPreviewCloseProc;
						// set frame hook so closing the frame window
						//  when in preview state will just end the mode
	hMenu = NULL;
	pViewActiveOld = NULL;
	hAccelTable = NULL;
}

/////////////////////////////////////////////////////////////////////////////
// CView's OnPrintPreview.  Here to force linkage

void CView::OnFilePrintPreview()
{
	// In derived classes, implement special window handling here
	// Be sure to Unhook Frame Window close if hooked.

	// must not create this on the frame.  Must outlive this function
	CPrintPreviewState* pState = new CPrintPreviewState;

	// DoPrintPreview's return value does not necessarily indicate that
	// Print preview succeeded or failed, but rather what actions are necessary
	// at this point.  If DoPrintPreview returns TRUE, it means that
	// OnEndPrintPreview will be (or has already been) called and the
	// pState structure will be/has been deleted.
	// If DoPrintPreview returns FALSE, it means that OnEndPrintPreview
	// WILL NOT be called and that cleanup, including deleting pState
	// must be done here.

	if (!DoPrintPreview(AFX_IDD_PREVIEW_TOOLBAR, this,
							RUNTIME_CLASS(CPreviewView), pState))
	{
		// In derived classes, reverse special window handling here for
		// Preview failure case

		TRACE0("Error: DoPrintPreview failed.\n");
		AfxMessageBox(AFX_IDP_COMMAND_FAILURE);
		delete pState;      // preview failed to initialize, delete State now
	}
}

BOOL CView::DoPrintPreview(UINT nIDResource, CView* pPrintView,
	CRuntimeClass* pPreviewViewClass, CPrintPreviewState* pState)
{
	ASSERT_VALID_IDR(nIDResource);
	ASSERT_VALID(pPrintView);
	ASSERT(pPreviewViewClass != NULL);
	ASSERT(pPreviewViewClass->IsDerivedFrom(RUNTIME_CLASS(CPreviewView)));
	ASSERT(pState != NULL);

	CFrameWnd* pParent = (CFrameWnd*)AfxGetThread()->m_pMainWnd;
	ASSERT_VALID(pParent);
	ASSERT_KINDOF(CFrameWnd, pParent);

	CCreateContext context;
	context.m_pCurrentFrame = pParent;
	context.m_pCurrentDoc = GetDocument();
	context.m_pLastView = this;

	// Create the preview view object
	CPreviewView* pView = (CPreviewView*)pPreviewViewClass->CreateObject();
	if (pView == NULL)
	{
		TRACE0("Error: Failed to create preview view.\n");
		return FALSE;
	}
	ASSERT_KINDOF(CPreviewView, pView);
	pView->m_pPreviewState = pState;        // save pointer

	pParent->OnSetPreviewMode(TRUE, pState);    // Take over Frame Window

#ifdef _MAC
	if (nIDResource == AFX_IDD_PREVIEW_TOOLBAR)
	{
		HINSTANCE hInst = AfxFindResourceHandle(
			MAKEINTRESOURCE(AFX_IDD_PREVIEW_TOOLBAR), RT_DIALOG);
		HRSRC hResource = FindResource(hInst,
			MAKEINTRESOURCE(AFX_IDD_PREVIEW_TOOLBAR), RT_DIALOG);

		HGLOBAL hdt = NULL;
		if (hResource != NULL)
			hdt = LoadResource(hInst, hResource);

		DLGTEMPLATE* pdt = NULL;
		if (hdt != NULL)
			pdt = (DLGTEMPLATE*)LockResource(hdt);

		if (pdt != NULL)
		{
			CRect rectParent;
			pParent->GetClientRect(&rectParent);

			int cxToolbar = MulDiv(LOWORD(GetDialogBaseUnits()), pdt->cx, 4);

			if (cxToolbar > rectParent.Width())
				nIDResource = AFX_IDD_PREVIEW_SHORTTOOLBAR;

			UnlockResource(hdt);
			FreeResource(hdt);
		}
	}
#endif

	// Create the toolbar from the dialog resource
	pView->m_pToolBar = new CDialogBar;
	if (!pView->m_pToolBar->Create(pParent, MAKEINTRESOURCE(nIDResource),
		CBRS_TOP, AFX_IDW_PREVIEW_BAR))
	{
		TRACE0("Error: Preview could not create toolbar dialog.\n");
		pParent->OnSetPreviewMode(FALSE, pState);   // restore Frame Window
		delete pView->m_pToolBar;       // not autodestruct yet
		pView->m_pToolBar = NULL;
		pView->m_pPreviewState = NULL;  // do not delete state structure
		delete pView;
		return FALSE;
	}
	pView->m_pToolBar->m_bAutoDelete = TRUE;    // automatic cleanup

	// Create the preview view as a child of the App Main Window.  This
	// is a sibling of this view if this is an SDI app.  This is NOT a sibling
	// if this is an MDI app.

	if (!pView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
		CRect(0,0,0,0), pParent, AFX_IDW_PANE_FIRST, &context))
	{
		TRACE0("Error: couldn't create preview view for frame.\n");
		pParent->OnSetPreviewMode(FALSE, pState);   // restore Frame Window
		pView->m_pPreviewState = NULL;  // do not delete state structure
		delete pView;
		return FALSE;
	}

	// Preview window shown now

	pState->pViewActiveOld = pParent->GetActiveView();
	CView* pActiveView = pParent->GetActiveFrame()->GetActiveView();
	if (pActiveView != NULL)
		pActiveView->OnActivateView(FALSE, pActiveView, pActiveView);

	if (!pView->SetPrintView(pPrintView))
	{
		pView->OnPreviewClose();
		return TRUE;            // signal that OnEndPrintPreview was called
	}

	pParent->SetActiveView(pView);  // set active view - even for MDI

	// update toolbar and redraw everything
	pView->m_pToolBar->SendMessage(WM_IDLEUPDATECMDUI, (WPARAM)TRUE);
	pParent->RecalcLayout();            // position and size everything
	pParent->UpdateWindow();

	return TRUE;
}

BOOL CALLBACK _AfxPreviewCloseProc(CFrameWnd* pFrameWnd)
{
	ASSERT_VALID(pFrameWnd);

	CPreviewView* pView = (CPreviewView*) pFrameWnd->GetDlgItem(AFX_IDW_PANE_FIRST);
	ASSERT_KINDOF(CPreviewView, pView);

	pView->OnPreviewClose();
	return FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Preview View

BEGIN_MESSAGE_MAP(CPreviewView, CScrollView)
	//{{AFX_MSG_MAP(CPreviewView)
	ON_WM_SIZE()        // overriding CScrollView
	ON_WM_CREATE()

	ON_COMMAND(AFX_ID_PREVIEW_CLOSE, OnPreviewClose)
	ON_COMMAND(AFX_ID_PREVIEW_NUMPAGE, OnNumPageChange)
	ON_COMMAND(AFX_ID_PREVIEW_NEXT, OnNextPage)
	ON_COMMAND(AFX_ID_PREVIEW_PREV, OnPrevPage)
	ON_COMMAND(AFX_ID_PREVIEW_PRINT, OnPreviewPrint)
	ON_COMMAND(AFX_ID_PREVIEW_ZOOMIN, OnZoomIn)
	ON_COMMAND(AFX_ID_PREVIEW_ZOOMOUT, OnZoomOut)

	ON_UPDATE_COMMAND_UI(AFX_ID_PREVIEW_NUMPAGE, OnUpdateNumPageChange)
	ON_UPDATE_COMMAND_UI(AFX_ID_PREVIEW_NEXT, OnUpdateNextPage)
	ON_UPDATE_COMMAND_UI(AFX_ID_PREVIEW_PREV, OnUpdatePrevPage)
	ON_UPDATE_COMMAND_UI(AFX_ID_PREVIEW_ZOOMIN, OnUpdateZoomIn)
	ON_UPDATE_COMMAND_UI(AFX_ID_PREVIEW_ZOOMOUT, OnUpdateZoomOut)

	ON_WM_VSCROLL()
	ON_WM_HSCROLL()
	ON_WM_LBUTTONDOWN()
	ON_WM_ERASEBKGND()
	ON_WM_SETCURSOR()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

CPreviewView::CPreviewView()
{
	m_pPrintView = NULL;
	m_pOrigView = NULL;
	m_pPreviewInfo = NULL;
	m_pPreviewDC = NULL;
	m_pPreviewState = NULL;
	m_hMagnifyCursor = NULL;
	m_bPageNumDisplayed = FALSE;
	m_nZoomState = ZOOM_OUT;

	// default to pointing to embedded array.  Allows for 2 pages
	m_pPageInfo = m_pageInfoArray;
	m_nMaxPages = 2;

	// initialize CScrollView members
	m_bCenter = TRUE;                   // Center Zoomed output in Scrollview
	m_nMapMode = MM_TEXT;
}

CPreviewView::~CPreviewView()
{
	m_dcPrint.Detach();         // print DC is deleted by CPrintInfo destructor

	delete m_pPreviewInfo;      // get rid of preview info
	delete m_pPreviewState;     // Get rid of preview state
	delete m_pPreviewDC;        // Get rid of preview DC object

	if (m_hMagnifyCursor != NULL)
	{
		// make sure that m_hMagnifyCursor isn't the current cursor when we destroy it
		::SetCursor(::LoadCursor(NULL, IDC_ARROW));
		DestroyCursor(m_hMagnifyCursor);
	}
}

int CPreviewView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	int retVal = CView::OnCreate(lpCreateStruct);
	if (retVal == -1)
		return -1;      // if -1 bag out

	CCreateContext* pContext = (CCreateContext*)lpCreateStruct->lpCreateParams;

	m_pOrigView = pContext->m_pLastView;
	ASSERT(m_pOrigView != NULL);
	ASSERT_KINDOF(CView, m_pOrigView);

	return retVal;
}

#ifdef _MAC
void CPreviewView::CalcWindowRect(LPRECT lpClientRect)
{
	CView::CalcWindowRect(lpClientRect);

	// The preview view is created with the WS_BORDER style, and when
	// the view is repositioned, these borders will by default be placed
	// outside the main monitor. If there are other monitors adjacent to
	// the main monitor, the border shows up on those monitors, which looks
	// sloppy. To prevent this we resize the window rect as necessary to
	// keep the borders inside the MDI parent frame window's client area.

	// Also note that we can't just get rid of the borders entirely because
	// this looks bad on a PowerBook where the edges of the screen are
	// white instead of black - if there aren't any borders, there's no
	// dividing line between the edge of the preview view's scrollbars
	// and the non-drawing areas of the screen.

	CWnd* pParent = GetParent();
	if (pParent->IsKindOf(RUNTIME_CLASS(CMDIFrameWnd)))
	{
		RECT rect;

		pParent->GetClientRect(&rect);
		if (lpClientRect->left < rect.left)
			lpClientRect->left = rect.left;
		if (lpClientRect->right > rect.right)
			lpClientRect->right = rect.right;
		if (lpClientRect->bottom > rect.bottom)
			lpClientRect->bottom = rect.bottom;

		// top not needed because it will be adjacent to the print
		// preview toolbar
	}
}
#endif

BOOL CPreviewView::SetPrintView(CView* pPrintView)
{
	ASSERT_VALID(pPrintView);

	m_pPrintView = pPrintView;

	// allocate preview info
	m_pPreviewInfo = new CPrintInfo;
	m_pPreviewInfo->m_pPD->SetHelpID(AFX_IDD_PRINTSETUP);
	m_pPreviewInfo->m_pPD->m_pd.Flags |= PD_PRINTSETUP;
	m_pPreviewInfo->m_pPD->m_pd.Flags &= ~PD_RETURNDC;

	m_pPreviewInfo->m_bPreview = TRUE;  // signal that this is preview
	ASSERT(m_pPreviewInfo->m_pPD != NULL);

	m_pPreviewDC = new CPreviewDC;      // must be created before any
										// possible error returns

	if (!m_pPrintView->OnPreparePrinting(m_pPreviewInfo))
		return FALSE;

#ifdef _DEBUG
	if (m_pPreviewInfo->m_pPD->m_pd.hDC == NULL)
	{
		TRACE0("Error: hDC not set for printing --\n");
		TRACE0("\tDid you remember to call DoPreparePrinting?\n");
		ASSERT(FALSE);      // common mistake gets trapped here
	}
#endif //_DEBUG

	m_dcPrint.Attach(m_pPreviewInfo->m_pPD->m_pd.hDC);
	m_pPreviewDC->SetAttribDC(m_pPreviewInfo->m_pPD->m_pd.hDC);
	m_pPreviewDC->m_bPrinting = TRUE;
	m_dcPrint.m_bPrinting = TRUE;

	m_dcPrint.SaveDC();     // Save pristine state of DC

	HDC hDC = ::GetDC(m_hWnd);
	m_pPreviewDC->SetOutputDC(hDC);
	m_pPrintView->OnBeginPrinting(m_pPreviewDC, m_pPreviewInfo);
	m_pPreviewDC->ReleaseOutputDC();
	::ReleaseDC(m_hWnd, hDC);

	m_dcPrint.RestoreDC(-1);    // restore to untouched state

	// Get Pixels per inch from Printer
	m_sizePrinterPPI.cx = m_dcPrint.GetDeviceCaps(LOGPIXELSX);
	m_sizePrinterPPI.cy = m_dcPrint.GetDeviceCaps(LOGPIXELSY);

	m_nPages = m_pPreviewInfo->m_nNumPreviewPages;
	if (m_nPages == 0)
		m_nPages = 1;
	else if (m_nPages > m_nMaxPages)
		m_nPages = m_nMaxPages;     // Sanity Check!

	m_nZoomOutPages = m_nPages;

	SetScrollSizes(MM_TEXT, CSize(1, 1));   // initialize mapping mode only

	if (m_pPreviewInfo->GetMaxPage() < 0x8000 &&
		m_pPreviewInfo->GetMaxPage() - m_pPreviewInfo->GetMinPage() <= 32767U)
		SetScrollRange(SB_VERT, m_pPreviewInfo->GetMinPage(),
				m_pPreviewInfo->GetMaxPage(), FALSE);
	else
		ShowScrollBar(SB_VERT, FALSE);      // if no range specified, or too
											// large don't show

	SetCurrentPage(m_pPreviewInfo->m_nCurPage, TRUE);
	return TRUE;
}

void CPreviewView::OnSize(UINT nType, int cx, int cy)
{
	// CScrollView handles everything if zoomed in.
	if (m_nZoomState == ZOOM_OUT)
	{
		// Force recalc of scale ratios on next draw
		for (UINT i = 0; i < m_nMaxPages; i++)
			m_pPageInfo[i].sizeScaleRatio.cx = 0;           // zero scale ratios

		CView::OnSize(nType, cx, cy);       // No scroll functionality
	}
	else
	{
		// adjust scroll size to size of page
		m_pageDev.cx = cx;
		m_pageDev.cy = cy;
		m_lineDev.cx = cx / 10;
		m_lineDev.cy = cy / 10;
		CScrollView::OnSize(nType, cx, cy);
	}
}

void CPreviewView::OnActivateView(BOOL bActivate, CView*, CView*)
{
	if (bActivate)
	{
		CWnd* pFocusWnd = GetFocus();
		if (pFocusWnd == NULL ||
			(m_pToolBar != NULL && !m_pToolBar->IsChild(pFocusWnd)))
		{
			// focus is not already on a toolbar button - set it to one
			m_pToolBar->GetDlgItem(AFX_ID_PREVIEW_PRINT)->SetFocus();
		}
	}

#ifdef _MAC
		// Make sure the preview frame's activation matches that of the frame
		// window so that scroll bars are drawn with the correct visibility.
		SendMessage(WM_NCACTIVATE, bActivate, 0);
#endif
}

void CPreviewView::OnPreviewClose()
{
	m_pToolBar->DestroyWindow();
	m_pToolBar = NULL;

	m_pPreviewInfo->m_nCurPage = m_nCurrentPage;
	m_pOrigView->OnEndPrintPreview(m_pPreviewDC, m_pPreviewInfo,
									CPoint(0, 0), this);
}

#define PREVIEW_MARGIN  8
#define PREVIEW_PAGEGAP 8

// Return is actually the fraction cx/cy. Simply using CSize for convenience
CSize CPreviewView::CalcScaleRatio(CSize screenSize, CSize actualSize)
{
	// Test ratio based on vertical dimension to see if it is the one to use
	int nNum = screenSize.cy;
	int nDen = actualSize.cy;

	// If scaled width too large, choose width as primary dimension
	if (MulDiv(actualSize.cx, nNum, nDen) > screenSize.cx)
	{
		// wrong ratio--base on width
		nNum = screenSize.cx;
		nDen = actualSize.cx;
	}
	CSize ratio(nNum, nDen);
	return ratio;
}

// Position Page...
// Generate a Screen MM_TEXT rectangle to enclose each page.  Dimensions
// of the rectangle must be 1 pixel Above and Left of the top/left corner
// of the page and the rectangle width and height must be THREE pixels
// larger than page in order to provide the correct placement of the
// two pixel border.
//
// This routine is called once for each page with the preview DC set up for
// that page

void CPreviewView::PositionPage(UINT nPage)
{
	CSize windowSize = CalcPageDisplaySize();

	VERIFY(m_dcPrint.Escape(GETPHYSPAGESIZE, 0, NULL,
			(LPVOID)&m_pPageInfo[nPage].sizeUnscaled));

	CSize* pSize = &m_pPageInfo[nPage].sizeUnscaled;

	// Convert page size to screen coordinates
	pSize->cx = MulDiv(pSize->cx, afxData.cxPixelsPerInch, m_sizePrinterPPI.cx);
	pSize->cy = MulDiv(pSize->cy, afxData.cyPixelsPerInch, m_sizePrinterPPI.cy);

	m_pPageInfo[nPage].sizeZoomOutRatio = CalcScaleRatio(windowSize, *pSize);

	SetScaledSize(nPage);
}

CSize CPreviewView::CalcPageDisplaySize()
	// calculate the current page size
	//  set 'm_nSecondPageOffset' to start of second page
	// return size of current page less margins
{
	CSize windowSize, scrollSize;
	GetTrueClientSize(windowSize, scrollSize);

	// subtract out vertical scrollbar if zoomed out and page range is known
	// and there is more than one page.
	if (m_nZoomState == ZOOM_OUT && (m_pPreviewInfo->GetMaxPage() != 0xffff) &&
		(m_pPreviewInfo->GetMaxPage() - m_pPreviewInfo->GetMinPage() != 0))
		windowSize.cx -= scrollSize.cx;

	m_nSecondPageOffset = (windowSize.cx - PREVIEW_MARGIN) / 2;

	windowSize.cx = (m_nPages == 2) ? (windowSize.cx - 3*PREVIEW_MARGIN) / 2 :
									windowSize.cx - 2*PREVIEW_MARGIN;

	windowSize.cy -= 2*PREVIEW_MARGIN;
	return windowSize;
}

void CPreviewView::SetScaledSize(UINT nPage)
{
	CSize* pSize = &m_pPageInfo[nPage].sizeUnscaled;
	CSize* pRatio = &m_pPageInfo[nPage].sizeScaleRatio;
	CSize* pZoomOutRatio = &m_pPageInfo[nPage].sizeZoomOutRatio;
	CSize windowSize = CalcPageDisplaySize();
	BOOL bPaperLarger = pZoomOutRatio->cx < pZoomOutRatio->cy;
		// whether the paper is larger than the screen, or vice versa

	switch (m_nZoomState)
	{
	case ZOOM_OUT:
		*pRatio = *pZoomOutRatio;
		break;

	case ZOOM_MIDDLE:
		// the middle zoom state is a ratio between cx/cy and
		// 1/1 (or cy/cy).  It is, therefore:
		//
		// (cx + cy)/2
		// -----------
		//     cy
		//
		// if the paper is larger than the screen, or
		//
		// (3*cx - cy)/2
		// -------------
		//      cy
		//
		// if the paper is smaller than the screen.
		if (bPaperLarger)
		{
			pRatio->cy = pZoomOutRatio->cy;
			pRatio->cx = (pZoomOutRatio->cx + pRatio->cy) / 2;
		}
		else
		{
			pRatio->cy = pZoomOutRatio->cy;
			pRatio->cx = (3*pZoomOutRatio->cx - pRatio->cy) / 2;
		}
		break;

	case ZOOM_IN:
		if (bPaperLarger)
			pRatio->cx = pRatio->cy = 1;
		else
		{
			// if the paper is smaller than the screen space we're displaying
			// it in, then using a ratio of 1/1 will result in a smaller image
			// on the screen, not a larger one. To get a larger image in this
			// case we double the zoom out ratio.
			pRatio->cy = pZoomOutRatio->cy;
			pRatio->cx = 2*pZoomOutRatio->cx - pZoomOutRatio->cy;
		}
		break;

	default:
		ASSERT(FALSE);
	}

	// Convert to scaled size
	CSize scaledSize;
	scaledSize.cx = MulDiv(pSize->cx, pRatio->cx, pRatio->cy);
	scaledSize.cy = MulDiv(pSize->cy, pRatio->cx, pRatio->cy);

	CRect* pRect = &m_pPageInfo[nPage].rectScreen;
	pRect->SetRect(PREVIEW_MARGIN, PREVIEW_MARGIN,
				   scaledSize.cx + PREVIEW_MARGIN + 3,
				   scaledSize.cy + PREVIEW_MARGIN + 3);

	if (m_nZoomState == ZOOM_OUT)
	{
		pRect->OffsetRect((windowSize.cx - pRect->Size().cx) / 2 - 1,
						  (windowSize.cy - pRect->Size().cy) / 2 - 1);

		if (nPage == 1)
			pRect->OffsetRect(m_nSecondPageOffset, 0);
	}
	else
	{
		// set up scroll size

		SetScrollSizes(MM_TEXT, pRect->Size() +
				CSize(PREVIEW_MARGIN * 2, PREVIEW_MARGIN * 2), windowSize);
	}
}

// Only use the PrepareDC from CScrollView if we are zoomed in
void CPreviewView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
	ASSERT_VALID(pDC);

	if (m_nZoomState == ZOOM_OUT)
		CView::OnPrepareDC(pDC, pInfo);
	else if (m_pPageInfo[0].sizeScaleRatio.cx != 0)
		CScrollView::OnPrepareDC(pDC, pInfo);
}

BOOL CPreviewView::OnEraseBkgnd(CDC* pDC)
{
	ASSERT_VALID(pDC);

	// Fill background with APPWORKSPACE
	CBrush backBrush(GetSysColor(COLOR_APPWORKSPACE));
	CBrush* pOldBrush = pDC->SelectObject(&backBrush);
	CRect rect;
	pDC->GetClipBox(&rect);     // Erase the area needed

	pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
	pDC->SelectObject(pOldBrush);
	return TRUE;
}

void CPreviewView::OnDraw(CDC* pDC)
{
	ASSERT_VALID(pDC);

	// don't do anything if not fully initialized
	if (m_pPrintView == NULL || m_dcPrint.m_hDC == NULL)
		return;

	CPoint ViewportOrg = pDC->GetViewportOrg();

	CPen rectPen;
	rectPen.CreatePen(PS_SOLID, 2, GetSysColor(COLOR_WINDOWFRAME));
	CPen shadowPen;
	shadowPen.CreatePen(PS_SOLID, 3, GetSysColor(COLOR_BTNSHADOW));

	m_pPreviewInfo->m_bContinuePrinting = TRUE;     // do this once each paint

	for (UINT nPage = 0; nPage < m_nPages; nPage++)
	{
		int nSavedState = m_dcPrint.SaveDC();       // Save pristine state of DC

		// Use paint DC for print preview output
		m_pPreviewDC->SetOutputDC(pDC->GetSafeHdc());

		m_pPreviewInfo->m_nCurPage = m_nCurrentPage + nPage;

		// Only call PrepareDC if within page range, otherwise use default
		// rect to draw page rectangle
		if (m_nCurrentPage + nPage <= m_pPreviewInfo->GetMaxPage())
			m_pPrintView->OnPrepareDC(m_pPreviewDC, m_pPreviewInfo);

		// Set up drawing rect to entire page (in logical coordinates)
		m_pPreviewInfo->m_rectDraw.SetRect(0, 0,
			m_pPreviewDC->GetDeviceCaps(HORZRES),
			m_pPreviewDC->GetDeviceCaps(VERTRES));
		m_pPreviewDC->DPtoLP(&m_pPreviewInfo->m_rectDraw);

		// Draw empty page on screen

		pDC->SaveDC();          // save the output dc state

		CSize* pRatio = &m_pPageInfo[nPage].sizeScaleRatio;
		CRect* pRect = &m_pPageInfo[nPage].rectScreen;

		if (pRatio->cx == 0)
		{   // page position has not been determined
			PositionPage(nPage);    // compute page position
			if (m_nZoomState != ZOOM_OUT)
				ViewportOrg = -GetDeviceScrollPosition();
		}

		pDC->SetMapMode(MM_TEXT);   // Page Rectangle is in screen device coords
		pDC->SetViewportOrg(ViewportOrg);
		pDC->SetWindowOrg(0, 0);

		pDC->SelectStockObject(HOLLOW_BRUSH);
		pDC->SelectObject(&rectPen);
		pDC->Rectangle(pRect);

		pDC->SelectObject(&shadowPen);

		pDC->MoveTo(pRect->right + 1, pRect->top + 3);
		pDC->LineTo(pRect->right + 1, pRect->bottom + 1);
		pDC->MoveTo(pRect->left + 3, pRect->bottom + 1);
		pDC->LineTo(pRect->right + 1, pRect->bottom + 1);

		// erase background to white (most paper is white)
		CRect rectFill = *pRect;
		rectFill.left += 1;
		rectFill.top += 1;
		rectFill.right -= 2;
		rectFill.bottom -= 2;
		::FillRect(pDC->m_hDC, rectFill, (HBRUSH)GetStockObject(WHITE_BRUSH));

		pDC->RestoreDC(-1);     // restore to synchronized state

		if (!m_pPreviewInfo->m_bContinuePrinting ||
				m_nCurrentPage + nPage > m_pPreviewInfo->GetMaxPage())
		{
			m_pPreviewDC->ReleaseOutputDC();
			m_dcPrint.RestoreDC(nSavedState);   // restore to untouched state

			// if the first page is not displayable, back up one page
			// but never go below 1
			if (nPage == 0 && m_nCurrentPage > 1)
				SetCurrentPage(m_nCurrentPage - 1, TRUE);
			break;
		}

		// Display page number
		OnDisplayPageNumber(m_nCurrentPage, nPage + 1);

		// Set scale ratio for this page
		m_pPreviewDC->SetScaleRatio(pRatio->cx, pRatio->cy);

		CSize PrintOffset;
		VERIFY(m_pPreviewDC->Escape(GETPRINTINGOFFSET, 0, NULL, (LPVOID)&PrintOffset));
		m_pPreviewDC->PrinterDPtoScreenDP((LPPOINT)&PrintOffset);
		PrintOffset += (CSize)pRect->TopLeft();
		PrintOffset += CSize(1, 1);
		PrintOffset += (CSize)ViewportOrg;  // For Scrolling

		m_pPreviewDC->SetTopLeftOffset(PrintOffset);

		m_pPreviewDC->ClipToPage();
		m_pPrintView->OnPrint(m_pPreviewDC, m_pPreviewInfo);

		m_pPreviewDC->ReleaseOutputDC();

		m_dcPrint.RestoreDC(nSavedState);   // restore to untouched state

	}

	rectPen.DeleteObject();
	shadowPen.DeleteObject();
}

void CPreviewView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	if (m_nZoomState != ZOOM_OUT)
		CScrollView::OnHScroll(nSBCode, nPos, pScrollBar);
}

void CPreviewView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	if (m_nZoomState != ZOOM_OUT)
	{
		CScrollView::OnVScroll(nSBCode, nPos, pScrollBar);
		return;
	}

	switch (nSBCode)
	{
	case SB_BOTTOM:
		SetCurrentPage(m_pPreviewInfo->GetMaxPage(), TRUE);
		break;

	case SB_TOP:
		SetCurrentPage(m_pPreviewInfo->GetMinPage(), TRUE);
		break;

	case SB_PAGEDOWN:
		SetCurrentPage(m_nCurrentPage +
			(m_pPreviewInfo->GetMaxPage() - m_pPreviewInfo->GetMinPage() + 9) / 10, TRUE);
		break;

	case SB_PAGEUP:
		SetCurrentPage(m_nCurrentPage -
			(m_pPreviewInfo->GetMaxPage() - m_pPreviewInfo->GetMinPage() + 9) / 10, TRUE);
		break;

	case SB_LINEDOWN:
		SetCurrentPage(m_nCurrentPage + 1, TRUE);
		break;

	case SB_LINEUP:
		SetCurrentPage(m_nCurrentPage - 1, TRUE);
		break;

	case SB_THUMBPOSITION:
		SetCurrentPage(nPos, TRUE);
		break;
	}
}

void CPreviewView::OnNumPageChange()
{
	ASSERT(m_nPages == 1 || m_nPages == 2);
	m_nPages = 3 - m_nPages;    // Toggle between 1 and 2
	AfxGetApp()->m_nNumPreviewPages = m_nPages;
	m_nZoomOutPages = m_nPages;

	// Just do this to set the status correctly and invalidate
	SetCurrentPage(m_nCurrentPage, TRUE);
}

void CPreviewView::OnNextPage()
{
	SetCurrentPage(m_nCurrentPage + 1, TRUE);
}

void CPreviewView::OnPrevPage()
{
	SetCurrentPage(m_nCurrentPage - 1, TRUE);
}

void CPreviewView::OnPreviewPrint()
{
	CView* pOrigView = m_pOrigView;
	OnPreviewClose();               // force close of Preview

	// cause print (can be overridden by catching the command)
	CWnd* pMainWnd = AfxGetThread()->m_pMainWnd;
	ASSERT_VALID(pMainWnd);
	pMainWnd->SendMessage(WM_COMMAND, ID_FILE_PRINT);
}

// Finds page pointed to and convert to 1:1 screen device units
BOOL CPreviewView::FindPageRect(CPoint& point, UINT& nPage)
{
	if (m_nZoomState != ZOOM_OUT)
		point += (CSize)GetDeviceScrollPosition();

	for (nPage = 0; nPage < m_nPages; nPage++)
	{
		if (m_pPageInfo[nPage].rectScreen.PtInRect(point))
		{
			// adjust point for page position
			point -= (CSize)m_pPageInfo[nPage].rectScreen.TopLeft();

			// convert to 1:1
			point.x = MulDiv(point.x, m_pPageInfo[nPage].sizeScaleRatio.cy,
									m_pPageInfo[nPage].sizeScaleRatio.cx);
			point.y = MulDiv(point.y, m_pPageInfo[nPage].sizeScaleRatio.cy,
									m_pPageInfo[nPage].sizeScaleRatio.cx);
			return TRUE;
		}
	}
	return FALSE;
}


void CPreviewView::OnLButtonDown(UINT, CPoint point)
{
	UINT nPage;
	if (!FindPageRect(point, nPage))
		return;                         // Didn't click on a page

	// Set new zoom state
	SetZoomState((m_nZoomState == ZOOM_IN) ? ZOOM_OUT : m_nZoomState + 1,
								nPage, point);
}

void CPreviewView::SetZoomState(UINT nNewState, UINT nPage, CPoint point)
{
	if (m_nZoomState != nNewState)
	{
		m_nZoomState = nNewState;
		DoZoom(nPage, point);
	}
}

void CPreviewView::OnZoomIn()
{
	if (m_nZoomState != ZOOM_IN)
		SetZoomState(m_nZoomState + 1, 0, CPoint(0, 0));
}

void CPreviewView::OnZoomOut()
{
	if (m_nZoomState != ZOOM_OUT)
		SetZoomState(m_nZoomState - 1, 0, CPoint(0, 0));
}

// Actual zoom code.
void CPreviewView::DoZoom(UINT nPage, CPoint point)
{
	if (m_nZoomState == ZOOM_OUT)
	{
		// taking over scroll bars
		m_nPages = m_nZoomOutPages;
		ShowScrollBar(SB_HORZ, FALSE);      //hide the horizontal bar

		BOOL bShowBar = m_pPreviewInfo->GetMaxPage() < 0x8000 &&
			m_pPreviewInfo->GetMaxPage() -
			m_pPreviewInfo->GetMinPage() <= 32767U;

		ShowScrollBar(SB_VERT, bShowBar);       //Show the vertical bar

		if (bShowBar)
		{
			SetScrollRange(SB_VERT, m_pPreviewInfo->GetMinPage(),
								m_pPreviewInfo->GetMaxPage(), FALSE);

			SetScrollPos(SB_VERT, m_nCurrentPage, TRUE);
		}

		SetCurrentPage(m_nCurrentPage, TRUE);
	}
	else
	{
		m_nPages = 1;       // only one page in zoomed states

		m_pPageInfo[0].sizeZoomOutRatio = m_pPageInfo[nPage].sizeZoomOutRatio;
		m_pPageInfo[0].sizeUnscaled = m_pPageInfo[nPage].sizeUnscaled;

		// Sets the printer page
		SetCurrentPage(m_nCurrentPage + nPage, FALSE);

		SetScaledSize(0);

		CSize* pRatio = &m_pPageInfo[nPage].sizeScaleRatio;

		// convert Hit Point from screen 1:1
		point.x = MulDiv(point.x, pRatio->cx, pRatio->cy);
		point.y = MulDiv(point.y, pRatio->cx, pRatio->cy);

		// Adjust point for page position
		point += (CSize)m_pPageInfo[0].rectScreen.TopLeft();

		// Scroll to center
		CenterOnPoint(point);
	}
}

void CPreviewView::SetCurrentPage(UINT nPage, BOOL bClearRatios)
{
	m_nCurrentPage = nPage;
	if (m_nCurrentPage > m_pPreviewInfo->GetMaxPage())
		m_nCurrentPage = m_pPreviewInfo->GetMaxPage();
	if (m_nCurrentPage < m_pPreviewInfo->GetMinPage())
		m_nCurrentPage = m_pPreviewInfo->GetMinPage();


	if (m_nZoomState == ZOOM_OUT)
		SetScrollPos(SB_VERT, m_nCurrentPage);

	if (bClearRatios)
	{
		// Force Recalc of layout
		for (UINT i = 0; i < m_nMaxPages; i++)
			m_pPageInfo[i].sizeScaleRatio.cx = 0;           // zero scale ratios
	}

	Invalidate(TRUE);
}

void CPreviewView::OnDisplayPageNumber(UINT nPage, UINT nPagesDisplayed)
{
	UINT nEndPage = nPage + nPagesDisplayed - 1;

	CFrameWnd* pParent = (CFrameWnd*)AfxGetThread()->m_pMainWnd;
	ASSERT_VALID(pParent);
	ASSERT_KINDOF(CFrameWnd, pParent);

	CString s;
	if (AfxExtractSubString(s, m_pPreviewInfo->m_strPageDesc,
												nPagesDisplayed - 1))
	{
		TCHAR szBuf[80];
		wsprintf(szBuf, s, nPage, nEndPage);
		pParent->SendMessage(WM_SETMESSAGESTRING, 0, (LPARAM)(LPVOID)szBuf);
	}
	else
	{
		TRACE1("Malformed Page Description string. Could not get string %d.\n",
			nPagesDisplayed);
	}
}


void CPreviewView::OnUpdateNumPageChange(CCmdUI* pCmdUI)
{
	// set text of button to opposite of current state
	CString text;
	UINT nPages = m_nZoomState == ZOOM_OUT ? m_nPages : m_nZoomOutPages;
	VERIFY(text.LoadString(nPages == 1 ? AFX_IDS_TWOPAGE : AFX_IDS_ONEPAGE));
	pCmdUI->SetText(text);

	// enable it only if valid to display another page and not zoomed
	pCmdUI->Enable(m_nZoomState == ZOOM_OUT && m_nMaxPages != 1 &&
		(m_pPreviewInfo->GetMaxPage() > 1 || m_nPages > 1));
}

void CPreviewView::OnUpdateNextPage(CCmdUI* pCmdUI)
{
	// enable if not showing last page
	pCmdUI->Enable(m_nCurrentPage+m_nPages-1 < m_pPreviewInfo->GetMaxPage());
}

void CPreviewView::OnUpdatePrevPage(CCmdUI* pCmdUI)
{
	// enable if not showing First page
	pCmdUI->Enable(m_nCurrentPage > m_pPreviewInfo->GetMinPage());
}

void CPreviewView::OnUpdateZoomIn(CCmdUI* pCmdUI)
{
	pCmdUI->Enable(m_nZoomState != ZOOM_IN);
}

void CPreviewView::OnUpdateZoomOut(CCmdUI* pCmdUI)
{
	pCmdUI->Enable(m_nZoomState != ZOOM_OUT);
}

BOOL CPreviewView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
	if (nHitTest != HTCLIENT)
		return CScrollView::OnSetCursor(pWnd, nHitTest, message);

	CPoint point;
	::GetCursorPos(&point);
	ScreenToClient(&point);     // client coordinates of mouse position

	UINT nPage;
	if (m_nZoomState != ZOOM_IN && FindPageRect(point, nPage))
	{                       // On a page and not zoomed all the way in
		if (m_hMagnifyCursor == NULL)
		{
			HINSTANCE hInst = AfxFindResourceHandle(
				MAKEINTRESOURCE(AFX_IDC_MAGNIFY), RT_GROUP_CURSOR);
			m_hMagnifyCursor = ::LoadCursor(hInst,
				MAKEINTRESOURCE(AFX_IDC_MAGNIFY));
		}
		::SetCursor(m_hMagnifyCursor);
	}
	else
	{
		::SetCursor(::LoadCursor(NULL, IDC_ARROW));
	}
	return 0;
}

/////////////////////////////////////////////////////////////////////////////
// CPreviewView diagnostics

#ifdef _DEBUG
void CPreviewView::AssertValid() const
{
	CView::AssertValid();
	ASSERT_VALID(&m_dcPrint);
	if (m_pPreviewDC != NULL)
		ASSERT_VALID(m_pPreviewDC);

	switch (m_nZoomState)
	{
	case ZOOM_OUT:
	case ZOOM_IN:
	case ZOOM_MIDDLE:
		break;
	default:
		ASSERT(FALSE); // unknown zoom state
	}

	switch (m_nMapMode)
	{
	case MM_TEXT:
	case MM_LOMETRIC:
	case MM_HIMETRIC:
	case MM_LOENGLISH:
	case MM_HIENGLISH:
	case MM_TWIPS:
	case MM_ISOTROPIC:
	case MM_ANISOTROPIC:
		break;
	default:
		ASSERT(FALSE); // unknown mapping mode
	}
}

void CPreviewView::Dump(CDumpContext& dc) const
{
	CView::Dump(dc);

	dc << "m_pPrintView = " << m_pPrintView;
	dc << "\nm_pOrigView = " << m_pOrigView;
	dc << "\nm_bPageNumDisplayed = " << m_bPageNumDisplayed;
	dc << "\nm_bCenter = " << m_bCenter;
	dc << "\nm_nPages = " << m_nPages;
	dc << "\nm_nCurrentPage " << m_nCurrentPage;
	dc << "\nm_nSecondPageOffset " << m_nSecondPageOffset;
	dc << "\nm_nMaxPages = " << m_nMaxPages;
	dc << "\nm_sizePrinterPPI = " << m_sizePrinterPPI;
	dc << "\nm_ptCenterPoint = " << m_ptCenterPoint;
	dc << "\nm_nZoomState = ";
	switch (m_nZoomState)
	{
	case ZOOM_OUT:
		dc << "ZOOM_OUT";
		break;
	case ZOOM_IN:
		dc << "ZOOM_IN";
		break;
	case ZOOM_MIDDLE:
		dc << "ZOOM_MIDDLE";
		break;
	default:
		dc << "*unknown*";
		break;
	}
	dc << "\nm_nMapMode = ";
	switch (m_nMapMode)
	{
	case MM_TEXT:
		dc << "MM_TEXT";
		break;
	case MM_LOMETRIC:
		dc << "MM_LOMETRIC";
		break;
	case MM_HIMETRIC:
		dc << "MM_HIMETRIC";
		break;
	case MM_LOENGLISH:
		dc << "MM_LOENGLISH";
		break;
	case MM_HIENGLISH:
		dc << "MM_HIENGLISH";
		break;
	case MM_TWIPS:
		dc << "MM_TWIPS";
		break;
	case MM_ISOTROPIC:
		dc << "MM_ISOTROPIC";
		break;
	case MM_ANISOTROPIC:
		dc << "MM_ANISOTROPIC";
		break;
	default:
		dc << "*unknown*";
		break;
	}
	dc << "\nm_dcPrint = " << &m_dcPrint;
	dc << "\nm_pPreviewDC = " << m_pPreviewDC;

	dc << "\n";
}
#endif //_DEBUG

#ifdef AFX_INIT_SEG
#pragma code_seg(AFX_INIT_SEG)
#endif

IMPLEMENT_DYNCREATE(CPreviewView, CScrollView)

/////////////////////////////////////////////////////////////////////////////