chromium/third_party/wtl/include/atlctrlx.h

// Windows Template Library - WTL version 10.0
// Copyright (C) Microsoft Corporation, WTL Team. All rights reserved.
//
// This file is a part of the Windows Template Library.
// The use and distribution terms for this software are covered by the
// Microsoft Public License (http://opensource.org/licenses/MS-PL)
// which can be found in the file MS-PL.txt at the root folder.

#ifndef __ATLCTRLX_H__
#define __ATLCTRLX_H__

#pragma once

#ifndef __ATLAPP_H__
	#error atlctrlx.h requires atlapp.h to be included first
#endif

#ifndef __ATLCTRLS_H__
	#error atlctrlx.h requires atlctrls.h to be included first
#endif


///////////////////////////////////////////////////////////////////////////////
// Classes in this file:
//
// CBitmapButtonImpl<T, TBase, TWinTraits>
// CBitmapButton
// CCheckListViewCtrlImpl<T, TBase, TWinTraits>
// CCheckListViewCtrl
// CHyperLinkImpl<T, TBase, TWinTraits>
// CHyperLink
// CWaitCursor
// CCustomWaitCursor
// CMultiPaneStatusBarCtrlImpl<T, TBase>
// CMultiPaneStatusBarCtrl
// CPaneContainerImpl<T, TBase, TWinTraits>
// CPaneContainer
// CSortListViewImpl<T>
// CSortListViewCtrlImpl<T, TBase, TWinTraits>
// CSortListViewCtrl
// CTabViewImpl<T, TBase, TWinTraits>
// CTabView

namespace WTL
{

///////////////////////////////////////////////////////////////////////////////
// CBitmapButton - bitmap button implementation

// bitmap button extended styles
#define BMPBTN_HOVER            0x00000001
#define BMPBTN_AUTO3D_SINGLE    0x00000002
#define BMPBTN_AUTO3D_DOUBLE    0x00000004
#define BMPBTN_AUTOSIZE         0x00000008
#define BMPBTN_SHAREIMAGELISTS  0x00000010
#define BMPBTN_AUTOFIRE         0x00000020
#define BMPBTN_CHECK            0x00000040
#define BMPBTN_AUTOCHECK        0x00000080

// Note: BMPBTN_CHECK/BMPBTN_AUTOCHECK disables BN_DOUBLECLICKED,
// BMPBTN_AUTOFIRE doesn't work with BMPBTN_CHECK/BMPBTN_AUTOCHECK

template <class T, class TBase = CButton, class TWinTraits = ATL::CControlWinTraits>
class ATL_NO_VTABLE CBitmapButtonImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >
{
public:
	DECLARE_WND_SUPERCLASS2(NULL, T, TBase::GetWndClassName())

	enum
	{
		_nImageNormal = 0,
		_nImagePushed,
		_nImageFocusOrHover,
		_nImageDisabled,

		_nImageCount = 4,
	};

	enum
	{
		ID_TIMER_FIRST = 1000,
		ID_TIMER_REPEAT = 1001
	};

	// Bitmap button specific extended styles
	DWORD m_dwExtendedStyle;

	CImageList m_ImageList;
	int m_nImage[_nImageCount];

	CToolTipCtrl m_tip;
	LPTSTR m_lpstrToolTipText;

	// Internal states
	unsigned m_fMouseOver:1;
	unsigned m_fFocus:1;
	unsigned m_fPressed:1;
	unsigned m_fChecked:1;


// Constructor/Destructor
	CBitmapButtonImpl(DWORD dwExtendedStyle = BMPBTN_AUTOSIZE, HIMAGELIST hImageList = NULL) : 
	                  m_dwExtendedStyle(dwExtendedStyle), m_ImageList(hImageList), 
	                  m_lpstrToolTipText(NULL),
	                  m_fMouseOver(0), m_fFocus(0), m_fPressed(0), m_fChecked(0)
	{
		m_nImage[_nImageNormal] = -1;
		m_nImage[_nImagePushed] = -1;
		m_nImage[_nImageFocusOrHover] = -1;
		m_nImage[_nImageDisabled] = -1;

#ifdef _DEBUG
		if(((m_dwExtendedStyle & BMPBTN_AUTOFIRE) != 0) && IsCheckMode())
			ATLTRACE2(atlTraceUI, 0, _T("CBitmapButtonImpl - Check mode and BMPBTN_AUTOFIRE cannot be used together, BMPBTN_AUTOFIRE will be ignored.\n"));
#endif // _DEBUG
	}

	~CBitmapButtonImpl()
	{
		if((m_dwExtendedStyle & BMPBTN_SHAREIMAGELISTS) == 0)
			m_ImageList.Destroy();
		delete [] m_lpstrToolTipText;
	}

	// overridden to provide proper initialization
	BOOL SubclassWindow(HWND hWnd)
	{
		BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
		if(bRet != FALSE)
		{
			T* pT = static_cast<T*>(this);
			pT->Init();
		}

		return bRet;
	}

// Attributes
	DWORD GetBitmapButtonExtendedStyle() const
	{
		return m_dwExtendedStyle;
	}

	DWORD SetBitmapButtonExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)
	{
		DWORD dwPrevStyle = m_dwExtendedStyle;
		if(dwMask == 0)
			m_dwExtendedStyle = dwExtendedStyle;
		else
			m_dwExtendedStyle = (m_dwExtendedStyle & ~dwMask) | (dwExtendedStyle & dwMask);

#ifdef _DEBUG
		if(((m_dwExtendedStyle & BMPBTN_AUTOFIRE) != 0) && IsCheckMode())
			ATLTRACE2(atlTraceUI, 0, _T("CBitmapButtonImpl - Check mode and BMPBTN_AUTOFIRE cannot be used together, BMPBTN_AUTOFIRE will be ignored.\n"));
#endif // _DEBUG

		return dwPrevStyle;
	}

	HIMAGELIST GetImageList() const
	{
		return m_ImageList;
	}

	HIMAGELIST SetImageList(HIMAGELIST hImageList)
	{
		HIMAGELIST hImageListPrev = m_ImageList;
		m_ImageList = hImageList;
		if(((m_dwExtendedStyle & BMPBTN_AUTOSIZE) != 0) && ::IsWindow(this->m_hWnd))
			SizeToImage();

		return hImageListPrev;
	}

	int GetToolTipTextLength() const
	{
		return (m_lpstrToolTipText == NULL) ? -1 : lstrlen(m_lpstrToolTipText);
	}

	bool GetToolTipText(LPTSTR lpstrText, int nLength) const
	{
		ATLASSERT(lpstrText != NULL);
		if(m_lpstrToolTipText == NULL)
			return false;

		errno_t nRet = ATL::Checked::tcsncpy_s(lpstrText, nLength, m_lpstrToolTipText, _TRUNCATE);

		return ((nRet == 0) || (nRet == STRUNCATE));
	}

	bool SetToolTipText(LPCTSTR lpstrText)
	{
		if(m_lpstrToolTipText != NULL)
		{
			delete [] m_lpstrToolTipText;
			m_lpstrToolTipText = NULL;
		}

		if(lpstrText == NULL)
		{
			if(m_tip.IsWindow())
				m_tip.Activate(FALSE);
			return true;
		}

		int cchLen = lstrlen(lpstrText) + 1;
		ATLTRY(m_lpstrToolTipText = new TCHAR[cchLen]);
		if(m_lpstrToolTipText == NULL)
			return false;

		ATL::Checked::tcscpy_s(m_lpstrToolTipText, cchLen, lpstrText);
		if(m_tip.IsWindow())
		{
			m_tip.Activate(TRUE);
			m_tip.AddTool(this->m_hWnd, m_lpstrToolTipText);
		}

		return true;
	}

	bool GetCheck() const
	{
		return (m_fChecked == 1);
	}

	void SetCheck(bool bCheck, bool bUpdate = true)
	{
		m_fChecked = bCheck ? 1 : 0;

		if(bUpdate)
		{
			this->Invalidate();
			this->UpdateWindow();
		}
	}

// Operations
	void SetImages(int nNormal, int nPushed = -1, int nFocusOrHover = -1, int nDisabled = -1)
	{
		if(nNormal != -1)
			m_nImage[_nImageNormal] = nNormal;
		if(nPushed != -1)
			m_nImage[_nImagePushed] = nPushed;
		if(nFocusOrHover != -1)
			m_nImage[_nImageFocusOrHover] = nFocusOrHover;
		if(nDisabled != -1)
			m_nImage[_nImageDisabled] = nDisabled;
	}

	BOOL SizeToImage()
	{
		ATLASSERT(::IsWindow(this->m_hWnd) && (m_ImageList.m_hImageList != NULL));
		int cx = 0;
		int cy = 0;
		if(!m_ImageList.GetIconSize(cx, cy))
			return FALSE;
		return this->ResizeClient(cx, cy);
	}

// Overrideables
	void DoPaint(CDCHandle dc)
	{
		ATLASSERT(m_ImageList.m_hImageList != NULL);   // image list must be set
		ATLASSERT(m_nImage[0] != -1);                  // main bitmap must be set

		// set bitmap according to the current button state
		bool bHover = IsHoverMode();
		bool bPressed = (m_fPressed == 1) || (IsCheckMode() && (m_fChecked == 1));
		int nImage = -1;
		if(!this->IsWindowEnabled())
			nImage = m_nImage[_nImageDisabled];
		else if(bPressed)
			nImage = m_nImage[_nImagePushed];
		else if((!bHover && (m_fFocus == 1)) || (bHover && (m_fMouseOver == 1)))
			nImage = m_nImage[_nImageFocusOrHover];

		// if none is set, use default one
		if(nImage == -1)
			nImage = m_nImage[_nImageNormal];

		// draw the button image
		bool bAuto3D = (m_dwExtendedStyle & (BMPBTN_AUTO3D_SINGLE | BMPBTN_AUTO3D_DOUBLE)) != 0;
		int xyPos = (bPressed && bAuto3D && (m_nImage[_nImagePushed] == -1)) ? 1 : 0;
		m_ImageList.Draw(dc, nImage, xyPos, xyPos, ILD_NORMAL);

		// draw 3D border if required
		if(bAuto3D)
		{
			RECT rect = {};
			this->GetClientRect(&rect);

			if(bPressed)
				dc.DrawEdge(&rect, ((m_dwExtendedStyle & BMPBTN_AUTO3D_SINGLE) != 0) ? BDR_SUNKENOUTER : EDGE_SUNKEN, BF_RECT);
			else if(!bHover || (m_fMouseOver == 1))
				dc.DrawEdge(&rect, ((m_dwExtendedStyle & BMPBTN_AUTO3D_SINGLE) != 0) ? BDR_RAISEDINNER : EDGE_RAISED, BF_RECT);

			if(!bHover && (m_fFocus == 1))
			{
				::InflateRect(&rect, -2 * ::GetSystemMetrics(SM_CXEDGE), -2 * ::GetSystemMetrics(SM_CYEDGE));
				dc.DrawFocusRect(&rect);
			}
		}
	}

// Message map and handlers
	BEGIN_MSG_MAP(CBitmapButtonImpl)
		MESSAGE_HANDLER(WM_CREATE, OnCreate)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
		MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseMessage)
		MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
		MESSAGE_HANDLER(WM_PAINT, OnPaint)
		MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
		MESSAGE_HANDLER(WM_SETFOCUS, OnFocus)
		MESSAGE_HANDLER(WM_KILLFOCUS, OnFocus)
		MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
		MESSAGE_HANDLER(WM_LBUTTONDBLCLK, OnLButtonDblClk)
		MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp)
		MESSAGE_HANDLER(WM_CAPTURECHANGED, OnCaptureChanged)
		MESSAGE_HANDLER(WM_ENABLE, OnEnable)
		MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
		MESSAGE_HANDLER(WM_MOUSELEAVE, OnMouseLeave)
		MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown)
		MESSAGE_HANDLER(WM_KEYUP, OnKeyUp)
		MESSAGE_HANDLER(WM_TIMER, OnTimer)
		MESSAGE_HANDLER(WM_UPDATEUISTATE, OnUpdateUiState)
	END_MSG_MAP()

	LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		T* pT = static_cast<T*>(this);
		pT->Init();

		bHandled = FALSE;
		return 1;
	}

	LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		if(m_tip.IsWindow())
		{
			m_tip.DestroyWindow();
			m_tip.m_hWnd = NULL;
		}
		bHandled = FALSE;
		return 1;
	}

	LRESULT OnMouseMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		MSG msg = { this->m_hWnd, uMsg, wParam, lParam };
		if(m_tip.IsWindow())
			m_tip.RelayEvent(&msg);
		bHandled = FALSE;
		return 1;
	}

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

	LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		T* pT = static_cast<T*>(this);
		if(wParam != NULL)
		{
			pT->DoPaint((HDC)wParam);
		}
		else
		{
			CPaintDC dc(this->m_hWnd);
			pT->DoPaint(dc.m_hDC);
		}
		return 0;
	}

	LRESULT OnFocus(UINT uMsg, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		m_fFocus = (uMsg == WM_SETFOCUS) ? 1 : 0;
		this->Invalidate();
		this->UpdateWindow();
		bHandled = FALSE;
		return 1;
	}

	LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		LRESULT lRet = 0;
		if(IsHoverMode())
			this->SetCapture();
		else
			lRet = this->DefWindowProc(uMsg, wParam, lParam);
		if(::GetCapture() == this->m_hWnd)
		{
			m_fPressed = 1;
			this->Invalidate();
			this->UpdateWindow();
		}
		if(((m_dwExtendedStyle & BMPBTN_AUTOFIRE) != 0) && !IsCheckMode())
		{
			int nElapse = 250;
			int nDelay = 0;
			if(::SystemParametersInfo(SPI_GETKEYBOARDDELAY, 0, &nDelay, 0))
				nElapse += nDelay * 250;   // all milli-seconds
			this->SetTimer(ID_TIMER_FIRST, nElapse);
		}
		return lRet;
	}

	LRESULT OnLButtonDblClk(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		LRESULT lRet = 0;
		if(!IsHoverMode() && !IsCheckMode())
			lRet = this->DefWindowProc(uMsg, wParam, lParam);
		if(::GetCapture() != this->m_hWnd)
			this->SetCapture();
		if(m_fPressed == 0)
		{
			m_fPressed = 1;
			this->Invalidate();
			this->UpdateWindow();
		}
		return lRet;
	}

	LRESULT OnLButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		if(((m_dwExtendedStyle & BMPBTN_AUTOCHECK) != 0) && (m_fPressed == 1))
			SetCheck(!GetCheck(), false);

		LRESULT lRet = 0;
		if(!IsHoverMode() && !IsCheckMode())
			lRet = this->DefWindowProc(uMsg, wParam, lParam);
		if(::GetCapture() == this->m_hWnd)
		{
			if((IsHoverMode() || IsCheckMode()) && (m_fPressed == 1))
				this->GetParent().SendMessage(WM_COMMAND, MAKEWPARAM(this->GetDlgCtrlID(), BN_CLICKED), (LPARAM)this->m_hWnd);
			::ReleaseCapture();
		}
		return lRet;
	}

	LRESULT OnCaptureChanged(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		if(m_fPressed == 1)
		{
			m_fPressed = 0;
			this->Invalidate();
			this->UpdateWindow();
		}
		bHandled = FALSE;
		return 1;
	}

	LRESULT OnEnable(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		this->Invalidate();
		this->UpdateWindow();
		bHandled = FALSE;
		return 1;
	}

	LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
	{
		if(::GetCapture() == this->m_hWnd)
		{
			POINT ptCursor = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
			this->ClientToScreen(&ptCursor);
			RECT rect = {};
			this->GetWindowRect(&rect);
			unsigned int uPressed = ::PtInRect(&rect, ptCursor) ? 1 : 0;
			if(m_fPressed != uPressed)
			{
				m_fPressed = uPressed;
				this->Invalidate();
				this->UpdateWindow();
			}
		}
		else if(IsHoverMode() && m_fMouseOver == 0)
		{
			m_fMouseOver = 1;
			this->Invalidate();
			this->UpdateWindow();
			StartTrackMouseLeave();
		}
		bHandled = FALSE;
		return 1;
	}

	LRESULT OnMouseLeave(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		if(m_fMouseOver == 1)
		{
			m_fMouseOver = 0;
			this->Invalidate();
			this->UpdateWindow();
		}
		return 0;
	}

	LRESULT OnKeyDown(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
	{
		if((wParam == VK_SPACE) && IsHoverMode())
			return 0;   // ignore if in hover mode
		if((wParam == VK_SPACE) && (m_fPressed == 0))
		{
			m_fPressed = 1;
			this->Invalidate();
			this->UpdateWindow();
		}
		bHandled = FALSE;
		return 1;
	}

	LRESULT OnKeyUp(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
	{
		if((wParam == VK_SPACE) && IsHoverMode())
			return 0;   // ignore if in hover mode
		if((wParam == VK_SPACE) && (m_fPressed == 1))
		{
			m_fPressed = 0;
			if((m_dwExtendedStyle & BMPBTN_AUTOCHECK) != 0)
				SetCheck(!GetCheck(), false);
			this->Invalidate();
			this->UpdateWindow();
		}
		bHandled = FALSE;
		return 1;
	}

	LRESULT OnTimer(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		ATLASSERT((m_dwExtendedStyle & BMPBTN_AUTOFIRE) != 0);
		switch(wParam)   // timer ID
		{
		case ID_TIMER_FIRST:
			this->KillTimer(ID_TIMER_FIRST);
			if(m_fPressed == 1)
			{
				this->GetParent().SendMessage(WM_COMMAND, MAKEWPARAM(this->GetDlgCtrlID(), BN_CLICKED), (LPARAM)this->m_hWnd);
				int nElapse = 250;
				int nRepeat = 40;
				if(::SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, &nRepeat, 0))
					nElapse = 10000 / (10 * nRepeat + 25);   // milli-seconds, approximated
				this->SetTimer(ID_TIMER_REPEAT, nElapse);
			}
			break;
		case ID_TIMER_REPEAT:
			if(m_fPressed == 1)
				this->GetParent().SendMessage(WM_COMMAND, MAKEWPARAM(this->GetDlgCtrlID(), BN_CLICKED), (LPARAM)this->m_hWnd);
			else if(::GetCapture() != this->m_hWnd)
				this->KillTimer(ID_TIMER_REPEAT);
			break;
		default:	// not our timer
			break;
		}
		return 0;
	}

	LRESULT OnUpdateUiState(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		// If the control is subclassed or superclassed, this message can cause
		// repainting without WM_PAINT. We don't use this state, so just do nothing.
		return 0;
	}

// Implementation
	void Init()
	{
		// We need this style to prevent Windows from painting the button
		this->ModifyStyle(0, BS_OWNERDRAW);

		// create a tool tip
		m_tip.Create(this->m_hWnd);
		ATLASSERT(m_tip.IsWindow());
		if(m_tip.IsWindow() && (m_lpstrToolTipText != NULL))
		{
			m_tip.Activate(TRUE);
			m_tip.AddTool(this->m_hWnd, m_lpstrToolTipText);
		}

		if((m_ImageList.m_hImageList != NULL) && ((m_dwExtendedStyle & BMPBTN_AUTOSIZE) != 0))
			SizeToImage();
	}

	BOOL StartTrackMouseLeave()
	{
		TRACKMOUSEEVENT tme = {};
		tme.cbSize = sizeof(tme);
		tme.dwFlags = TME_LEAVE;
		tme.hwndTrack = this->m_hWnd;
		return ::TrackMouseEvent(&tme);
	}

	bool IsHoverMode() const
	{
		return ((m_dwExtendedStyle & BMPBTN_HOVER) != 0);
	}

	bool IsCheckMode() const
	{
		return ((m_dwExtendedStyle & (BMPBTN_CHECK | BMPBTN_AUTOCHECK)) != 0);
	}
};

class CBitmapButton : public CBitmapButtonImpl<CBitmapButton>
{
public:
	DECLARE_WND_SUPERCLASS(_T("WTL_BitmapButton"), GetWndClassName())

	CBitmapButton(DWORD dwExtendedStyle = BMPBTN_AUTOSIZE, HIMAGELIST hImageList = NULL) : 
		CBitmapButtonImpl<CBitmapButton>(dwExtendedStyle, hImageList)
	{ }
};


