#include "stdafx.h"
#include "global.h"
#include "sprite.h"

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

#ifdef _DEBUG
IMPLEMENT_DYNAMIC( CDragger, CObject )
IMPLEMENT_DYNAMIC( CMultiDragger, CDragger )
IMPLEMENT_DYNAMIC(CSprite, CDragger)
#endif

#include "memtrace.h"

extern BOOL moduleInit;

  /**********************************************************************/
  /*                      CDragger Implementation                       */
  /**********************************************************************/

/*
 * OPTIMIZATION
 *
 * At the moment, draggers get a new DC whenever they need to draw or
 * erase.  We could cut that in half easily by merging the draw/erase
 * code, and achieve even better wins by allocating a single DC for a
 * multiple selection draw/erase.
 */

CDragger::CDragger( CWnd* pWnd, CRect* pRect )
    {
    ASSERT(pWnd != NULL);

    m_pWnd  = pWnd;
    m_state = hidden;

    m_rect.SetRect(0,0,0,0);

    if (pRect != NULL)
        m_rect = *pRect;
    }

CDragger::~CDragger()
    {
    if (m_pWnd->m_hWnd != NULL && m_state != hidden)
        Hide();
    }

/* CDragger::Draw
 *
 * This is a specialized Draw to draw our drag rectangles; drag
 * rectangles are the dotted rectangles which we draw when the user is
 * dragging a tracker to move or resize a control.
 */
void CDragger::Draw()
    {
    ASSERT( m_pWnd != NULL );

    CRect rect = m_rect;

    /*
     * This gets complex -- hold on to your hat.  The m_rect is
     * measured in client coordinates of the window, but since we
     * need to use GetWindowDC rather than GetDC (to avoid having
     * the m_rect clipped by the dialog's children) we must map
     * these coordinates to window coords.  We do this by mapping
     * them into screen coordinates, computing the offset from the
     * upper left corner of the dialog's WindowRect, and mapping
     * them back.  It's the most efficient way I can think to do
     * it; other suggestions are welcome.
     */
    CRect parentRect;

    m_pWnd->GetWindowRect( &parentRect );
    m_pWnd->ClientToScreen( &rect );

    rect.OffsetRect( -parentRect.left, -parentRect.top );

    // now we've got "rect" in the coordinates of the thing we
    // want to draw on.

    int dx = (rect.right - rect.left) - 1;
    int dy = (rect.bottom - rect.top) - 1;

    CDC* dc = m_pWnd->GetWindowDC();

    ASSERT( dc != NULL );

    CBrush* oldBrush = dc->SelectObject( GetHalftoneBrush() );

    dc->PatBlt( rect.left     , rect.top       , dx, 1 , PATINVERT );
    dc->PatBlt( rect.left     , rect.bottom - 1, dx, 1 , PATINVERT );
    dc->PatBlt( rect.left     , rect.top       , 1 , dy, PATINVERT );
    dc->PatBlt( rect.right - 1, rect.top       , 1 , dy, PATINVERT );

    dc->SelectObject( oldBrush );

    m_pWnd->ReleaseDC( dc );
    }

/* CDragger::Erase
 *
 * Since the default draw uses XOR, we can just Draw again to erase!
 */
void CDragger::Erase()
    {
    Draw();
    }

/* CDragger::Show, Hide
 *
 * The "drag rectangle" is the dotted rectangle which we draw when the
 * user is moving or resizing a control by dragging it with the mouse.
 * These functions erase and draw the drag rectangle, respectively.
 */
void CDragger::Hide()
    {
    if (m_state != shown)
        return;

    m_state = hidden;
    Erase();
    }

void CDragger::Show()
    {
    if (m_state != hidden)
        return;

    m_state = shown;
    Draw();
    }


void CDragger::Obscure( BOOL bObscure )
    {
    if (bObscure)
        {
        if (m_state != shown)
            return;

        Hide();
        m_state = obscured;
        }
    else
        {
        if (m_state != obscured)
            return;

        m_state = hidden;
        Show();
        }
    }

/* CDragger::Move
 *
 * Since nearly every single occurance of "CDragger->Show" occurred in
 * the context "Hide, m_rect = foo, Show", I decided to merge this
 * functionality into a single C++ function.
 */
void CDragger::Move(const CRect& newRect, BOOL bForceShow)
    {
    if ((m_rect == newRect) && !bForceShow)
        return;

    BOOL fShow = bForceShow || m_state == shown;
    Hide();
    m_rect = newRect;

    if (fShow)
        Show();
    }

