Mercurial > foo_out_sdl
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; +}