///////////////////////////////////////////////////////////////////////////////
// CCheckListCtrlView - list view control with check boxes

template <DWORD t_dwStyle, DWORD t_dwExStyle, DWORD t_dwExListViewStyle>
class CCheckListViewCtrlImplTraits
{
public:
	static DWORD GetWndStyle(DWORD dwStyle)
	{
		return (dwStyle == 0) ? t_dwStyle : dwStyle;
	}

	static DWORD GetWndExStyle(DWORD dwExStyle)
	{
		return (dwExStyle == 0) ? t_dwExStyle : dwExStyle;
	}

	static DWORD GetExtendedLVStyle()
	{
		return t_dwExListViewStyle;
	}
};

typedef CCheckListViewCtrlImplTraits<WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SHOWSELALWAYS, WS_EX_CLIENTEDGE, LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT>   CCheckListViewCtrlTraits;

template <class T, class TBase = CListViewCtrl, class TWinTraits = CCheckListViewCtrlTraits>
class ATL_NO_VTABLE CCheckListViewCtrlImpl : public ATL::CWindowImpl<T, TBase, TWinTraits >
{
public:
	DECLARE_WND_SUPERCLASS2(NULL, T, TBase::GetWndClassName())

// Attributes
	static DWORD GetExtendedLVStyle()
	{
		return TWinTraits::GetExtendedLVStyle();
	}

// Operations
	BOOL SubclassWindow(HWND hWnd)
	{
		BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
		if(bRet != FALSE)
		{
			T* pT = static_cast<T*>(this);
			pT->Init();
		}

		return bRet;
	}

	void CheckSelectedItems(int nCurrItem)
	{
		// first check if this item is selected
		LVITEM lvi = {};
		lvi.iItem = nCurrItem;
		lvi.iSubItem = 0;
		lvi.mask = LVIF_STATE;
		lvi.stateMask = LVIS_SELECTED;
		this->GetItem(&lvi);
		// if item is not selected, don't do anything
		if(!(lvi.state & LVIS_SELECTED))
			return;
		// new check state will be reverse of the current state,
		BOOL bCheck = !this->GetCheckState(nCurrItem);
		int nItem = -1;
		int nOldItem = -1;
		while((nItem = this->GetNextItem(nOldItem, LVNI_SELECTED)) != -1)
		{
			if(nItem != nCurrItem)
				this->SetCheckState(nItem, bCheck);
			nOldItem = nItem;
		}
	}

// Implementation
	void Init()
	{
		T* pT = static_cast<T*>(this);
		(void)pT;   // avoid level 4 warning
		ATLASSERT((pT->GetExtendedLVStyle() & LVS_EX_CHECKBOXES) != 0);
		this->SetExtendedListViewStyle(pT->GetExtendedLVStyle());
	}

// Message map and handlers
	BEGIN_MSG_MAP(CCheckListViewCtrlImpl)
		MESSAGE_HANDLER(WM_CREATE, OnCreate)
		MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
		MESSAGE_HANDLER(WM_LBUTTONDBLCLK, OnLButtonDown)
		MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown)
	END_MSG_MAP()

	LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		// first let list view control initialize everything
		LRESULT lRet = this->DefWindowProc(uMsg, wParam, lParam);
		if(lRet == 0)
		{
			T* pT = static_cast<T*>(this);
			pT->Init();
		}

		return lRet;
	}

	LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
	{
		POINT ptMsg = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
		LVHITTESTINFO lvh = {};
		lvh.pt = ptMsg;
		if((this->HitTest(&lvh) != -1) && (lvh.flags == LVHT_ONITEMSTATEICON) && (::GetKeyState(VK_CONTROL) >= 0))
		{
			T* pT = static_cast<T*>(this);
			pT->CheckSelectedItems(lvh.iItem);
		}
		bHandled = FALSE;
		return 1;
	}

	LRESULT OnKeyDown(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
	{
		if(wParam == VK_SPACE)
		{
			int nCurrItem = this->GetNextItem(-1, LVNI_FOCUSED);
			if((nCurrItem != -1)  && (::GetKeyState(VK_CONTROL) >= 0))
			{
				T* pT = static_cast<T*>(this);
				pT->CheckSelectedItems(nCurrItem);
			}
		}
		bHandled = FALSE;
		return 1;
	}
};

class CCheckListViewCtrl : public CCheckListViewCtrlImpl<CCheckListViewCtrl>
{
public:
	DECLARE_WND_SUPERCLASS(_T("WTL_CheckListView"), GetWndClassName())
};


///////////////////////////////////////////////////////////////////////////////
// CHyperLink - hyper link control implementation

#define HLINK_UNDERLINED           0x00000000
#define HLINK_NOTUNDERLINED        0x00000001
#define HLINK_UNDERLINEHOVER       0x00000002
#define HLINK_COMMANDBUTTON        0x00000004
#define HLINK_NOTIFYBUTTON         0x0000000C
#define HLINK_USETAGS              0x00000010
#define HLINK_USETAGSBOLD          0x00000030
#define HLINK_NOTOOLTIP            0x00000040
#define HLINK_AUTOCREATELINKFONT   0x00000080
#define HLINK_SINGLELINE           0x00000100

// Notes:
// - HLINK_USETAGS and HLINK_USETAGSBOLD are always left-aligned
// - When HLINK_USETAGSBOLD is used, the underlined styles will be ignored

template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CControlWinTraits>
class ATL_NO_VTABLE CHyperLinkImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >
{
public:
	LPTSTR m_lpstrLabel;
	LPTSTR m_lpstrHyperLink;

	HCURSOR m_hCursor;
	HFONT m_hFontLink;
	HFONT m_hFontNormal;

	RECT m_rcLink;
	CToolTipCtrl m_tip;

	COLORREF m_clrLink;
	COLORREF m_clrVisited;

	DWORD m_dwExtendedStyle;   // Hyper Link specific extended styles

	bool m_bPaintLabel:1;
	bool m_bVisited:1;
	bool m_bHover:1;
	bool m_bInternalLinkFont:1;
	bool m_bInternalNormalFont:1;


// Constructor/Destructor
	CHyperLinkImpl(DWORD dwExtendedStyle = HLINK_UNDERLINED) : 
			m_lpstrLabel(NULL), m_lpstrHyperLink(NULL),
			m_hCursor(NULL), m_hFontLink(NULL), m_hFontNormal(NULL),
			m_clrLink(RGB(0, 0, 255)), m_clrVisited(RGB(128, 0, 128)),
			m_dwExtendedStyle(dwExtendedStyle),
			m_bPaintLabel(true), m_bVisited(false),
			m_bHover(false), m_bInternalLinkFont(false), m_bInternalNormalFont(false)
	{
		::SetRectEmpty(&m_rcLink);
	}

	~CHyperLinkImpl()
	{
		delete [] m_lpstrLabel;
		delete [] m_lpstrHyperLink;
	}

// Attributes
	DWORD GetHyperLinkExtendedStyle() const
	{
		return m_dwExtendedStyle;
	}

	DWORD SetHyperLinkExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)
	{
		DWORD dwPrevStyle = m_dwExtendedStyle;
		if(dwMask == 0)
			m_dwExtendedStyle = dwExtendedStyle;
		else
			m_dwExtendedStyle = (m_dwExtendedStyle & ~dwMask) | (dwExtendedStyle & dwMask);
		return dwPrevStyle;
	}

	bool GetLabel(LPTSTR lpstrBuffer, int nLength) const
	{
		if(m_lpstrLabel == NULL)
			return false;
		ATLASSERT(lpstrBuffer != NULL);
		if(nLength <= lstrlen(m_lpstrLabel))
			return false;

		ATL::Checked::tcscpy_s(lpstrBuffer, nLength, m_lpstrLabel);

		return true;
	}

	bool SetLabel(LPCTSTR lpstrLabel)
	{
		delete [] m_lpstrLabel;
		m_lpstrLabel = NULL;
		int cchLen = lstrlen(lpstrLabel) + 1;
		ATLTRY(m_lpstrLabel = new TCHAR[cchLen]);
		if(m_lpstrLabel == NULL)
			return false;

		ATL::Checked::tcscpy_s(m_lpstrLabel, cchLen, lpstrLabel);
		T* pT = static_cast<T*>(this);
		pT->CalcLabelRect();

		if(this->m_hWnd != NULL)
			this->SetWindowText(lpstrLabel);   // Set this for accessibility

		return true;
	}

	bool GetHyperLink(LPTSTR lpstrBuffer, int nLength) const
	{
		if(m_lpstrHyperLink == NULL)
			return false;
		ATLASSERT(lpstrBuffer != NULL);
		if(nLength <= lstrlen(m_lpstrHyperLink))
			return false;

		ATL::Checked::tcscpy_s(lpstrBuffer, nLength, m_lpstrHyperLink);

		return true;
	}

	bool SetHyperLink(LPCTSTR lpstrLink)
	{
		delete [] m_lpstrHyperLink;
		m_lpstrHyperLink = NULL;
		int cchLen = lstrlen(lpstrLink) + 1;
		ATLTRY(m_lpstrHyperLink = new TCHAR[cchLen]);
		if(m_lpstrHyperLink == NULL)
			return false;

		ATL::Checked::tcscpy_s(m_lpstrHyperLink, cchLen, lpstrLink);
		if(m_lpstrLabel == NULL)
		{
			T* pT = static_cast<T*>(this);
			pT->CalcLabelRect();
		}

		if(m_tip.IsWindow())
		{
			m_tip.Activate(TRUE);
			m_tip.AddTool(this->m_hWnd, m_lpstrHyperLink, &m_rcLink, 1);
		}

		return true;
	}

	HFONT GetLinkFont() const
	{
		return m_hFontLink;
	}

	void SetLinkFont(HFONT hFont)
	{
		if(m_bInternalLinkFont)
		{
			::DeleteObject(m_hFontLink);
			m_bInternalLinkFont = false;
		}

		m_hFontLink = hFont;

		T* pT = static_cast<T*>(this);
		pT->CalcLabelRect();
	}

	int GetIdealHeight() const
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		if((m_lpstrLabel == NULL) && (m_lpstrHyperLink == NULL))
			return -1;
		if(!m_bPaintLabel)
			return -1;

		UINT uFormat = IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;

		CClientDC dc(this->m_hWnd);
		RECT rect = {};
		this->GetClientRect(&rect);
		HFONT hFontOld = dc.SelectFont(m_hFontNormal);
		RECT rcText = rect;
		dc.DrawText(_T("NS"), -1, &rcText, DT_LEFT | uFormat | DT_CALCRECT);
		dc.SelectFont(m_hFontLink);
		RECT rcLink = rect;
		dc.DrawText(_T("NS"), -1, &rcLink, DT_LEFT | uFormat | DT_CALCRECT);
		dc.SelectFont(hFontOld);
		return __max(rcText.bottom - rcText.top, rcLink.bottom - rcLink.top);
	}

	bool GetIdealSize(SIZE& size) const
	{
		int cx = 0, cy = 0;
		bool bRet = GetIdealSize(cx, cy);
		if(bRet)
		{
			size.cx = cx;
			size.cy = cy;
		}
		return bRet;
	}

	bool GetIdealSize(int& cx, int& cy) const
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		if((m_lpstrLabel == NULL) && (m_lpstrHyperLink == NULL))
			return false;
		if(!m_bPaintLabel)
			return false;

		CClientDC dc(this->m_hWnd);
		RECT rcClient = {};
		this->GetClientRect(&rcClient);
		RECT rcAll = rcClient;

		if(IsUsingTags())
		{
			// find tags and label parts
			LPTSTR lpstrLeft = NULL;
			int cchLeft = 0;
			LPTSTR lpstrLink = NULL;
			int cchLink = 0;
			LPTSTR lpstrRight = NULL;
			int cchRight = 0;

			const T* pT = static_cast<const T*>(this);
			pT->CalcLabelParts(lpstrLeft, cchLeft, lpstrLink, cchLink, lpstrRight, cchRight);

			// get label part rects
			UINT uFormat = IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;

			HFONT hFontOld = dc.SelectFont(m_hFontNormal);
			RECT rcLeft = rcClient;
			dc.DrawText(lpstrLeft, cchLeft, &rcLeft, DT_LEFT | uFormat | DT_CALCRECT);

			dc.SelectFont(m_hFontLink);
			RECT rcLink = { rcLeft.right, rcLeft.top, rcClient.right, rcClient.bottom };
			dc.DrawText(lpstrLink, cchLink, &rcLink, DT_LEFT | uFormat | DT_CALCRECT);

			dc.SelectFont(m_hFontNormal);
			RECT rcRight = { rcLink.right, rcLink.top, rcClient.right, rcClient.bottom };
			dc.DrawText(lpstrRight, cchRight, &rcRight, DT_LEFT | uFormat | DT_CALCRECT);

			dc.SelectFont(hFontOld);

			int cyMax = __max(rcLeft.bottom, __max(rcLink.bottom, rcRight.bottom));
			::SetRect(&rcAll, rcLeft.left, rcLeft.top, rcRight.right, cyMax);
		}
		else
		{
			HFONT hOldFont = NULL;
			if(m_hFontLink != NULL)
				hOldFont = dc.SelectFont(m_hFontLink);
			LPTSTR lpstrText = (m_lpstrLabel != NULL) ? m_lpstrLabel : m_lpstrHyperLink;
			DWORD dwStyle = this->GetStyle();
			UINT uFormat = DT_LEFT;
			if (dwStyle & SS_CENTER)
				uFormat = DT_CENTER;
			else if (dwStyle & SS_RIGHT)
				uFormat = DT_RIGHT;
			uFormat |= IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;
			dc.DrawText(lpstrText, -1, &rcAll, uFormat | DT_CALCRECT);
			if(m_hFontLink != NULL)
				dc.SelectFont(hOldFont);
			if (dwStyle & SS_CENTER)
			{
				int dx = (rcClient.right - rcAll.right) / 2;
				::OffsetRect(&rcAll, dx, 0);
			}
			else if (dwStyle & SS_RIGHT)
			{
				int dx = rcClient.right - rcAll.right;
				::OffsetRect(&rcAll, dx, 0);
			}
		}

		cx = rcAll.right - rcAll.left;
		cy = rcAll.bottom - rcAll.top;

		return true;
	}

	// for command buttons only
	bool GetToolTipText(LPTSTR lpstrBuffer, int nLength) const
	{
		ATLASSERT(IsCommandButton());
		return GetHyperLink(lpstrBuffer, nLength);
	}

	bool SetToolTipText(LPCTSTR lpstrToolTipText)
	{
		ATLASSERT(IsCommandButton());
		return SetHyperLink(lpstrToolTipText);
	}

// Operations
	BOOL SubclassWindow(HWND hWnd)
	{
		ATLASSERT(this->m_hWnd == NULL);
		ATLASSERT(::IsWindow(hWnd));
		if(m_hFontNormal == NULL)
			m_hFontNormal = (HFONT)::SendMessage(hWnd, WM_GETFONT, 0, 0L);

		BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
		if(bRet != FALSE)
		{
			T* pT = static_cast<T*>(this);
			pT->Init();
		}

		return bRet;
	}

	bool Navigate()
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		bool bRet = true;
		if(IsNotifyButton())
		{
			NMHDR nmhdr = { this->m_hWnd, (UINT_PTR)this->GetDlgCtrlID(), NM_CLICK };
			this->GetParent().SendMessage(WM_NOTIFY, this->GetDlgCtrlID(), (LPARAM)&nmhdr);
		}
		else if(IsCommandButton())
		{
			this->GetParent().SendMessage(WM_COMMAND, MAKEWPARAM(this->GetDlgCtrlID(), BN_CLICKED), (LPARAM)this->m_hWnd);
		}
		else
		{
			ATLASSERT(m_lpstrHyperLink != NULL);
			DWORD_PTR dwRet = (DWORD_PTR)::ShellExecute(0, _T("open"), m_lpstrHyperLink, 0, 0, SW_SHOWNORMAL);
			bRet = (dwRet > 32);
			ATLASSERT(bRet);
			if(bRet)
			{
				m_bVisited = true;
				this->Invalidate();
			}
		}
		return bRet;
	}

	void CreateLinkFontFromNormal()
	{
		if(m_bInternalLinkFont)
		{
			::DeleteObject(m_hFontLink);
			m_bInternalLinkFont = false;
		}

		CFontHandle font = (m_hFontNormal != NULL) ? m_hFontNormal : (HFONT)::GetStockObject(SYSTEM_FONT);
		LOGFONT lf = {};
		font.GetLogFont(&lf);

		if(IsUsingTagsBold())
			lf.lfWeight = FW_BOLD;
		else if(!IsNotUnderlined())
			lf.lfUnderline = TRUE;

		m_hFontLink = ::CreateFontIndirect(&lf);
		m_bInternalLinkFont = true;
		ATLASSERT(m_hFontLink != NULL);
	}

// Message map and handlers
	BEGIN_MSG_MAP(CHyperLinkImpl)
		MESSAGE_HANDLER(WM_CREATE, OnCreate)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
		MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseMessage)
		MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
		MESSAGE_HANDLER(WM_PAINT, OnPaint)
		MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
		MESSAGE_HANDLER(WM_SETFOCUS, OnFocus)
		MESSAGE_HANDLER(WM_KILLFOCUS, OnFocus)
		MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
		MESSAGE_HANDLER(WM_MOUSELEAVE, OnMouseLeave)
		MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
		MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp)
		MESSAGE_HANDLER(WM_CHAR, OnChar)
		MESSAGE_HANDLER(WM_GETDLGCODE, OnGetDlgCode)
		MESSAGE_HANDLER(WM_SETCURSOR, OnSetCursor)
		MESSAGE_HANDLER(WM_ENABLE, OnEnable)
		MESSAGE_HANDLER(WM_GETFONT, OnGetFont)
		MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
		MESSAGE_HANDLER(WM_UPDATEUISTATE, OnUpdateUiState)
		MESSAGE_HANDLER(WM_SIZE, OnSize)
	END_MSG_MAP()

	LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		T* pT = static_cast<T*>(this);
		pT->Init();
		return 0;
	}

	LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		if(m_tip.IsWindow())
		{
			m_tip.DestroyWindow();
			m_tip.m_hWnd = NULL;
		}

		if(m_bInternalLinkFont)
		{
			::DeleteObject(m_hFontLink);
			m_hFontLink = NULL;
			m_bInternalLinkFont = false;
		}

		if(m_bInternalNormalFont)
		{
			::DeleteObject(m_hFontNormal);
			m_hFontNormal = NULL;
			m_bInternalNormalFont = false;
		}

		bHandled = FALSE;
		return 1;
	}

	LRESULT OnMouseMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		MSG msg = { this->m_hWnd, uMsg, wParam, lParam };
		if(m_tip.IsWindow() && IsUsingToolTip())
			m_tip.RelayEvent(&msg);
		bHandled = FALSE;
		return 1;
	}

	LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		return 1;   // no background painting needed (we do it all during WM_PAINT)
	}

	LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
	{
		if(!m_bPaintLabel)
		{
			bHandled = FALSE;
			return 1;
		}

		T* pT = static_cast<T*>(this);
		if(wParam != NULL)
		{
			pT->DoEraseBackground((HDC)wParam);
			pT->DoPaint((HDC)wParam);
		}
		else
		{
			CPaintDC dc(this->m_hWnd);
			pT->DoEraseBackground(dc.m_hDC);
			pT->DoPaint(dc.m_hDC);
		}

		return 0;
	}

	LRESULT OnFocus(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		if(m_bPaintLabel)
			this->Invalidate();
		else
			bHandled = FALSE;
		return 0;
	}

	LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
	{
		POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
		if(((m_lpstrHyperLink != NULL) || IsCommandButton()) && ::PtInRect(&m_rcLink, pt))
		{
			::SetCursor(m_hCursor);
			if(IsUnderlineHover())
			{
				if(!m_bHover)
				{
					m_bHover = true;
					this->InvalidateRect(&m_rcLink);
					this->UpdateWindow();
					StartTrackMouseLeave();
				}
			}
		}
		else
		{
			if(IsUnderlineHover())
			{
				if(m_bHover)
				{
					m_bHover = false;
					this->InvalidateRect(&m_rcLink);
					this->UpdateWindow();
				}
			}
			bHandled = FALSE;
		}
		return 0;
	}

	LRESULT OnMouseLeave(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		if(IsUnderlineHover() && m_bHover)
		{
			m_bHover = false;
			this->InvalidateRect(&m_rcLink);
			this->UpdateWindow();
		}
		return 0;
	}

	LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
	{
		POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
		if(::PtInRect(&m_rcLink, pt))
		{
			this->SetFocus();
			this->SetCapture();
		}
		return 0;
	}

	LRESULT OnLButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
	{
		if(GetCapture() == this->m_hWnd)
		{
			ReleaseCapture();
			POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
			if(::PtInRect(&m_rcLink, pt))
			{
				T* pT = static_cast<T*>(this);
				pT->Navigate();
			}
		}
		return 0;
	}

	LRESULT OnChar(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		if((wParam == VK_RETURN) || (wParam == VK_SPACE))
		{
			T* pT = static_cast<T*>(this);
			pT->Navigate();
		}
		return 0;
	}

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

	LRESULT OnSetCursor(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		POINT pt = {};
		GetCursorPos(&pt);
		this->ScreenToClient(&pt);
		if(((m_lpstrHyperLink != NULL)  || IsCommandButton()) && ::PtInRect(&m_rcLink, pt))
		{
			return TRUE;
		}
		bHandled = FALSE;
		return FALSE;
	}

	LRESULT OnEnable(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		this->Invalidate();
		this->UpdateWindow();
		return 0;
	}

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

	LRESULT OnSetFont(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		if(m_bInternalNormalFont)
		{
			::DeleteObject(m_hFontNormal);
			m_bInternalNormalFont = false;
		}

		bool bCreateLinkFont = m_bInternalLinkFont;

		m_hFontNormal = (HFONT)wParam;

		if(bCreateLinkFont || IsAutoCreateLinkFont())
			CreateLinkFontFromNormal();

		T* pT = static_cast<T*>(this);
		pT->CalcLabelRect();

		if((BOOL)lParam)
		{
			this->Invalidate();
			this->UpdateWindow();
		}

		return 0;
	}

	LRESULT OnUpdateUiState(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		// If the control is subclassed or superclassed, this message can cause
		// repainting without WM_PAINT. We don't use this state, so just do nothing.
		return 0;
	}

	LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		T* pT = static_cast<T*>(this);
		pT->CalcLabelRect();
		pT->Invalidate();
		return 0;
	}

