// zoomview.cpp : Implements Zooming functions in a CScrollView window
//
// Written by Brad Pirtle, CS:72450,1156, Internet:pirtle@qlogic.com
// Copyright 1994, QuickLogic Corp., all rights reserved.
// Version 1.0

#include "stdafx.h"
#include "zoomview.h"
#include "resource.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

BEGIN_MESSAGE_MAP(CZoomView, CScrollView)
    //{{AFX_MSG_MAP(CZoomView)
    ON_WM_LBUTTONDOWN()
    ON_WM_LBUTTONUP()
    ON_WM_MOUSEMOVE()
    ON_WM_RBUTTONDOWN()
    ON_WM_SETCURSOR()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

#define MAXZOOMIN    4        // Maximum zoom-in factor
#define PICKMARGIN  10        // Screen pixels apart for region zoom

/////////////////////////////////////////////////////////////////////////////
// CZoomView
IMPLEMENT_DYNCREATE(CZoomView, CScrollView)

/*---------------------------------------------------------------------------
   FUNCTION: CZoomView
   PURPOSE : Constructor for the CZoomView class
---------------------------------------------------------------------------*/
CZoomView::CZoomView()
        : CScrollView()
{
    // Init zoom mode to nothing
    m_zoomMode    = MODE_ZOOMOFF;
    m_bCaptured   = false;
    m_zoomScale   = 1.0;
    m_ptDragRect.SetRectEmpty();

    // Load the zoom cursor
    m_hZoomCursor = ::LoadCursor(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDC_ZOOMCURSOR));

    // Default to centering on full fit
    m_bCenter = true;
} // CZoomView

/*---------------------------------------------------------------------------
   FUNCTION: ~CZoomView
   PURPOSE : Destructor for the CZoomView class
---------------------------------------------------------------------------*/
CZoomView::~CZoomView()
{
    // Clean up the cursors if they were loaded properly
    if (m_hZoomCursor) DestroyCursor(m_hZoomCursor);
} // ~CZoomView

/////////////////////////////////////////////////////////////////////////////
// CZoomView overridden default CScrollView members
/////////////////////////////////////////////////////////////////////////////

/*---------------------------------------------------------------------------
   FUNCTION: SetZoomSizes
   PURPOSE : Set up the CZoomView class with the logical page size, and
         scrolling page/line units.
         This replaces CScrollView::SetScrollSizes.
---------------------------------------------------------------------------*/
void CZoomView::SetZoomSizes (
    SIZE sizeTotal,
    const SIZE& sizePage, // in logical units
    const SIZE& sizeLine) // in logical units
{
    // Set up the defaults
    ASSERT(sizeTotal.cx >= 0 && sizeTotal.cy >= 0);
    m_nMapMode    = MM_ANISOTROPIC; // Need for arbitrary scaling
    m_totalLog    = sizeTotal;
    // Setup default Viewport extent to be conversion of Window extent
    // into device units.
    //BLOCK for DC
    {
        CWindowDC dc(NULL);
        dc.SetMapMode(m_nMapMode);

        // total size
        m_totalDev = m_totalLog;
        dc.LPtoDP((LPPOINT)&m_totalDev);
    } // Release DC here

    // Save the original Viewport Extent
    m_origTotalDev = m_totalDev;
    // Save the original scrollbar info - for CalcBars
    m_origPageDev = sizePage;
    m_origLineDev = sizeLine;
    // Fugure out scroll bar info
    CalcBars();
    // Notify the class that the zoom scale was set
    NotifyZoom();
} // SetZoomSizes

