// Copyright (c) 2002

// Sergey Klimov (kidd@ukr.net)
// WTL Docking windows
//
// This code is provided "as is", with absolutely no warranty expressed
// or implied. Any use is at your own risk.
//
// This code may be used in compiled form in any way you desire. This
// file may be redistributed unmodified by any means PROVIDING it is
// not sold for profit without the authors written consent, and
// providing that this notice and the authors name is included. If
// the source code in  this file is used in any commercial application
// then a simple email would be nice.

#ifndef WTL_DW_DWAUTOHIDE_H_INCLUDED_
#define WTL_DW_DWAUTOHIDE_H_INCLUDED_

#pragma once

#ifndef DF_AUTO_HIDE_FEATURES
#define DF_AUTO_HIDE_FEATURES
#endif // DF_AUTO_HIDE_FEATURES

#include <queue>
#include <deque>

#include "ssec.h"
#include "DockMisc.h"
#include "ExtDockingWindow.h"
#include "dockwins.h"

namespace dockwins
{

typedef CDockingWindowTraits < COutlookLikeCaption,
        WS_CAPTION | WS_CHILD |
        WS_CLIPCHILDREN | WS_CLIPSIBLINGS, WS_EX_TOOLWINDOW >
        COutlookLikeAutoHidePaneTraits;


template < class TAutoHidePaneTraits, class TSplitterBar, /* DWORD TDockFrameStyle=0,*/
         DWORD t_dwStyle = 0, DWORD t_dwExStyle = 0 >
struct CDockingFrameTraitsT : CWinTraits <t_dwStyle, t_dwExStyle>
{
    typedef TSplitterBar        CSplitterBar;
    typedef TAutoHidePaneTraits CAutoHidePaneTraits;
};

typedef CDockingFrameTraitsT < COutlookLikeAutoHidePaneTraits, CSimpleSplitterBar<>,
        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
        WS_EX_APPWINDOW | WS_EX_WINDOWEDGE > CDockingFrameTraits;

typedef CDockingFrameTraitsT < COutlookLikeAutoHidePaneTraits, CSimpleSplitterBarEx<>,
        WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0 > CDockingSiteTraits;

struct IPinnedLabel
{
    typedef CDockingSide    CSide;
    enum
    {
        leftBorder = 3,
        rightBorder = 3,
        labelEdge = 2,
        labelPadding = 5,          // padding between the labels
        captionPadding = 3      // padding between border of the label and the lable caption
    };

    class CPinnedWindow
    {
    public:
        CPinnedWindow()
            : m_hWnd(NULL), m_icon(0), m_width(0)
        {
        }

        int Assign(HWND hWnd, UINT width)
        {
            ATLASSERT(::IsWindow(hWnd));
            m_hWnd = hWnd;
            m_width = width;
            m_icon = reinterpret_cast<HICON>(::SendMessage(hWnd, WM_GETICON, FALSE, 0));

            if (m_icon == NULL)
                m_icon = reinterpret_cast<HICON>(::GetClassLong(hWnd, GCLP_HICONSM));

            ATL::CWindow(hWnd).GetWindowText(m_txt);
            return m_txt.GetLength();
        }
        HWND Wnd() const
        {
            return m_hWnd;
        }
        HICON Icon() const
        {
            return m_icon;
        }
        const _CSTRING_NS::CString& Text() const
        {
            return m_txt;
        }
        UINT Width() const
        {
            return m_width;
        }
        void Width(UINT width)
        {
            m_width = width;
        }
        void PrepareForDock(HDOCKBAR hBar, bool bHorizontal)
        {
            ::ShowWindow(m_hWnd, SW_HIDE);
            DWORD style = ::GetWindowLong(m_hWnd, GWL_STYLE);
            DWORD newStyle = style & (~(WS_POPUP | WS_CAPTION)) | WS_CHILD;
            ::SetWindowLong(m_hWnd, GWL_STYLE, newStyle);
            ::SetParent(m_hWnd, hBar);
            ::SendMessage(m_hWnd, WM_NCACTIVATE, TRUE, NULL);
            ::SendMessage(m_hWnd, WMDF_NDOCKSTATECHANGED,
                          MAKEWPARAM(TRUE, bHorizontal),
                          reinterpret_cast<LPARAM>(hBar));
        }
        void PrepareForUndock(HDOCKBAR    hBar)
        {
            ::ShowWindow(m_hWnd, SW_HIDE);
            DWORD style = ::GetWindowLong(m_hWnd, GWL_STYLE);
            DWORD newStyle = style & (~WS_CHILD) | WS_POPUP | WS_CAPTION;
            ::SetWindowLong(m_hWnd, GWL_STYLE, newStyle);
            ::SetParent(m_hWnd, NULL);
            ::SendMessage(m_hWnd, WMDF_NDOCKSTATECHANGED,
                          FALSE,
                          reinterpret_cast<LPARAM>(hBar));
        }
        void DrawLabel(CDC& dc, const CRect& rc, const CSide& side) const
        {
            CRect rcOutput(rc);
            rcOutput.DeflateRect(captionPadding, captionPadding);

            if (m_icon != NULL)
            {
                CDWSettings settings;
                CSize  szIcon(settings.CXMinIcon(), settings.CYMinIcon());

                if (side.IsHorizontal())
                {
                    if (rc.Width() > szIcon.cx + 2 * captionPadding)
                    {
                        POINT pt = {rcOutput.left, rc.top + (rc.Height() - szIcon.cx) / 2};
                        rcOutput.left += szIcon.cx + captionPadding;
                        dc.DrawIconEx(pt, m_icon, szIcon);
                    }
                }
                else
                {
                    if (rc.Height() > szIcon.cy + 2 * captionPadding)
                    {
                        POINT pt = {rc.left + (rc.Width() - szIcon.cy) / 2, rcOutput.top};
                        rcOutput.top += szIcon.cy + captionPadding;
                        dc.DrawIconEx(pt, m_icon, szIcon);
                    }
                }
            }

            DrawEllipsisText(dc, m_txt, m_txt.GetLength(), &rcOutput, side.IsHorizontal());
        }
    protected:
        UINT    m_width;
        HWND            m_hWnd;
        HICON            m_icon;
        _CSTRING_NS::CString    m_txt;
    };

    class CCmp
    {
    public:
        CCmp(HWND hWnd): m_hWnd(hWnd)
        {
        }
        bool operator()(const IPinnedLabel* ptr) const
        {
            return ptr->IsOwner(m_hWnd);
        }
    protected:
        HWND m_hWnd;
    };

    virtual ~IPinnedLabel() {}

    virtual IPinnedLabel* Remove(HWND hWnd, HDOCKBAR hBar) = 0;
    virtual bool UnPin(HWND hWnd, HDOCKBAR hBar, DFDOCKPOS* pHdr) = 0;

    virtual long Width() const = 0;
    virtual void Width(long width) = 0;
    virtual long DesiredWidth(CDC& dc) const = 0;

    virtual bool GetDockingPosition(DFDOCKPOS* pHdr) const = 0;

    virtual CPinnedWindow* ActivePinnedWindow() = 0;
    virtual CPinnedWindow* FromPoint(long x, bool bActivate) = 0;
    virtual bool IsOwner(HWND hWnd) const = 0;
    virtual void Draw(CDC& dc, const CRect& rc, const CSide& side) const = 0;

};

class CSinglePinnedLabel : public IPinnedLabel
{
public:
    CSinglePinnedLabel(DFPINUP* pHdr, bool bHorizontal)
        : m_width(0)
    {
        ATLASSERT((pHdr->n == 0) || (pHdr->n == 1));
        m_wnd.Assign(pHdr->hdr.hWnd, pHdr->nWidth);
        m_wnd.PrepareForDock(pHdr->hdr.hBar, bHorizontal);
    }
    CSinglePinnedLabel(const CPinnedWindow& wnd)
        : m_wnd(wnd), m_width(0)
    {
    }

