diff foosdk/wtl/Include/atlsplit.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/atlsplit.h	Mon Jan 05 02:15:46 2026 -0500
@@ -0,0 +1,1129 @@
+// 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 __ATLSPLIT_H__
+#define __ATLSPLIT_H__
+
+#pragma once
+
+#ifndef __ATLAPP_H__
+	#error atlsplit.h requires atlapp.h to be included first
+#endif
+
+#ifndef __ATLWIN_H__
+	#error atlsplit.h requires atlwin.h to be included first
+#endif
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Classes in this file:
+//
+// CSplitterImpl<T>
+// CSplitterWindowImpl<T, TBase, TWinTraits>
+// CSplitterWindowT<t_bVertical> - CSplitterWindow, CHorSplitterWindow
+
+
+namespace WTL
+{
+
+///////////////////////////////////////////////////////////////////////////////
+// CSplitterImpl - Provides splitter support to any window
+
+// Splitter panes constants
+#define SPLIT_PANE_LEFT			 0
+#define SPLIT_PANE_RIGHT		 1
+#define SPLIT_PANE_TOP			 SPLIT_PANE_LEFT
+#define SPLIT_PANE_BOTTOM		 SPLIT_PANE_RIGHT
+#define SPLIT_PANE_NONE			-1
+
+// Splitter extended styles
+#define SPLIT_PROPORTIONAL		0x00000001
+#define SPLIT_NONINTERACTIVE		0x00000002
+#define SPLIT_RIGHTALIGNED		0x00000004
+#define SPLIT_BOTTOMALIGNED		SPLIT_RIGHTALIGNED
+#define SPLIT_GRADIENTBAR		0x00000008
+#define SPLIT_FLATBAR			0x00000020
+#define SPLIT_FIXEDBARSIZE		0x00000010
+
+// Note: SPLIT_PROPORTIONAL and SPLIT_RIGHTALIGNED/SPLIT_BOTTOMALIGNED are 
+// mutually exclusive. If both are set, splitter defaults to SPLIT_PROPORTIONAL.
+// Also, SPLIT_FLATBAR overrides SPLIT_GRADIENTBAR if both are set.
+
+
+template <class T>
+class CSplitterImpl
+{
+public:
+	enum { m_nPanesCount = 2, m_nPropMax = INT_MAX, m_cxyStep = 10 };
+
+	bool m_bVertical;
+	HWND m_hWndPane[m_nPanesCount];
+	RECT m_rcSplitter;
+	int m_xySplitterPos;            // splitter bar position
+	int m_xySplitterPosNew;         // internal - new position while moving
+	HWND m_hWndFocusSave;
+	int m_nDefActivePane;
+	int m_cxySplitBar;              // splitter bar width/height
+	HCURSOR m_hCursor;
+	int m_cxyMin;                   // minimum pane size
+	int m_cxyBarEdge;              	// splitter bar edge
+	bool m_bFullDrag;
+	int m_cxyDragOffset;		// internal
+	int m_nProportionalPos;
+	bool m_bUpdateProportionalPos;
+	DWORD m_dwExtendedStyle;        // splitter specific extended styles
+	int m_nSinglePane;              // single pane mode
+	int m_xySplitterDefPos;         // default position
+	bool m_bProportionalDefPos;     // porportinal def pos
+
+// Constructor
+	CSplitterImpl(bool bVertical = true) : 
+	              m_bVertical(bVertical), m_xySplitterPos(-1), m_xySplitterPosNew(-1), m_hWndFocusSave(NULL), 
+	              m_nDefActivePane(SPLIT_PANE_NONE), m_cxySplitBar(4), m_hCursor(NULL), m_cxyMin(0), m_cxyBarEdge(0), 
+	              m_bFullDrag(true), m_cxyDragOffset(0), m_nProportionalPos(0), m_bUpdateProportionalPos(true),
+	              m_dwExtendedStyle(SPLIT_PROPORTIONAL), m_nSinglePane(SPLIT_PANE_NONE), 
+	              m_xySplitterDefPos(-1), m_bProportionalDefPos(false)
+	{
+		m_hWndPane[SPLIT_PANE_LEFT] = NULL;
+		m_hWndPane[SPLIT_PANE_RIGHT] = NULL;
+
+		::SetRectEmpty(&m_rcSplitter);
+	}
+
+// Attributes
+	void SetSplitterRect(LPRECT lpRect = NULL, bool bUpdate = true)
+	{
+		if(lpRect == NULL)
+		{
+			T* pT = static_cast<T*>(this);
+			pT->GetClientRect(&m_rcSplitter);
+		}
+		else
+		{
+			m_rcSplitter = *lpRect;
+		}
+
+		if(IsProportional())
+			UpdateProportionalPos();
+		else if(IsRightAligned())
+			UpdateRightAlignPos();
+
+		if(bUpdate)
+			UpdateSplitterLayout();
+	}
+
+	void GetSplitterRect(LPRECT lpRect) const
+	{
+		ATLASSERT(lpRect != NULL);
+		*lpRect = m_rcSplitter;
+	}
+
+	bool SetSplitterPos(int xyPos = -1, bool bUpdate = true)
+	{
+		if(xyPos == -1)   // -1 == default position
+		{
+			if(m_bProportionalDefPos)
+			{
+				ATLASSERT((m_xySplitterDefPos >= 0) && (m_xySplitterDefPos <= m_nPropMax));
+
+				if(m_bVertical)
+					xyPos = ::MulDiv(m_xySplitterDefPos, m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge, m_nPropMax);
+				else
+					xyPos = ::MulDiv(m_xySplitterDefPos, m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge, m_nPropMax);
+			}
+			else if(m_xySplitterDefPos != -1)
+			{
+				xyPos = m_xySplitterDefPos;
+			}
+			else   // not set, use middle position
+			{
+				if(m_bVertical)
+					xyPos = (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) / 2;
+				else
+					xyPos = (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge) / 2;
+			}
+		}
+
+		// Adjust if out of valid range
+		int cxyMax = 0;
+		if(m_bVertical)
+			cxyMax = m_rcSplitter.right - m_rcSplitter.left;
+		else
+			cxyMax = m_rcSplitter.bottom - m_rcSplitter.top;
+
+		if(xyPos < m_cxyMin + m_cxyBarEdge)
+			xyPos = m_cxyMin;
+		else if(xyPos > (cxyMax - m_cxySplitBar - m_cxyBarEdge - m_cxyMin))
+			xyPos = cxyMax - m_cxySplitBar - m_cxyBarEdge - m_cxyMin;
+
+		// Set new position and update if requested
+		bool bRet = (m_xySplitterPos != xyPos);
+		m_xySplitterPos = xyPos;
+
+		if(m_bUpdateProportionalPos)
+		{
+			if(IsProportional())
+				StoreProportionalPos();
+			else if(IsRightAligned())
+				StoreRightAlignPos();
+		}
+		else
+		{
+			m_bUpdateProportionalPos = true;
+		}
+
+		if(bUpdate && bRet)
+			UpdateSplitterLayout();
+
+		return bRet;
+	}
+
+	int GetSplitterPos() const
+	{
+		return m_xySplitterPos;
+	}
+
+	void SetSplitterPosPct(int nPct, bool bUpdate = true)
+	{
+		ATLASSERT((nPct >= 0) && (nPct <= 100));
+
+		m_nProportionalPos = ::MulDiv(nPct, m_nPropMax, 100);
+		UpdateProportionalPos();
+
+		if(bUpdate)
+			UpdateSplitterLayout();
+	}
+
+	int GetSplitterPosPct() const
+	{
+		int cxyTotal = m_bVertical ? (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) : (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge);
+		return ((cxyTotal > 0) && (m_xySplitterPos >= 0)) ? ::MulDiv(m_xySplitterPos, 100, cxyTotal) : -1;
+	}
+
+	bool SetSinglePaneMode(int nPane = SPLIT_PANE_NONE)
+	{
+		ATLASSERT((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT) || (nPane == SPLIT_PANE_NONE));
+		if(!((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT) || (nPane == SPLIT_PANE_NONE)))
+			return false;
+
+		if(nPane != SPLIT_PANE_NONE)
+		{
+			if(::IsWindowVisible(m_hWndPane[nPane]) == FALSE)
+				::ShowWindow(m_hWndPane[nPane], SW_SHOW);
+			int nOtherPane = (nPane == SPLIT_PANE_LEFT) ? SPLIT_PANE_RIGHT : SPLIT_PANE_LEFT;
+			::ShowWindow(m_hWndPane[nOtherPane], SW_HIDE);
+			if(m_nDefActivePane != nPane)
+				m_nDefActivePane = nPane;
+		}
+		else if(m_nSinglePane != SPLIT_PANE_NONE)
+		{
+			int nOtherPane = (m_nSinglePane == SPLIT_PANE_LEFT) ? SPLIT_PANE_RIGHT : SPLIT_PANE_LEFT;
+			::ShowWindow(m_hWndPane[nOtherPane], SW_SHOW);
+		}
+
+		m_nSinglePane = nPane;
+		UpdateSplitterLayout();
+
+		return true;
+	}
+
+	int GetSinglePaneMode() const
+	{
+		return m_nSinglePane;
+	}
+
+	DWORD GetSplitterExtendedStyle() const
+	{
+		return m_dwExtendedStyle;
+	}
+
+	DWORD SetSplitterExtendedStyle(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(IsProportional() && IsRightAligned())
+			ATLTRACE2(atlTraceUI, 0, _T("CSplitterImpl::SetSplitterExtendedStyle - SPLIT_PROPORTIONAL and SPLIT_RIGHTALIGNED are mutually exclusive, defaulting to SPLIT_PROPORTIONAL.\n"));
+#endif // _DEBUG
+
+		return dwPrevStyle;
+	}
+
+	void SetSplitterDefaultPos(int xyPos = -1)
+	{
+		m_xySplitterDefPos = xyPos;
+		m_bProportionalDefPos = false;
+	}
+
+	void SetSplitterDefaultPosPct(int nPct)
+	{
+		ATLASSERT((nPct >= 0) && (nPct <= 100));
+
+		m_xySplitterDefPos = ::MulDiv(nPct, m_nPropMax, 100);
+		m_bProportionalDefPos = true;
+	}
+
+// Splitter operations
+	void SetSplitterPanes(HWND hWndLeftTop, HWND hWndRightBottom, bool bUpdate = true)
+	{
+		m_hWndPane[SPLIT_PANE_LEFT] = hWndLeftTop;
+		m_hWndPane[SPLIT_PANE_RIGHT] = hWndRightBottom;
+		ATLASSERT((m_hWndPane[SPLIT_PANE_LEFT] == NULL) || (m_hWndPane[SPLIT_PANE_RIGHT] == NULL) || (m_hWndPane[SPLIT_PANE_LEFT] != m_hWndPane[SPLIT_PANE_RIGHT]));
+		if(bUpdate)
+			UpdateSplitterLayout();
+	}
+
+	bool SetSplitterPane(int nPane, HWND hWnd, bool bUpdate = true)
+	{
+		ATLASSERT((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT));
+		if((nPane != SPLIT_PANE_LEFT) && (nPane != SPLIT_PANE_RIGHT))
+			return false;
+
+		m_hWndPane[nPane] = hWnd;
+		ATLASSERT((m_hWndPane[SPLIT_PANE_LEFT] == NULL) || (m_hWndPane[SPLIT_PANE_RIGHT] == NULL) || (m_hWndPane[SPLIT_PANE_LEFT] != m_hWndPane[SPLIT_PANE_RIGHT]));
+		if(bUpdate)
+			UpdateSplitterLayout();
+
+		return true;
+	}
+
+	HWND GetSplitterPane(int nPane) const
+	{
+		ATLASSERT((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT));
+		if((nPane != SPLIT_PANE_LEFT) && (nPane != SPLIT_PANE_RIGHT))
+			return NULL;
+
+		return m_hWndPane[nPane];
+	}
+
+	bool SetActivePane(int nPane)
+	{
+		ATLASSERT((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT));
+		if((nPane != SPLIT_PANE_LEFT) && (nPane != SPLIT_PANE_RIGHT))
+			return false;
+		if((m_nSinglePane != SPLIT_PANE_NONE) && (nPane != m_nSinglePane))
+			return false;
+
+		::SetFocus(m_hWndPane[nPane]);
+		m_nDefActivePane = nPane;
+
+		return true;
+	}
+
+	int GetActivePane() const
+	{
+		int nRet = SPLIT_PANE_NONE;
+		HWND hWndFocus = ::GetFocus();
+		if(hWndFocus != NULL)
+		{
+			for(int nPane = 0; nPane < m_nPanesCount; nPane++)
+			{
+				if((hWndFocus == m_hWndPane[nPane]) || (::IsChild(m_hWndPane[nPane], hWndFocus) != FALSE))
+				{
+					nRet = nPane;
+					break;
+				}
+			}
+		}
+
+		return nRet;
+	}
+
+	bool ActivateNextPane(bool bNext = true)
+	{
+		int nPane = m_nSinglePane;
+		if(nPane == SPLIT_PANE_NONE)
+		{
+			switch(GetActivePane())
+			{
+			case SPLIT_PANE_LEFT:
+				nPane = SPLIT_PANE_RIGHT;
+				break;
+			case SPLIT_PANE_RIGHT:
+				nPane = SPLIT_PANE_LEFT;
+				break;
+			default:
+				nPane = bNext ? SPLIT_PANE_LEFT : SPLIT_PANE_RIGHT;
+				break;
+			}
+		}
+
+		return SetActivePane(nPane);
+	}
+
+	bool SetDefaultActivePane(int nPane)
+	{
+		ATLASSERT((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT));
+		if((nPane != SPLIT_PANE_LEFT) && (nPane != SPLIT_PANE_RIGHT))
+			return false;
+
+		m_nDefActivePane = nPane;
+
+		return true;
+	}
+
+	bool SetDefaultActivePane(HWND hWnd)
+	{
+		for(int nPane = 0; nPane < m_nPanesCount; nPane++)
+		{
+			if(hWnd == m_hWndPane[nPane])
+			{
+				m_nDefActivePane = nPane;
+				return true;
+			}
+		}
+
+		return false;   // not found
+	}
+
+	int GetDefaultActivePane() const
+	{
+		return m_nDefActivePane;
+	}
+
+	void DrawSplitter(CDCHandle dc)
+	{
+		ATLASSERT(dc.m_hDC != NULL);
+		if((m_nSinglePane == SPLIT_PANE_NONE) && (m_xySplitterPos == -1))
+			return;
+
+		T* pT = static_cast<T*>(this);
+		if(m_nSinglePane == SPLIT_PANE_NONE)
+		{
+			pT->DrawSplitterBar(dc);
+
+			for(int nPane = 0; nPane < m_nPanesCount; nPane++)
+			{
+				if(m_hWndPane[nPane] == NULL)
+					pT->DrawSplitterPane(dc, nPane);
+			}
+		}
+		else
+		{
+			if(m_hWndPane[m_nSinglePane] == NULL)
+				pT->DrawSplitterPane(dc, m_nSinglePane);
+		}
+	}
+
+	// call to initiate moving splitter bar with keyboard
+	void MoveSplitterBar()
+	{
+		T* pT = static_cast<T*>(this);
+
+		int x = 0;
+		int y = 0;
+		if(m_bVertical)
+		{
+			x = m_xySplitterPos + (m_cxySplitBar / 2) + m_cxyBarEdge;
+			y = (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge) / 2;
+		}
+		else
+		{
+			x = (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) / 2;
+			y = m_xySplitterPos + (m_cxySplitBar / 2) + m_cxyBarEdge;
+		}
+
+		POINT pt = { x, y };
+		pT->ClientToScreen(&pt);
+		::SetCursorPos(pt.x, pt.y);
+
+		m_xySplitterPosNew = m_xySplitterPos;
+		pT->SetCapture();
+		m_hWndFocusSave = pT->SetFocus();
+		::SetCursor(m_hCursor);
+		if(!m_bFullDrag)
+			DrawGhostBar();
+		if(m_bVertical)
+			m_cxyDragOffset = x - m_rcSplitter.left - m_xySplitterPos;
+		else
+			m_cxyDragOffset = y - m_rcSplitter.top - m_xySplitterPos;
+	}
+
+	void SetOrientation(bool bVertical, bool bUpdate = true)
+	{
+		if(m_bVertical != bVertical)
+		{
+			m_bVertical = bVertical;
+
+			m_hCursor = ::LoadCursor(NULL, m_bVertical ? IDC_SIZEWE : IDC_SIZENS);
+
+			T* pT = static_cast<T*>(this);
+			pT->GetSystemSettings(false);
+
+			if(m_bVertical)
+				m_xySplitterPos = ::MulDiv(m_xySplitterPos, m_rcSplitter.right - m_rcSplitter.left, m_rcSplitter.bottom - m_rcSplitter.top);
+			else
+				m_xySplitterPos = ::MulDiv(m_xySplitterPos, m_rcSplitter.bottom - m_rcSplitter.top, m_rcSplitter.right - m_rcSplitter.left);
+		}
+
+		if(bUpdate)
+			UpdateSplitterLayout();
+	}
+
+// Overrideables
+	void DrawSplitterBar(CDCHandle dc)
+	{
+		RECT rect = {};
+		if(GetSplitterBarRect(&rect))
+		{
+			dc.FillRect(&rect, COLOR_3DFACE);
+
+			if((m_dwExtendedStyle & SPLIT_FLATBAR) != 0)
+			{
+				RECT rect1 = rect;
+				if(m_bVertical)
+					rect1.right = rect1.left + 1;
+				else
+					rect1.bottom = rect1.top + 1;
+				dc.FillRect(&rect1, COLOR_WINDOW);
+
+				rect1 = rect;
+				if(m_bVertical)
+					rect1.left = rect1.right - 1;
+				else
+					rect1.top = rect1.bottom - 1;
+				dc.FillRect(&rect1, COLOR_3DSHADOW);
+			}
+			else if((m_dwExtendedStyle & SPLIT_GRADIENTBAR) != 0)
+			{
+				RECT rect2 = rect;
+				if(m_bVertical)
+					rect2.left = (rect.left + rect.right) / 2 - 1;
+				else
+					rect2.top = (rect.top + rect.bottom) / 2 - 1;
+
+				dc.GradientFillRect(rect2, ::GetSysColor(COLOR_3DFACE), ::GetSysColor(COLOR_3DSHADOW), m_bVertical);
+			}
+
+			// draw 3D edge if needed
+			T* pT = static_cast<T*>(this);
+			if((pT->GetExStyle() & WS_EX_CLIENTEDGE) != 0)
+				dc.DrawEdge(&rect, EDGE_RAISED, m_bVertical ? (BF_LEFT | BF_RIGHT) : (BF_TOP | BF_BOTTOM));
+		}
+	}
+
+	// called only if pane is empty
+	void DrawSplitterPane(CDCHandle dc, int nPane)
+	{
+		RECT rect = {};
+		if(GetSplitterPaneRect(nPane, &rect))
+		{
+			T* pT = static_cast<T*>(this);
+			if((pT->GetExStyle() & WS_EX_CLIENTEDGE) == 0)
+				dc.DrawEdge(&rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+			dc.FillRect(&rect, COLOR_APPWORKSPACE);
+		}
+	}
+
+// Message map and handlers
+	BEGIN_MSG_MAP(CSplitterImpl)
+		MESSAGE_HANDLER(WM_CREATE, OnCreate)
+		MESSAGE_HANDLER(WM_PAINT, OnPaint)
+		MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
+		if(IsInteractive())
+		{
+			MESSAGE_HANDLER(WM_SETCURSOR, OnSetCursor)
+			MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
+			MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
+			MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp)
+			MESSAGE_HANDLER(WM_LBUTTONDBLCLK, OnLButtonDoubleClick)
+			MESSAGE_HANDLER(WM_CAPTURECHANGED, OnCaptureChanged)
+			MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown)
+		}
+		MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
+		MESSAGE_HANDLER(WM_MOUSEACTIVATE, OnMouseActivate)
+		MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChange)
+	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 OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
+	{
+		T* pT = static_cast<T*>(this);
+
+		// try setting position if not set
+		if((m_nSinglePane == SPLIT_PANE_NONE) && (m_xySplitterPos == -1))
+			pT->SetSplitterPos();
+
+		// do painting
+		if(wParam != NULL)
+		{
+			pT->DrawSplitter((HDC)wParam);
+		}
+		else
+		{
+			CPaintDC dc(pT->m_hWnd);
+			pT->DrawSplitter(dc.m_hDC);
+		}
+
+		return 0;
+	}
+
+	LRESULT OnSetCursor(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+	{
+		T* pT = static_cast<T*>(this);
+		if(((HWND)wParam == pT->m_hWnd) && (LOWORD(lParam) == HTCLIENT))
+		{
+			DWORD dwPos = ::GetMessagePos();
+			POINT ptPos = { GET_X_LPARAM(dwPos), GET_Y_LPARAM(dwPos) };
+			pT->ScreenToClient(&ptPos);
+			if(IsOverSplitterBar(ptPos.x, ptPos.y))
+				return 1;
+		}
+
+		bHandled = FALSE;
+		return 0;
+	}
+
+	LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
+	{
+		T* pT = static_cast<T*>(this);
+		int xPos = GET_X_LPARAM(lParam);
+		int yPos = GET_Y_LPARAM(lParam);
+		if(::GetCapture() == pT->m_hWnd)
+		{
+			int xyNewSplitPos = 0;
+			if(m_bVertical)
+				xyNewSplitPos = xPos - m_rcSplitter.left - m_cxyDragOffset;
+			else
+				xyNewSplitPos = yPos - m_rcSplitter.top - m_cxyDragOffset;
+
+			if(xyNewSplitPos == -1)   // avoid -1, that means default position
+				xyNewSplitPos = -2;
+
+			if(m_xySplitterPos != xyNewSplitPos)
+			{
+				if(m_bFullDrag)
+				{
+					if(pT->SetSplitterPos(xyNewSplitPos, true))
+						pT->UpdateWindow();
+				}
+				else
+				{
+					DrawGhostBar();
+					pT->SetSplitterPos(xyNewSplitPos, false);
+					DrawGhostBar();
+				}
+			}
+		}
+		else		// not dragging, just set cursor
+		{
+			if(IsOverSplitterBar(xPos, yPos))
+				::SetCursor(m_hCursor);
+			bHandled = FALSE;
+		}
+
+		return 0;
+	}
+
+	LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
+	{
+		T* pT = static_cast<T*>(this);
+		int xPos = GET_X_LPARAM(lParam);
+		int yPos = GET_Y_LPARAM(lParam);
+		if((::GetCapture() != pT->m_hWnd) && IsOverSplitterBar(xPos, yPos))
+		{
+			m_xySplitterPosNew = m_xySplitterPos;
+			pT->SetCapture();
+			m_hWndFocusSave = pT->SetFocus();
+			::SetCursor(m_hCursor);
+			if(!m_bFullDrag)
+				DrawGhostBar();
+			if(m_bVertical)
+				m_cxyDragOffset = xPos - m_rcSplitter.left - m_xySplitterPos;
+			else
+				m_cxyDragOffset = yPos - m_rcSplitter.top - m_xySplitterPos;
+		}
+		else if((::GetCapture() == pT->m_hWnd) && !IsOverSplitterBar(xPos, yPos))
+		{
+			::ReleaseCapture();
+		}
+
+		bHandled = FALSE;
+		return 1;
+	}
+
+	LRESULT OnLButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
+	{
+		T* pT = static_cast<T*>(this);
+		if(::GetCapture() == pT->m_hWnd)
+		{
+			m_xySplitterPosNew = m_xySplitterPos;
+			::ReleaseCapture();
+		}
+
+		bHandled = FALSE;
+		return 1;
+	}
+
+	LRESULT OnLButtonDoubleClick(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
+	{
+		T* pT = static_cast<T*>(this);
+		pT->SetSplitterPos();   // default
+
+		return 0;
+	}
+
+	LRESULT OnCaptureChanged(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
+	{
+		if(!m_bFullDrag)
+			DrawGhostBar();
+
+		if((m_xySplitterPosNew != -1) && (!m_bFullDrag || (m_xySplitterPos != m_xySplitterPosNew)))
+		{
+			m_xySplitterPos = m_xySplitterPosNew;
+			m_xySplitterPosNew = -1;
+			UpdateSplitterLayout();
+			T* pT = static_cast<T*>(this);
+			pT->UpdateWindow();
+		}
+
+		if(m_hWndFocusSave != NULL)
+			::SetFocus(m_hWndFocusSave);
+
+		return 0;
+	}
+
+	LRESULT OnKeyDown(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
+	{
+		T* pT = static_cast<T*>(this);
+		if(::GetCapture() == pT->m_hWnd)
+		{
+			switch(wParam)
+			{
+			case VK_RETURN:
+				m_xySplitterPosNew = m_xySplitterPos;
+				// FALLTHROUGH
+			case VK_ESCAPE:
+				::ReleaseCapture();
+				break;
+			case VK_LEFT:
+			case VK_RIGHT:
+				if(m_bVertical)
+				{
+					POINT pt = {};
+					::GetCursorPos(&pt);
+					int xyPos = m_xySplitterPos + ((wParam == VK_LEFT) ? -pT->m_cxyStep : pT->m_cxyStep);
+					int cxyMax = m_rcSplitter.right - m_rcSplitter.left;
+					if(xyPos < (m_cxyMin + m_cxyBarEdge))
+						xyPos = m_cxyMin;
+					else if(xyPos > (cxyMax - m_cxySplitBar - m_cxyBarEdge - m_cxyMin))
+						xyPos = cxyMax - m_cxySplitBar - m_cxyBarEdge - m_cxyMin;
+					pt.x += xyPos - m_xySplitterPos;
+					::SetCursorPos(pt.x, pt.y);
+				}
+				break;
+			case VK_UP:
+			case VK_DOWN:
+				if(!m_bVertical)
+				{
+					POINT pt = {};
+					::GetCursorPos(&pt);
+					int xyPos = m_xySplitterPos + ((wParam == VK_UP) ? -pT->m_cxyStep : pT->m_cxyStep);
+					int cxyMax = m_rcSplitter.bottom - m_rcSplitter.top;
+					if(xyPos < (m_cxyMin + m_cxyBarEdge))
+						xyPos = m_cxyMin;
+					else if(xyPos > (cxyMax - m_cxySplitBar - m_cxyBarEdge - m_cxyMin))
+						xyPos = cxyMax - m_cxySplitBar - m_cxyBarEdge - m_cxyMin;
+					pt.y += xyPos - m_xySplitterPos;
+					::SetCursorPos(pt.x, pt.y);
+				}
+				break;
+			default:
+				break;
+			}
+		}
+		else
+		{
+			bHandled = FALSE;
+		}
+
+		return 0;
+	}
+
+	LRESULT OnSetFocus(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM, BOOL& bHandled)
+	{
+		T* pT = static_cast<T*>(this);
+		if(::GetCapture() != pT->m_hWnd)
+		{
+			if(m_nSinglePane == SPLIT_PANE_NONE)
+			{
+				if((m_nDefActivePane == SPLIT_PANE_LEFT) || (m_nDefActivePane == SPLIT_PANE_RIGHT))
+					::SetFocus(m_hWndPane[m_nDefActivePane]);
+			}
+			else
+			{
+				::SetFocus(m_hWndPane[m_nSinglePane]);
+			}
+		}
+
+		bHandled = FALSE;
+		return 1;
+	}
+
+	LRESULT OnMouseActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
+	{
+		T* pT = static_cast<T*>(this);
+		LRESULT lRet = pT->DefWindowProc(uMsg, wParam, lParam);
+		if((lRet == MA_ACTIVATE) || (lRet == MA_ACTIVATEANDEAT))
+		{
+			DWORD dwPos = ::GetMessagePos();
+			POINT pt = { GET_X_LPARAM(dwPos), GET_Y_LPARAM(dwPos) };
+			pT->ScreenToClient(&pt);
+			RECT rcPane = {};
+			for(int nPane = 0; nPane < m_nPanesCount; nPane++)
+			{
+				if(GetSplitterPaneRect(nPane, &rcPane) && (::PtInRect(&rcPane, pt) != FALSE))
+				{
+					m_nDefActivePane = nPane;
+					break;
+				}
+			}
+		}
+
+		return lRet;
+	}
+
+	LRESULT OnSettingChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
+	{
+		T* pT = static_cast<T*>(this);
+		pT->GetSystemSettings(true);
+
+		return 0;
+	}
+
+// Implementation - internal helpers
+	void Init()
+	{
+		m_hCursor = ::LoadCursor(NULL, m_bVertical ? IDC_SIZEWE : IDC_SIZENS);
+
+		T* pT = static_cast<T*>(this);
+		pT->GetSystemSettings(false);
+	}
+
+	void UpdateSplitterLayout()
+	{
+		if((m_nSinglePane == SPLIT_PANE_NONE) && (m_xySplitterPos == -1))
+			return;
+
+		T* pT = static_cast<T*>(this);
+		RECT rect = {};
+		if(m_nSinglePane == SPLIT_PANE_NONE)
+		{
+			if(GetSplitterBarRect(&rect))
+				pT->InvalidateRect(&rect);
+
+			for(int nPane = 0; nPane < m_nPanesCount; nPane++)
+			{
+				if(GetSplitterPaneRect(nPane, &rect))
+				{
+					if(m_hWndPane[nPane] != NULL)
+						::SetWindowPos(m_hWndPane[nPane], NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER);
+					else
+						pT->InvalidateRect(&rect);
+				}
+			}
+		}
+		else
+		{
+			if(GetSplitterPaneRect(m_nSinglePane, &rect))
+			{
+				if(m_hWndPane[m_nSinglePane] != NULL)
+					::SetWindowPos(m_hWndPane[m_nSinglePane], NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER);
+				else
+					pT->InvalidateRect(&rect);
+			}
+		}
+	}
+
+	bool GetSplitterBarRect(LPRECT lpRect) const
+	{
+		ATLASSERT(lpRect != NULL);
+		if((m_nSinglePane != SPLIT_PANE_NONE) || (m_xySplitterPos == -1))
+			return false;
+
+		if(m_bVertical)
+		{
+			lpRect->left = m_rcSplitter.left + m_xySplitterPos;
+			lpRect->top = m_rcSplitter.top;
+			lpRect->right = m_rcSplitter.left + m_xySplitterPos + m_cxySplitBar + m_cxyBarEdge;
+			lpRect->bottom = m_rcSplitter.bottom;
+		}
+		else
+		{
+			lpRect->left = m_rcSplitter.left;
+			lpRect->top = m_rcSplitter.top + m_xySplitterPos;
+			lpRect->right = m_rcSplitter.right;
+			lpRect->bottom = m_rcSplitter.top + m_xySplitterPos + m_cxySplitBar + m_cxyBarEdge;
+		}
+
+		return true;
+	}
+
+	bool GetSplitterPaneRect(int nPane, LPRECT lpRect) const
+	{
+		ATLASSERT((nPane == SPLIT_PANE_LEFT) || (nPane == SPLIT_PANE_RIGHT));
+		ATLASSERT(lpRect != NULL);
+		bool bRet = true;
+		if(m_nSinglePane != SPLIT_PANE_NONE)
+		{
+			if(nPane == m_nSinglePane)
+				*lpRect = m_rcSplitter;
+			else
+				bRet = false;
+		}
+		else if(nPane == SPLIT_PANE_LEFT)
+		{
+			if(m_bVertical)
+			{
+				lpRect->left = m_rcSplitter.left;
+				lpRect->top = m_rcSplitter.top;
+				lpRect->right = m_rcSplitter.left + m_xySplitterPos;
+				lpRect->bottom = m_rcSplitter.bottom;
+			}
+			else
+			{
+				lpRect->left = m_rcSplitter.left;
+				lpRect->top = m_rcSplitter.top;
+				lpRect->right = m_rcSplitter.right;
+				lpRect->bottom = m_rcSplitter.top + m_xySplitterPos;
+			}
+		}
+		else if(nPane == SPLIT_PANE_RIGHT)
+		{
+			if(m_bVertical)
+			{
+				lpRect->left = m_rcSplitter.left + m_xySplitterPos + m_cxySplitBar + m_cxyBarEdge;
+				lpRect->top = m_rcSplitter.top;
+				lpRect->right = m_rcSplitter.right;
+				lpRect->bottom = m_rcSplitter.bottom;
+			}
+			else
+			{
+				lpRect->left = m_rcSplitter.left;
+				lpRect->top = m_rcSplitter.top + m_xySplitterPos + m_cxySplitBar + m_cxyBarEdge;
+				lpRect->right = m_rcSplitter.right;
+				lpRect->bottom = m_rcSplitter.bottom;
+			}
+		}
+		else
+		{
+			bRet = false;
+		}
+
+		return bRet;
+	}
+
+	bool IsOverSplitterRect(int x, int y) const
+	{
+		// -1 == don't check
+		return (((x == -1) || ((x >= m_rcSplitter.left) && (x <= m_rcSplitter.right))) &&
+			((y == -1) || ((y >= m_rcSplitter.top) && (y <= m_rcSplitter.bottom))));
+	}
+
+	bool IsOverSplitterBar(int x, int y) const
+	{
+		if(m_nSinglePane != SPLIT_PANE_NONE)
+			return false;
+		if((m_xySplitterPos == -1) || !IsOverSplitterRect(x, y))
+			return false;
+		int xy = m_bVertical ? x : y;
+		int xyOff = m_bVertical ? m_rcSplitter.left : m_rcSplitter.top;
+
+		return ((xy >= (xyOff + m_xySplitterPos)) && (xy < (xyOff + m_xySplitterPos + m_cxySplitBar + m_cxyBarEdge)));
+	}
+
+	void DrawGhostBar()
+	{
+		RECT rect = {};
+		if(GetSplitterBarRect(&rect))
+		{
+			// convert client to window coordinates
+			T* pT = static_cast<T*>(this);
+			RECT rcWnd = {};
+			pT->GetWindowRect(&rcWnd);
+			::MapWindowPoints(NULL, pT->m_hWnd, (LPPOINT)&rcWnd, 2);
+			::OffsetRect(&rect, -rcWnd.left, -rcWnd.top);
+
+			// invert the brush pattern (looks just like frame window sizing)
+			CWindowDC dc(pT->m_hWnd);
+			CBrush brush(CDCHandle::GetHalftoneBrush());
+			if(brush.m_hBrush != NULL)
+			{
+				CBrushHandle brushOld = dc.SelectBrush(brush);
+				dc.PatBlt(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, PATINVERT);
+				dc.SelectBrush(brushOld);
+			}
+		}
+	}
+
+	void GetSystemSettings(bool bUpdate)
+	{
+		if((m_dwExtendedStyle & SPLIT_FIXEDBARSIZE) == 0)
+		{
+			m_cxySplitBar = ::GetSystemMetrics(m_bVertical ? SM_CXSIZEFRAME : SM_CYSIZEFRAME);
+		}
+
+		T* pT = static_cast<T*>(this);
+		if((pT->GetExStyle() & WS_EX_CLIENTEDGE) != 0)
+		{
+			m_cxyBarEdge = 2 * ::GetSystemMetrics(m_bVertical ? SM_CXEDGE : SM_CYEDGE);
+			m_cxyMin = 0;
+		}
+		else
+		{
+			m_cxyBarEdge = 0;
+			m_cxyMin = 2 * ::GetSystemMetrics(m_bVertical ? SM_CXEDGE : SM_CYEDGE);
+		}
+
+		::SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, &m_bFullDrag, 0);
+
+		if(bUpdate)
+			UpdateSplitterLayout();
+	}
+
+	bool IsProportional() const
+	{
+		return ((m_dwExtendedStyle & SPLIT_PROPORTIONAL) != 0);
+	}
+
+	void StoreProportionalPos()
+	{
+		int cxyTotal = m_bVertical ? (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) : (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge);
+		if(cxyTotal > 0)
+			m_nProportionalPos = ::MulDiv(m_xySplitterPos, m_nPropMax, cxyTotal);
+		else
+			m_nProportionalPos = 0;
+		ATLTRACE2(atlTraceUI, 0, _T("CSplitterImpl::StoreProportionalPos - %i\n"), m_nProportionalPos);
+	}
+
+	void UpdateProportionalPos()
+	{
+		int cxyTotal = m_bVertical ? (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) : (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge);
+		if(cxyTotal > 0)
+		{
+			int xyNewPos = ::MulDiv(m_nProportionalPos, cxyTotal, m_nPropMax);
+			m_bUpdateProportionalPos = false;
+			T* pT = static_cast<T*>(this);
+			pT->SetSplitterPos(xyNewPos, false);
+		}
+	}
+
+	bool IsRightAligned() const
+	{
+		return ((m_dwExtendedStyle & SPLIT_RIGHTALIGNED) != 0);
+	}
+
+	void StoreRightAlignPos()
+	{
+		int cxyTotal = m_bVertical ? (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) : (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge);
+		if(cxyTotal > 0)
+			m_nProportionalPos = cxyTotal - m_xySplitterPos;
+		else
+			m_nProportionalPos = 0;
+		ATLTRACE2(atlTraceUI, 0, _T("CSplitterImpl::StoreRightAlignPos - %i\n"), m_nProportionalPos);
+	}
+
+	void UpdateRightAlignPos()
+	{
+		int cxyTotal = m_bVertical ? (m_rcSplitter.right - m_rcSplitter.left - m_cxySplitBar - m_cxyBarEdge) : (m_rcSplitter.bottom - m_rcSplitter.top - m_cxySplitBar - m_cxyBarEdge);
+		if(cxyTotal > 0)
+		{
+			m_bUpdateProportionalPos = false;
+			T* pT = static_cast<T*>(this);
+			pT->SetSplitterPos(cxyTotal - m_nProportionalPos, false);
+		}
+	}
+
+	bool IsInteractive() const
+	{
+		return ((m_dwExtendedStyle & SPLIT_NONINTERACTIVE) == 0);
+	}
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// CSplitterWindowImpl - Implements a splitter window
+
+template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CControlWinTraits>
+class ATL_NO_VTABLE CSplitterWindowImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >, public CSplitterImpl< T >
+{
+public:
+	DECLARE_WND_CLASS_EX2(NULL, T, CS_DBLCLKS, COLOR_WINDOW)
+
+	CSplitterWindowImpl(bool bVertical = true) : CSplitterImpl< T >(bVertical)
+	{ }
+
+	BOOL SubclassWindow(HWND hWnd)
+	{
+		BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
+		if(bRet != FALSE)
+		{
+			T* pT = static_cast<T*>(this);
+			pT->Init();
+
+			this->SetSplitterRect();
+		}
+
+		return bRet;
+	}
+
+	BEGIN_MSG_MAP(CSplitterWindowImpl)
+		MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
+		MESSAGE_HANDLER(WM_SIZE, OnSize)
+		CHAIN_MSG_MAP(CSplitterImpl< T >)
+		FORWARD_NOTIFICATIONS()
+	END_MSG_MAP()
+
+	LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
+	{
+		// handled, no background painting needed
+		return 1;
+	}
+
+	LRESULT OnSize(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
+	{
+		if(wParam != SIZE_MINIMIZED)
+			this->SetSplitterRect();
+
+		bHandled = FALSE;
+		return 1;
+	}
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// CSplitterWindow/CHorSplitterWindow - Implements splitter windows to be used as is
+
+template <bool t_bVertical = true>
+class CSplitterWindowT : public CSplitterWindowImpl<CSplitterWindowT<t_bVertical> >
+{
+public:
+	DECLARE_WND_CLASS_EX2(_T("WTL_SplitterWindow"), CSplitterWindowT<t_bVertical>, CS_DBLCLKS, COLOR_WINDOW)
+
+	CSplitterWindowT() : CSplitterWindowImpl<CSplitterWindowT<t_bVertical> >(t_bVertical)
+	{ }
+};
+
+typedef CSplitterWindowT<true>    CSplitterWindow;
+typedef CSplitterWindowT<false>   CHorSplitterWindow;
+
+} // namespace WTL
+
+#endif // __ATLSPLIT_H__