/*---------------------------------------------------------------------------
   FUNCTION: OnPrepareDC
   PURPOSE : Override of CScrollView for MM_ANISOTROPIC zoom mode
---------------------------------------------------------------------------*/
void CZoomView::OnPrepareDC (
    CDC* pDC,
    CPrintInfo* pInfo)
{
#ifdef _DEBUG
    if (m_nMapMode != MM_ANISOTROPIC) {
        TRACE0("Error: must call SetZoomSizes() before painting zoom view\n");
        // ASSERT(false);
        return;
    }
#endif //_DEBUG

    ASSERT_VALID(pDC);
    ASSERT(m_totalLog.cx >= 0 && m_totalLog.cy >= 0);
    ASSERT(m_totalDev.cx >= 0 && m_totalDev.cx >= 0);
    // Set the Mapping mode, and the window and viewport extents
    pDC->SetMapMode(m_nMapMode);
    pDC->SetWindowExt(m_totalLog);   // in logical coordinates
    CPoint ptVpOrg;

    if (!pDC->IsPrinting()) {
        pDC->SetViewportExt(m_totalDev); // in device coordinates

        // by default shift viewport origin in negative direction of scroll
        ASSERT(pDC->GetWindowOrg() == CPoint(0,0));
        ptVpOrg = -GetDeviceScrollPosition();

        // Center full fit
        if (m_bCenter) {
            CRect rect;
            GetClientRect(&rect);

            // if client area is larger than total device size,
            // override scroll positions to place origin such that
            // output is centered in the window
            if (m_totalDev.cx < rect.Width())
                ptVpOrg.x = (rect.Width() - m_totalDev.cx) / 2;
            if (m_totalDev.cy < rect.Height())
                ptVpOrg.y = (rect.Height() - m_totalDev.cy) / 2;
        }
    } else {
        // Special case for printing
        CSize  printSize;
        printSize.cx = pDC->GetDeviceCaps(HORZRES);
        printSize.cy = pDC->GetDeviceCaps(VERTRES);
        // Maintain the original ratio, setup origin shift
        PersistRatio(m_totalLog, printSize, ptVpOrg);
        // Zoom completely out
        pDC->SetViewportExt(printSize);
    }
    // Set the new origin
    pDC->SetViewportOrg(ptVpOrg);
    // For default Printing behavior
    CView::OnPrepareDC(pDC, pInfo);
} // OnPrepareDC

/*---------------------------------------------------------------------------
   FUNCTION: CalcBars
   PURPOSE : Update the scrollbars - uses logical units
         Call when the Viewport changes size.
---------------------------------------------------------------------------*/
void CZoomView::CalcBars (void)
{
    { // BLOCK for DC
        CWindowDC dc(NULL);
        dc.SetMapMode(m_nMapMode);

        // Calculate new device units for scrollbar
        // Start with original logical units from SetScrollPos
        m_pageDev = m_origPageDev;
        dc.LPtoDP((LPPOINT)&m_pageDev);
        m_lineDev = m_origLineDev;
        dc.LPtoDP((LPPOINT)&m_lineDev);
    } // Free DC

    // Make sure of the range
    if (m_pageDev.cy < 0)  m_pageDev.cy = -m_pageDev.cy;
    if (m_lineDev.cy < 0)  m_lineDev.cy = -m_lineDev.cy;

    // If none specified - use one tenth
    ASSERT(m_totalDev.cx >= 0 && m_totalDev.cy >= 0);
    if (m_pageDev.cx == 0) m_pageDev.cx = m_totalDev.cx / 10;
    if (m_pageDev.cy == 0) m_pageDev.cy = m_totalDev.cy / 10;
    if (m_lineDev.cx == 0) m_lineDev.cx = m_pageDev.cx  / 10;
    if (m_lineDev.cy == 0) m_lineDev.cy = m_pageDev.cy  / 10;

    // Now update the scrollbars
    if (m_hWnd != NULL) {
        UpdateBars();
        Invalidate(true); // Zoom scale changed, redraw all
    }
} // CalcBars

/*---------------------------------------------------------------------------
   FUNCTION: AssertValid
   PURPOSE : Make sure valid class
---------------------------------------------------------------------------*/
#ifdef _DEBUG
void CZoomView::AssertValid() const
{
    // Bypass CScrollView because of MM_ANISOTROPIC map mode
    CView::AssertValid();
} // AssertValid
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CZoomView custom members to implement zooming functionality
/////////////////////////////////////////////////////////////////////////////