// Implementation
	void Init()
	{
		ATLASSERT(::IsWindow(this->m_hWnd));

		// Check if we should paint a label
		const int cchBuff = 8;
		TCHAR szBuffer[cchBuff] = {};
		if(::GetClassName(this->m_hWnd, szBuffer, cchBuff))
		{
			if(lstrcmpi(szBuffer, _T("static")) == 0)
			{
				this->ModifyStyle(0, SS_NOTIFY);   // we need this
				DWORD dwStyle = this->GetStyle() & 0x000000FF;
				if((dwStyle == SS_ICON) || (dwStyle == SS_BLACKRECT) || (dwStyle == SS_GRAYRECT) || 
						(dwStyle == SS_WHITERECT) || (dwStyle == SS_BLACKFRAME) || (dwStyle == SS_GRAYFRAME) || 
						(dwStyle == SS_WHITEFRAME) || (dwStyle == SS_OWNERDRAW) || 
						(dwStyle == SS_BITMAP) || (dwStyle == SS_ENHMETAFILE))
					m_bPaintLabel = false;
			}
		}

		// create or load a cursor
		m_hCursor = ::LoadCursor(NULL, IDC_HAND);
		ATLASSERT(m_hCursor != NULL);

		// set fonts
		if(m_bPaintLabel)
		{
			if(m_hFontNormal == NULL)
			{
				m_hFontNormal = AtlCreateControlFont();
				m_bInternalNormalFont = true;
			}

			if(m_hFontLink == NULL)
				CreateLinkFontFromNormal();
		}

		// create a tool tip
		m_tip.Create(this->m_hWnd);
		ATLASSERT(m_tip.IsWindow());

		// set label (defaults to window text)
		if(m_lpstrLabel == NULL)
		{
			int nLen = this->GetWindowTextLength();
			if(nLen > 0)
			{
				ATLTRY(m_lpstrLabel = new TCHAR[nLen + 1]);
				if(m_lpstrLabel != NULL)
					ATLVERIFY(this->GetWindowText(m_lpstrLabel, nLen + 1) > 0);
			}
		}

		T* pT = static_cast<T*>(this);
		pT->CalcLabelRect();

		// set hyperlink (defaults to label), or just activate tool tip if already set
		if((m_lpstrHyperLink == NULL) && !IsCommandButton())
		{
			if(m_lpstrLabel != NULL)
				SetHyperLink(m_lpstrLabel);
		}
		else
		{
			m_tip.Activate(TRUE);
			m_tip.AddTool(this->m_hWnd, m_lpstrHyperLink, &m_rcLink, 1);
		}

		// set link colors
		if(m_bPaintLabel)
		{
			ATL::CRegKey rk;
			LONG lRet = rk.Open(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Internet Explorer\\Settings"));
			if(lRet == ERROR_SUCCESS)
			{
				const int cchValue = 12;
				TCHAR szValue[cchValue] = {};
				ULONG ulCount = cchValue;
				lRet = rk.QueryStringValue(_T("Anchor Color"), szValue, &ulCount);
				if(lRet == ERROR_SUCCESS)
				{
					COLORREF clr = pT->_ParseColorString(szValue);
					ATLASSERT(clr != CLR_INVALID);
					if(clr != CLR_INVALID)
						m_clrLink = clr;
				}

				ulCount = cchValue;
				lRet = rk.QueryStringValue(_T("Anchor Color Visited"), szValue, &ulCount);
				if(lRet == ERROR_SUCCESS)
				{
					COLORREF clr = pT->_ParseColorString(szValue);
					ATLASSERT(clr != CLR_INVALID);
					if(clr != CLR_INVALID)
						m_clrVisited = clr;
				}
			}
		}
	}

	static COLORREF _ParseColorString(LPTSTR lpstr)
	{
		int c[3] = { -1, -1, -1 };
		LPTSTR p = NULL;
		for(int i = 0; i < 2; i++)
		{
			for(p = lpstr; *p != _T('\0'); p = ::CharNext(p))
			{
				if(*p == _T(','))
				{
					*p = _T('\0');
					c[i] = _ttoi(lpstr);
					lpstr = &p[1];
					break;
				}
			}
			if(c[i] == -1)
				return CLR_INVALID;
		}
		if(*lpstr == _T('\0'))
			return CLR_INVALID;
		c[2] = _ttoi(lpstr);

		return RGB(c[0], c[1], c[2]);
	}

	bool CalcLabelRect()
	{
		if(!::IsWindow(this->m_hWnd))
			return false;
		if((m_lpstrLabel == NULL) && (m_lpstrHyperLink == NULL))
			return false;

		CClientDC dc(this->m_hWnd);
		RECT rcClient = {};
		this->GetClientRect(&rcClient);
		m_rcLink = rcClient;
		if(!m_bPaintLabel)
			return true;

		if(IsUsingTags())
		{
			// find tags and label parts
			LPTSTR lpstrLeft = NULL;
			int cchLeft = 0;
			LPTSTR lpstrLink = NULL;
			int cchLink = 0;
			LPTSTR lpstrRight = NULL;
			int cchRight = 0;

			T* pT = static_cast<T*>(this);
			pT->CalcLabelParts(lpstrLeft, cchLeft, lpstrLink, cchLink, lpstrRight, cchRight);
			ATLASSERT(lpstrLink != NULL);
			ATLASSERT(cchLink > 0);

			// get label part rects
			HFONT hFontOld = dc.SelectFont(m_hFontNormal);

			UINT uFormat = IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;

			RECT rcLeft = rcClient;
			if(lpstrLeft != NULL)
				dc.DrawText(lpstrLeft, cchLeft, &rcLeft, DT_LEFT | uFormat | DT_CALCRECT);

			dc.SelectFont(m_hFontLink);
			RECT rcLink = rcClient;
			if(lpstrLeft != NULL)
				rcLink.left = rcLeft.right;
			dc.DrawText(lpstrLink, cchLink, &rcLink, DT_LEFT | uFormat | DT_CALCRECT);

			dc.SelectFont(hFontOld);

			m_rcLink = rcLink;
		}
		else
		{
			HFONT hOldFont = NULL;
			if(m_hFontLink != NULL)
				hOldFont = dc.SelectFont(m_hFontLink);
			LPTSTR lpstrText = (m_lpstrLabel != NULL) ? m_lpstrLabel : m_lpstrHyperLink;
			DWORD dwStyle = this->GetStyle();
			UINT uFormat = DT_LEFT;
			if (dwStyle & SS_CENTER)
				uFormat = DT_CENTER;
			else if (dwStyle & SS_RIGHT)
				uFormat = DT_RIGHT;
			uFormat |= IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;
			dc.DrawText(lpstrText, -1, &m_rcLink, uFormat | DT_CALCRECT);
			if(m_hFontLink != NULL)
				dc.SelectFont(hOldFont);
			if (dwStyle & SS_CENTER)
			{
				int dx = (rcClient.right - m_rcLink.right) / 2;
				::OffsetRect(&m_rcLink, dx, 0);
			}
			else if (dwStyle & SS_RIGHT)
			{
				int dx = rcClient.right - m_rcLink.right;
				::OffsetRect(&m_rcLink, dx, 0);
			}
		}

		return true;
	}

	void CalcLabelParts(LPTSTR& lpstrLeft, int& cchLeft, LPTSTR& lpstrLink, int& cchLink, LPTSTR& lpstrRight, int& cchRight) const
	{
		lpstrLeft = NULL;
		cchLeft = 0;
		lpstrLink = NULL;
		cchLink = 0;
		lpstrRight = NULL;
		cchRight = 0;

		LPTSTR lpstrText = (m_lpstrLabel != NULL) ? m_lpstrLabel : m_lpstrHyperLink;
		int cchText = lstrlen(lpstrText);
		bool bOutsideLink = true;
		for(int i = 0; i < cchText; i++)
		{
			if(lpstrText[i] != _T('<'))
				continue;

			if(bOutsideLink)
			{
				if(::CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, &lpstrText[i], 3, _T("<A>"), 3) == CSTR_EQUAL)
				{
					if(i > 0)
					{
						lpstrLeft = lpstrText;
						cchLeft = i;
					}
					lpstrLink = &lpstrText[i + 3];
					bOutsideLink = false;
				}
			}
			else
			{
				if(::CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, &lpstrText[i], 4, _T("</A>"), 4) == CSTR_EQUAL)
				{
					cchLink = i - 3 - cchLeft;
					if(lpstrText[i + 4] != 0)
					{
						lpstrRight = &lpstrText[i + 4];
						cchRight = cchText - (i + 4);
						break;
					}
				}
			}
		}

	}

	void DoEraseBackground(CDCHandle dc)
	{
		HBRUSH hBrush = (HBRUSH)this->GetParent().SendMessage(WM_CTLCOLORSTATIC, (WPARAM)dc.m_hDC, (LPARAM)this->m_hWnd);
		if(hBrush != NULL)
		{
			RECT rect = {};
			this->GetClientRect(&rect);
			dc.FillRect(&rect, hBrush);
		}
	}

	void DoPaint(CDCHandle dc)
	{
		if(IsUsingTags())
		{
			// find tags and label parts
			LPTSTR lpstrLeft = NULL;
			int cchLeft = 0;
			LPTSTR lpstrLink = NULL;
			int cchLink = 0;
			LPTSTR lpstrRight = NULL;
			int cchRight = 0;

			T* pT = static_cast<T*>(this);
			pT->CalcLabelParts(lpstrLeft, cchLeft, lpstrLink, cchLink, lpstrRight, cchRight);

			// get label part rects
			RECT rcClient = {};
			this->GetClientRect(&rcClient);

			dc.SetBkMode(TRANSPARENT);
			HFONT hFontOld = dc.SelectFont(m_hFontNormal);

			UINT uFormat = IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;

			if(lpstrLeft != NULL)
				dc.DrawText(lpstrLeft, cchLeft, &rcClient, DT_LEFT | uFormat);

			COLORREF clrOld = dc.SetTextColor(this->IsWindowEnabled() ? (m_bVisited ? m_clrVisited : m_clrLink) : (::GetSysColor(COLOR_GRAYTEXT)));
			if((m_hFontLink != NULL) && (!IsUnderlineHover() || (IsUnderlineHover() && m_bHover)))
				dc.SelectFont(m_hFontLink);
			else
				dc.SelectFont(m_hFontNormal);

			dc.DrawText(lpstrLink, cchLink, &m_rcLink, DT_LEFT | uFormat);

			dc.SetTextColor(clrOld);
			dc.SelectFont(m_hFontNormal);
			if(lpstrRight != NULL)
			{
				RECT rcRight = { m_rcLink.right, m_rcLink.top, rcClient.right, rcClient.bottom };
				dc.DrawText(lpstrRight, cchRight, &rcRight, DT_LEFT | uFormat);
			}

			if(GetFocus() == this->m_hWnd)
				dc.DrawFocusRect(&m_rcLink);

			dc.SelectFont(hFontOld);
		}
		else
		{
			dc.SetBkMode(TRANSPARENT);
			COLORREF clrOld = dc.SetTextColor(this->IsWindowEnabled() ? (m_bVisited ? m_clrVisited : m_clrLink) : (::GetSysColor(COLOR_GRAYTEXT)));

			HFONT hFontOld = NULL;
			if((m_hFontLink != NULL) && (!IsUnderlineHover() || (IsUnderlineHover() && m_bHover)))
				hFontOld = dc.SelectFont(m_hFontLink);
			else
				hFontOld = dc.SelectFont(m_hFontNormal);

			LPTSTR lpstrText = (m_lpstrLabel != NULL) ? m_lpstrLabel : m_lpstrHyperLink;

			DWORD dwStyle = this->GetStyle();
			UINT uFormat = DT_LEFT;
			if (dwStyle & SS_CENTER)
				uFormat = DT_CENTER;
			else if (dwStyle & SS_RIGHT)
				uFormat = DT_RIGHT;
			uFormat |= IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;

			dc.DrawText(lpstrText, -1, &m_rcLink, uFormat);

			if(GetFocus() == this->m_hWnd)
				dc.DrawFocusRect(&m_rcLink);

			dc.SetTextColor(clrOld);
			dc.SelectFont(hFontOld);
		}
	}

	BOOL StartTrackMouseLeave()
	{
		TRACKMOUSEEVENT tme = {};
		tme.cbSize = sizeof(tme);
		tme.dwFlags = TME_LEAVE;
		tme.hwndTrack = this->m_hWnd;
		return ::TrackMouseEvent(&tme);
	}

// Implementation helpers
	bool IsUnderlined() const
	{
		return ((m_dwExtendedStyle & (HLINK_NOTUNDERLINED | HLINK_UNDERLINEHOVER)) == 0);
	}

	bool IsNotUnderlined() const
	{
		return ((m_dwExtendedStyle & HLINK_NOTUNDERLINED) != 0);
	}

	bool IsUnderlineHover() const
	{
		return ((m_dwExtendedStyle & HLINK_UNDERLINEHOVER) != 0);
	}

	bool IsCommandButton() const
	{
		return ((m_dwExtendedStyle & HLINK_COMMANDBUTTON) != 0);
	}

	bool IsNotifyButton() const
	{
		return ((m_dwExtendedStyle & HLINK_NOTIFYBUTTON) == HLINK_NOTIFYBUTTON);
	}

	bool IsUsingTags() const
	{
		return ((m_dwExtendedStyle & HLINK_USETAGS) != 0);
	}

	bool IsUsingTagsBold() const
	{
		return ((m_dwExtendedStyle & HLINK_USETAGSBOLD) == HLINK_USETAGSBOLD);
	}

	bool IsUsingToolTip() const
	{
		return ((m_dwExtendedStyle & HLINK_NOTOOLTIP) == 0);
	}

	bool IsAutoCreateLinkFont() const
	{
		return ((m_dwExtendedStyle & HLINK_AUTOCREATELINKFONT) == HLINK_AUTOCREATELINKFONT);
	}

	bool IsSingleLine() const
	{
		return ((m_dwExtendedStyle & HLINK_SINGLELINE) == HLINK_SINGLELINE);
	}
};

class CHyperLink : public CHyperLinkImpl<CHyperLink>
{
public:
	DECLARE_WND_CLASS(_T("WTL_HyperLink"))
};


///////////////////////////////////////////////////////////////////////////////
// CWaitCursor - displays a wait cursor

class CWaitCursor
{
public:
// Data
	HCURSOR m_hWaitCursor;
	HCURSOR m_hOldCursor;
	bool m_bInUse;

// Constructor/destructor
	CWaitCursor(bool bSet = true, LPCTSTR lpstrCursor = IDC_WAIT, bool bSys = true) : m_hOldCursor(NULL), m_bInUse(false)
	{
		HINSTANCE hInstance = bSys ? NULL : ModuleHelper::GetResourceInstance();
		m_hWaitCursor = ::LoadCursor(hInstance, lpstrCursor);
		ATLASSERT(m_hWaitCursor != NULL);

		if(bSet)
			Set();
	}

	~CWaitCursor()
	{
		Restore();
	}

// Methods
	bool Set()
	{
		if(m_bInUse)
			return false;
		m_hOldCursor = ::SetCursor(m_hWaitCursor);
		m_bInUse = true;
		return true;
	}

	bool Restore()
	{
		if(!m_bInUse)
			return false;
		::SetCursor(m_hOldCursor);
		m_bInUse = false;
		return true;
	}
};


///////////////////////////////////////////////////////////////////////////////
// CCustomWaitCursor - for custom and animated cursors

class CCustomWaitCursor : public CWaitCursor
{
public:
// Constructor/destructor
	CCustomWaitCursor(ATL::_U_STRINGorID cursor, bool bSet = true, HINSTANCE hInstance = NULL) : 
			CWaitCursor(false, IDC_WAIT, true)
	{
		if(hInstance == NULL)
			hInstance = ModuleHelper::GetResourceInstance();
		m_hWaitCursor = (HCURSOR)::LoadImage(hInstance, cursor.m_lpstr, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE);

		if(bSet)
			Set();
	}

	~CCustomWaitCursor()
	{
		Restore();
		::DestroyCursor(m_hWaitCursor);
	}
};


///////////////////////////////////////////////////////////////////////////////
// CMultiPaneStatusBarCtrl - Status Bar with multiple panes

template <class T, class TBase = CStatusBarCtrl>
class ATL_NO_VTABLE CMultiPaneStatusBarCtrlImpl : public ATL::CWindowImpl< T, TBase >
{
public:
	DECLARE_WND_SUPERCLASS2(NULL, T, TBase::GetWndClassName())

// Data
	enum { m_cxPaneMargin = 3 };

	int m_nPanes;
	int* m_pPane;

// Constructor/destructor
	CMultiPaneStatusBarCtrlImpl() : m_nPanes(0), m_pPane(NULL)
	{ }

	~CMultiPaneStatusBarCtrlImpl()
	{
		delete [] m_pPane;
	}

// Methods
	HWND Create(HWND hWndParent, LPCTSTR lpstrText, DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | SBARS_SIZEGRIP, UINT nID = ATL_IDW_STATUS_BAR)
	{
		return ATL::CWindowImpl< T, TBase >::Create(hWndParent, this->rcDefault, lpstrText, dwStyle, 0, nID);
	}

	HWND Create(HWND hWndParent, UINT nTextID = ATL_IDS_IDLEMESSAGE, DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | SBARS_SIZEGRIP, UINT nID = ATL_IDW_STATUS_BAR)
	{
		const int cchMax = 128;   // max text length is 127 for status bars (+1 for null)
		TCHAR szText[cchMax] = {};
		::LoadString(ModuleHelper::GetResourceInstance(), nTextID, szText, cchMax);
		return Create(hWndParent, szText, dwStyle, nID);
	}

	BOOL SetPanes(int* pPanes, int nPanes, bool bSetText = true)
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		ATLASSERT(nPanes > 0);

		m_nPanes = nPanes;
		delete [] m_pPane;
		m_pPane = NULL;

		ATLTRY(m_pPane = new int[nPanes]);
		ATLASSERT(m_pPane != NULL);
		if(m_pPane == NULL)
			return FALSE;