void CDragger::MoveBy(int cx, int cy, BOOL bForceShow)
    {
    CSize offset (cx, cy);
    CPoint newTopLeft = m_rect.TopLeft() + offset;
    Move(newTopLeft, bForceShow);
    }

CRect CDragger::GetRect() const
    {
    return m_rect;
    }

void CDragger::Move(const CPoint& newTopLeft, BOOL bForceShow)
    {
    Move(m_rect - m_rect.TopLeft() + newTopLeft, bForceShow);
    }

void CDragger::SetSize(const CSize& newSize, BOOL bForceShow)
    {
    CRect newRect  = m_rect;
    newRect.right  = newRect.left + newSize.cx;
    newRect.bottom = newRect.top  + newSize.cy;

    Move(newRect, bForceShow);
    }

CMultiDragger::CMultiDragger() : m_draggerList()
    {
    ASSERT( m_draggerList.IsEmpty() );
    }

CMultiDragger::CMultiDragger(CWnd *pWnd) : CDragger(pWnd), m_draggerList()
    {
    ASSERT(m_draggerList.IsEmpty());
    }


CMultiDragger::~CMultiDragger()
    {
    POSITION pos = m_draggerList.GetHeadPosition();
    while (pos != NULL)
        {
        CDragger *pDragger = (CDragger*) m_draggerList.GetNext(pos);
        delete pDragger;
        }
    }

CRect CMultiDragger::GetRect() const
    {
    // accumulate the bounding rectangle for the group
    POSITION pos = m_draggerList.GetHeadPosition();

    CRect boundRect (32767, 32767, -32767, -32767);
    while (pos != NULL)
        {
        CDragger    *pDragger = (CDragger*) m_draggerList.GetNext(pos);
        boundRect.left  = min (boundRect.left, pDragger->m_rect.left);
        boundRect.right = max (boundRect.right, pDragger->m_rect.right);
        boundRect.top   = min (boundRect.top, pDragger->m_rect.top);
        boundRect.bottom= max (boundRect.bottom, pDragger->m_rect.bottom);
        }

    return boundRect;
    }

void CMultiDragger::Hide()
    {
    // hide each dragger on the list
    POSITION pos = m_draggerList.GetHeadPosition();
    while (pos != NULL)
        {
        CDragger* pDragger = (CDragger*) m_draggerList.GetNext(pos);
        pDragger->Hide();
        }
    }

void CMultiDragger::Show()
    {
    // show each dragger on the list
    POSITION pos = m_draggerList.GetHeadPosition();
    while (pos != NULL)
        {
        CDragger* pDragger = (CDragger*) m_draggerList.GetNext(pos);
        pDragger->Show();
        }
    }

void CMultiDragger::Draw()
    {
    // draw each dragger on the list
    POSITION pos = m_draggerList.GetHeadPosition();
    while (pos != NULL)
        {
        CDragger* pDragger = (CDragger*) m_draggerList.GetNext(pos);
        pDragger->Draw();
        }
    }

void CMultiDragger::Erase()
    {
    // erase each dragger on the list
    POSITION pos = m_draggerList.GetHeadPosition();

    while (pos != NULL)
        {
        CDragger* pDragger = (CDragger*) m_draggerList.GetNext(pos);
        pDragger->Erase();
        }
    }

void CMultiDragger::Move(const CPoint& newTopLeft, BOOL bForceShow)
    {
    // move each dragger to the new top left

    // first go through the list and find the current topmost leftmost
    // point

    CPoint  topLeft (32767, 32767);
    POSITION pos = m_draggerList.GetHeadPosition();
    while (pos != NULL)
        {
        CDragger* pDragger = (CDragger*) m_draggerList.GetNext(pos);
        CRect   draggerRect = pDragger->GetRect();
        if (draggerRect.left < topLeft.x)
            topLeft.x= draggerRect.left;
        if (draggerRect.top < topLeft.y)
            topLeft.y= draggerRect.top;
        }

    // now find the offset and move each dragger
    CSize   offset = newTopLeft - topLeft;
    pos = m_draggerList.GetHeadPosition();
    while (pos != NULL)
        {
        CDragger* pDragger = (CDragger*) m_draggerList.GetNext(pos);
        pDragger->MoveBy(offset.cx, offset.cy, bForceShow);
        }
    }

void CMultiDragger::Add(CDragger *pDragger)
    {
    // add the dragger to the list
    ASSERT(pDragger != NULL);
    m_draggerList.AddTail(pDragger);
    }

