/////////////////////////////////////////////////////////////////////////////
//
// 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 Implementation of the CResizableLayout class.
 */

#include "stdafx.h"
#include "ResizableLayout.h"
#include "ResizableVersion.h"

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

/*!
 *  @internal Constant used to detect clipping and refresh properties
 *
 *  @note In August 2002 Platform SDK, some guy at MS thought it was time
 *        to add the missing symbol BS_TYPEMASK, but forgot its original
 *        meaning and so now he's telling us not to use that symbol because
 *        its value is likely to change in the future SDK releases, including
 *        all the BS_* style bits in the mask, not just the button's type
 *        as the symbol's name suggests.
 *     @n So now we're forced to define another symbol, great!
 */
#define _BS_TYPEMASK 0x0000000FL

/*!
 *  This function adds a new control to the layout manager and sets anchor
 *  points for its top-left and bottom-right corners.
 *
 *  @param hWnd Window handle to the control to be added
 *  @param anchorTopLeft Anchor point for the top-left corner
 *  @param anchorBottomRight Anchor point for the bottom-right corner
 *
 *  @remarks Overlapping controls, like group boxes and the controls inside,
 *           must be added from the outer controls to the inner ones, to let
 *           the clipping routines work correctly.
 *
 *  @sa AddAnchorCallback RemoveAnchor
 */
void CResizableLayout::AddAnchor(HWND hWnd, ANCHOR anchorTopLeft, ANCHOR anchorBottomRight)
{
    m_anchors[hWnd] = std::make_tuple(anchorTopLeft, anchorBottomRight);
    CWnd* pParent = GetResizableWnd();

    // child window must be valid
    ASSERT(::IsWindow(hWnd));
    // must be child of parent window
    ASSERT(::IsChild(pParent->GetSafeHwnd(), hWnd));

    // get parent window's rect
    CRect rectParent;
    GetTotalClientRect(&rectParent);
    // and child control's rect
    CRect rectChild;
    ::GetWindowRect(hWnd, &rectChild);
    ::MapWindowPoints(NULL, pParent->m_hWnd, (LPPOINT)&rectChild, 2);

    // adjust position, if client area has been scrolled
    rectChild.OffsetRect(-rectParent.TopLeft());

    // go calculate margins
    CSize marginTopLeft, marginBottomRight;

    // calculate margin for the top-left corner

    marginTopLeft.cx = rectChild.left - rectParent.Width() * anchorTopLeft.cx / 100;
    marginTopLeft.cy = rectChild.top - rectParent.Height() * anchorTopLeft.cy / 100;

    // calculate margin for the bottom-right corner

    marginBottomRight.cx = rectChild.right - rectParent.Width() * anchorBottomRight.cx / 100;
    marginBottomRight.cy = rectChild.bottom - rectParent.Height() * anchorBottomRight.cy / 100;

    // prepare the structure
    LAYOUTINFO layout(hWnd, anchorTopLeft, marginTopLeft,
        anchorBottomRight, marginBottomRight);

    // get control's window class
    GetClassName(hWnd, layout.sWndClass, MAX_PATH);

    // initialize resize properties (overridable)
    InitResizeProperties(layout);

    // must not be already there!
    // (this is probably due to a duplicate call to AddAnchor)
    POSITION pos;
    ASSERT(!m_mapLayout.Lookup(hWnd, pos));

    // add to the list and the map
    pos = m_listLayout.AddTail(layout);
    m_mapLayout.SetAt(hWnd, pos);
}

/*!
 *  This function adds a placeholder to the layout manager, that will be
 *  dinamically set by a callback function whenever required.
 *
 *  @return The return value is an integer used to distinguish between
 *          different placeholders in the callback implementation.
 *
 *  @remarks You must override @ref ArrangeLayoutCallback to provide layout
 *           information.
 *
 *  @sa AddAnchor ArrangeLayoutCallback ArrangeLayout
 */
