/////////////////////////////////////////////////////////////////////////////
//
// This file is part of ResizableLib
// http://sourceforge.net/projects/resizablelib
//
// Copyright (C) 2000-2004 by Paolo Messina
// http://www.geocities.com/ppescher - mailto:ppescher@hotmail.com
//
// The contents of this file are subject to the Artistic License (the "License").
// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
// http://www.opensource.org/licenses/artistic-license.html
//
// If you find this code useful, credits would be nice!
//
/////////////////////////////////////////////////////////////////////////////

/*!
 *  @file
 *  @brief Interface for the CResizableLayout class.
 */

#if !defined(AFX_RESIZABLELAYOUT_H__INCLUDED_)
#define AFX_RESIZABLELAYOUT_H__INCLUDED_

#include <afxtempl.h>
#include "ResizableMsgSupport.h"
#include <map>

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

/*! @addtogroup CoreComponents
 *  @{
 */

//! @brief Special type for layout alignment
/*!
 *  Implements anchor points as a percentage of the parent window client area.
 *  Control corners are always kept at a fixed distance from their anchor points,
 *  thus allowing a control to resize proportionally as the parent window is resized.
 */
typedef struct tagANCHOR
{
    int cx; //!< horizontal component, in percent
    int cy; //!< vertical component, in percent

    tagANCHOR() {}

    tagANCHOR(int x, int y)
    {
        cx = x;
        cy = y;
    }

} ANCHOR, *PANCHOR, *LPANCHOR;

/*! @defgroup ConstAnchors Alignment Constants
 *  Define common layout alignment constants for anchor points.
 *  @{
 */
    //! Anchor to the top-left corner
    const ANCHOR TOP_LEFT(0, 0);
    //! Anchor to the top edge and center horizontally
    const ANCHOR TOP_CENTER(50, 0);
    //! Anchor to the top-right corner
    const ANCHOR TOP_RIGHT(100, 0);
    //! Anchor to the left edge and center vertically
    const ANCHOR MIDDLE_LEFT(0, 50);
    //! Anchor to the center
    const ANCHOR MIDDLE_CENTER(50, 50);
    //! Anchor to the right edge and center vertically
    const ANCHOR MIDDLE_RIGHT(100, 50);
    //! Anchor to the bottom-left corner
    const ANCHOR BOTTOM_LEFT(0, 100);
    //! Anchor to the bottom edge and center horizontally
    const ANCHOR BOTTOM_CENTER(50, 100);
    //! Anchor to the bottom-right corner
    const ANCHOR BOTTOM_RIGHT(100, 100);
// @}

//! @brief Holds a control layout settings
/*!
 *  Layout settings specify how a control must be moved and resized with respect to
 *  the parent window and how it reacts to dynamic changes to its size when painting
 *  its client area, with special care for flickering.
 */
typedef struct tagLAYOUTINFO
{
    //! Handle of the window the layout of which is being defined
    HWND hWnd;
    //! Identification number assigned to the callback slot
    UINT_PTR nCallbackID;

    //! Window class name to identify standard controls
    TCHAR sWndClass[MAX_PATH];

    //! Anchor point for the top-left corner
    ANCHOR anchorTopLeft;
    //! Fixed distance for the top-left corner
    SIZE marginTopLeft;

    //! Anchor point for the bottom-right corner
    ANCHOR anchorBottomRight;
    //! Fixed distance for the bottom-right corner
    SIZE marginBottomRight;

    //! Flag that enables support for custom windows
    BOOL bMsgSupport;
    //! Redraw settings for anti-flickering and proper painting
    RESIZEPROPERTIES properties;

    tagLAYOUTINFO() : hWnd(NULL), nCallbackID(0), bMsgSupport(FALSE)
    {
        sWndClass[0] = 0;
    }

    tagLAYOUTINFO(HWND hwnd, ANCHOR tl_type, SIZE tl_margin,
        ANCHOR br_type, SIZE br_margin)
        :
        hWnd(hwnd), nCallbackID(0), bMsgSupport(FALSE),
        anchorTopLeft(tl_type), marginTopLeft(tl_margin),
        anchorBottomRight(br_type), marginBottomRight(br_margin)
    {
        sWndClass[0] = 0;
    }

} LAYOUTINFO, *PLAYOUTINFO, *LPLAYOUTINFO;

//! @brief Layout manager implementation
/*!
 *  Derive from this class to implement resizable windows, adding the ability
 *  to dinamically resize and reposition child controls.
 *  Special care is taken to ensure a smooth animation during the resize
 *  operations performed by the users, without annoying flickering effects.
 */
class CResizableLayout
{
private:
    //@{
    //! @brief Collection of layout settings for each control
    CMap<HWND, HWND, POSITION, POSITION> m_mapLayout;
    CList<LAYOUTINFO, LAYOUTINFO&> m_listLayout;
    CList<LAYOUTINFO, LAYOUTINFO&> m_listLayoutCB;
    //@}

    std::map<HWND, std::tuple<ANCHOR, ANCHOR>> m_anchors;

    //@{
    //! @brief Used for clipping implementation
    HRGN m_hOldClipRgn;
    int m_nOldClipRgn;
    //@}

    //@{
    //! @brief Used for advanced anti-flickering
    RECT m_rectClientBefore;
    BOOL m_bNoRecursion;
    //@}

    //! @brief Apply clipping settings for the specified control
    void ClipChildWindow(const LAYOUTINFO &layout, CRgn* pRegion) const;

