diff foosdk/sdk/libPPUI/CListControlHeaderImpl.cpp @ 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/sdk/libPPUI/CListControlHeaderImpl.cpp	Mon Jan 05 02:15:46 2026 -0500
@@ -0,0 +1,1309 @@
+#include "stdafx.h"
+#include "CListControl.h"
+#include "CListControlHeaderImpl.h" // redundant but makes intelisense quit showing false errors
+#include "CListControl-Cells.h"
+#include "PaintUtils.h"
+#include "GDIUtils.h"
+#include "win32_utility.h"
+#include "DarkMode.h"
+#include <vssym32.h>
+
+static constexpr int lineBelowHeaderCY = 1;
+
+// Prevent erratic behavior of header control
+static constexpr uint32_t columnWidthSanityLimit = 10000;
+
+static uint32_t columWidthToPixels(uint32_t user) {
+	PFC_ASSERT(user <= CListControlHeaderImpl::columnWidthMax);
+	return user < columnWidthSanityLimit ? user : columnWidthSanityLimit;
+}
+
+static bool testDrawLineBelowHeader() {
+	// Win10
+	return IsWindows10OrGreater();
+}
+
+void CListControlHeaderImpl::OnThemeChangedPT() {
+	if (m_header) {
+		auto dark = GetDarkMode();
+		if (dark != m_headerDark) {
+			m_headerDark = dark;
+			DarkMode::ApplyDarkThemeCtrl(m_header, dark, L"ItemsView");
+		}
+	}
+	this->SetMsgHandled(FALSE);
+}
+
+void CListControlHeaderImpl::InitializeHeaderCtrl(DWORD flags) {
+	PFC_ASSERT(IsWindow());
+	PFC_ASSERT(!IsHeaderEnabled());
+	m_headerDark = false;
+	WIN32_OP_D( m_header.Create(*this,NULL,NULL,WS_CHILD | flags) != NULL );
+	m_header.SetFont( GetFont() );
+
+	InjectParentEraseHandler(m_header);
+
+	this->OnThemeChangedPT();
+
+	if (testDrawLineBelowHeader()) {
+		WIN32_OP_D( m_headerLine.Create( *this, NULL, NULL, WS_CHILD ) != NULL );
+		InjectParentEraseHandler(m_headerLine);
+	}
+
+	UpdateHeaderLayout();
+}
+
+void CListControlHeaderImpl::UpdateHeaderLayout() {
+	CRect client; WIN32_OP_D( GetClientRect(client) );
+	int cw_old = m_clientWidth;
+	m_clientWidth = client.Width();
+	if (IsHeaderEnabled()) {
+		auto rc = client; 
+		rc.left -= GetViewOffset().x;
+		WINDOWPOS wPos = {};
+		HDLAYOUT layout = {&rc, &wPos};
+		if (m_header.Layout(&layout)) {
+			m_header.SetWindowPos(wPos.hwndInsertAfter,wPos.x,wPos.y,wPos.cx,wPos.cy,wPos.flags | SWP_SHOWWINDOW);
+			if (m_headerLine != NULL) m_headerLine.SetWindowPos(m_header, wPos.x, wPos.y + wPos.cy, wPos.cx, lineBelowHeaderCY, wPos.flags | SWP_SHOWWINDOW);
+		} else {
+			m_header.ShowWindow(SW_HIDE);
+			if (m_headerLine != NULL) m_headerLine.ShowWindow(SW_HIDE);
+		}
+	} else {
+		if ( cw_old != m_clientWidth) Invalidate();
+	}
+}
+
+int CListControlHeaderImpl::GetItemWidth() const {
+	if (IsHeaderEnabled()) return m_itemWidth;
+	else return m_clientWidth;
+}
+
+LRESULT CListControlHeaderImpl::OnSizePassThru(UINT,WPARAM,LPARAM) {
+	UpdateHeaderLayout();
+
+	ProcessAutoWidth();
+	
+	SetMsgHandled(FALSE);
+	return 0;
+}
+
+void CListControlHeaderImpl::OnViewOriginChange(CPoint p_delta) {
+	TParent::OnViewOriginChange(p_delta);
+	if (p_delta.x != 0) UpdateHeaderLayout();
+}
+
+void CListControlHeaderImpl::SetHeaderFont(HFONT font) {
+	if (IsHeaderEnabled()) {
+		m_header.SetFont(font); UpdateHeaderLayout();
+	}
+}
+
+LRESULT CListControlHeaderImpl::OnHeaderCustomDraw(LPNMHDR hdr) {
+	LPNMCUSTOMDRAW nmcd = reinterpret_cast<LPNMCUSTOMDRAW>(hdr);
+	if ( m_header != NULL && this->GetDarkMode() && nmcd->hdr.hwndFrom == m_header) {
+		switch (nmcd->dwDrawStage)
+		{
+		case CDDS_PREPAINT:
+			return CDRF_NOTIFYITEMDRAW;
+		case CDDS_ITEMPREPAINT:
+			{
+				CDCHandle dc(nmcd->hdc);
+				dc.SetTextColor(0xdedede); 
+				dc.SetBkColor(0x191919); // disregarded anyway
+			}
+			return CDRF_DODEFAULT;
+		}
+	}
+	SetMsgHandled(FALSE); return 0;
+}
+
+LRESULT CListControlHeaderImpl::OnDividerDoubleClick(int,LPNMHDR hdr,BOOL&) {
+	const NMHEADER * info = (const NMHEADER *) hdr;
+	if (info->iButton == 0) {
+		AutoColumnWidth((t_size)info->iItem);
+	}
+	return 0;
+}
+
+LRESULT CListControlHeaderImpl::OnHeaderItemClick(int,LPNMHDR p_hdr,BOOL&) {
+	const NMHEADER * info = (const NMHEADER *) p_hdr;
+	if (info->iButton == 0) {
+		OnColumnHeaderClick((t_uint32)info->iItem);
+	}
+	return 0;
+}
+
+LRESULT CListControlHeaderImpl::OnHeaderItemChanged(int,LPNMHDR p_hdr,BOOL&) {
+	const NMHEADER * info = (const NMHEADER*) p_hdr;
+	if (info->pitem->mask & (HDI_WIDTH | HDI_ORDER)) {
+		if(!m_ownColumnsChange) ProcessColumnsChange();
+	}
+	return 0;
+}
+
+LRESULT CListControlHeaderImpl::OnHeaderEndDrag(int,LPNMHDR hdr,BOOL&) {
+	NMHEADER * info = (NMHEADER*) hdr;
+	return OnColumnHeaderDrag(info->iItem,info->pitem->iOrder) ? TRUE : FALSE;
+}
+
+bool CListControlHeaderImpl::OnColumnHeaderDrag(t_size index, t_size newPos) {
+	index = GetSubItemOrder(index);
+	const t_size count = this->GetColumnCount();
+	if ( count == 0 ) return false;
+	std::vector<size_t> perm; perm.resize(count); pfc::create_move_items_permutation(&perm[0],count, pfc::bit_array_one(index), (int) newPos - (int) index );
+	std::vector<int> order, newOrder; order.resize(count); newOrder.resize(count);
+	WIN32_OP_D(m_header.GetOrderArray((int)count, &order[0]));
+	for(t_size walk = 0; walk < count; ++walk) newOrder[walk] = order[perm[walk]];
+	WIN32_OP_D(m_header.SetOrderArray((int)count, &newOrder[0]));
+	OnColumnsChanged();
+	return true;
+}
+t_size CListControlHeaderImpl::SubItemFromPointAbs(CPoint pt) const {
+	
+	auto order = GetColumnOrderArray();
+	const t_size colCount = order.size();
+
+	size_t item;
+	if (! ItemFromPointAbs(pt, item ) ) item = SIZE_MAX;
+
+	long xWalk = 0;
+	for(t_size _walk = 0; _walk < colCount; ) {
+		const t_size walk = (t_size) order[_walk];
+
+		size_t span = 1;
+		if (item != SIZE_MAX) span = this->GetSubItemSpan(item, walk);
+
+		PFC_ASSERT( span == 1 || _walk == walk );
+
+		if ( walk + span > colCount ) span = colCount - walk;
+
+		long width = 0;
+		for( size_t sub = 0; sub < span; ++ sub ) {
+			width += (long)this->GetSubItemWidth(walk + sub);
+		}
+
+		if (xWalk + width > pt.x) return walk;
+		xWalk += width;
+		_walk += span;
+	}
+	return SIZE_MAX;
+}
+
+bool CListControlHeaderImpl::OnClickedSpecial(DWORD status, CPoint pt) {
+	
+	const DWORD maskButtons = MK_LBUTTON | MK_RBUTTON | MK_MBUTTON | MK_XBUTTON1 | MK_XBUTTON2;
+
+	if ( (status & maskButtons) != MK_LBUTTON ) return false;
+
+	if (!GetCellTypeSupported()) return false;
+
+	size_t item; size_t subItem;
+	if (! this->GetItemAtPointAbsEx( this->PointClientToAbs(pt), item, subItem ) ) {
+		return false;
+	}
+
+	auto cellType = GetCellType(item, subItem);
+	if ( !CellTypeReactsToMouseOver( cellType ) ) return false;
+	auto rcHot = CellHotRect( item, subItem, cellType );
+	if (!rcHot.PtInRect( pt )) return false;
+
+	SetPressedItem(item, subItem);
+	mySetCapture([=](UINT msg, DWORD newStatus, CPoint pt) {
+
+		if (msg == WM_MOUSELEAVE) {
+			ClearPressedItem(); return false;
+		}
+
+		{
+			CRect rc = this->GetClientRectHook();
+			if (!rc.PtInRect(pt)) {
+				ClearPressedItem(); return false;
+			}
+		}
+
+		size_t newItem, newSubItem;
+		if (!this->GetItemAtPointAbsEx(this->PointClientToAbs(pt), newItem, newSubItem) || newItem != item || newSubItem != subItem) {
+			ClearPressedItem(); return false;
+		}
+
+		DWORD buttons = newStatus & maskButtons;
+		if (buttons == 0) {
+			// button released?
+			this->defer( [=] {
+				OnSubItemClicked(item, subItem, pt);
+			} );
+			ClearPressedItem(); return false;
+		}
+		if (buttons != MK_LBUTTON) {
+			// another button pressed?
+			ClearPressedItem(); return false;
+		}
+
+		return true;
+	});
+	return true;
+}
+
+bool CListControlHeaderImpl::CellTypeUsesSpecialHitTests( cellType_t ct ) {
+	if ( ct == nullptr ) return false;
+	return ct->SuppressRowSelect();
+}
+
+bool CListControlHeaderImpl::OnClickedSpecialHitTest(CPoint pt) {
+	if ( ! GetCellTypeSupported() ) return false;
+	auto ct = GetCellTypeAtPointAbs( PointClientToAbs( pt ) );
+	return CellTypeUsesSpecialHitTests(ct);
+}
+
+void CListControlHeaderImpl::OnItemClicked(t_size item, CPoint pt) {
+	t_size subItem = SubItemFromPointAbs( PointClientToAbs( pt ) );
+	if (subItem != SIZE_MAX) {
+		if ( this->GetCellTypeSupported() ) {
+			auto ct = this->GetCellType(item, subItem );
+			// we don't handle hyperlink & button clicks thru here
+			if (CellTypeUsesSpecialHitTests(ct)) return;
+		}
+		OnSubItemClicked(item, subItem, pt);
+	}
+}
+
+std::vector<int> CListControlHeaderImpl::GetColumnOrderArray() const {
+	const size_t cCount = this->GetColumnCount();
+	std::vector<int> order; 
+	if ( cCount > 0 ) {
+		order.resize(cCount);
+		if (IsHeaderEnabled()) {
+			PFC_ASSERT(m_header.IsWindow());
+			PFC_ASSERT(m_header.GetItemCount() == (int)cCount);
+			WIN32_OP_D(m_header.GetOrderArray((int)cCount, &order[0]));
+		} else {
+			for (size_t c = 0; c < cCount; ++c) order[c] = (int)c;
+		}
+	}
+	return order;
+}
+
+void CListControlHeaderImpl::RenderItemText(t_size item,const CRect & itemRect,const CRect & updateRect,CDCHandle dc, bool allowColors) {
+	
+	int xWalk = itemRect.left;
+	CRect subItemRect(itemRect);
+	auto order = GetColumnOrderArray();
+	const size_t cCount = order.size();
+	SelectObjectScope fontScope(dc,GetFont());
+	for(t_size _walk = 0; _walk < cCount; ) {
+		const t_size walk = order[_walk];
+		
+		size_t span = GetSubItemSpan(item, walk);
+
+		PFC_ASSERT( walk == _walk || span == 1 );
+
+		int width = GetSubItemWidth(walk);
+
+		if ( span > 1 ) {
+			if ( walk + span > cCount ) span = cCount - walk;
+			for( size_t extraWalk = 1; extraWalk < span; ++ extraWalk ) {
+				width += GetSubItemWidth(walk + extraWalk);
+			}
+		}
+
+		subItemRect.left = xWalk; subItemRect.right = xWalk + width;
+		CRect test;
+		if (test.IntersectRect(itemRect, subItemRect)) {
+			CRect subUpdate;
+			if (subUpdate.IntersectRect(subItemRect, updateRect)) {
+				DCStateScope scope(dc);
+				if (dc.IntersectClipRect(subItemRect) != NULLREGION) {
+					RenderSubItemText(item, walk, subItemRect, subUpdate, dc, allowColors);
+				}
+			}
+		}
+		xWalk += width;
+
+		_walk += span;
+	}
+}
+
+t_size CListControlHeaderImpl::GetSubItemOrder(t_size subItem) const {
+	if ( ! IsHeaderEnabled( ) ) return subItem;
+	HDITEM hditem = {};
+	hditem.mask = HDI_ORDER;
+	WIN32_OP_D( m_header.GetItem( (int) subItem, &hditem ) );
+	return (t_size) hditem.iOrder;
+}
+
+size_t CListControlHeaderImpl::GetSubItemSpan(size_t row, size_t column) const {
+	(void)row; (void)column;
+	return 1;
+}
+
+uint32_t CListControlHeaderImpl::GetSubItemWidth(t_size subItem) const {
+	if ( ! IsHeaderEnabled( ) ) {
+		// Should be overridden for custom columns layout
+		PFC_ASSERT( GetColumnCount() == 1 );
+		PFC_ASSERT( subItem == 0 );
+		return GetItemWidth();
+	}
+
+	if ( subItem < m_colRuntime.size() ) return m_colRuntime[subItem].m_widthPixels;
+	PFC_ASSERT( !"bad column idx");
+	return 0;
+}
+
+int CListControlHeaderImpl::GetHeaderItemWidth( int which ) {
+	HDITEM hditem = {};
+	hditem.mask = HDI_WIDTH;
+	WIN32_OP_D( m_header.GetItem( which, &hditem) );
+	return hditem.cxy;
+}
+
+void CListControlHeaderImpl::OnColumnsChanged() {
+	if ( IsHeaderEnabled() ) {
+		for( size_t walk = 0; walk < m_colRuntime.size(); ++ walk ) {
+			m_colRuntime[walk].m_widthPixels = GetHeaderItemWidth( (int) walk );
+		}
+		RecalcItemWidth();
+	}
+	this->OnViewAreaChanged();
+}
+
+void CListControlHeaderImpl::ResetColumns(bool update) {
+	m_colRuntime.clear();
+	m_itemWidth = 0;
+	PFC_ASSERT(IsHeaderEnabled());
+	for(;;) {
+		int count = m_header.GetItemCount();
+		if (count <= 0) break;
+		m_header.DeleteItem(count - 1);
+	}
+	if (update) OnColumnsChanged();
+}
+
+void CListControlHeaderImpl::SetColumn( size_t which, const char * label, DWORD fmtFlags, bool updateView) {
+	PFC_ASSERT( IsHeaderEnabled() );
+	pfc::stringcvt::string_os_from_utf8 labelOS;
+
+	HDITEM item = {};
+
+	if (label != nullptr) {
+		if (which < m_colRuntime.size()) m_colRuntime[which].m_text = label;
+
+		labelOS.convert(label);
+		item.mask |= HDI_TEXT;
+		item.pszText = const_cast<TCHAR*>(labelOS.get_ptr());
+	}
+	if (fmtFlags != UINT32_MAX) {
+		item.mask |= HDI_FORMAT;
+		item.fmt = fmtFlags | HDF_STRING;
+	}
+	
+	m_header.SetItem( (int) which, &item );
+
+	if (updateView) OnColumnsChanged();
+}
+
+void CListControlHeaderImpl::GetColumnText(size_t which, pfc::string_base & out) const {
+	if (which < m_colRuntime.size()) {
+		out = m_colRuntime[which].m_text.c_str();
+	} else {
+		out = "";
+	}
+}
+
+void CListControlHeaderImpl::ResizeColumn(t_size index, t_uint32 userWidth, bool updateView) {
+	PFC_ASSERT( IsHeaderEnabled() );
+	PFC_ASSERT( index < m_colRuntime.size() );
+	auto& rt = m_colRuntime[index];
+	rt.m_userWidth = userWidth;
+	if (rt.autoWidth()) {
+		this->ProcessAutoWidth();
+	} else {
+		auto widthPixels = columWidthToPixels(userWidth);
+		rt.m_widthPixels = widthPixels;
+		HDITEM item = {};
+		item.mask = HDI_WIDTH;
+		item.cxy = widthPixels;
+		{ pfc::vartoggle_t<bool> scope(m_ownColumnsChange, true); m_header.SetItem((int)index, &item); }
+		RecalcItemWidth();
+		if (updateView) OnColumnsChanged();
+	}
+}
+
+void CListControlHeaderImpl::DeleteColumns( pfc::bit_array const & mask, bool updateView ) {
+	int nDeleted = 0;
+	const size_t oldCount = GetColumnCount();
+	mask.for_each(true, 0, oldCount, [&] (size_t idx) {
+		int iDelete = (int) idx - nDeleted;
+		bool bDeleted = m_header.DeleteItem( iDelete );
+		PFC_ASSERT( bDeleted );
+		if ( bDeleted ) ++ nDeleted;
+		} );
+
+	pfc::remove_mask_t( m_colRuntime, mask );
+
+	ColumnWidthFix();
+
+	if (updateView) {
+		OnColumnsChanged();
+	}
+
+}
+
+bool CListControlHeaderImpl::DeleteColumn(size_t index, bool update) {
+	PFC_ASSERT( IsHeaderEnabled() );
+
+	if (!m_header.DeleteItem( (int) index )) return false;
+
+	pfc::remove_mask_t( m_colRuntime, pfc::bit_array_one( index ) );
+
+	ColumnWidthFix();
+
+	if (update) {
+		OnColumnsChanged();
+	}
+
+	return true;
+}
+
+void CListControlHeaderImpl::SetSortIndicator( size_t whichColumn, bool isUp ) {
+	HeaderControl_SetSortIndicator( GetHeaderCtrl(), (int) whichColumn, isUp );
+}
+
+void CListControlHeaderImpl::ClearSortIndicator() {
+	HeaderControl_SetSortIndicator(GetHeaderCtrl(), -1, false);
+}
+
+bool CListControlHeaderImpl::HaveAutoWidthContentColumns() const {
+	for( auto i = m_colRuntime.begin(); i != m_colRuntime.end(); ++ i ) {
+		if ( i->autoWidthContent() ) return true;
+	}
+	return false;
+}
+bool CListControlHeaderImpl::HaveAutoWidthColumns() const {
+	for( auto i = m_colRuntime.begin(); i != m_colRuntime.end(); ++ i ) {
+		if ( i->autoWidth() ) return true;
+	}
+	return false;
+}
+void CListControlHeaderImpl::AddColumnEx( const char * label, uint32_t widthPixelsAt96DPI, DWORD fmtFlags, bool update ) {
+	uint32_t w = widthPixelsAt96DPI;
+	if ( w <= columnWidthMax ) {
+		w = MulDiv( w, m_dpi.cx, 96 );
+	}
+	AddColumn( label, w, fmtFlags, update );
+}
+
+void CListControlHeaderImpl::AddColumnDLU( const char * label, uint32_t widthDLU, DWORD fmtFlags, bool update ) {
+	uint32_t w = widthDLU;
+	if ( w <= columnWidthMax ) {
+		w = ::MapDialogWidth( GetParent(), w );
+	}
+	AddColumn( label, w, fmtFlags, update );
+}
+void CListControlHeaderImpl::AddColumnF( const char * label, float widthF, DWORD fmtFlags, bool update) {
+	uint32_t w = columnWidthMax;
+	if ( widthF >= 0 ) {
+		w = pfc::rint32( widthF * m_dpi.cx / 96.0f );
+	}
+	AddColumn( label, w, fmtFlags, update );
+}
+void CListControlHeaderImpl::AddColumn(const char * label, uint32_t userWidth, DWORD fmtFlags,bool update) {
+	// userWidth is either UINT32_MAX or desired width in pixels
+	PFC_ASSERT(IsWindow());
+	if (! IsHeaderEnabled( ) ) InitializeHeaderCtrl();
+
+	uint32_t widthPixels = 0;
+
+	pfc::stringcvt::string_os_from_utf8 labelOS(label);
+	HDITEM item = {};
+	item.mask = HDI_TEXT | HDI_FORMAT;
+	if ( userWidth <= columnWidthMax ) {
+		widthPixels = columWidthToPixels(userWidth);
+		item.cxy = widthPixels;
+		item.mask |= HDI_WIDTH;
+	}
+	
+	item.pszText = const_cast<TCHAR*>(labelOS.get_ptr());
+	item.fmt = HDF_STRING | fmtFlags;
+	int iColumn;
+	WIN32_OP_D( (iColumn = m_header.InsertItem(m_header.GetItemCount(),&item) ) >= 0 );
+	colRuntime_t rt;
+	rt.m_text = label;
+	rt.m_userWidth = userWidth;
+	m_itemWidth += widthPixels;
+	rt.m_widthPixels = widthPixels;
+	m_colRuntime.push_back( std::move(rt) );
+	
+	if (update) OnColumnsChanged();
+
+	ProcessAutoWidth();
+}
+
+float CListControlHeaderImpl::GetColumnWidthF(size_t subItem) const {
+	auto w = GetSubItemWidth(subItem);
+	return (float) w * 96.0f / (float)m_dpi.cx;
+}
+
+void CListControlHeaderImpl::RenderBackground(CDCHandle dc, CRect const& rc) {
+	__super::RenderBackground(dc,rc);
+#if 0
+	if ( m_drawLineBelowHeader && IsHeaderEnabled()) {
+		CRect rcHeader;
+		if (m_header.GetWindowRect(rcHeader)) {
+			// Draw a grid line below header
+			int y = rcHeader.Height();
+			if ( y >= rc.top && y < rc.bottom ) {
+				CDCPen pen(dc, GridColor());
+				SelectObjectScope scope(dc, pen);
+				dc.MoveTo(rc.left, y);
+				dc.LineTo(rc.right, y);
+			}
+		}
+	}
+#endif
+}
+
+CRect CListControlHeaderImpl::GetClientRectHook() const {
+	CRect rcClient = __super::GetClientRectHook();
+	if (m_header != NULL) {
+		PFC_ASSERT( m_header.IsWindow() );
+		CRect rcHeader;
+		if (m_header.GetWindowRect(rcHeader)) {
+			int h = rcHeader.Height();
+			if ( m_headerLine != NULL ) h += lineBelowHeaderCY;
+			rcClient.top = pfc::min_t(rcClient.bottom,rcClient.top + h);
+		}
+	}
+	return rcClient;
+}
+
+CRect CListControlHeaderImpl::GetItemTextRectHook(size_t, size_t, CRect const & rc) const {
+	return GetItemTextRect( rc );
+}
+
+CRect CListControlHeaderImpl::GetItemTextRect(const CRect & itemRect) const {
+	CRect rc ( itemRect );
+	rc.DeflateRect(GetColumnSpacing(),0);
+	return rc;
+}
+
+void CListControlHeaderImpl::SetColumnSort(t_size which, bool isUp) {
+	HeaderControl_SetSortIndicator(m_header,(int)which,isUp);
+}
+
+void CListControlHeaderImpl::SetColumnFormat(t_size which, DWORD format) {
+	HDITEM item = {};
+	item.mask = HDI_FORMAT;
+	item.fmt = HDF_STRING | format;
+	WIN32_OP_D( m_header.SetItem((int)which,&item) );
+}
+
+DWORD CListControlHeaderImpl::GetColumnFormat(t_size which) const {
+	if (!IsHeaderEnabled()) return HDF_LEFT;
+	HDITEM hditem = {};
+	hditem.mask = HDI_FORMAT;
+	WIN32_OP_D( m_header.GetItem( (int) which, &hditem) );
+	return hditem.fmt;
+}
+
+BOOL CListControlHeaderImpl::OnSetCursor(CWindow, UINT nHitTest, UINT message) {
+	(void)nHitTest;
+	if ( message != 0 && GetCellTypeSupported() ) {
+		CPoint pt( (LPARAM) GetMessagePos() );
+		WIN32_OP_D( ScreenToClient( &pt ) );
+		size_t item, subItem;
+		if (GetItemAtPointAbsEx(this->PointClientToAbs(pt), item, subItem)) {
+			auto ct = GetCellType(item, subItem);
+			if (CellTypeReactsToMouseOver(ct)) {
+				auto rc = CellHotRect(item, subItem, ct);
+				if (PtInRect(rc, pt)) {
+					auto cursor = ct->HotCursor();
+					if (cursor) {
+						SetCursor(cursor); return TRUE;
+					}
+				}
+			}
+		}
+	}
+	SetMsgHandled(FALSE); return FALSE;
+}
+
+bool CListControlHeaderImpl::GetItemAtPointAbsEx(CPoint pt, size_t & outItem, size_t & outSubItem) const {
+	size_t item, subItem;
+	if (ItemFromPointAbs(pt, item)) {
+		subItem = SubItemFromPointAbs(pt);
+		if (subItem != SIZE_MAX) {
+			outItem = item; outSubItem = subItem; return true;
+		}
+	}
+	return false;
+}
+
+CListControlHeaderImpl::cellType_t CListControlHeaderImpl::GetCellTypeAtPointAbs(CPoint pt) const {
+	size_t item, subItem;
+	if ( GetItemAtPointAbsEx( pt, item, subItem) ) {
+		return GetCellType( item, subItem );
+	}
+	return nullptr;
+}
+
+CListCell * CListControlHeaderImpl::GetCellType( size_t item, size_t subItem ) const {
+	(void)item; (void)subItem;
+	return &PFC_SINGLETON(CListCell_Text);
+}
+
+void CListControlHeaderImpl::RenderSubItemText(t_size item, t_size subItem,const CRect & subItemRect,const CRect & updateRect,CDCHandle dc, bool allowColors) {
+	(void)updateRect;
+	const auto cellType = GetCellType( item, subItem );
+	if ( cellType == nullptr ) {
+		return;
+	}
+	pfc::string_formatter label;
+	const bool bHaveText = GetSubItemText(item,subItem,label);
+	if (! bHaveText ) {
+		label = ""; //sanity
+	}
+
+	pfc::stringcvt::string_os_from_utf8_fast labelOS ( label );
+	CListCell::DrawContentArg_t arg;
+	arg.darkMode = this->GetDarkMode();
+	arg.hdrFormat = GetColumnFormat( subItem );
+	arg.subItemRect = subItemRect;
+	arg.dc = dc;
+	arg.text = labelOS.get_ptr();
+	arg.allowColors = allowColors;
+	bool bPressed;
+	if ( cellType->IsToggle() ) bPressed = this->GetCellCheckState(item, subItem);
+	else bPressed = (item == m_pressedItem) && (subItem == m_pressedSubItem);
+	bool bHot = (item == m_hotItem) && ( subItem == m_hotSubItem );
+	if ( bPressed ) arg.cellState |= CListCell::cellState_pressed;
+	if ( bHot ) arg.cellState|= CListCell::cellState_hot;
+	arg.rcText = GetItemTextRectHook(item, subItem, subItemRect);
+	arg.rcHot = CellHotRect(item, subItem, cellType, subItemRect);
+
+	auto strTheme = cellType->Theme();
+	if ( strTheme != nullptr ) {
+		arg.theme = themeFor( strTheme ).m_theme;
+	}
+	arg.colorHighlight = GetSysColorHook(colorHighlight);
+
+	arg.thisWnd = m_hWnd;
+
+	if (this->IsSubItemGrayed( item, subItem ) ) {
+		arg.cellState |= CListCell::cellState_disabled;
+	}
+
+	if (this->RenderCellImageTest(item, subItem)) {
+		arg.imageRenderer = [this, item, subItem](CDCHandle dc, const CRect& rc) { this->RenderCellImage(item, subItem, dc, rc); };
+	}
+
+	CFontHandle fontRestore;
+	CFont fontOverride;
+
+	LOGFONT data;
+	if (dc.GetCurrentFont().GetLogFont(&data)) {
+		if ( cellType->ApplyTextStyle( data, CellTextScale(item, subItem ), arg.cellState ) ) {
+			if (fontOverride.CreateFontIndirect( & data )) {
+				fontRestore = dc.SelectFont( fontOverride );
+			}
+		}
+	}
+	cellType->DrawContent( arg );
+
+	if ( fontRestore != NULL ) dc.SelectFont( fontRestore );
+}
+
+void CListControlHeaderImpl::RenderGroupHeaderText2(size_t baseItem,const CRect & headerRect,const CRect & updateRect,CDCHandle dc) {
+	(void)updateRect;
+	pfc::string_formatter label;
+	if (GetGroupHeaderText2(baseItem,label)) {
+		SelectObjectScope fontScope(dc,GetGroupHeaderFont());
+		pfc::stringcvt::string_os_from_utf8 cvt(label);
+		CRect contentRect(GetItemTextRect(headerRect));
+		dc.DrawText(cvt,(int)cvt.length(),contentRect,DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER | DT_LEFT );
+		SIZE txSize;
+		const int lineSpacing = contentRect.Height() / 2;
+		if (dc.GetTextExtent(cvt,(int)cvt.length(),&txSize)) {
+			if (txSize.cx + lineSpacing < contentRect.Width()) {
+				const CPoint center = contentRect.CenterPoint();
+				const CPoint pt1(contentRect.left + txSize.cx + lineSpacing, center.y), pt2(contentRect.right, center.y);
+				const COLORREF lineColor = PaintUtils::BlendColor(dc.GetTextColor(),dc.GetBkColor(),25);
+
+#ifndef CListControl_ScrollWindowFix
+#error FIXME CMemoryDC needed
+#endif
+				PaintUtils::DrawSmoothedLine(dc, pt1, pt2, lineColor, 1.0 * (double)m_dpi.cy / 96.0);
+			}
+		}
+	}
+}
+
+uint32_t CListControlHeaderImpl::GetOptimalColumnWidthFixed(const char * fixedText, bool pad) const {
+	CWindowDC dc(*this);
+	SelectObjectScope fontScope(dc, GetFont());
+	GetOptimalWidth_Cache cache;
+	cache.m_dc = dc;
+	cache.m_stringTemp = fixedText;
+	uint32_t ret = cache.GetStringTempWidth();
+	if ( pad ) ret += this->GetColumnSpacing() * 2;
+	return ret;
+}
+
+t_uint32 CListControlHeaderImpl::GetOptimalSubItemWidth(t_size item, t_size subItem, GetOptimalWidth_Cache & cache) const {
+	const t_uint32 base = this->GetColumnSpacing() * 2;
+	if (GetSubItemText(item,subItem,cache.m_stringTemp)) {
+		return base + cache.GetStringTempWidth();
+	} else {
+		return base;
+	}
+}
+
+t_uint32 CListControlHeaderImpl::GetOptimalWidth_Cache::GetStringTempWidth() {
+	if (m_stringTemp.replace_string_ex(m_stringTempFixAmpersands, "&", "&&") > 0) {
+		m_convertTemp.convert(m_stringTempFixAmpersands);
+	} else {
+		m_convertTemp.convert(m_stringTemp);
+	}
+	return PaintUtils::TextOutColors_CalcWidth(m_dc, m_convertTemp);
+}
+
+t_uint32 CListControlHeaderImpl::GetOptimalColumnWidth(t_size which, GetOptimalWidth_Cache & cache) const {
+	const t_size totalItems = GetItemCount();
+	t_uint32 val = 0;
+	for(t_size item = 0; item < totalItems; ++item) {
+		pfc::max_acc( val, GetOptimalSubItemWidth( item, which, cache ) );
+	}
+	return val;
+}
+
+t_uint32 CListControlHeaderImpl::GetOptimalSubItemWidthSimple(t_size item, t_size subItem) const {
+	CWindowDC dc(*this);
+	SelectObjectScope fontScope(dc, GetFont() );
+	GetOptimalWidth_Cache cache;
+	cache.m_dc = dc;
+	return GetOptimalSubItemWidth(item, subItem, cache);
+}
+
+LRESULT CListControlHeaderImpl::OnKeyDown(UINT,WPARAM wp,LPARAM,BOOL& bHandled) {
+	switch(wp) {
+	case VK_ADD:
+		if (IsKeyPressed(VK_CONTROL)) {
+			AutoColumnWidths();
+			return 0;
+		}
+		break;
+	}
+	bHandled = FALSE;
+	return 0;
+}
+
+uint32_t CListControlHeaderImpl::GetOptimalColumnWidth(size_t colIndex) const {
+	CWindowDC dc(*this);
+	SelectObjectScope fontScope(dc, GetFont());
+	GetOptimalWidth_Cache cache;
+	cache.m_dc = dc;
+	uint32_t ret = 0;
+	const auto itemCount = GetItemCount();
+	for (t_size walk = 0; walk < itemCount; ++walk) {
+		pfc::max_acc(ret, GetOptimalSubItemWidth(walk, colIndex, cache));
+	}
+	return ret;
+}
+
+void CListControlHeaderImpl::AutoColumnWidths(const pfc::bit_array & mask, bool expandLast) {
+	PFC_ASSERT( IsHeaderEnabled() );
+	if (!IsHeaderEnabled()) return;
+	const t_size itemCount = GetItemCount();
+	if (itemCount == 0) return;
+	const t_size columnCount = (t_size) m_header.GetItemCount();
+	if (columnCount == 0) return;
+	pfc::array_t<t_uint32> widths; widths.set_size(columnCount); widths.fill_null();
+	{
+		CWindowDC dc(*this);
+		SelectObjectScope fontScope(dc,GetFont());
+		GetOptimalWidth_Cache cache;
+		cache.m_dc = dc;
+		for(t_size walk = 0; walk < itemCount; ++walk) {
+			for(t_size colWalk = mask.find_first(true,0,columnCount); colWalk < columnCount; colWalk = mask.find_next(true,colWalk,columnCount)) {
+				pfc::max_acc(widths[colWalk], GetOptimalSubItemWidth(walk,colWalk,cache));
+			}
+		}
+	}
+
+	// Enforce limit
+	for (auto& walk : widths) walk = columWidthToPixels(walk);
+
+	if (expandLast) {
+		uint32_t usedWidth = 0; size_t lastCol = SIZE_MAX;
+		pfc::array_t<int> order; order.set_size(columnCount);
+		WIN32_OP_D( m_header.GetOrderArray((int)columnCount,order.get_ptr()) );
+		for(size_t _walk = 0; _walk < columnCount; ++_walk) {
+			const size_t colWalk = (size_t) order[_walk];
+			PFC_ASSERT( colWalk < columnCount );
+			if (mask[colWalk]) {
+				lastCol = colWalk;
+				usedWidth += widths[colWalk];
+			} else {
+				usedWidth += GetSubItemWidth(colWalk);
+			}
+		}
+		if (lastCol != SIZE_MAX) {
+			t_uint32 clientWidth = this->GetClientRectHook().Width(); 
+			if (clientWidth > 0) --clientWidth; // $!@# scrollbar hack
+			if (usedWidth < clientWidth) {
+				widths[lastCol] += clientWidth - usedWidth;
+			}
+		}
+	}
+	for(t_size colWalk = mask.find_first(true,0,columnCount); colWalk < columnCount; colWalk = mask.find_next(true,colWalk,columnCount)) {
+		ResizeColumn(colWalk,widths[colWalk],false);
+	}
+	ProcessColumnsChange();
+}
+
+t_uint32 CListControlHeaderImpl::GetOptimalGroupHeaderWidth2(size_t baseItem) const {
+	CWindowDC dc(*this);
+	SelectObjectScope fontScope(dc,GetGroupHeaderFont());
+	GetOptimalWidth_Cache cache; cache.m_dc = dc;
+	const t_uint32 base = this->GetColumnSpacing() * 2;
+	if (GetGroupHeaderText2(baseItem,cache.m_stringTemp)) {
+		return base + cache.GetStringTempWidth();
+	} else {
+		return base;
+	}
+}
+
+size_t CListControlHeaderImpl::GetColumnCount() const {
+	if ( ! IsHeaderEnabled() ) return 1;
+#if PFC_DEBUG
+	int iHeaderCount = m_header.GetItemCount();
+	PFC_ASSERT( m_colRuntime.size() == (size_t) iHeaderCount );
+#endif
+	return m_colRuntime.size();
+}
+
+void CListControlHeaderImpl::ColumnWidthFix() {
+	if ( this->HaveAutoWidthColumns() ) {
+		ProcessAutoWidth();
+	} else {
+		RecalcItemWidth();
+	}
+}
+
+void CListControlHeaderImpl::ProcessAutoWidth() {
+	if ( HaveAutoWidthColumns() ) {
+		const int clientWidth = this->GetClientRectHook().Width();
+
+		if ( ! this->HaveAutoWidthContentColumns( ) && clientWidth == m_itemWidth) return;
+
+		const size_t count = GetColumnCount();
+		uint32_t totalNonAuto = 0;
+		size_t numAutoWidth = 0;
+		for(size_t walk = 0; walk < count; ++ walk ) {
+			if ( m_colRuntime[walk].autoWidth() ) {
+				++ numAutoWidth;
+			} else {
+				totalNonAuto += GetSubItemWidth(walk);
+			}
+		}
+		int toDivide = clientWidth - totalNonAuto;
+		if ( toDivide < 0 ) toDivide = 0;
+
+		size_t numLeft = numAutoWidth;
+		auto worker = [&] ( size_t iCol ) {
+			auto & rt = m_colRuntime[iCol];
+			int lo = this->GetOptimalColumnWidthFixed( rt.m_text.c_str() );
+			if ( rt.autoWidthContent() ) {
+				int lo2 = this->GetOptimalColumnWidth( iCol );
+				if ( lo < lo2 ) lo = lo2;
+			}
+			int width = (int)(toDivide / numLeft);
+			if ( width < lo ) width = lo;
+			
+			HDITEM item = {};
+			item.mask = HDI_WIDTH;
+			item.cxy = width;
+			WIN32_OP_D( m_header.SetItem( (int) iCol, &item ) );
+			rt.m_widthPixels = width;
+
+			if ( toDivide > width ) {
+				toDivide -= width;
+			} else {
+				toDivide = 0;
+			}
+			-- numLeft;
+
+		};
+		for( size_t iCol = 0; iCol < count; ++ iCol ) {
+			if (m_colRuntime[iCol].autoWidthContent() ) worker(iCol);
+		}
+		for( size_t iCol = 0; iCol < count; ++ iCol ) {
+			if ( m_colRuntime[iCol].autoWidthPlain() ) worker(iCol);
+		}
+
+		RecalcItemWidth();
+		OnColumnsChanged();
+		m_header.Invalidate();
+	}
+}
+
+void CListControlHeaderImpl::RecalcItemWidth() {
+	int total = 0;
+	const t_size count = GetColumnCount();
+	for(t_size walk = 0; walk < count; ++walk) total += GetSubItemWidth(walk);
+	m_itemWidth = total;
+}
+
+CRect CListControlHeaderImpl::GetSubItemRectAbs(t_size item,t_size subItem) const {
+	CRect rc = GetItemRectAbs(item);
+	auto order = GetColumnOrderArray();
+	const t_size colCount = order.size();
+	for(t_size _walk = 0; _walk < colCount; ++_walk) {
+		const t_size walk = (t_size) order[_walk];
+
+		t_size width = this->GetSubItemWidth(walk);
+		if (subItem == walk) {
+			
+			size_t span = GetSubItemSpan(item, walk);
+			if ( walk + span > colCount ) span = colCount - walk;
+			for( size_t extra = 1; extra < span; ++ extra ) {
+				width += GetSubItemWidth( walk + extra);
+			}
+			
+			rc.right = rc.left + (long)width;
+
+			return rc;
+		} else {
+			rc.left += (long)width;
+		}
+	}
+	throw pfc::exception_invalid_params();
+}
+
+CRect CListControlHeaderImpl::GetSubItemRect(t_size item,t_size subItem) const {
+	return RectAbsToClient(GetSubItemRectAbs(item, subItem));
+}
+
+void CListControlHeaderImpl::SetHotItem(size_t row, size_t column) {
+	if ( m_hotItem != row  || m_hotSubItem != column ) {
+		const size_t total = GetItemCount();
+		if (m_hotItem < total) InvalidateRect(GetSubItemRect(m_hotItem, m_hotSubItem));
+		m_hotItem = row; m_hotSubItem = column;
+		if (m_hotItem < total) InvalidateRect(GetSubItemRect(m_hotItem, m_hotSubItem));
+		HotItemChanged(row, column);
+	}
+}
+
+void CListControlHeaderImpl::SetPressedItem(size_t row, size_t column) {
+	if (m_pressedItem != row || m_pressedSubItem != column) {
+		if (m_pressedItem != SIZE_MAX) InvalidateRect(GetSubItemRect(m_pressedItem, m_pressedSubItem));
+		m_pressedItem = row; m_pressedSubItem = column;
+		if (m_pressedItem != SIZE_MAX) InvalidateRect(GetSubItemRect(m_pressedItem, m_pressedSubItem));
+		PressedItemChanged(row, column);
+	}
+}
+
+void CListControlHeaderImpl::SetCellCheckState(size_t item, size_t subItem, bool value) {
+	ReloadItem(item); (void)subItem; (void)value;
+	// Subclass deals with keeping track of state
+}
+
+bool CListControlHeaderImpl::ToggleSelectedItemsHook(const pfc::bit_array & mask) {
+	if (this->GetCellTypeSupported() ) {
+		bool handled = false;
+		bool setTo = true;
+
+		mask.walk(GetItemCount(), [&](size_t idx) {
+			auto ct = this->GetCellType(idx, 0);
+			if ( ct != nullptr && ct->IsToggle() ) {
+				if ( ct->IsRadioToggle() ) {
+					if (!handled) {
+						handled = true;
+						setTo = !this->GetCellCheckState(idx, 0);
+						this->SetCellCheckState(idx, 0, setTo);
+					}
+				} else {
+					if (!handled) {
+						handled = true;
+						setTo = ! this->GetCellCheckState(idx,0);
+					}
+					this->SetCellCheckState(idx,0,setTo);
+				}
+			}
+		});
+
+		if (handled) return true;
+	}
+	return __super::ToggleSelectedItemsHook(mask);
+}
+
+void CListControlHeaderImpl::OnSubItemClicked(t_size item, t_size subItem, CPoint pt) {
+	auto ct = GetCellType(item, subItem);
+	if (ct != nullptr) {
+		if (ct->IsToggle()) {
+			if (ct->HotRect(GetSubItemRect(item, subItem)).PtInRect(pt)) {
+				this->SetCellCheckState(item, subItem, !GetCellCheckState(item, subItem));
+			}
+		} else if (ct->ClickToEdit()) {
+			this->RequestEditItem(item, subItem);
+		}
+	}
+}
+
+
+bool CListControlHeaderImpl::AllowTypeFindInCell(size_t item, size_t subItem) const {
+	auto cell = GetCellType( item, subItem );
+	if ( cell == nullptr ) return false;
+	return cell->AllowTypeFind();
+}
+
+bool CListControlHeaderImpl::CellTypeReactsToMouseOver(cellType_t ct) {
+	return ct != nullptr && ct->IsInteractive();
+}
+
+CRect CListControlHeaderImpl::CellHotRect( size_t, size_t, cellType_t ct, CRect rcCell) {
+	if ( ct != nullptr ) {
+		return ct->HotRect(rcCell);
+	}
+	return rcCell;
+}
+CRect CListControlHeaderImpl::CellHotRect(size_t item, size_t subItem, cellType_t ct) {
+	return CellHotRect( item, subItem, ct, GetSubItemRect( item, subItem ) );
+}
+void CListControlHeaderImpl::OnMouseMove(UINT nFlags, CPoint pt) {
+	const DWORD maskButtons = MK_LBUTTON | MK_RBUTTON | MK_MBUTTON | MK_XBUTTON1 | MK_XBUTTON2;
+	if (GetCellTypeSupported() && (nFlags & maskButtons) == 0 ) {
+		size_t item; size_t subItem;
+		if (this->GetItemAtPointAbsEx( PointClientToAbs(pt), item, subItem)) {
+			auto ct = this->GetCellType( item, subItem );
+			if (CellTypeReactsToMouseOver(ct) ) {
+				auto rc = CellHotRect( item, subItem, ct );
+				if ( PtInRect( rc, pt ) ) {
+					const bool bTrack = ct != nullptr && ct->TracksMouseMove();
+					SetHotItem(item, subItem);
+
+					if (bTrack) this->CellTrackMouseMove(item, subItem, WM_MOUSEMOVE, nFlags, pt);
+
+					mySetCapture([=](UINT msg, DWORD newStatus, CPoint pt) {
+						if (msg == WM_MOUSELEAVE) {
+							this->ClearHotItem();
+							return false;
+						}
+
+						if ((newStatus & maskButtons) != 0 || msg == WM_MOUSEWHEEL || msg == WM_MOUSEHWHEEL ) {
+							// A button has been pressed or wheel has been moved
+							this->ClearHotItem();
+							mySetCaptureMsgHandled(FALSE);
+							return false;
+						}
+
+						if ( ! PtInRect( rc, pt ) || item >= GetItemCount() ) {
+							// Left the rect
+							this->ClearHotItem();
+							this->mySetCaptureMsgHandled(FALSE);
+							return false;
+						}
+						if (bTrack) {
+							this->CellTrackMouseMove(item, subItem, msg, newStatus, pt);
+						}
+
+						return true;
+					});
+				}
+			}
+		}
+	}
+	SetMsgHandled(FALSE);
+}
+
+bool CListControlHeaderImpl::AllowScrollbar(bool vertical) const {
+	if ( vertical ) {
+		// vertical
+		return true;
+	} else {
+		// horizontal
+		if (! IsHeaderEnabled( ) ) return false; // no header?
+		return true;
+	}
+}
+
+void CListControlHeaderImpl::OnDestroy() {
+	m_colRuntime.clear();
+	m_header = NULL;
+	m_headerLine = NULL;
+	m_headerDark = false;
+	SetMsgHandled(FALSE);
+}
+
+
+uint32_t CListControlHeaderImpl::GetColumnsBlankWidth( size_t colExclude ) const {
+	auto client = this->GetClientRectHook().Width();
+	int item = GetItemWidth();
+	if (colExclude) item -= GetSubItemWidth(colExclude);
+	if ( item  < 0 ) item = 0;
+	if ( item < client ) return (uint32_t)( client - item );
+	else return 0;
+}
+
+void CListControlHeaderImpl::SizeColumnToContent( size_t which, uint32_t minWidth ) {
+	auto width = this->GetOptimalColumnWidth( which );
+	if ( width < minWidth ) width = minWidth;
+	this->ResizeColumn( which, width );
+}
+
+void CListControlHeaderImpl::SizeColumnToContentFillBlank( size_t which ) {
+	this->SizeColumnToContent( which, this->GetColumnsBlankWidth(which) );
+}
+
+HBRUSH CListControlHeaderImpl::OnCtlColorStatic(CDCHandle dc, CStatic wndStatic) {
+	if ( wndStatic == m_headerLine ) {
+		COLORREF col = GridColor();
+		dc.SetDCBrushColor( col );
+		return (HBRUSH) GetStockObject(DC_BRUSH);
+	}
+	SetMsgHandled(FALSE);
+	return NULL;
+}
+
+void CListControlHeaderImpl::ReloadData() {
+	__super::ReloadData();
+	if ( this->HaveAutoWidthContentColumns( ) ) {
+		this->ColumnWidthFix();
+	}
+}
+
+void CListControlHeaderImpl::RenderGroupOverlay(size_t baseItem, const CRect& p_groupWhole, const CRect& p_updateRect, CDCHandle dc) {
+	CRect subItemRect = p_groupWhole;
+	t_uint32 xWalk = p_groupWhole.left;
+	auto order = GetColumnOrderArray();
+	const size_t cCount = order.size();
+	for (t_size _walk = 0; _walk < cCount; ) {
+		const t_size walk = order[_walk];
+
+		t_uint32 width = GetSubItemWidth(walk);
+
+		subItemRect.left = xWalk; subItemRect.right = xWalk + width;
+		CRect subUpdate;
+		if (subUpdate.IntersectRect(subItemRect, p_updateRect)) {
+			DCStateScope scope(dc);
+			if (dc.IntersectClipRect(subItemRect) != NULLREGION) {
+				this->RenderGroupOverlayColumn(baseItem, walk, subItemRect, subUpdate, dc);
+			}
+		}
+		xWalk += width;
+
+		_walk += 1;
+	}
+}
+
+void CListControlHeaderImpl::UpdateGroupOverlayColumnByID(groupID_t groupID, size_t subItem) {
+	if (this->GetItemCount() == 0) return;
+	CRect rc = this->GetSubItemRectAbs(0, subItem);
+	this->UpdateGroupOverlayByID(groupID, rc.left, rc.right);
+}
+
+void CListControlHeaderImpl::mySetCapture(CaptureProc_t proc) {
+	this->m_captureProc = proc;
+	this->TrackMouseLeave();
+}
+
+void CListControlHeaderImpl::myReleaseCapture() {
+	m_captureProc = nullptr;
+}
+
+void CListControlHeaderImpl::TrackMouseLeave() {
+	TRACKMOUSEEVENT tme = { sizeof(tme) };
+	tme.dwFlags = TME_LEAVE;
+	tme.hwndTrack = m_hWnd;
+	TrackMouseEvent(&tme);
+}
+
+LRESULT CListControlHeaderImpl::MousePassThru(UINT msg, WPARAM wp, LPARAM lp) {
+	auto p = m_captureProc; // create local ref in case something in mid-captureproc clears it
+	if (p) {
+		CPoint pt(lp);
+		if (!p(msg, (DWORD)wp, pt)) {
+			myReleaseCapture();
+		}
+		return 0;
+	}
+
+	SetMsgHandled(FALSE);
+	return 0;
+}
+
+void CListControlHeaderImpl::OnMouseLeave() {
+	if (m_captureProc) {
+		m_captureProc(WM_MOUSELEAVE, 0, -1);
+		myReleaseCapture();
+	}
+}
+
+void CListControlHeaderImpl::OnKillFocus(CWindow) {
+	myReleaseCapture();
+	SetMsgHandled(FALSE);
+}
+
+void CListControlHeaderImpl::OnWindowPosChanged(LPWINDOWPOS arg) {
+	if (arg->flags & SWP_HIDEWINDOW) {
+		myReleaseCapture();
+	}
+	SetMsgHandled(FALSE);
+}
+
+void CListControlHeaderImpl::RequestEditItem(size_t, size_t) {
+	PFC_ASSERT(!"Please enable CListControl_EditImpl");
+}
+
+void CListControlHeaderImpl::OnLButtonDblClk(UINT nFlags, CPoint point) {
+	(void)nFlags;
+	if (this->GetCellTypeSupported()) {
+		auto ptAbs = PointClientToAbs(point);
+		size_t item = this->ItemFromPointAbs(ptAbs);
+		size_t subItem = SubItemFromPointAbs(ptAbs);
+		if (item != SIZE_MAX && subItem != SIZE_MAX) {
+			auto ct = GetCellType(item, subItem);
+			if (ct != nullptr) {
+				if (ct->IsToggle()) {
+					if (ct->HotRect(GetSubItemRect(item, subItem)).PtInRect(point)) {
+						return; // disregard doubleclick on checkbox
+					}
+				}
+			}
+		}
+	}
+	SetMsgHandled(FALSE); // handle doubleclick
+}
+
+
+void CListControlHeaderImpl::RenderItemBackground(CDCHandle p_dc, const CRect& p_itemRect, size_t item, uint32_t bkColor) {
+	if (!this->DelimitColumns()) {
+		__super::RenderItemBackground(p_dc, p_itemRect, item, bkColor);
+	} else {
+		auto cnt = this->GetColumnCount();
+		const auto order = this->GetColumnOrderArray();
+		uint32_t x = p_itemRect.left;
+		for (size_t walk = 0; walk < cnt; ) {
+			const size_t sub = order[walk];
+			auto span = this->GetSubItemSpan(item, sub);
+			PFC_ASSERT(span > 0);
+			uint32_t width = 0;
+			for (size_t walk2 = 0; walk2 < span; ++walk2) {
+				width += this->GetSubItemWidth(sub + walk2);
+			}
+			CRect rc = p_itemRect;
+			rc.left = x;
+			x += width;
+			rc.right = x;
+
+			CRect test;
+			if (test.IntersectRect(rc, p_itemRect)) {
+				DCStateScope s(p_dc);
+				if (p_dc.IntersectClipRect(rc) != NULLREGION) {
+					__super::RenderItemBackground(p_dc, rc, item, bkColor);
+				}
+			}
+			walk += span;
+		}
+	}
+}