UINT_PTR CResizableLayout::AddAnchorCallback()
{
    // one callback control cannot rely upon another callback control's
    // size and/or position (they're updated all together at the end)
    // it can however use a non-callback control, calling GetAnchorPosition()

    // add to the list
    LAYOUTINFO layout;
    layout.nCallbackID = m_listLayoutCB.GetCount() + 1;
    m_listLayoutCB.AddTail(layout);
    return layout.nCallbackID;
}

/*!
 *  This function is called for each placeholder added to the layout manager
 *  and must be overridden to provide the necessary layout information.
 *
 *  @param layout Reference to a LAYOUTINFO structure to be filled with
 *         layout information for the specified placeholder.
 *         On input, nCallbackID is the identification number
 *         returned by AddAnchorCallback. On output, anchor points and
 *         the window handle must be set and valid.
 *
 *  @return The return value is @c TRUE if the layout information has been
 *          provided successfully, @c FALSE to skip this placeholder.
 *
 *  @remarks When implementing this function, unknown placeholders should be
 *           passed to the base class. Unhandled cases will fire an assertion
 *           in the debug version.
 *
 *  @sa AddAnchorCallback ArrangeLayout LAYOUTINFO
 */
BOOL CResizableLayout::ArrangeLayoutCallback(LAYOUTINFO& layout) const
{
    UNREFERENCED_PARAMETER(layout);

    ASSERT(FALSE); // must be overridden, if callback is used

    return FALSE; // no useful output data
}

void CResizableLayout::AddAllAnchors(const std::map<HWND, std::tuple<ANCHOR, ANCHOR>>& anchors)
{
    RemoveAllAnchors();
    m_anchors.clear();
    for (const auto& anchor : anchors)
    {
        AddAnchor(anchor.first, std::get<0>(anchor.second), std::get<1>(anchor.second));
    }
}

/*!
 *  This function should be called in resizable window classes whenever the
 *  controls layout should be updated, usually after a resize operation.
 *
 *  @remarks All the controls added to the layout are moved and resized at
 *           once for performace reasons, so all the controls are in their
 *           old position when AddAnchorCallback is called.
 *           To know where a control will be placed use GetAnchorPosition.
 *
 *  @sa AddAnchor AddAnchorCallback ArrangeLayoutCallback GetAnchorPosition
 */
void CResizableLayout::ArrangeLayout() const
{
    // common vars
    UINT uFlags;
    LAYOUTINFO layout;
    CRect rectParent, rectChild;
    INT_PTR count = m_listLayout.GetCount();
    INT_PTR countCB = m_listLayoutCB.GetCount();

    if (count + countCB == 0)
        return;

    // get parent window's rect
    GetTotalClientRect(&rectParent);

    // reposition child windows
    HDWP hdwp = ::BeginDeferWindowPos (static_cast<int>(count + countCB));

    POSITION pos = m_listLayout.GetHeadPosition();
    while (pos != NULL)
    {
        // get layout info
        layout = m_listLayout.GetNext(pos);

        // calculate new child's position, size and flags for SetWindowPos
        CalcNewChildPosition(layout, rectParent, rectChild, uFlags);

        // only if size or position changed
        if ((uFlags & (SWP_NOMOVE|SWP_NOSIZE)) != (SWP_NOMOVE|SWP_NOSIZE))
        {
            hdwp = ::DeferWindowPos(hdwp, layout.hWnd, NULL, rectChild.left,
                rectChild.top, rectChild.Width(), rectChild.Height(), uFlags);
        }
    }

    // for callback items you may use GetAnchorPosition to know the
    // new position and size of a non-callback item after resizing

    pos = m_listLayoutCB.GetHeadPosition();
    while (pos != NULL)
    {
        // get layout info
        layout = m_listLayoutCB.GetNext(pos);
        // request layout data
        if (!ArrangeLayoutCallback(layout))
            continue;

        // calculate new child's position, size and flags for SetWindowPos
        CalcNewChildPosition(layout, rectParent, rectChild, uFlags);

        // only if size or position changed
        if ((uFlags & (SWP_NOMOVE|SWP_NOSIZE)) != (SWP_NOMOVE|SWP_NOSIZE))
        {
            hdwp = ::DeferWindowPos(hdwp, layout.hWnd, NULL, rectChild.left,
                rectChild.top, rectChild.Width(), rectChild.Height(), uFlags);
        }
    }

    // finally move all the windows at once
    ::EndDeferWindowPos(hdwp);
}