    virtual IPinnedLabel* Remove(HWND hWnd, HDOCKBAR hBar)
    {
        ATLASSERT(IsOwner(hWnd));
        m_wnd.PrepareForUndock(hBar);
        return 0;
    }
    virtual bool UnPin(HWND hWnd, HDOCKBAR hBar, DFDOCKPOS* pHdr)
    {
        GetDockingPosition(pHdr);
        bool bRes = (Remove(hWnd, hBar) == 0);

        if (bRes)
        {
            pHdr->hdr.code = DC_SETDOCKPOSITION;
            ::SendMessage(pHdr->hdr.hBar, WMDF_DOCK, NULL, reinterpret_cast<LPARAM>(pHdr));
        }

        return bRes;
    }
    virtual long Width() const
    {
        return m_width;
    }

    virtual void Width(long width)
    {
        m_width = width;
    }

    virtual long DesiredWidth(CDC& dc) const
    {
        SIZE sz;
        const _CSTRING_NS::CString& text = m_wnd.Text();
        ATLVERIFY(dc.GetTextExtent(text, text.GetLength(), &sz));
        UINT width = sz.cx + 2 * captionPadding;

        if (m_wnd.Icon() != NULL)
        {
            CDWSettings settings;
            width += settings.CXMinIcon() + captionPadding;
        }

        return width;
    }

    virtual CPinnedWindow* ActivePinnedWindow()
    {
        return &m_wnd;
    }
    virtual CPinnedWindow* FromPoint(long x, bool /*bActivate*/)
    {
        ATLASSERT(x >= 0 && x < Width());
        return ActivePinnedWindow();
    }

    virtual void Draw(CDC& dc, const CRect& rc, const CSide& side) const
    {
        dc.Rectangle(&rc);
        m_wnd.DrawLabel(dc, rc, side);
    }

    virtual bool IsOwner(HWND hWnd) const
    {
        return m_wnd.Wnd() == hWnd;
    }
    virtual bool GetDockingPosition(DFDOCKPOS* pHdr) const
    {
        ATLASSERT(pHdr->hdr.hWnd == m_wnd.Wnd());
        pHdr->nBar = 0;
        pHdr->nWidth = m_wnd.Width();
        pHdr->nHeight = 1;
        pHdr->nIndex = 0;
        pHdr->fPctPos = 0;
        return true;
    }
protected:
    CPinnedWindow    m_wnd;
    long            m_width;
};

class CMultyPinnedLabel : public IPinnedLabel
{
    enum {npos = ULONG_MAX/*std::numeric_limits<UINT>::max()*/};
public:
    CMultyPinnedLabel(DFPINUP* pHdr, bool bHorizontal)
        : m_width(0)
    {
        ATLASSERT(pHdr->n > 1);
        m_n = pHdr->n;
        m_tabs = new CPinnedWindow[m_n];
        int maxLen = 0;

        for (UINT i = 0; i < m_n; i++)
        {
            int len = m_tabs[i].Assign(pHdr->phWnds[i], pHdr->nWidth);
            m_tabs[i].PrepareForDock(pHdr->hdr.hBar, bHorizontal);

            if (len > maxLen)
            {
                maxLen = len;
                m_longestTextTab = i;
            }

            if (pHdr->phWnds[i] == pHdr->hdr.hWnd)
                m_activeTab = i;
        }
    }
    ~CMultyPinnedLabel()
    {
        delete [] m_tabs;
    }

    virtual bool UnPin(HWND hWnd, HDOCKBAR hBar, DFDOCKPOS* pHdr)
    {
        ATLASSERT(pHdr->hdr.hWnd == hWnd);
        GetDockingPosition(pHdr);
        pHdr->hdr.hWnd = m_tabs[0].Wnd();
        pHdr->hdr.code = DC_SETDOCKPOSITION;
        m_tabs[0].PrepareForUndock(hBar);
        ::SendMessage(pHdr->hdr.hBar, WMDF_DOCK, NULL, reinterpret_cast<LPARAM>(pHdr));
        pHdr->hdr.hBar = pHdr->hdr.hWnd;

        for (UINT i = 1; i < m_n; i++)
        {
            pHdr->nIndex = i;
            pHdr->hdr.hWnd = m_tabs[i].Wnd();
            m_tabs[i].PrepareForUndock(hBar);
            ::SendMessage(pHdr->hdr.hBar, WMDF_DOCK, NULL, reinterpret_cast<LPARAM>(pHdr));
        }

        pHdr->hdr.code = DC_ACTIVATE;
        pHdr->hdr.hWnd = m_tabs[m_activeTab].Wnd();
        ::SendMessage(pHdr->hdr.hBar, WMDF_DOCK, NULL, reinterpret_cast<LPARAM>(&(pHdr->hdr)));
        return true;
    }
    virtual IPinnedLabel* Remove(HWND hWnd, HDOCKBAR hBar)
    {
        ATLASSERT(IsOwner(hWnd));
        IPinnedLabel* ptr = this;

        try
        {
            if (m_n == 2)
            {
                UINT i = (m_tabs[0].Wnd() != hWnd);
                ptr = new CSinglePinnedLabel(m_tabs[i]);
                m_tabs[!i].PrepareForUndock(hBar);
            }
            else
            {
                CPinnedWindow* ptr = m_tabs;
                m_tabs = new CPinnedWindow[m_n - 1];
                UINT j = 0;
                unsigned int maxLen = 0;

                for (UINT i = 0; i < m_n; i++)
                {
                    if (ptr[i].Wnd() != hWnd)
                    {
                        if (maxLen < _tcslen(ptr[i].Text()))
                            m_longestTextTab = j;

                        m_tabs[j++] = ptr[i];
                    }
                    else
                        ptr[i].PrepareForUndock(hBar);
                }

                if (m_activeTab == --m_n)
                    --m_activeTab;

                delete [] ptr;
            }
        }
        catch (std::bad_alloc& /*e*/)
        {
        }

        return ptr;
    }
    UINT Locate(HWND hWnd) const
    {
        for (UINT i = 0; i < m_n; i++)
            if (m_tabs[i].Wnd() == hWnd)
                return i;

        return (UINT)npos;
    }
    virtual bool IsOwner(HWND hWnd) const
    {
        return (Locate(hWnd) != npos);
    }

    virtual long Width() const
    {
        return m_width;
    }
    virtual void Width(long width)
    {
        m_width = width;

        if (m_width < m_passiveTabWidth * long(m_n))
        {
            if (m_width < long(m_n))
                m_passiveTabWidth = 0;
            else
                m_passiveTabWidth = static_cast<long>(m_width / m_n);
        }
    }
    virtual long DesiredWidth(CDC& dc) const
    {
        SIZE sz;
        const _CSTRING_NS::CString& text = m_tabs[m_longestTextTab].Text();
        ATLVERIFY(dc.GetTextExtent(text, text.GetLength(), &sz));
        long width = sz.cx + 2 * captionPadding;
        CDWSettings settings;
        width += settings.CXMinIcon() + captionPadding;
        m_passiveTabWidth = settings.CXMinIcon() + 2 * captionPadding;
        width += static_cast<long>(m_passiveTabWidth * (m_n - 1));
        return width;
    }

    virtual CPinnedWindow* ActivePinnedWindow()
    {
        return m_tabs + m_activeTab;
    }
    virtual CPinnedWindow* FromPoint(long x, bool bActivate)
    {
        ATLASSERT(x >= 0 && x < Width());
        UINT i = m_activeTab;

        if (x < long(m_activeTab)*m_passiveTabWidth)
            i = x / m_passiveTabWidth;
        else
        {
            long width = static_cast<long>
                (Width() - (m_n - m_activeTab - 1) * m_passiveTabWidth);

            if (width < x)
                i += (x - width) / m_passiveTabWidth + 1;
        }

        ATLASSERT(m_activeTab < m_n);

        if (bActivate)
            m_activeTab = i;

        return m_tabs + i;
    }