/*---------------------------------------------------------------------------
   FUNCTION: DoZoomIn
   PURPOSE : Zoom the view in on a rect
---------------------------------------------------------------------------*/
int  CZoomView::DoZoomIn (
    CRect &rect)   // rect in logical coordinates
{
    ASSERT(m_nMapMode == MM_ANISOTROPIC);

    // Make sure that the rect is normalized
    CRect zoomRect = rect;
    NormalizeRect(zoomRect);

    // Get the center of rect
    CPoint ptCenter;
    ptCenter.x = ((zoomRect.left + zoomRect.right)  / 2);
    ptCenter.y = ((zoomRect.top  + zoomRect.bottom) / 2);

    // See if the rect is small enough for a point zoom (Device coordinates)
    CRect rectDP = zoomRect;
    ViewLPtoDP((LPPOINT)&rectDP, 2);
    bool bPointZoom = (max(rectDP.Width(), rectDP.Height()) < PICKMARGIN);
    if (bPointZoom) {
        // Just do normal point zoom
        return DoZoomIn(&ptCenter);
    }

    CRect clientRect;
    GetClientRect(&clientRect);

    // Calculate the new zoom scale.
    double scaleH = (double) (clientRect.right  + 1) / (double) zoomRect.Width();
    double scaleV = (double) (clientRect.bottom + 1) / (double) zoomRect.Height();

    double zoomBackup = m_zoomScale;

    // Keep the scale Isotropic
    m_zoomScale = min(scaleH, scaleV);

    // Maximum size reached?
    if(m_zoomScale > 80)
    {
      m_zoomScale = zoomBackup;
      return true;
    }

    // Modify the Viewport extent
    m_totalDev.cx = (int) ((double) m_origTotalDev.cx * m_zoomScale);
    m_totalDev.cy = (int) ((double) m_origTotalDev.cy * m_zoomScale);
    CalcBars();

    // Set the current center point.
    CenterOnLogicalPoint(ptCenter);
    // Notify the class that a new zoom scale was done
    NotifyZoom();
    return true;
} // DoZoomIn (Rect)

/*---------------------------------------------------------------------------
   FUNCTION: DoZoomIn
   PURPOSE : Zoom the view in on a point by the specified scale factor
---------------------------------------------------------------------------*/
int  CZoomView::DoZoomIn (
    CPoint *point,   // point in logical coordinates
    double  delta)    // scale factor
{
    CPoint ptCenter;

    ASSERT(m_nMapMode == MM_ANISOTROPIC);
    // Save the current center point.
    if (!point) {
        ptCenter = GetLogicalCenterPoint();
    } else {
        ptCenter = *point;
    }

    // Increase the zoom scale.
    m_zoomScale *= delta;

    // Maximum size reached?
    if(m_zoomScale > 80)
     {
      m_zoomScale /= delta;
      return true;
     }

    // Modify the Viewport extent
    m_totalDev.cx = (int) ((double) m_origTotalDev.cx * m_zoomScale);
    m_totalDev.cy = (int) ((double) m_origTotalDev.cy * m_zoomScale);
    CalcBars();
    // Set the current center point.
    CenterOnLogicalPoint(ptCenter);
    // Notify the class that a new zoom scale was done
    NotifyZoom();
    return true;
} // DoZoomIn (Pt)

/*---------------------------------------------------------------------------
   FUNCTION: DoZoomOut
   PURPOSE : Zoom the view out on a point by one scale factor
---------------------------------------------------------------------------*/
int  CZoomView::DoZoomOut (
    CPoint *point,   // point in logical coordinates
    double  delta)    // scale factor
{
    CPoint ptCenter;

    ASSERT(m_nMapMode == MM_ANISOTROPIC);
    // Save the current center point.
    if (!point) {
        ptCenter = GetLogicalCenterPoint();
    } else {
        ptCenter = *point;
    }

    // Decrease the zoom scale.
    m_zoomScale /= delta;

    // Modify the Viewport extent
    m_totalDev.cx = (int) ((double) m_origTotalDev.cx * m_zoomScale);
    m_totalDev.cy = (int) ((double) m_origTotalDev.cy * m_zoomScale);
    CalcBars();
    // Set the current center point (logical coordinates.
    CenterOnLogicalPoint(ptCenter);
    // Notify the class that a new zoom scale was done
    NotifyZoom();
    return true;
} // DoZoomOut