		ATL::CTempBuffer<int, _WTL_STACK_ALLOC_THRESHOLD> buff;
		int* pPanesPos = buff.Allocate(nPanes);
		ATLASSERT(pPanesPos != NULL);
		if(pPanesPos == NULL)
			return FALSE;

		ATL::Checked::memcpy_s(m_pPane, nPanes * sizeof(int), pPanes, nPanes * sizeof(int));

		// get status bar DC and set font
		CClientDC dc(this->m_hWnd);
		HFONT hOldFont = dc.SelectFont(this->GetFont());

		// get status bar borders
		int arrBorders[3] = {};
		this->GetBorders(arrBorders);

		const int cchBuff = 128;
		TCHAR szBuff[cchBuff] = {};
		int cxLeft = arrBorders[0];

		// calculate right edge of each part
		for(int i = 0; i < nPanes; i++)
		{
			if(pPanes[i] == ID_DEFAULT_PANE)
			{
				// make very large, will be resized later
				pPanesPos[i] = INT_MAX / 2;
			}
			else
			{
				::LoadString(ModuleHelper::GetResourceInstance(), pPanes[i], szBuff, cchBuff);
				SIZE size = {};
				dc.GetTextExtent(szBuff, lstrlen(szBuff), &size);
				T* pT = static_cast<T*>(this);
				(void)pT;   // avoid level 4 warning
				pPanesPos[i] = cxLeft + size.cx + arrBorders[2] + 2 * pT->m_cxPaneMargin;
			}
			cxLeft = pPanesPos[i];
		}

		BOOL bRet = this->SetParts(nPanes, pPanesPos);

		if(bRet && bSetText)
		{
			for(int i = 0; i < nPanes; i++)
			{
				if(pPanes[i] != ID_DEFAULT_PANE)
				{
					::LoadString(ModuleHelper::GetResourceInstance(), pPanes[i], szBuff, cchBuff);
					SetPaneText(m_pPane[i], szBuff);
				}
			}
		}

		dc.SelectFont(hOldFont);
		return bRet;
	}

	bool GetPaneTextLength(int nPaneID, int* pcchLength = NULL, int* pnType = NULL) const
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		int nIndex  = GetPaneIndexFromID(nPaneID);
		if(nIndex == -1)
			return false;

		int nLength = this->GetTextLength(nIndex, pnType);
		if(pcchLength != NULL)
			*pcchLength = nLength;

		return true;
	}

	BOOL GetPaneText(int nPaneID, LPTSTR lpstrText, int* pcchLength = NULL, int* pnType = NULL) const
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		int nIndex  = GetPaneIndexFromID(nPaneID);
		if(nIndex == -1)
			return FALSE;

		int nLength = this->GetText(nIndex, lpstrText, pnType);
		if(pcchLength != NULL)
			*pcchLength = nLength;

		return TRUE;
	}

#ifdef __ATLSTR_H__
	BOOL GetPaneText(int nPaneID, ATL::CString& strText, int* pcchLength = NULL, int* pnType = NULL) const
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		int nIndex  = GetPaneIndexFromID(nPaneID);
		if(nIndex == -1)
			return FALSE;

		int nLength = this->GetText(nIndex, strText, pnType);
		if(pcchLength != NULL)
			*pcchLength = nLength;

		return TRUE;
	}
#endif // __ATLSTR_H__

	BOOL SetPaneText(int nPaneID, LPCTSTR lpstrText, int nType = 0)
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		int nIndex  = GetPaneIndexFromID(nPaneID);
		if(nIndex == -1)
			return FALSE;

		return this->SetText(nIndex, lpstrText, nType);
	}

	BOOL GetPaneRect(int nPaneID, LPRECT lpRect) const
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		int nIndex  = GetPaneIndexFromID(nPaneID);
		if(nIndex == -1)
			return FALSE;

		return this->GetRect(nIndex, lpRect);
	}

	BOOL SetPaneWidth(int nPaneID, int cxWidth)
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		ATLASSERT(nPaneID != ID_DEFAULT_PANE);   // Can't resize this one
		int nIndex  = GetPaneIndexFromID(nPaneID);
		if(nIndex == -1)
			return FALSE;

		// get pane positions
		ATL::CTempBuffer<int, _WTL_STACK_ALLOC_THRESHOLD> buff;
		int* pPanesPos = buff.Allocate(m_nPanes);
		if(pPanesPos == NULL)
			return FALSE;
		this->GetParts(m_nPanes, pPanesPos);
		// calculate offset
		int cxPaneWidth = pPanesPos[nIndex] - ((nIndex == 0) ? 0 : pPanesPos[nIndex - 1]);
		int cxOff = cxWidth - cxPaneWidth;
		// find variable width pane
		int nDef = m_nPanes;
		for(int i = 0; i < m_nPanes; i++)
		{
			if(m_pPane[i] == ID_DEFAULT_PANE)
			{
				nDef = i;
				break;
			}
		}
		// resize
		if(nIndex < nDef)   // before default pane
		{
			for(int i = nIndex; i < nDef; i++)
				pPanesPos[i] += cxOff;
				
		}
		else			// after default one
		{
			for(int i = nDef; i < nIndex; i++)
				pPanesPos[i] -= cxOff;
		}
		// set pane postions
		return this->SetParts(m_nPanes, pPanesPos);
	}

	BOOL GetPaneTipText(int nPaneID, LPTSTR lpstrText, int nSize) const
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		int nIndex  = GetPaneIndexFromID(nPaneID);
		if(nIndex == -1)
			return FALSE;

		this->GetTipText(nIndex, lpstrText, nSize);
		return TRUE;
	}

	BOOL SetPaneTipText(int nPaneID, LPCTSTR lpstrText)
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		int nIndex  = GetPaneIndexFromID(nPaneID);
		if(nIndex == -1)
			return FALSE;

		this->SetTipText(nIndex, lpstrText);
		return TRUE;
	}

	BOOL GetPaneIcon(int nPaneID, HICON& hIcon) const
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		int nIndex  = GetPaneIndexFromID(nPaneID);
		if(nIndex == -1)
			return FALSE;

		hIcon = this->GetIcon(nIndex);
		return TRUE;
	}

	BOOL SetPaneIcon(int nPaneID, HICON hIcon)
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		int nIndex  = GetPaneIndexFromID(nPaneID);
		if(nIndex == -1)
			return FALSE;

		return this->SetIcon(nIndex, hIcon);
	}

// Message map and handlers
	BEGIN_MSG_MAP(CMultiPaneStatusBarCtrlImpl< T >)
		MESSAGE_HANDLER(WM_SIZE, OnSize)
	END_MSG_MAP()

	LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		LRESULT lRet = this->DefWindowProc(uMsg, wParam, lParam);
		if((wParam != SIZE_MINIMIZED) && (m_nPanes > 0))
		{
			T* pT = static_cast<T*>(this);
			pT->UpdatePanesLayout();
		}
		return lRet;
	}

// Implementation
	BOOL UpdatePanesLayout()
	{
		// get pane positions
		ATL::CTempBuffer<int, _WTL_STACK_ALLOC_THRESHOLD> buff;
		int* pPanesPos = buff.Allocate(m_nPanes);
		ATLASSERT(pPanesPos != NULL);
		if(pPanesPos == NULL)
			return FALSE;
		int nRet = this->GetParts(m_nPanes, pPanesPos);
		ATLASSERT(nRet == m_nPanes);
		if(nRet != m_nPanes)
			return FALSE;
		// calculate offset
		RECT rcClient = {};
		this->GetClientRect(&rcClient);
		int cxOff = rcClient.right - pPanesPos[m_nPanes - 1];
		// Move panes left if size grip box is present
		if((this->GetStyle() & SBARS_SIZEGRIP) != 0)
			cxOff -= ::GetSystemMetrics(SM_CXVSCROLL) + ::GetSystemMetrics(SM_CXEDGE);
		// find variable width pane
		int i;
		for(i = 0; i < m_nPanes; i++)
		{
			if(m_pPane[i] == ID_DEFAULT_PANE)
				break;
		}
		// resize all panes from the variable one to the right
		if((i < m_nPanes) && (pPanesPos[i] + cxOff) > ((i == 0) ? 0 : pPanesPos[i - 1]))
		{
			for(; i < m_nPanes; i++)
				pPanesPos[i] += cxOff;
		}
		// set pane postions
		return this->SetParts(m_nPanes, pPanesPos);
	}

	int GetPaneIndexFromID(int nPaneID) const
	{
		for(int i = 0; i < m_nPanes; i++)
		{
			if(m_pPane[i] == nPaneID)
				return i;
		}

		return -1;   // not found
	}
};

class CMultiPaneStatusBarCtrl : public CMultiPaneStatusBarCtrlImpl<CMultiPaneStatusBarCtrl>
{
public:
	DECLARE_WND_SUPERCLASS(_T("WTL_MultiPaneStatusBar"), GetWndClassName())
};


///////////////////////////////////////////////////////////////////////////////
// CPaneContainer - provides header with title and close button for panes

// pane container extended styles
#define PANECNT_NOCLOSEBUTTON   0x00000001
#define PANECNT_VERTICAL        0x00000002
#define PANECNT_FLATBORDER      0x00000004
#define PANECNT_NOBORDER        0x00000008
#define PANECNT_DIVIDER         0x00000010
#define PANECNT_GRADIENT        0x00000020

template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CControlWinTraits>
class ATL_NO_VTABLE CPaneContainerImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >, public CCustomDraw< T >
{
public:
	DECLARE_WND_CLASS_EX2(NULL, T, 0, -1)

// Constants
	enum
	{
		m_cxyBorder = 2,
		m_cxyTextOffset = 4,
		m_cxyBtnOffset = 1,

		m_cchTitle = 80,

		m_cxImageTB = 13,
		m_cyImageTB = 11,
		m_cxyBtnAddTB = 7,

		m_cxToolBar = m_cxImageTB + m_cxyBtnAddTB + m_cxyBorder + m_cxyBtnOffset,

		m_xBtnImageLeft = 6,
		m_yBtnImageTop = 5,
		m_xBtnImageRight = 12,
		m_yBtnImageBottom = 11,

		m_nCloseBtnID = ID_PANE_CLOSE
	};

// Data members
	CToolBarCtrl m_tb;
	ATL::CWindow m_wndClient;
	int m_cxyHeader;
	TCHAR m_szTitle[m_cchTitle];
	DWORD m_dwExtendedStyle;   // Pane container specific extended styles
	HFONT m_hFont;
	bool m_bInternalFont;


// Constructor
	CPaneContainerImpl() : m_cxyHeader(0), m_dwExtendedStyle(0), m_hFont(NULL), m_bInternalFont(false)
	{
		m_szTitle[0] = 0;
	}

// Attributes
	DWORD GetPaneContainerExtendedStyle() const
	{
		return m_dwExtendedStyle;
	}

	DWORD SetPaneContainerExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)
	{
		DWORD dwPrevStyle = m_dwExtendedStyle;
		if(dwMask == 0)
			m_dwExtendedStyle = dwExtendedStyle;
		else
			m_dwExtendedStyle = (m_dwExtendedStyle & ~dwMask) | (dwExtendedStyle & dwMask);
		if(this->m_hWnd != NULL)
		{
			T* pT = static_cast<T*>(this);
			bool bUpdate = false;

			if(((dwPrevStyle & PANECNT_NOCLOSEBUTTON) != 0) && ((m_dwExtendedStyle & PANECNT_NOCLOSEBUTTON) == 0))   // add close button
			{
				pT->CreateCloseButton();
				bUpdate = true;
			}
			else if(((dwPrevStyle & PANECNT_NOCLOSEBUTTON) == 0) && ((m_dwExtendedStyle & PANECNT_NOCLOSEBUTTON) != 0))   // remove close button
			{
				pT->DestroyCloseButton();
				bUpdate = true;
			}

			if((dwPrevStyle & PANECNT_VERTICAL) != (m_dwExtendedStyle & PANECNT_VERTICAL))   // change orientation
			{
				pT->CalcSize();
				bUpdate = true;
			}

			if((dwPrevStyle & (PANECNT_FLATBORDER | PANECNT_NOBORDER)) != 
			   (m_dwExtendedStyle & (PANECNT_FLATBORDER | PANECNT_NOBORDER)))   // change border
			{
				bUpdate = true;
			}

			if((dwPrevStyle & PANECNT_GRADIENT) != (m_dwExtendedStyle & PANECNT_GRADIENT))   // change background
			{
				bUpdate = true;
			}

			if(bUpdate)
				pT->UpdateLayout();
		}
		return dwPrevStyle;
	}

	HWND GetClient() const
	{
		return m_wndClient;
	}

	HWND SetClient(HWND hWndClient)
	{
		HWND hWndOldClient = m_wndClient;
		m_wndClient = hWndClient;
		if(this->m_hWnd != NULL)
		{
			T* pT = static_cast<T*>(this);
			pT->UpdateLayout();
		}
		return hWndOldClient;
	}

	BOOL GetTitle(LPTSTR lpstrTitle, int cchLength) const
	{
		ATLASSERT(lpstrTitle != NULL);

		errno_t nRet = ATL::Checked::tcsncpy_s(lpstrTitle, cchLength, m_szTitle, _TRUNCATE);

		return ((nRet == 0) || (nRet == STRUNCATE));
	}

	BOOL SetTitle(LPCTSTR lpstrTitle)
	{
		ATLASSERT(lpstrTitle != NULL);

		errno_t nRet = ATL::Checked::tcsncpy_s(m_szTitle, m_cchTitle, lpstrTitle, _TRUNCATE);
		bool bRet = ((nRet == 0) || (nRet == STRUNCATE));
		if(bRet && (this->m_hWnd != NULL))
		{
			T* pT = static_cast<T*>(this);
			pT->UpdateLayout();
		}

		return bRet;
	}

	int GetTitleLength() const
	{
		return lstrlen(m_szTitle);
	}

// Methods
	HWND Create(HWND hWndParent, LPCTSTR lpstrTitle = NULL, DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
			DWORD dwExStyle = 0, UINT nID = 0, LPVOID lpCreateParam = NULL)
	{
		if(lpstrTitle != NULL)
			ATL::Checked::tcsncpy_s(m_szTitle, m_cchTitle, lpstrTitle, _TRUNCATE);
		return ATL::CWindowImpl< T, TBase, TWinTraits >::Create(hWndParent, this->rcDefault, NULL, dwStyle, dwExStyle, nID, lpCreateParam);
	}

	HWND Create(HWND hWndParent, UINT uTitleID, DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
			DWORD dwExStyle = 0, UINT nID = 0, LPVOID lpCreateParam = NULL)
	{
		if(uTitleID != 0U)
			::LoadString(ModuleHelper::GetResourceInstance(), uTitleID, m_szTitle, m_cchTitle);
		return ATL::CWindowImpl< T, TBase, TWinTraits >::Create(hWndParent, this->rcDefault, NULL, dwStyle, dwExStyle, nID, lpCreateParam);
	}

	BOOL SubclassWindow(HWND hWnd)
	{
		BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
		if(bRet != FALSE)
		{
			T* pT = static_cast<T*>(this);
			pT->Init();

			RECT rect = {};
			this->GetClientRect(&rect);
			pT->UpdateLayout(rect.right, rect.bottom);
		}

		return bRet;
	}

	BOOL EnableCloseButton(BOOL bEnable)
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		T* pT = static_cast<T*>(this);
		(void)pT;   // avoid level 4 warning
		return (m_tb.m_hWnd != NULL) ? m_tb.EnableButton(pT->m_nCloseBtnID, bEnable) : FALSE;
	}

	void UpdateLayout()
	{
		RECT rcClient = {};
		this->GetClientRect(&rcClient);
		T* pT = static_cast<T*>(this);
		pT->UpdateLayout(rcClient.right, rcClient.bottom);
	}

// Message map and handlers
	BEGIN_MSG_MAP(CPaneContainerImpl)
		MESSAGE_HANDLER(WM_CREATE, OnCreate)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
		MESSAGE_HANDLER(WM_SIZE, OnSize)
		MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
		MESSAGE_HANDLER(WM_GETFONT, OnGetFont)
		MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
		MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
		MESSAGE_HANDLER(WM_PAINT, OnPaint)
		MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
		MESSAGE_HANDLER(WM_NOTIFY, OnNotify)
		MESSAGE_HANDLER(WM_COMMAND, OnCommand)
		FORWARD_NOTIFICATIONS()
	END_MSG_MAP()

	LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		T* pT = static_cast<T*>(this);
		pT->Init();

		return 0;
	}

	LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		if(m_bInternalFont)
		{
			::DeleteObject(m_hFont);
			m_hFont = NULL;
			m_bInternalFont = false;
		}

		return 0;
	}

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

	LRESULT OnSetFocus(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		if(m_wndClient.m_hWnd != NULL)
			m_wndClient.SetFocus();
		return 0;
	}

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

	LRESULT OnSetFont(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		if(m_bInternalFont)
		{
			::DeleteObject(m_hFont);
			m_bInternalFont = false;
		}

		m_hFont = (HFONT)wParam;

		T* pT = static_cast<T*>(this);
		pT->CalcSize();

		if((BOOL)lParam != FALSE)
			pT->UpdateLayout();

		return 0;
	}

	LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		T* pT = static_cast<T*>(this);
		pT->DrawPaneTitleBackground((HDC)wParam);

		return 1;
	}

	LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		T* pT = static_cast<T*>(this);
		if(wParam != NULL)
		{
			pT->DrawPaneTitle((HDC)wParam);

			if(m_wndClient.m_hWnd == NULL)   // no client window
				pT->DrawPane((HDC)wParam);
		}
		else
		{
			CPaintDC dc(this->m_hWnd);
			pT->DrawPaneTitle(dc.m_hDC);

			if(m_wndClient.m_hWnd == NULL)   // no client window
				pT->DrawPane(dc.m_hDC);
		}

		return 0;
	}

	LRESULT OnNotify(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
	{
		if(m_tb.m_hWnd == NULL)
		{
			bHandled = FALSE;
			return 1;
		}

		T* pT = static_cast<T*>(this);
		(void)pT;   // avoid level 4 warning
		LPNMHDR lpnmh = (LPNMHDR)lParam;
		LRESULT lRet = 0;

		// pass toolbar custom draw notifications to the base class
		if((lpnmh->code == NM_CUSTOMDRAW) && (lpnmh->hwndFrom == m_tb.m_hWnd))
			lRet = CCustomDraw< T >::OnCustomDraw(0, lpnmh, bHandled);
		// tooltip notifications come with the tooltip window handle and button ID,
		// pass them to the parent if we don't handle them
		else if((lpnmh->code == TTN_GETDISPINFO) && (lpnmh->idFrom == pT->m_nCloseBtnID))
			bHandled = pT->GetToolTipText(lpnmh);
		// only let notifications not from the toolbar go to the parent
		else if((lpnmh->hwndFrom != m_tb.m_hWnd) && (lpnmh->idFrom != pT->m_nCloseBtnID))
			bHandled = FALSE;

		return lRet;
	}

	LRESULT OnCommand(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		// if command comes from the close button, substitute HWND of the pane container instead
		if((m_tb.m_hWnd != NULL) && ((HWND)lParam == m_tb.m_hWnd))
			return this->GetParent().SendMessage(WM_COMMAND, wParam, (LPARAM)this->m_hWnd);

		bHandled = FALSE;
		return 1;
	}

// Custom draw overrides
	DWORD OnPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/)
	{
		return CDRF_NOTIFYITEMDRAW;   // we need per-item notifications
	}

	DWORD OnItemPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/)
	{
		return CDRF_NOTIFYPOSTPAINT;
	}

	DWORD OnItemPostPaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
	{
		CDCHandle dc = lpNMCustomDraw->hdc;
		RECT& rc = lpNMCustomDraw->rc;

		RECT rcImage = { m_xBtnImageLeft, m_yBtnImageTop, m_xBtnImageRight + 1, m_yBtnImageBottom + 1 };
		::OffsetRect(&rcImage, rc.left, rc.top);
		T* pT = static_cast<T*>(this);

		if((lpNMCustomDraw->uItemState & CDIS_DISABLED) != 0)
		{
			RECT rcShadow = rcImage;
			::OffsetRect(&rcShadow, 1, 1);
			CPen pen1;
			pen1.CreatePen(PS_SOLID, 0, ::GetSysColor(COLOR_3DHILIGHT));
			pT->DrawButtonImage(dc, rcShadow, pen1);
			CPen pen2;
			pen2.CreatePen(PS_SOLID, 0, ::GetSysColor(COLOR_3DSHADOW));
			pT->DrawButtonImage(dc, rcImage, pen2);
		}
		else
		{
			if((lpNMCustomDraw->uItemState & CDIS_SELECTED) != 0)
				::OffsetRect(&rcImage, 1, 1);
			CPen pen;
			pen.CreatePen(PS_SOLID, 0, ::GetSysColor(COLOR_BTNTEXT));
			pT->DrawButtonImage(dc, rcImage, pen);
		}

		return CDRF_DODEFAULT;   // continue with the default item painting
	}