/*!
 *  @internal This function adds or removes a control window region
 *  to or from the specified clipping region, according to its layout
 *  properties.
 */
void CResizableLayout::ClipChildWindow(const LAYOUTINFO& layout,
                                       CRgn* pRegion) const
{
    // obtain window position
    CRect rect;
    ::GetWindowRect(layout.hWnd, &rect);
#if (_WIN32_WINNT >= 0x0501)
    //! @todo decide when to clip client only or non-client too (themes?)
    //! (leave disabled meanwhile, until I find a good solution)
    //! @note wizard97 with watermark bitmap and themes won't look good!
    // if (real_WIN32_WINNT >= 0x501)
    //  ::SendMessage(layout.hWnd, WM_NCCALCSIZE, FALSE, (LPARAM)&rect);
#endif
    ::MapWindowPoints(NULL, GetResizableWnd()->m_hWnd, (LPPOINT)&rect, 2);

    // use window region if any
    CRgn rgn;
    rgn.CreateRectRgn(0,0,0,0);
    switch (::GetWindowRgn(layout.hWnd, rgn))
    {
    case COMPLEXREGION:
    case SIMPLEREGION:
        rgn.OffsetRgn(rect.TopLeft());
        break;

    default:
        rgn.SetRectRgn(&rect);
    }

    // get the clipping property
    BOOL bClipping = layout.properties.bAskClipping ?
        LikesClipping(layout) : layout.properties.bCachedLikesClipping;

    // modify region accordingly
    if (bClipping)
        pRegion->CombineRgn(pRegion, &rgn, RGN_DIFF);
    else
        pRegion->CombineRgn(pRegion, &rgn, RGN_OR);
}

/*!
 *  This function retrieves the clipping region for the current layout.
 *  It can be used to draw directly inside the region, without applying
 *  clipping as the ClipChildren function does.
 *
 *  @param pRegion Pointer to a CRegion object that holds the
 *         calculated clipping region upon return
 *
 *  @deprecated For anti-flickering ClipChildren should be preferred
 *              as it is more complete for platform compatibility.
 *              It will probably become a private function.
 */
void CResizableLayout::GetClippingRegion(CRgn* pRegion) const
{
    CWnd* pWnd = GetResizableWnd();

    // System's default clipping area is screen's size,
    // not enough for max track size, for example:
    // if screen is 1024 x 768 and resizing border is 4 pixels,
    // maximized size is 1024+4*2=1032 x 768+4*2=776,
    // but max track size is 4 pixels bigger 1036 x 780 (don't ask me why!)
    // So, if you resize the window to maximum size, the last 4 pixels
    // are clipped out by the default clipping region, that gets created
    // as soon as you call clipping functions (my guess).

    // reset clipping region to the whole client area
    CRect rect;
    pWnd->GetClientRect(&rect);
    pRegion->CreateRectRgnIndirect(&rect);

    // clip only anchored controls
    LAYOUTINFO layout;
    POSITION pos = m_listLayout.GetHeadPosition();
    while (pos != NULL)
    {
        // get layout info
        layout = m_listLayout.GetNext(pos);

        if (::IsWindowVisible(layout.hWnd))
            ClipChildWindow(layout, pRegion);
    }
    pos = m_listLayoutCB.GetHeadPosition();
    while (pos != NULL)
    {
        // get layout info
        layout = m_listLayoutCB.GetNext(pos);
        // request data
        if (!ArrangeLayoutCallback(layout))
            continue;

        if (::IsWindowVisible(layout.hWnd))
            ClipChildWindow(layout, pRegion);
    }
//! @todo Has XP changed this??? It doesn't seem correct anymore!
/*
    // fix for RTL layouts (1 pixel of horz offset)
    if (pWnd->GetExStyle() & WS_EX_LAYOUTRTL)
        pRegion->OffsetRgn(-1,0);
*/
}