/*---------------------------------------------------------------------------
   FUNCTION: DoZoomFull
   PURPOSE : Zoom the view to full state
---------------------------------------------------------------------------*/
int  CZoomView::DoZoomFull (void)
{
    ASSERT(m_nMapMode == MM_ANISOTROPIC);
    CRect  rc;
    CPoint pt;
    CSize  sizeSb;
    // Just set Viewport Extent to Client size for full fit
    GetTrueClientSize(m_totalDev, sizeSb);
    // Maintain original ratio
    PersistRatio(m_totalLog, m_totalDev, pt);
    // Set the new zoom scale (could use cx or cy)
    m_zoomScale = (double) m_totalDev.cx / m_origTotalDev.cx;
    // Remove the scrollbars
    UpdateBars();
    // Complete redraw
    Invalidate(true);
    // Notify the class that a new zoom scale was done
    NotifyZoom();
    return true;
} // DoZoomInFull

/*---------------------------------------------------------------------------
   FUNCTION: SetZoomMode
   PURPOSE : Put the view into the specified zoom mode
---------------------------------------------------------------------------*/
void CZoomView::SetZoomMode (
    ZoomMode_ zoomMode)
{
    ASSERT(m_nMapMode == MM_ANISOTROPIC);
    if (zoomMode != m_zoomMode) {
        m_zoomMode = zoomMode;
        // Force cursor change now
        OnSetCursor(NULL, HTCLIENT, 0);
    }
} // SetZoomMode


/*---------------------------------------------------------------------------
   FUNCTION: CenterOnLogicalPoint
   PURPOSE : Same as CScrollView::CenterOnPoint, but for logical coordinates
---------------------------------------------------------------------------*/
void CZoomView::CenterOnLogicalPoint(CPoint pt)
{
    // Convert the point to device coordinates
    ViewLPtoDP(&pt);
    // Account for scroll bar position
    ClientToDevice(pt);
    // Use CScrollView's function for device coordinates
    CScrollView::CenterOnPoint(pt);
} // CenterOnLogicalPoint

/*---------------------------------------------------------------------------
   FUNCTION: GetLogicalCenterPoint
   PURPOSE : Get the center of screen in logical coordinates
---------------------------------------------------------------------------*/
CPoint CZoomView::GetLogicalCenterPoint (void)  // Point in logical units
{
    CPoint pt;
    CRect rect;
    // Get the center of screen
    GetClientRect(&rect);
    pt.x = (rect.Width()  / 2);
    pt.y = (rect.Height() / 2);

    // Convert the point to logical coordinates
    ViewDPtoLP(&pt);
    return pt;
} // GetLogicalCenterPoint

/*---------------------------------------------------------------------------
   FUNCTION: DrawBox
   PURPOSE : Draw a box - XOR if want to erase
---------------------------------------------------------------------------*/
void CZoomView::DrawBox (
    CDC   &dc,
    CRect &rect,
    bool  xor)
{
    CPen pen;
    // Save the device context
    dc.SaveDC();
    if (xor) {
        dc.SetROP2(R2_NOTXORPEN);
        pen.CreatePen(PS_DASH,  0, RGB(0, 0, 0)); // 0 width = 1 device unit
    } else {
        pen.CreatePen(PS_SOLID, 0, RGB(0, 0, 0)); // 0 width = 1 device unit
    }
    dc.SelectObject(&pen);
    // Draw the rect with lines (eliminate rect middle fill)
    dc.MoveTo(rect.left,  rect.top);
    dc.LineTo(rect.right, rect.top);
    dc.LineTo(rect.right, rect.bottom);
    dc.LineTo(rect.left,  rect.bottom);
    dc.LineTo(rect.left,  rect.top);
    // Clean up
    dc.RestoreDC(-1);
} // DrawBox