    //! @brief Helper function to calculate new layout
    void CalcNewChildPosition(const LAYOUTINFO &layout,
        const CRect &rectParent, CRect &rectChild, UINT& uFlags) const;

protected:
    //! @brief Override to initialize resize properties (clipping, refresh)
    virtual void InitResizeProperties(LAYOUTINFO& layout) const;

    //! @brief Override to specify clipping for unsupported windows
    virtual BOOL LikesClipping(const LAYOUTINFO &layout) const;

    //! @brief Override to specify refresh for unsupported windows
    virtual BOOL NeedsRefresh(const LAYOUTINFO &layout,
        const CRect &rectOld, const CRect &rectNew) const;

    //! @brief Clip controls in the layout out of the specified device context
    BOOL ClipChildren(CDC* pDC, BOOL bUndo);

    //! @brief Get the layout clipping region
    void GetClippingRegion(CRgn* pRegion) const;

    //! @brief Override for scrollable or expanding parent windows
    virtual void GetTotalClientRect(LPRECT lpRect) const;

    //@{
    //! @brief Add anchor points for the specified control to the layout
    void AddAnchor(HWND hWnd, ANCHOR anchorTopLeft, ANCHOR anchorBottomRight);

    void AddAnchor(HWND hWnd, ANCHOR anchorTopLeft)
    {
        AddAnchor(hWnd, anchorTopLeft, anchorTopLeft);
    }

    void AddAnchor(UINT nID, ANCHOR anchorTopLeft, ANCHOR anchorBottomRight)
    {
        AddAnchor(::GetDlgItem(GetResizableWnd()->GetSafeHwnd(), nID),
            anchorTopLeft, anchorBottomRight);
    }

    void AddAnchor(UINT nID, ANCHOR anchorTopLeft)
    {
        AddAnchor(::GetDlgItem(GetResizableWnd()->GetSafeHwnd(), nID),
            anchorTopLeft, anchorTopLeft);
    }
    //@}

    //! @brief Add a callback slot to the layout for dynamic controls or anchor points
    UINT_PTR AddAnchorCallback();

    //@{
    //! @brief Get position and size of a control in the layout from the parent's client area
    BOOL GetAnchorPosition(HWND hWnd, const CRect &rectParent,
        CRect &rectChild, UINT* lpFlags = NULL) const
    {
        POSITION pos;
        if (!m_mapLayout.Lookup(hWnd, pos))
            return FALSE;

        UINT uTmpFlags = 0;
        CalcNewChildPosition(m_listLayout.GetAt(pos), rectParent, rectChild,
            (lpFlags != NULL) ? (*lpFlags) : uTmpFlags);
        return TRUE;
    }

    BOOL GetAnchorPosition(UINT nID, const CRect &rectParent,
        CRect &rectChild, UINT* lpFlags = NULL) const
    {
        return GetAnchorPosition(::GetDlgItem(GetResizableWnd()->GetSafeHwnd(), nID),
            rectParent, rectChild, lpFlags);
    }
    //@}

    //@{
    //! @brief Get margins surrounding a control in the layout with the given size
    BOOL GetAnchorMargins(HWND hWnd, const CSize &sizeChild, CRect &rectMargins) const;

    BOOL GetAnchorMargins(UINT nID, const CSize &sizeChild, CRect &rectMargins) const
    {
        return GetAnchorMargins(::GetDlgItem(GetResizableWnd()->GetSafeHwnd(), nID),
            sizeChild, rectMargins);
    }
    //@}

    //@{
    //! @brief Remove a control from the layout
    BOOL RemoveAnchor(HWND hWnd)
    {
        POSITION pos;
        if (!m_mapLayout.Lookup(hWnd, pos))
            return FALSE;

        m_listLayout.RemoveAt(pos);
        return m_mapLayout.RemoveKey(hWnd);
    }

    BOOL RemoveAnchor(UINT nID)
    {
        return RemoveAnchor(::GetDlgItem(GetResizableWnd()->GetSafeHwnd(), nID));
    }
    //@}

    //! @brief Reset the layout content
    void RemoveAllAnchors()
    {
        m_mapLayout.RemoveAll();
        m_listLayout.RemoveAll();
        m_listLayoutCB.RemoveAll();
    }

    std::map<HWND, std::tuple<ANCHOR, ANCHOR>> GetAllAnchors() const { return m_anchors; }
    void AddAllAnchors(const std::map<HWND, std::tuple<ANCHOR, ANCHOR>>& anchors);

    //! @brief Reposition and size all the controls in the layout
    void ArrangeLayout() const;

    //! @brief Override to provide dynamic control's layout info
    virtual BOOL ArrangeLayoutCallback(LAYOUTINFO& layout) const;

    //! @brief Override to provide the parent window
    virtual CWnd* GetResizableWnd() const = 0;

    //! @brief Enhance anti-flickering
    void HandleNcCalcSize(BOOL bAfterDefault, LPNCCALCSIZE_PARAMS lpncsp, LRESULT& lResult);

    //! @brief Enable resizable style for top level parent windows
    void MakeResizable(LPCREATESTRUCT lpCreateStruct);

public:
    CResizableLayout()
    {
        m_bNoRecursion = FALSE;
        m_hOldClipRgn = ::CreateRectRgn(0,0,0,0);
        m_nOldClipRgn = 0;
    }

    virtual ~CResizableLayout()
    {
        // just for safety
        RemoveAllAnchors();

        DeleteObject(m_hOldClipRgn);
    }
};

// @}
#endif // !defined(AFX_RESIZABLELAYOUT_H__INCLUDED_)