//! @internal @brief Implements GetAncestor(pWnd->GetSafeHwnd(), GA_ROOT)
inline CWnd* GetRootParentWnd(CWnd* pWnd)
{
    // GetAncestor API not present, emulate
    if (!(pWnd->GetStyle() & WS_CHILD))
        return NULL;
    while (pWnd->GetStyle() & WS_CHILD)
        pWnd = pWnd->GetParent();
    return pWnd;
}

/*!
 *  This function enables or restores clipping on the specified DC when
 *  appropriate. It should be called whenever drawing on the window client
 *  area to avoid flickering.
 *
 *  @param pDC Pointer to the target device context
 *  @param bUndo Flag that specifies wether to restore the clipping region
 *
 *  @return The return value is @c TRUE if the clipping region has been
 *          modified, @c FALSE if clipping was not necessary.
 *
 *  @remarks For anti-flickering to work, you should wrap your
 *           @c WM_ERASEBKGND message handler inside a pair of calls to
 *           this function, with the last parameter set to @c TRUE first
 *           and to @c FALSE at the end.
 */
BOOL CResizableLayout::ClipChildren(CDC* pDC, BOOL bUndo)
{
#if (_WIN32_WINNT >= 0x0501 && !defined(RSZLIB_NO_XP_DOUBLE_BUFFER))
    // clipping not necessary when double-buffering enabled
    if (real_WIN32_WINNT >= 0x0501)
    {
        CWnd *pWnd = GetRootParentWnd(GetResizableWnd());
        if (pWnd == NULL)
            pWnd = GetResizableWnd();
        if (pWnd->GetExStyle() & WS_EX_COMPOSITED)
            return FALSE;
    }
#endif

    HDC hDC = pDC->GetSafeHdc();
    HWND hWnd = GetResizableWnd()->GetSafeHwnd();

    m_nOldClipRgn = -1; // invalid region by default

    // Some controls (such as transparent toolbars and standard controls
    // with XP theme enabled) send a WM_ERASEBKGND msg to the parent
    // to draw themselves, in which case we must not enable clipping.

    // We check that the window associated with the DC is the
    // resizable window and not a child control.

    if (!bUndo && (hWnd == ::WindowFromDC(hDC)))
    {
        // save old DC clipping region
        m_nOldClipRgn = ::GetClipRgn(hDC, m_hOldClipRgn);

        // clip out supported child windows
        CRgn rgnClip;
        GetClippingRegion(&rgnClip);
        ::ExtSelectClipRgn(hDC, rgnClip, RGN_AND);

        return TRUE;
    }

    // restore old clipping region, only if modified and valid
    if (bUndo && m_nOldClipRgn >= 0)
    {
        if (m_nOldClipRgn == 1)
            ::SelectClipRgn(hDC, m_hOldClipRgn);
        else
            ::SelectClipRgn(hDC, NULL);

        return TRUE;
    }

    return FALSE;
}

/*!
 *  This function is used by this class, and should be used by derived
 *  classes too, in place of the standard GetClientRect. It can be useful
 *  for windows with scrollbars or expanding windows, to provide the true
 *  client area, including even those parts which are not visible.
 *
 *  @param lpRect Pointer to the RECT structure that holds the result
 *
 *  @remarks Override this function to provide the client area the class uses
 *           to perform layout calculations, both when adding controls and
 *           when rearranging the layout.
 *        @n The base implementation simply calls @c GetClientRect
 */
void CResizableLayout::GetTotalClientRect(LPRECT lpRect) const
{
    GetResizableWnd()->GetClientRect(lpRect);
}