    void DrawPassiveTab(UINT i, CDC& dc, const CRect& rc, const CSide& side) const
    {
        CRect rcOutput(rc);
        rcOutput.DeflateRect(captionPadding, captionPadding);
        HICON icon = m_tabs[i].Icon();
        CDWSettings settings;
        CSize sz(settings.CXMinIcon(), settings.CYMinIcon());

        if (icon && (sz.cx <= (rc.Width() - 2 * captionPadding))
                && (sz.cy <= (rc.Height() - 2 * captionPadding)))
        {
            POINT pt;

            if (side.IsHorizontal())
            {
                pt.x = rcOutput.left;
                pt.y = rcOutput.top + (rcOutput.Height() - sz.cx) / 2;
            }
            else
            {
                pt.x = rcOutput.left + (rcOutput.Width() - sz.cy) / 2;
                pt.y = rcOutput.top;
            }

            dc.DrawIconEx(pt, icon, sz);
        }
        else
        {
            LPCTSTR text = m_tabs[i].Text();
            DrawEllipsisText(dc, text, -1, &rcOutput, side.IsHorizontal());
        }
    }
    void DrawActiveTab(UINT i, CDC& dc, const CRect& rc, const CSide& side) const
    {
        m_tabs[i].DrawLabel(dc, rc, side.IsHorizontal());
    }
    virtual void Draw(CDC& dc, const CRect& rc, const CSide& side) const
    {
        CRect rcOutput(rc);
        dc.Rectangle(&rcOutput);
        long* pLeft;
        long* pRight;
        long* px;
        long* py;

        if (side.IsHorizontal())
        {
            pLeft = &rcOutput.left;
            pRight = &rcOutput.right;
            px = &rcOutput.left;
            py = &rcOutput.bottom;
        }
        else
        {
            pLeft = &rcOutput.top;
            pRight = &rcOutput.bottom;
            px = &rcOutput.right;
            py = &rcOutput.top;
        }

        for (UINT i = 0; i < m_n; i++)
        {
            if (i == m_activeTab)
            {
                *pRight = static_cast<long>
                    (*pLeft + m_width - m_passiveTabWidth * (m_n - 1));
                ATLASSERT(*pRight <= (side.IsHorizontal() ? rcOutput.right : rcOutput.bottom));
                DrawActiveTab(i, dc, rcOutput, side.IsHorizontal());
            }
            else
            {
                *pRight = *pLeft + m_passiveTabWidth;
                ATLASSERT(*pRight <= (side.IsHorizontal() ? rcOutput.right : rcOutput.bottom));
                DrawPassiveTab(i, dc, rcOutput, side);
            }

            dc.MoveTo(rcOutput.left, rcOutput.top);
            dc.LineTo(*px, *py);
            *pLeft = *pRight;
        }
    }

    virtual bool GetDockingPosition(DFDOCKPOS* pHdr) const
    {
        UINT i = Locate(pHdr->hdr.hWnd);
        bool bRes = (i != npos);

        if (bRes)
        {
            if (m_activeTab == i)
                pHdr->dwDockSide |= CDockingSide::sActive;

            pHdr->nBar = 0;
            pHdr->nWidth = m_tabs[i].Width();
            pHdr->nHeight = 1;
            pHdr->nIndex = i;
            pHdr->fPctPos = 0;
        }

        return bRes;
    }
protected:
    SIZE_T    m_n;
    CPinnedWindow*    m_tabs;
    long            m_width;

    mutable  long   m_passiveTabWidth;
    UINT    m_activeTab;
    UINT    m_longestTextTab;
};

class CAutoHideBar: protected CRect
{
    typedef CRect    baseClass;
protected:
    typedef IPinnedLabel*                    CPinnedLabelPtr;
    typedef std::deque<CPinnedLabelPtr>        CBunch;
    typedef CBunch::const_iterator            const_iterator;
public:
    typedef IPinnedLabel::CSide    CSide;

    CAutoHideBar()
    {
        SetRectEmpty();
    }
    ~CAutoHideBar()
    {
        for (CBunch::iterator i = m_bunch.begin(); i != m_bunch.end(); ++i)
            delete *i;
    }
    const CRect& GetBounds() const
    {
        return *this;
    }
    bool IsPtIn(const CPoint& pt) const
    {
        return (PtInRect(pt) != FALSE);
    }
    const CSide& Orientation() const
    {
        return m_side;
    }
    bool IsBarVisible() const
    {
        return !m_bunch.empty();
    }
    bool IsHorizontal() const
    {
        return Orientation().IsHorizontal();
    }
    bool IsTop() const
    {
        return  Orientation().IsTop();
    }
    void Initialize(CSide side)
    {
        m_side = side;
    }
    bool CalculateRect(CDC& dc, CRect& rc, long width, long leftPadding, long rightPadding)
    {
        if (IsBarVisible())
        {
            CopyRect(rc);

            if (IsHorizontal())
            {
                if (IsTop())
                    rc.top = bottom = top + width;
                else
                    rc.bottom = top = bottom - width;

                left += leftPadding;
                right -= rightPadding;
            }
            else
            {
                if (IsTop())
                    rc.left = right = left + width;
                else
                    rc.right = left = right - width;

                top += leftPadding;
                bottom -= rightPadding;
            }

            UpdateLayout(dc);
        }

        return true;
    }
    void UpdateLayout(CDC& dc) const
    {
        bool bHorizontal = IsHorizontal();
        HFONT hPrevFont;
        long availableWidth;
//        long availableWidth=bHorizontal ? Width() : Height();
        CDWSettings settings;

        if (bHorizontal)
        {
            availableWidth = Width();
            hPrevFont = dc.SelectFont(settings.HSysFont());
        }
        else
        {
            availableWidth = Height();
            hPrevFont = dc.SelectFont(settings.VSysFont());
        }

        availableWidth += IPinnedLabel::labelPadding - IPinnedLabel::leftBorder - IPinnedLabel::rightBorder;
        typedef std::priority_queue<long, std::deque<long>, std::greater<long> > CQWidth;
        CQWidth widths;
        long width = 0;

        for (const_iterator i = m_bunch.begin(); i != m_bunch.end(); ++i)
        {
            int labelWidth = (*i)->DesiredWidth(dc);
            (*i)->Width(labelWidth);
            labelWidth += IPinnedLabel::labelPadding;
            widths.push(labelWidth);
            width += labelWidth;
        }

        CBunch::difference_type averageLableWidth = width;
        CBunch::size_type n = m_bunch.size();

        if (n > 0 && (width > availableWidth))
        {
            width = availableWidth;
            CBunch::difference_type itemsLeft = n;
            averageLableWidth = width / itemsLeft;
            CBunch::difference_type diffrence = width % itemsLeft;

            while (!widths.empty())
            {
                CBunch::difference_type itemWidth = widths.top();
                CBunch::difference_type diff = static_cast<CBunch::difference_type>(averageLableWidth - itemWidth);

                if (diff > 0)
                {
                    diffrence += diff;
                    --itemsLeft;
                    widths.pop();
                }
                else
                {
                    if (diffrence < itemsLeft)
                        break;

                    averageLableWidth += diffrence / itemsLeft;
                    diffrence = diffrence % itemsLeft;
                }
            }

            averageLableWidth -= IPinnedLabel::labelPadding;

            if (averageLableWidth < IPinnedLabel::labelPadding)
                averageLableWidth = 0;

            for (const_iterator i = m_bunch.begin(); i != m_bunch.end(); ++i)
            {
                CBunch::difference_type labelWidth = (*i)->Width();

                if (labelWidth > averageLableWidth)
                    labelWidth = static_cast<CBunch::difference_type>(averageLableWidth);

                (*i)->Width(static_cast<long>(labelWidth));
            }
        }

        dc.SelectFont(hPrevFont);
    }