// Implementation - overrideable methods
	void Init()
	{
		if(m_hFont == NULL)
		{
			// The same as AtlCreateControlFont() for horizontal pane
			LOGFONT lf = {};
			ATLVERIFY(::SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &lf, 0) != FALSE);
			if(IsVertical())
				lf.lfEscapement = 900;   // 90 degrees
			m_hFont = ::CreateFontIndirect(&lf);
			m_bInternalFont = true;
		}

		T* pT = static_cast<T*>(this);
		pT->CalcSize();

		if((m_dwExtendedStyle & PANECNT_NOCLOSEBUTTON) == 0)
			pT->CreateCloseButton();
	}

	void UpdateLayout(int cxWidth, int cyHeight)
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		RECT rect = {};

		if(IsVertical())
		{
			::SetRect(&rect, 0, 0, m_cxyHeader, cyHeight);
			if(m_tb.m_hWnd != NULL)
				m_tb.SetWindowPos(NULL, m_cxyBorder, m_cxyBorder + m_cxyBtnOffset, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);

			if(m_wndClient.m_hWnd != NULL)
				m_wndClient.SetWindowPos(NULL, m_cxyHeader, 0, cxWidth - m_cxyHeader, cyHeight, SWP_NOZORDER);
			else
				rect.right = cxWidth;
		}
		else
		{
			::SetRect(&rect, 0, 0, cxWidth, m_cxyHeader);
			if(m_tb.m_hWnd != NULL)
				m_tb.SetWindowPos(NULL, rect.right - m_cxToolBar, m_cxyBorder + m_cxyBtnOffset, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);

			if(m_wndClient.m_hWnd != NULL)
				m_wndClient.SetWindowPos(NULL, 0, m_cxyHeader, cxWidth, cyHeight - m_cxyHeader, SWP_NOZORDER);
			else
				rect.bottom = cyHeight;
		}

		this->InvalidateRect(&rect);
	}

	void CreateCloseButton()
	{
		ATLASSERT(m_tb.m_hWnd == NULL);
		// create toolbar for the "x" button
		m_tb.Create(this->m_hWnd, this->rcDefault, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | CCS_NODIVIDER | CCS_NORESIZE | CCS_NOPARENTALIGN | CCS_NOMOVEY | TBSTYLE_TOOLTIPS | TBSTYLE_FLAT, 0);
		ATLASSERT(m_tb.IsWindow());

		if(m_tb.m_hWnd != NULL)
		{
			T* pT = static_cast<T*>(this);
			(void)pT;   // avoid level 4 warning

			m_tb.SetButtonStructSize();

			TBBUTTON tbbtn = {};
			tbbtn.idCommand = pT->m_nCloseBtnID;
			tbbtn.fsState = TBSTATE_ENABLED;
			tbbtn.fsStyle = BTNS_BUTTON;
			m_tb.AddButtons(1, &tbbtn);

			m_tb.SetBitmapSize(m_cxImageTB, m_cyImageTB);
			m_tb.SetButtonSize(m_cxImageTB + m_cxyBtnAddTB, m_cyImageTB + m_cxyBtnAddTB);

			if(IsVertical())
				m_tb.SetWindowPos(NULL, m_cxyBorder + m_cxyBtnOffset, m_cxyBorder + m_cxyBtnOffset, m_cxImageTB + m_cxyBtnAddTB, m_cyImageTB + m_cxyBtnAddTB + 1, SWP_NOZORDER | SWP_NOACTIVATE);
			else
				m_tb.SetWindowPos(NULL, 0, 0, m_cxImageTB + m_cxyBtnAddTB, m_cyImageTB + m_cxyBtnAddTB + 1, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
		}
	}

	void DestroyCloseButton()
	{
		if(m_tb.m_hWnd != NULL)
			m_tb.DestroyWindow();
	}

	void CalcSize()
	{
		T* pT = static_cast<T*>(this);
		CFontHandle font = pT->GetTitleFont();
		if(font.IsNull())
			font = (HFONT)::GetStockObject(SYSTEM_FONT);
		LOGFONT lf = {};
		font.GetLogFont(lf);
		if(IsVertical())
		{
			m_cxyHeader = m_cxImageTB + m_cxyBtnAddTB + m_cxyBorder + 1;
		}
		else
		{
			int cyFont = abs(lf.lfHeight) + m_cxyBorder + 2 * m_cxyTextOffset;
			int cyBtn = m_cyImageTB + m_cxyBtnAddTB + m_cxyBorder + 2 * m_cxyBtnOffset + 1;
			m_cxyHeader = __max(cyFont, cyBtn);
		}
	}

	HFONT GetTitleFont() const
	{
		return m_hFont;
	}

	BOOL GetToolTipText(LPNMHDR /*lpnmh*/)
	{
		return FALSE;
	}

	void DrawPaneTitle(CDCHandle dc)
	{
		RECT rect = {};
		this->GetClientRect(&rect);

		UINT uBorder = BF_LEFT | BF_TOP | BF_ADJUST;
		if(IsVertical())
		{
			rect.right = rect.left + m_cxyHeader;
			uBorder |= BF_BOTTOM;
		}
		else
		{
			rect.bottom = rect.top + m_cxyHeader;
			uBorder |= BF_RIGHT;
		}

		if((m_dwExtendedStyle & PANECNT_NOBORDER) == 0)
		{
			if((m_dwExtendedStyle & PANECNT_FLATBORDER) != 0)
				uBorder |= BF_FLAT;
			dc.DrawEdge(&rect, EDGE_ETCHED, uBorder);
		}

		if((m_dwExtendedStyle & PANECNT_DIVIDER) != 0)
		{
			uBorder = BF_FLAT | BF_ADJUST | (IsVertical() ? BF_RIGHT : BF_BOTTOM);
			dc.DrawEdge(&rect, BDR_SUNKENOUTER, uBorder);
		}

		// draw title text
		dc.SetTextColor(::GetSysColor(COLOR_WINDOWTEXT));
		dc.SetBkMode(TRANSPARENT);
		T* pT = static_cast<T*>(this);
		HFONT hFontOld = dc.SelectFont(pT->GetTitleFont());

		if(IsVertical())
		{
			rect.top += m_cxyTextOffset;
			rect.bottom -= m_cxyTextOffset;
			if(m_tb.m_hWnd != NULL)
				rect.top += m_cxToolBar;;

			RECT rcCalc = { rect.left, rect.bottom, rect.right, rect.top };
			int cxFont = dc.DrawText(m_szTitle, -1, &rcCalc, DT_TOP | DT_SINGLELINE | DT_END_ELLIPSIS | DT_CALCRECT);
			RECT rcText = {};
			rcText.left = (rect.right - rect.left - cxFont) / 2;
			rcText.right = rcText.left + (rect.bottom - rect.top);
			rcText.top = rect.bottom;
			rcText.bottom = rect.top;
			dc.DrawText(m_szTitle, -1, &rcText, DT_TOP | DT_SINGLELINE | DT_END_ELLIPSIS);
		}
		else
		{
			rect.left += m_cxyTextOffset;
			rect.right -= m_cxyTextOffset;
			if(m_tb.m_hWnd != NULL)
				rect.right -= m_cxToolBar;;

			dc.DrawText(m_szTitle, -1, &rect, DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS);
		}

		dc.SelectFont(hFontOld);
	}

	void DrawPaneTitleBackground(CDCHandle dc)
	{
		RECT rect = {};
		this->GetClientRect(&rect);
		if(IsVertical())
			rect.right = m_cxyHeader;
		else
			rect.bottom = m_cxyHeader;

		if((m_dwExtendedStyle & PANECNT_GRADIENT) != 0)
			dc.GradientFillRect(rect, ::GetSysColor(COLOR_WINDOW), ::GetSysColor(COLOR_3DFACE), IsVertical());
		else
			dc.FillRect(&rect, COLOR_3DFACE);
	}

	// called only if pane is empty
	void DrawPane(CDCHandle dc)
	{
		RECT rect = {};
		this->GetClientRect(&rect);
		if(IsVertical())
			rect.left += m_cxyHeader;
		else
			rect.top += m_cxyHeader;
		if((this->GetExStyle() & WS_EX_CLIENTEDGE) == 0)
			dc.DrawEdge(&rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
		dc.FillRect(&rect, COLOR_APPWORKSPACE);
	}

	// drawing helper - draws "x" button image
	void DrawButtonImage(CDCHandle dc, RECT& rcImage, HPEN hPen)
	{
		HPEN hPenOld = dc.SelectPen(hPen);

		dc.MoveTo(rcImage.left, rcImage.top);
		dc.LineTo(rcImage.right, rcImage.bottom);
		dc.MoveTo(rcImage.left + 1, rcImage.top);
		dc.LineTo(rcImage.right + 1, rcImage.bottom);

		dc.MoveTo(rcImage.left, rcImage.bottom - 1);
		dc.LineTo(rcImage.right, rcImage.top - 1);
		dc.MoveTo(rcImage.left + 1, rcImage.bottom - 1);
		dc.LineTo(rcImage.right + 1, rcImage.top - 1);

		dc.SelectPen(hPenOld);
	}

	bool IsVertical() const
	{
		return ((m_dwExtendedStyle & PANECNT_VERTICAL) != 0);
	}
};

class CPaneContainer : public CPaneContainerImpl<CPaneContainer>
{
public:
	DECLARE_WND_CLASS_EX(_T("WTL_PaneContainer"), 0, -1)
};


///////////////////////////////////////////////////////////////////////////////
// CSortListViewCtrl - implements sorting for a listview control

// sort listview extended styles
#define SORTLV_USESHELLBITMAPS	0x00000001

// Notification sent to parent when sort column is changed by user clicking header.  
#define SLVN_SORTCHANGED	LVN_LAST

// A LPNMSORTLISTVIEW is sent with the SLVN_SORTCHANGED notification
typedef struct tagNMSORTLISTVIEW
{
    NMHDR hdr;
    int iNewSortColumn;
    int iOldSortColumn;
} NMSORTLISTVIEW, *LPNMSORTLISTVIEW;

// Column sort types. Can be set on a per-column basis with the SetColumnSortType method.
enum
{
	LVCOLSORT_NONE,
	LVCOLSORT_TEXT,   // default
	LVCOLSORT_TEXTNOCASE,
	LVCOLSORT_LONG,
	LVCOLSORT_DOUBLE,
	LVCOLSORT_DECIMAL,
	LVCOLSORT_DATETIME,
	LVCOLSORT_DATE,
	LVCOLSORT_TIME,
	LVCOLSORT_CUSTOM,
	LVCOLSORT_LAST = LVCOLSORT_CUSTOM
};


template <class T>
class CSortListViewImpl
{
public:
	enum
	{
		m_cchCmpTextMax = 32, // overrideable
		m_cxSortImage = 16,
		m_cySortImage = 15,
		m_cxSortArrow = 11,
		m_cySortArrow = 6,
		m_iSortUp = 0,        // index of sort bitmaps
		m_iSortDown = 1,
		m_nShellSortUpID = 133
	};

	// passed to LVCompare functions as lParam1 and lParam2 
	struct LVCompareParam
	{
		int iItem;
		DWORD_PTR dwItemData;
		union
		{
			long lValue;
			double dblValue;
			DECIMAL decValue;
			LPCTSTR pszValue;
		};
	};
	
	// passed to LVCompare functions as the lParamSort parameter
	struct LVSortInfo
	{
		T* pT;
		int iSortCol;
		bool bDescending;
	};

	bool m_bSortDescending;
	bool m_bCommCtrl6;
	int m_iSortColumn;
	CBitmap m_bmSort[2];
	int m_fmtOldSortCol;
	HBITMAP m_hbmOldSortCol;
	DWORD m_dwSortLVExtendedStyle;
	ATL::CSimpleArray<WORD> m_arrColSortType;
	bool m_bUseWaitCursor;
	
	CSortListViewImpl() :
			m_bSortDescending(false),
			m_bCommCtrl6(false),
			m_iSortColumn(-1), 
			m_fmtOldSortCol(0),
			m_hbmOldSortCol(NULL),
			m_dwSortLVExtendedStyle(SORTLV_USESHELLBITMAPS),
			m_bUseWaitCursor(true)
	{
		DWORD dwMajor = 0;
		DWORD dwMinor = 0;
		HRESULT hRet = ATL::AtlGetCommCtrlVersion(&dwMajor, &dwMinor);
		m_bCommCtrl6 = SUCCEEDED(hRet) && (dwMajor >= 6);
	}
	
// Attributes
	void SetSortColumn(int iCol)
	{
		T* pT = static_cast<T*>(this);
		ATLASSERT(::IsWindow(pT->m_hWnd));
		CHeaderCtrl header = pT->GetHeader();
		ATLASSERT(header.m_hWnd != NULL);
		ATLASSERT((iCol >= -1) && (iCol < m_arrColSortType.GetSize()));

		int iOldSortCol = m_iSortColumn;
		m_iSortColumn = iCol;
		if(m_bCommCtrl6)
		{
			const int nMask = HDF_SORTUP | HDF_SORTDOWN;
			HDITEM hditem = { HDI_FORMAT };
			if((iOldSortCol != iCol) && (iOldSortCol >= 0) && header.GetItem(iOldSortCol, &hditem))
			{
				hditem.fmt &= ~nMask;
				header.SetItem(iOldSortCol, &hditem);
			}
			if((iCol >= 0) && header.GetItem(iCol, &hditem))
			{
				hditem.fmt &= ~nMask;
				hditem.fmt |= m_bSortDescending ? HDF_SORTDOWN : HDF_SORTUP;
				header.SetItem(iCol, &hditem);
			}
			return;
		}

		if(m_bmSort[m_iSortUp].IsNull())
			pT->CreateSortBitmaps();

		// restore previous sort column's bitmap, if any, and format
		HDITEM hditem = { HDI_BITMAP | HDI_FORMAT };
		if((iOldSortCol != iCol) && (iOldSortCol >= 0))
		{
			hditem.hbm = m_hbmOldSortCol;
			hditem.fmt = m_fmtOldSortCol;
			header.SetItem(iOldSortCol, &hditem);
		}

		// save new sort column's bitmap and format, and add our sort bitmap
		if((iCol >= 0) && header.GetItem(iCol, &hditem))
		{
			if(iOldSortCol != iCol)
			{
				m_fmtOldSortCol = hditem.fmt;
				m_hbmOldSortCol = hditem.hbm;
			}
			hditem.fmt &= ~HDF_IMAGE;
			hditem.fmt |= HDF_BITMAP | HDF_BITMAP_ON_RIGHT;
			int i = m_bSortDescending ? m_iSortDown : m_iSortUp;
			hditem.hbm = m_bmSort[i];
			header.SetItem(iCol, &hditem);
		}
	}

	int GetSortColumn() const
	{
		return m_iSortColumn;
	}

	void SetColumnSortType(int iCol, WORD wType)
	{
		ATLASSERT((iCol >= 0) && (iCol < m_arrColSortType.GetSize()));
		ATLASSERT((wType >= LVCOLSORT_NONE) && (wType <= LVCOLSORT_LAST));
		m_arrColSortType[iCol] = wType;
	}

	WORD GetColumnSortType(int iCol) const
	{
		ATLASSERT((iCol >= 0) && (iCol < m_arrColSortType.GetSize()));
		return m_arrColSortType[iCol];
	}

	int GetColumnCount() const
	{
		const T* pT = static_cast<const T*>(this);
		ATLASSERT(::IsWindow(pT->m_hWnd));
		CHeaderCtrl header = pT->GetHeader();
		return header.m_hWnd != NULL ? header.GetItemCount() : 0;
	}

	bool IsSortDescending() const
	{
		return m_bSortDescending;
	}

	DWORD GetSortListViewExtendedStyle() const
	{
		return m_dwSortLVExtendedStyle;
	}

	DWORD SetSortListViewExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)
	{
		DWORD dwPrevStyle = m_dwSortLVExtendedStyle;
		if(dwMask == 0)
			m_dwSortLVExtendedStyle = dwExtendedStyle;
		else
			m_dwSortLVExtendedStyle = (m_dwSortLVExtendedStyle & ~dwMask) | (dwExtendedStyle & dwMask);
		return dwPrevStyle;
	}

// Operations
	bool DoSortItems(int iCol, bool bDescending = false)
	{
		T* pT = static_cast<T*>(this);
		ATLASSERT(::IsWindow(pT->m_hWnd));
		ATLASSERT((iCol >= 0) && (iCol < m_arrColSortType.GetSize()));

		WORD wType = m_arrColSortType[iCol];
		if(wType == LVCOLSORT_NONE)
			return false;

		int nCount = pT->GetItemCount();
		if(nCount < 2)
		{
			m_bSortDescending = bDescending;
			SetSortColumn(iCol);
			return true;
		}

		CWaitCursor waitCursor(false);
		if(m_bUseWaitCursor)
			waitCursor.Set();

		LVCompareParam* pParam = NULL;
		ATLTRY(pParam = new LVCompareParam[nCount]);
		PFNLVCOMPARE pFunc = NULL;
		TCHAR pszTemp[pT->m_cchCmpTextMax] = {};
		bool bStrValue = false;

		switch(wType)
		{
		case LVCOLSORT_TEXT:
			pFunc = (PFNLVCOMPARE)pT->LVCompareText;
		case LVCOLSORT_TEXTNOCASE:
			if(pFunc == NULL)
				pFunc = (PFNLVCOMPARE)pT->LVCompareTextNoCase;
		case LVCOLSORT_CUSTOM:
			{
				if(pFunc == NULL)
					pFunc = (PFNLVCOMPARE)pT->LVCompareCustom;

				for(int i = 0; i < nCount; i++)
				{
					pParam[i].iItem = i;
					pParam[i].dwItemData = pT->GetItemData(i);
					pParam[i].pszValue = new TCHAR[pT->m_cchCmpTextMax];
					pT->GetItemText(i, iCol, (LPTSTR)pParam[i].pszValue, pT->m_cchCmpTextMax);
					pT->SetItemData(i, (DWORD_PTR)&pParam[i]);
				}
				bStrValue = true;
			}
			break;
		case LVCOLSORT_LONG:
			{
				pFunc = (PFNLVCOMPARE)pT->LVCompareLong;
				for(int i = 0; i < nCount; i++)
				{
					pParam[i].iItem = i;
					pParam[i].dwItemData = pT->GetItemData(i);
					pT->GetItemText(i, iCol, pszTemp, pT->m_cchCmpTextMax);
					pParam[i].lValue = pT->StrToLong(pszTemp);
					pT->SetItemData(i, (DWORD_PTR)&pParam[i]);
				}
			}
			break;
		case LVCOLSORT_DOUBLE:
			{
				pFunc = (PFNLVCOMPARE)pT->LVCompareDouble;
				for(int i = 0; i < nCount; i++)
				{
					pParam[i].iItem = i;
					pParam[i].dwItemData = pT->GetItemData(i);
					pT->GetItemText(i, iCol, pszTemp, pT->m_cchCmpTextMax);
					pParam[i].dblValue = pT->StrToDouble(pszTemp);
					pT->SetItemData(i, (DWORD_PTR)&pParam[i]);
				}
			}
			break;
		case LVCOLSORT_DECIMAL:
			{
				pFunc = (PFNLVCOMPARE)pT->LVCompareDecimal;
				for(int i = 0; i < nCount; i++)
				{
					pParam[i].iItem = i;
					pParam[i].dwItemData = pT->GetItemData(i);
					pT->GetItemText(i, iCol, pszTemp, pT->m_cchCmpTextMax);
					pT->StrToDecimal(pszTemp, &pParam[i].decValue);
					pT->SetItemData(i, (DWORD_PTR)&pParam[i]);
				}
			}
			break;
		case LVCOLSORT_DATETIME:
		case LVCOLSORT_DATE:
		case LVCOLSORT_TIME:
			{
				pFunc = (PFNLVCOMPARE)pT->LVCompareDouble;
				DWORD dwFlags = LOCALE_NOUSEROVERRIDE;
				if(wType == LVCOLSORT_DATE)
					dwFlags |= VAR_DATEVALUEONLY;
				else if(wType == LVCOLSORT_TIME)
					dwFlags |= VAR_TIMEVALUEONLY;
				for(int i = 0; i < nCount; i++)
				{
					pParam[i].iItem = i;
					pParam[i].dwItemData = pT->GetItemData(i);
					pT->GetItemText(i, iCol, pszTemp, pT->m_cchCmpTextMax);
					pParam[i].dblValue = pT->DateStrToDouble(pszTemp, dwFlags);
					pT->SetItemData(i, (DWORD_PTR)&pParam[i]);
				}
			}
			break;
		default:
			ATLTRACE2(atlTraceUI, 0, _T("Unknown value for sort type in CSortListViewImpl::DoSortItems()\n"));
			break;
		} // switch(wType)

		ATLASSERT(pFunc != NULL);
		LVSortInfo lvsi = { pT, iCol, bDescending };
		bool bRet = ((BOOL)pT->DefWindowProc(LVM_SORTITEMS, (WPARAM)&lvsi, (LPARAM)pFunc) != FALSE);
		for(int i = 0; i < nCount; i++)
		{
			DWORD_PTR dwItemData = pT->GetItemData(i);
			LVCompareParam* p = (LVCompareParam*)dwItemData;
			ATLASSERT(p != NULL);
			if(bStrValue)
				delete [] (TCHAR*)p->pszValue;
			pT->SetItemData(i, p->dwItemData);
		}
		delete [] pParam;

		if(bRet)
		{
			m_bSortDescending = bDescending;
			SetSortColumn(iCol);
		}

		if(m_bUseWaitCursor)
			waitCursor.Restore();

		return bRet;
	}

	void CreateSortBitmaps()
	{
		if((m_dwSortLVExtendedStyle & SORTLV_USESHELLBITMAPS) != 0)
		{
			bool bFree = false;
			LPCTSTR pszModule = _T("shell32.dll"); 
			HINSTANCE hShell = ::GetModuleHandle(pszModule);

			if (hShell == NULL)		
			{
				hShell = ::LoadLibrary(pszModule);
				bFree = true;
			}
 
			if (hShell != NULL)
			{
				bool bSuccess = true;
				for(int i = m_iSortUp; i <= m_iSortDown; i++)
				{
					if(!m_bmSort[i].IsNull())
						m_bmSort[i].DeleteObject();
					m_bmSort[i] = (HBITMAP)::LoadImage(hShell, MAKEINTRESOURCE(m_nShellSortUpID + i), 
						IMAGE_BITMAP, 0, 0, LR_LOADMAP3DCOLORS);
					if(m_bmSort[i].IsNull())
					{
						bSuccess = false;
						break;
					}
				}
				if(bFree)
					::FreeLibrary(hShell);
				if(bSuccess)
					return;
			}
		}

		T* pT = static_cast<T*>(this);
		for(int i = m_iSortUp; i <= m_iSortDown; i++)
		{
			if(!m_bmSort[i].IsNull())
				m_bmSort[i].DeleteObject();

			CDC dcMem;
			CClientDC dc(::GetDesktopWindow());
			dcMem.CreateCompatibleDC(dc.m_hDC);
			m_bmSort[i].CreateCompatibleBitmap(dc.m_hDC, m_cxSortImage, m_cySortImage);
			HBITMAP hbmOld = dcMem.SelectBitmap(m_bmSort[i]);
			RECT rc = { 0, 0, m_cxSortImage, m_cySortImage };
			pT->DrawSortBitmap(dcMem.m_hDC, i, &rc);
			dcMem.SelectBitmap(hbmOld);
			dcMem.DeleteDC();
		}
	}

	void NotifyParentSortChanged(int iNewSortCol, int iOldSortCol)
	{
		T* pT = static_cast<T*>(this);
		int nID = pT->GetDlgCtrlID();
		NMSORTLISTVIEW nm = { { pT->m_hWnd, (UINT_PTR)nID, SLVN_SORTCHANGED }, iNewSortCol, iOldSortCol };
		::SendMessage(pT->GetParent(), WM_NOTIFY, (WPARAM)nID, (LPARAM)&nm);
	}

// Overrideables
	int CompareItemsCustom(LVCompareParam* /*pItem1*/, LVCompareParam* /*pItem2*/, int /*iSortCol*/)
	{
		// pItem1 and pItem2 contain valid iItem, dwItemData, and pszValue members.
		// If item1 > item2 return 1, if item1 < item2 return -1, else return 0.
		return 0;
	}

	void DrawSortBitmap(CDCHandle dc, int iBitmap, LPRECT prc)
	{
		dc.FillRect(prc, ::GetSysColorBrush(COLOR_BTNFACE));	
		HBRUSH hbrOld = dc.SelectBrush(::GetSysColorBrush(COLOR_BTNSHADOW));
		CPen pen;
		pen.CreatePen(PS_SOLID, 0, ::GetSysColor(COLOR_BTNSHADOW));
		HPEN hpenOld = dc.SelectPen(pen);
		POINT ptOrg = { (m_cxSortImage - m_cxSortArrow) / 2, (m_cySortImage - m_cySortArrow) / 2 };
		if(iBitmap == m_iSortUp)
		{
			POINT pts[3] = 
			{
				{ ptOrg.x + m_cxSortArrow / 2, ptOrg.y },
				{ ptOrg.x, ptOrg.y + m_cySortArrow - 1 }, 
				{ ptOrg.x + m_cxSortArrow - 1, ptOrg.y + m_cySortArrow - 1 }
			};
			dc.Polygon(pts, 3);
		}
		else
		{
			POINT pts[3] = 
			{
				{ ptOrg.x, ptOrg.y },
				{ ptOrg.x + m_cxSortArrow / 2, ptOrg.y + m_cySortArrow - 1 },
				{ ptOrg.x + m_cxSortArrow - 1, ptOrg.y }
			};
			dc.Polygon(pts, 3);
		}
		dc.SelectBrush(hbrOld);
		dc.SelectPen(hpenOld);
	}

	double DateStrToDouble(LPCTSTR lpstr, DWORD dwFlags)
	{
		ATLASSERT(lpstr != NULL);
		if((lpstr == NULL) || (lpstr[0] == _T('\0')))
			return 0;

		USES_CONVERSION;
		HRESULT hRet = E_FAIL;
		DATE dRet = 0;
		if (FAILED(hRet = ::VarDateFromStr((LPOLESTR)T2COLE(lpstr), LANG_USER_DEFAULT, dwFlags, &dRet)))
		{
			ATLTRACE2(atlTraceUI, 0, _T("VarDateFromStr failed with result of 0x%8.8X\n"), hRet);
			dRet = 0;
		}
		return dRet;
	}

	long StrToLong(LPCTSTR lpstr)
	{
		ATLASSERT(lpstr != NULL);
		if((lpstr == NULL) || (lpstr[0] == _T('\0')))
			return 0;
		
		USES_CONVERSION;
		HRESULT hRet = E_FAIL;
		long lRet = 0;
		if (FAILED(hRet = ::VarI4FromStr((LPOLESTR)T2COLE(lpstr), LANG_USER_DEFAULT, LOCALE_NOUSEROVERRIDE, &lRet)))
		{
			ATLTRACE2(atlTraceUI, 0, _T("VarI4FromStr failed with result of 0x%8.8X\n"), hRet);
			lRet = 0;
		}
		return lRet;
	}

	double StrToDouble(LPCTSTR lpstr)
	{
		ATLASSERT(lpstr != NULL);
		if((lpstr == NULL) || (lpstr[0] == _T('\0')))
			return 0;

		USES_CONVERSION;
		HRESULT hRet = E_FAIL;
		double dblRet = 0;
		if (FAILED(hRet = ::VarR8FromStr((LPOLESTR)T2COLE(lpstr), LANG_USER_DEFAULT, LOCALE_NOUSEROVERRIDE, &dblRet)))
		{
			ATLTRACE2(atlTraceUI, 0, _T("VarR8FromStr failed with result of 0x%8.8X\n"), hRet);
			dblRet = 0;
		}
		return dblRet;
	}

	bool StrToDecimal(LPCTSTR lpstr, DECIMAL* pDecimal)
	{
		ATLASSERT(lpstr != NULL);
		ATLASSERT(pDecimal != NULL);
		if((lpstr == NULL) || (pDecimal == NULL))
			return false;

		USES_CONVERSION;
		HRESULT hRet = E_FAIL;
		if (FAILED(hRet = ::VarDecFromStr((LPOLESTR)T2COLE(lpstr), LANG_USER_DEFAULT, LOCALE_NOUSEROVERRIDE, pDecimal)))
		{
			ATLTRACE2(atlTraceUI, 0, _T("VarDecFromStr failed with result of 0x%8.8X\n"), hRet);
			pDecimal->Lo64 = 0;
			pDecimal->Hi32 = 0;
			pDecimal->signscale = 0;
			return false;
		}
		return true;
	}

// Overrideable PFNLVCOMPARE functions
	static int CALLBACK LVCompareText(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
	{
		ATLASSERT((lParam1 != NULL) && (lParam2 != NULL) && (lParamSort != NULL));

		LVCompareParam* pParam1 = (LVCompareParam*)lParam1;
		LVCompareParam* pParam2 = (LVCompareParam*)lParam2;
		LVSortInfo* pInfo = (LVSortInfo*)lParamSort;
		
		int nRet = lstrcmp(pParam1->pszValue, pParam2->pszValue);
		return pInfo->bDescending ? -nRet : nRet;
	}

	static int CALLBACK LVCompareTextNoCase(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
	{
		ATLASSERT((lParam1 != NULL) && (lParam2 != NULL) && (lParamSort != NULL));

		LVCompareParam* pParam1 = (LVCompareParam*)lParam1;
		LVCompareParam* pParam2 = (LVCompareParam*)lParam2;
		LVSortInfo* pInfo = (LVSortInfo*)lParamSort;
		
		int nRet = lstrcmpi(pParam1->pszValue, pParam2->pszValue);
		return pInfo->bDescending ? -nRet : nRet;
	}

	static int CALLBACK LVCompareLong(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
	{
		ATLASSERT((lParam1 != NULL) && (lParam2 != NULL) && (lParamSort != NULL));

		LVCompareParam* pParam1 = (LVCompareParam*)lParam1;
		LVCompareParam* pParam2 = (LVCompareParam*)lParam2;
		LVSortInfo* pInfo = (LVSortInfo*)lParamSort;
		
		int nRet = 0;
		if(pParam1->lValue > pParam2->lValue)
			nRet = 1;
		else if(pParam1->lValue < pParam2->lValue)
			nRet = -1;
		return pInfo->bDescending ? -nRet : nRet;
	}

	static int CALLBACK LVCompareDouble(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
	{
		ATLASSERT((lParam1 != NULL) && (lParam2 != NULL) && (lParamSort != NULL));

		LVCompareParam* pParam1 = (LVCompareParam*)lParam1;
		LVCompareParam* pParam2 = (LVCompareParam*)lParam2;
		LVSortInfo* pInfo = (LVSortInfo*)lParamSort;
		
		int nRet = 0;
		if(pParam1->dblValue > pParam2->dblValue)
			nRet = 1;
		else if(pParam1->dblValue < pParam2->dblValue)
			nRet = -1;
		return pInfo->bDescending ? -nRet : nRet;
	}

	static int CALLBACK LVCompareCustom(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
	{
		ATLASSERT((lParam1 != NULL) && (lParam2 != NULL) && (lParamSort != NULL));

		LVCompareParam* pParam1 = (LVCompareParam*)lParam1;
		LVCompareParam* pParam2 = (LVCompareParam*)lParam2;
		LVSortInfo* pInfo = (LVSortInfo*)lParamSort;
		
		int nRet = pInfo->pT->CompareItemsCustom(pParam1, pParam2, pInfo->iSortCol);
		return pInfo->bDescending ? -nRet : nRet;
	}

	static int CALLBACK LVCompareDecimal(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
	{
		ATLASSERT((lParam1 != NULL) && (lParam2 != NULL) && (lParamSort != NULL));

		LVCompareParam* pParam1 = (LVCompareParam*)lParam1;
		LVCompareParam* pParam2 = (LVCompareParam*)lParam2;
		LVSortInfo* pInfo = (LVSortInfo*)lParamSort;
		
		int nRet = (int)::VarDecCmp(&pParam1->decValue, &pParam2->decValue);
		nRet--;
		return pInfo->bDescending ? -nRet : nRet;
	}

	BEGIN_MSG_MAP(CSortListViewImpl)
		MESSAGE_HANDLER(LVM_INSERTCOLUMN, OnInsertColumn)
		MESSAGE_HANDLER(LVM_DELETECOLUMN, OnDeleteColumn)
		NOTIFY_CODE_HANDLER(HDN_ITEMCLICKA, OnHeaderItemClick)
		NOTIFY_CODE_HANDLER(HDN_ITEMCLICKW, OnHeaderItemClick)
		MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChange)
	END_MSG_MAP()

	LRESULT OnInsertColumn(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)	
	{
		T* pT = static_cast<T*>(this);
		LRESULT lRet = pT->DefWindowProc(uMsg, wParam, lParam);
		if(lRet == -1)
			return -1;

		WORD wType = 0;
		m_arrColSortType.Add(wType);
		int nCount = m_arrColSortType.GetSize();
		ATLASSERT(nCount == GetColumnCount());

		for(int i = nCount - 1; i > lRet; i--)
			m_arrColSortType[i] = m_arrColSortType[i - 1];
		m_arrColSortType[(int)lRet] = LVCOLSORT_TEXT;

		if(lRet <= m_iSortColumn)
			m_iSortColumn++;

		return lRet;
	}

	LRESULT OnDeleteColumn(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)	
	{
		T* pT = static_cast<T*>(this);
		LRESULT lRet = pT->DefWindowProc(uMsg, wParam, lParam);
		if(lRet == 0)
			return 0;

		int iCol = (int)wParam; 
		if(m_iSortColumn == iCol)
			m_iSortColumn = -1;
		else if(m_iSortColumn > iCol)
			m_iSortColumn--;
		m_arrColSortType.RemoveAt(iCol);

		return lRet;
	}

	LRESULT OnHeaderItemClick(int /*idCtrl*/, LPNMHDR pnmh, BOOL& bHandled)
	{
		LPNMHEADER p = (LPNMHEADER)pnmh;
		if(p->iButton == 0)
		{
			int iOld = m_iSortColumn;
			bool bDescending = (m_iSortColumn == p->iItem) ? !m_bSortDescending : false;
			if(DoSortItems(p->iItem, bDescending))
				NotifyParentSortChanged(p->iItem, iOld);				
		}
		bHandled = FALSE;
		return 0;
	}

	LRESULT OnSettingChange(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
	{
		if(wParam == SPI_SETNONCLIENTMETRICS)
			GetSystemSettings();
		bHandled = FALSE;
		return 0;
	}

	void GetSystemSettings()
	{
		if(!m_bCommCtrl6 && !m_bmSort[m_iSortUp].IsNull())
		{
			T* pT = static_cast<T*>(this);
			pT->CreateSortBitmaps();
			if(m_iSortColumn != -1)
				SetSortColumn(m_iSortColumn);
		}
	}

};


typedef ATL::CWinTraits<WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | LVS_REPORT | LVS_SHOWSELALWAYS , WS_EX_CLIENTEDGE>   CSortListViewCtrlTraits;

template <class T, class TBase = CListViewCtrl, class TWinTraits = CSortListViewCtrlTraits>
class ATL_NO_VTABLE CSortListViewCtrlImpl: public ATL::CWindowImpl<T, TBase, TWinTraits>, public CSortListViewImpl<T>
{
public:
	DECLARE_WND_SUPERCLASS2(NULL, T, TBase::GetWndClassName())

	bool SortItems(int iCol, bool bDescending = false)
	{
		return this->DoSortItems(iCol, bDescending);
	}
		
	BEGIN_MSG_MAP(CSortListViewCtrlImpl)
		MESSAGE_HANDLER(LVM_INSERTCOLUMN, CSortListViewImpl<T>::OnInsertColumn)
		MESSAGE_HANDLER(LVM_DELETECOLUMN, CSortListViewImpl<T>::OnDeleteColumn)
		NOTIFY_CODE_HANDLER(HDN_ITEMCLICKA, CSortListViewImpl<T>::OnHeaderItemClick)
		NOTIFY_CODE_HANDLER(HDN_ITEMCLICKW, CSortListViewImpl<T>::OnHeaderItemClick)
		MESSAGE_HANDLER(WM_SETTINGCHANGE, CSortListViewImpl<T>::OnSettingChange)
	END_MSG_MAP()
};

class CSortListViewCtrl : public CSortListViewCtrlImpl<CSortListViewCtrl>
{
public:
	DECLARE_WND_SUPERCLASS(_T("WTL_SortListViewCtrl"), GetWndClassName())
};


///////////////////////////////////////////////////////////////////////////////
// CTabView - implements tab view window

// TabView Notifications
#define TBVN_PAGEACTIVATED   (0U-741)
#define TBVN_CONTEXTMENU     (0U-742)

// Notification data for TBVN_CONTEXTMENU
struct TBVCONTEXTMENUINFO
{
	NMHDR hdr;
	POINT pt;
};

typedef TBVCONTEXTMENUINFO* LPTBVCONTEXTMENUINFO;


template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CControlWinTraits>
class ATL_NO_VTABLE CTabViewImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >
{
public:
	DECLARE_WND_CLASS_EX2(NULL, T, 0, COLOR_APPWORKSPACE)

// Declarations and enums
	struct TABVIEWPAGE
	{
		HWND hWnd;
		LPTSTR lpstrTitle;
		LPVOID pData;
	};

	struct TCITEMEXTRA
	{
		TCITEMHEADER tciheader;
		TABVIEWPAGE tvpage;

		operator LPTCITEM() { return (LPTCITEM)this; }
	};

	enum
	{
		m_nTabID = 1313,
		m_cxMoveMark = 6,
		m_cyMoveMark = 3,
		m_nMenuItemsMax = (ID_WINDOW_TABLAST - ID_WINDOW_TABFIRST + 1)
	};

	enum { _nAutoScrollTimerID = 4321 };

	enum AutoScroll
	{
		_AUTOSCROLL_NONE = 0,
		_AUTOSCROLL_LEFT = -1,
		_AUTOSCROLL_RIGHT = 1
	};

// Data members
	ATL::CContainedWindowT<CTabCtrl> m_tab;
	int m_cyTabHeight;

	int m_nActivePage;

	int m_nInsertItem;
	POINT m_ptStartDrag;

	CMenuHandle m_menu;

	int m_cchTabTextLength;

	int m_nMenuItemsCount;

	ATL::CWindow m_wndTitleBar;
	LPTSTR m_lpstrTitleBarBase;
	int m_cchTitleBarLength;

	CImageList m_ilDrag;

	AutoScroll m_AutoScroll;
	CUpDownCtrl m_ud;

	bool m_bDestroyPageOnRemove:1;
	bool m_bDestroyImageList:1;
	bool m_bActivePageMenuItem:1;
	bool m_bActiveAsDefaultMenuItem:1;
	bool m_bEmptyMenuItem:1;
	bool m_bWindowsMenuItem:1;
	bool m_bNoTabDrag:1;
	bool m_bNoTabDragAutoScroll:1;
	// internal
	bool m_bTabCapture:1;
	bool m_bTabDrag:1;
	bool m_bInternalFont:1;

// Constructor/destructor
	CTabViewImpl() :
			m_tab(this, 1),
			m_cyTabHeight(0),
			m_nActivePage(-1),
			m_nInsertItem(-1), 
			m_cchTabTextLength(30), 
			m_nMenuItemsCount(10), 
			m_lpstrTitleBarBase(NULL), 
			m_cchTitleBarLength(100), 
	                m_AutoScroll(_AUTOSCROLL_NONE), 
			m_bDestroyPageOnRemove(true), 
			m_bDestroyImageList(true), 
			m_bActivePageMenuItem(true), 
			m_bActiveAsDefaultMenuItem(false), 
			m_bEmptyMenuItem(false), 
			m_bWindowsMenuItem(false), 
			m_bNoTabDrag(false), 
	                m_bNoTabDragAutoScroll(false), 
			m_bTabCapture(false), 
			m_bTabDrag(false), 
			m_bInternalFont(false)
	{
		m_ptStartDrag.x = 0;
		m_ptStartDrag.y = 0;
	}

	~CTabViewImpl()
	{
		delete [] m_lpstrTitleBarBase;
	}

// Message filter function - to be called from PreTranslateMessage of the main window
	BOOL PreTranslateMessage(MSG* pMsg)
	{
		if(this->IsWindow() == FALSE)
			return FALSE;

		BOOL bRet = FALSE;

		// Check for TabView built-in accelerators (Ctrl+Tab/Ctrl+Shift+Tab - next/previous page)
		int nCount = GetPageCount();
		if(nCount > 0)
		{
			bool bControl = (::GetKeyState(VK_CONTROL) < 0);
			if((pMsg->message == WM_KEYDOWN) && (pMsg->wParam == VK_TAB) && bControl)
			{
				if(nCount > 1)
				{
					int nPage = m_nActivePage;
					bool bShift = (::GetKeyState(VK_SHIFT) < 0);
					if(bShift)
						nPage = (nPage > 0) ? (nPage - 1) : (nCount - 1);
					else
						nPage = ((nPage >= 0) && (nPage < (nCount - 1))) ? (nPage + 1) : 0;

					SetActivePage(nPage);
					T* pT = static_cast<T*>(this);
					pT->OnPageActivated(m_nActivePage);
				}

				bRet = TRUE;
			}
		}

		// If we are doing drag-drop, check for Escape key that cancels it
		if(bRet == FALSE)
		{
			if(m_bTabCapture && (pMsg->message == WM_KEYDOWN) && (pMsg->wParam == VK_ESCAPE))
			{
				::ReleaseCapture();
				bRet = TRUE;
			}
		}

		// Pass the message to the active page
		if(bRet == FALSE)
		{
			if(m_nActivePage != -1)
				bRet = (BOOL)::SendMessage(GetPageHWND(m_nActivePage), WM_FORWARDMSG, 0, (LPARAM)pMsg);
		}

		return bRet;
	}

// Attributes
	int GetPageCount() const
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		return m_tab.GetItemCount();
	}

	int GetActivePage() const
	{
		return m_nActivePage;
	}

	void SetActivePage(int nPage)
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		ATLASSERT(IsValidPageIndex(nPage));

		T* pT = static_cast<T*>(this);

		this->SetRedraw(FALSE);

		if(m_nActivePage != -1)
			::ShowWindow(GetPageHWND(m_nActivePage), SW_HIDE);
		m_nActivePage = nPage;
		m_tab.SetCurSel(m_nActivePage);
		::ShowWindow(GetPageHWND(m_nActivePage), SW_SHOW);

		pT->UpdateLayout();

		this->SetRedraw(TRUE);
		this->RedrawWindow(NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);

		if(::GetFocus() != m_tab.m_hWnd)
			::SetFocus(GetPageHWND(m_nActivePage));

		pT->UpdateTitleBar();
		pT->UpdateMenu();
	}

	HIMAGELIST GetImageList() const
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		return m_tab.GetImageList();
	}

	HIMAGELIST SetImageList(HIMAGELIST hImageList)
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		return m_tab.SetImageList(hImageList);
	}

	void SetWindowMenu(HMENU hMenu)
	{
		ATLASSERT(::IsWindow(this->m_hWnd));

		m_menu = hMenu;

		T* pT = static_cast<T*>(this);
		pT->UpdateMenu();
	}

	void SetTitleBarWindow(HWND hWnd)
	{
		ATLASSERT(::IsWindow(this->m_hWnd));

		delete [] m_lpstrTitleBarBase;
		m_lpstrTitleBarBase = NULL;

		m_wndTitleBar = hWnd;
		if(hWnd == NULL)
			return;

		int cchLen = m_wndTitleBar.GetWindowTextLength() + 1;
		ATLTRY(m_lpstrTitleBarBase = new TCHAR[cchLen]);
		if(m_lpstrTitleBarBase != NULL)
		{
			m_wndTitleBar.GetWindowText(m_lpstrTitleBarBase, cchLen);
			T* pT = static_cast<T*>(this);
			pT->UpdateTitleBar();
		}
	}

// Page attributes
	HWND GetPageHWND(int nPage) const
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		ATLASSERT(IsValidPageIndex(nPage));

		TCITEMEXTRA tcix = {};
		tcix.tciheader.mask = TCIF_PARAM;
		m_tab.GetItem(nPage, tcix);

		return tcix.tvpage.hWnd;
	}

	LPCTSTR GetPageTitle(int nPage) const
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		ATLASSERT(IsValidPageIndex(nPage));

		TCITEMEXTRA tcix = {};
		tcix.tciheader.mask = TCIF_PARAM;
		if(m_tab.GetItem(nPage, tcix) == FALSE)
			return NULL;

		return tcix.tvpage.lpstrTitle;
	}

	bool SetPageTitle(int nPage, LPCTSTR lpstrTitle)
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		ATLASSERT(IsValidPageIndex(nPage));

		T* pT = static_cast<T*>(this);

		int cchBuff = lstrlen(lpstrTitle) + 1;
		LPTSTR lpstrBuff = NULL;
		ATLTRY(lpstrBuff = new TCHAR[cchBuff]);
		if(lpstrBuff == NULL)
			return false;

		ATL::Checked::tcscpy_s(lpstrBuff, cchBuff, lpstrTitle);
		TCITEMEXTRA tcix = {};
		tcix.tciheader.mask = TCIF_PARAM;
		if(m_tab.GetItem(nPage, tcix) == FALSE)
			return false;

		ATL::CTempBuffer<TCHAR, _WTL_STACK_ALLOC_THRESHOLD> buff;
		LPTSTR lpstrTabText = buff.Allocate(m_cchTabTextLength + 1);
		if(lpstrTabText == NULL)
			return false;

		delete [] tcix.tvpage.lpstrTitle;

		pT->ShortenTitle(lpstrTitle, lpstrTabText, m_cchTabTextLength + 1);

		tcix.tciheader.mask = TCIF_TEXT | TCIF_PARAM;
		tcix.tciheader.pszText = lpstrTabText;
		tcix.tvpage.lpstrTitle = lpstrBuff;
		if(m_tab.SetItem(nPage, tcix) == FALSE)
			return false;

		pT->UpdateTitleBar();
		pT->UpdateMenu();

		return true;
	}

	LPVOID GetPageData(int nPage) const
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		ATLASSERT(IsValidPageIndex(nPage));

		TCITEMEXTRA tcix = {};
		tcix.tciheader.mask = TCIF_PARAM;
		m_tab.GetItem(nPage, tcix);

		return tcix.tvpage.pData;
	}

	LPVOID SetPageData(int nPage, LPVOID pData)
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		ATLASSERT(IsValidPageIndex(nPage));

		TCITEMEXTRA tcix = {};
		tcix.tciheader.mask = TCIF_PARAM;
		m_tab.GetItem(nPage, tcix);
		LPVOID pDataOld = tcix.tvpage.pData;

		tcix.tvpage.pData = pData;
		m_tab.SetItem(nPage, tcix);

		return pDataOld;
	}

	int GetPageImage(int nPage) const
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		ATLASSERT(IsValidPageIndex(nPage));

		TCITEMEXTRA tcix = {};
		tcix.tciheader.mask = TCIF_IMAGE;
		m_tab.GetItem(nPage, tcix);

		return tcix.tciheader.iImage;
	}

	int SetPageImage(int nPage, int nImage)
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		ATLASSERT(IsValidPageIndex(nPage));

		TCITEMEXTRA tcix = {};
		tcix.tciheader.mask = TCIF_IMAGE;
		m_tab.GetItem(nPage, tcix);
		int nImageOld = tcix.tciheader.iImage;

		tcix.tciheader.iImage = nImage;
		m_tab.SetItem(nPage, tcix);

		return nImageOld;
	}