/*!
 *  This function is used to determine if a control needs to be painted when
 *  it is moved or resized by the layout manager.
 *
 *  @param layout Reference to a @c LAYOUTINFO structure for the control
 *  @param rectOld Reference to a @c RECT structure that holds the control
 *         position and size before the layout update
 *  @param rectNew Reference to a @c RECT structure that holds the control
 *         position and size after the layout update
 *
 *  @return The return value is @c TRUE if the control should be freshly
 *          painted after a layout update, @c FALSE if not necessary.
 *
 *  @remarks The default implementation tries to identify windows that
 *           need refresh by their class name and window style.
 *        @n Override this function if you need a different behavior or if
 *           you have custom controls that fail to be identified.
 *
 *  @sa LikesClipping InitResizeProperties
 */
BOOL CResizableLayout::NeedsRefresh(const LAYOUTINFO& layout,
                                const CRect& rectOld, const CRect& rectNew) const
{
    if (layout.bMsgSupport)
    {
        REFRESHPROPERTY refresh;
        refresh.rcOld = rectOld;
        refresh.rcNew = rectNew;
        if (Send_NeedsRefresh(layout.hWnd, &refresh))
            return refresh.bNeedsRefresh;
    }

    int nDiffWidth = (rectNew.Width() - rectOld.Width());
    int nDiffHeight = (rectNew.Height() - rectOld.Height());

    // is the same size?
    if (nDiffWidth == 0 && nDiffHeight == 0)
        return FALSE;

    // optimistic, no need to refresh
    BOOL bRefresh = FALSE;

    // window classes that need refresh when resized
    if (0 == lstrcmp(layout.sWndClass, WC_STATIC))
    {
        DWORD style = ::GetWindowLong(layout.hWnd, GWL_STYLE);

        switch (style & SS_TYPEMASK)
        {
        case SS_LEFT:
        case SS_CENTER:
        case SS_RIGHT:
            // word-wrapped text
            bRefresh = bRefresh || (nDiffWidth != 0);
            // vertically centered text
            if (style & SS_CENTERIMAGE)
                bRefresh = bRefresh || (nDiffHeight != 0);
            break;

        case SS_LEFTNOWORDWRAP:
            // text with ellipsis
            if (style & SS_ELLIPSISMASK)
                bRefresh = bRefresh || (nDiffWidth != 0);
            // vertically centered text
            if (style & SS_CENTERIMAGE)
                bRefresh = bRefresh || (nDiffHeight != 0);
            break;

        case SS_ENHMETAFILE:
        case SS_BITMAP:
        case SS_ICON:
            // images
        case SS_BLACKFRAME:
        case SS_GRAYFRAME:
        case SS_WHITEFRAME:
        case SS_ETCHEDFRAME:
            // and frames
            bRefresh = TRUE;
            break;
        }
        return bRefresh;
    }

    // window classes that don't redraw client area correctly
    // when the hor scroll pos changes due to a resizing
    BOOL bHScroll = FALSE;
    if (0 == lstrcmp(layout.sWndClass, WC_LISTBOX))
        bHScroll = TRUE;

    // fix for horizontally scrollable windows, if wider
    if (bHScroll && (nDiffWidth > 0))
    {
        // get max scroll position
        SCROLLINFO info;
        info.cbSize = sizeof(SCROLLINFO);
        info.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
        if (::GetScrollInfo(layout.hWnd, SB_HORZ, &info))
        {
            // subtract the page size
            info.nMax -= __max(info.nPage - 1, 0);
        }

        // resizing will cause the text to scroll on the right
        // because the scrollbar is going beyond the right limit
        if ((info.nMax > 0) && (info.nPos + nDiffWidth > info.nMax))
        {
            // needs repainting, due to horiz scrolling
            bRefresh = TRUE;
        }
    }

    return bRefresh;
}

/*!
 *  This function is used to determine if a control can be safely clipped
 *  out of the parent window client area when it is repainted, usually
 *  after a resize operation.
 *
 *  @param layout Reference to a @c LAYOUTINFO structure for the control
 *
 *  @return The return value is @c TRUE if clipping is supported by the
 *          control, @c FALSE otherwise.
 *
 *  @remarks The default implementation tries to identify @a clippable
 *           windows by their class name and window style.
 *        @n Override this function if you need a different behavior or if
 *           you have custom controls that fail to be identified.
 *
 *  @sa NeedsRefresh InitResizeProperties
 */
