#include "stdafx.h" #include "CListControl.h" #include "PaintUtils.h" #include "CListControlUserOptions.h" #include "GDIUtils.h" CListControlUserOptions * CListControlUserOptions::instance = nullptr; CRect CListControlImpl::GetClientRectHook() const { CRect temp; if (!GetClientRect(temp)) temp.SetRectEmpty(); return temp; } bool CListControlImpl::UserEnabledSmoothScroll() const { auto i = CListControlUserOptions::instance; if ( i != nullptr ) return i->useSmoothScroll(); return false; } LRESULT CListControlImpl::SetFocusPassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) { SetFocus(); bHandled = FALSE; return 0; } void CListControlImpl::EnsureVisibleRectAbs(const CRect & p_rect) { const CRect rcView = GetVisibleRectAbs(); const CRect rcItem = p_rect; int deltaX = 0, deltaY = 0; const bool centerOnItem = m_ensureVisibleUser; if (rcItem.top < rcView.top || rcItem.bottom > rcView.bottom) { if (rcItem.Height() > rcView.Height()) { deltaY = rcItem.top - rcView.top; } else { if (centerOnItem) { deltaY = rcItem.CenterPoint().y - rcView.CenterPoint().y; } else { if (rcItem.bottom > rcView.bottom) deltaY = rcItem.bottom - rcView.bottom; else deltaY = rcItem.top - rcView.top; } } } if (rcItem.left < rcView.left || rcItem.right > rcView.right) { if (rcItem.Width() > rcView.Width()) { if (rcItem.left > rcView.left || rcItem.right < rcView.right) deltaX = rcItem.left - rcView.left; } else { if (centerOnItem) { deltaX = rcItem.CenterPoint().x - rcView.CenterPoint().x; } else { if (rcItem.right > rcView.right) deltaX = rcItem.right - rcView.right; else deltaX = rcItem.left - rcView.left; } } } if (deltaX != 0 || deltaY != 0) { MoveViewOriginDelta(CPoint(deltaX,deltaY)); } } void CListControlImpl::EnsureItemVisible(t_size p_item, bool bUser) { m_ensureVisibleUser = bUser; EnsureVisibleRectAbs(GetItemRectAbs(p_item)); m_ensureVisibleUser = false; } void CListControlImpl::EnsureHeaderVisible(int p_group) { CRect rect; if (GetGroupHeaderRectAbs(p_group,rect)) EnsureVisibleRectAbs(rect); } void CListControlImpl::RefreshSlider(bool p_vertical) { const CRect viewArea = GetViewAreaRectAbs(); const CRect rcVisible = GetVisibleRectAbs(); SCROLLINFO si = {}; si.cbSize = sizeof(si); si.fMask = SIF_PAGE|SIF_RANGE|SIF_POS; if (AllowScrollbar(p_vertical)) { if (p_vertical) { si.nPage = rcVisible.Height(); si.nMin = viewArea.top; si.nMax = viewArea.bottom - 1; si.nPos = rcVisible.top; } else { si.nPage = rcVisible.Width(); si.nMin = viewArea.left; si.nMax = viewArea.right - 1; si.nPos = rcVisible.left; } } SetScrollInfo(p_vertical ? SB_VERT : SB_HORZ, &si); } void CListControlImpl::RefreshSliders() { //PROBLEM: while lots of data can be reused across those, it has to be recalculated inbetween because view area etc may change when scroll info changes RefreshSlider(false); RefreshSlider(true); } int CListControlImpl::GetScrollThumbPos(int which) { SCROLLINFO si = {}; si.cbSize = sizeof(si); si.fMask = SIF_TRACKPOS; GetScrollInfo(which,&si); return si.nTrackPos; } namespace { class ResolveGroupHelper { public: ResolveGroupHelper(const CListControlImpl & p_control) : m_control(p_control) {} int operator[](t_size p_index) const {return m_control.GetItemGroup(p_index);} private: const CListControlImpl & m_control; }; } bool CListControlImpl::ResolveGroupRange(int p_id,t_size & p_base,t_size & p_count) const { return pfc::binarySearch<>::runGroup(ResolveGroupHelper(*this),0,GetItemCount(),p_id,p_base,p_count); //return pfc::bsearch_range_t(GetItemCount(),ResolveGroupHelper(*this),pfc::compare_t,p_id,p_base,p_count); } static int HandleScroll(WORD p_code,int p_offset,int p_page, int p_line, int p_bottom, int p_thumbpos) { switch(p_code) { case SB_LINEUP: return p_offset - p_line; case SB_LINEDOWN: return p_offset + p_line; case SB_BOTTOM: return p_bottom - p_page; case SB_TOP: return 0; case SB_PAGEUP: return p_offset - p_page; case SB_PAGEDOWN: return p_offset + p_page; case SB_THUMBPOSITION: return p_thumbpos; case SB_THUMBTRACK: return p_thumbpos; default: return p_offset; } } static CPoint ClipPointToRect(CPoint const & p_pt,CRect const & p_rect) { return CPoint(pfc::clip_t(p_pt.x,p_rect.left,p_rect.right),pfc::clip_t(p_pt.y,p_rect.top,p_rect.bottom)); } void CListControlImpl::MoveViewOriginNoClip(CPoint p_target) { UpdateWindow(); const CPoint old = m_viewOrigin; m_viewOrigin = p_target; if (m_viewOrigin != old) { if (m_viewOrigin.x != old.x) SetScrollPos(SB_HORZ,m_viewOrigin.x); if (m_viewOrigin.y != old.y) SetScrollPos(SB_VERT,m_viewOrigin.y); const CPoint delta = old - m_viewOrigin; if (FixedOverlayPresent()) Invalidate(); else { DWORD flags = SW_INVALIDATE | SW_ERASE; const DWORD smoothScrollMS = 50; if (this->UserEnabledSmoothScroll() && this->CanSmoothScroll()) { flags |= SW_SMOOTHSCROLL | (smoothScrollMS << 16); } ScrollWindowEx(delta.x,delta.y,GetClientRectHook(),NULL,0,0,flags ); } OnViewOriginChange(m_viewOrigin - old); } } CPoint CListControlImpl::ClipViewOrigin(CPoint p_origin) const { return ClipPointToRect(p_origin,GetValidViewOriginArea()); } void CListControlImpl::MoveViewOrigin(CPoint p_target) { MoveViewOriginNoClip(ClipViewOrigin(p_target)); } #ifndef SPI_GETWHEELSCROLLCHARS #define SPI_GETWHEELSCROLLCHARS 0x006C #endif int CListControlImpl::HandleWheel(int & p_accum,int p_delta, bool bHoriz) { if ( m_suppressMouseWheel ) return 0; UINT scrollLines = 1; SystemParametersInfo(bHoriz ? SPI_GETWHEELSCROLLCHARS : SPI_GETWHEELSCROLLLINES,0,&scrollLines,0); if (scrollLines == ~0) { p_accum = 0; int rv = -pfc::sgn_t(p_delta); CRect client = GetClientRectHook(); if (bHoriz) rv *= client.Width(); else rv *= client.Height(); return rv; } const int itemHeight = GetItemHeight(); const int extraScale = 10000; p_accum += p_delta * extraScale; if ((int)scrollLines < 1) scrollLines = 1; int multiplier = (WHEEL_DELTA * extraScale) / (scrollLines * itemHeight); if (multiplier<1) multiplier = 1; int delta = pfc::rint32( (double) p_accum / (double) multiplier ); p_accum -= delta * multiplier; return -delta; /* if (p_accum<=-multiplier || p_accum>=multiplier) { int direction; int ov = p_accum; if (ov<0) { direction = -1; ov = -ov; p_accum = - ((-p_accum)%multiplier); } else { p_accum %= multiplier; direction = 1; } return - (direction * (ov + multiplier - 1) ) / multiplier; } else { return 0; } */ } LRESULT CListControlImpl::OnVWheel(UINT,WPARAM p_wp,LPARAM p_lp,BOOL&) { const CRect client = GetClientRectHook(), view = this->GetViewAreaRectAbs(); int deltaPixels = HandleWheel(m_wheelAccumY,(short)HIWORD(p_wp), false); const bool canVScroll = client.Height() < view.Height(); const bool canHScroll = client.Width() < view.Width(); CPoint ptDelta; if ( canVScroll && canHScroll && GetHotkeyModifierFlags() == MOD_SHIFT) { ptDelta = CPoint(deltaPixels, 0); // default to horizontal scroll if shift is pressed } else if (canVScroll) { ptDelta = CPoint(0,deltaPixels); } else if (canHScroll) { ptDelta = CPoint(deltaPixels,0); } if ( ptDelta != CPoint(0,0) ) { MoveViewOriginDelta(ptDelta); } return 0; } LRESULT CListControlImpl::OnHWheel(UINT,WPARAM p_wp,LPARAM p_lp,BOOL&) { const CRect client = GetClientRectHook(); int deltaPixels = HandleWheel(m_wheelAccumX,(short)HIWORD(p_wp), true); MoveViewOriginDelta(CPoint(-deltaPixels,0)); return 0; } LRESULT CListControlImpl::OnVScroll(UINT,WPARAM p_wp,LPARAM,BOOL&) { int target = HandleScroll(LOWORD(p_wp),m_viewOrigin.y,GetVisibleRectAbs().Height(),GetItemHeight(),GetViewAreaRectAbs().bottom,GetScrollThumbPos(SB_VERT)); MoveViewOrigin(CPoint(m_viewOrigin.x,target)); return 0; } LRESULT CListControlImpl::OnHScroll(UINT,WPARAM p_wp,LPARAM,BOOL&) { int target = HandleScroll(LOWORD(p_wp),m_viewOrigin.x,GetVisibleRectAbs().Width(),GetItemHeight() /*fixme*/,GetViewAreaRectAbs().right,GetScrollThumbPos(SB_HORZ)); MoveViewOrigin(CPoint(target,m_viewOrigin.y)); return 0; } LRESULT CListControlImpl::OnGesture(UINT,WPARAM,LPARAM lParam,BOOL& bHandled) { if (!this->m_gestureAPI.IsAvailable()) { bHandled = FALSE; return 0; } HGESTUREINFO hGesture = (HGESTUREINFO) lParam; GESTUREINFO gestureInfo = {sizeof(gestureInfo)}; if (m_gestureAPI.GetGestureInfo(hGesture, &gestureInfo)) { //console::formatter() << "WM_GESTURE " << pfc::format_hex( gestureInfo.dwFlags ) << " " << (int)gestureInfo.dwID << " X:" << gestureInfo.ptsLocation.x << " Y:" << gestureInfo.ptsLocation.y << " arg:" << (__int64) gestureInfo.ullArguments; CPoint pt( gestureInfo.ptsLocation.x, gestureInfo.ptsLocation.y ); switch(gestureInfo.dwID) { case GID_BEGIN: m_gesturePoint = pt; break; case GID_END: break; case GID_PAN: MoveViewOriginDelta( this->m_gesturePoint - pt); m_gesturePoint = pt; break; } } m_gestureAPI.CloseGestureInfoHandle(hGesture); bHandled = TRUE; return 0; } LRESULT CListControlImpl::OnSize(UINT,WPARAM,LPARAM p_lp,BOOL&) { OnSizeAsync_Trigger(); RefreshSliders(); return 0; } void CListControlImpl::RenderBackground( CDCHandle dc, CRect const & rc ) { PaintUtils::FillRectSimple(dc,rc,GetSysColorHook(colorBackground)); } void CListControlImpl::PaintContent(CRect rcPaint, HDC dc) { CDCHandle renderDC(dc); CMemoryDC bufferDC(renderDC,rcPaint); renderDC = bufferDC; this->RenderBackground(renderDC, rcPaint); { const CPoint pt = GetViewOffset(); OffsetWindowOrgScope offsetScope(renderDC, pt); CRect renderRect = rcPaint; renderRect.OffsetRect(pt); RenderRect(renderRect,renderDC); } } void CListControlImpl::OnPrintClient(HDC dc, UINT uFlags) { CRect rcClient; this->GetClientRect( rcClient ); PaintContent( rcClient, dc ); } LRESULT CListControlImpl::OnPaint(UINT,WPARAM,LPARAM,BOOL&) { CPaintDC paintDC(*this); PaintContent( paintDC.m_ps.rcPaint, paintDC.m_hDC ); return 0; } namespace { class comparator_rect { public: static int compare(const CRect & p_rect1,const CRect & p_rect2) { if (p_rect1.bottom <= p_rect2.top) return -1; else if (p_rect1.top >= p_rect2.bottom) return 1; else return 0; } }; static int RectPointCompare(const CRect & p_item1,const int p_y) { if (p_item1.bottom <= p_y) return -1; else if (p_item1.top > p_y) return 1; else return 0; } class RectSearchHelper_Items { public: RectSearchHelper_Items(const CListControlImpl & p_control) : m_control(p_control) {} CRect operator[](t_size p_index) const { return m_control.GetItemRectAbs(p_index); } private: const CListControlImpl & m_control; }; class RectSearchHelper_Groups { public: RectSearchHelper_Groups(const CListControlImpl & p_control) : m_control(p_control) {} CRect operator[](t_size p_index) const { CRect rect; if (!m_control.GetGroupHeaderRectAbs((int)(p_index + 1),rect)) rect.SetRectEmpty(); return rect; } private: const CListControlImpl & m_control; }; } bool CListControlImpl::GetItemRange(const CRect & p_rect,t_size & p_base,t_size & p_count) const { CRect temp(p_rect); temp.OffsetRect( GetViewOffset() ); return GetItemRangeAbs(temp, p_base, p_count); } bool CListControlImpl::GetItemRangeAbsInclHeaders(const CRect & p_rect,t_size & p_base,t_size & p_count) const { CRect temp(p_rect); temp.bottom += this->GetGroupHeaderHeight(); return GetItemRangeAbs(temp, p_base, p_count); } bool CListControlImpl::GetItemRangeAbs(const CRect & p_rect,t_size & p_base,t_size & p_count) const { if (p_rect.right < 0 || p_rect.left >= GetItemWidth()) return false; return pfc::binarySearch::runGroup(RectSearchHelper_Items(*this),0,GetItemCount(),p_rect,p_base,p_count); } void CListControlImpl::RenderRect(const CRect & p_rect,CDCHandle p_dc) { const CRect rectAbs = p_rect; t_size base, count; if (GetItemRangeAbs(rectAbs,base,count)) { for(t_size walk = 0; walk < count; ++walk) { CRect rcUpdate, rcItem = GetItemRectAbs(base+walk); if (rcUpdate.IntersectRect(rcItem,p_rect)) { DCStateScope dcState(p_dc); if (p_dc.IntersectClipRect(rcUpdate) != NULLREGION) { try { RenderItem(base+walk,rcItem,rcUpdate,p_dc); } catch(std::exception const & e) { (void) e; // console::complain("List Control: Item rendering failure", e); } } } } } if (pfc::binarySearch::runGroup(RectSearchHelper_Groups(*this),0,GetGroupCount(),rectAbs,base,count)) { for(t_size walk = 0; walk < count; ++walk) { CRect rcHeader, rcUpdate; const int id = (int)(base+walk+1); if (GetGroupHeaderRectAbs(id,rcHeader) && rcUpdate.IntersectRect(rcHeader,p_rect)) { DCStateScope dcState(p_dc); if (p_dc.IntersectClipRect(rcUpdate) != NULLREGION) { try { RenderGroupHeader(id,rcHeader,rcUpdate,p_dc); } catch(std::exception const & e) { (void) e; // console::complain("List Control: Group header rendering failure", e); } } } } } RenderOverlay(p_rect,p_dc); } CRect CListControlImpl::GetItemRect(t_size p_item) const { CRect rcItem = GetItemRectAbs(p_item); rcItem.OffsetRect( - GetViewOffset() ); return rcItem; } bool CListControlImpl::GetGroupHeaderRect(int p_group,CRect & p_rect) const { if (!GetGroupHeaderRectAbs(p_group,p_rect)) return false; p_rect.OffsetRect( - GetViewOffset() ); return true; } int CListControlImpl::GetViewAreaHeight() const { const t_size itemCount = GetItemCount(); int subAreaBase = 0; if (itemCount > 0) { subAreaBase = GetItemRectAbs(itemCount - 1).bottom; } return subAreaBase; } CRect CListControlImpl::GetItemRectAbs(t_size p_item) const { CRect rcItem; const int itemHeight = GetItemHeight(), itemWidth = GetItemWidth(), groupHeight = GetGroupHeaderHeight(), itemGroup = GetItemGroup(p_item); rcItem.top = (int)p_item * itemHeight + groupHeight * itemGroup; rcItem.bottom = rcItem.top + itemHeight; rcItem.left = 0; rcItem.right = rcItem.left + itemWidth; return rcItem; } bool CListControlImpl::GetGroupHeaderRectAbs(int p_group,CRect & p_rect) const { if (p_group == 0) return false; t_size itemBase, itemCount; if (!ResolveGroupRange(p_group,itemBase,itemCount)) return false; const int itemHeight = GetItemHeight(), itemWidth = GetItemWidth(), groupHeight = GetGroupHeaderHeight(); p_rect.bottom = (int) itemBase * itemHeight + groupHeight * p_group; p_rect.top = p_rect.bottom - groupHeight; p_rect.left = 0; p_rect.right = p_rect.left + itemWidth; return true; } CRect CListControlImpl::GetViewAreaRectAbs() const { return CRect(0,0,GetViewAreaWidth(),GetViewAreaHeight()); } CRect CListControlImpl::GetViewAreaRect() const { CRect rc = GetViewAreaRectAbs(); rc.OffsetRect( - GetViewOffset() ); CRect ret; ret.IntersectRect(rc,GetClientRectHook()); return ret; } t_size CListControlImpl::GetGroupCount() const { const t_size itemCount = GetItemCount(); if (itemCount > 0) { return (t_size) GetItemGroup(itemCount-1); } else { return 0; } } void CListControlImpl::UpdateGroupHeader(int p_id) { CRect rect; if (GetGroupHeaderRect(p_id,rect)) { InvalidateRect(rect); } } static void AddUpdateRect(HRGN p_rgn,CRect const & p_rect) { CRgn temp; temp.CreateRectRgnIndirect(p_rect); CRgnHandle(p_rgn).CombineRgn(temp,RGN_OR); } void CListControlImpl::OnItemsReordered( const size_t * order, size_t count ) { PFC_ASSERT( count == GetItemCount() ); ReloadItems( pfc::bit_array_order_changed(order) ); } void CListControlImpl::UpdateItems(const pfc::bit_array & p_mask) { t_size base,count; if (GetItemRangeAbs(GetVisibleRectAbs(),base,count)) { const t_size max = base+count; CRgn updateRgn; updateRgn.CreateRectRgn(0,0,0,0); bool found = false; for(t_size walk = p_mask.find_first(true,base,max); walk < max; walk = p_mask.find_next(true,walk,max)) { found = true; AddUpdateRect(updateRgn,GetItemRect(walk)); } if (found) { InvalidateRgn(updateRgn); } } } void CListControlImpl::UpdateItemsAndHeaders(const pfc::bit_array & p_mask) { t_size base,count; int groupWalk = 0; if (GetItemRangeAbsInclHeaders(GetVisibleRectAbs(),base,count)) { const t_size max = base+count; CRgn updateRgn; updateRgn.CreateRectRgn(0,0,0,0); bool found = false; for(t_size walk = p_mask.find_first(true,base,max); walk < max; walk = p_mask.find_next(true,walk,max)) { found = true; const int groupId = GetItemGroup(walk); if (groupId != groupWalk) { if (groupId > 0) { CRect rect; if (GetGroupHeaderRect(groupId,rect)) { AddUpdateRect(updateRgn,rect); } } groupWalk = groupId; } AddUpdateRect(updateRgn,GetItemRect(walk)); } if (found) { InvalidateRgn(updateRgn); } } } CRect CListControlImpl::GetValidViewOriginArea() const { const CRect rcView = GetViewAreaRectAbs(); const CRect rcClient = GetClientRectHook(); CRect rcArea = rcView; rcArea.right -= pfc::min_t(rcView.Width(),rcClient.Width()); rcArea.bottom -= pfc::min_t(rcView.Height(),rcClient.Height()); return rcArea; } void CListControlImpl::OnViewAreaChanged(CPoint p_originOverride) { const CPoint oldViewOrigin = m_viewOrigin; m_viewOrigin = ClipPointToRect(p_originOverride,GetValidViewOriginArea()); RefreshSliders(); Invalidate(); if (oldViewOrigin != m_viewOrigin) { OnViewOriginChange(m_viewOrigin - oldViewOrigin); } } bool CListControlImpl::ItemFromPointAbs(CPoint const & p_pt,t_size & p_item) const { if (p_pt.x < 0 || p_pt.x >= GetItemWidth()) return false; t_size dummy; return GetItemRangeAbs(CRect(p_pt,p_pt + CPoint(1,1)),p_item,dummy); } bool CListControlImpl::GroupHeaderFromPointAbs(CPoint const & p_pt,int & p_group) const { if (p_pt.x < 0 || p_pt.x >= GetItemWidth()) return false; t_size result; if (!pfc::binarySearch::run(RectSearchHelper_Groups(*this),0,GetGroupCount(),CRect(p_pt,p_pt + CSize(1,1)),result)) return false; //if (!pfc::bsearch_t(GetGroupCount(),RectSearchHelper_Groups(*this),RectCompare,CRect(p_pt,p_pt),result)) return false; p_group = (int) (result + 1); return true; } void CListControlImpl::OnThemeChanged() { m_themeCache.remove_all(); } CTheme & CListControlImpl::themeFor(const char * what) { bool bNew; auto & ret = this->m_themeCache.find_or_add_ex( what, bNew ); if (bNew) ret.OpenThemeData(*this, pfc::stringcvt::string_wide_from_utf8(what)); return ret; } LRESULT CListControlImpl::OnCreatePassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) { ::SetWindowTheme(*this, _T("explorer"), NULL); OnViewAreaChanged(); if (m_gestureAPI.IsAvailable()) { GESTURECONFIG config = {GID_PAN, GC_PAN_WITH_SINGLE_FINGER_VERTICALLY|GC_PAN_WITH_INERTIA, GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY | GC_PAN_WITH_GUTTER}; m_gestureAPI.SetGestureConfig( *this, 0, 1, &config, sizeof(GESTURECONFIG)); } bHandled = FALSE; return 0; } bool CListControlImpl::IsSameItemOrHeaderAbs(const CPoint & p_point1, const CPoint & p_point2) const { t_size item1, item2; int group1, group2; if (ItemFromPointAbs(p_point1, item1)) { if (ItemFromPointAbs(p_point2,item2)) { return item1 == item2; } else { return false; } } if (GroupHeaderFromPointAbs(p_point1, group1)) { if (GroupHeaderFromPointAbs(p_point2, group2)) { return group1 == group2; } else { return false; } } return false; } void CListControlImpl::OnSizeAsync_Trigger() { if (!m_sizeAsyncPending) { if (PostMessage(MSG_SIZE_ASYNC,0,0)) { m_sizeAsyncPending = true; } else { PFC_ASSERT(!"Shouldn't get here!"); //should not happen ListHandleResize(); } } } void CListControlImpl::ListHandleResize() { MoveViewOriginDelta(CPoint(0,0)); m_sizeAsyncPending = false; } void CListControlImpl::AddGroupHeaderToUpdateRgn(HRGN p_rgn, int id) const { if (id > 0) { CRect rcHeader; if (GetGroupHeaderRect(id,rcHeader)) AddUpdateRect(p_rgn,rcHeader); } } void CListControlImpl::AddItemToUpdateRgn(HRGN p_rgn, t_size p_index) const { if (p_index < this->GetItemCount()) { AddUpdateRect(p_rgn,GetItemRect(p_index)); } } COLORREF CListControlImpl::GetSysColorHook(int colorIndex) const { return GetSysColor(colorIndex); } LRESULT CListControlImpl::OnEraseBkgnd(UINT,WPARAM wp,LPARAM,BOOL&) { #ifndef CListControl_ScrollWindowFix const CRect rcClient = GetClientRectHook(); PaintUtils::FillRectSimple((HDC)wp,rcClient,GetColor(ui_color_background)); #endif return 1; } t_size CListControlImpl::InsertIndexFromPointEx(const CPoint & pt, bool & bInside) const { bInside = false; t_size insertMark = ~0; t_size itemIdx; int groupId; CPoint test(pt); test += GetViewOffset(); test.x = GetItemWidth() / 2; if (test.y >= GetViewAreaHeight()) { return GetItemCount(); } else if (ItemFromPointAbs(test,itemIdx)) { const CRect rc = GetItemRectAbs(itemIdx); if (test.y > rc.top + MulDiv(rc.Height(),2,3)) itemIdx++; else if (test.y >= rc.top + MulDiv(rc.Height(),1,3)) bInside = true; return itemIdx; } else if (GroupHeaderFromPointAbs(test,groupId)) { t_size base,count; if (ResolveGroupRange(groupId,base,count)) { return base; } } return ~0; } t_size CListControlImpl::InsertIndexFromPoint(const CPoint & pt) const { bool dummy; return InsertIndexFromPointEx(pt,dummy); } COLORREF CListControlImpl::BlendGridColor( COLORREF bk ) { return BlendGridColor( bk, PaintUtils::DetermineTextColor( bk ) ); } COLORREF CListControlImpl::BlendGridColor( COLORREF bk, COLORREF tx ) { return PaintUtils::BlendColor(bk, tx, 10); } COLORREF CListControlImpl::GridColor() { return BlendGridColor( GetSysColorHook(colorBackground), GetSysColorHook(colorText) ); } void CListControlImpl::RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,size_t p_item, uint32_t bkColor) { switch( this->m_rowStyle ) { case rowStylePlaylistDelimited: PaintUtils::RenderItemBackground(p_dc,p_itemRect,p_item+GetItemGroup(p_item),bkColor); { auto blend = BlendGridColor(bkColor); CDCPen pen(p_dc, blend); SelectObjectScope scope(p_dc, pen); p_dc.MoveTo( p_itemRect.right-1, p_itemRect.top ); p_dc.LineTo( p_itemRect.right-1, p_itemRect.bottom ); } break; case rowStylePlaylist: PaintUtils::RenderItemBackground(p_dc,p_itemRect,p_item+GetItemGroup(p_item),bkColor); break; case rowStyleGrid: PaintUtils::FillRectSimple(p_dc, p_itemRect, bkColor ); { auto blend = BlendGridColor(bkColor); CDCBrush brush(p_dc, blend); p_dc.FrameRect(&p_itemRect, brush); } break; case rowStyleFlat: PaintUtils::FillRectSimple(p_dc, p_itemRect, bkColor ); break; } } void CListControlImpl::RenderGroupHeaderBackground(CDCHandle p_dc,const CRect & p_headerRect,int p_group) { const t_uint32 bkColor = GetSysColorHook(colorBackground); t_size index = 0; t_size base, count; if (p_group > 0 && ResolveGroupRange(p_group,base,count)) { index = base + (t_size) p_group - 1; } switch( this->m_rowStyle ) { default: PaintUtils::FillRectSimple( p_dc, p_headerRect, bkColor ); break; case rowStylePlaylistDelimited: case rowStylePlaylist: PaintUtils::RenderItemBackground(p_dc,p_headerRect,index,bkColor); break; } } void CListControlImpl::RenderItem(t_size p_item,const CRect & p_itemRect,const CRect & p_updateRect,CDCHandle p_dc) { this->RenderItemBackground(p_dc, p_itemRect, p_item, GetSysColorHook(colorBackground) ); DCStateScope backup(p_dc); p_dc.SetBkMode(TRANSPARENT); p_dc.SetBkColor(GetSysColorHook(colorBackground)); p_dc.SetTextColor(GetSysColorHook(colorText)); RenderItemText(p_item,p_itemRect,p_updateRect,p_dc, true); } void CListControlImpl::RenderGroupHeader(int p_group,const CRect & p_headerRect,const CRect & p_updateRect,CDCHandle p_dc) { this->RenderGroupHeaderBackground(p_dc, p_headerRect, p_group ); DCStateScope backup(p_dc); p_dc.SetBkMode(TRANSPARENT); p_dc.SetBkColor(GetSysColorHook(colorBackground)); p_dc.SetTextColor(GetSysColorHook(colorHighlight)); RenderGroupHeaderText(p_group,p_headerRect,p_updateRect,p_dc); } CListControlFontOps::CListControlFontOps() : m_font((HFONT)::GetStockObject(DEFAULT_GUI_FONT)), m_itemHeight(), m_groupHeaderHeight() { UpdateGroupHeaderFont(); CalculateHeights(); } void CListControlFontOps::UpdateGroupHeaderFont() { try { m_groupHeaderFont = NULL; LOGFONT lf = {}; WIN32_OP_D( m_font.GetLogFont(lf) ); lf.lfHeight = pfc::rint32( (double) lf.lfHeight * GroupHeaderFontScale() ); lf.lfWeight = GroupHeaderFontWeight(lf.lfWeight); WIN32_OP_D( m_groupHeaderFont.CreateFontIndirect(&lf) != NULL ); } catch(std::exception const & e) { (void) e; // console::print(e.what()); m_groupHeaderFont = (HFONT)::GetStockObject(DEFAULT_GUI_FONT); } } void CListControlFontOps::CalculateHeights() { const t_uint32 spacing = MulDiv(4, m_dpi.cy, 96); m_itemHeight = GetFontHeight( m_font ) + spacing; m_groupHeaderHeight = GetFontHeight( m_groupHeaderFont ) + spacing; } void CListControlFontOps::SetFont(HFONT font,bool bUpdateView) { m_font = font; UpdateGroupHeaderFont(); CalculateHeights(); OnSetFont(bUpdateView); if (bUpdateView && m_hWnd != NULL) OnViewAreaChanged(); } LRESULT CListControlFontOps::OnSetFont(UINT,WPARAM wp,LPARAM,BOOL&) { SetFont((HFONT)wp); return 0; } LRESULT CListControlFontOps::OnGetFont(UINT,WPARAM,LPARAM,BOOL&) { return (LRESULT)(HFONT)m_font; } void CListControlImpl::SetCaptureEx(CaptureProc_t proc) { this->m_captureProc = proc; SetCapture(); } LRESULT CListControlImpl::MousePassThru(UINT msg, WPARAM wp, LPARAM lp) { auto p = m_captureProc; // create local ref in case something in mid-captureproc clears it if ( p ) { CPoint pt(lp); if (!p(msg, (DWORD) wp, pt ) ) { ReleaseCapture(); m_captureProc = nullptr; } return 0; } SetMsgHandled(FALSE); return 0; } LRESULT CListControlImpl::OnGetDlgCode(UINT, WPARAM wp, LPARAM) { switch(wp) { case VK_RETURN: return m_dlgWantEnter ? DLGC_WANTMESSAGE : 0; default: SetMsgHandled(FALSE); return 0; } } void CListControlImpl::CreateInDialog(CWindow wndDialog, UINT replaceControlID ) { CWindow lstReplace = wndDialog.GetDlgItem(replaceControlID); PFC_ASSERT( lstReplace != NULL ); auto status = lstReplace.SendMessage(WM_GETDLGCODE, VK_RETURN ); m_dlgWantEnter = (status & DLGC_WANTMESSAGE); CRect rc; CWindow wndPrev = wndDialog.GetNextDlgTabItem(lstReplace, TRUE); WIN32_OP_D( lstReplace.GetWindowRect(&rc) ); WIN32_OP_D( wndDialog.ScreenToClient(rc) ); WIN32_OP_D( lstReplace.DestroyWindow() ); WIN32_OP_D( this->Create(wndDialog, &rc, 0, 0, WS_EX_STATICEDGE, replaceControlID) ); if (wndPrev != NULL ) this->SetWindowPos(wndPrev, 0,0,0,0, SWP_NOSIZE | SWP_NOMOVE ); this->SetFont(wndDialog.GetFont()); } void CListControlImpl::defer(std::function f) { m_deferred.push_back( f ); if (!m_defferredMsgPending) { if ( PostMessage(MSG_EXEC_DEFERRED) ) m_defferredMsgPending = true; } } LRESULT CListControlImpl::OnExecDeferred(UINT, WPARAM, LPARAM) { for ( ;; ) { auto i = m_deferred.begin(); if ( i == m_deferred.end() ) break; auto op = std::move(*i); m_deferred.erase(i); // erase first, execute later - avoid erratic behavior if op alters the list op(); } m_defferredMsgPending = false; return 0; } // ======================================================================================== // Mouse wheel vs drag&drop hacks // Install MouseHookProc for the duration of DoDragDrop and handle the input from there // ======================================================================================== static HHOOK g_hook = NULL; static CListControlImpl * g_dragDropInstance = nullptr; LRESULT CALLBACK CListControlImpl::MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode == HC_ACTION && g_dragDropInstance != nullptr) { switch (wParam) { case WM_MOUSEWHEEL: case WM_MOUSEHWHEEL: g_dragDropInstance->MouseWheelFromHook((UINT)wParam, lParam); break; } } return CallNextHookEx(g_hook, nCode, wParam, lParam); } bool CListControlImpl::MouseWheelFromHook(UINT msg, LPARAM data) { MOUSEHOOKSTRUCTEX const * mhs = reinterpret_cast ( data ); if ( ::WindowFromPoint(mhs->pt) != m_hWnd ) return false; LRESULT dummyResult = 0; WPARAM wp = mhs->mouseData; LPARAM lp = MAKELPARAM( mhs->pt.x, mhs->pt.y ); // If we get here, m_suppressMouseWheel should be true per our DoDragDrop() pfc::vartoggle_t scope(m_suppressMouseWheel, false); this->ProcessWindowMessage( m_hWnd, msg, wp, lp, dummyResult ); return true; } HRESULT CListControlImpl::DoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect) { HRESULT ret = E_FAIL; // Should not get here with non null g_dragDropInstance - means we have a recursive call PFC_ASSERT(g_dragDropInstance == nullptr); if ( g_dragDropInstance == nullptr ) { // futureproofing: kill mouse wheel message processing if we get them delivered the regular way while this is in progress pfc::vartoggle_t scope(m_suppressMouseWheel, true); g_dragDropInstance = this; g_hook = SetWindowsHookEx(WH_MOUSE, MouseHookProc, NULL, GetCurrentThreadId()); try { ret = ::DoDragDrop(pDataObj, pDropSource, dwOKEffects, pdwEffect); } catch (...) { } g_dragDropInstance = nullptr; UnhookWindowsHookEx(pfc::replace_null_t(g_hook)); } return ret; } void CListControlImpl::OnKillFocus(CWindow) { if (m_captureProc) { ReleaseCapture(); m_captureProc = nullptr; } SetMsgHandled(FALSE); } void CListControlImpl::OnWindowPosChanged(LPWINDOWPOS arg) { if ( arg->flags & SWP_HIDEWINDOW ) { if (m_captureProc) { ReleaseCapture(); m_captureProc = nullptr; } } SetMsgHandled(FALSE); } void CListControlHeaderImpl::RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,size_t item, uint32_t bkColor) { if ( ! this->DelimitColumns() ) { __super::RenderItemBackground(p_dc, p_itemRect, item, bkColor); } else { auto cnt = this->GetColumnCount(); uint32_t x = 0; for( size_t walk = 0; walk < cnt; ) { auto span = this->GetSubItemSpan( item, walk ); PFC_ASSERT( span > 0 ); uint32_t width = 0; for( size_t walk2 = 0; walk2 < span; ++ walk2 ) { width += this->GetSubItemWidth( walk + walk2 ); } CRect rc = p_itemRect; rc.left = x; x += width; rc.right = x; __super::RenderItemBackground(p_dc, rc, item, bkColor); walk += span; } } }