// Operations
	bool AddPage(HWND hWndView, LPCTSTR lpstrTitle, int nImage = -1, LPVOID pData = NULL)
	{
		return InsertPage(GetPageCount(), hWndView, lpstrTitle, nImage, pData);
	}

	bool InsertPage(int nPage, HWND hWndView, LPCTSTR lpstrTitle, int nImage = -1, LPVOID pData = NULL)
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		ATLASSERT((nPage == GetPageCount()) || IsValidPageIndex(nPage));

		T* pT = static_cast<T*>(this);

		int cchBuff = lstrlen(lpstrTitle) + 1;
		LPTSTR lpstrBuff = NULL;
		ATLTRY(lpstrBuff = new TCHAR[cchBuff]);
		if(lpstrBuff == NULL)
			return false;

		ATL::Checked::tcscpy_s(lpstrBuff, cchBuff, lpstrTitle);

		ATL::CTempBuffer<TCHAR, _WTL_STACK_ALLOC_THRESHOLD> buff;
		LPTSTR lpstrTabText = buff.Allocate(m_cchTabTextLength + 1);
		if(lpstrTabText == NULL)
			return false;

		pT->ShortenTitle(lpstrTitle, lpstrTabText, m_cchTabTextLength + 1);

		this->SetRedraw(FALSE);

		TCITEMEXTRA tcix = {};
		tcix.tciheader.mask = TCIF_TEXT | TCIF_IMAGE | TCIF_PARAM;
		tcix.tciheader.pszText = lpstrTabText;
		tcix.tciheader.iImage = nImage;
		tcix.tvpage.hWnd = hWndView;
		tcix.tvpage.lpstrTitle = lpstrBuff;
		tcix.tvpage.pData = pData;
		int nItem = m_tab.InsertItem(nPage, tcix);
		if(nItem == -1)
		{
			delete [] lpstrBuff;
			this->SetRedraw(TRUE);
			return false;
		}

		// adjust active page index, if inserted before it
		if(nPage <= m_nActivePage)
			m_nActivePage++;

		SetActivePage(nItem);
		pT->OnPageActivated(m_nActivePage);

		if(GetPageCount() == 1)
			pT->ShowTabControl(true);

		pT->UpdateLayout();

		this->SetRedraw(TRUE);
		this->RedrawWindow(NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);

		return true;
	}

	void RemovePage(int nPage)
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		ATLASSERT(IsValidPageIndex(nPage));

		T* pT = static_cast<T*>(this);

		this->SetRedraw(FALSE);

		if(GetPageCount() == 1)
			pT->ShowTabControl(false);

		if(m_bDestroyPageOnRemove)
			::DestroyWindow(GetPageHWND(nPage));
		else
			::ShowWindow(GetPageHWND(nPage), SW_HIDE);
		LPTSTR lpstrTitle = (LPTSTR)GetPageTitle(nPage);
		delete [] lpstrTitle;

		ATLVERIFY(m_tab.DeleteItem(nPage) != FALSE);

		if(m_nActivePage == nPage)
		{
			m_nActivePage = -1;

			if(nPage > 0)
			{
				SetActivePage(nPage - 1);
			}
			else if(GetPageCount() > 0)
			{
				SetActivePage(nPage);
			}
			else
			{
				this->SetRedraw(TRUE);
				this->Invalidate();
				this->UpdateWindow();
				pT->UpdateTitleBar();
				pT->UpdateMenu();
			}
		}
		else
		{
			nPage = (nPage < m_nActivePage) ? (m_nActivePage - 1) : m_nActivePage;
			m_nActivePage = -1;
			SetActivePage(nPage);
		}

		pT->OnPageActivated(m_nActivePage);
	}

	void RemoveAllPages()
	{
		ATLASSERT(::IsWindow(this->m_hWnd));

		if(GetPageCount() == 0)
			return;

		T* pT = static_cast<T*>(this);

		this->SetRedraw(FALSE);

		pT->ShowTabControl(false);

		for(int i = 0; i < GetPageCount(); i++)
		{
			if(m_bDestroyPageOnRemove)
				::DestroyWindow(GetPageHWND(i));
			else
				::ShowWindow(GetPageHWND(i), SW_HIDE);
			LPTSTR lpstrTitle = (LPTSTR)GetPageTitle(i);
			delete [] lpstrTitle;
		}
		m_tab.DeleteAllItems();

		m_nActivePage = -1;
		pT->OnPageActivated(m_nActivePage);

		this->SetRedraw(TRUE);
		this->Invalidate();
		this->UpdateWindow();

		pT->UpdateTitleBar();
		pT->UpdateMenu();
	}

	int PageIndexFromHwnd(HWND hWnd) const
	{
		int nIndex = -1;

		for(int i = 0; i < GetPageCount(); i++)
		{
			if(GetPageHWND(i) == hWnd)
			{
				nIndex = i;
				break;
			}
		}

		return nIndex;
	}

	void BuildWindowMenu(HMENU hMenu, int nMenuItemsCount = 10, bool bEmptyMenuItem = true, bool bWindowsMenuItem = true, bool bActivePageMenuItem = true, bool bActiveAsDefaultMenuItem = false)
	{
		ATLASSERT(::IsWindow(this->m_hWnd));

		CMenuHandle menu = hMenu;
		T* pT = static_cast<T*>(this);
		(void)pT;   // avoid level 4 warning
		int nFirstPos = 0;

		// Find first menu item in our range
		for(nFirstPos = 0; nFirstPos < menu.GetMenuItemCount(); nFirstPos++)
		{
			UINT nID = menu.GetMenuItemID(nFirstPos);
			if(((nID >= ID_WINDOW_TABFIRST) && (nID <= ID_WINDOW_TABLAST)) || (nID == ID_WINDOW_SHOWTABLIST))
				break;
		}

		// Remove all menu items for tab pages
		BOOL bRet = TRUE;
		while(bRet != FALSE)
			bRet = menu.DeleteMenu(nFirstPos, MF_BYPOSITION);

		// Add separator if it's not already there
		int nPageCount = GetPageCount();
		if((bWindowsMenuItem || (nPageCount > 0)) && (nFirstPos > 0))
		{
			CMenuItemInfo mii;
			mii.fMask = MIIM_TYPE;
			menu.GetMenuItemInfo(nFirstPos - 1, TRUE, &mii);
			if((nFirstPos <= 0) || ((mii.fType & MFT_SEPARATOR) == 0))
			{
				menu.AppendMenu(MF_SEPARATOR);
				nFirstPos++;
			}
		}

		// Add menu items for all pages
		if(nPageCount > 0)
		{
			// Append menu items for all pages
			const int cchPrefix = 3;   // 2 digits + space
			nMenuItemsCount = __min(__min(nPageCount, nMenuItemsCount), (int)m_nMenuItemsMax);
			ATLASSERT(nMenuItemsCount < 100);   // 2 digits only
			if(nMenuItemsCount >= 100)
				nMenuItemsCount = 99;

			for(int i = 0; i < nMenuItemsCount; i++)
			{
				LPCTSTR lpstrTitle = GetPageTitle(i);
				int nLen = lstrlen(lpstrTitle);
				ATL::CTempBuffer<TCHAR, _WTL_STACK_ALLOC_THRESHOLD> buff;
				LPTSTR lpstrText = buff.Allocate(cchPrefix + nLen + 1);
				ATLASSERT(lpstrText != NULL);
				if(lpstrText != NULL)
				{
					LPCTSTR lpstrFormat = (i < 9) ? _T("&%i %s") : _T("%i %s");
					_stprintf_s(lpstrText, cchPrefix + nLen + 1, lpstrFormat, i + 1, lpstrTitle);
					menu.AppendMenu(MF_STRING, ID_WINDOW_TABFIRST + i, lpstrText);
				}
			}

			// Mark active page
			if(bActivePageMenuItem && (m_nActivePage != -1))
			{
				if(bActiveAsDefaultMenuItem)
				{
					menu.SetMenuDefaultItem((UINT)-1,  TRUE);
					menu.SetMenuDefaultItem(nFirstPos + m_nActivePage,  TRUE);
				}
				else
				{
					menu.CheckMenuRadioItem(nFirstPos, nFirstPos + nMenuItemsCount, nFirstPos + m_nActivePage, MF_BYPOSITION);
				}
			}
		}
		else
		{
			if(bEmptyMenuItem)
			{
				menu.AppendMenu(MF_BYPOSITION | MF_STRING, ID_WINDOW_TABFIRST, pT->GetEmptyListText());
				menu.EnableMenuItem(ID_WINDOW_TABFIRST, MF_GRAYED);
			}

			// Remove separator if nothing else is there
			if(!bEmptyMenuItem && !bWindowsMenuItem && (nFirstPos > 0))
			{
				CMenuItemInfo mii;
				mii.fMask = MIIM_TYPE;
				menu.GetMenuItemInfo(nFirstPos - 1, TRUE, &mii);
				if((mii.fType & MFT_SEPARATOR) != 0)
					menu.DeleteMenu(nFirstPos - 1, MF_BYPOSITION);
			}
		}

		// Add "Windows..." menu item
		if(bWindowsMenuItem)
			menu.AppendMenu(MF_BYPOSITION | MF_STRING, ID_WINDOW_SHOWTABLIST, pT->GetWindowsMenuItemText());
	}

	BOOL SubclassWindow(HWND hWnd)
	{
		BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
		if(bRet != FALSE)
		{
			T* pT = static_cast<T*>(this);
			pT->CreateTabControl();
			pT->UpdateLayout();
		}

		return bRet;
	}