BOOL CResizableLayout::LikesClipping(const LAYOUTINFO& layout) const
{
    if (layout.bMsgSupport)
    {
        CLIPPINGPROPERTY clipping;
        if (Send_LikesClipping(layout.hWnd, &clipping))
            return clipping.bLikesClipping;
    }

    DWORD style = ::GetWindowLong(layout.hWnd, GWL_STYLE);

    // skip windows that wants background repainted
    if (0 == lstrcmp(layout.sWndClass, WC_BUTTON))
    {
        CRect rect;
        switch (style & _BS_TYPEMASK)
        {
        case BS_GROUPBOX:
            return FALSE;

        case BS_OWNERDRAW:
            // ownerdraw buttons must return correct hittest code
            // to notify their transparency to the system and this library
            // or they could use the registered message (more reliable)
            ::GetWindowRect(layout.hWnd, &rect);
            ::SendMessage(layout.hWnd, WM_NCCALCSIZE, FALSE, (LPARAM)&rect);
            if ( HTTRANSPARENT == ::SendMessage(layout.hWnd,
                WM_NCHITTEST, 0, MAKELPARAM(rect.left, rect.top)) )
                return FALSE;
            break;
        }
        return TRUE;
    }
    else if (0 == lstrcmp(layout.sWndClass, WC_STATIC))
    {
        switch (style & SS_TYPEMASK)
        {
        case SS_LEFT:
        case SS_CENTER:
        case SS_RIGHT:
        case SS_LEFTNOWORDWRAP:
            // text
        case SS_BLACKRECT:
        case SS_GRAYRECT:
        case SS_WHITERECT:
            // filled rects
        case SS_ETCHEDHORZ:
        case SS_ETCHEDVERT:
            // etched lines
        case SS_BITMAP:
            // bitmaps
            return TRUE;
            break;

        case SS_ICON:
        case SS_ENHMETAFILE:
            if (style & SS_CENTERIMAGE)
                return FALSE;
            return TRUE;
            break;

        default:
            return FALSE;
        }
    }

    // assume the others like clipping
    return TRUE;
}


/*!
 *  @internal This function calculates the new size and position of a
 *  control in the layout and flags for @c SetWindowPos
 */
void CResizableLayout::CalcNewChildPosition(const LAYOUTINFO& layout,
                        const CRect &rectParent, CRect &rectChild, UINT& uFlags) const
{
    CWnd* pParent = GetResizableWnd();

    ::GetWindowRect(layout.hWnd, &rectChild);
    ::MapWindowPoints(NULL, pParent->m_hWnd, (LPPOINT)&rectChild, 2);

    CRect rectNew;

    // calculate new top-left corner
    rectNew.left = layout.marginTopLeft.cx + rectParent.Width() * layout.anchorTopLeft.cx / 100;
    rectNew.top = layout.marginTopLeft.cy + rectParent.Height() * layout.anchorTopLeft.cy / 100;

    // calculate new bottom-right corner
    rectNew.right = layout.marginBottomRight.cx + rectParent.Width() * layout.anchorBottomRight.cx / 100;
    rectNew.bottom = layout.marginBottomRight.cy + rectParent.Height() * layout.anchorBottomRight.cy / 100;

    // adjust position, if client area has been scrolled
    rectNew.OffsetRect(rectParent.TopLeft());

    // get the refresh property
    BOOL bRefresh = layout.properties.bAskRefresh ?
        NeedsRefresh(layout, rectChild, rectNew) : layout.properties.bCachedNeedsRefresh;

    // set flags
    uFlags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION;
    if (bRefresh)
        uFlags |= SWP_NOCOPYBITS;
    if (rectNew.TopLeft() == rectChild.TopLeft())
        uFlags |= SWP_NOMOVE;
    if (rectNew.Size() == rectChild.Size())
        uFlags |= SWP_NOSIZE;

    // update rect
    rectChild = rectNew;
}