void CMultiDragger::Remove(CDragger *pDragger)
    {
    // remove the dragger from the list
    ASSERT(pDragger != NULL);
    POSITION pos = m_draggerList.Find(pDragger);
    if (pos != NULL)
        m_draggerList.RemoveAt(pos);
    }


CSprite::CSprite() : m_saveBits()
    {
    m_state = hidden;
    m_pWnd = NULL;
    }


CSprite::CSprite(CWnd* pWnd, CRect* pRect)
        : CDragger(pWnd, pRect), m_saveBits()
    {
    m_state = hidden;
    m_pWnd = pWnd;
    }


CSprite::~CSprite()
    {
    if (m_pWnd->m_hWnd != NULL && m_state != hidden)
        Hide();
    }


void CSprite::Move(const CRect& newRect, BOOL bForceShow)
    {
    CRect rect = newRect;

    if ((rect == m_rect) && !bForceShow)
        return;

    STATE oldState = m_state;
    Hide();
    if (newRect.Size() != m_rect.Size())
        m_saveBits.DeleteObject();
    m_rect = rect;
    if (bForceShow || oldState == shown)
        Show();
    }

void CSprite::SaveBits()
    {
    CClientDC dcWnd(m_pWnd);
    CDC dcSave;
    CBitmap* pOldBitmap;

    dcSave.CreateCompatibleDC(&dcWnd);
    if (m_saveBits.m_hObject == NULL)
        {
        m_saveBits.CreateCompatibleBitmap(&dcWnd, m_rect.Width(),
            m_rect.Height());
        }
    pOldBitmap = dcSave.SelectObject(&m_saveBits);
    dcSave.BitBlt(0, 0, m_rect.Width(), m_rect.Height(),
        &dcWnd, m_rect.left, m_rect.top, SRCCOPY);
    dcSave.SelectObject(pOldBitmap);
    }


void CSprite::Erase()
    {
    if (m_saveBits.m_hObject == NULL)
        return;

    LONG dwStyle = ::GetWindowLong(m_pWnd->m_hWnd, GWL_STYLE);
    ::SetWindowLong(m_pWnd->m_hWnd, GWL_STYLE, dwStyle & ~WS_CLIPCHILDREN);

    CClientDC dcWnd(m_pWnd);
    CDC dcSave;
    CBitmap* pOldBitmap;

    dcSave.CreateCompatibleDC(&dcWnd);
    pOldBitmap = dcSave.SelectObject(&m_saveBits);
    dcWnd.ExcludeUpdateRgn(m_pWnd);
    dcWnd.BitBlt(m_rect.left, m_rect.top, m_rect.Width(), m_rect.Height(),
        &dcSave, 0, 0, SRCCOPY);
    dcSave.SelectObject(pOldBitmap);

    ::SetWindowLong(m_pWnd->m_hWnd, GWL_STYLE, dwStyle);
    }


CHighlight::CHighlight()
    {
    m_bdrSize = 2;
    }


CHighlight::CHighlight(CWnd *pWnd, CRect *pRect, int bdrSize)
           : CDragger(pWnd, pRect)
    {
    m_bdrSize = bdrSize;
    m_rect.InflateRect(m_bdrSize, m_bdrSize);
    }

CHighlight::~CHighlight()
    {
    if (m_pWnd->m_hWnd != NULL && m_state != hidden)
        Hide();
    }


void CHighlight::Draw()
    {
    m_pWnd->UpdateWindow();

    CClientDC   dc(m_pWnd);
    CBrush      *pOldBrush  = dc.SelectObject(GetSysBrush(COLOR_HIGHLIGHT));

    // draw the top, right, bottom and left sides
    dc.PatBlt(m_rect.left    + m_bdrSize, m_rect.top                 ,
              m_rect.Width() - m_bdrSize, m_bdrSize                  , PATCOPY);
    dc.PatBlt(m_rect.right - m_bdrSize  , m_rect.top + m_bdrSize     ,
              m_bdrSize                 , m_rect.Height() - m_bdrSize, PATCOPY);
    dc.PatBlt(m_rect.left               , m_rect.bottom - m_bdrSize  ,
              m_rect.Width() - m_bdrSize, m_bdrSize                  , PATCOPY);
    dc.PatBlt(m_rect.left               , m_rect.top                 ,
              m_bdrSize                 , m_rect.Height() - m_bdrSize, PATCOPY);

    // restore the state of the DC
    dc.SelectObject(pOldBrush);
    }

void CHighlight::Erase()
    {
    m_pWnd->InvalidateRect(&m_rect);
    m_pWnd->UpdateWindow();
    }