// Message map and handlers
	BEGIN_MSG_MAP(CTabViewImpl)
		MESSAGE_HANDLER(WM_CREATE, OnCreate)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
		MESSAGE_HANDLER(WM_SIZE, OnSize)
		MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
		MESSAGE_HANDLER(WM_GETFONT, OnGetFont)
		MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
		MESSAGE_HANDLER(WM_TIMER, OnTimer)
		MESSAGE_HANDLER(WM_CONTEXTMENU, OnTabContextMenu)
		NOTIFY_HANDLER(m_nTabID, TCN_SELCHANGE, OnTabChanged)
		NOTIFY_ID_HANDLER(m_nTabID, OnTabNotification)
		NOTIFY_CODE_HANDLER(TTN_GETDISPINFO, OnTabGetDispInfo)
		FORWARD_NOTIFICATIONS()
	ALT_MSG_MAP(1)   // tab control
		MESSAGE_HANDLER(WM_LBUTTONDOWN, OnTabLButtonDown)
		MESSAGE_HANDLER(WM_LBUTTONUP, OnTabLButtonUp)
		MESSAGE_HANDLER(WM_CAPTURECHANGED, OnTabCaptureChanged)
		MESSAGE_HANDLER(WM_MOUSEMOVE, OnTabMouseMove)
	END_MSG_MAP()

	LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		T* pT = static_cast<T*>(this);
		pT->CreateTabControl();

		return 0;
	}

	LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		RemoveAllPages();

		if(m_bDestroyImageList)
		{
			CImageList il = m_tab.SetImageList(NULL);
			if(il.m_hImageList != NULL)
				il.Destroy();
		}

		if(m_bInternalFont)
		{
			HFONT hFont = m_tab.GetFont();
			m_tab.SetFont(NULL, FALSE);
			::DeleteObject(hFont);
			m_bInternalFont = false;
		}

		m_ud.m_hWnd = NULL;

		return 0;
	}

	LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		T* pT = static_cast<T*>(this);
		pT->UpdateLayout();
		return 0;
	}

	LRESULT OnSetFocus(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		if(m_nActivePage != -1)
			::SetFocus(GetPageHWND(m_nActivePage));
		return 0;
	}

	LRESULT OnGetFont(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		return m_tab.SendMessage(WM_GETFONT);
	}

	LRESULT OnSetFont(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		if(m_bInternalFont)
		{
			HFONT hFont = m_tab.GetFont();
			m_tab.SetFont(NULL, FALSE);
			::DeleteObject(hFont);
			m_bInternalFont = false;
		}

		m_tab.SendMessage(WM_SETFONT, wParam, lParam);

		T* pT = static_cast<T*>(this);
		m_cyTabHeight = pT->CalcTabHeight();

		if((BOOL)lParam != FALSE)
			pT->UpdateLayout();

		return 0;
	}

	LRESULT OnTimer(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
	{
		if(wParam == _nAutoScrollTimerID)
		{
			T* pT = static_cast<T*>(this);
			pT->DoAutoScroll();
		}
		else
		{
			bHandled = FALSE;
		}

		return 0;
	}

	LRESULT OnTabContextMenu(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
		int nPage = m_nActivePage;
		bool bAction = false;
		if((HWND)wParam == m_tab.m_hWnd)
		{
			if((pt.x == -1) && (pt.y == -1))   // keyboard
			{
				RECT rect = {};
				m_tab.GetItemRect(m_nActivePage, &rect);
				pt.x = rect.left;
				pt.y = rect.bottom;
				m_tab.ClientToScreen(&pt);
				bAction = true;
			}
			else if(::WindowFromPoint(pt) == m_tab.m_hWnd)
			{
				TCHITTESTINFO hti = {};
				hti.pt = pt;
				this->ScreenToClient(&hti.pt);
				nPage = m_tab.HitTest(&hti);

				bAction = true;
			}
		}

		if(bAction)
		{
			T* pT = static_cast<T*>(this);
			pT->OnContextMenu(nPage, pt);
		}
		else
		{
			bHandled = FALSE;
		}

		return 0;
	}

	LRESULT OnTabChanged(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
	{
		SetActivePage(m_tab.GetCurSel());
		T* pT = static_cast<T*>(this);
		pT->OnPageActivated(m_nActivePage);

		return 0;
	}

	LRESULT OnTabNotification(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
	{
		// nothing to do - this just blocks all tab control
		// notifications from being propagated further
		return 0;
	}

	LRESULT OnTabGetDispInfo(int /*idCtrl*/, LPNMHDR pnmh, BOOL& bHandled)
	{
		LPNMTTDISPINFO pTTDI = (LPNMTTDISPINFO)pnmh;
		if(pTTDI->hdr.hwndFrom == m_tab.GetTooltips())
		{
			T* pT = static_cast<T*>(this);
			pT->UpdateTooltipText(pTTDI);
		}
		else
		{
			bHandled = FALSE;
		}

		return 0;
	}

// Tab control message handlers
	LRESULT OnTabLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
	{
		if(!m_bNoTabDrag && (m_tab.GetItemCount() > 1))
		{
			m_bTabCapture = true;
			m_tab.SetCapture();

			m_ptStartDrag.x = GET_X_LPARAM(lParam);
			m_ptStartDrag.y = GET_Y_LPARAM(lParam);
		}

		bHandled = FALSE;
		return 0;
	}

	LRESULT OnTabLButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
	{
		if(m_bTabCapture)
		{
			if(m_bTabDrag)
			{
				T* pT = static_cast<T*>(this);
				POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
				int nItem = pT->DragHitTest(pt);
				if(nItem != -1)
					MovePage(m_nActivePage, nItem);
			}

			::ReleaseCapture();
		}

		bHandled = FALSE;
		return 0;
	}

	LRESULT OnTabCaptureChanged(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		if(m_bTabCapture)
		{
			m_bTabCapture = false;

			if(m_bTabDrag)
			{
				m_bTabDrag = false;

				T* pT = static_cast<T*>(this);
				if(!m_bNoTabDragAutoScroll)
					pT->StartStopAutoScroll(-1);

				pT->DrawMoveMark(-1);

				m_ilDrag.DragLeave(GetDesktopWindow());
				m_ilDrag.EndDrag();

				m_ilDrag.Destroy();
				m_ilDrag.m_hImageList = NULL;
			}
		}

		bHandled = FALSE;
		return 0;
	}

	LRESULT OnTabMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
	{
		bHandled = FALSE;

		if(m_bTabCapture)
		{
			POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };

			if(!m_bTabDrag)
			{
				if((abs(m_ptStartDrag.x - GET_X_LPARAM(lParam)) >= ::GetSystemMetrics(SM_CXDRAG)) ||
				   (abs(m_ptStartDrag.y - GET_Y_LPARAM(lParam)) >= ::GetSystemMetrics(SM_CYDRAG)))
				{
					T* pT = static_cast<T*>(this);
					pT->GenerateDragImage(m_nActivePage);

					int cxCursor = ::GetSystemMetrics(SM_CXCURSOR);
					int cyCursor = ::GetSystemMetrics(SM_CYCURSOR);
					m_ilDrag.BeginDrag(0, -(cxCursor / 2), -(cyCursor / 2));
					POINT ptEnter = m_ptStartDrag;
					m_tab.ClientToScreen(&ptEnter);
					m_ilDrag.DragEnter(GetDesktopWindow(), ptEnter);

					m_bTabDrag = true;
				}
			}

			if(m_bTabDrag)
			{
				T* pT = static_cast<T*>(this);
				int nItem = pT->DragHitTest(pt);

				pT->SetMoveCursor(nItem != -1);

				if(m_nInsertItem != nItem)
					pT->DrawMoveMark(nItem);

				if(!m_bNoTabDragAutoScroll)
					pT->StartStopAutoScroll(pt.x);

				m_ilDrag.DragShowNolock((nItem != -1) ? TRUE : FALSE);
				m_tab.ClientToScreen(&pt);
				m_ilDrag.DragMove(pt);

				bHandled = TRUE;
			}
		}

		return 0;
	}

// Implementation helpers
	bool IsValidPageIndex(int nPage) const
	{
		return ((nPage >= 0) && (nPage < GetPageCount()));
	}

	bool MovePage(int nMovePage, int nInsertBeforePage)
	{
		ATLASSERT(IsValidPageIndex(nMovePage));
		ATLASSERT(IsValidPageIndex(nInsertBeforePage));

		if(!IsValidPageIndex(nMovePage) || !IsValidPageIndex(nInsertBeforePage))
			return false;

		if(nMovePage == nInsertBeforePage)
			return true;   // nothing to do

		ATL::CTempBuffer<TCHAR, _WTL_STACK_ALLOC_THRESHOLD> buff;
		LPTSTR lpstrTabText = buff.Allocate(m_cchTabTextLength + 1);
		if(lpstrTabText == NULL)
			return false;
		TCITEMEXTRA tcix = {};
		tcix.tciheader.mask = TCIF_TEXT | TCIF_IMAGE | TCIF_PARAM;
		tcix.tciheader.pszText = lpstrTabText;
		tcix.tciheader.cchTextMax = m_cchTabTextLength + 1;
		BOOL bRet = m_tab.GetItem(nMovePage, tcix);
		ATLASSERT(bRet != FALSE);
		if(bRet == FALSE)
			return false;

		int nInsertItem = (nInsertBeforePage > nMovePage) ? nInsertBeforePage + 1 : nInsertBeforePage;
		int nNewItem = m_tab.InsertItem(nInsertItem, tcix);
		ATLASSERT(nNewItem == nInsertItem);
		if(nNewItem != nInsertItem)
		{
			ATLVERIFY(m_tab.DeleteItem(nNewItem));
			return false;
		}

		if(nMovePage > nInsertBeforePage)
			ATLVERIFY(m_tab.DeleteItem(nMovePage + 1) != FALSE);
		else if(nMovePage < nInsertBeforePage)
			ATLVERIFY(m_tab.DeleteItem(nMovePage) != FALSE);

		SetActivePage(nInsertBeforePage);
		T* pT = static_cast<T*>(this);
		pT->OnPageActivated(m_nActivePage);

		return true;
	}

// Implementation overrideables
	bool CreateTabControl()
	{
		m_tab.Create(this->m_hWnd, this->rcDefault, NULL, WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | TCS_TOOLTIPS, 0, m_nTabID);
		ATLASSERT(m_tab.m_hWnd != NULL);
		if(m_tab.m_hWnd == NULL)
			return false;

		m_tab.SetFont(AtlCreateControlFont());
		m_bInternalFont = true;

		m_tab.SetItemExtra(sizeof(TABVIEWPAGE));

		T* pT = static_cast<T*>(this);
		m_cyTabHeight = pT->CalcTabHeight();

		return true;
	}

	int CalcTabHeight()
	{
		int nCount = m_tab.GetItemCount();
		TCHAR szText[] = _T("NS");
		TCITEMEXTRA tcix = {};
		tcix.tciheader.mask = TCIF_TEXT;
		tcix.tciheader.pszText = szText;
		int nIndex = m_tab.InsertItem(nCount, tcix);

		RECT rect = { 0, 0, 1000, 1000 };
		m_tab.AdjustRect(FALSE, &rect);

		RECT rcWnd = { 0, 0, 1000, rect.top };
		::AdjustWindowRectEx(&rcWnd, m_tab.GetStyle(), FALSE, m_tab.GetExStyle());

		int nHeight = rcWnd.bottom - rcWnd.top;

		m_tab.DeleteItem(nIndex);

		return nHeight;
	}

	void ShowTabControl(bool bShow)
	{
		m_tab.ShowWindow(bShow ? SW_SHOWNOACTIVATE : SW_HIDE);
		T* pT = static_cast<T*>(this);
		pT->UpdateLayout();
	}

	void UpdateLayout()
	{
		RECT rect = {};
		this->GetClientRect(&rect);

		int cyOffset = 0;
		if(m_tab.IsWindow() && ((m_tab.GetStyle() & WS_VISIBLE) != 0))
		{
			m_tab.SetWindowPos(NULL, 0, 0, rect.right - rect.left, m_cyTabHeight, SWP_NOZORDER);
			cyOffset = m_cyTabHeight;
		}

		if(m_nActivePage != -1)
			::SetWindowPos(GetPageHWND(m_nActivePage), NULL, 0, cyOffset, rect.right - rect.left, rect.bottom - rect.top - cyOffset, SWP_NOZORDER);
	}

	void UpdateMenu()
	{
		if(m_menu.m_hMenu != NULL)
			BuildWindowMenu(m_menu, m_nMenuItemsCount, m_bEmptyMenuItem, m_bWindowsMenuItem, m_bActivePageMenuItem, m_bActiveAsDefaultMenuItem);
	}

	void UpdateTitleBar()
	{
		if(!m_wndTitleBar.IsWindow() || (m_lpstrTitleBarBase == NULL))
			return;   // nothing to do

		if(m_nActivePage != -1)
		{
			T* pT = static_cast<T*>(this);
			LPCTSTR lpstrTitle = pT->GetPageTitle(m_nActivePage);
			LPCTSTR lpstrDivider = pT->GetTitleDividerText();
			int cchBuffer = m_cchTitleBarLength + lstrlen(lpstrDivider) + lstrlen(m_lpstrTitleBarBase) + 1;
			ATL::CTempBuffer<TCHAR, _WTL_STACK_ALLOC_THRESHOLD> buff;
			LPTSTR lpstrPageTitle = buff.Allocate(cchBuffer);
			ATLASSERT(lpstrPageTitle != NULL);
			if(lpstrPageTitle != NULL)
			{
				pT->ShortenTitle(lpstrTitle, lpstrPageTitle, m_cchTitleBarLength + 1);
				ATL::Checked::tcscat_s(lpstrPageTitle, cchBuffer, lpstrDivider);
				ATL::Checked::tcscat_s(lpstrPageTitle, cchBuffer, m_lpstrTitleBarBase);
			}
			else
			{
				lpstrPageTitle = m_lpstrTitleBarBase;
			}

			m_wndTitleBar.SetWindowText(lpstrPageTitle);
		}
		else
		{
			m_wndTitleBar.SetWindowText(m_lpstrTitleBarBase);
		}
	}

	void DrawMoveMark(int nItem)
	{
		T* pT = static_cast<T*>(this);

		if(m_nInsertItem != -1)
		{
			RECT rect = {};
			pT->GetMoveMarkRect(rect);
			m_tab.InvalidateRect(&rect);
		}

		m_nInsertItem = nItem;

		if(m_nInsertItem != -1)
		{
			CClientDC dc(m_tab.m_hWnd);

			RECT rect = {};
			pT->GetMoveMarkRect(rect);

			CPen pen;
			pen.CreatePen(PS_SOLID, 1, ::GetSysColor(COLOR_WINDOWTEXT));
			CBrush brush;
			brush.CreateSolidBrush(::GetSysColor(COLOR_WINDOWTEXT));

			HPEN hPenOld = dc.SelectPen(pen);
			HBRUSH hBrushOld = dc.SelectBrush(brush);

			int x = rect.left;
			int y = rect.top;
			POINT ptsTop[3] = { { x, y }, { x + m_cxMoveMark, y }, { x + (m_cxMoveMark / 2), y + m_cyMoveMark } };
			dc.Polygon(ptsTop, 3);

			y = rect.bottom - 1;
			POINT ptsBottom[3] = { { x, y }, { x + m_cxMoveMark, y }, { x + (m_cxMoveMark / 2), y - m_cyMoveMark } };
			dc.Polygon(ptsBottom, 3);

			dc.SelectPen(hPenOld);
			dc.SelectBrush(hBrushOld);
		}
	}

	void GetMoveMarkRect(RECT& rect) const
	{
		m_tab.GetClientRect(&rect);

		RECT rcItem = {};
		m_tab.GetItemRect(m_nInsertItem, &rcItem);

		if(m_nInsertItem <= m_nActivePage)
		{
			rect.left = rcItem.left - m_cxMoveMark / 2 - 1;
			rect.right = rcItem.left + m_cxMoveMark / 2;
		}
		else
		{
			rect.left = rcItem.right - m_cxMoveMark / 2 - 1;
			rect.right = rcItem.right + m_cxMoveMark / 2;
		}
	}

	void SetMoveCursor(bool bCanMove)
	{
		::SetCursor(::LoadCursor(NULL, bCanMove ? IDC_ARROW : IDC_NO));
	}

	void GenerateDragImage(int nItem)
	{
		ATLASSERT(IsValidPageIndex(nItem));

		RECT rcItem = {};
		m_tab.GetItemRect(nItem, &rcItem);
		::InflateRect(&rcItem, 2, 2);   // make bigger to cover selected item

		ATLASSERT(m_ilDrag.m_hImageList == NULL);
		m_ilDrag.Create(rcItem.right - rcItem.left, rcItem.bottom - rcItem.top, ILC_COLORDDB | ILC_MASK, 1, 1);

		CClientDC dc(this->m_hWnd);
		CDC dcMem;
		dcMem.CreateCompatibleDC(dc);
		ATLASSERT(dcMem.m_hDC != NULL);
		dcMem.SetViewportOrg(-rcItem.left, -rcItem.top);

		CBitmap bmp;
		bmp.CreateCompatibleBitmap(dc, rcItem.right - rcItem.left, rcItem.bottom - rcItem.top);
		ATLASSERT(bmp.m_hBitmap != NULL);

		HBITMAP hBmpOld = dcMem.SelectBitmap(bmp);
		m_tab.SendMessage(WM_PRINTCLIENT, (WPARAM)dcMem.m_hDC);
		dcMem.SelectBitmap(hBmpOld);

		ATLVERIFY(m_ilDrag.Add(bmp.m_hBitmap, RGB(255, 0, 255)) != -1);
	}

	void ShortenTitle(LPCTSTR lpstrTitle, LPTSTR lpstrShortTitle, int cchShortTitle)
	{
		if(lstrlen(lpstrTitle) >= cchShortTitle)
		{
			LPCTSTR lpstrEllipsis = _T("...");
			int cchEllipsis = lstrlen(lpstrEllipsis);
			ATL::Checked::tcsncpy_s(lpstrShortTitle, cchShortTitle, lpstrTitle, cchShortTitle - cchEllipsis - 1);
			ATL::Checked::tcscat_s(lpstrShortTitle, cchShortTitle, lpstrEllipsis);
		}
		else
		{
			ATL::Checked::tcscpy_s(lpstrShortTitle, cchShortTitle, lpstrTitle);
		}
	}

	void UpdateTooltipText(LPNMTTDISPINFO pTTDI)
	{
		ATLASSERT(pTTDI != NULL);
		pTTDI->lpszText = (LPTSTR)GetPageTitle((int)pTTDI->hdr.idFrom);
	}

	int DragHitTest(POINT pt) const
	{
		RECT rect = {};
		this->GetClientRect(&rect);
		if(::PtInRect(&rect, pt) == FALSE)
			return -1;

		m_tab.GetClientRect(&rect);
		TCHITTESTINFO hti = {};
		hti.pt.x = pt.x;
		hti.pt.y = rect.bottom / 2;   // use middle to ignore
		int nItem = m_tab.HitTest(&hti);
		if(nItem == -1)
		{
			int nLast = m_tab.GetItemCount() - 1;
			RECT rcItem = {};
			m_tab.GetItemRect(nLast, &rcItem);
			if(pt.x >= rcItem.right)
				nItem = nLast;
		}

		return nItem;
	}

	void StartStopAutoScroll(int x)
	{
		AutoScroll scroll = _AUTOSCROLL_NONE;
		if(x != -1)
		{
			RECT rect = {};
			m_tab.GetClientRect(&rect);
			int dx = ::GetSystemMetrics(SM_CXVSCROLL);
			if((x >= 0) && (x < dx))
			{
				RECT rcItem = {};
				m_tab.GetItemRect(0, &rcItem);
				if(rcItem.left < rect.left)
					scroll = _AUTOSCROLL_LEFT;
			}
			else if((x >= (rect.right - dx)) && (x < rect.right))
			{
				RECT rcItem = {};
				m_tab.GetItemRect(m_tab.GetItemCount() - 1, &rcItem);
				if(rcItem.right > rect.right)
					scroll = _AUTOSCROLL_RIGHT;
			}
		}

		if(scroll != _AUTOSCROLL_NONE)
		{
			if(m_ud.m_hWnd == NULL)
				m_ud = m_tab.GetWindow(GW_CHILD);

			if(m_AutoScroll != scroll)
			{
				m_AutoScroll = scroll;
				this->SetTimer(_nAutoScrollTimerID, 300);
			}
		}
		else
		{
			this->KillTimer(_nAutoScrollTimerID);
			m_AutoScroll = _AUTOSCROLL_NONE;
		}
	}

	void DoAutoScroll()
	{
		ATLASSERT(m_AutoScroll != _AUTOSCROLL_NONE);

		int nMin = -1, nMax = -1;
		m_ud.GetRange(nMin, nMax);
		int nPos = m_ud.GetPos();

		int nNewPos = -1;
		if((m_AutoScroll == _AUTOSCROLL_LEFT) && (nPos > nMin))
			nNewPos = nPos - 1;
		else if((m_AutoScroll == _AUTOSCROLL_RIGHT) && (nPos < nMax))
			nNewPos = nPos + 1;
		if(nNewPos != -1)
		{
			m_tab.SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, nNewPos));
			m_tab.SendMessage(WM_HSCROLL, MAKEWPARAM(SB_ENDSCROLL, 0));

			POINT pt = {};
			::GetCursorPos(&pt);
			m_tab.ScreenToClient(&pt);
			m_tab.SendMessage(WM_MOUSEMOVE, NULL, MAKELPARAM(pt.x, pt.y));
		}
	}