/*!
 *  This function calculates the top, left, bottom, right margins for a
 *  given size of the specified control.
 *
 *  @param hWnd Window handle to a control in the layout
 *  @param sizeChild Size of the control to use in calculations
 *  @param rectMargins Holds the calculated margins
 *
 *  @return The return value is @c TRUE if successful, @c FALSE otherwise
 *
 *  @remarks This function can be used to infer the parent window size
 *           from the size of one of its child controls.
 *           It is used to implement cascading of size constraints.
 */
BOOL CResizableLayout::GetAnchorMargins(HWND hWnd, const CSize &sizeChild, CRect &rectMargins) const
{
    POSITION pos;
    if (!m_mapLayout.Lookup(hWnd, pos))
        return FALSE;

    const LAYOUTINFO& layout = m_listLayout.GetAt(pos);

    // augmented size, relative to anchor points
    CSize size = sizeChild + layout.marginTopLeft - layout.marginBottomRight;

    // percent of parent size occupied by this control
    CSize percent(layout.anchorBottomRight.cx - layout.anchorTopLeft.cx,
        layout.anchorBottomRight.cy - layout.anchorTopLeft.cy);

    // calculate total margins
    rectMargins.left = size.cx * layout.anchorTopLeft.cx / percent.cx + layout.marginTopLeft.cx;
    rectMargins.top = size.cy * layout.anchorTopLeft.cy / percent.cy + layout.marginTopLeft.cy;
    rectMargins.right = size.cx * (100 - layout.anchorBottomRight.cx) / percent.cx - layout.marginBottomRight.cx;
    rectMargins.bottom = size.cy * (100 - layout.anchorBottomRight.cy) / percent.cy - layout.marginBottomRight.cy;

    return TRUE;
}

/*!
 *  This function is used to set the initial resize properties of a control
 *  in the layout, that are stored in the @c properties member of the
 *  related @c LAYOUTINFO structure.
 *
 *  @param layout Reference to the @c LAYOUTINFO structure to be set
 *
 *  @remarks The various flags are used to specify whether the resize
 *           properties (clipping, refresh) can change at run-time, and a new
 *           call to the property querying functions is needed at every
 *           layout update, or they are static properties, and the cached
 *           value is used whenever necessary.
 *        @n The default implementation sends a registered message to the
 *           control, giving it the opportunity to specify its resize
 *           properties, which takes precedence if the message is supported.
 *           It then sets the @a clipping property as static, calling
 *           @c LikesClipping only once, and the @a refresh property as
 *           dynamic, causing @c NeedsRefresh to be called every time.
 *        @n This should be right for most situations, as the need for
 *           @a refresh usually depends on the size fo a control, while the
 *           support for @a clipping is usually linked to the specific type
 *           of control, which is unlikely to change at run-time, but you can
 *           still override this function if a different beahvior is needed.
 *
 *  @sa LikesClipping NeedsRefresh LAYOUTINFO RESIZEPROPERTIES
 */
void CResizableLayout::InitResizeProperties(LAYOUTINFO &layout) const
{
    // check if custom window supports this library
    // (properties must be correctly set by the window)
    layout.bMsgSupport = Send_QueryProperties(layout.hWnd, &layout.properties);

    // default properties
    if (!layout.bMsgSupport)
    {
        // clipping property is assumed as static
        layout.properties.bAskClipping = FALSE;
        layout.properties.bCachedLikesClipping = LikesClipping(layout);
        // refresh property is assumed as dynamic
        layout.properties.bAskRefresh = TRUE;
    }
}

/*!
 *  This function modifies a window to enable resizing functionality.
 *  This affects the window style, size, system menu and appearance.
 *
 *  @param lpCreateStruct Pointer to a @c CREATESTRUCT structure, usually
 *         passed by the system to the window procedure in a @c WM_CREATE
 *         or @c WM_NCCREATE
 *
 *  @remarks The function is intended to be called only inside a @c WM_CREATE
 *           or @c WM_NCCREATE message handler.
 */
