Files
foobar2000-sdk/libPPUI/CListControl.cpp
2021-12-14 00:28:25 -07:00

990 lines
30 KiB
C++

#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<int,int>,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<comparator_rect>::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<comparator_rect>::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<comparator_rect>::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<void() > 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<MOUSEHOOKSTRUCTEX const *> ( 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<bool> 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<bool> 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;
}
}
}