diff foosdk/wtl/Include/atlscrl.h @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/foosdk/wtl/Include/atlscrl.h	Mon Jan 05 02:15:46 2026 -0500
@@ -0,0 +1,2127 @@
+// 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 __ATLSCRL_H__
+#define __ATLSCRL_H__
+
+#pragma once
+
+#ifndef __ATLAPP_H__
+	#error atlscrl.h requires atlapp.h to be included first
+#endif
+
+#ifndef __ATLWIN_H__
+	#error atlscrl.h requires atlwin.h to be included first
+#endif
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Classes in this file:
+//
+// CScrollImpl<T>
+// CScrollWindowImpl<T, TBase, TWinTraits>
+// CMapScrollImpl<T>
+// CMapScrollWindowImpl<T, TBase, TWinTraits>
+// CFSBWindowT<TBase>
+// CZoomScrollImpl<T>
+// CZoomScrollWindowImpl<T, TBase, TWinTraits>
+// CScrollContainerImpl<T, TBase, TWinTraits>
+// CScrollContainer
+
+namespace WTL
+{
+
+///////////////////////////////////////////////////////////////////////////////
+// CScrollImpl - Provides scrolling support to any window
+
+// Scroll extended styles
+#define SCRL_SCROLLCHILDREN	0x00000001
+#define SCRL_ERASEBACKGROUND	0x00000002
+#define SCRL_NOTHUMBTRACKING	0x00000004
+#define SCRL_SMOOTHSCROLL	0x00000008
+#define SCRL_DISABLENOSCROLLV	0x00000010
+#define SCRL_DISABLENOSCROLLH	0x00000020
+#define SCRL_DISABLENOSCROLL	(SCRL_DISABLENOSCROLLV | SCRL_DISABLENOSCROLLH)
+
+
+template <class T>
+class CScrollImpl
+{
+public:
+	enum { uSCROLL_FLAGS = SW_INVALIDATE };
+
+	POINT m_ptOffset;
+	SIZE m_sizeAll;
+	SIZE m_sizeLine;
+	SIZE m_sizePage;
+	SIZE m_sizeClient;
+	int m_zDelta;              // current wheel value
+	int m_nWheelLines;         // number of lines to scroll on wheel
+	int m_zHDelta;              // current horizontal wheel value
+	int m_nHWheelChars;         // number of chars to scroll on horizontal wheel
+	UINT m_uScrollFlags;
+	DWORD m_dwExtendedStyle;   // scroll specific extended styles
+
+// Constructor
+	CScrollImpl() : m_zDelta(0), m_nWheelLines(3), 
+			m_zHDelta(0), m_nHWheelChars(3), 
+			m_uScrollFlags(0U), m_dwExtendedStyle(0)
+	{
+		m_ptOffset.x = 0;
+		m_ptOffset.y = 0;
+		m_sizeAll.cx = 0;
+		m_sizeAll.cy = 0;
+		m_sizePage.cx = 0;
+		m_sizePage.cy = 0;
+		m_sizeLine.cx = 0;
+		m_sizeLine.cy = 0;
+		m_sizeClient.cx = 0;
+		m_sizeClient.cy = 0;
+
+		SetScrollExtendedStyle(SCRL_SCROLLCHILDREN | SCRL_ERASEBACKGROUND);
+	}
+
+// Attributes & Operations
+	DWORD GetScrollExtendedStyle() const
+	{
+		return m_dwExtendedStyle;
+	}
+
+	DWORD SetScrollExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)
+	{
+		DWORD dwPrevStyle = m_dwExtendedStyle;
+		if(dwMask == 0)
+			m_dwExtendedStyle = dwExtendedStyle;
+		else
+			m_dwExtendedStyle = (m_dwExtendedStyle & ~dwMask) | (dwExtendedStyle & dwMask);
+		// cache scroll flags
+		T* pT = static_cast<T*>(this);
+		(void)pT;   // avoid level 4 warning
+		m_uScrollFlags = pT->uSCROLL_FLAGS | (IsScrollingChildren() ? SW_SCROLLCHILDREN : 0) | (IsErasingBackground() ? SW_ERASE : 0);
+		m_uScrollFlags |= (IsSmoothScroll() ? SW_SMOOTHSCROLL : 0);
+		return dwPrevStyle;
+	}
+
+	// offset operations
+	void SetScrollOffset(int x, int y, BOOL bRedraw = TRUE)
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+
+		pT->AdjustScrollOffset(x, y);
+
+		int dx = m_ptOffset.x - x;
+		int dy = m_ptOffset.y - y;
+		m_ptOffset.x = x;
+		m_ptOffset.y = y;
+
+		// block: set horizontal scroll bar
+		{
+			SCROLLINFO si = { sizeof(SCROLLINFO) };
+			si.fMask = SIF_POS;
+			if((m_dwExtendedStyle & SCRL_DISABLENOSCROLLH) != 0)
+				si.fMask |= SIF_DISABLENOSCROLL;
+			si.nPos = m_ptOffset.x;
+			pT->SetScrollInfo(SB_HORZ, &si, bRedraw);
+		}
+
+		// block: set vertical scroll bar
+		{
+			SCROLLINFO si = { sizeof(SCROLLINFO) };
+			si.fMask = SIF_POS;
+			if((m_dwExtendedStyle & SCRL_DISABLENOSCROLLV) != 0)
+				si.fMask |= SIF_DISABLENOSCROLL;
+			si.nPos = m_ptOffset.y;
+			pT->SetScrollInfo(SB_VERT, &si, bRedraw);
+		}
+
+		// Move all children if needed
+		if(IsScrollingChildren() && ((dx != 0) || (dy != 0)))
+		{
+			for(HWND hWndChild = ::GetWindow(pT->m_hWnd, GW_CHILD); hWndChild != NULL; hWndChild = ::GetWindow(hWndChild, GW_HWNDNEXT))
+			{
+				RECT rect = {};
+				::GetWindowRect(hWndChild, &rect);
+				::MapWindowPoints(NULL, pT->m_hWnd, (LPPOINT)&rect, 1);
+				::SetWindowPos(hWndChild, NULL, rect.left + dx, rect.top + dy, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);
+			}
+		}
+
+		if(bRedraw)
+			pT->Invalidate();
+	}
+
+	void SetScrollOffset(POINT ptOffset, BOOL bRedraw = TRUE)
+	{
+		SetScrollOffset(ptOffset.x, ptOffset.y, bRedraw);
+	}
+
+	void GetScrollOffset(POINT& ptOffset) const
+	{
+		ptOffset = m_ptOffset;
+	}
+
+	// size operations
+	void SetScrollSize(int cx, int cy, BOOL bRedraw = TRUE, bool bResetOffset = true)
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+
+		m_sizeAll.cx = cx;
+		m_sizeAll.cy = cy;
+
+		int x = 0;
+		int y = 0;
+		if(!bResetOffset)
+		{
+			x = m_ptOffset.x;
+			y = m_ptOffset.y;
+			pT->AdjustScrollOffset(x, y);
+		}
+
+		int dx = m_ptOffset.x - x;
+		int dy = m_ptOffset.y - y;
+		m_ptOffset.x = x;
+		m_ptOffset.y = y;
+
+		// block: set horizontal scroll bar
+		{
+			SCROLLINFO si = { sizeof(SCROLLINFO) };
+			si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
+			if((m_dwExtendedStyle & SCRL_DISABLENOSCROLLH) != 0)
+				si.fMask |= SIF_DISABLENOSCROLL;
+			si.nMin = 0;
+			si.nMax = m_sizeAll.cx - 1;
+			si.nPage = m_sizeClient.cx;
+			si.nPos = m_ptOffset.x;
+			pT->SetScrollInfo(SB_HORZ, &si, bRedraw);
+		}
+
+		// block: set vertical scroll bar
+		{
+			SCROLLINFO si = { sizeof(SCROLLINFO) };
+			si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
+			if((m_dwExtendedStyle & SCRL_DISABLENOSCROLLV) != 0)
+				si.fMask |= SIF_DISABLENOSCROLL;
+			si.nMin = 0;
+			si.nMax = m_sizeAll.cy - 1;
+			si.nPage = m_sizeClient.cy;
+			si.nPos = m_ptOffset.y;
+			pT->SetScrollInfo(SB_VERT, &si, bRedraw);
+		}
+
+		// Move all children if needed
+		if(IsScrollingChildren() && ((dx != 0) || (dy != 0)))
+		{
+			for(HWND hWndChild = ::GetWindow(pT->m_hWnd, GW_CHILD); hWndChild != NULL; hWndChild = ::GetWindow(hWndChild, GW_HWNDNEXT))
+			{
+				RECT rect = {};
+				::GetWindowRect(hWndChild, &rect);
+				::MapWindowPoints(NULL, pT->m_hWnd, (LPPOINT)&rect, 1);
+				::SetWindowPos(hWndChild, NULL, rect.left + dx, rect.top + dy, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);
+			}
+		}
+
+		SetScrollLine(0, 0);
+		SetScrollPage(0, 0);
+
+		if(bRedraw)
+			pT->Invalidate();
+	}
+
+	void SetScrollSize(SIZE size, BOOL bRedraw = TRUE, bool bResetOffset = true)
+	{
+		SetScrollSize(size.cx, size.cy, bRedraw, bResetOffset);
+	}
+
+	void GetScrollSize(SIZE& sizeWnd) const
+	{
+		sizeWnd = m_sizeAll;
+	}
+
+	// line operations
+	void SetScrollLine(int cxLine, int cyLine)
+	{
+		ATLASSERT((cxLine >= 0) && (cyLine >= 0));
+		ATLASSERT((m_sizeAll.cx != 0) && (m_sizeAll.cy != 0));
+
+		m_sizeLine.cx = T::CalcLineOrPage(cxLine, m_sizeAll.cx, 100);
+		m_sizeLine.cy = T::CalcLineOrPage(cyLine, m_sizeAll.cy, 100);
+	}
+
+	void SetScrollLine(SIZE sizeLine)
+	{
+		SetScrollLine(sizeLine.cx, sizeLine.cy);
+	}
+
+	void GetScrollLine(SIZE& sizeLine) const
+	{
+		sizeLine = m_sizeLine;
+	}
+
+	// page operations
+	void SetScrollPage(int cxPage, int cyPage)
+	{
+		ATLASSERT((cxPage >= 0) && (cyPage >= 0));
+		ATLASSERT((m_sizeAll.cx != 0) && (m_sizeAll.cy != 0));
+
+		m_sizePage.cx = T::CalcLineOrPage(cxPage, m_sizeAll.cx, 10);
+		m_sizePage.cy = T::CalcLineOrPage(cyPage, m_sizeAll.cy, 10);
+	}
+
+	void SetScrollPage(SIZE sizePage)
+	{
+		SetScrollPage(sizePage.cx, sizePage.cy);
+	}
+
+	void GetScrollPage(SIZE& sizePage) const
+	{
+		sizePage = m_sizePage;
+	}
+
+	// commands
+	void ScrollLineDown()
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		pT->DoScroll(SB_VERT, SB_LINEDOWN, (int&)m_ptOffset.y, m_sizeAll.cy, m_sizePage.cy, m_sizeLine.cy);
+	}
+
+	void ScrollLineUp()
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		pT->DoScroll(SB_VERT, SB_LINEUP, (int&)m_ptOffset.y, m_sizeAll.cy, m_sizePage.cy, m_sizeLine.cy);
+	}
+
+	void ScrollPageDown()
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		pT->DoScroll(SB_VERT, SB_PAGEDOWN, (int&)m_ptOffset.y, m_sizeAll.cy, m_sizePage.cy, m_sizeLine.cy);
+	}
+
+	void ScrollPageUp()
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		pT->DoScroll(SB_VERT, SB_PAGEUP, (int&)m_ptOffset.y, m_sizeAll.cy, m_sizePage.cy, m_sizeLine.cy);
+	}
+
+	void ScrollTop()
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		pT->DoScroll(SB_VERT, SB_TOP, (int&)m_ptOffset.y, m_sizeAll.cy, m_sizePage.cy, m_sizeLine.cy);
+	}
+
+	void ScrollBottom()
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		pT->DoScroll(SB_VERT, SB_BOTTOM, (int&)m_ptOffset.y, m_sizeAll.cy, m_sizePage.cy, m_sizeLine.cy);
+	}
+
+	void ScrollLineRight()
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		pT->DoScroll(SB_HORZ, SB_LINEDOWN, (int&)m_ptOffset.x, m_sizeAll.cx, m_sizePage.cx, m_sizeLine.cx);
+	}
+
+	void ScrollLineLeft()
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		pT->DoScroll(SB_HORZ, SB_LINEUP, (int&)m_ptOffset.x, m_sizeAll.cx, m_sizePage.cx, m_sizeLine.cx);
+	}
+
+	void ScrollPageRight()
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		pT->DoScroll(SB_HORZ, SB_PAGEDOWN, (int&)m_ptOffset.x, m_sizeAll.cx, m_sizePage.cx, m_sizeLine.cx);
+	}
+
+	void ScrollPageLeft()
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		pT->DoScroll(SB_HORZ, SB_PAGEUP, (int&)m_ptOffset.x, m_sizeAll.cx, m_sizePage.cx, m_sizeLine.cx);
+	}
+
+	void ScrollAllLeft()
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		pT->DoScroll(SB_HORZ, SB_TOP, (int&)m_ptOffset.x, m_sizeAll.cx, m_sizePage.cx, m_sizeLine.cx);
+	}
+
+	void ScrollAllRight()
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		pT->DoScroll(SB_HORZ, SB_BOTTOM, (int&)m_ptOffset.x, m_sizeAll.cx, m_sizePage.cx, m_sizeLine.cx);
+	}
+
+	// scroll to make point/view/window visible
+	void ScrollToView(POINT pt)
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		RECT rect = { pt.x, pt.y, pt.x, pt.y };
+		pT->ScrollToView(rect);
+	}
+
+	void ScrollToView(RECT& rect)
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+
+		RECT rcClient = {};
+		pT->GetClientRect(&rcClient);
+
+		int x = m_ptOffset.x;
+		if(rect.left < m_ptOffset.x)
+			x = rect.left;
+		else if(rect.right > (m_ptOffset.x + rcClient.right))
+			x = rect.right - rcClient.right;
+
+		int y = m_ptOffset.y;
+		if(rect.top < m_ptOffset.y)
+			y = rect.top;
+		else if(rect.bottom > (m_ptOffset.y + rcClient.bottom))
+			y = rect.bottom - rcClient.bottom;
+
+		SetScrollOffset(x, y);
+	}
+
+	void ScrollToView(HWND hWnd)
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+
+		RECT rect = {};
+		::GetWindowRect(hWnd, &rect);
+		::OffsetRect(&rect, m_ptOffset.x, m_ptOffset.y);
+		::MapWindowPoints(NULL, pT->m_hWnd, (LPPOINT)&rect, 2);
+		ScrollToView(rect);
+	}
+
+	BEGIN_MSG_MAP(CScrollImpl)
+		MESSAGE_HANDLER(WM_CREATE, OnCreate)
+		MESSAGE_HANDLER(WM_VSCROLL, OnVScroll)
+		MESSAGE_HANDLER(WM_HSCROLL, OnHScroll)
+		MESSAGE_HANDLER(WM_MOUSEWHEEL, OnMouseWheel)
+		MESSAGE_HANDLER(WM_MOUSEHWHEEL, OnMouseHWheel)
+		MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChange)
+		MESSAGE_HANDLER(WM_SIZE, OnSize)
+		MESSAGE_HANDLER(WM_PAINT, OnPaint)
+		MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
+	// standard scroll commands
+	ALT_MSG_MAP(1)
+		COMMAND_ID_HANDLER(ID_SCROLL_UP, OnScrollUp)
+		COMMAND_ID_HANDLER(ID_SCROLL_DOWN, OnScrollDown)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_UP, OnScrollPageUp)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_DOWN, OnScrollPageDown)
+		COMMAND_ID_HANDLER(ID_SCROLL_TOP, OnScrollTop)
+		COMMAND_ID_HANDLER(ID_SCROLL_BOTTOM, OnScrollBottom)
+		COMMAND_ID_HANDLER(ID_SCROLL_LEFT, OnScrollLeft)
+		COMMAND_ID_HANDLER(ID_SCROLL_RIGHT, OnScrollRight)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_LEFT, OnScrollPageLeft)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_RIGHT, OnScrollPageRight)
+		COMMAND_ID_HANDLER(ID_SCROLL_ALL_LEFT, OnScrollAllLeft)
+		COMMAND_ID_HANDLER(ID_SCROLL_ALL_RIGHT, OnScrollAllRight)
+	END_MSG_MAP()
+
+	LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
+	{
+		T* pT = static_cast<T*>(this);
+		pT->GetSystemSettings();
+
+		bHandled = FALSE;
+		return 1;
+	}
+
+	LRESULT OnVScroll(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		pT->DoScroll(SB_VERT, (int)(short)LOWORD(wParam), (int&)m_ptOffset.y, m_sizeAll.cy, m_sizePage.cy, m_sizeLine.cy);
+		return 0;
+	}
+
+	LRESULT OnHScroll(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		pT->DoScroll(SB_HORZ, (int)(short)LOWORD(wParam), (int&)m_ptOffset.x, m_sizeAll.cx, m_sizePage.cx, m_sizeLine.cx);
+		return 0;
+	}
+
+	LRESULT OnMouseWheel(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+
+		int zDelta = (int)GET_WHEEL_DELTA_WPARAM(wParam);
+		int nScrollCode = (m_nWheelLines == WHEEL_PAGESCROLL) ? ((zDelta > 0) ? SB_PAGEUP : SB_PAGEDOWN) : ((zDelta > 0) ? SB_LINEUP : SB_LINEDOWN);
+		m_zDelta += zDelta;   // cumulative
+		int zTotal = (m_nWheelLines == WHEEL_PAGESCROLL) ? abs(m_zDelta) : abs(m_zDelta) * m_nWheelLines;
+		if(m_sizeAll.cy > m_sizeClient.cy)
+		{
+			for(int i = 0; i < zTotal; i += WHEEL_DELTA)
+			{
+				pT->DoScroll(SB_VERT, nScrollCode, (int&)m_ptOffset.y, m_sizeAll.cy, m_sizePage.cy, m_sizeLine.cy);
+				pT->UpdateWindow();
+			}
+		}
+		else if(m_sizeAll.cx > m_sizeClient.cx)   // can't scroll vertically, scroll horizontally
+		{
+			for(int i = 0; i < zTotal; i += WHEEL_DELTA)
+			{
+				pT->DoScroll(SB_HORZ, nScrollCode, (int&)m_ptOffset.x, m_sizeAll.cx, m_sizePage.cx, m_sizeLine.cx);
+				pT->UpdateWindow();
+			}
+		}
+		m_zDelta %= WHEEL_DELTA;
+
+		return 0;
+	}
+
+	LRESULT OnMouseHWheel(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+
+		int zDelta = (int)GET_WHEEL_DELTA_WPARAM(wParam);
+		int nScrollCode = (m_nHWheelChars == WHEEL_PAGESCROLL) ? ((zDelta > 0) ? SB_PAGERIGHT : SB_PAGELEFT) : ((zDelta > 0) ? SB_LINERIGHT : SB_LINELEFT);
+		m_zHDelta += zDelta;   // cumulative
+		int zTotal = (m_nHWheelChars == WHEEL_PAGESCROLL) ? abs(m_zHDelta) : abs(m_zHDelta) * m_nHWheelChars;
+		if(m_sizeAll.cx > m_sizeClient.cx)
+		{
+			for(int i = 0; i < zTotal; i += WHEEL_DELTA)
+			{
+				pT->DoScroll(SB_HORZ, nScrollCode, (int&)m_ptOffset.x, m_sizeAll.cx, m_sizePage.cx, m_sizeLine.cx);
+				pT->UpdateWindow();
+			}
+		}
+		m_zHDelta %= WHEEL_DELTA;
+
+		return 0;
+	}
+
+	LRESULT OnSettingChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
+	{
+		GetSystemSettings();
+		return 0;
+	}
+
+	LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+
+		pT->DoSize(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
+
+		bHandled = FALSE;
+		return 1;
+	}
+
+	LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		if(wParam != NULL)
+		{
+			CDCHandle dc = (HDC)wParam;
+			POINT ptViewportOrg = { 0, 0 };
+			dc.SetViewportOrg(-m_ptOffset.x, -m_ptOffset.y, &ptViewportOrg);
+			pT->DoPaint(dc);
+			dc.SetViewportOrg(ptViewportOrg);
+		}
+		else
+		{
+			CPaintDC dc(pT->m_hWnd);
+			dc.SetViewportOrg(-m_ptOffset.x, -m_ptOffset.y);
+			pT->DoPaint(dc.m_hDC);
+		}
+		return 0;
+	}
+
+	// scrolling handlers
+	LRESULT OnScrollUp(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
+	{
+		ScrollLineUp();
+		return 0;
+	}
+
+	LRESULT OnScrollDown(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
+	{
+		ScrollLineDown();
+		return 0;
+	}
+
+	LRESULT OnScrollPageUp(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
+	{
+		ScrollPageUp();
+		return 0;
+	}
+
+	LRESULT OnScrollPageDown(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
+	{
+		ScrollPageDown();
+		return 0;
+	}
+
+	LRESULT OnScrollTop(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
+	{
+		ScrollTop();
+		return 0;
+	}
+
+	LRESULT OnScrollBottom(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
+	{
+		ScrollBottom();
+		return 0;
+	}
+
+	LRESULT OnScrollLeft(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
+	{
+		ScrollLineLeft();
+		return 0;
+	}
+
+	LRESULT OnScrollRight(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
+	{
+		ScrollLineRight();
+		return 0;
+	}
+
+	LRESULT OnScrollPageLeft(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
+	{
+		ScrollPageLeft();
+		return 0;
+	}
+
+	LRESULT OnScrollPageRight(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
+	{
+		ScrollPageRight();
+		return 0;
+	}
+
+	LRESULT OnScrollAllLeft(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
+	{
+		ScrollAllLeft();
+		return 0;
+	}
+
+	LRESULT OnScrollAllRight(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
+	{
+		ScrollAllRight();
+		return 0;
+	}
+
+// Overrideables
+	void DoPaint(CDCHandle /*dc*/)
+	{
+		// must be implemented in a derived class
+		ATLASSERT(FALSE);
+	}
+
+// Implementation
+	void DoSize(int cx, int cy)
+	{
+		m_sizeClient.cx = cx;
+		m_sizeClient.cy = cy;
+
+		T* pT = static_cast<T*>(this);
+
+		// block: set horizontal scroll bar
+		{
+			SCROLLINFO si = { sizeof(SCROLLINFO) };
+			si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
+			si.nMin = 0;
+			si.nMax = m_sizeAll.cx - 1;
+			if((m_dwExtendedStyle & SCRL_DISABLENOSCROLLH) != 0)
+				si.fMask |= SIF_DISABLENOSCROLL;
+			si.nPage = m_sizeClient.cx;
+			si.nPos = m_ptOffset.x;
+			pT->SetScrollInfo(SB_HORZ, &si, TRUE);
+		}
+
+		// block: set vertical scroll bar
+		{
+			SCROLLINFO si = { sizeof(SCROLLINFO) };
+			si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
+			si.nMin = 0;
+			si.nMax = m_sizeAll.cy - 1;
+			if((m_dwExtendedStyle & SCRL_DISABLENOSCROLLV) != 0)
+				si.fMask |= SIF_DISABLENOSCROLL;
+			si.nPage = m_sizeClient.cy;
+			si.nPos = m_ptOffset.y;
+			pT->SetScrollInfo(SB_VERT, &si, TRUE);
+		}
+
+		int x = m_ptOffset.x;
+		int y = m_ptOffset.y;
+		if(pT->AdjustScrollOffset(x, y))
+		{
+			// Children will be moved in SetScrollOffset, if needed
+			pT->ScrollWindowEx(m_ptOffset.x - x, m_ptOffset.y - y, (m_uScrollFlags & ~SCRL_SCROLLCHILDREN));
+			SetScrollOffset(x, y, FALSE);
+		}
+	}
+
+	void DoScroll(int nType, int nScrollCode, int& cxyOffset, int cxySizeAll, int cxySizePage, int cxySizeLine)
+	{
+		T* pT = static_cast<T*>(this);
+		RECT rect = {};
+		pT->GetClientRect(&rect);
+		int cxyClient = (nType == SB_VERT) ? rect.bottom : rect.right;
+		int cxyMax = cxySizeAll - cxyClient;
+
+		if(cxyMax < 0)   // can't scroll, client area is bigger
+			return;
+
+		bool bUpdate = true;
+		int cxyScroll = 0;
+
+		switch(nScrollCode)
+		{
+		case SB_TOP:		// top or all left
+			cxyScroll = cxyOffset;
+			cxyOffset = 0;
+			break;
+		case SB_BOTTOM:		// bottom or all right
+			cxyScroll = cxyOffset - cxyMax;
+			cxyOffset = cxyMax;
+			break;
+		case SB_LINEUP:		// line up or line left
+			if(cxyOffset >= cxySizeLine)
+			{
+				cxyScroll = cxySizeLine;
+				cxyOffset -= cxySizeLine;
+			}
+			else
+			{
+				cxyScroll = cxyOffset;
+				cxyOffset = 0;
+			}
+			break;
+		case SB_LINEDOWN:	// line down or line right
+			if(cxyOffset < cxyMax - cxySizeLine)
+			{
+				cxyScroll = -cxySizeLine;
+				cxyOffset += cxySizeLine;
+			}
+			else
+			{
+				cxyScroll = cxyOffset - cxyMax;
+				cxyOffset = cxyMax;
+			}
+			break;
+		case SB_PAGEUP:		// page up or page left
+			if(cxyOffset >= cxySizePage)
+			{
+				cxyScroll = cxySizePage;
+				cxyOffset -= cxySizePage;
+			}
+			else
+			{
+				cxyScroll = cxyOffset;
+				cxyOffset = 0;
+			}
+			break;
+		case SB_PAGEDOWN:	// page down or page right
+			if(cxyOffset < cxyMax - cxySizePage)
+			{
+				cxyScroll = -cxySizePage;
+				cxyOffset += cxySizePage;
+			}
+			else
+			{
+				cxyScroll = cxyOffset - cxyMax;
+				cxyOffset = cxyMax;
+			}
+			break;
+		case SB_THUMBTRACK:
+			if(IsNoThumbTracking())
+				break;
+			// else fall through
+		case SB_THUMBPOSITION:
+			{
+				SCROLLINFO si = { sizeof(SCROLLINFO), SIF_TRACKPOS };
+				if(pT->GetScrollInfo(nType, &si))
+				{
+					cxyScroll = cxyOffset - si.nTrackPos;
+					cxyOffset = si.nTrackPos;
+				}
+			}
+			break;
+		case SB_ENDSCROLL:
+		default:
+			bUpdate = false;
+			break;
+		}
+
+		if(bUpdate && (cxyScroll != 0))
+		{
+			pT->SetScrollPos(nType, cxyOffset, TRUE);
+			if(nType == SB_VERT)
+				pT->ScrollWindowEx(0, cxyScroll, m_uScrollFlags);
+			else
+				pT->ScrollWindowEx(cxyScroll, 0, m_uScrollFlags);
+		}
+	}
+
+	static int CalcLineOrPage(int nVal, int nMax, int nDiv)
+	{
+		if(nVal == 0)
+		{
+			nVal = nMax / nDiv;
+			if(nVal < 1)
+				nVal = 1;
+		}
+		else if(nVal > nMax)
+		{
+			nVal = nMax;
+		}
+
+		return nVal;
+	}
+
+	bool AdjustScrollOffset(int& x, int& y)
+	{
+		int xOld = x;
+		int yOld = y;
+
+		int cxMax = m_sizeAll.cx - m_sizeClient.cx;
+		if(x > cxMax)
+			x = (cxMax >= 0) ? cxMax : 0;
+		else if(x < 0)
+			x = 0;
+
+		int cyMax = m_sizeAll.cy - m_sizeClient.cy;
+		if(y > cyMax)
+			y = (cyMax >= 0) ? cyMax : 0;
+		else if(y < 0)
+			y = 0;
+
+		return ((x != xOld) || (y != yOld));
+	}
+
+	void GetSystemSettings()
+	{
+		::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &m_nWheelLines, 0);
+
+#ifndef SPI_GETWHEELSCROLLCHARS
+		const UINT SPI_GETWHEELSCROLLCHARS = 0x006C;
+#endif
+		::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &m_nHWheelChars, 0);
+	}
+
+	bool IsScrollingChildren() const
+	{
+		return (m_dwExtendedStyle & SCRL_SCROLLCHILDREN) != 0;
+	}
+
+	bool IsErasingBackground() const
+	{
+		return (m_dwExtendedStyle & SCRL_ERASEBACKGROUND) != 0;
+	}
+
+	bool IsNoThumbTracking() const
+	{
+		return (m_dwExtendedStyle & SCRL_NOTHUMBTRACKING) != 0;
+	}
+
+	bool IsSmoothScroll() const
+	{
+		return (m_dwExtendedStyle & SCRL_SMOOTHSCROLL) != 0;
+	}
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// CScrollWindowImpl - Implements a scrollable window
+
+template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CControlWinTraits>
+class ATL_NO_VTABLE CScrollWindowImpl : public ATL::CWindowImpl<T, TBase, TWinTraits>, public CScrollImpl< T >
+{
+public:
+	BOOL SubclassWindow(HWND hWnd)
+	{
+		BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
+		if(bRet != FALSE)
+		{
+			T* pT = static_cast<T*>(this);
+			pT->GetSystemSettings();
+
+			RECT rect = {};
+			this->GetClientRect(&rect);
+			pT->DoSize(rect.right, rect.bottom);
+		}
+
+		return bRet;
+	}
+
+	BEGIN_MSG_MAP(CScrollWindowImpl)
+		MESSAGE_HANDLER(WM_VSCROLL, CScrollImpl< T >::OnVScroll)
+		MESSAGE_HANDLER(WM_HSCROLL, CScrollImpl< T >::OnHScroll)
+		MESSAGE_HANDLER(WM_MOUSEWHEEL, CScrollImpl< T >::OnMouseWheel)
+		MESSAGE_HANDLER(WM_MOUSEHWHEEL, CScrollImpl< T >::OnMouseHWheel)
+		MESSAGE_HANDLER(WM_SETTINGCHANGE, CScrollImpl< T >::OnSettingChange)
+		MESSAGE_HANDLER(WM_SIZE, CScrollImpl< T >::OnSize)
+		MESSAGE_HANDLER(WM_PAINT, CScrollImpl< T >::OnPaint)
+		MESSAGE_HANDLER(WM_PRINTCLIENT, CScrollImpl< T >::OnPaint)
+	ALT_MSG_MAP(1)
+		COMMAND_ID_HANDLER(ID_SCROLL_UP, CScrollImpl< T >::OnScrollUp)
+		COMMAND_ID_HANDLER(ID_SCROLL_DOWN, CScrollImpl< T >::OnScrollDown)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_UP, CScrollImpl< T >::OnScrollPageUp)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_DOWN, CScrollImpl< T >::OnScrollPageDown)
+		COMMAND_ID_HANDLER(ID_SCROLL_TOP, CScrollImpl< T >::OnScrollTop)
+		COMMAND_ID_HANDLER(ID_SCROLL_BOTTOM, CScrollImpl< T >::OnScrollBottom)
+		COMMAND_ID_HANDLER(ID_SCROLL_LEFT, CScrollImpl< T >::OnScrollLeft)
+		COMMAND_ID_HANDLER(ID_SCROLL_RIGHT, CScrollImpl< T >::OnScrollRight)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_LEFT, CScrollImpl< T >::OnScrollPageLeft)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_RIGHT, CScrollImpl< T >::OnScrollPageRight)
+		COMMAND_ID_HANDLER(ID_SCROLL_ALL_LEFT, CScrollImpl< T >::OnScrollAllLeft)
+		COMMAND_ID_HANDLER(ID_SCROLL_ALL_RIGHT, CScrollImpl< T >::OnScrollAllRight)
+	END_MSG_MAP()
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// CMapScrollImpl - Provides mapping and scrolling support to any window
+
+template <class T>
+class CMapScrollImpl : public CScrollImpl< T >
+{
+public:
+	int m_nMapMode;
+	RECT m_rectLogAll;
+	SIZE m_sizeLogLine;
+	SIZE m_sizeLogPage;
+
+// Constructor
+	CMapScrollImpl() : m_nMapMode(MM_TEXT)
+	{
+		::SetRectEmpty(&m_rectLogAll);
+		m_sizeLogPage.cx = 0;
+		m_sizeLogPage.cy = 0;
+		m_sizeLogLine.cx = 0;
+		m_sizeLogLine.cy = 0;
+	}
+
+// Attributes & Operations
+	// mapping mode operations
+	void SetScrollMapMode(int nMapMode)
+	{
+		ATLASSERT((nMapMode >= MM_MIN) && (nMapMode <= MM_MAX_FIXEDSCALE));
+		m_nMapMode = nMapMode;
+	}
+
+	int GetScrollMapMode() const
+	{
+		ATLASSERT((m_nMapMode >= MM_MIN) && (m_nMapMode <= MM_MAX_FIXEDSCALE));
+		return m_nMapMode;
+	}
+
+	// offset operations
+	void SetScrollOffset(int x, int y, BOOL bRedraw = TRUE)
+	{
+		ATLASSERT((m_nMapMode >= MM_MIN) && (m_nMapMode <= MM_MAX_FIXEDSCALE));
+		POINT ptOff = { x, y };
+		// block: convert logical to device units
+		{
+			CWindowDC dc(NULL);
+			dc.SetMapMode(m_nMapMode);
+			dc.LPtoDP(&ptOff);
+		}
+		CScrollImpl< T >::SetScrollOffset(ptOff, bRedraw);
+	}
+
+	void SetScrollOffset(POINT ptOffset, BOOL bRedraw = TRUE)
+	{
+		SetScrollOffset(ptOffset.x, ptOffset.y, bRedraw);
+	}
+
+	void GetScrollOffset(POINT& ptOffset) const
+	{
+		ATLASSERT((m_nMapMode >= MM_MIN) && (m_nMapMode <= MM_MAX_FIXEDSCALE));
+		ptOffset = this->m_ptOffset;
+		// block: convert device to logical units
+		{
+			CWindowDC dc(NULL);
+			dc.SetMapMode(m_nMapMode);
+			dc.DPtoLP(&ptOffset);
+		}
+	}
+
+	// size operations
+	void SetScrollSize(int xMin, int yMin, int xMax, int yMax, BOOL bRedraw = TRUE, bool bResetOffset = true)
+	{
+		ATLASSERT((xMax > xMin) && (yMax > yMin));
+		ATLASSERT((m_nMapMode >= MM_MIN) && (m_nMapMode <= MM_MAX_FIXEDSCALE));
+
+		::SetRect(&m_rectLogAll, xMin, yMin, xMax, yMax);
+
+		SIZE sizeAll = {};
+		sizeAll.cx = xMax - xMin + 1;
+		sizeAll.cy = yMax - yMin + 1;
+		// block: convert logical to device units
+		{
+			CWindowDC dc(NULL);
+			dc.SetMapMode(m_nMapMode);
+			dc.LPtoDP(&sizeAll);
+		}
+		CScrollImpl< T >::SetScrollSize(sizeAll, bRedraw, bResetOffset);
+		SetScrollLine(0, 0);
+		SetScrollPage(0, 0);
+	}
+
+	void SetScrollSize(RECT& rcScroll, BOOL bRedraw = TRUE, bool bResetOffset = true)
+	{
+		SetScrollSize(rcScroll.left, rcScroll.top, rcScroll.right, rcScroll.bottom, bRedraw, bResetOffset);
+	}
+
+	void SetScrollSize(int cx, int cy, BOOL bRedraw = TRUE, bool bResetOffset = true)
+	{
+		SetScrollSize(0, 0, cx, cy, bRedraw, bResetOffset);
+	}
+
+	void SetScrollSize(SIZE size, BOOL bRedraw = TRUE, bool bResetOffset = true)
+	{
+		SetScrollSize(0, 0, size.cx, size.cy, bRedraw, bResetOffset);
+	}
+
+	void GetScrollSize(RECT& rcScroll) const
+	{
+		ATLASSERT((m_nMapMode >= MM_MIN) && (m_nMapMode <= MM_MAX_FIXEDSCALE));
+		rcScroll = m_rectLogAll;
+	}
+
+	// line operations
+	void SetScrollLine(int cxLine, int cyLine)
+	{
+		ATLASSERT((cxLine >= 0) && (cyLine >= 0));
+		ATLASSERT((m_nMapMode >= MM_MIN) && (m_nMapMode <= MM_MAX_FIXEDSCALE));
+
+		m_sizeLogLine.cx = cxLine;
+		m_sizeLogLine.cy = cyLine;
+		SIZE sizeLine = m_sizeLogLine;
+		// block: convert logical to device units
+		{
+			CWindowDC dc(NULL);
+			dc.SetMapMode(m_nMapMode);
+			dc.LPtoDP(&sizeLine);
+		}
+		CScrollImpl< T >::SetScrollLine(sizeLine);
+	}
+
+	void SetScrollLine(SIZE sizeLine)
+	{
+		SetScrollLine(sizeLine.cx, sizeLine.cy);
+	}
+
+	void GetScrollLine(SIZE& sizeLine) const
+	{
+		ATLASSERT((m_nMapMode >= MM_MIN) && (m_nMapMode <= MM_MAX_FIXEDSCALE));
+		sizeLine = m_sizeLogLine;
+	}
+
+	// page operations
+	void SetScrollPage(int cxPage, int cyPage)
+	{
+		ATLASSERT((cxPage >= 0) && (cyPage >= 0));
+		ATLASSERT((m_nMapMode >= MM_MIN) && (m_nMapMode <= MM_MAX_FIXEDSCALE));
+
+		m_sizeLogPage.cx = cxPage;
+		m_sizeLogPage.cy = cyPage;
+		SIZE sizePage = m_sizeLogPage;
+		// block: convert logical to device units
+		{
+			CWindowDC dc(NULL);
+			dc.SetMapMode(m_nMapMode);
+			dc.LPtoDP(&sizePage);
+		}
+		CScrollImpl< T >::SetScrollPage(sizePage);
+	}
+
+	void SetScrollPage(SIZE sizePage)
+	{
+		SetScrollPage(sizePage.cx, sizePage.cy);
+	}
+
+	void GetScrollPage(SIZE& sizePage) const
+	{
+		ATLASSERT((m_nMapMode >= MM_MIN) && (m_nMapMode <= MM_MAX_FIXEDSCALE));
+		sizePage = m_sizeLogPage;
+	}
+
+	BEGIN_MSG_MAP(CMapScrollImpl)
+		MESSAGE_HANDLER(WM_VSCROLL, CScrollImpl< T >::OnVScroll)
+		MESSAGE_HANDLER(WM_HSCROLL, CScrollImpl< T >::OnHScroll)
+		MESSAGE_HANDLER(WM_MOUSEWHEEL, CScrollImpl< T >::OnMouseWheel)
+		MESSAGE_HANDLER(WM_MOUSEHWHEEL, CScrollImpl< T >::OnMouseHWheel)
+		MESSAGE_HANDLER(WM_SETTINGCHANGE, CScrollImpl< T >::OnSettingChange)
+		MESSAGE_HANDLER(WM_SIZE, CScrollImpl< T >::OnSize)
+		MESSAGE_HANDLER(WM_PAINT, OnPaint)
+		MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
+	ALT_MSG_MAP(1)
+		COMMAND_ID_HANDLER(ID_SCROLL_UP, CScrollImpl< T >::OnScrollUp)
+		COMMAND_ID_HANDLER(ID_SCROLL_DOWN, CScrollImpl< T >::OnScrollDown)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_UP, CScrollImpl< T >::OnScrollPageUp)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_DOWN, CScrollImpl< T >::OnScrollPageDown)
+		COMMAND_ID_HANDLER(ID_SCROLL_TOP, CScrollImpl< T >::OnScrollTop)
+		COMMAND_ID_HANDLER(ID_SCROLL_BOTTOM, CScrollImpl< T >::OnScrollBottom)
+		COMMAND_ID_HANDLER(ID_SCROLL_LEFT, CScrollImpl< T >::OnScrollLeft)
+		COMMAND_ID_HANDLER(ID_SCROLL_RIGHT, CScrollImpl< T >::OnScrollRight)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_LEFT, CScrollImpl< T >::OnScrollPageLeft)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_RIGHT, CScrollImpl< T >::OnScrollPageRight)
+		COMMAND_ID_HANDLER(ID_SCROLL_ALL_LEFT, CScrollImpl< T >::OnScrollAllLeft)
+		COMMAND_ID_HANDLER(ID_SCROLL_ALL_RIGHT, CScrollImpl< T >::OnScrollAllRight)
+	END_MSG_MAP()
+
+	LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		if(wParam != NULL)
+		{
+			CDCHandle dc = (HDC)wParam;
+			int nMapModeSav = dc.GetMapMode();
+			dc.SetMapMode(m_nMapMode);
+			POINT ptViewportOrg = { 0, 0 };
+			if(m_nMapMode == MM_TEXT)
+				dc.SetViewportOrg(-this->m_ptOffset.x, -this->m_ptOffset.y, &ptViewportOrg);
+			else
+				dc.SetViewportOrg(-this->m_ptOffset.x, -this->m_ptOffset.y + this->m_sizeAll.cy, &ptViewportOrg);
+			POINT ptWindowOrg = { 0, 0 };
+			dc.SetWindowOrg(m_rectLogAll.left, m_rectLogAll.top, &ptWindowOrg);
+
+			pT->DoPaint(dc);
+
+			dc.SetMapMode(nMapModeSav);
+			dc.SetViewportOrg(ptViewportOrg);
+			dc.SetWindowOrg(ptWindowOrg);
+		}
+		else
+		{
+			CPaintDC dc(pT->m_hWnd);
+			dc.SetMapMode(m_nMapMode);
+			if(m_nMapMode == MM_TEXT)
+				dc.SetViewportOrg(-this->m_ptOffset.x, -this->m_ptOffset.y);
+			else
+				dc.SetViewportOrg(-this->m_ptOffset.x, -this->m_ptOffset.y + this->m_sizeAll.cy);
+			dc.SetWindowOrg(m_rectLogAll.left, m_rectLogAll.top);
+			pT->DoPaint(dc.m_hDC);
+		}
+		return 0;
+	}
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// CMapScrollWindowImpl - Implements scrolling window with mapping
+
+template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CControlWinTraits>
+class ATL_NO_VTABLE CMapScrollWindowImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >, public CMapScrollImpl< T >
+{
+public:
+	BOOL SubclassWindow(HWND hWnd)
+	{
+		BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
+		if(bRet != FALSE)
+		{
+			T* pT = static_cast<T*>(this);
+			pT->GetSystemSettings();
+
+			RECT rect = {};
+			this->GetClientRect(&rect);
+			pT->DoSize(rect.right, rect.bottom);
+		}
+
+		return bRet;
+	}
+
+	BEGIN_MSG_MAP(CMapScrollWindowImpl)
+		MESSAGE_HANDLER(WM_VSCROLL, CScrollImpl< T >::OnVScroll)
+		MESSAGE_HANDLER(WM_HSCROLL, CScrollImpl< T >::OnHScroll)
+		MESSAGE_HANDLER(WM_MOUSEWHEEL, CScrollImpl< T >::OnMouseWheel)
+		MESSAGE_HANDLER(WM_MOUSEHWHEEL, CScrollImpl< T >::OnMouseHWheel)
+		MESSAGE_HANDLER(WM_SETTINGCHANGE, CScrollImpl< T >::OnSettingChange)
+		MESSAGE_HANDLER(WM_SIZE, CScrollImpl< T >::OnSize)
+		MESSAGE_HANDLER(WM_PAINT, CMapScrollImpl< T >::OnPaint)
+		MESSAGE_HANDLER(WM_PRINTCLIENT, CMapScrollImpl< T >::OnPaint)
+	ALT_MSG_MAP(1)
+		COMMAND_ID_HANDLER(ID_SCROLL_UP, CScrollImpl< T >::OnScrollUp)
+		COMMAND_ID_HANDLER(ID_SCROLL_DOWN, CScrollImpl< T >::OnScrollDown)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_UP, CScrollImpl< T >::OnScrollPageUp)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_DOWN, CScrollImpl< T >::OnScrollPageDown)
+		COMMAND_ID_HANDLER(ID_SCROLL_TOP, CScrollImpl< T >::OnScrollTop)
+		COMMAND_ID_HANDLER(ID_SCROLL_BOTTOM, CScrollImpl< T >::OnScrollBottom)
+		COMMAND_ID_HANDLER(ID_SCROLL_LEFT, CScrollImpl< T >::OnScrollLeft)
+		COMMAND_ID_HANDLER(ID_SCROLL_RIGHT, CScrollImpl< T >::OnScrollRight)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_LEFT, CScrollImpl< T >::OnScrollPageLeft)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_RIGHT, CScrollImpl< T >::OnScrollPageRight)
+		COMMAND_ID_HANDLER(ID_SCROLL_ALL_LEFT, CScrollImpl< T >::OnScrollAllLeft)
+		COMMAND_ID_HANDLER(ID_SCROLL_ALL_RIGHT, CScrollImpl< T >::OnScrollAllRight)
+	END_MSG_MAP()
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// CFSBWindow - Use as a base instead of CWindow to get flat scroll bar support
+
+#ifdef __ATLCTRLS_H__
+
+template <class TBase = ATL::CWindow>
+class CFSBWindowT : public TBase, public CFlatScrollBarImpl<CFSBWindowT< TBase > >
+{
+public:
+// Constructors
+	CFSBWindowT(HWND hWnd = NULL) : TBase(hWnd)
+	{ }
+
+	CFSBWindowT< TBase >& operator =(HWND hWnd)
+	{
+		this->m_hWnd = hWnd;
+		return *this;
+	}
+
+// CWindow overrides that use flat scroll bar API
+// (only those methods that are used by scroll window classes)
+	int SetScrollPos(int nBar, int nPos, BOOL bRedraw = TRUE)
+	{
+		ATLASSERT(::IsWindow(this->m_hWnd));
+		return this->FlatSB_SetScrollPos(nBar, nPos, bRedraw);
+	}
+
+	BOOL GetScrollInfo(int nBar, LPSCROLLINFO lpScrollInfo)
+	{
+		ATLASSERT(::IsWindow(this->m_hWnd));
+		return this->FlatSB_GetScrollInfo(nBar, lpScrollInfo);
+	}
+
+	BOOL SetScrollInfo(int nBar, LPSCROLLINFO lpScrollInfo, BOOL bRedraw = TRUE)
+	{
+		ATLASSERT(::IsWindow(this->m_hWnd));
+		return this->FlatSB_SetScrollInfo(nBar, lpScrollInfo, bRedraw);
+	}
+};
+
+typedef CFSBWindowT<ATL::CWindow>   CFSBWindow;
+
+#endif // __ATLCTRLS_H__
+
+
+///////////////////////////////////////////////////////////////////////////////
+// CZoomScrollImpl - Provides zooming and scrolling support to any window
+
+// The zoom modes that can be set with the SetZoomMode method
+enum
+{
+	ZOOMMODE_OFF, 
+	ZOOMMODE_IN,   // If left mouse button is clicked or dragged, zoom in on point clicked or rectangle dragged.
+	ZOOMMODE_OUT   // If left mouse button clicked, zoom out on point clicked.
+};
+
+// Notification to parent that zoom scale changed as a result of user mouse action.
+#define ZSN_ZOOMCHANGED	(NM_FIRST - 50) 
+
+template <class T>
+class CZoomScrollImpl : public CScrollImpl< T >
+{
+public:
+	enum { m_cxyMinZoomRect = 12 };   // min rect size to zoom in on rect.
+
+	struct _ChildPlacement
+	{
+		HWND hWnd;
+		int x;
+		int y;
+		int cx;
+		int cy;
+
+		bool operator ==(const _ChildPlacement& cp) const { return (memcmp(this, &cp, sizeof(_ChildPlacement)) == 0); }
+	};
+
+// Data members
+	SIZE m_sizeLogAll;		
+	SIZE m_sizeLogLine;	
+	SIZE m_sizeLogPage;
+	float m_fZoomScale;
+	float m_fZoomScaleMin;
+	float m_fZoomScaleMax;
+	float m_fZoomDelta;   // Used in ZOOMMODE_IN and ZOOMMODE_OUT on left-button click.
+	int m_nZoomMode;		
+	RECT m_rcTrack;
+	bool m_bTracking;
+
+	bool m_bZoomChildren;
+	ATL::CSimpleArray<_ChildPlacement> m_arrChildren;
+
+// Constructor
+	CZoomScrollImpl(): m_fZoomScale(1.0f), m_fZoomScaleMin(0.1f), m_fZoomScaleMax(100.0f), m_fZoomDelta(0.5f), 
+	                   m_nZoomMode(ZOOMMODE_OFF), m_bTracking(false), m_bZoomChildren(false)
+	{
+		m_sizeLogAll.cx = 0;
+		m_sizeLogAll.cy = 0;
+		m_sizeLogPage.cx = 0;
+		m_sizeLogPage.cy = 0;
+		m_sizeLogLine.cx = 0;
+		m_sizeLogLine.cy = 0;
+		::SetRectEmpty(&m_rcTrack);
+	}
+
+// Attributes & Operations
+	// size operations
+	void SetScrollSize(int cxLog, int cyLog, BOOL bRedraw = TRUE, bool bResetOffset = true)
+	{
+		ATLASSERT((cxLog >= 0) && (cyLog >= 0));
+
+		// Set up the defaults
+		if((cxLog == 0) && (cyLog == 0))
+		{
+			cxLog = 1;
+			cyLog = 1;
+		}
+
+		m_sizeLogAll.cx = cxLog;
+		m_sizeLogAll.cy = cyLog;
+		SIZE sizeAll = {};
+		sizeAll.cx = (int)((float)m_sizeLogAll.cx * m_fZoomScale);
+		sizeAll.cy = (int)((float)m_sizeLogAll.cy * m_fZoomScale);
+
+		CScrollImpl< T >::SetScrollSize(sizeAll, bRedraw, bResetOffset);
+	}
+
+	void SetScrollSize(SIZE sizeLog, BOOL bRedraw = TRUE, bool bResetOffset = true)
+	{
+		SetScrollSize(sizeLog.cx, sizeLog.cy, bRedraw, bResetOffset);
+	}
+
+	void GetScrollSize(SIZE& sizeLog) const
+	{
+		sizeLog = m_sizeLogAll;
+	}
+
+	// line operations
+	void SetScrollLine(int cxLogLine, int cyLogLine)
+	{
+		ATLASSERT((cxLogLine >= 0) && (cyLogLine >= 0));
+
+		m_sizeLogLine.cx = cxLogLine;
+		m_sizeLogLine.cy = cyLogLine;
+
+		SIZE sizeLine = {};
+		sizeLine.cx = (int)((float)m_sizeLogLine.cx * m_fZoomScale);
+		sizeLine.cy = (int)((float)m_sizeLogLine.cy * m_fZoomScale);
+		CScrollImpl< T >::SetScrollLine(sizeLine);
+	}
+
+	void SetScrollLine(SIZE sizeLogLine)
+	{
+		SetScrollLine(sizeLogLine.cx, sizeLogLine.cy);
+	}
+
+	void GetScrollLine(SIZE& sizeLogLine) const
+	{
+		sizeLogLine = m_sizeLogLine;
+	}
+
+	// page operations
+	void SetScrollPage(int cxLogPage, int cyLogPage)
+	{
+		ATLASSERT((cxLogPage >= 0) && (cyLogPage >= 0));
+
+		m_sizeLogPage.cx = cxLogPage;
+		m_sizeLogPage.cy = cyLogPage;
+
+		SIZE sizePage = {};
+		sizePage.cx = (int)((float)m_sizeLogPage.cx * m_fZoomScale);
+		sizePage.cy = (int)((float)m_sizeLogPage.cy * m_fZoomScale);
+
+		CScrollImpl< T >::SetScrollPage(sizePage);
+	}
+
+	void SetScrollPage(SIZE sizeLogPage)
+	{
+		SetScrollPage(sizeLogPage.cx, sizeLogPage.cy);
+	}
+
+	void GetScrollPage(SIZE& sizeLogPage) const
+	{
+		sizeLogPage = m_sizeLogPage;
+	}
+
+	void SetZoomScale(float fZoomScale)
+	{
+		ATLASSERT(fZoomScale > 0.0f);
+		if(fZoomScale <= 0.0f)
+			return;
+
+		m_fZoomScale = fZoomScale;
+		if(m_fZoomScale < m_fZoomScaleMin)
+			m_fZoomScale = m_fZoomScaleMin;
+		else if(m_fZoomScale > m_fZoomScaleMax)
+			m_fZoomScale = m_fZoomScaleMax;
+	}
+
+	float GetZoomScale() const
+	{
+		return m_fZoomScale;
+	}
+
+	void SetZoomScaleMin(float fZoomScaleMin)
+	{
+		ATLASSERT(fZoomScaleMin > 0.0f);
+		ATLASSERT(fZoomScaleMin <= m_fZoomScaleMax);
+
+		m_fZoomScaleMin = fZoomScaleMin;
+	}
+
+	float GetZoomScaleMin() const
+	{
+		return m_fZoomScaleMin;
+	}
+
+	void SetZoomScaleMax(float fZoomScaleMax)
+	{
+		ATLASSERT(fZoomScaleMax > 0.0f);
+		ATLASSERT(m_fZoomScaleMin <= fZoomScaleMax);
+
+		m_fZoomScaleMax = fZoomScaleMax;
+	}
+
+	float GetZoomScaleMax() const
+	{
+		return m_fZoomScaleMax;
+	}
+
+	void SetZoomDelta(float fZoomDelta)
+	{
+		ATLASSERT(fZoomDelta >= 0.0f);
+
+		if(fZoomDelta >= 0.0f)
+			m_fZoomDelta = fZoomDelta;
+	}
+
+	float GetZoomDelta() const
+	{
+		return m_fZoomDelta;
+	}
+
+	void SetZoomMode(int nZoomMode)
+	{
+		m_nZoomMode = nZoomMode;
+	}
+
+	int GetZoomMode() const
+	{
+		return m_nZoomMode;
+	}
+
+	void SetZoomChildren(bool bEnable = true)
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+
+		m_bZoomChildren = bEnable;
+
+		m_arrChildren.RemoveAll();
+		if(m_bZoomChildren)
+		{
+			for(HWND hWndChild = ::GetWindow(pT->m_hWnd, GW_CHILD); hWndChild != NULL; hWndChild = ::GetWindow(hWndChild, GW_HWNDNEXT))
+			{
+				RECT rect = {};
+				::GetWindowRect(hWndChild, &rect);
+				::MapWindowPoints(NULL, pT->m_hWnd, (LPPOINT)&rect, 2);
+
+				_ChildPlacement cp = {};
+				cp.hWnd = hWndChild;
+				cp.x = rect.left;
+				cp.y = rect.top;
+				cp.cx = rect.right - rect.left;
+				cp.cy = rect.bottom - rect.top;
+				m_arrChildren.Add(cp);
+			}
+		}
+	}
+
+	bool GetZoomChildren() const
+	{
+		return m_bZoomChildren;
+	}
+
+	void Zoom(int x, int y, float fZoomScale)
+	{
+		if(fZoomScale <= 0.0f)
+			return;
+
+		if(fZoomScale < m_fZoomScaleMin)
+			fZoomScale = m_fZoomScaleMin;
+		else if(fZoomScale > m_fZoomScaleMax)
+			fZoomScale = m_fZoomScaleMax;
+
+		T* pT = static_cast<T*>(this);
+		POINT pt = { x, y };
+		if(!pT->PtInDevRect(pt))
+			return;
+
+		pT->ViewDPtoLP(&pt);
+		pT->Zoom(fZoomScale, false);
+		pT->CenterOnLogicalPoint(pt);
+	}
+
+	void Zoom(POINT pt, float fZoomScale)
+	{
+		T* pT = static_cast<T*>(this);
+		pT->Zoom(pt.x, pt.y, fZoomScale);
+	}
+
+	void Zoom(RECT& rc)
+	{
+		T* pT = static_cast<T*>(this);
+		RECT rcZoom = rc;
+		pT->NormalizeRect(rcZoom);
+		SIZE size = { rcZoom.right - rcZoom.left, rcZoom.bottom - rcZoom.top };
+		POINT pt = { rcZoom.left + size.cx / 2, rcZoom.top + size.cy / 2 };
+		if((size.cx < m_cxyMinZoomRect) || (size.cy < m_cxyMinZoomRect))
+		{
+			pT->Zoom(pt, m_fZoomScale + m_fZoomDelta);
+			return;
+		}
+
+		ATLASSERT((size.cx > 0) && (size.cy > 0));
+		
+		float fScaleH = (float)(this->m_sizeClient.cx  + 1) / (float)size.cx;
+		float fScaleV = (float)(this->m_sizeClient.cy + 1) / (float)size.cy;
+		float fZoomScale = __min(fScaleH, fScaleV) * m_fZoomScale;
+		pT->Zoom(pt, fZoomScale);		
+	}
+
+	void Zoom(float fZoomScale, bool bCenter = true)
+	{
+		if(fZoomScale <= 0.0f)
+			return;
+
+		if(fZoomScale < m_fZoomScaleMin)
+			fZoomScale = m_fZoomScaleMin;
+		else if(fZoomScale > m_fZoomScaleMax)
+			fZoomScale = m_fZoomScaleMax;
+
+		T* pT = static_cast<T*>(this);
+		POINT pt = { 0, 0 };
+		if(bCenter)
+		{
+			RECT rcClient = {};
+			::GetClientRect(pT->m_hWnd, &rcClient);
+			pt.x = rcClient.right / 2;
+			pt.y = rcClient.bottom / 2;
+			pT->ViewDPtoLP(&pt);
+		}
+
+		// Modify the Viewport extent
+		SIZE sizeAll = {};
+		sizeAll.cx = (int)((float)m_sizeLogAll.cx * fZoomScale);
+		sizeAll.cy = (int)((float)m_sizeLogAll.cy * fZoomScale);
+		
+		// Update scroll bars and window
+		CScrollImpl< T >::SetScrollSize(sizeAll);
+
+		// Zoom all children if needed
+		if(m_bZoomChildren && (m_fZoomScale != fZoomScale))
+		{
+			for(int i = 0; i < m_arrChildren.GetSize(); i++)
+			{
+				ATLASSERT(::IsWindow(m_arrChildren[i].hWnd));
+
+				::SetWindowPos(m_arrChildren[i].hWnd, NULL, 
+					(int)((float)m_arrChildren[i].x * fZoomScale + 0.5f), 
+					(int)((float)m_arrChildren[i].y * fZoomScale + 0.5f), 
+					(int)((float)m_arrChildren[i].cx * fZoomScale + 0.5f), 
+					(int)((float)m_arrChildren[i].cy * fZoomScale + 0.5f), 
+					SWP_NOZORDER | SWP_NOACTIVATE);
+			}
+		}
+
+		// Set new zoom scale
+		m_fZoomScale = fZoomScale;
+
+		if(bCenter)
+			pT->CenterOnLogicalPoint(pt);
+	}
+
+	void ZoomIn(bool bCenter = true)
+	{
+		T* pT = static_cast<T*>(this);
+		pT->Zoom(m_fZoomScale + m_fZoomDelta, bCenter);
+	}
+
+	void ZoomOut(bool bCenter = true)
+	{
+		T* pT = static_cast<T*>(this);
+		pT->Zoom(m_fZoomScale - m_fZoomDelta, bCenter);
+	}
+
+	void ZoomDefault(bool bCenter = true)
+	{
+		T* pT = static_cast<T*>(this);
+		pT->Zoom(1.0f, bCenter);
+	}
+
+	// Helper functions
+	void PrepareDC(CDCHandle dc)
+	{
+		ATLASSERT((this->m_sizeAll.cx >= 0) && (this->m_sizeAll.cy >= 0));
+		dc.SetMapMode(MM_ANISOTROPIC);
+		dc.SetWindowExt(this->m_sizeLogAll);
+		dc.SetViewportExt(this->m_sizeAll);
+		dc.SetViewportOrg(-this->m_ptOffset.x, -this->m_ptOffset.y);
+	}
+
+	void ViewDPtoLP(LPPOINT lpPoints, int nCount = 1)
+	{
+		ATLASSERT(lpPoints);
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+
+		CWindowDC dc(pT->m_hWnd);
+		pT->PrepareDC(dc.m_hDC);
+		dc.DPtoLP(lpPoints, nCount);
+	}
+
+	void ViewLPtoDP(LPPOINT lpPoints, int nCount = 1)
+	{
+		ATLASSERT(lpPoints);
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+	
+		CWindowDC dc(pT->m_hWnd);
+		pT->PrepareDC(dc.m_hDC);
+		dc.LPtoDP(lpPoints, nCount);
+	}
+
+	void ClientToDevice(POINT &pt)
+	{
+		pt.x += this->m_ptOffset.x;
+		pt.y += this->m_ptOffset.y;
+	}	 
+
+	void DeviceToClient(POINT &pt)
+	{
+		pt.x -= this->m_ptOffset.x;
+		pt.y -= this->m_ptOffset.y;
+	}
+
+	void CenterOnPoint(POINT pt)
+	{
+		T* pT = static_cast<T*>(this);
+		RECT rect = {};
+		pT->GetClientRect(&rect);
+
+		int xOfs = pt.x - (rect.right / 2) + this->m_ptOffset.x;
+		if(xOfs < 0)
+		{
+			xOfs = 0;
+		}
+		else 
+		{
+			int xMax = __max((int)(this->m_sizeAll.cx - rect.right), 0);
+			if(xOfs > xMax)
+				xOfs = xMax;
+		}
+		
+		int yOfs = pt.y - (rect.bottom / 2) + this->m_ptOffset.y;
+		if(yOfs < 0)
+		{
+			yOfs = 0;
+		}
+		else 
+		{
+			int yMax = __max((int)(this->m_sizeAll.cy - rect.bottom), 0);
+			if(yOfs > yMax)
+				yOfs = yMax;
+		}
+
+		CScrollImpl< T >::SetScrollOffset(xOfs, yOfs);
+	}
+
+	void CenterOnLogicalPoint(POINT ptLog)
+	{
+		T* pT = static_cast<T*>(this);
+		pT->ViewLPtoDP(&ptLog);
+		pT->DeviceToClient(ptLog);
+		pT->CenterOnPoint(ptLog);
+	}
+
+	BOOL PtInDevRect(POINT pt)
+	{
+		RECT rc = { 0, 0, this->m_sizeAll.cx, this->m_sizeAll.cy };
+		::OffsetRect(&rc, -this->m_ptOffset.x, -this->m_ptOffset.y);
+		return ::PtInRect(&rc, pt);
+	}
+
+	void NormalizeRect(RECT& rc)
+	{
+		if(rc.left > rc.right) 
+		{
+			int r = rc.right;
+			rc.right = rc.left;
+			rc.left = r;
+		}
+
+		if(rc.top > rc.bottom)
+		{
+			int b = rc.bottom;
+			rc.bottom = rc.top;
+			rc.top = b;
+		}
+	}
+
+	void DrawTrackRect()
+	{
+		T* pT = static_cast<T*>(this);
+		const SIZE sizeLines = { 2, 2 };
+		RECT rc = m_rcTrack;
+		pT->NormalizeRect(rc);
+		if(!::IsRectEmpty(&rc))
+		{
+			CClientDC dc(pT->m_hWnd);
+			dc.DrawDragRect(&rc, sizeLines, NULL, sizeLines);
+		}
+	}
+
+	void NotifyParentZoomChanged()
+	{
+		T* pT = static_cast<T*>(this);
+		int nId = pT->GetDlgCtrlID();
+		NMHDR nmhdr = { pT->m_hWnd, (UINT_PTR)nId, ZSN_ZOOMCHANGED };
+		::SendMessage(pT->GetParent(), WM_NOTIFY, (WPARAM)nId, (LPARAM)&nmhdr);
+	}
+
+	void DoWheelZoom(int zDelta)
+	{
+		float fZoomScale = m_fZoomScale + ((zDelta > 0) ? m_fZoomDelta : -m_fZoomDelta);
+		T* pT = static_cast<T*>(this);
+		pT->Zoom(fZoomScale);
+		pT->NotifyParentZoomChanged();
+	}
+
+	BEGIN_MSG_MAP(CZoomScrollImpl)
+		MESSAGE_HANDLER(WM_SETCURSOR, OnSetCursor)
+		MESSAGE_HANDLER(WM_VSCROLL, CScrollImpl< T >::OnVScroll)
+		MESSAGE_HANDLER(WM_HSCROLL, CScrollImpl< T >::OnHScroll)
+		MESSAGE_HANDLER(WM_MOUSEWHEEL, OnMouseWheel)
+		MESSAGE_HANDLER(WM_MOUSEHWHEEL, CScrollImpl< T >::OnMouseHWheel)
+		MESSAGE_HANDLER(WM_SETTINGCHANGE, CScrollImpl< T >::OnSettingChange)
+		MESSAGE_HANDLER(WM_SIZE, CScrollImpl< T >::OnSize)
+		MESSAGE_HANDLER(WM_PAINT, OnPaint)
+		MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
+		MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
+		MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
+		MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp)
+		MESSAGE_HANDLER(WM_CAPTURECHANGED, OnCaptureChanged)
+	ALT_MSG_MAP(1)
+		COMMAND_ID_HANDLER(ID_SCROLL_UP, CScrollImpl< T >::OnScrollUp)
+		COMMAND_ID_HANDLER(ID_SCROLL_DOWN, CScrollImpl< T >::OnScrollDown)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_UP, CScrollImpl< T >::OnScrollPageUp)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_DOWN, CScrollImpl< T >::OnScrollPageDown)
+		COMMAND_ID_HANDLER(ID_SCROLL_TOP, CScrollImpl< T >::OnScrollTop)
+		COMMAND_ID_HANDLER(ID_SCROLL_BOTTOM, CScrollImpl< T >::OnScrollBottom)
+		COMMAND_ID_HANDLER(ID_SCROLL_LEFT, CScrollImpl< T >::OnScrollLeft)
+		COMMAND_ID_HANDLER(ID_SCROLL_RIGHT, CScrollImpl< T >::OnScrollRight)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_LEFT, CScrollImpl< T >::OnScrollPageLeft)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_RIGHT, CScrollImpl< T >::OnScrollPageRight)
+		COMMAND_ID_HANDLER(ID_SCROLL_ALL_LEFT, CScrollImpl< T >::OnScrollAllLeft)
+		COMMAND_ID_HANDLER(ID_SCROLL_ALL_RIGHT, CScrollImpl< T >::OnScrollAllRight)
+	END_MSG_MAP()
+
+	LRESULT OnSetCursor(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+	{
+		if((LOWORD(lParam) == HTCLIENT) && (m_nZoomMode != ZOOMMODE_OFF))
+		{
+			T* pT = static_cast<T*>(this);
+			if((HWND)wParam == pT->m_hWnd)
+			{
+				DWORD dwPos = ::GetMessagePos();
+				POINT pt = { GET_X_LPARAM(dwPos), GET_Y_LPARAM(dwPos) };
+				pT->ScreenToClient(&pt);
+				if(pT->PtInDevRect(pt))
+				{
+					::SetCursor(::LoadCursor(NULL, IDC_CROSS));
+					return 1;
+				}
+			}
+		}
+
+		bHandled = FALSE;
+		return 0;
+	}
+
+	LRESULT OnMouseWheel(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+	{
+		if((GET_KEYSTATE_WPARAM(wParam) & MK_CONTROL) != 0)   // handle zoom if Ctrl is pressed
+		{
+			int zDelta = (int)GET_WHEEL_DELTA_WPARAM(wParam);
+			T* pT = static_cast<T*>(this);
+			pT->DoWheelZoom(zDelta);
+		}
+		else
+		{
+			CScrollImpl< T >::OnMouseWheel(uMsg, wParam, lParam, bHandled);
+		}
+
+		return 0;
+	}
+
+	LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
+	{
+		T* pT = static_cast<T*>(this);
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		ATLASSERT((m_sizeLogAll.cx >= 0) && (m_sizeLogAll.cy >= 0));
+		ATLASSERT((this->m_sizeAll.cx >= 0) && (this->m_sizeAll.cy >= 0));
+
+		if(wParam != NULL)
+		{
+			CDCHandle dc = (HDC)wParam;
+			int nMapModeSav = dc.GetMapMode();
+			dc.SetMapMode(MM_ANISOTROPIC);
+			SIZE szWindowExt = { 0, 0 };
+			dc.SetWindowExt(m_sizeLogAll, &szWindowExt);
+			SIZE szViewportExt = { 0, 0 };
+			dc.SetViewportExt(this->m_sizeAll, &szViewportExt);
+			POINT ptViewportOrg = { 0, 0 };
+			dc.SetViewportOrg(-this->m_ptOffset.x, -this->m_ptOffset.y, &ptViewportOrg);
+
+			pT->DoPaint(dc);
+
+			dc.SetMapMode(nMapModeSav);
+			dc.SetWindowExt(szWindowExt);
+			dc.SetViewportExt(szViewportExt);
+			dc.SetViewportOrg(ptViewportOrg);
+		}
+		else
+		{
+			CPaintDC dc(pT->m_hWnd);
+			pT->PrepareDC(dc.m_hDC);
+			pT->DoPaint(dc.m_hDC);
+		}
+
+		return 0;
+	}
+
+	LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
+	{
+		if((m_nZoomMode == ZOOMMODE_IN) && !m_bTracking)
+		{
+			T* pT = static_cast<T*>(this);
+			POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
+			if(pT->PtInDevRect(pt))
+			{
+				pT->SetCapture();
+				m_bTracking = true;
+				::SetRect(&m_rcTrack, pt.x, pt.y, pt.x, pt.y);
+
+				RECT rcClip;
+				pT->GetClientRect(&rcClip);
+				if((this->m_ptOffset.x == 0) && (this->m_ptOffset.y == 0))
+				{
+					if(rcClip.right > this->m_sizeAll.cx)
+						rcClip.right = this->m_sizeAll.cx;
+					if(rcClip.bottom > this->m_sizeAll.cy)
+						rcClip.bottom = this->m_sizeAll.cy;
+				}
+				::MapWindowPoints(pT->m_hWnd, NULL, (LPPOINT)&rcClip, 2);
+				::ClipCursor(&rcClip);
+			}	
+		}
+
+		bHandled = FALSE;
+		return 0;
+	}
+
+	LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
+	{
+		if(m_bTracking)
+		{
+			T* pT = static_cast<T*>(this);
+			POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
+			if(pT->PtInDevRect(pt))
+			{
+				pT->DrawTrackRect();
+				m_rcTrack.right = pt.x + 1;
+				m_rcTrack.bottom = pt.y + 1;
+				pT->DrawTrackRect();
+			}
+		}
+
+		bHandled = FALSE;
+		return 0;
+	}
+
+	LRESULT OnLButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
+	{
+		::ReleaseCapture();
+		if(m_nZoomMode == ZOOMMODE_OUT)
+		{
+			T* pT = static_cast<T*>(this);
+			pT->Zoom(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), m_fZoomScale - m_fZoomDelta);
+			pT->NotifyParentZoomChanged();
+		}
+
+		bHandled = FALSE;
+		return 0;
+	}
+
+	LRESULT OnCaptureChanged(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
+	{
+		if(m_bTracking)
+		{
+			m_bTracking = false;
+			T* pT = static_cast<T*>(this);
+			pT->DrawTrackRect();
+			pT->Zoom(m_rcTrack);
+			pT->NotifyParentZoomChanged();
+			::SetRectEmpty(&m_rcTrack);
+			::ClipCursor(NULL);
+		}
+
+		bHandled = FALSE;
+		return 0;
+	}	
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// CZoomScrollWindowImpl - Implements scrolling window with zooming
+
+template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CControlWinTraits>
+class ATL_NO_VTABLE CZoomScrollWindowImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >, public CZoomScrollImpl< T >
+{
+public:
+	BOOL SubclassWindow(HWND hWnd)
+	{
+		BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
+		if(bRet != FALSE)
+		{
+			T* pT = static_cast<T*>(this);
+			pT->GetSystemSettings();
+
+			RECT rect = {};
+			this->GetClientRect(&rect);
+			pT->DoSize(rect.right, rect.bottom);
+		}
+
+		return bRet;
+	}
+
+	BEGIN_MSG_MAP(CZoomScrollWindowImpl)
+		MESSAGE_HANDLER(WM_SETCURSOR, CZoomScrollImpl< T >::OnSetCursor)
+		MESSAGE_HANDLER(WM_VSCROLL, CScrollImpl< T >::OnVScroll)
+		MESSAGE_HANDLER(WM_HSCROLL, CScrollImpl< T >::OnHScroll)
+		MESSAGE_HANDLER(WM_MOUSEWHEEL, CZoomScrollImpl< T >::OnMouseWheel)
+		MESSAGE_HANDLER(WM_MOUSEHWHEEL, CScrollImpl< T >::OnMouseHWheel)
+		MESSAGE_HANDLER(WM_SETTINGCHANGE, CScrollImpl< T >::OnSettingChange)
+		MESSAGE_HANDLER(WM_SIZE, CScrollImpl< T >::OnSize)
+		MESSAGE_HANDLER(WM_PAINT, CZoomScrollImpl< T >::OnPaint)
+		MESSAGE_HANDLER(WM_PRINTCLIENT, CZoomScrollImpl< T >::OnPaint)
+		MESSAGE_HANDLER(WM_LBUTTONDOWN, CZoomScrollImpl< T >::OnLButtonDown)
+		MESSAGE_HANDLER(WM_MOUSEMOVE, CZoomScrollImpl< T >::OnMouseMove)
+		MESSAGE_HANDLER(WM_LBUTTONUP, CZoomScrollImpl< T >::OnLButtonUp)
+		MESSAGE_HANDLER(WM_CAPTURECHANGED, CZoomScrollImpl< T >::OnCaptureChanged)
+	ALT_MSG_MAP(1)
+		COMMAND_ID_HANDLER(ID_SCROLL_UP, CScrollImpl< T >::OnScrollUp)
+		COMMAND_ID_HANDLER(ID_SCROLL_DOWN, CScrollImpl< T >::OnScrollDown)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_UP, CScrollImpl< T >::OnScrollPageUp)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_DOWN, CScrollImpl< T >::OnScrollPageDown)
+		COMMAND_ID_HANDLER(ID_SCROLL_TOP, CScrollImpl< T >::OnScrollTop)
+		COMMAND_ID_HANDLER(ID_SCROLL_BOTTOM, CScrollImpl< T >::OnScrollBottom)
+		COMMAND_ID_HANDLER(ID_SCROLL_LEFT, CScrollImpl< T >::OnScrollLeft)
+		COMMAND_ID_HANDLER(ID_SCROLL_RIGHT, CScrollImpl< T >::OnScrollRight)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_LEFT, CScrollImpl< T >::OnScrollPageLeft)
+		COMMAND_ID_HANDLER(ID_SCROLL_PAGE_RIGHT, CScrollImpl< T >::OnScrollPageRight)
+		COMMAND_ID_HANDLER(ID_SCROLL_ALL_LEFT, CScrollImpl< T >::OnScrollAllLeft)
+		COMMAND_ID_HANDLER(ID_SCROLL_ALL_RIGHT, CScrollImpl< T >::OnScrollAllRight)
+	END_MSG_MAP()
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// CScrollContainer
+
+template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CControlWinTraits>
+class ATL_NO_VTABLE CScrollContainerImpl : public CScrollWindowImpl< T, TBase, TWinTraits >
+{
+public:
+	DECLARE_WND_CLASS_EX2(NULL, T, 0, -1)
+
+	typedef CScrollWindowImpl< T, TBase, TWinTraits >   _baseClass;
+
+// Data members
+	ATL::CWindow m_wndClient;
+	bool m_bAutoSizeClient;
+	bool m_bDrawEdgeIfEmpty;
+
+// Constructor
+	CScrollContainerImpl() : m_bAutoSizeClient(true), m_bDrawEdgeIfEmpty(false)
+	{
+		// Set CScrollWindowImpl extended style
+		this->SetScrollExtendedStyle(SCRL_SCROLLCHILDREN);
+	}
+
+// Attributes
+	HWND GetClient() const
+	{
+		return m_wndClient;
+	}
+
+	HWND SetClient(HWND hWndClient, bool bClientSizeAsMin = true)
+	{
+		ATLASSERT(::IsWindow(this->m_hWnd));
+
+		HWND hWndOldClient = m_wndClient;
+		m_wndClient = hWndClient;
+
+		this->SetRedraw(FALSE);
+		this->SetScrollSize(1, 1, FALSE);
+
+		if(m_wndClient.m_hWnd != NULL)
+		{
+			m_wndClient.SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
+
+			if(bClientSizeAsMin)
+			{
+				RECT rect = {};
+				m_wndClient.GetWindowRect(&rect);
+				if(((rect.right - rect.left) > 0) && ((rect.bottom - rect.top) > 0))
+					this->SetScrollSize(rect.right - rect.left, rect.bottom - rect.top, FALSE);
+			}
+
+			T* pT = static_cast<T*>(this);
+			pT->UpdateLayout();
+		}
+
+		this->SetRedraw(TRUE);
+		this->RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_FRAME | RDW_UPDATENOW | RDW_ALLCHILDREN);
+
+		return hWndOldClient;
+	}
+
+// Message map and handlers
+	BEGIN_MSG_MAP(CScrollContainerImpl)
+		MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
+		MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
+		CHAIN_MSG_MAP(_baseClass)
+		FORWARD_NOTIFICATIONS()
+	ALT_MSG_MAP(1)
+		CHAIN_MSG_MAP_ALT(_baseClass, 1)
+	END_MSG_MAP()
+
+	LRESULT OnSetFocus(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
+	{
+		if(m_wndClient.m_hWnd != NULL)
+			m_wndClient.SetFocus();
+
+		return 0;
+	}
+
+	LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
+	{
+		return 1;   // no background needed
+	}
+
+// Overrides for CScrollWindowImpl
+	void DoSize(int cx, int cy)
+	{
+		_baseClass::DoSize(cx, cy);
+
+		T* pT = static_cast<T*>(this);
+		pT->UpdateLayout();
+	}
+
+	void DoPaint(CDCHandle dc)
+	{
+		if(!m_bAutoSizeClient || (m_wndClient.m_hWnd == NULL))
+		{
+			T* pT = static_cast<T*>(this);
+			RECT rect = {};
+			pT->GetContainerRect(rect);
+
+			if(m_bDrawEdgeIfEmpty && (m_wndClient.m_hWnd == NULL))
+				dc.DrawEdge(&rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+
+			dc.FillRect(&rect, COLOR_APPWORKSPACE);
+		}
+	}
+
+	void ScrollToView(POINT pt)
+	{
+		CScrollWindowImpl< T, TBase, TWinTraits >::ScrollToView(pt);
+	}
+
+	void ScrollToView(RECT& rect)
+	{
+		CScrollWindowImpl< T, TBase, TWinTraits >::ScrollToView(rect);
+	}
+
+	void ScrollToView(HWND hWnd)   // client window coordinates
+	{
+		T* pT = static_cast<T*>(this);
+		(void)pT;   // avoid level 4 warning
+		ATLASSERT(::IsWindow(pT->m_hWnd));
+		ATLASSERT(m_wndClient.IsWindow());
+
+		RECT rect = {};
+		::GetWindowRect(hWnd, &rect);
+		::MapWindowPoints(NULL, m_wndClient.m_hWnd, (LPPOINT)&rect, 2);
+		ScrollToView(rect);
+	}
+
+// Implementation - overrideable methods
+	void UpdateLayout()
+	{
+		ATLASSERT(::IsWindow(this->m_hWnd));
+
+		if(m_bAutoSizeClient && (m_wndClient.m_hWnd != NULL))
+		{
+			T* pT = static_cast<T*>(this);
+			RECT rect = {};
+			pT->GetContainerRect(rect);
+
+			m_wndClient.SetWindowPos(NULL, &rect, SWP_NOZORDER | SWP_NOMOVE);
+		}
+		else
+		{
+			this->Invalidate();
+		}
+	}
+
+	void GetContainerRect(RECT& rect)
+	{
+		this->GetClientRect(&rect);
+
+		if(rect.right < this->m_sizeAll.cx)
+			rect.right = this->m_sizeAll.cx;
+
+		if(rect.bottom < this->m_sizeAll.cy)
+			rect.bottom = this->m_sizeAll.cy;
+	}
+};
+
+class CScrollContainer : public CScrollContainerImpl<CScrollContainer>
+{
+public:
+	DECLARE_WND_CLASS_EX(_T("WTL_ScrollContainer"), 0, -1)
+};
+
+} // namespace WTL
+
+#endif // __ATLSCRL_H__