    void Draw(CDC& dc, bool bEraseBackground = true)
    {
        if (IsBarVisible())
        {
            if (bEraseBackground)
            {
                CDWSettings settings;
                CBrush bgrBrush;
                bgrBrush.CreateSolidBrush(settings.CoolCtrlBackgroundColor());
                HBRUSH hOldBrush = dc.SelectBrush(bgrBrush);
                dc.PatBlt(left, top, Width(), Height(), PATCOPY);
                dc.SelectBrush(hOldBrush);
            }

            CDWSettings settings;
            CPen pen;
            pen.CreatePen(PS_SOLID, 1, ::GetSysColor(COLOR_BTNSHADOW));
            HPEN hOldPen = dc.SelectPen(pen);
            CPen penEraser;
            penEraser.CreatePen(PS_SOLID, 1, ::GetSysColor(COLOR_BTNFACE));
            COLORREF oldColor = dc.SetTextColor(settings.AutoHideBarTextColor());
            CBrush brush;
            brush.CreateSolidBrush(::GetSysColor(COLOR_BTNFACE));
            HBRUSH hOldBrush = dc.SelectBrush(brush);
            int oldBkMode = dc.SetBkMode(TRANSPARENT);
            HFONT hOldFont;
            long* pLeft;
            long* pRight;
            CRect rcLabel(this);
            long* xELine, *yELine;
            long tmp;

            if (IsHorizontal())
            {
                xELine = &rcLabel.right;

                if (IsTop())
                {
                    rcLabel.bottom -= IPinnedLabel::labelEdge;
                    yELine = &rcLabel.top;
                }
                else
                {
                    rcLabel.top += IPinnedLabel::labelEdge;
                    tmp = rcLabel.bottom - 1;
                    yELine = &tmp;
                }

                hOldFont = dc.SelectFont(settings.HSysFont());
                pLeft = &rcLabel.left;
                pRight = &rcLabel.right;
            }
            else
            {
                yELine = &rcLabel.bottom;

                if (IsTop())
                {
                    rcLabel.right -= IPinnedLabel::labelEdge;
                    xELine = &rcLabel.left;
                }
                else
                {
                    rcLabel.left += IPinnedLabel::labelEdge;
                    tmp = rcLabel.right - 1;
                    xELine = &tmp;
                }

                hOldFont = dc.SelectFont(settings.VSysFont());
                pLeft = &rcLabel.top;
                pRight = &rcLabel.bottom;
            }

            *pLeft += IPinnedLabel::leftBorder;
            *pRight -= IPinnedLabel::rightBorder;
            CBunch::difference_type minSize = m_bunch.size() * IPinnedLabel::labelPadding - IPinnedLabel::labelPadding;

            if (minSize < (*pRight - *pLeft))
            {
                *pRight = *pLeft + 1;
                CPoint ptELine;

                for (const_iterator i = m_bunch.begin(); i != m_bunch.end(); ++i)
                {
                    ptELine.x = *xELine;
                    ptELine.y = *yELine;
                    *pRight = *pLeft + (*i)->Width();
                    ATLASSERT(m_side.IsHorizontal() ? *pRight <= right : *pRight <= bottom);
                    (*i)->Draw(dc, rcLabel, m_side);
                    *pLeft = *pRight + IPinnedLabel::labelPadding;
                    --*pRight;
                    HPEN hPrevPen = dc.SelectPen(penEraser);
                    dc.MoveTo(ptELine);
                    dc.LineTo(*xELine, *yELine);
                    dc.SelectPen(hPrevPen);
                    *pRight = *pLeft + 1;
                    ATLASSERT(m_side.IsHorizontal()
                              ? (*pLeft >= left && (*pLeft <= right + IPinnedLabel::labelPadding))
                              : (*pLeft >= top && (*pLeft <= bottom + IPinnedLabel::labelPadding)));
                }
            }

            dc.SelectFont(hOldFont);
            dc.SelectPen(hOldPen);
            dc.SetTextColor(oldColor);
            dc.SelectBrush(hOldBrush);
            dc.SetBkMode(oldBkMode);
        }
    }

