diff foosdk/sdk/libPPUI/CListControlWithSelection.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/CListControlWithSelection.cpp	Mon Jan 05 02:15:46 2026 -0500
@@ -0,0 +1,1662 @@
+#include "stdafx.h"
+#include <vsstyle.h>
+#include <memory>
+#include "CListControlWithSelection.h"
+#include "PaintUtils.h"
+#include "IDataObjectUtils.h"
+#include "SmartStrStr.h"
+#include "CListControl-Cells.h"
+
+namespace {
+	class bit_array_selection_CListControl : public pfc::bit_array {
+	public:
+		bit_array_selection_CListControl(CListControlWithSelectionBase const & list) : m_list(list) {}
+		bool get(t_size n) const {return m_list.IsItemSelected(n);}
+	private:
+		CListControlWithSelectionBase const & m_list;
+	};
+}
+
+bool CListControlWithSelectionBase::SelectAll() {
+	SetSelection(pfc::bit_array_true(), pfc::bit_array_true() );
+	return true;
+}
+void CListControlWithSelectionBase::SelectNone() {
+	SetSelection(pfc::bit_array_true(), pfc::bit_array_false() );
+}
+void CListControlWithSelectionBase::SetSelectionAt(size_t idx, bool bSel) {
+	if (bSel == this->IsItemSelected(idx)) return;
+	this->SetSelection(pfc::bit_array_one(idx), pfc::bit_array_val(bSel));
+}
+void CListControlWithSelectionBase::SelectSingle(size_t which) {
+	this->SetFocusItem( which );
+	SetSelection(pfc::bit_array_true(), pfc::bit_array_one( which ) );
+}
+
+LRESULT CListControlWithSelectionBase::OnFocus(UINT,WPARAM,LPARAM,BOOL& bHandled) {
+	UpdateItems(pfc::bit_array_or(bit_array_selection_CListControl(*this), pfc::bit_array_one(GetFocusItem())));
+	bHandled = FALSE;
+	return 0;
+}
+
+void CListControlWithSelectionBase::OnKeyDown_SetIndexDeltaPageHelper(int p_delta, int p_keys) {
+	const CRect rcClient = GetClientRectHook();
+	int itemHeight = GetItemHeight();
+	if (itemHeight > 0) {
+		int delta = rcClient.Height() / itemHeight;
+		if (delta < 1) delta = 1;
+		OnKeyDown_SetIndexDeltaHelper(delta * p_delta,p_keys);
+	}
+}
+
+void CListControlWithSelectionBase::OnKeyDown_HomeEndHelper(bool isEnd, int p_keys) {
+	if (!isEnd) {
+		//SPECIAL FIX - ensure first group header visibility.
+		MoveViewOrigin(CPoint(GetViewOrigin().x,0));
+	}
+
+	const t_size itemCount = GetItemCount();
+	if (itemCount == 0) return;//don't bother
+
+	if ((p_keys & (MK_SHIFT|MK_CONTROL)) == (MK_SHIFT|MK_CONTROL)) {
+		RequestMoveSelection( isEnd ? (int)itemCount : -(int)itemCount );
+	} else {
+		OnKeyDown_SetIndexHelper(isEnd ? (int)(itemCount-1) : 0,p_keys);
+	}
+}
+void CListControlWithSelectionBase::OnKeyDown_SetIndexHelper(int p_index, int p_keys) {
+	const t_size count = GetItemCount();
+	if (count > 0) {
+		t_size idx = (t_size)pfc::clip_t(p_index,0,(int)(count - 1));
+		const t_size oldFocus = GetFocusItem();
+		if (p_keys & MK_CONTROL) {
+			SetFocusItem(idx);
+		} else if ((p_keys & MK_SHIFT) != 0 && this->AllowRangeSelect() ) {
+			t_size selStart = GetSelectionStart();
+			if (selStart == pfc_infinite) selStart = oldFocus;
+			if (selStart == pfc_infinite) selStart = idx;
+			t_size selFirst, selCount;
+			selFirst = pfc::min_t(selStart,idx);
+			selCount = pfc::max_t(selStart,idx) + 1 - selFirst;
+			SetSelection(pfc::bit_array_true(), pfc::bit_array_range(selFirst,selCount));
+			SetFocusItem(idx);
+			SetSelectionStart(selStart);
+		} else {
+			SetFocusItem(idx);
+			SetSelection(pfc::bit_array_true(), pfc::bit_array_one(idx));
+		}
+	}
+}
+
+void CListControlWithSelectionBase::SelectGroupHelper2(size_t base,int p_keys) {
+	size_t count = this->ResolveGroupRange2(base);
+	if ( count > 0 ) {
+		if (p_keys & MK_CONTROL) {
+			SetGroupFocusByItem(base);
+		} /*else if (p_keys & MK_SHIFT) {
+		} */else {
+			SetGroupFocusByItem(base);
+			SetSelection(pfc::bit_array_true(), pfc::bit_array_range(base,count));
+		}
+	}
+}
+
+void CListControlWithSelectionBase::OnKeyDown_SetIndexDeltaLineHelper(int p_delta, int p_keys) {
+	if ((p_keys & (MK_SHIFT | MK_CONTROL)) == (MK_SHIFT|MK_CONTROL)) {
+		this->RequestMoveSelection(p_delta);
+		return;
+	}
+	const t_size total = GetItemCount();
+	t_size current = GetFocusItem(); 
+	const size_t focusGroupItem = this->GetGroupFocus2();
+	if (focusGroupItem != SIZE_MAX) current = focusGroupItem;
+
+	if (current == pfc_infinite) {
+		OnKeyDown_SetIndexDeltaHelper(p_delta,p_keys);
+		return;
+	}
+
+	const groupID_t currentGroup = GetItemGroup(current);
+	if (GroupFocusActive()) {
+		if (p_delta < 0 && focusGroupItem > 0) {
+			OnKeyDown_SetIndexHelper((int)(focusGroupItem-1), p_keys);
+		} else if (p_delta > 0) {
+			OnKeyDown_SetIndexHelper((int) current, p_keys);
+		}
+	} else {
+		if ((p_keys & MK_SHIFT) != 0) {
+			OnKeyDown_SetIndexDeltaHelper(p_delta,p_keys);
+		} else if (p_delta < 0) {
+			if (currentGroup == 0 || (current > 0 && currentGroup == GetItemGroup(current - 1))) {
+				OnKeyDown_SetIndexDeltaHelper(p_delta,p_keys);
+			} else {
+				SelectGroupHelper2(current, p_keys);
+			}
+		} else if (p_delta > 0) {
+			if (current + 1 >= total || currentGroup == GetItemGroup(current + 1)) {
+				OnKeyDown_SetIndexDeltaHelper(p_delta,p_keys);
+			} else {
+				SelectGroupHelper2(current + 1, p_keys);
+			}
+		}
+	}
+}
+
+LRESULT CListControlWithSelectionBase::OnLButtonDblClk(UINT,WPARAM p_wp,LPARAM p_lp,BOOL& bHandled) {
+	CPoint pt(p_lp);
+	if (OnClickedSpecialHitTest(pt)) {
+		return OnButtonDown(WM_LBUTTONDOWN, p_wp, p_lp, bHandled);
+	}
+	t_size item;
+	if (ItemFromPoint(pt,item)) {
+		ExecuteDefaultAction(item);
+		return 0;
+	} else if (GroupHeaderFromPoint2(pt,item)) {
+		t_size count = this->ResolveGroupRange2(item);
+		if (count > 0) ExecuteDefaultActionGroup(item, count);
+		return 0;
+	} else if (ExecuteCanvasDefaultAction(pt)) {
+		return 0;
+	} else {
+		return OnButtonDown(WM_LBUTTONDOWN,p_wp,p_lp,bHandled);
+	}
+}
+void CListControlWithSelectionBase::ExecuteDefaultActionByFocus() {
+	size_t groupFocus = this->GetGroupFocus2();
+	if ( groupFocus != SIZE_MAX ) {
+		size_t count = this->ResolveGroupRange2(groupFocus);
+		if (count > 0) {
+			ExecuteDefaultActionGroup(groupFocus, count);
+		}
+	} else {
+		size_t index = this->GetFocusItem();
+		if (index != SIZE_MAX) this->ExecuteDefaultAction(index);
+	}
+}
+
+t_size CListControlWithSelectionBase::GetSingleSel() const {
+	t_size total = GetItemCount();
+	t_size first = SIZE_MAX;
+	for(t_size walk = 0; walk < total; ++walk) {
+		if (IsItemSelected(walk)) {
+			if (first == SIZE_MAX) first = walk;
+			else return SIZE_MAX;
+		}
+	}
+	return first;
+}
+
+t_size CListControlWithSelectionBase::GetSelectedCount(pfc::bit_array const & mask,t_size max) const {
+	const t_size itemCount = this->GetItemCount();
+	t_size found = 0;
+	for(t_size walk = mask.find_first(true,0,itemCount); walk < itemCount && found < max; walk = mask.find_next(true,walk,itemCount)) {
+		if (IsItemSelected(walk)) ++found;
+	}
+	return found;
+}
+LRESULT CListControlWithSelectionBase::OnButtonDown(UINT p_msg,WPARAM p_wp,LPARAM p_lp,BOOL&) {
+	pfc::vartoggle_t<bool> l_noEnsureVisible(m_noEnsureVisible,true);
+	if (m_selectDragMode) {
+		AbortSelectDragMode();
+		return 0;
+	}
+
+	CPoint pt(p_lp);
+
+	if (OnClickedSpecial( (DWORD) p_wp, pt)) {
+		return 0;
+	}
+
+	
+	size_t item;
+	const bool isRightClick = (p_msg == WM_RBUTTONDOWN || p_msg == WM_RBUTTONDBLCLK);
+	const bool gotCtrl = (p_wp & MK_CONTROL) != 0 && !isRightClick;
+	const bool gotShift = (p_wp & MK_SHIFT) != 0 && !isRightClick;
+	
+	
+
+	const bool bCanSelect = !OnClickedSpecialHitTest( pt );
+
+	const bool instaDrag = false;
+	const bool ddSupported = IsDragDropSupported();
+
+	if (GroupHeaderFromPoint2(pt, item)) {
+		t_size base = item,count = ResolveGroupRange2(base);
+		if (AllowRangeSelect() && count > 0) {
+			SetGroupFocusByItem(base);
+			pfc::bit_array_range groupRange(base,count);
+			bool instaDragOverride = false;
+			if (gotCtrl) {
+				ToggleRangeSelection(groupRange);
+			} else if (gotShift) {
+				SetSelection(groupRange, pfc::bit_array_true());
+			} else {
+				if (GetSelectedCount(groupRange) == count) instaDragOverride = true;
+				else SetSelection(pfc::bit_array_true(),groupRange);
+			}
+			if (ddSupported && (instaDrag || instaDragOverride)) {
+				PrepareDragDrop(pt,isRightClick);
+			} else {
+				InitSelectDragMode(pt, isRightClick);
+			}
+		}
+	} else if (ItemFromPoint(pt,item)) {
+		const t_size oldFocus = GetFocusItem();
+		const t_size selStartBefore = GetSelectionStart();
+		if ( bCanSelect ) SetFocusItem(item);
+		if (gotShift && AllowRangeSelect() ) {
+			if (bCanSelect) {
+				t_size selStart = selStartBefore;
+				if (selStart == pfc_infinite) selStart = oldFocus;
+				if (selStart == pfc_infinite) selStart = item;
+				SetSelectionStart(selStart);
+				t_size selFirst, selCount;
+				selFirst = pfc::min_t(selStart, item);
+				selCount = pfc::max_t(selStart, item) + 1 - selFirst;
+				pfc::bit_array_range rangeMask(selFirst, selCount);
+				pfc::bit_array_true trueMask;
+				SetSelection(gotCtrl ? pfc::implicit_cast<const pfc::bit_array&>(rangeMask) : pfc::implicit_cast<const pfc::bit_array&>(trueMask), rangeMask);
+				//if (!instaDrag) InitSelectDragMode(pt, isRightClick);
+			}
+		} else {
+			if (gotCtrl) {
+				if (bCanSelect) SetSelection(pfc::bit_array_one(item), pfc::bit_array_val(!IsItemSelected(item)));
+				if (!instaDrag) InitSelectDragMode(pt, isRightClick);
+			} else {
+				if (!IsItemSelected(item)) {
+					if (bCanSelect) SetSelection(pfc::bit_array_true(), pfc::bit_array_one(item));
+					if (ddSupported && instaDrag) {
+						PrepareDragDrop(pt,isRightClick);
+					} else {
+						InitSelectDragMode(pt, isRightClick);
+					}
+				} else {
+					if (ddSupported) {
+						PrepareDragDrop(pt,isRightClick);
+					} else {
+						InitSelectDragMode(pt, isRightClick);
+					}
+				}
+			}
+		}
+	} else {
+		if (!gotShift && !gotCtrl && bCanSelect) SelectNone();
+		InitSelectDragMode(pt, isRightClick);
+	}
+	return 0;
+}
+
+
+void CListControlWithSelectionBase::ToggleRangeSelection(pfc::bit_array const & mask) {
+	SetSelection(mask, pfc::bit_array_val(GetSelectedCount(mask,1) == 0));
+}
+void CListControlWithSelectionBase::ToggleGroupSelection2(size_t base) {
+	size_t count = this->ResolveGroupRange2(base);
+	if (count > 0) {
+		ToggleRangeSelection(pfc::bit_array_range(base, count));
+	}
+}
+
+LRESULT CListControlWithSelectionBase::OnRButtonUp(UINT,WPARAM,LPARAM,BOOL& bHandled) {
+	bHandled = FALSE;
+	AbortPrepareDragDropMode();
+	AbortSelectDragMode();
+	return 0;
+}
+
+bool CListControlWithSelectionBase::ShouldBeginDrag(CPoint ptRef, CPoint ptNow) const {
+	auto threshold = PP::queryDragThresholdForDPI(this->GetDPI());
+	return abs(ptNow.x - ptRef.x) > threshold.cx || abs(ptNow.y - ptRef.y) > threshold.cy;
+}
+
+LRESULT CListControlWithSelectionBase::OnMouseMove(UINT,WPARAM,LPARAM p_lp,BOOL&) {
+	if (m_prepareDragDropMode) {
+		if (ShouldBeginDrag(m_prepareDragDropOrigin, CPoint(p_lp))) {
+			AbortPrepareDragDropMode();
+			if (!m_ownDDActive) {
+				pfc::vartoggle_t<bool> ownDD(m_ownDDActive,true);
+				RunDragDrop( PointClientToAbs( m_prepareDragDropOrigin ),m_prepareDragDropModeRightClick);
+			}
+		}
+	} else if (m_selectDragMode) {
+		HandleDragSel(CPoint(p_lp));
+	}
+	return 0;
+}
+LRESULT CListControlWithSelectionBase::OnLButtonUp(UINT,WPARAM p_wp,LPARAM p_lp,BOOL&) {
+	const bool wasPreparingDD = m_prepareDragDropMode;
+	AbortPrepareDragDropMode();
+	CPoint pt(p_lp);
+	const bool gotCtrl = (p_wp & MK_CONTROL) != 0;
+	const bool gotShift = (p_wp & MK_SHIFT) != 0;
+	bool click = false;
+	bool processSel = wasPreparingDD;
+	if (m_selectDragMode) {
+		processSel = !m_selectDragMoved;
+		AbortSelectDragMode();
+	}
+	if (processSel) {
+		click = true;
+		if (!OnClickedSpecialHitTest(pt) ) {
+			size_t item;
+			if (GroupHeaderFromPoint2(pt, item)) {
+				t_size base = item, count = ResolveGroupRange2(base);
+				if ( count > 0 ) {
+					if (gotCtrl) {
+					} else {
+						SetSelection(pfc::bit_array_true(), pfc::bit_array_range(base, count));
+					}
+				}
+			} else if (ItemFromPoint(pt, item)) {
+				const t_size selStartBefore = GetSelectionStart();
+				if (gotCtrl) {
+				} else if (gotShift) {
+					SetSelectionStart(selStartBefore);
+				} else {
+					SetSelection(pfc::bit_array_true(), pfc::bit_array_one(item));
+				}
+			}
+		}
+	}
+	if (click && !gotCtrl && !gotShift) {
+		size_t item;
+		if (GroupHeaderFromPoint2(pt,item)) {
+			OnGroupHeaderClicked(GetItemGroup(item),pt);
+		} else if (ItemFromPoint(pt,item)) {
+			OnItemClicked(item,pt);
+		}
+	}
+	return 0;
+}
+
+void CListControlWithSelectionBase::OnKeyDown_SetIndexDeltaHelper(int p_delta, int p_keys) {
+	size_t focus = SIZE_MAX;
+	if (this->GroupFocusActive()) {
+		focus = this->GetGroupFocus2();
+	} else {
+		focus = GetFocusItem();
+	}
+	int target = 0;
+	if (focus != SIZE_MAX) target = (int) focus + p_delta;
+	OnKeyDown_SetIndexHelper(target,p_keys);
+}
+
+
+static int _get_keyflags() {
+	int ret = 0;
+	if (IsKeyPressed(VK_CONTROL)) ret |= MK_CONTROL;
+	if (IsKeyPressed(VK_SHIFT)) ret |= MK_SHIFT;
+	return ret;
+}
+
+LRESULT CListControlWithSelectionBase::OnKeyDown(UINT,WPARAM p_wp,LPARAM,BOOL& bHandled) {
+	switch(p_wp) {
+	case VK_NEXT:
+		OnKeyDown_SetIndexDeltaPageHelper(1,_get_keyflags());
+		return 0;
+	case VK_PRIOR:
+		OnKeyDown_SetIndexDeltaPageHelper(-1,_get_keyflags());
+		return 0;
+	case VK_DOWN:
+		OnKeyDown_SetIndexDeltaLineHelper(1,_get_keyflags());
+		return 0;
+	case VK_UP:
+		OnKeyDown_SetIndexDeltaLineHelper(-1,_get_keyflags());
+		return 0;
+	case VK_HOME:
+		OnKeyDown_HomeEndHelper(false,_get_keyflags());
+		return 0;
+	case VK_END:
+		OnKeyDown_HomeEndHelper(true,_get_keyflags());
+		return 0;
+	case VK_SPACE:
+		if (!TypeFindCheck()) {
+			ToggleSelectedItems();
+		}
+		return 0;
+	case VK_RETURN:
+		ExecuteDefaultActionByFocus();
+		return 0;
+	case VK_DELETE:
+		if (GetHotkeyModifierFlags() == 0) {
+			RequestRemoveSelection();
+			return 0;
+		}
+		break;
+	case 'A':
+		if (GetHotkeyModifierFlags() == MOD_CONTROL) {
+			if (SelectAll()) {
+				return 0;
+			}
+			// otherwise unhandled
+		}
+		break;
+	}
+
+	bHandled = FALSE;
+	return 0;
+}
+
+void CListControlWithSelectionBase::ToggleSelectedItems() {
+	if (ToggleSelectedItemsHook(bit_array_selection_CListControl(*this))) return;
+	if (GroupFocusActive()) {
+		ToggleGroupSelection2(this->GetGroupFocus2());
+	} else {
+		const t_size focus = GetFocusItem();
+		if (focus != pfc_infinite) {
+			ToggleRangeSelection(pfc::bit_array_range(focus, 1));
+		}
+	}
+}
+
+LRESULT CListControlWithSelectionBase::OnTimer(UINT,WPARAM p_wp,LPARAM,BOOL& bHandled) {
+	switch((DWORD)p_wp) {
+	case (DWORD)KSelectionTimerID:
+		if (m_selectDragMode) {
+			CPoint pt;
+			if (GetCursorPos(&pt) && ScreenToClient(&pt)) {
+				const CRect client = GetClientRectHook();
+				CPoint delta(0, 0);
+				if (pt.x < client.left) {
+					delta.x = pt.x - client.left;
+				} else if (pt.x > client.right) {
+					delta.x = pt.x - client.right;
+				}
+				if (pt.y < client.top) {
+					delta.y = pt.y - client.top;
+				} else if (pt.y > client.bottom) {
+					delta.y = pt.y - client.bottom;
+				}
+
+				MoveViewOriginDelta(delta);
+				HandleDragSel(pt);
+			}
+		}
+		return 0;
+	case TDDScrollControl::KTimerID:
+		HandleDDScroll();
+		return 0;
+	default:
+		bHandled = FALSE;
+		return 0;
+	}
+}
+
+bool CListControlWithSelectionBase::MoveSelectionProbe(int delta) {
+	pfc::array_t<size_t> order; order.set_size(GetItemCount());
+	{
+		bit_array_selection_CListControl sel(*this);
+		pfc::create_move_items_permutation(order.get_ptr(), order.get_size(), sel, delta);
+	}
+	for( size_t w = 0; w < order.get_size(); ++w ) if ( order[w] != w ) return true;
+	return false;
+}
+void CListControlWithSelectionBase::RequestMoveSelection(int delta) {
+	pfc::array_t<size_t> order; order.set_size(GetItemCount());
+	{
+		bit_array_selection_CListControl sel(*this);
+		pfc::create_move_items_permutation(order.get_ptr(), order.get_size(), sel, delta);
+	}
+
+	this->RequestReorder(order.get_ptr(), order.get_size());
+
+	if (delta < 0) {
+		size_t idx = GetFirstSelected();
+		if (idx != pfc_infinite) EnsureItemVisible(idx);
+	} else {
+		size_t idx = GetLastSelected();
+		if (idx != pfc_infinite) EnsureItemVisible(idx);
+	}
+}
+
+void CListControlWithSelectionBase::ToggleSelection(pfc::bit_array const & mask) {
+	const t_size count = GetItemCount();
+	pfc::bit_array_bittable table(count);
+	for(t_size walk = mask.find_first(true,0,count); walk < count; walk = mask.find_next(true,walk,count)) {
+		table.set(walk,!IsItemSelected(walk));
+	}
+	this->SetSelection(mask,table);
+}
+
+static HRGN FrameRectRgn(const CRect & rect) {
+	CRect exterior(rect); exterior.InflateRect(1,1);
+	CRgn rgn; rgn.CreateRectRgnIndirect(exterior);
+	CRect interior(rect); interior.DeflateRect(1,1);
+	if (!interior.IsRectEmpty()) {
+		CRgn rgn2; rgn2.CreateRectRgnIndirect(interior);
+		rgn.CombineRgn(rgn2,RGN_DIFF);
+	}
+	return rgn.Detach();
+}
+
+void CListControlWithSelectionBase::HandleDragSel(const CPoint & p_pt) {
+	const CPoint pt = PointClientToAbs(p_pt);
+	if (m_selectDragMoved || ShouldBeginDrag(m_selectDragCurrentAbs, pt)) {
+
+		if (!this->AllowRangeSelect()) {
+			// simplified
+			m_selectDragCurrentAbs = pt;
+			if (pt != m_selectDragOriginAbs) m_selectDragMoved = true;
+			return;
+		}
+
+		CRect rcOld(m_selectDragOriginAbs,m_selectDragCurrentAbs); rcOld.NormalizeRect();
+		m_selectDragCurrentAbs = pt;
+		CRect rcNew(m_selectDragOriginAbs,m_selectDragCurrentAbs); rcNew.NormalizeRect();
+
+
+		{
+			CRgn rgn = FrameRectRgn(rcNew);
+			CRgn rgn2 = FrameRectRgn(rcOld);
+			rgn.CombineRgn(rgn2,RGN_OR);
+			rgn.OffsetRgn( - GetViewOffset() );
+			InvalidateRgn(rgn);
+		}
+
+		if (pt != m_selectDragOriginAbs) m_selectDragMoved = true;
+
+		if (m_selectDragChanged || !IsSameItemOrHeaderAbs(pt,m_selectDragOriginAbs)) {
+			m_selectDragChanged = true;
+			const int keys = _get_keyflags();
+			t_size base,count, baseOld, countOld;
+			if (!GetItemRangeAbs(rcNew,base,count)) base = count = 0;
+			if (!GetItemRangeAbs(rcOld,baseOld,countOld)) baseOld = countOld = 0;
+			{
+				pfc::bit_array_range rangeNew(base,count), rangeOld(baseOld,countOld);
+				if (keys & MK_CONTROL) {
+					ToggleSelection(pfc::bit_array_xor(rangeNew,rangeOld));
+				} else if (keys & MK_SHIFT) {
+					SetSelection(pfc::bit_array_or(rangeNew,rangeOld),rangeNew);
+				} else {
+					SetSelection(pfc::bit_array_true(),rangeNew);
+				}
+			}
+			if (ItemFromPointAbs(pt,base)) {
+				const CRect rcVisible = GetVisibleRectAbs(), rcItem = GetItemRectAbs(base);
+				if (rcItem.top >= rcVisible.top && rcItem.bottom <= rcVisible.bottom) {
+					SetFocusItem(base);
+				}
+			} else if (GroupHeaderFromPointAbs2(pt,base)) {
+				const CRect rcVisible = GetVisibleRectAbs();
+				CRect rcGroup;
+				if (GetGroupHeaderRectAbs2(base,rcGroup)) {
+					if (rcGroup.top >= rcVisible.top && rcGroup.bottom <= rcVisible.bottom) {
+						this->SetGroupFocusByItem(base);
+					}
+				}
+			}
+		}
+	}
+}
+
+void CListControlWithSelectionBase::InitSelectDragMode(const CPoint & p_pt,bool p_rightClick) {
+	// Perform the bookkeeping even if multiple selection is disabled, detection of clicks relies on it
+	(void)p_rightClick;
+	SetTimer(KSelectionTimerID,KSelectionTimerPeriod);
+	m_selectDragMode = true;
+	m_selectDragOriginAbs = m_selectDragCurrentAbs = PointClientToAbs(p_pt);
+	m_selectDragChanged = false; m_selectDragMoved = false;
+	SetCapture();
+}
+
+void CListControlWithSelectionBase::AbortSelectDragMode(bool p_lostCapture) {
+	if (m_selectDragMode) {
+		m_selectDragMode = false;
+		CRect rcSelect(m_selectDragOriginAbs,m_selectDragCurrentAbs); rcSelect.NormalizeRect();
+		rcSelect.OffsetRect( - GetViewOffset() );
+		if (!p_lostCapture) ::SetCapture(NULL);
+		rcSelect.InflateRect(1,1);
+		InvalidateRect(rcSelect);
+		KillTimer(KSelectionTimerID);
+	}
+}
+
+
+LRESULT CListControlWithSelectionBase::OnCaptureChanged(UINT,WPARAM,LPARAM,BOOL&) {
+	AbortPrepareDragDropMode(true);
+	AbortSelectDragMode(true);
+	return 0;
+}
+
+void CListControlWithSelectionBase::RenderOverlay2(const CRect & p_updaterect,CDCHandle p_dc)  {
+	if (m_selectDragMode && this->AllowRangeSelect() ) {
+		CRect rcSelect(m_selectDragOriginAbs,m_selectDragCurrentAbs);
+		rcSelect.NormalizeRect();
+		rcSelect.OffsetRect(-GetViewOffset());
+		PaintUtils::FocusRect(p_dc,rcSelect);
+	}
+	if (m_dropMark != SIZE_MAX) {
+		RenderDropMarkerClipped2(p_dc, p_updaterect, m_dropMark, m_dropMarkInside);
+	}
+	TParent::RenderOverlay2(p_updaterect,p_dc);
+}
+
+void CListControlWithSelectionBase::SetDropMark(size_t mark, bool inside) {
+	if (mark != m_dropMark || inside != m_dropMarkInside) {
+		CRgn updateRgn; updateRgn.CreateRectRgn(0, 0, 0, 0);
+		AddDropMarkToUpdateRgn(updateRgn, m_dropMark, m_dropMarkInside);
+		m_dropMark = mark;
+		m_dropMarkInside = inside;
+		AddDropMarkToUpdateRgn(updateRgn, m_dropMark, m_dropMarkInside);
+		RedrawWindow(NULL, updateRgn);
+	}
+}
+
+static int transformDDScroll(int p_value,int p_width, int p_dpi) {
+	if (p_dpi <= 0) p_dpi = 96;
+	const double dpiMul = 96.0 / (double) p_dpi;
+	double val = (double)(p_width - p_value);
+	val *= dpiMul;
+	val = pow(val,1.1) * 0.33;
+	val /= dpiMul;
+	return pfc::rint32(val);
+}
+
+void CListControlWithSelectionBase::HandleDDScroll() {
+	CPoint position;
+	if (m_ddScroll.m_timerActive && GetCursorPos(&position)) {
+		CRect client = GetClientRectHook();
+		CPoint delta (0,0);
+		if (ClientToScreen(client)) {
+			const CSize DPI = QueryScreenDPIEx();
+			const int scrollZoneWidthBase = GetItemHeight() * 2;
+			const int scrollZoneHeight = pfc::min_t<int>( scrollZoneWidthBase, client.Height() / 4 );
+			const int scrollZoneWidth = pfc::min_t<int>( scrollZoneWidthBase, client.Width() / 4 );
+		
+			if (position.y >= client.top && position.y < client.top + scrollZoneHeight) {
+				delta.y -= transformDDScroll(position.y - client.top, scrollZoneHeight, DPI.cy);
+			} else if (position.y >= client.bottom - scrollZoneHeight && position.y < client.bottom) {
+				delta.y += transformDDScroll(client.bottom - position.y, scrollZoneHeight, DPI.cy);
+			}
+
+			if (position.x >= client.left && position.x < client.left + scrollZoneWidth) {
+				delta.x -= transformDDScroll(position.x - client.left, scrollZoneWidth, DPI.cx);
+			} else if (position.x >= client.right - scrollZoneWidth && position.x < client.right) {
+				delta.x += transformDDScroll(client.right - position.x, scrollZoneWidth, DPI.cx);
+			}
+		}
+
+		if (delta != CPoint(0,0)) MoveViewOriginDelta(delta);
+	}
+}
+
+void CListControlWithSelectionBase::ToggleDDScroll(bool p_state) {
+	if (p_state != m_ddScroll.m_timerActive) {
+		if (p_state) {
+			SetTimer(m_ddScroll.KTimerID,m_ddScroll.KTimerPeriod);
+		} else {
+			KillTimer(m_ddScroll.KTimerID);
+		}
+		m_ddScroll.m_timerActive = p_state;
+	}
+}
+
+void CListControlWithSelectionBase::PrepareDragDrop(const CPoint & p_point,bool p_isRightClick) {
+	m_prepareDragDropMode = true;
+	m_prepareDragDropOrigin = p_point;
+	m_prepareDragDropModeRightClick = p_isRightClick;
+	SetCapture();
+}
+void CListControlWithSelectionBase::AbortPrepareDragDropMode(bool p_lostCapture) {
+	if (m_prepareDragDropMode) {
+		m_prepareDragDropMode = false;
+		if (!p_lostCapture) ::SetCapture(NULL);
+	}
+}
+
+
+void CListControlWithSelectionBase::RenderItem(t_size p_item,const CRect & p_itemRect,const CRect & p_updateRect,CDCHandle p_dc) {
+	//console::formatter() << "RenderItem: " << p_item;
+	const bool weHaveFocus = ::GetFocus() == m_hWnd;
+	const bool isSelected = this->IsItemSelected(p_item);
+
+	const t_uint32 bkColor = GetSysColorHook(colorBackground);
+	const t_uint32 hlColor = GetSysColorHook(colorSelection);
+	const t_uint32 bkColorUsed = isSelected ? (weHaveFocus ? hlColor : PaintUtils::BlendColor(hlColor,bkColor)) : bkColor;
+
+	bool alternateTextColor = false, dtt = false;
+	auto & m_theme = theme();
+	CRect rcSelection = p_itemRect;
+	this->AdjustSelectionRect(p_item, rcSelection);
+	if (m_theme != NULL && isSelected && hlColor == GetSysColor(COLOR_HIGHLIGHT) && /*bkColor == GetSysColor(COLOR_WINDOW) && */ IsThemePartDefined(m_theme, LVP_LISTITEM, 0)) {
+		//PaintUtils::RenderItemBackground(p_dc,p_itemRect,p_item+GetItemGroup(p_item),bkColor);
+		DrawThemeBackground(m_theme, p_dc, LVP_LISTITEM, weHaveFocus ? LISS_SELECTED : LISS_SELECTEDNOTFOCUS, rcSelection, p_updateRect);
+		// drawthemetext is acting whacky with dark mode
+		if (!this->GetDarkMode()) dtt = true;
+	} else {
+		this->RenderItemBackground(p_dc, rcSelection, p_item, bkColorUsed );
+		// PaintUtils::RenderItemBackground(p_dc,p_itemRect,p_item+GetItemGroup(p_item),bkColorUsed);
+		if (isSelected) alternateTextColor = true;
+	}
+
+	{
+		DCStateScope backup(p_dc);
+		p_dc.SetBkMode(TRANSPARENT);
+		p_dc.SetBkColor(bkColorUsed);
+		p_dc.SetTextColor(alternateTextColor ? PaintUtils::DetermineTextColor(bkColorUsed) : this->GetSysColorHook(colorText));
+		pfc::vartoggle_t<bool> toggle(m_drawThemeText, dtt);
+		RenderItemText(p_item,p_itemRect,p_updateRect,p_dc, !alternateTextColor);
+	}
+
+	if (IsItemFocused(p_item) && weHaveFocus) {
+		PaintUtils::FocusRect2(p_dc,rcSelection, bkColorUsed);
+	}
+}
+
+void CListControlWithSelectionBase::RenderSubItemText(t_size item, t_size subItem,const CRect & subItemRect,const CRect & updateRect,CDCHandle dc, bool allowColors) {
+#if 0
+	auto ct = GetCellType(item, subItem);
+	if ( ct == nullptr ) return;
+
+	if (m_drawThemeText && ct->AllowDrawThemeText() && !this->IsSubItemGrayed(item, subItem)) for(;;) {
+		pfc::string_formatter label;
+		if (!GetSubItemText(item,subItem,label)) return;
+		const bool weHaveFocus = ::GetFocus() == m_hWnd;
+		// const bool isSelected = this->IsItemSelected(item);
+		pfc::stringcvt::string_os_from_utf8 cvt(label);
+		if (PaintUtils::TextContainsCodes(cvt)) break;
+		CRect clip = GetItemTextRect(subItemRect);
+		const t_uint32 format = PaintUtils::DrawText_TranslateHeaderAlignment(GetColumnFormat(subItem));
+		DrawThemeText(theme(), dc, LVP_LISTITEM, weHaveFocus ? LISS_SELECTED : LISS_SELECTEDNOTFOCUS, cvt, (int)cvt.length(), DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER | format, 0, clip);
+		return;
+	}
+#endif
+	__super::RenderSubItemText(item, subItem, subItemRect, updateRect, dc, allowColors);
+}
+
+void CListControlWithSelectionBase::RenderGroupHeader2(size_t baseItem,const CRect & p_headerRect,const CRect & p_updateRect,CDCHandle p_dc) {
+	TParent::RenderGroupHeader2(baseItem,p_headerRect,p_updateRect,p_dc);
+	if (IsGroupHeaderFocused2(baseItem)) {
+		PaintUtils::FocusRect(p_dc,p_headerRect);
+	}
+}
+
+CRect CListControlWithSelectionBase::DropMarkerUpdateRect(t_size index,bool bInside) const {
+	if (index != SIZE_MAX) {
+		CRect rect;
+		if (bInside) {
+			rect = GetItemRect(index);
+			rect.InflateRect(DropMarkerMargin());
+		} else {
+			rect = DropMarkerRect(DropMarkerOffset(index));
+		}
+		return rect;
+	} else {
+		return CRect(0,0,0,0);
+	}
+}
+void CListControlWithSelectionBase::AddDropMarkToUpdateRgn(HRGN p_rgn, t_size p_index, bool bInside) const {
+	CRect rect = DropMarkerUpdateRect(p_index,bInside);
+	if (!rect.IsRectEmpty()) PaintUtils::AddRectToRgn(p_rgn,rect);
+}
+
+CRect CListControlWithSelectionBase::DropMarkerRect(int offset) const {
+	const int delta = MulDiv(5,m_dpi.cy,96);
+	CRect rc(0,offset - delta,GetViewAreaWidth(), offset + delta);
+	rc.InflateRect(DropMarkerMargin());
+	return rc;
+}
+
+int CListControlWithSelectionBase::DropMarkerOffset(t_size marker) const {
+	
+	return (marker > 0 ? this->GetItemBottomOffsetAbs(marker-1): 0) - GetViewOffset().y;
+}
+
+bool CListControlWithSelectionBase::RenderDropMarkerClipped2(CDCHandle dc, const CRect & update, t_size item, bool bInside) {
+	CRect markerRect = DropMarkerUpdateRect(item,bInside);
+	CRect affected;
+	if (affected.IntersectRect(markerRect,update)) {
+		DCStateScope state(dc);
+		if (dc.IntersectClipRect(affected)) {
+			RenderDropMarker2(dc,item,bInside);
+			return true;
+		}
+	}
+	return false;
+}
+void CListControlWithSelectionBase::RenderDropMarker2(CDCHandle dc, t_size item, bool bInside) {
+	if (item != SIZE_MAX) {
+		if (bInside) {
+			CPen pen; MakeDropMarkerPen(pen);
+			SelectObjectScope penScope(dc,pen);
+			const CRect rc = GetItemRect(item);
+			dc.MoveTo(rc.left,rc.top);
+			dc.LineTo(rc.right,rc.top);
+			dc.LineTo(rc.right,rc.bottom);
+			dc.LineTo(rc.left,rc.bottom);
+			dc.LineTo(rc.left,rc.top);
+		} else {
+			RenderDropMarkerByOffset2(DropMarkerOffset(item),dc);
+		}
+	}
+}
+
+SIZE CListControlWithSelectionBase::DropMarkerMargin() const {
+	const int penDeltaX = MulDiv(5 /* we don't know how to translate CreatePen units... */,m_dpi.cx,96);
+	const int penDeltaY = MulDiv(5 /* we don't know how to translate CreatePen units... */,m_dpi.cy,96);
+	SIZE s = {penDeltaX,penDeltaY};
+	return s;
+}
+void CListControlWithSelectionBase::MakeDropMarkerPen(CPen & out) const {
+	WIN32_OP_D( out.CreatePen(PS_SOLID,3,GetSysColorHook(colorText)) != NULL );
+}
+
+void CListControlWithSelectionBase::RenderDropMarkerByOffset2(int offset,CDCHandle p_dc) {
+	CPen pen; MakeDropMarkerPen(pen);
+	const int delta = MulDiv(5,m_dpi.cy,96);
+	SelectObjectScope penScope(p_dc,pen);
+	const int width = GetViewAreaWidth();
+	if (width > 0) {
+		const int vx = this->GetViewOffset().x;
+		const int left = -vx;
+		const int right = width - 1 - vx;
+		p_dc.MoveTo(left,offset);
+		p_dc.LineTo(right,offset);
+		p_dc.MoveTo(left,offset-delta);
+		p_dc.LineTo(left,offset+delta);
+		p_dc.MoveTo(right,offset-delta);
+		p_dc.LineTo(right,offset+delta);
+	}
+}
+
+void CListControlWithSelectionBase::FocusToUpdateRgn(HRGN rgn) {
+	size_t focusItem = GetFocusItem();
+	if (focusItem != SIZE_MAX) AddItemToUpdateRgn(rgn,focusItem);
+	size_t focusGroup = GetGroupFocus2();
+	if (focusGroup != SIZE_MAX) AddGroupHeaderToUpdateRgn2(rgn,focusGroup);
+}
+
+void CListControlWithSelectionImpl::ReloadData() {
+	if ( GetItemCount() != m_selection.get_size() ) {
+		this->SelHandleReset();
+	}
+	__super::ReloadData();
+}
+
+size_t CListControlWithSelectionImpl::GetGroupFocus2() const {
+	return (m_groupFocus && m_focus < GetItemCount()) ? m_focus : SIZE_MAX;
+}
+
+void CListControlWithSelectionImpl::SetSelectionImpl(pfc::bit_array const & affected, pfc::bit_array const & status) {
+	const t_size total = m_selection.get_size();
+	pfc::bit_array_flatIndexList toUpdate;
+
+	// Only fire UpdateItems for stuff that's both on-screen and actually changed
+	// Firing for whole affected mask will repaint everything when selecting one item
+	t_size base, count;
+	if (!GetItemRangeAbs(GetVisibleRectAbs(), base, count)) { base = count = 0; }
+
+	affected.walk( total, [&] (size_t idx) {
+		if ( m_selection[idx] != status[idx] && this->CanSelectItem(idx) ) {
+			m_selection[idx] = status[idx];
+			if ( idx >= base && idx < base+count ) toUpdate.add(idx);
+		}
+	} );
+
+	if ( toUpdate.get_count() > 0 ) {
+		UpdateItems(toUpdate);
+	}
+
+
+	// Fire subclassable method ONLY WITH ITEMS THAT CHANGED
+	// We provide no other means for them to know old state
+	this->OnSelectionChanged(toUpdate, status );
+}
+
+void CListControlWithSelectionImpl::SetSelection(pfc::bit_array const & affected, pfc::bit_array const & status) {
+	RefreshSelectionSize();
+
+
+	if ( m_selectionSupport == selectionSupportNone ) return;
+
+	if ( m_selectionSupport == selectionSupportSingle ) {
+		size_t single = SIZE_MAX;
+		bool selNone = true;
+		const size_t total = m_selection.get_size();
+		for( size_t walk = 0; walk < total; ++ walk ) {
+			if ( affected.get(walk) ) {
+				if ( status.get(walk) && single == SIZE_MAX ) {
+					single = walk;
+				}
+			} else if ( IsItemSelected( walk ) ) {
+				selNone = false;
+			}
+		}
+		if ( single < total ) {
+			SetSelectionImpl( pfc::bit_array_true(), pfc::bit_array_one( single ) );
+		} else if ( selNone ) {
+			this->SetSelectionImpl( pfc::bit_array_true(), pfc::bit_array_false() );
+		}
+	} else {
+		SetSelectionImpl( affected, status );
+	}
+
+}
+
+void CListControlWithSelectionImpl::RefreshSelectionSize() {
+	RefreshSelectionSize(GetItemCount());
+}
+void CListControlWithSelectionImpl::RefreshSelectionSize(t_size total) {
+	const t_size oldSize = m_selection.get_size();
+	if (total != oldSize) {
+		m_selection.set_size(total);
+		for(t_size walk = oldSize; walk < total; ++walk) m_selection[walk] = false;
+	}
+}
+
+void CListControlWithSelectionImpl::SetGroupFocusByItem(t_size item) {
+	CRgn update; update.CreateRectRgn(0,0,0,0);
+	FocusToUpdateRgn(update);
+	m_groupFocus = true; m_focus = item;
+	FocusToUpdateRgn(update);
+	InvalidateRgn(update);
+
+	
+	CRect header; 
+	if (GetGroupHeaderRectAbs2(item,header)) EnsureVisibleRectAbs(header);
+
+	this->OnFocusChangedGroup2( item );
+}
+
+void CListControlWithSelectionImpl::SetFocusItem(t_size index) {
+	CRgn update; update.CreateRectRgn(0,0,0,0);
+	FocusToUpdateRgn(update);
+	size_t oldFocus = m_focus;
+	m_groupFocus = false; m_focus = index;
+	FocusToUpdateRgn(update);
+	InvalidateRgn(update);
+	
+	if ( index != SIZE_MAX ) {
+		EnsureVisibleRectAbs(GetItemRectAbs(index));
+	}
+
+	SetSelectionStart(index);
+
+	this->OnFocusChanged(oldFocus, index);
+}
+
+static void UpdateIndexOnReorder(t_size & index, const t_size * order, t_size count) {
+	index = pfc::permutation_find_reverse(order,count,index);
+}
+static void UpdateIndexOnRemoval(t_size & index, const pfc::bit_array & mask, t_size oldCount, t_size newCount) {
+	if (index >= oldCount || newCount == 0) {index = SIZE_MAX; return;}
+	for(t_size walk = mask.find_first(true,0,oldCount); walk < newCount; ++walk) {
+		if (walk < index) --index;
+		else break;
+	}
+	if (index >= newCount) index = newCount - 1;
+}
+
+static void UpdateIndexOnInsert(size_t& index, pfc::bit_array const& mask, size_t newCount) {
+	if (index == SIZE_MAX) return;
+	for (size_t walk = 0; walk < index; ) {
+		size_t delta = mask.calc_count(true, walk, index - walk);
+		if (delta == 0) break;
+		walk = index; index += delta;
+	}
+}
+
+static void UpdateIndexOnInsert(t_size & index, t_size base, t_size count) {
+	if (index != SIZE_MAX && index >= base) index += count;
+}
+
+void CListControlWithSelectionImpl::SelHandleReorder(const t_size * order, t_size count) {
+	RefreshSelectionSize();
+	UpdateIndexOnReorder(m_focus,order,count);
+	UpdateIndexOnReorder(m_selectionStart,order,count);
+	pfc::array_t<bool> newSel; newSel.set_size(m_selection.get_size());
+	for(t_size walk = 0; walk < m_selection.get_size(); ++walk) newSel[walk] = m_selection[order[walk]];
+	m_selection = newSel;
+}
+
+void CListControlWithSelectionImpl::SelHandleReset() {
+	RefreshSelectionSize(GetItemCount());
+	for(t_size walk = 0; walk < m_selection.get_size(); walk++) m_selection[walk] = false;
+	m_focus = SIZE_MAX;
+	m_groupFocus = false;
+
+}
+void CListControlWithSelectionImpl::SelHandleRemoval(const pfc::bit_array & mask, t_size oldCount) {
+	RefreshSelectionSize(oldCount);
+	const t_size newCount = GetItemCount();
+	UpdateIndexOnRemoval(m_focus,mask,oldCount,newCount);
+	UpdateIndexOnRemoval(m_selectionStart,mask,oldCount,newCount);
+	pfc::remove_mask_t(m_selection,mask);
+}
+
+void CListControlWithSelectionImpl::SelHandleInsertion(pfc::bit_array const& mask, size_t oldCount, size_t newCount, bool select) {
+	PFC_ASSERT(newCount == GetItemCount());
+	PFC_ASSERT(oldCount <= newCount); (void)oldCount;
+
+	// To behave sanely in single-select mode, we'd have to alter selection of other items from here
+	// Let caller worry and outright deny select requests in modes other than multisel
+	if (m_selectionSupport != selectionSupportMulti) select = false;
+
+	UpdateIndexOnInsert(m_focus, mask, newCount);
+	UpdateIndexOnInsert(m_selectionStart, mask, newCount);
+	pfc::array_t<bool> newSel;
+	newSel.resize(newCount);
+
+	size_t inWalk = 0;
+	for (size_t walk = 0; walk < newCount; ++walk) {
+		bool v = false;
+		if (mask[walk]) {
+			v = select;
+		} else if (inWalk < m_selection.get_size()) {
+			v = m_selection[inWalk++];
+		}
+		newSel[walk] = v;
+	}
+
+	m_selection = std::move(newSel);
+}
+
+void CListControlWithSelectionImpl::SelHandleInsertion(t_size base, t_size count, bool select) {
+	PFC_ASSERT(base + count <= GetItemCount());
+
+	// To behave sanely in single-select mode, we'd have to alter selection of other items from here
+	// Let caller worry and outright deny select requests in modes other than multisel
+	if (m_selectionSupport != selectionSupportMulti) select = false;
+
+	UpdateIndexOnInsert(m_focus,base,count);
+	UpdateIndexOnInsert(m_selectionStart,base,count);
+	RefreshSelectionSize(GetItemCount() - count);
+
+
+	m_selection.insert_multi(select,base,count);
+}
+
+
+LRESULT CListControlWithSelectionBase::OnGetDlgCode(UINT,WPARAM,LPARAM p_lp,BOOL& bHandled) {
+	if (p_lp == 0) {
+		return DLGC_WANTALLKEYS | DLGC_WANTCHARS | DLGC_WANTARROWS;
+	} else {
+		const MSG * pmsg = reinterpret_cast<const MSG *>(p_lp);
+		switch(pmsg->message) {
+		case WM_KEYDOWN:
+		case WM_KEYUP:
+			switch(pmsg->wParam) {
+			case VK_ESCAPE:
+			case VK_TAB:
+				bHandled = FALSE;
+				return 0;
+			default:
+				return DLGC_WANTMESSAGE;
+			}
+		case WM_CHAR:
+			return DLGC_WANTMESSAGE;
+		default:
+			bHandled = FALSE;
+			return 0;
+		}
+	}
+}
+
+bool CListControlWithSelectionBase::GetFocusRect(CRect & p_rect) {
+	CRect temp;
+	if (!GetFocusRectAbs(temp)) return false;
+	p_rect = RectClientToAbs(temp);
+	return true;
+}
+
+bool CListControlWithSelectionBase::GetFocusRectAbs(CRect & p_rect) {
+	size_t item = this->GetFocusItem();
+	if (item != SIZE_MAX) {
+		p_rect = this->GetItemRectAbs(item);
+		return true;
+	}
+
+	item = this->GetGroupFocus2();
+	if (item != SIZE_MAX) {
+		return this->GetGroupHeaderRectAbs2(item, p_rect);
+	}
+
+	return false;
+}
+
+bool CListControlWithSelectionBase::GetContextMenuPoint2(CPoint& ptInOut) {
+	CPoint ptInvalid(-1,-1);
+	if (ptInOut == ptInvalid) {
+		ptInOut = GetContextMenuPointDefault();
+		return ptInOut != ptInvalid;
+	} else {
+		CRect rc = this->GetClientRectHook();
+		WIN32_OP_D( ClientToScreen(rc) );
+		return !!rc.PtInRect(ptInOut);
+	}
+}
+
+CPoint CListControlWithSelectionBase::GetContextMenuPointDefault() {
+	CRect rect;
+	if (!GetFocusRectAbs(rect)) return CPoint(-1,-1);
+	EnsureVisibleRectAbs(rect);
+	CPoint pt = rect.CenterPoint() - GetViewOffset();
+	ClientToScreen(&pt);
+	return pt;
+}
+
+CPoint CListControlWithSelectionBase::GetContextMenuPoint(CPoint ptGot) {
+	CPoint pt;
+	if (ptGot.x == -1 && ptGot.y == -1) {
+		pt = GetContextMenuPointDefault();
+	} else {
+		pt = ptGot;
+	}
+	return pt;
+}
+
+CPoint CListControlWithSelectionBase::GetContextMenuPoint(LPARAM lp) {
+	CPoint pt;
+	if (lp == -1) {
+		pt = GetContextMenuPointDefault();
+	} else {
+		pt = lp;
+	}
+	return pt;
+}
+
+bool CListControlWithSelectionBase::MakeDropReorderPermutation(pfc::array_t<t_size> & out, CPoint ptDrop) const {
+	t_size insertMark = InsertIndexFromPoint(ptDrop);
+	/*if (insertMark != this->GetFocusItem())*/ {
+		const t_size count = GetItemCount();
+		if (insertMark > count) insertMark = count;
+		{
+			t_size selBefore = 0;
+			for(t_size walk = 0; walk < insertMark; ++walk) {
+				if (IsItemSelected(walk)) selBefore++;
+			}
+			insertMark -= selBefore;
+		}
+		{
+			pfc::array_t<t_size> permutation, selected, nonselected;
+			const t_size selcount = this->GetSelectedCount();
+			selected.set_size(selcount); nonselected.set_size(count - selcount);
+			permutation.set_size(count);
+			if (insertMark > nonselected.get_size()) insertMark = nonselected.get_size();
+			for(t_size walk = 0, swalk = 0, nwalk = 0; walk < count; ++walk) {
+				if (IsItemSelected(walk)) {
+					selected[swalk++] = walk;
+				} else {
+					nonselected[nwalk++] = walk;
+				}
+			}
+			for(t_size walk = 0; walk < insertMark; ++walk) {
+				permutation[walk] = nonselected[walk];
+			}
+			for(t_size walk = 0; walk < selected.get_size(); ++walk) {
+				permutation[insertMark + walk] = selected[walk];
+			}
+			for(t_size walk = insertMark; walk < nonselected.get_size(); ++walk) {
+				permutation[selected.get_size() + walk] = nonselected[walk];
+			}
+			for(t_size walk = 0; walk < permutation.get_size(); ++walk) {
+				if (permutation[walk] != walk) {
+					out = permutation;
+					return true;
+				}
+			}
+		}
+	}
+	return false;
+}
+
+void CListControlWithSelectionBase::EnsureVisibleRectAbs(const CRect & p_rect) {
+	if (!m_noEnsureVisible) TParent::EnsureVisibleRectAbs(p_rect);
+}
+
+bool CListControlWithSelectionBase::TypeFindCheck(DWORD ts) const {
+	if (m_typeFindTS == 0) return false;
+	return ts - m_typeFindTS < 1000;
+}
+
+void CListControlWithSelectionBase::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) {
+	(void)nRepCnt; (void)nFlags;
+	if (nChar < 32) {
+		m_typeFindTS = 0;
+		return;
+	}
+	
+	const DWORD ts = GetTickCount();
+	if (!TypeFindCheck(ts)) m_typeFind.reset();
+
+	if (nChar == ' ' && m_typeFind.is_empty()) {
+		m_typeFindTS = 0;
+		return;
+	}
+
+	m_typeFindTS = ts;
+	if (m_typeFindTS == 0) m_typeFindTS = UINT32_MAX;
+	char temp[10] = {};
+	pfc::utf8_encode_char(nChar, temp);
+	m_typeFind += temp;
+	RunTypeFind();
+}
+
+static unsigned detectRepetition( pfc::string8 const & str ) {
+	size_t count = 0;
+	size_t walk = 0;
+	uint32_t first = 0;
+
+	while( walk < str.length() ) {
+		uint32_t current;
+		auto delta = pfc::utf8_decode_char( str.c_str() + walk, current, str.length() - walk ); 
+		if ( delta == 0 ) break;
+		walk += delta;
+
+		if ( count == 0 ) first = current;
+		else if ( first != current ) return 0;
+		
+		++ count;
+	}
+
+	if ( count > 1 ) return first;
+	return 0;
+}
+
+size_t CListControlWithSelectionBase::EvalTypeFind() {
+	if ( GetItemCount() == 0 ) return SIZE_MAX;
+
+	static SmartStrStr tool;
+
+	const size_t itemCount = GetItemCount();
+	const size_t colCount = GetColumnCount();
+	pfc::string_formatter temp; temp.prealloc(1024);
+	t_size searchBase = this->GetFocusItem();
+	if (searchBase >= itemCount) searchBase = 0;
+
+	size_t partial = SIZE_MAX;
+	size_t repetition = SIZE_MAX;
+	bool useRepetition = false;
+	pfc::string8 strRepetitionChar;
+	unsigned repChar = detectRepetition( m_typeFind );
+	if ( repChar != 0 ) {
+		useRepetition = true;
+		strRepetitionChar.add_char( repChar );
+	}
+	
+	for(t_size walk = 0; walk < itemCount; ++walk) {
+		t_size index = (walk + searchBase) % itemCount;
+		for(size_t cWalk = 0; cWalk < colCount; ++cWalk) {
+			
+			temp.reset();
+
+			if (AllowTypeFindInCell( index, cWalk )) {
+				this->GetSubItemText(index, cWalk, temp);
+			}
+			
+			if ( temp.length() == 0 ) {
+				continue;
+			}
+			if (partial == SIZE_MAX) {
+				size_t matchAt;
+				if (tool.strStrEnd( temp, m_typeFind, & matchAt ) != nullptr) {
+					if ( matchAt == 0 ) return index;
+					partial = index;
+				}
+			} else {
+				if ( tool.matchHere( temp, m_typeFind ) ) return index;
+			}
+			if (useRepetition && index != searchBase) {
+				if ( tool.matchHere( temp, strRepetitionChar ) ) {
+					useRepetition = false;
+					repetition = index;
+				}
+			}
+		}
+	}
+	if (partial < itemCount) return partial;
+	if (repetition < itemCount) return repetition;
+	return SIZE_MAX;
+}
+
+void CListControlWithSelectionBase::RunTypeFind() {
+	size_t index = EvalTypeFind();
+	if (index < GetItemCount() ) {
+		this->SetFocusItem( index );
+		this->SetSelection(pfc::bit_array_true(), pfc::bit_array_one(index) );
+	} else {
+		MessageBeep(0);
+	}
+}
+
+size_t CListControlWithSelectionBase::GetFirstSelected() const {
+	const size_t count = GetItemCount();
+	for( size_t w = 0; w < count; ++w ) {
+		if ( IsItemSelected(w) ) return w;
+	}
+	return SIZE_MAX;
+}
+
+size_t CListControlWithSelectionBase::GetLastSelected() const {
+	const size_t count = GetItemCount();
+	for( size_t w = count - 1; (t_ssize) w >= 0; --w ) {
+		if ( IsItemSelected(w) ) return w;
+	}
+	return SIZE_MAX;
+}
+
+namespace {
+	class CDropTargetImpl : public ImplementCOMRefCounter<IDropTarget> {
+	public:
+		COM_QI_BEGIN()
+			COM_QI_ENTRY(IUnknown)
+			COM_QI_ENTRY(IDropTarget)
+		COM_QI_END()
+
+		bool valid = true;
+		std::function<void(CPoint pt) > Track;
+		std::function<DWORD (IDataObject*)> HookAccept;
+		std::function<void (IDataObject*, CPoint pt)> HookDrop;
+		std::function<void ()> HookLeave;
+		
+		DWORD m_effect = DROPEFFECT_NONE;
+		
+		HRESULT STDMETHODCALLTYPE DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
+			(void)grfKeyState; (void)pt;
+			if (pDataObj == NULL || pdwEffect == NULL) return E_INVALIDARG;
+			if (!valid) return E_FAIL;
+			if ( HookAccept ) {
+				m_effect = HookAccept(pDataObj);
+			} else {
+				m_effect = DROPEFFECT_MOVE;
+			}
+			*pdwEffect = m_effect;
+			return S_OK;
+		}
+		HRESULT STDMETHODCALLTYPE DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
+			(void)grfKeyState;
+			if (pdwEffect == NULL) return E_INVALIDARG;
+			if (!valid) return E_FAIL;
+			if ( m_effect != DROPEFFECT_NONE ) Track(CPoint(pt.x, pt.y));
+			*pdwEffect = m_effect;
+			return S_OK;
+		}
+		HRESULT STDMETHODCALLTYPE DragLeave() {
+			if (HookLeave) HookLeave();
+			return S_OK;
+		}
+		HRESULT STDMETHODCALLTYPE Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
+			(void)grfKeyState; (void)pdwEffect;
+			if ( HookDrop && m_effect != DROPEFFECT_NONE ) {
+				HookDrop( pDataObj, CPoint(pt.x, pt.y) );
+			}
+			return S_OK;
+		}
+	};
+
+	class CDropSourceImpl : public ImplementCOMRefCounter<IDropSource> {
+	public:
+		CPoint droppedAt;
+		bool droppedAtValid = false;
+		bool allowReorder = false;
+
+		bool allowDragOutside = false;
+		CWindow wndOrigin;
+
+		COM_QI_BEGIN()
+			COM_QI_ENTRY(IUnknown)
+			COM_QI_ENTRY(IDropSource)
+		COM_QI_END()
+
+		HRESULT STDMETHODCALLTYPE GiveFeedback(DWORD dwEffect) {
+			m_effect = dwEffect;
+			return DRAGDROP_S_USEDEFAULTCURSORS;
+		}
+
+		HRESULT STDMETHODCALLTYPE QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState) {
+
+			if (fEscapePressed || (grfKeyState & MK_RBUTTON) != 0) {
+				return DRAGDROP_S_CANCEL;
+			} else if (!(grfKeyState & MK_LBUTTON)) {
+				if (m_effect == DROPEFFECT_NONE) return DRAGDROP_S_CANCEL;
+
+				CPoint pt;
+				if (!GetCursorPos(&pt)) return DRAGDROP_S_CANCEL;
+				bool bInside = false;
+				if (wndOrigin) {
+					CRect rc;
+					WIN32_OP_D(wndOrigin.GetWindowRect(rc));
+					bInside = rc.PtInRect(pt);
+				}
+				if (!allowDragOutside && !bInside) return DRAGDROP_S_CANCEL;
+				
+				if ( allowReorder && bInside) {
+					droppedAt = pt;
+					droppedAtValid = true;
+					return DRAGDROP_S_CANCEL;
+				}
+				return DRAGDROP_S_DROP;
+				
+			} else {
+				return S_OK;
+			}
+		}
+	private:
+		DWORD m_effect = 0;
+	};
+}
+
+void CListControlWithSelectionBase::RunDragDrop(const CPoint & p_origin, bool p_isRightClick) {
+
+	uint32_t flags = this->QueryDragDropTypes();
+	if ( flags == 0 ) {
+		PFC_ASSERT(!"How did we get here?");
+		return;
+	}
+	if ( flags == dragDrop_reorder ) {
+		if ( p_isRightClick ) return;
+		CPoint ptDrop;
+		if ( RunReorderDragDrop( p_origin, ptDrop ) ) {
+			pfc::array_t<size_t> order;
+			if (MakeDropReorderPermutation(order, ptDrop)) {
+				this->RequestReorder(order.get_ptr(), order.get_size());
+			}
+		}
+		return;
+	}
+
+	auto obj = this->MakeDataObject();
+	if (obj.is_empty()) {
+		PFC_ASSERT(!"How did we get here? No IDataObject");
+		return;
+	}
+
+	pfc::com_ptr_t<CDropSourceImpl> source = new CDropSourceImpl();
+	source->wndOrigin = m_hWnd;
+	source->allowDragOutside = true;
+	source->allowReorder = (flags & dragDrop_reorder) != 0;
+
+	DWORD outEffect = DROPEFFECT_NONE;
+	HRESULT status = DoDragDrop(obj.get_ptr(), source.get_ptr(), DragDropSourceEffects() , &outEffect);
+
+	if ( source->droppedAtValid ) {
+		CPoint ptDrop = source->droppedAt;
+		WIN32_OP_D(this->ScreenToClient(&ptDrop));
+		pfc::array_t<size_t> order;
+		if (MakeDropReorderPermutation(order, ptDrop)) {
+			this->RequestReorder(order.get_ptr(), order.get_size());
+		}
+	} else if (status == DRAGDROP_S_DROP) {
+		DragDropSourceSucceeded(outEffect);
+	}
+}
+
+pfc::com_ptr_t<IDataObject> CListControlWithSelectionBase::MakeDataObject() {
+	// return dummy IDataObject, presume derived transmits drag and drop payload by other means
+	using namespace IDataObjectUtils;
+	return new ImplementCOMRefCounter< CDataObjectBase >();
+}
+
+bool CListControlWithSelectionBase::RunReorderDragDrop(CPoint ptOrigin, CPoint & ptDrop) {
+	(void)ptOrigin;
+	pfc::com_ptr_t<CDropSourceImpl> source = new CDropSourceImpl();
+	pfc::com_ptr_t<CDropTargetImpl> target = new CDropTargetImpl();
+	
+	source->wndOrigin = m_hWnd;
+	source->allowDragOutside = false;
+	source->allowReorder = true;
+
+	target->Track = [this](CPoint pt) { 
+		WIN32_OP_D(this->ScreenToClient(&pt));
+		size_t idx = this->InsertIndexFromPoint(pt);
+		this->SetDropMark(idx, false);
+	};
+
+	if ( FAILED(RegisterDragDrop(*this, target.get_ptr())) ) {
+		// OleInitialize not called?
+		PFC_ASSERT( !"Should not get here" );
+		return false;
+	}
+
+	pfc::onLeaving scope([=] { target->valid = false; RevokeDragDrop(*this); });
+
+	using namespace IDataObjectUtils;
+	pfc::com_ptr_t<IDataObject> dataobject = new ImplementCOMRefCounter< CDataObjectBase >();
+	DWORD outeffect = 0;
+
+	ToggleDDScroll(true);
+	DoDragDrop(dataobject.get_ptr(), source.get_ptr(), DROPEFFECT_MOVE, &outeffect);
+	ClearDropMark();
+	ToggleDDScroll(false);
+	if (source->droppedAtValid ) {
+		CPoint pt = source->droppedAt;
+		WIN32_OP_D( this->ScreenToClient( &pt ) );
+		ptDrop = pt;
+		return true;
+	}
+	return false;
+}
+
+int CListControlWithSelectionBase::OnCreatePassThru(LPCREATESTRUCT) {
+	const uint32_t flags = this->QueryDragDropTypes();
+	if ( flags & dragDrop_external ) {
+
+		pfc::com_ptr_t<CDropTargetImpl> target = new CDropTargetImpl();
+
+		auto dda = std::make_shared<dragDropAccept_t>();
+
+		target->HookAccept = [this, flags, dda] ( IDataObject * obj ) {
+			if (this->m_ownDDActive && (flags & dragDrop_reorder) != 0) {
+				// Do not generate OnDrop for reorderings
+				dda->showDropMark = true;
+				dda->dwEFfect = DROPEFFECT_MOVE;
+			} else {
+				*dda = this->DragDropAccept2(obj);
+			}
+			return dda->dwEFfect;
+		};
+		target->HookDrop = [this, flags] ( IDataObject * obj, CPoint pt ) {
+			this->ToggleDDScroll(false);
+			this->ClearDropMark();
+			if ( this->m_ownDDActive ) {
+				// Do not generate OnDrop for reorderings
+				if ( flags & dragDrop_reorder ) return;
+			}
+			this->OnDrop( obj, pt );
+		};
+		target->HookLeave = [this] {
+			this->ClearDropMark();
+			this->ToggleDDScroll(false);
+		};
+
+		target->Track = [this, dda](CPoint pt) {
+			this->ToggleDDScroll(true);
+			if ( dda->showDropMark ) {
+				WIN32_OP_D(this->ScreenToClient(&pt));
+				size_t idx = this->InsertIndexFromPoint(pt);
+				if (dda->dropOnItem) {
+					if (idx < this->GetItemCount()) {
+						this->SetDropMark(idx, true);
+					} else {
+						this->ClearDropMark();
+					}
+				} else {
+					this->SetDropMark(idx, false);
+				}
+				
+			} else {
+				this->ClearDropMark();
+			}
+		};
+
+		RegisterDragDrop(*this, target.get_ptr() );
+	}
+	SetMsgHandled(FALSE); return 0;
+}
+
+void CListControlWithSelectionBase::OnDestroyPassThru() {
+	AbortSelectDragMode();
+	ToggleDDScroll(false);
+	RevokeDragDrop(*this);
+	SetMsgHandled(FALSE);
+}
+
+size_t CListControlWithSelectionBase::GetPasteTarget(const CPoint * ptPaste) const {
+	size_t target = SIZE_MAX;
+	if (ptPaste != nullptr) {
+		CPoint pt(*ptPaste); WIN32_OP_D(ScreenToClient(&pt));
+		size_t groupBase;
+		if (GroupHeaderFromPoint2(pt, groupBase)) {
+			target = groupBase;
+		} else if (ItemFromPoint(pt, target)) {
+			auto rc = GetItemRect(target);
+			auto height = rc.Height();
+			if (height > 0) {
+				double posInItem = (double)(pt.y - rc.top) / (double)height;
+				if (posInItem >= 0.5) ++target;
+			}
+		}
+	} else if (GroupFocusActive()) {
+		target = GetGroupFocus2();
+	} else {
+		target = GetFocusItem();
+	}
+	return target;
+}
+
+
+pfc::bit_array_table CListControlWithSelectionImpl::GetSelectionMaskRef() const {
+	return pfc::bit_array_table(m_selection.get_ptr(), m_selection.get_size());
+}
+pfc::bit_array_bittable CListControlWithSelectionImpl::GetSelectionMask() const {
+	pfc::bit_array_bittable ret;
+	const auto count = GetItemCount();
+	ret.resize( GetItemCount() );
+	for( size_t walk = 0; walk < count; ++ walk ) {
+		ret.set(walk, IsItemSelected(walk));
+	}
+	return ret;
+}
+
+void CListControlWithSelectionImpl::OnItemsReordered( const size_t * order, size_t count) {
+	PFC_ASSERT( count == GetItemCount() );
+
+	SelHandleReorder( order, count );
+	__super::OnItemsReordered(order, count);
+}
+
+void CListControlWithSelectionImpl::OnItemsRemoved( pfc::bit_array const & mask, size_t oldCount) {
+	SelHandleRemoval(mask, oldCount);
+	__super::OnItemsRemoved( mask, oldCount );
+}
+
+void CListControlWithSelectionImpl::OnItemsInsertedEx(pfc::bit_array const& mask, size_t oldCount, size_t newCount, bool bSelect) {
+	SelHandleInsertion( mask, oldCount, newCount, bSelect);
+	__super::OnItemsInsertedEx( mask, oldCount, newCount, bSelect );
+}
+
+bool CListControlWithSelectionImpl::SelectAll() {
+	if ( m_selectionSupport != selectionSupportMulti ) return false;
+	return __super::SelectAll();
+}
+
+DWORD CListControlWithSelectionBase::DragDropAccept(IDataObject* obj, bool& showDropMark) { 
+	(void)obj;
+	showDropMark = false; return DROPEFFECT_NONE; 
+}
+
+CListControlWithSelectionBase::dragDropAccept_t CListControlWithSelectionBase::DragDropAccept2(IDataObject* obj) {
+	dragDropAccept_t ret;
+	ret.dwEFfect = this->DragDropAccept(obj, ret.showDropMark);
+	return ret;
+}