/*---------------------------------------------------------------------------
   FUNCTION: DrawLine
   PURPOSE : Draw a line - XOR to erase
---------------------------------------------------------------------------*/
void CZoomView::DrawLine (
    CDC &dc,
    const int &x1, // Logical units
    const int &y1,
    const int &x2,
    const int &y2,
    bool      xor)
{
    CPen pen;
    // Save the device context
    dc.SaveDC();
    if (xor) {
        dc.SetROP2(R2_NOTXORPEN);
        pen.CreatePen(PS_DASH,  0, RGB(0, 0, 0)); // 0 width = 1 device unit
    } else {
        pen.CreatePen(PS_SOLID, 0, RGB(0, 0, 0)); // 0 width = 1 device unit
    }
    dc.SelectObject(&pen);

    // Draw the line
    dc.MoveTo(x1, y1);
    dc.LineTo(x2, y2);
    // Clean up
    dc.RestoreDC(-1);
} // DrawLine

/*---------------------------------------------------------------------------
   FUNCTION: OnLButtonDown
   PURPOSE : Handle the left mouse click
---------------------------------------------------------------------------*/
void CZoomView::OnLButtonDown(
    UINT nFlags,
    CPoint point)
{
    // Pass the message along
    CScrollView::OnLButtonDown(nFlags, point);

    switch (m_zoomMode) {
    case MODE_ZOOMIN:
        // Capture the mouse for zooming in
        m_bCaptured = true;
        SetCapture();
        // Save the mouse down point for XOR rect
        ViewDPtoLP(&point);
        m_ptDragRect.SetRect(point.x, point.y, point.x, point.y);
        // Set the cursor to the cross hair
        ::SetCursor(::LoadCursor(NULL, MAKEINTRESOURCE(IDC_CROSS)));
        break;

    default:
        // Do nothing.
        break;
    }
} // OnLButtonDown

/*---------------------------------------------------------------------------
   FUNCTION: OnMouseMove
   PURPOSE : Handle the mouse movement
---------------------------------------------------------------------------*/
void CZoomView::OnMouseMove(UINT nFlags, CPoint point)
{
    // Pass the message along
    CScrollView::OnMouseMove(nFlags, point);

    if (m_bCaptured) {
        // Get the Device Context
        CClientDC dc(this);
        OnPrepareDC(&dc);

        switch (m_zoomMode) {
        case MODE_ZOOMIN:
            // Draw the drag-rect
            // Erase last rect
            DrawBox(dc, m_ptDragRect);
            // Draw new rect
            dc.DPtoLP(&point);
            m_ptDragRect.BottomRight() = point;
            DrawBox(dc, m_ptDragRect);
            break;

        default:
            // Do nothing.
            break;
        }
    }
} // OnMouseMove

/*---------------------------------------------------------------------------
   FUNCTION: OnLButtonUp
   PURPOSE : Handle the left mouse release
---------------------------------------------------------------------------*/
void CZoomView::OnLButtonUp (
    UINT nFlags,
    CPoint point)
{
    // Pass the message along
    CScrollView::OnLButtonUp(nFlags, point);

    switch (m_zoomMode) {
    case MODE_ZOOMIN:
        // Uncapture the mouse?
        if (m_bCaptured) {
            m_bCaptured = false;
            ReleaseCapture();
            // Set back the cross cursor to the Z
            ::SetCursor(m_hZoomCursor);
            // Get the Device Context
            CClientDC dc(this);
            OnPrepareDC(&dc);
            // Erase the bounding box
            DrawBox(dc, m_ptDragRect);
            // Now Zoom in on logical rectangle
            DoZoomIn(m_ptDragRect);
        }
        break;

    case MODE_ZOOMOUT:
        ViewDPtoLP(&point);
        DoZoomOut(&point);
        break;

    default:
        // Do nothing.
        break;
    }
} // OnLButtonUp

/*---------------------------------------------------------------------------
   FUNCTION: OnRButtonDown
   PURPOSE : Handle the right mouse click - CANCELS CURRENT ZOOM MODE OR DRAG
---------------------------------------------------------------------------*/
void CZoomView::OnRButtonDown(UINT nFlags, CPoint point)
{
    CScrollView::OnRButtonDown(nFlags, point);

    // See if currently captured
    if (m_bCaptured) {
        // Maintain current mode, just stop current drag
        m_bCaptured = false;
        ReleaseCapture();
        // Get the Device Context
        CClientDC dc(this);
        OnPrepareDC(&dc);

        switch (m_zoomMode) {
        case MODE_ZOOMIN:
            // Erase last rect
            DrawBox(dc, m_ptDragRect);
            break;

        default:
            // Do nothing.
            break;
        }
    } else {
        // Cancel current mode
        m_zoomMode = MODE_ZOOMOFF;
    }
} // OnRButtonDown