void CResizableLayout::MakeResizable(LPCREATESTRUCT lpCreateStruct)
{
    if (lpCreateStruct->style & WS_CHILD)
        return;

    CWnd* pWnd = GetResizableWnd();

#if (_WIN32_WINNT >= 0x0501 && !defined(RSZLIB_NO_XP_DOUBLE_BUFFER))
    // enable double-buffering on supported platforms
    pWnd->ModifyStyleEx(0, WS_EX_COMPOSITED);
#endif

    if (!(lpCreateStruct->style & WS_THICKFRAME))
    {
        // set resizable style
        pWnd->ModifyStyle(DS_MODALFRAME, WS_THICKFRAME);
        // keep client area
        CRect rect(CPoint(lpCreateStruct->x, lpCreateStruct->y),
            CSize(lpCreateStruct->cx, lpCreateStruct->cy));
        pWnd->SendMessage(WM_NCCALCSIZE, FALSE, (LPARAM)&rect);
        // adjust size to reflect new style
        ::AdjustWindowRectEx(&rect, pWnd->GetStyle(),
            ::IsMenu(pWnd->GetMenu()->GetSafeHmenu()), pWnd->GetExStyle());
        pWnd->SetWindowPos(NULL, 0, 0, rect.Width(), rect.Height(),
            SWP_NOSENDCHANGING|SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOREPOSITION);
        // update dimensions
        lpCreateStruct->cx = rect.Width();
        lpCreateStruct->cy = rect.Height();
    }
}

/*!
 *  This function should be called inside the parent window @c WM_NCCALCSIZE
 *  message handler to help eliminate flickering.
 *
 *  @param bAfterDefault Flag that specifies wether the call is made before
 *         or after the default handler
 *  @param lpncsp Pointer to the @c NCCALCSIZE_PARAMS structure that is
 *         passed to the message handler
 *  @param lResult Reference to the result of the message handler.
 *         It contains the default handler result on input and the value to
 *         return from the window procedure on output.
 *
 *  @remarks This function fixes the annoying flickering effect that is
 *           visible when resizing the top or left edges of the window
 *           (at least on a "left to right" Windows localized version).
 */
void CResizableLayout::HandleNcCalcSize(BOOL bAfterDefault, LPNCCALCSIZE_PARAMS lpncsp, LRESULT &lResult)
{
    // prevent useless complication when size is not changing
    // prevent recursion when resetting the window region (see below)
    if ((lpncsp->lppos->flags & SWP_NOSIZE)
#if (_WIN32_WINNT >= 0x0501)
        || m_bNoRecursion
#endif
        )
        return;

    if (!bAfterDefault)
    {
        // save a copy before default handler gets called
        m_rectClientBefore = lpncsp->rgrc[2];
    }
    else // after default WM_NCCALCSIZE msg processing
    {
        if (lResult != 0)
        {
            // default handler already uses an advanced validation policy, give up
            return;
        }
        // default calculated client rect
        RECT &rectClientAfter = lpncsp->rgrc[0];

        // intersection between old and new client area is to be preserved
        // set source and destination rects to this intersection
        RECT &rectPreserve = lpncsp->rgrc[1];
        ::IntersectRect(&rectPreserve, &rectClientAfter, &m_rectClientBefore);
        lpncsp->rgrc[2] = rectPreserve;

        lResult = WVR_VALIDRECTS;

        // FIX: window region must be updated before the result of the
        //      WM_NCCALCSIZE message gets processed by the system,
        //      otherwise the old window region will clip the client
        //      area during the preservation process.
        //      This is especially evident on WinXP when the non-client
        //      area is rendered with Visual Styles enabled and the
        //      windows have a non rectangular region.
        //! @todo change rt check to only if themed frame. what about custom wnd region?
            CWnd* pWnd = GetResizableWnd();
            DWORD dwStyle = pWnd->GetStyle();
            if ((dwStyle & (WS_CAPTION|WS_MAXIMIZE)) == WS_CAPTION)
            {
                m_bNoRecursion = TRUE;
                pWnd->SetWindowRgn(NULL, FALSE);
                m_bNoRecursion = FALSE;
            }
    }
}