    IPinnedLabel::CPinnedWindow* MouseEnter(const CPoint& pt, bool bActivate = false) const
    {
        IPinnedLabel::CPinnedWindow* ptr = 0;

        if (IsBarVisible() && IsPtIn(pt))
        {
            int x;
            int vRight;

            if (IsHorizontal())
            {
                x = pt.x;
                vRight = left;
            }
            else
            {
                x = pt.y;
                vRight = top;
            }

            for (const_iterator i = m_bunch.begin(); i != m_bunch.end(); ++i)
            {
                UINT vLeft = vRight;
                vRight += (*i)->Width();

                if (vRight > x)
                {
                    ptr = (*i)->FromPoint(x - vLeft, bActivate);
                    break;
                }

                vRight += IPinnedLabel::labelPadding;

                if (vRight > x)
                    break;
            }
        }

        return ptr;
    }
    CPinnedLabelPtr Insert(DFPINUP* pHdr)
    {
        ATLASSERT(m_side.Side() == CSide(pHdr->dwDockSide).Side());
        CPinnedLabelPtr ptr = 0;

        try
        {
            if (pHdr->n > 1)
                ptr = new CMultyPinnedLabel(pHdr, IsHorizontal());
            else
                ptr = new CSinglePinnedLabel(pHdr, IsHorizontal());

            m_bunch.push_back(ptr);
        }
        catch (std::bad_alloc& /*e*/)
        {
        }

        return ptr;
    }
    bool Remove(HWND hWnd, HDOCKBAR hBar, DFDOCKPOS* pHdr)
    {
        CBunch::iterator i = std::find_if(m_bunch.begin(), m_bunch.end(),
                                          IPinnedLabel::CCmp(hWnd));
        bool bRes = (i != m_bunch.end());

        if (bRes)
        {
            CPinnedLabelPtr ptr = *i;

            if (pHdr == 0)
                (*i) = ptr->Remove(hWnd, hBar);
            else
            {
                pHdr->dwDockSide = m_side.Side() | CDockingSide::sSingle | CSide::sPinned;
                ptr->UnPin(hWnd, hBar, pHdr);
                (*i) = 0;
            }

            if ((*i) != ptr)
                delete ptr;

            if ((*i) == 0)
                m_bunch.erase(i);

            if (!IsBarVisible())
                SetRectEmpty();
        }

        return bRes;
    }
    bool GetDockingPosition(DFDOCKPOS* pHdr) const
    {
        CBunch::const_iterator i = std::find_if(m_bunch.begin(), m_bunch.end(),
                                                IPinnedLabel::CCmp(pHdr->hdr.hWnd));
        bool bRes = (i != m_bunch.end());

        if (bRes)
        {
            pHdr->dwDockSide = m_side.Side() | CDockingSide::sSingle | CSide::sPinned;
            (*i)->GetDockingPosition(pHdr);
            pHdr->nBar = std::distance(m_bunch.begin(), i);
        }

        return bRes;
    }
protected:
    CSide            m_side;
    CBunch            m_bunch;
};

const UINT HTSPLITTERH = HTLEFT;
const UINT HTSPLITTERV = HTTOP;

template < class T,
         class TBase,
         class TAutoHidePaneTraits >
class ATL_NO_VTABLE CAutoHidePaneImpl :
    public CWindowImpl< T, TBase, TAutoHidePaneTraits >
{
    typedef CWindowImpl< T, TBase,  TAutoHidePaneTraits >        baseClass;
    typedef CAutoHidePaneImpl< T, TBase, TAutoHidePaneTraits >    thisClass;
protected:
    typedef typename TAutoHidePaneTraits::CCaption    CCaption;
    typedef typename CAutoHideBar::CSide            CSide;
    struct  CSplitterBar : CSimpleSplitterBarEx<>
    {
        CSplitterBar(bool bHorizontal = true): CSimpleSplitterBarEx<>(bHorizontal)
        {
        }
        void CalculateRect(CRect& rc, DWORD side)
        {
            CopyRect(rc);

            switch (side)
            {
                case CSide::sTop:
                    rc.bottom = top = bottom - GetThickness();
                    break;

                case CSide::sBottom:
                    rc.top = bottom = top + GetThickness();
                    break;

                case CSide::sRight:
                    rc.left = right = left + GetThickness();
                    break;

                case CSide::sLeft:
                    rc.right = left = right - GetThickness();
                    break;
            };
        }
    };
public:
    CAutoHidePaneImpl()
    {
        m_caption.SetPinButtonState(CPinIcons::sUnPinned);
    }
protected:
    CSide Orientation() const
    {
        return m_side;
    }
    void Orientation(const CSide& side)
    {
        m_side = side;
        m_splitter.SetOrientation(IsHorizontal());
        m_caption.SetOrientation(IsHorizontal());
    }

    bool IsHorizontal() const
    {
        return m_side.IsHorizontal();
    }

    bool IsTop() const
    {
        return m_side.IsTop();
    }
public:
    void GetMinMaxInfo(LPMINMAXINFO pMinMaxInfo) const
    {
        pMinMaxInfo->ptMinTrackSize.x = 0;
        pMinMaxInfo->ptMinTrackSize.y = 0;
    }

    LRESULT NcHitTest(CPoint pt)
    {
        LRESULT lRes = HTNOWHERE;
        RECT rc;

        if (GetWindowRect(&rc))
        {
            pt.x -= rc.left;
            pt.y -= rc.top;

            if (m_splitter.PtInRect(pt))
                lRes = (IsHorizontal()) ? HTSPLITTERV : HTSPLITTERH;
            else
            {
                lRes = m_caption.HitTest(pt);

                if (lRes == HTNOWHERE
                        || lRes == HTCAPTION)
                    lRes = HTCLIENT;
            }
        }

        return lRes;
    }

    void NcMouseMove(const CPoint& /*pt*/, WPARAM nHitTest)
    {
        m_caption.HotTrack(m_hWnd, nHitTest);
    }

    BOOL NcLButtonDown(const CPoint& pt, WPARAM nHitTest)
    {
        T* pThis = static_cast<T*>(this);
        bool res = m_caption.Action(m_hWnd, pt, nHitTest);

        if (res)
        {
            CDropPointTracker tracker;
            bool drop = TrackDragAndDrop(tracker, pThis->m_hWnd);

            if (drop)
            {
                CPoint pt(tracker.DropPoint());
                drop = ClientToScreen(&pt)
                       && (unsigned int(NcHitTest(pt)) == nHitTest);
            }

            m_caption.ActionDone(m_hWnd, nHitTest, drop);

            if (drop)
            {
                switch (nHitTest)
                {
                    case HTCLOSE:
                        pThis->CloseBtnPress();
                        break;
#ifdef DF_AUTO_HIDE_FEATURES

                    case HTPIN:
                        pThis->PinBtnPress();
                        break;
#endif
                }
            }
        }

        return res;
    }

    void NcCalcSize(CRect* pRc)
    {
        m_splitter.CalculateRect(*pRc, m_side.Side());
        DWORD style = GetWindowLong(GWL_STYLE);

        if ((style & WS_CAPTION) == 0)
            m_caption.SetRectEmpty();
        else
            m_caption.CalculateRect(*pRc, true);
    }
    void NcDraw(CDC& dc)
    {
        DWORD style = GetWindowLong(GWL_STYLE);

        if ((style & WS_CAPTION) != 0)
            m_caption.Draw(m_hWnd, dc);

        DockingSide side;

        switch (m_side & 3)
        {
            case CDockingSide::sTop:
                side = DockingSide_Top;
                break;
            case CDockingSide::sBottom:
                side = DockingSide_Bottom;
                break;
            case CDockingSide::sLeft:
                side = DockingSide_Left;
                break;
            case CDockingSide::sRight:
                side = DockingSide_Right;
                break;
            default:
                ATLASSERT(FALSE);
        }

        m_splitter.Draw(dc, side);
    }

    bool CloseBtnPress()
    {
        PostMessage(WM_CLOSE);
        return false;
    }
    bool PinBtnPress()
    {
        return true;
    }
    void StartResizing(const CPoint& pt)
    {
        ATLASSERT(false);
    }
    bool OnClosing()
    {
        return false;
    }
    bool AnimateWindow(long time, bool bShow)
    {
        const int n = 10;
        CRect rc;
        GetWindowRect(&rc);
        CWindow parent(GetParent());

        if (parent.m_hWnd != NULL)
            parent.ScreenToClient(&rc);

        long* ppoint;
        long  step;
        CRect rcInvalidate(rc);
        long* pipoint;

        if (m_side.IsHorizontal())
        {
            step = rc.Height() / n;

            if (m_side.IsTop())
            {
                ppoint = &rc.bottom;
                pipoint = &rc.bottom;
                rcInvalidate.bottom = rcInvalidate.top;
            }
            else
            {
                ppoint = &rc.top;
                pipoint = &rc.top;
                rcInvalidate.top = rcInvalidate.bottom;
                step = -step;
            }
        }
        else
        {
            step = rc.Width() / n;

            if (m_side.IsTop())
            {
                ppoint = &rc.right;
                pipoint = &rc.right;
                rcInvalidate.left = rcInvalidate.right;
            }
            else
            {
                ppoint = &rc.left;
                pipoint = &rc.left;
                rcInvalidate.right = rcInvalidate.left;
                step = -step;
            }
        }

        if (!bShow)
            step = -step;
        else
        {
            parent.RedrawWindow(&rc, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
            *ppoint -= step * n;
            SetWindowPos(HWND_TOP, &rc, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
        }

        bool bRes = true;

        for (int i = 0; i < n; i++)
        {
            *ppoint += step;
            bRes = (SetWindowPos(HWND_TOP, &rc, NULL) != FALSE);

            if (!bShow)
            {
                *pipoint += step;
                parent.RedrawWindow(&rcInvalidate, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
            }
            else
            {
                CRect rcInvalidateClient(rc);
                parent.MapWindowPoints(m_hWnd, &rcInvalidateClient);
                RedrawWindow(&rcInvalidateClient, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
            }

            Sleep(time / n);
        }

        return bRes;
    }
protected:
    BEGIN_MSG_MAP(thisClass)
    MESSAGE_HANDLER(WM_GETMINMAXINFO, OnGetMinMaxInfo)
    MESSAGE_HANDLER(WM_NCCALCSIZE, OnNcCalcSize)
    MESSAGE_HANDLER(WM_NCACTIVATE, OnNcActivate)
    MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
    MESSAGE_HANDLER(WM_NCPAINT, OnNcPaint)
    MESSAGE_HANDLER(WM_NCMOUSEMOVE, OnNcMouseMove)
    MESSAGE_HANDLER(WM_SETTEXT, OnCaptionChange)
    MESSAGE_HANDLER(WM_SETICON, OnCaptionChange)
    MESSAGE_HANDLER(WM_NCLBUTTONDOWN, OnNcLButtonDown)
    MESSAGE_HANDLER(WM_NCLBUTTONDBLCLK, OnNcLButtonDblClk)
    MESSAGE_HANDLER(WM_CLOSE, OnClose)
    MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChange)
    MESSAGE_HANDLER(WM_SYSCOLORCHANGE, OnSettingChange /*OnSysColorChange*/)
    END_MSG_MAP()

    LRESULT OnGetMinMaxInfo(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
    {
        T* pThis = static_cast<T*>(this);
        LRESULT lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
        pThis->GetMinMaxInfo(reinterpret_cast<LPMINMAXINFO>(lParam));
        return lRes;
    }

    LRESULT OnNcActivate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
    {
        bHandled = IsWindowEnabled();
        return TRUE;
    }

    LRESULT OnNcCalcSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
    {
        T* pThis = static_cast<T*>(this);
        CRect* pRc = reinterpret_cast<CRect*>(lParam);
        CPoint ptTop(pRc->TopLeft());
        (*pRc) -= ptTop;
        pThis->NcCalcSize(pRc);
        (*pRc) += ptTop;
        return NULL;
    }

    LRESULT OnNcHitTest(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
    {
        return static_cast<T*>(this)->NcHitTest(CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
    }

    LRESULT OnNcPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        CWindowDC dc(m_hWnd);
        T* pThis = static_cast<T*>(this);
        pThis->NcDraw(dc);
        return NULL;
    }

    LRESULT OnNcMouseMove(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
    {
        T* pThis = static_cast<T*>(this);
        pThis->NcMouseMove(CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)), wParam);
        return 0;
    }

    LRESULT OnNcLButtonDown(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        T* pThis = static_cast<T*>(this);

        if ((wParam == HTSPLITTERH) || (wParam == HTSPLITTERV))
            pThis->StartResizing(CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)));
        else
            bHandled = pThis->NcLButtonDown(CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)), wParam);

        return 0;
    }

    LRESULT OnNcLButtonDblClk(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        return 0;
    }

//OnSetIcon
//OnSetText
    LRESULT OnCaptionChange(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
    {
//        LockWindowUpdate();
        DWORD style = ::GetWindowLong(m_hWnd, GWL_STYLE);
        ::SetWindowLong(m_hWnd, GWL_STYLE, style & (~WS_CAPTION));
        LRESULT lRes = DefWindowProc(uMsg, wParam, lParam);
        ::SetWindowLong(m_hWnd, GWL_STYLE, style);
        T* pThis = static_cast<T*>(this);
        pThis->SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
//        CWindowDC dc(m_hWnd);
//        pThis->NcDraw(dc);
//        LockWindowUpdate(FALSE);
        return lRes;
    }

    LRESULT OnClose(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM/* lParam*/, BOOL& bHandled)
    {
        T* pThis = reinterpret_cast<T*>(this);
        bHandled = pThis->OnClosing();
        return 0;
    }

    LRESULT OnSettingChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
    {
        // Note: We can depend on CDWSettings already being updated
        //  since we will always be a descendant of the main frame
        m_caption.UpdateMetrics();
        T* pThis = static_cast<T*>(this);
        pThis->SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
        bHandled = FALSE;
        return 1;
    }

//    LRESULT OnSysColorChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
//    {
//        return 1;
//    }

protected:
    CCaption        m_caption;
    CSplitterBar    m_splitter;
    CSide            m_side;
};

template<class TAutoHidePaneTraits>
class CAutoHideManager
    : public CAutoHidePaneImpl<CAutoHideManager<TAutoHidePaneTraits>, CWindow, TAutoHidePaneTraits>
{
    typedef CAutoHidePaneImpl<CAutoHideManager<TAutoHidePaneTraits>, CWindow, TAutoHidePaneTraits>    baseClass;
    typedef CAutoHideManager                                                                        thisClass;
protected:
    typedef typename CAutoHideBar::CSide        CSide;
    enum { tmID = 1, tmTimeout = 1300};
    enum { animateTimeout = 100};
    enum { hoverTimeout = 50/*HOVER_DEFAULT*/};
    enum { barsCount = 4 };

    class CSizeTrackerFull : public IDDTracker
    {
    protected:
        typedef ssec::bounds_type<long> CBounds;
    public:
        bool IsHorizontal() const
        {
            return m_side.IsHorizontal();
        }
        bool IsTop() const
        {
            return m_side.IsTop();
        }
        CSizeTrackerFull(HWND hWnd, CPoint pt, const CSide& side, long minSize, const CRect& rcBound)
            : m_wnd(hWnd), m_side(side)
        {
            m_wnd.GetWindowRect(&m_rc);
            CWindow wndParent = m_wnd.GetParent();
            wndParent.ScreenToClient(&m_rc);
            wndParent.ScreenToClient(&pt);

            if (IsHorizontal())
            {
                if (IsTop())
                    m_ppos = &m_rc.bottom;
                else
                    m_ppos = &m_rc.top;

                m_bounds.low = rcBound.top;
                m_bounds.hi = rcBound.bottom;
                m_offset = pt.y - *m_ppos;
            }
            else
            {
                if (IsTop())
                    m_ppos = &m_rc.right;
                else
                    m_ppos = &m_rc.left;

                m_bounds.low = rcBound.left;
                m_bounds.hi = rcBound.right;
                m_offset = pt.x - *m_ppos;
            }

            m_bounds.low += minSize;
            m_bounds.hi -= minSize;
        }
        void OnMove(long x, long y)
        {
            long pos = IsHorizontal() ? y : x;
            pos -= m_offset;
            pos = m_bounds.bind(pos);

            if (*m_ppos != pos)
            {
                *m_ppos = pos;
                Move();
            }
        }
        void SetPosition()
        {
            m_wnd.SetWindowPos(NULL, &m_rc, SWP_NOZORDER | SWP_NOACTIVATE);
        }
        virtual void Move()
        {
            SetPosition();
        }
        bool ProcessWindowMessage(MSG* pMsg)
        {
            return (pMsg->message == WM_TIMER);
        }

    protected:
        CWindow        m_wnd;
        CBounds        m_bounds;
        CRect        m_rc;
        const CSide    m_side;
        long*        m_ppos;
        long        m_offset;
    };
    class CSizeTrackerGhost : public CSizeTrackerFull
    {
        typedef CSimpleSplitterBarSlider<CSplitterBar> CSlider;
    public:
        CSizeTrackerGhost(HWND hWnd, CPoint pt, const CSide& side, CSplitterBar& splitter, const CRect& rcBound)
            : CSizeTrackerFull(hWnd, pt, side, splitter.GetThickness(), rcBound), m_dc(::GetWindowDC(NULL))
            , m_splitter(splitter), m_slider(splitter)
        {
            m_spOffset = m_slider - *m_ppos;
        }
        void BeginDrag()
        {
            m_splitter.DrawGhostBar(m_dc);
        }
        void EndDrag(bool bCanceled)
        {
            m_splitter.CleanGhostBar(m_dc);

            if (!bCanceled)
                SetPosition();
        }
        virtual void Move()
        {
            m_splitter.CleanGhostBar(m_dc);
            m_slider = *m_ppos + m_spOffset;
            m_splitter.DrawGhostBar(m_dc);
        }
    protected:
        CDC                m_dc;
        CSplitterBar&    m_splitter;
        CSlider            m_slider;
        long            m_spOffset;
    };
public:
    CAutoHideManager()
        : m_pActive(0)
    {
        m_side = 0;
    }
    bool Initialize(HWND hWnd)
    {
        ApplySystemSettings(hWnd);
        m_bars[CSide::sTop].Initialize(CSide(CSide::sTop));
        m_bars[CSide::sBottom].Initialize(CSide(CSide::sBottom));
        m_bars[CSide::sLeft].Initialize(CSide(CSide::sLeft));
        m_bars[CSide::sRight].Initialize(CSide(CSide::sRight));
        RECT rc = {0, 0, 0, 0};
        return (Create(hWnd, rc) != NULL);
    }
    void ApplySystemSettings(HWND hWnd)
    {
        CClientDC dc(hWnd);
        CDWSettings settings;
        HFONT hOldFont = dc.SelectFont(settings.VSysFont());
        TEXTMETRIC tm;
        dc.GetTextMetrics(&tm);
        m_barThickness = tm.tmHeight;
        dc.SelectFont(settings.HSysFont());
        dc.GetTextMetrics(&tm);

        if (m_barThickness < tm.tmHeight)
            m_barThickness = tm.tmHeight;

        dc.SelectFont(hOldFont);
        int widthIcon = settings.CXMinIcon();
        ATLASSERT(widthIcon == settings.CYMinIcon()); //if it throw let me know ;)

        if (widthIcon > m_barThickness)
            m_barThickness = widthIcon;

        m_barThickness += 2 * IPinnedLabel::captionPadding + IPinnedLabel::labelEdge;
    }

    void UpdateLayout(CDC& dc, CRect& rc)
    {
        long leftPadding = (m_bars[CSide::sLeft].IsBarVisible()) ? m_barThickness : 0;
        long rightPadding = (m_bars[CSide::sRight].IsBarVisible()) ? m_barThickness : 0;
        m_bars[CSide::sTop].CalculateRect(dc, rc, m_barThickness, leftPadding, rightPadding);
        m_bars[CSide::sBottom].CalculateRect(dc, rc, m_barThickness, leftPadding, rightPadding);
        leftPadding = 0;
        rightPadding = 0;
        m_bars[CSide::sLeft].CalculateRect(dc, rc, m_barThickness, leftPadding, rightPadding);
        m_bars[CSide::sRight].CalculateRect(dc, rc, m_barThickness, leftPadding, rightPadding);
        m_rcBound.CopyRect(&rc);

        if (m_pActive)
            FitPane();
    }
    long Width() const
    {
        long width = 0;

        if (m_bars[CSide::sLeft].IsBarVisible())
            width += m_barThickness;

        if (m_bars[CSide::sRight].IsBarVisible())
            width += m_barThickness;

        return width;
    }
    long Height() const
    {
        long height = 0;

        if (m_bars[CSide::sTop].IsBarVisible())
            height += m_barThickness;

        if (m_bars[CSide::sBottom].IsBarVisible())
            height += m_barThickness;

        return height;
    }
    bool FitPane()
    {
        CRect rc(m_rcBound);
        long spliterWidth = m_splitter.GetThickness();
        long width = m_pActive->Width() + spliterWidth;

        if (IsHorizontal())
        {
            long maxWidth = rc.Height();
            maxWidth = (maxWidth < spliterWidth) ? spliterWidth : maxWidth - spliterWidth;

            if (IsTop())
                rc.bottom = rc.top + ((maxWidth > width) ? width : maxWidth);
            else
                rc.top = rc.bottom - ((maxWidth > width) ? width : maxWidth);
        }
        else
        {
            long maxWidth = rc.Width();
            maxWidth = (maxWidth < spliterWidth) ? spliterWidth : maxWidth - spliterWidth;

            if (IsTop())
                rc.right = rc.left + ((maxWidth > width) ? width : maxWidth);
            else
                rc.left = rc.right - ((maxWidth > width) ? width : maxWidth);
        }

        return (SetWindowPos(HWND_TOP, rc, SWP_NOACTIVATE) != FALSE);
    }

    IPinnedLabel::CPinnedWindow* LocatePinnedWindow(const CPoint& pt) const
    {
        IPinnedLabel::CPinnedWindow* ptr = 0;

        for (int i = 0; i < barsCount; i++)
        {
            ptr = m_bars[i].MouseEnter(pt);

            if (ptr != 0)
                break;
        }

        return ptr;
    }
    bool IsPtIn(const CPoint& pt) const
    {
        bool bRes;

        for (int i = 0; i < barsCount; i++)
        {
            bRes = m_bars[i].IsPtIn(pt);

            if (bRes)
                break;
        }

        return bRes;
    }
    bool MouseEnter(HWND hWnd, const CPoint& pt)
    {
        IPinnedLabel::CPinnedWindow* ptr = 0;

        for (int i = 0; i < barsCount; i++)
        {
            CAutoHideBar* pbar = m_bars + i;
            ptr = pbar->MouseEnter(pt);

            if ((ptr != 0)
                    && IsVisualizationNeeded(ptr))
            {
                m_pTracked = ptr;
                TRACKMOUSEEVENT tme = { 0 };
                tme.cbSize = sizeof(tme);
                tme.hwndTrack = hWnd;
                tme.dwFlags = TME_HOVER;
                tme.dwHoverTime = hoverTimeout;
                ::_TrackMouseEvent(&tme);
                break;
            }
        }

        return (ptr != 0);
    }

    bool MouseHover(HWND hWnd, const CPoint& pt)
    {
        IPinnedLabel::CPinnedWindow* ptr = 0;

        for (int i = 0; i < barsCount; i++)
        {
            CAutoHideBar* pbar = m_bars + i;
            ptr = pbar->MouseEnter(pt, true);

            if ((ptr != 0)
                    && (ptr == m_pTracked)
                    && IsVisualizationNeeded(ptr))
            {
                CClientDC dc(hWnd);
                pbar->Draw(dc);
                Visualize(ptr, i, true);
                break;
            }
        }

        return (ptr != 0);
    }

    void Draw(CDC& dc)
    {
        EraseBackground(dc);

        for (int i = 0; i < barsCount; i++)
            m_bars[i].Draw(dc, false);
    }
    void EraseBackground(CDC& dc)
    {
        CDWSettings settings;
        CBrush bgrBrush;
        bgrBrush.CreateSolidBrush(settings.CoolCtrlBackgroundColor());
        HBRUSH hOldBrush = dc.SelectBrush(bgrBrush);
        CRect rcTop(m_bars[CSide::sTop].GetBounds());
        CRect rcBottom(m_bars[CSide::sBottom].GetBounds());

        if (m_bars[CSide::sLeft].IsBarVisible())
        {
            const CRect& rc = m_bars[CSide::sLeft].GetBounds();
            rcTop.left -= rc.Height();
            rcBottom.left -= rc.Height();
            dc.PatBlt(rc.left, rc.top, rc.Width(), rc.Height(), PATCOPY);
        }

        if (m_bars[CSide::sRight].IsBarVisible())
        {
            const CRect& rc = m_bars[CSide::sRight].GetBounds();
            rcTop.right += rc.Height();
            rcBottom.right += rc.Height();
            dc.PatBlt(rc.left, rc.top, rc.Width(), rc.Height(), PATCOPY);
        }

        if (m_bars[CSide::sTop].IsBarVisible())
            dc.PatBlt(rcTop.left, rcTop.top, rcTop.Width(), rcTop.Height(), PATCOPY);

        if (m_bars[CSide::sBottom].IsBarVisible())
            dc.PatBlt(rcBottom.left, rcBottom.top, rcBottom.Width(), rcBottom.Height(), PATCOPY);

        dc.SelectBrush(hOldBrush);
    }

    bool PinUp(HWND hWnd, DFPINUP* pHdr, bool& bUpdate)
    {
        pHdr->hdr.hBar = m_hWnd;
        CSide side(pHdr->dwDockSide);
        ATLASSERT(side.IsValid());
        CAutoHideBar* pbar = m_bars + side.Side();
        bUpdate = !pbar->IsBarVisible();
        IPinnedLabel* pLabel = pbar->Insert(pHdr);
        bool bRes = (pLabel != 0);

        CWindow(hWnd).GetTopLevelParent().SetActiveWindow();

        if (bRes && ((pHdr->dwFlags & DFPU_VISUALIZE) != 0))
            Visualize(pLabel->ActivePinnedWindow(), side);

        if (!bUpdate)
        {
            CClientDC dc(hWnd);
            pbar->UpdateLayout(dc);
            pbar->Draw(dc);
        }

        return bRes;
    }

    bool Remove(HWND hWnd, bool bUnpin = false)
    {
        if (m_pActive != 0 && (m_pActive->Wnd() == hWnd))
            Vanish();

        HDOCKBAR hBar = GetParent();
        ATLASSERT(::IsWindow(hBar));
        DFDOCKPOS* pHdr = 0;
        DFDOCKPOS dockHdr;

        if (bUnpin)
        {
//            dockHdr.hdr.code=DC_SETDOCKPOSITION;
            dockHdr.hdr.hWnd = hWnd;
            dockHdr.hdr.hBar = hBar;
            pHdr = &dockHdr;
        }

        bool bRes = false;

        for (int i = 0; i < barsCount; i++)
        {
            CAutoHideBar* pbar = m_bars + i;
            bRes = pbar->Remove(hWnd, m_hWnd, pHdr);

            if (bRes)
            {
                if (pbar->IsBarVisible())
                {
                    CClientDC dc(hBar);
                    pbar->UpdateLayout(dc);
                    pbar->Draw(dc);
                }
                else
                {
                    ::SendMessage(hBar, WM_SIZE, 0, 0);
                    ::RedrawWindow(hBar, NULL, NULL,
                                   RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE);
                }

                break;
            }
        }

        CWindow(hWnd).GetTopLevelParent().SetActiveWindow();

        return bRes;
    }

    bool GetDockingPosition(DFDOCKPOS* pHdr) const
    {
        bool bRes = false;

        for (int i = 0; i < barsCount; i++)
        {
            bRes = m_bars[i].GetDockingPosition(pHdr);

            if (bRes)
                break;
        }

        return bRes;
    }
///////////////////////////////////////////////////////////
    bool IsVisualizationNeeded(const IPinnedLabel::CPinnedWindow* ptr) const
    {
        return (ptr != m_pActive);
    }

    bool Visualize(IPinnedLabel::CPinnedWindow* ptr, const CSide& side, bool bAnimate = false)
    {
        ATLASSERT(ptr);
        ATLASSERT(IsVisualizationNeeded(ptr));
        Vanish();
        Orientation(side);
        ATLASSERT(m_pActive == 0);
        m_pActive = ptr;
        bool bRes = (::SetWindowPos(m_pActive->Wnd(), HWND_TOP, 0, 0, 0, 0,
                                    SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW) != FALSE);

        if (bRes)
        {
            bRes = FitPane();

            if (bRes)
            {
                SetWindowText(m_pActive->Text());
                CDWSettings setting;

                if (bAnimate && setting.IsAnimationEnabled())
                    AnimateWindow(animateTimeout, true);
                else
                    bRes = (SetWindowPos(HWND_TOP, 0, 0, 0, 0,
                                         SWP_NOMOVE | SWP_NOSIZE |
                                         SWP_SHOWWINDOW | SWP_FRAMECHANGED) != FALSE);

                if (bRes)
                {
                    BOOL dummy;
                    OnSize(0, 0, 0, dummy);
                    bRes = (SetTimer(tmID, tmTimeout) == tmID);
                }
            }
        }

        ATLASSERT(bRes);
        return bRes;
    }

    bool Vanish(bool bAnimate = false)
    {
        bool bRes = (m_pActive == 0);

        if (!bRes)
        {
            KillTimer(tmID);
            ::ShowWindow(m_pActive->Wnd(), SW_HIDE);
            m_pActive = 0;
            CDWSettings setting;

            if (bAnimate && setting.IsAnimationEnabled())
                AnimateWindow(animateTimeout, false);

            bRes = ShowWindow(SW_HIDE) != FALSE;
        }

        return bRes;
    }
///////////////////////////////////////////////////////////
    void StartResizing(const CPoint& pt)
    {
        std::auto_ptr<CSizeTrackerFull> pTracker;
        CDWSettings settings;

        if (settings.GhostDrag())
        {
            CRect rc;
            GetWindowRect(&rc);
            CSplitterBar splitter(IsHorizontal());
            splitter.CalculateRect(rc, m_side.Side());
            pTracker = std::auto_ptr<CSizeTrackerFull>(
                           new CSizeTrackerGhost(m_hWnd, pt, Orientation(), splitter, m_rcBound));
        }
        else
            pTracker = std::auto_ptr<CSizeTrackerFull>(
                           new CSizeTrackerFull(m_hWnd, pt, Orientation(), m_splitter.GetThickness(), m_rcBound));

        HWND hWndParent = GetParent();
        ATLASSERT(hWndParent);
        TrackDragAndDrop(*pTracker, hWndParent);
    }

    bool PinBtnPress()
    {
        ATLASSERT(m_pActive);
        return Remove(m_pActive->Wnd(), true);
    }

    bool OnClosing()
    {
        ATLASSERT(m_pActive);
        ::PostMessage(m_pActive->Wnd(), WM_CLOSE, NULL, NULL);
        return true;
    }

    DECLARE_WND_CLASS_EX(_T("CAutoHideManager"), 0, COLOR_WINDOW)
protected:
    BEGIN_MSG_MAP(thisClass)
    MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
    MESSAGE_HANDLER(WM_TIMER, OnTimer)
    MESSAGE_HANDLER(WM_SIZE, OnSize)
/////////////////
    MESSAGE_HANDLER(WMDF_DOCK, OnDock)
    CHAIN_MSG_MAP(baseClass)
    END_MSG_MAP()

    LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
    {
        bHandled = false;
        Vanish(false);
        return 0;
    }

    LRESULT OnTimer(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        POINT pt;
        CRect rc;
        GetCursorPos(&pt);
        GetWindowRect(&rc);

        if (!rc.PtInRect(pt))
        {
            CWindow wndParent(GetParent());
            wndParent.ScreenToClient(&pt);
            IPinnedLabel::CPinnedWindow* ptr = LocatePinnedWindow(pt);

            if (ptr == 0 || IsVisualizationNeeded(ptr))
            {
                HWND hWnd = GetFocus();

                while (hWnd != m_hWnd)
                {
                    if (hWnd == NULL)
                    {
                        Vanish(true);
                        break;
                    }

                    hWnd =::GetParent(hWnd);
                }
            }
        }

        return 0;
    }

    LRESULT OnSize(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
    {
        if (wParam != SIZE_MINIMIZED && (m_pActive != 0))
        {
            CRect rc;
            GetClientRect(&rc);
            ::SetWindowPos(m_pActive->Wnd(), NULL,
                           rc.left, rc.top,
                           rc.Width(), rc.Height(),
                           SWP_NOZORDER | SWP_NOACTIVATE);
            GetWindowRect(&rc);
            long width = (IsHorizontal())    ? rc.Height() : rc.Width();
            width -= m_splitter.GetThickness();

            if (width > m_caption.GetThickness()/*0*/)
                m_pActive->Width(width);
        }

        bHandled = FALSE;
        return 1;
    }
    LRESULT OnDock(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
    {
        LRESULT lRes = FALSE;
        DFMHDR* pHdr = reinterpret_cast<DFMHDR*>(lParam);

        switch (pHdr->code)
        {
            case DC_UNDOCK:
                ATLASSERT(::IsWindow(pHdr->hWnd));
                lRes = Remove(pHdr->hWnd);
                break;
        }

        return lRes;
    }
protected:
    long                            m_barThickness;
    IPinnedLabel::CPinnedWindow*    m_pActive;
    IPinnedLabel::CPinnedWindow*    m_pTracked;
    CRect                            m_rcBound;
    CAutoHideBar                    m_bars[barsCount];
};

}//namespace dockwins
#endif // WTL_DW_DWAUTOHIDE_H_INCLUDED_