// Text for menu items and title bar - override to provide different strings
	static LPCTSTR GetEmptyListText()
	{
		return _T("(Empty)");
	}

	static LPCTSTR GetWindowsMenuItemText()
	{
		return _T("&Windows...");
	}

	static LPCTSTR GetTitleDividerText()
	{
		return _T(" - ");
	}

// Notifications - override to provide different behavior
	void OnPageActivated(int nPage)
	{
		NMHDR nmhdr = {};
		nmhdr.hwndFrom = this->m_hWnd;
		nmhdr.idFrom = nPage;
		nmhdr.code = TBVN_PAGEACTIVATED;
		this->GetParent().SendMessage(WM_NOTIFY, this->GetDlgCtrlID(), (LPARAM)&nmhdr);
	}

	void OnContextMenu(int nPage, POINT pt)
	{
		TBVCONTEXTMENUINFO cmi = {};
		cmi.hdr.hwndFrom = this->m_hWnd;
		cmi.hdr.idFrom = nPage;
		cmi.hdr.code = TBVN_CONTEXTMENU;
		cmi.pt = pt;
		this->GetParent().SendMessage(WM_NOTIFY, this->GetDlgCtrlID(), (LPARAM)&cmi);
	}
};

class CTabView : public CTabViewImpl<CTabView>
{
public:
	DECLARE_WND_CLASS_EX(_T("WTL_TabView"), 0, COLOR_APPWORKSPACE)
};

} // namespace WTL

#endif // __ATLCTRLX_H__