/*---------------------------------------------------------------------------
   FUNCTION: OnSetCursor
   PURPOSE : Set the cursor depending on the zoom mode
---------------------------------------------------------------------------*/
BOOL CZoomView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
    if (nHitTest != HTCLIENT)
        return CScrollView::OnSetCursor(pWnd, nHitTest, message);

    switch (m_zoomMode) {
    case MODE_ZOOMOFF:
        ::SetCursor(::LoadCursor(NULL, IDC_ARROW));
        break;

    default:
        // All other zoom modes
        ::SetCursor(m_hZoomCursor);
        break;
    } // Zoom mode select

    return true;
} // OnSetCursor

/*---------------------------------------------------------------------------
   FUNCTION: ViewDPtoLP
   PURPOSE : Same as DPtoLP, but gets the Client DC for the view
---------------------------------------------------------------------------*/
void CZoomView::ViewDPtoLP (
    LPPOINT lpPoints,
    int     nCount)
{
    // Convert to logical units
    // Called from View when no DC is available
    ASSERT(m_nMapMode > 0); // must be set
    CWindowDC dc(this);
    OnPrepareDC(&dc);
    dc.DPtoLP(lpPoints, nCount);
} // ViewDPtoLP

/*---------------------------------------------------------------------------
   FUNCTION: ViewLPtoDP
   PURPOSE : Same as LPtoDP, but gets the Client DC for the view
---------------------------------------------------------------------------*/
void CZoomView::ViewLPtoDP (
    LPPOINT lpPoints,
    int     nCount)
{
    // Convert to logical units
    // Called from View when no DC is available
    ASSERT(m_nMapMode > 0); // must be set
    CWindowDC dc(this);
    OnPrepareDC(&dc);
    dc.LPtoDP(lpPoints, nCount);
} // ViewLPtoDP

/*---------------------------------------------------------------------------
   FUNCTION: ClientToDevice
   PURPOSE : Convert from Client coordinates to relative Device coordinates
---------------------------------------------------------------------------*/
void CZoomView::ClientToDevice (
    CPoint &point)
{
    // Need to account for scrollbar position
    CPoint scrollPt = GetDeviceScrollPosition();
    point.x += scrollPt.x;
    point.y += scrollPt.y;
} // ClientToDevice

/*---------------------------------------------------------------------------
   FUNCTION: NormalizeRect
   PURPOSE : Normalize the rectangle
---------------------------------------------------------------------------*/
void CZoomView::NormalizeRect (
    CRect &rect)
{
    if (rect.left > rect.right) {
        int r = rect.right;
        rect.right = rect.left;
        rect.left = r;
    }
    if (rect.top > rect.bottom) {
        int b = rect.bottom;
        rect.bottom = rect.top;
        rect.top = b;
    }
} // NormalizeRect

/*---------------------------------------------------------------------------
   FUNCTION: PersistRatio
   PURPOSE : Make a CSize maintain the given ratio (by shrinking if nescessary)
---------------------------------------------------------------------------*/
void CZoomView::PersistRatio (
    const CSize &orig,
    CSize       &dest,
    CPoint      &remainder)
{
    double ratio1 = (double) orig.cx / orig.cy;
    double ratio2 = (double) dest.cx / dest.cy;
    int   newSize;

    // Do nothing if they are the same
    if (ratio1 > ratio2) {
        // Shrink hieght
        newSize = (int)(dest.cx / ratio1);
        remainder.x = 0;
        remainder.y = dest.cy - newSize;
        dest.cy = newSize;
    } else if (ratio2 > ratio1) {
        // Shrink width
        newSize = (int)(dest.cy * ratio1);
        remainder.x = dest.cx - newSize;
        remainder.y = 0;
        dest.cx = newSize;
    }
} // PersistRatio
