comparison foosdk/sdk/libPPUI/CListControlHeaderImpl.cpp @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents
children
comparison
equal deleted inserted replaced
0:e9bb126753e7 1:20d02a178406
1 #include "stdafx.h"
2 #include "CListControl.h"
3 #include "CListControlHeaderImpl.h" // redundant but makes intelisense quit showing false errors
4 #include "CListControl-Cells.h"
5 #include "PaintUtils.h"
6 #include "GDIUtils.h"
7 #include "win32_utility.h"
8 #include "DarkMode.h"
9 #include <vssym32.h>
10
11 static constexpr int lineBelowHeaderCY = 1;
12
13 // Prevent erratic behavior of header control
14 static constexpr uint32_t columnWidthSanityLimit = 10000;
15
16 static uint32_t columWidthToPixels(uint32_t user) {
17 PFC_ASSERT(user <= CListControlHeaderImpl::columnWidthMax);
18 return user < columnWidthSanityLimit ? user : columnWidthSanityLimit;
19 }
20
21 static bool testDrawLineBelowHeader() {
22 // Win10
23 return IsWindows10OrGreater();
24 }
25
26 void CListControlHeaderImpl::OnThemeChangedPT() {
27 if (m_header) {
28 auto dark = GetDarkMode();
29 if (dark != m_headerDark) {
30 m_headerDark = dark;
31 DarkMode::ApplyDarkThemeCtrl(m_header, dark, L"ItemsView");
32 }
33 }
34 this->SetMsgHandled(FALSE);
35 }
36
37 void CListControlHeaderImpl::InitializeHeaderCtrl(DWORD flags) {
38 PFC_ASSERT(IsWindow());
39 PFC_ASSERT(!IsHeaderEnabled());
40 m_headerDark = false;
41 WIN32_OP_D( m_header.Create(*this,NULL,NULL,WS_CHILD | flags) != NULL );
42 m_header.SetFont( GetFont() );
43
44 InjectParentEraseHandler(m_header);
45
46 this->OnThemeChangedPT();
47
48 if (testDrawLineBelowHeader()) {
49 WIN32_OP_D( m_headerLine.Create( *this, NULL, NULL, WS_CHILD ) != NULL );
50 InjectParentEraseHandler(m_headerLine);
51 }
52
53 UpdateHeaderLayout();
54 }
55
56 void CListControlHeaderImpl::UpdateHeaderLayout() {
57 CRect client; WIN32_OP_D( GetClientRect(client) );
58 int cw_old = m_clientWidth;
59 m_clientWidth = client.Width();
60 if (IsHeaderEnabled()) {
61 auto rc = client;
62 rc.left -= GetViewOffset().x;
63 WINDOWPOS wPos = {};
64 HDLAYOUT layout = {&rc, &wPos};
65 if (m_header.Layout(&layout)) {
66 m_header.SetWindowPos(wPos.hwndInsertAfter,wPos.x,wPos.y,wPos.cx,wPos.cy,wPos.flags | SWP_SHOWWINDOW);
67 if (m_headerLine != NULL) m_headerLine.SetWindowPos(m_header, wPos.x, wPos.y + wPos.cy, wPos.cx, lineBelowHeaderCY, wPos.flags | SWP_SHOWWINDOW);
68 } else {
69 m_header.ShowWindow(SW_HIDE);
70 if (m_headerLine != NULL) m_headerLine.ShowWindow(SW_HIDE);
71 }
72 } else {
73 if ( cw_old != m_clientWidth) Invalidate();
74 }
75 }
76
77 int CListControlHeaderImpl::GetItemWidth() const {
78 if (IsHeaderEnabled()) return m_itemWidth;
79 else return m_clientWidth;
80 }
81
82 LRESULT CListControlHeaderImpl::OnSizePassThru(UINT,WPARAM,LPARAM) {
83 UpdateHeaderLayout();
84
85 ProcessAutoWidth();
86
87 SetMsgHandled(FALSE);
88 return 0;
89 }
90
91 void CListControlHeaderImpl::OnViewOriginChange(CPoint p_delta) {
92 TParent::OnViewOriginChange(p_delta);
93 if (p_delta.x != 0) UpdateHeaderLayout();
94 }
95
96 void CListControlHeaderImpl::SetHeaderFont(HFONT font) {
97 if (IsHeaderEnabled()) {
98 m_header.SetFont(font); UpdateHeaderLayout();
99 }
100 }
101
102 LRESULT CListControlHeaderImpl::OnHeaderCustomDraw(LPNMHDR hdr) {
103 LPNMCUSTOMDRAW nmcd = reinterpret_cast<LPNMCUSTOMDRAW>(hdr);
104 if ( m_header != NULL && this->GetDarkMode() && nmcd->hdr.hwndFrom == m_header) {
105 switch (nmcd->dwDrawStage)
106 {
107 case CDDS_PREPAINT:
108 return CDRF_NOTIFYITEMDRAW;
109 case CDDS_ITEMPREPAINT:
110 {
111 CDCHandle dc(nmcd->hdc);
112 dc.SetTextColor(0xdedede);
113 dc.SetBkColor(0x191919); // disregarded anyway
114 }
115 return CDRF_DODEFAULT;
116 }
117 }
118 SetMsgHandled(FALSE); return 0;
119 }
120
121 LRESULT CListControlHeaderImpl::OnDividerDoubleClick(int,LPNMHDR hdr,BOOL&) {
122 const NMHEADER * info = (const NMHEADER *) hdr;
123 if (info->iButton == 0) {
124 AutoColumnWidth((t_size)info->iItem);
125 }
126 return 0;
127 }
128
129 LRESULT CListControlHeaderImpl::OnHeaderItemClick(int,LPNMHDR p_hdr,BOOL&) {
130 const NMHEADER * info = (const NMHEADER *) p_hdr;
131 if (info->iButton == 0) {
132 OnColumnHeaderClick((t_uint32)info->iItem);
133 }
134 return 0;
135 }
136
137 LRESULT CListControlHeaderImpl::OnHeaderItemChanged(int,LPNMHDR p_hdr,BOOL&) {
138 const NMHEADER * info = (const NMHEADER*) p_hdr;
139 if (info->pitem->mask & (HDI_WIDTH | HDI_ORDER)) {
140 if(!m_ownColumnsChange) ProcessColumnsChange();
141 }
142 return 0;
143 }
144
145 LRESULT CListControlHeaderImpl::OnHeaderEndDrag(int,LPNMHDR hdr,BOOL&) {
146 NMHEADER * info = (NMHEADER*) hdr;
147 return OnColumnHeaderDrag(info->iItem,info->pitem->iOrder) ? TRUE : FALSE;
148 }
149
150 bool CListControlHeaderImpl::OnColumnHeaderDrag(t_size index, t_size newPos) {
151 index = GetSubItemOrder(index);
152 const t_size count = this->GetColumnCount();
153 if ( count == 0 ) return false;
154 std::vector<size_t> perm; perm.resize(count); pfc::create_move_items_permutation(&perm[0],count, pfc::bit_array_one(index), (int) newPos - (int) index );
155 std::vector<int> order, newOrder; order.resize(count); newOrder.resize(count);
156 WIN32_OP_D(m_header.GetOrderArray((int)count, &order[0]));
157 for(t_size walk = 0; walk < count; ++walk) newOrder[walk] = order[perm[walk]];
158 WIN32_OP_D(m_header.SetOrderArray((int)count, &newOrder[0]));
159 OnColumnsChanged();
160 return true;
161 }
162 t_size CListControlHeaderImpl::SubItemFromPointAbs(CPoint pt) const {
163
164 auto order = GetColumnOrderArray();
165 const t_size colCount = order.size();
166
167 size_t item;
168 if (! ItemFromPointAbs(pt, item ) ) item = SIZE_MAX;
169
170 long xWalk = 0;
171 for(t_size _walk = 0; _walk < colCount; ) {
172 const t_size walk = (t_size) order[_walk];
173
174 size_t span = 1;
175 if (item != SIZE_MAX) span = this->GetSubItemSpan(item, walk);
176
177 PFC_ASSERT( span == 1 || _walk == walk );
178
179 if ( walk + span > colCount ) span = colCount - walk;
180
181 long width = 0;
182 for( size_t sub = 0; sub < span; ++ sub ) {
183 width += (long)this->GetSubItemWidth(walk + sub);
184 }
185
186 if (xWalk + width > pt.x) return walk;
187 xWalk += width;
188 _walk += span;
189 }
190 return SIZE_MAX;
191 }
192
193 bool CListControlHeaderImpl::OnClickedSpecial(DWORD status, CPoint pt) {
194
195 const DWORD maskButtons = MK_LBUTTON | MK_RBUTTON | MK_MBUTTON | MK_XBUTTON1 | MK_XBUTTON2;
196
197 if ( (status & maskButtons) != MK_LBUTTON ) return false;
198
199 if (!GetCellTypeSupported()) return false;
200
201 size_t item; size_t subItem;
202 if (! this->GetItemAtPointAbsEx( this->PointClientToAbs(pt), item, subItem ) ) {
203 return false;
204 }
205
206 auto cellType = GetCellType(item, subItem);
207 if ( !CellTypeReactsToMouseOver( cellType ) ) return false;
208 auto rcHot = CellHotRect( item, subItem, cellType );
209 if (!rcHot.PtInRect( pt )) return false;
210
211 SetPressedItem(item, subItem);
212 mySetCapture([=](UINT msg, DWORD newStatus, CPoint pt) {
213
214 if (msg == WM_MOUSELEAVE) {
215 ClearPressedItem(); return false;
216 }
217
218 {
219 CRect rc = this->GetClientRectHook();
220 if (!rc.PtInRect(pt)) {
221 ClearPressedItem(); return false;
222 }
223 }
224
225 size_t newItem, newSubItem;
226 if (!this->GetItemAtPointAbsEx(this->PointClientToAbs(pt), newItem, newSubItem) || newItem != item || newSubItem != subItem) {
227 ClearPressedItem(); return false;
228 }
229
230 DWORD buttons = newStatus & maskButtons;
231 if (buttons == 0) {
232 // button released?
233 this->defer( [=] {
234 OnSubItemClicked(item, subItem, pt);
235 } );
236 ClearPressedItem(); return false;
237 }
238 if (buttons != MK_LBUTTON) {
239 // another button pressed?
240 ClearPressedItem(); return false;
241 }
242
243 return true;
244 });
245 return true;
246 }
247
248 bool CListControlHeaderImpl::CellTypeUsesSpecialHitTests( cellType_t ct ) {
249 if ( ct == nullptr ) return false;
250 return ct->SuppressRowSelect();
251 }
252
253 bool CListControlHeaderImpl::OnClickedSpecialHitTest(CPoint pt) {
254 if ( ! GetCellTypeSupported() ) return false;
255 auto ct = GetCellTypeAtPointAbs( PointClientToAbs( pt ) );
256 return CellTypeUsesSpecialHitTests(ct);
257 }
258
259 void CListControlHeaderImpl::OnItemClicked(t_size item, CPoint pt) {
260 t_size subItem = SubItemFromPointAbs( PointClientToAbs( pt ) );
261 if (subItem != SIZE_MAX) {
262 if ( this->GetCellTypeSupported() ) {
263 auto ct = this->GetCellType(item, subItem );
264 // we don't handle hyperlink & button clicks thru here
265 if (CellTypeUsesSpecialHitTests(ct)) return;
266 }
267 OnSubItemClicked(item, subItem, pt);
268 }
269 }
270
271 std::vector<int> CListControlHeaderImpl::GetColumnOrderArray() const {
272 const size_t cCount = this->GetColumnCount();
273 std::vector<int> order;
274 if ( cCount > 0 ) {
275 order.resize(cCount);
276 if (IsHeaderEnabled()) {
277 PFC_ASSERT(m_header.IsWindow());
278 PFC_ASSERT(m_header.GetItemCount() == (int)cCount);
279 WIN32_OP_D(m_header.GetOrderArray((int)cCount, &order[0]));
280 } else {
281 for (size_t c = 0; c < cCount; ++c) order[c] = (int)c;
282 }
283 }
284 return order;
285 }
286
287 void CListControlHeaderImpl::RenderItemText(t_size item,const CRect & itemRect,const CRect & updateRect,CDCHandle dc, bool allowColors) {
288
289 int xWalk = itemRect.left;
290 CRect subItemRect(itemRect);
291 auto order = GetColumnOrderArray();
292 const size_t cCount = order.size();
293 SelectObjectScope fontScope(dc,GetFont());
294 for(t_size _walk = 0; _walk < cCount; ) {
295 const t_size walk = order[_walk];
296
297 size_t span = GetSubItemSpan(item, walk);
298
299 PFC_ASSERT( walk == _walk || span == 1 );
300
301 int width = GetSubItemWidth(walk);
302
303 if ( span > 1 ) {
304 if ( walk + span > cCount ) span = cCount - walk;
305 for( size_t extraWalk = 1; extraWalk < span; ++ extraWalk ) {
306 width += GetSubItemWidth(walk + extraWalk);
307 }
308 }
309
310 subItemRect.left = xWalk; subItemRect.right = xWalk + width;
311 CRect test;
312 if (test.IntersectRect(itemRect, subItemRect)) {
313 CRect subUpdate;
314 if (subUpdate.IntersectRect(subItemRect, updateRect)) {
315 DCStateScope scope(dc);
316 if (dc.IntersectClipRect(subItemRect) != NULLREGION) {
317 RenderSubItemText(item, walk, subItemRect, subUpdate, dc, allowColors);
318 }
319 }
320 }
321 xWalk += width;
322
323 _walk += span;
324 }
325 }
326
327 t_size CListControlHeaderImpl::GetSubItemOrder(t_size subItem) const {
328 if ( ! IsHeaderEnabled( ) ) return subItem;
329 HDITEM hditem = {};
330 hditem.mask = HDI_ORDER;
331 WIN32_OP_D( m_header.GetItem( (int) subItem, &hditem ) );
332 return (t_size) hditem.iOrder;
333 }
334
335 size_t CListControlHeaderImpl::GetSubItemSpan(size_t row, size_t column) const {
336 (void)row; (void)column;
337 return 1;
338 }
339
340 uint32_t CListControlHeaderImpl::GetSubItemWidth(t_size subItem) const {
341 if ( ! IsHeaderEnabled( ) ) {
342 // Should be overridden for custom columns layout
343 PFC_ASSERT( GetColumnCount() == 1 );
344 PFC_ASSERT( subItem == 0 );
345 return GetItemWidth();
346 }
347
348 if ( subItem < m_colRuntime.size() ) return m_colRuntime[subItem].m_widthPixels;
349 PFC_ASSERT( !"bad column idx");
350 return 0;
351 }
352
353 int CListControlHeaderImpl::GetHeaderItemWidth( int which ) {
354 HDITEM hditem = {};
355 hditem.mask = HDI_WIDTH;
356 WIN32_OP_D( m_header.GetItem( which, &hditem) );
357 return hditem.cxy;
358 }
359
360 void CListControlHeaderImpl::OnColumnsChanged() {
361 if ( IsHeaderEnabled() ) {
362 for( size_t walk = 0; walk < m_colRuntime.size(); ++ walk ) {
363 m_colRuntime[walk].m_widthPixels = GetHeaderItemWidth( (int) walk );
364 }
365 RecalcItemWidth();
366 }
367 this->OnViewAreaChanged();
368 }
369
370 void CListControlHeaderImpl::ResetColumns(bool update) {
371 m_colRuntime.clear();
372 m_itemWidth = 0;
373 PFC_ASSERT(IsHeaderEnabled());
374 for(;;) {
375 int count = m_header.GetItemCount();
376 if (count <= 0) break;
377 m_header.DeleteItem(count - 1);
378 }
379 if (update) OnColumnsChanged();
380 }
381
382 void CListControlHeaderImpl::SetColumn( size_t which, const char * label, DWORD fmtFlags, bool updateView) {
383 PFC_ASSERT( IsHeaderEnabled() );
384 pfc::stringcvt::string_os_from_utf8 labelOS;
385
386 HDITEM item = {};
387
388 if (label != nullptr) {
389 if (which < m_colRuntime.size()) m_colRuntime[which].m_text = label;
390
391 labelOS.convert(label);
392 item.mask |= HDI_TEXT;
393 item.pszText = const_cast<TCHAR*>(labelOS.get_ptr());
394 }
395 if (fmtFlags != UINT32_MAX) {
396 item.mask |= HDI_FORMAT;
397 item.fmt = fmtFlags | HDF_STRING;
398 }
399
400 m_header.SetItem( (int) which, &item );
401
402 if (updateView) OnColumnsChanged();
403 }
404
405 void CListControlHeaderImpl::GetColumnText(size_t which, pfc::string_base & out) const {
406 if (which < m_colRuntime.size()) {
407 out = m_colRuntime[which].m_text.c_str();
408 } else {
409 out = "";
410 }
411 }
412
413 void CListControlHeaderImpl::ResizeColumn(t_size index, t_uint32 userWidth, bool updateView) {
414 PFC_ASSERT( IsHeaderEnabled() );
415 PFC_ASSERT( index < m_colRuntime.size() );
416 auto& rt = m_colRuntime[index];
417 rt.m_userWidth = userWidth;
418 if (rt.autoWidth()) {
419 this->ProcessAutoWidth();
420 } else {
421 auto widthPixels = columWidthToPixels(userWidth);
422 rt.m_widthPixels = widthPixels;
423 HDITEM item = {};
424 item.mask = HDI_WIDTH;
425 item.cxy = widthPixels;
426 { pfc::vartoggle_t<bool> scope(m_ownColumnsChange, true); m_header.SetItem((int)index, &item); }
427 RecalcItemWidth();
428 if (updateView) OnColumnsChanged();
429 }
430 }
431
432 void CListControlHeaderImpl::DeleteColumns( pfc::bit_array const & mask, bool updateView ) {
433 int nDeleted = 0;
434 const size_t oldCount = GetColumnCount();
435 mask.for_each(true, 0, oldCount, [&] (size_t idx) {
436 int iDelete = (int) idx - nDeleted;
437 bool bDeleted = m_header.DeleteItem( iDelete );
438 PFC_ASSERT( bDeleted );
439 if ( bDeleted ) ++ nDeleted;
440 } );
441
442 pfc::remove_mask_t( m_colRuntime, mask );
443
444 ColumnWidthFix();
445
446 if (updateView) {
447 OnColumnsChanged();
448 }
449
450 }
451
452 bool CListControlHeaderImpl::DeleteColumn(size_t index, bool update) {
453 PFC_ASSERT( IsHeaderEnabled() );
454
455 if (!m_header.DeleteItem( (int) index )) return false;
456
457 pfc::remove_mask_t( m_colRuntime, pfc::bit_array_one( index ) );
458
459 ColumnWidthFix();
460
461 if (update) {
462 OnColumnsChanged();
463 }
464
465 return true;
466 }
467
468 void CListControlHeaderImpl::SetSortIndicator( size_t whichColumn, bool isUp ) {
469 HeaderControl_SetSortIndicator( GetHeaderCtrl(), (int) whichColumn, isUp );
470 }
471
472 void CListControlHeaderImpl::ClearSortIndicator() {
473 HeaderControl_SetSortIndicator(GetHeaderCtrl(), -1, false);
474 }
475
476 bool CListControlHeaderImpl::HaveAutoWidthContentColumns() const {
477 for( auto i = m_colRuntime.begin(); i != m_colRuntime.end(); ++ i ) {
478 if ( i->autoWidthContent() ) return true;
479 }
480 return false;
481 }
482 bool CListControlHeaderImpl::HaveAutoWidthColumns() const {
483 for( auto i = m_colRuntime.begin(); i != m_colRuntime.end(); ++ i ) {
484 if ( i->autoWidth() ) return true;
485 }
486 return false;
487 }
488 void CListControlHeaderImpl::AddColumnEx( const char * label, uint32_t widthPixelsAt96DPI, DWORD fmtFlags, bool update ) {
489 uint32_t w = widthPixelsAt96DPI;
490 if ( w <= columnWidthMax ) {
491 w = MulDiv( w, m_dpi.cx, 96 );
492 }
493 AddColumn( label, w, fmtFlags, update );
494 }
495
496 void CListControlHeaderImpl::AddColumnDLU( const char * label, uint32_t widthDLU, DWORD fmtFlags, bool update ) {
497 uint32_t w = widthDLU;
498 if ( w <= columnWidthMax ) {
499 w = ::MapDialogWidth( GetParent(), w );
500 }
501 AddColumn( label, w, fmtFlags, update );
502 }
503 void CListControlHeaderImpl::AddColumnF( const char * label, float widthF, DWORD fmtFlags, bool update) {
504 uint32_t w = columnWidthMax;
505 if ( widthF >= 0 ) {
506 w = pfc::rint32( widthF * m_dpi.cx / 96.0f );
507 }
508 AddColumn( label, w, fmtFlags, update );
509 }
510 void CListControlHeaderImpl::AddColumn(const char * label, uint32_t userWidth, DWORD fmtFlags,bool update) {
511 // userWidth is either UINT32_MAX or desired width in pixels
512 PFC_ASSERT(IsWindow());
513 if (! IsHeaderEnabled( ) ) InitializeHeaderCtrl();
514
515 uint32_t widthPixels = 0;
516
517 pfc::stringcvt::string_os_from_utf8 labelOS(label);
518 HDITEM item = {};
519 item.mask = HDI_TEXT | HDI_FORMAT;
520 if ( userWidth <= columnWidthMax ) {
521 widthPixels = columWidthToPixels(userWidth);
522 item.cxy = widthPixels;
523 item.mask |= HDI_WIDTH;
524 }
525
526 item.pszText = const_cast<TCHAR*>(labelOS.get_ptr());
527 item.fmt = HDF_STRING | fmtFlags;
528 int iColumn;
529 WIN32_OP_D( (iColumn = m_header.InsertItem(m_header.GetItemCount(),&item) ) >= 0 );
530 colRuntime_t rt;
531 rt.m_text = label;
532 rt.m_userWidth = userWidth;
533 m_itemWidth += widthPixels;
534 rt.m_widthPixels = widthPixels;
535 m_colRuntime.push_back( std::move(rt) );
536
537 if (update) OnColumnsChanged();
538
539 ProcessAutoWidth();
540 }
541
542 float CListControlHeaderImpl::GetColumnWidthF(size_t subItem) const {
543 auto w = GetSubItemWidth(subItem);
544 return (float) w * 96.0f / (float)m_dpi.cx;
545 }
546
547 void CListControlHeaderImpl::RenderBackground(CDCHandle dc, CRect const& rc) {
548 __super::RenderBackground(dc,rc);
549 #if 0
550 if ( m_drawLineBelowHeader && IsHeaderEnabled()) {
551 CRect rcHeader;
552 if (m_header.GetWindowRect(rcHeader)) {
553 // Draw a grid line below header
554 int y = rcHeader.Height();
555 if ( y >= rc.top && y < rc.bottom ) {
556 CDCPen pen(dc, GridColor());
557 SelectObjectScope scope(dc, pen);
558 dc.MoveTo(rc.left, y);
559 dc.LineTo(rc.right, y);
560 }
561 }
562 }
563 #endif
564 }
565
566 CRect CListControlHeaderImpl::GetClientRectHook() const {
567 CRect rcClient = __super::GetClientRectHook();
568 if (m_header != NULL) {
569 PFC_ASSERT( m_header.IsWindow() );
570 CRect rcHeader;
571 if (m_header.GetWindowRect(rcHeader)) {
572 int h = rcHeader.Height();
573 if ( m_headerLine != NULL ) h += lineBelowHeaderCY;
574 rcClient.top = pfc::min_t(rcClient.bottom,rcClient.top + h);
575 }
576 }
577 return rcClient;
578 }
579
580 CRect CListControlHeaderImpl::GetItemTextRectHook(size_t, size_t, CRect const & rc) const {
581 return GetItemTextRect( rc );
582 }
583
584 CRect CListControlHeaderImpl::GetItemTextRect(const CRect & itemRect) const {
585 CRect rc ( itemRect );
586 rc.DeflateRect(GetColumnSpacing(),0);
587 return rc;
588 }
589
590 void CListControlHeaderImpl::SetColumnSort(t_size which, bool isUp) {
591 HeaderControl_SetSortIndicator(m_header,(int)which,isUp);
592 }
593
594 void CListControlHeaderImpl::SetColumnFormat(t_size which, DWORD format) {
595 HDITEM item = {};
596 item.mask = HDI_FORMAT;
597 item.fmt = HDF_STRING | format;
598 WIN32_OP_D( m_header.SetItem((int)which,&item) );
599 }
600
601 DWORD CListControlHeaderImpl::GetColumnFormat(t_size which) const {
602 if (!IsHeaderEnabled()) return HDF_LEFT;
603 HDITEM hditem = {};
604 hditem.mask = HDI_FORMAT;
605 WIN32_OP_D( m_header.GetItem( (int) which, &hditem) );
606 return hditem.fmt;
607 }
608
609 BOOL CListControlHeaderImpl::OnSetCursor(CWindow, UINT nHitTest, UINT message) {
610 (void)nHitTest;
611 if ( message != 0 && GetCellTypeSupported() ) {
612 CPoint pt( (LPARAM) GetMessagePos() );
613 WIN32_OP_D( ScreenToClient( &pt ) );
614 size_t item, subItem;
615 if (GetItemAtPointAbsEx(this->PointClientToAbs(pt), item, subItem)) {
616 auto ct = GetCellType(item, subItem);
617 if (CellTypeReactsToMouseOver(ct)) {
618 auto rc = CellHotRect(item, subItem, ct);
619 if (PtInRect(rc, pt)) {
620 auto cursor = ct->HotCursor();
621 if (cursor) {
622 SetCursor(cursor); return TRUE;
623 }
624 }
625 }
626 }
627 }
628 SetMsgHandled(FALSE); return FALSE;
629 }
630
631 bool CListControlHeaderImpl::GetItemAtPointAbsEx(CPoint pt, size_t & outItem, size_t & outSubItem) const {
632 size_t item, subItem;
633 if (ItemFromPointAbs(pt, item)) {
634 subItem = SubItemFromPointAbs(pt);
635 if (subItem != SIZE_MAX) {
636 outItem = item; outSubItem = subItem; return true;
637 }
638 }
639 return false;
640 }
641
642 CListControlHeaderImpl::cellType_t CListControlHeaderImpl::GetCellTypeAtPointAbs(CPoint pt) const {
643 size_t item, subItem;
644 if ( GetItemAtPointAbsEx( pt, item, subItem) ) {
645 return GetCellType( item, subItem );
646 }
647 return nullptr;
648 }
649
650 CListCell * CListControlHeaderImpl::GetCellType( size_t item, size_t subItem ) const {
651 (void)item; (void)subItem;
652 return &PFC_SINGLETON(CListCell_Text);
653 }
654
655 void CListControlHeaderImpl::RenderSubItemText(t_size item, t_size subItem,const CRect & subItemRect,const CRect & updateRect,CDCHandle dc, bool allowColors) {
656 (void)updateRect;
657 const auto cellType = GetCellType( item, subItem );
658 if ( cellType == nullptr ) {
659 return;
660 }
661 pfc::string_formatter label;
662 const bool bHaveText = GetSubItemText(item,subItem,label);
663 if (! bHaveText ) {
664 label = ""; //sanity
665 }
666
667 pfc::stringcvt::string_os_from_utf8_fast labelOS ( label );
668 CListCell::DrawContentArg_t arg;
669 arg.darkMode = this->GetDarkMode();
670 arg.hdrFormat = GetColumnFormat( subItem );
671 arg.subItemRect = subItemRect;
672 arg.dc = dc;
673 arg.text = labelOS.get_ptr();
674 arg.allowColors = allowColors;
675 bool bPressed;
676 if ( cellType->IsToggle() ) bPressed = this->GetCellCheckState(item, subItem);
677 else bPressed = (item == m_pressedItem) && (subItem == m_pressedSubItem);
678 bool bHot = (item == m_hotItem) && ( subItem == m_hotSubItem );
679 if ( bPressed ) arg.cellState |= CListCell::cellState_pressed;
680 if ( bHot ) arg.cellState|= CListCell::cellState_hot;
681 arg.rcText = GetItemTextRectHook(item, subItem, subItemRect);
682 arg.rcHot = CellHotRect(item, subItem, cellType, subItemRect);
683
684 auto strTheme = cellType->Theme();
685 if ( strTheme != nullptr ) {
686 arg.theme = themeFor( strTheme ).m_theme;
687 }
688 arg.colorHighlight = GetSysColorHook(colorHighlight);
689
690 arg.thisWnd = m_hWnd;
691
692 if (this->IsSubItemGrayed( item, subItem ) ) {
693 arg.cellState |= CListCell::cellState_disabled;
694 }
695
696 if (this->RenderCellImageTest(item, subItem)) {
697 arg.imageRenderer = [this, item, subItem](CDCHandle dc, const CRect& rc) { this->RenderCellImage(item, subItem, dc, rc); };
698 }
699
700 CFontHandle fontRestore;
701 CFont fontOverride;
702
703 LOGFONT data;
704 if (dc.GetCurrentFont().GetLogFont(&data)) {
705 if ( cellType->ApplyTextStyle( data, CellTextScale(item, subItem ), arg.cellState ) ) {
706 if (fontOverride.CreateFontIndirect( & data )) {
707 fontRestore = dc.SelectFont( fontOverride );
708 }
709 }
710 }
711 cellType->DrawContent( arg );
712
713 if ( fontRestore != NULL ) dc.SelectFont( fontRestore );
714 }
715
716 void CListControlHeaderImpl::RenderGroupHeaderText2(size_t baseItem,const CRect & headerRect,const CRect & updateRect,CDCHandle dc) {
717 (void)updateRect;
718 pfc::string_formatter label;
719 if (GetGroupHeaderText2(baseItem,label)) {
720 SelectObjectScope fontScope(dc,GetGroupHeaderFont());
721 pfc::stringcvt::string_os_from_utf8 cvt(label);
722 CRect contentRect(GetItemTextRect(headerRect));
723 dc.DrawText(cvt,(int)cvt.length(),contentRect,DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER | DT_LEFT );
724 SIZE txSize;
725 const int lineSpacing = contentRect.Height() / 2;
726 if (dc.GetTextExtent(cvt,(int)cvt.length(),&txSize)) {
727 if (txSize.cx + lineSpacing < contentRect.Width()) {
728 const CPoint center = contentRect.CenterPoint();
729 const CPoint pt1(contentRect.left + txSize.cx + lineSpacing, center.y), pt2(contentRect.right, center.y);
730 const COLORREF lineColor = PaintUtils::BlendColor(dc.GetTextColor(),dc.GetBkColor(),25);
731
732 #ifndef CListControl_ScrollWindowFix
733 #error FIXME CMemoryDC needed
734 #endif
735 PaintUtils::DrawSmoothedLine(dc, pt1, pt2, lineColor, 1.0 * (double)m_dpi.cy / 96.0);
736 }
737 }
738 }
739 }
740
741 uint32_t CListControlHeaderImpl::GetOptimalColumnWidthFixed(const char * fixedText, bool pad) const {
742 CWindowDC dc(*this);
743 SelectObjectScope fontScope(dc, GetFont());
744 GetOptimalWidth_Cache cache;
745 cache.m_dc = dc;
746 cache.m_stringTemp = fixedText;
747 uint32_t ret = cache.GetStringTempWidth();
748 if ( pad ) ret += this->GetColumnSpacing() * 2;
749 return ret;
750 }
751
752 t_uint32 CListControlHeaderImpl::GetOptimalSubItemWidth(t_size item, t_size subItem, GetOptimalWidth_Cache & cache) const {
753 const t_uint32 base = this->GetColumnSpacing() * 2;
754 if (GetSubItemText(item,subItem,cache.m_stringTemp)) {
755 return base + cache.GetStringTempWidth();
756 } else {
757 return base;
758 }
759 }
760
761 t_uint32 CListControlHeaderImpl::GetOptimalWidth_Cache::GetStringTempWidth() {
762 if (m_stringTemp.replace_string_ex(m_stringTempFixAmpersands, "&", "&&") > 0) {
763 m_convertTemp.convert(m_stringTempFixAmpersands);
764 } else {
765 m_convertTemp.convert(m_stringTemp);
766 }
767 return PaintUtils::TextOutColors_CalcWidth(m_dc, m_convertTemp);
768 }
769
770 t_uint32 CListControlHeaderImpl::GetOptimalColumnWidth(t_size which, GetOptimalWidth_Cache & cache) const {
771 const t_size totalItems = GetItemCount();
772 t_uint32 val = 0;
773 for(t_size item = 0; item < totalItems; ++item) {
774 pfc::max_acc( val, GetOptimalSubItemWidth( item, which, cache ) );
775 }
776 return val;
777 }
778
779 t_uint32 CListControlHeaderImpl::GetOptimalSubItemWidthSimple(t_size item, t_size subItem) const {
780 CWindowDC dc(*this);
781 SelectObjectScope fontScope(dc, GetFont() );
782 GetOptimalWidth_Cache cache;
783 cache.m_dc = dc;
784 return GetOptimalSubItemWidth(item, subItem, cache);
785 }
786
787 LRESULT CListControlHeaderImpl::OnKeyDown(UINT,WPARAM wp,LPARAM,BOOL& bHandled) {
788 switch(wp) {
789 case VK_ADD:
790 if (IsKeyPressed(VK_CONTROL)) {
791 AutoColumnWidths();
792 return 0;
793 }
794 break;
795 }
796 bHandled = FALSE;
797 return 0;
798 }
799
800 uint32_t CListControlHeaderImpl::GetOptimalColumnWidth(size_t colIndex) const {
801 CWindowDC dc(*this);
802 SelectObjectScope fontScope(dc, GetFont());
803 GetOptimalWidth_Cache cache;
804 cache.m_dc = dc;
805 uint32_t ret = 0;
806 const auto itemCount = GetItemCount();
807 for (t_size walk = 0; walk < itemCount; ++walk) {
808 pfc::max_acc(ret, GetOptimalSubItemWidth(walk, colIndex, cache));
809 }
810 return ret;
811 }
812
813 void CListControlHeaderImpl::AutoColumnWidths(const pfc::bit_array & mask, bool expandLast) {
814 PFC_ASSERT( IsHeaderEnabled() );
815 if (!IsHeaderEnabled()) return;
816 const t_size itemCount = GetItemCount();
817 if (itemCount == 0) return;
818 const t_size columnCount = (t_size) m_header.GetItemCount();
819 if (columnCount == 0) return;
820 pfc::array_t<t_uint32> widths; widths.set_size(columnCount); widths.fill_null();
821 {
822 CWindowDC dc(*this);
823 SelectObjectScope fontScope(dc,GetFont());
824 GetOptimalWidth_Cache cache;
825 cache.m_dc = dc;
826 for(t_size walk = 0; walk < itemCount; ++walk) {
827 for(t_size colWalk = mask.find_first(true,0,columnCount); colWalk < columnCount; colWalk = mask.find_next(true,colWalk,columnCount)) {
828 pfc::max_acc(widths[colWalk], GetOptimalSubItemWidth(walk,colWalk,cache));
829 }
830 }
831 }
832
833 // Enforce limit
834 for (auto& walk : widths) walk = columWidthToPixels(walk);
835
836 if (expandLast) {
837 uint32_t usedWidth = 0; size_t lastCol = SIZE_MAX;
838 pfc::array_t<int> order; order.set_size(columnCount);
839 WIN32_OP_D( m_header.GetOrderArray((int)columnCount,order.get_ptr()) );
840 for(size_t _walk = 0; _walk < columnCount; ++_walk) {
841 const size_t colWalk = (size_t) order[_walk];
842 PFC_ASSERT( colWalk < columnCount );
843 if (mask[colWalk]) {
844 lastCol = colWalk;
845 usedWidth += widths[colWalk];
846 } else {
847 usedWidth += GetSubItemWidth(colWalk);
848 }
849 }
850 if (lastCol != SIZE_MAX) {
851 t_uint32 clientWidth = this->GetClientRectHook().Width();
852 if (clientWidth > 0) --clientWidth; // $!@# scrollbar hack
853 if (usedWidth < clientWidth) {
854 widths[lastCol] += clientWidth - usedWidth;
855 }
856 }
857 }
858 for(t_size colWalk = mask.find_first(true,0,columnCount); colWalk < columnCount; colWalk = mask.find_next(true,colWalk,columnCount)) {
859 ResizeColumn(colWalk,widths[colWalk],false);
860 }
861 ProcessColumnsChange();
862 }
863
864 t_uint32 CListControlHeaderImpl::GetOptimalGroupHeaderWidth2(size_t baseItem) const {
865 CWindowDC dc(*this);
866 SelectObjectScope fontScope(dc,GetGroupHeaderFont());
867 GetOptimalWidth_Cache cache; cache.m_dc = dc;
868 const t_uint32 base = this->GetColumnSpacing() * 2;
869 if (GetGroupHeaderText2(baseItem,cache.m_stringTemp)) {
870 return base + cache.GetStringTempWidth();
871 } else {
872 return base;
873 }
874 }
875
876 size_t CListControlHeaderImpl::GetColumnCount() const {
877 if ( ! IsHeaderEnabled() ) return 1;
878 #if PFC_DEBUG
879 int iHeaderCount = m_header.GetItemCount();
880 PFC_ASSERT( m_colRuntime.size() == (size_t) iHeaderCount );
881 #endif
882 return m_colRuntime.size();
883 }
884
885 void CListControlHeaderImpl::ColumnWidthFix() {
886 if ( this->HaveAutoWidthColumns() ) {
887 ProcessAutoWidth();
888 } else {
889 RecalcItemWidth();
890 }
891 }
892
893 void CListControlHeaderImpl::ProcessAutoWidth() {
894 if ( HaveAutoWidthColumns() ) {
895 const int clientWidth = this->GetClientRectHook().Width();
896
897 if ( ! this->HaveAutoWidthContentColumns( ) && clientWidth == m_itemWidth) return;
898
899 const size_t count = GetColumnCount();
900 uint32_t totalNonAuto = 0;
901 size_t numAutoWidth = 0;
902 for(size_t walk = 0; walk < count; ++ walk ) {
903 if ( m_colRuntime[walk].autoWidth() ) {
904 ++ numAutoWidth;
905 } else {
906 totalNonAuto += GetSubItemWidth(walk);
907 }
908 }
909 int toDivide = clientWidth - totalNonAuto;
910 if ( toDivide < 0 ) toDivide = 0;
911
912 size_t numLeft = numAutoWidth;
913 auto worker = [&] ( size_t iCol ) {
914 auto & rt = m_colRuntime[iCol];
915 int lo = this->GetOptimalColumnWidthFixed( rt.m_text.c_str() );
916 if ( rt.autoWidthContent() ) {
917 int lo2 = this->GetOptimalColumnWidth( iCol );
918 if ( lo < lo2 ) lo = lo2;
919 }
920 int width = (int)(toDivide / numLeft);
921 if ( width < lo ) width = lo;
922
923 HDITEM item = {};
924 item.mask = HDI_WIDTH;
925 item.cxy = width;
926 WIN32_OP_D( m_header.SetItem( (int) iCol, &item ) );
927 rt.m_widthPixels = width;
928
929 if ( toDivide > width ) {
930 toDivide -= width;
931 } else {
932 toDivide = 0;
933 }
934 -- numLeft;
935
936 };
937 for( size_t iCol = 0; iCol < count; ++ iCol ) {
938 if (m_colRuntime[iCol].autoWidthContent() ) worker(iCol);
939 }
940 for( size_t iCol = 0; iCol < count; ++ iCol ) {
941 if ( m_colRuntime[iCol].autoWidthPlain() ) worker(iCol);
942 }
943
944 RecalcItemWidth();
945 OnColumnsChanged();
946 m_header.Invalidate();
947 }
948 }
949
950 void CListControlHeaderImpl::RecalcItemWidth() {
951 int total = 0;
952 const t_size count = GetColumnCount();
953 for(t_size walk = 0; walk < count; ++walk) total += GetSubItemWidth(walk);
954 m_itemWidth = total;
955 }
956
957 CRect CListControlHeaderImpl::GetSubItemRectAbs(t_size item,t_size subItem) const {
958 CRect rc = GetItemRectAbs(item);
959 auto order = GetColumnOrderArray();
960 const t_size colCount = order.size();
961 for(t_size _walk = 0; _walk < colCount; ++_walk) {
962 const t_size walk = (t_size) order[_walk];
963
964 t_size width = this->GetSubItemWidth(walk);
965 if (subItem == walk) {
966
967 size_t span = GetSubItemSpan(item, walk);
968 if ( walk + span > colCount ) span = colCount - walk;
969 for( size_t extra = 1; extra < span; ++ extra ) {
970 width += GetSubItemWidth( walk + extra);
971 }
972
973 rc.right = rc.left + (long)width;
974
975 return rc;
976 } else {
977 rc.left += (long)width;
978 }
979 }
980 throw pfc::exception_invalid_params();
981 }
982
983 CRect CListControlHeaderImpl::GetSubItemRect(t_size item,t_size subItem) const {
984 return RectAbsToClient(GetSubItemRectAbs(item, subItem));
985 }
986
987 void CListControlHeaderImpl::SetHotItem(size_t row, size_t column) {
988 if ( m_hotItem != row || m_hotSubItem != column ) {
989 const size_t total = GetItemCount();
990 if (m_hotItem < total) InvalidateRect(GetSubItemRect(m_hotItem, m_hotSubItem));
991 m_hotItem = row; m_hotSubItem = column;
992 if (m_hotItem < total) InvalidateRect(GetSubItemRect(m_hotItem, m_hotSubItem));
993 HotItemChanged(row, column);
994 }
995 }
996
997 void CListControlHeaderImpl::SetPressedItem(size_t row, size_t column) {
998 if (m_pressedItem != row || m_pressedSubItem != column) {
999 if (m_pressedItem != SIZE_MAX) InvalidateRect(GetSubItemRect(m_pressedItem, m_pressedSubItem));
1000 m_pressedItem = row; m_pressedSubItem = column;
1001 if (m_pressedItem != SIZE_MAX) InvalidateRect(GetSubItemRect(m_pressedItem, m_pressedSubItem));
1002 PressedItemChanged(row, column);
1003 }
1004 }
1005
1006 void CListControlHeaderImpl::SetCellCheckState(size_t item, size_t subItem, bool value) {
1007 ReloadItem(item); (void)subItem; (void)value;
1008 // Subclass deals with keeping track of state
1009 }
1010
1011 bool CListControlHeaderImpl::ToggleSelectedItemsHook(const pfc::bit_array & mask) {
1012 if (this->GetCellTypeSupported() ) {
1013 bool handled = false;
1014 bool setTo = true;
1015
1016 mask.walk(GetItemCount(), [&](size_t idx) {
1017 auto ct = this->GetCellType(idx, 0);
1018 if ( ct != nullptr && ct->IsToggle() ) {
1019 if ( ct->IsRadioToggle() ) {
1020 if (!handled) {
1021 handled = true;
1022 setTo = !this->GetCellCheckState(idx, 0);
1023 this->SetCellCheckState(idx, 0, setTo);
1024 }
1025 } else {
1026 if (!handled) {
1027 handled = true;
1028 setTo = ! this->GetCellCheckState(idx,0);
1029 }
1030 this->SetCellCheckState(idx,0,setTo);
1031 }
1032 }
1033 });
1034
1035 if (handled) return true;
1036 }
1037 return __super::ToggleSelectedItemsHook(mask);
1038 }
1039
1040 void CListControlHeaderImpl::OnSubItemClicked(t_size item, t_size subItem, CPoint pt) {
1041 auto ct = GetCellType(item, subItem);
1042 if (ct != nullptr) {
1043 if (ct->IsToggle()) {
1044 if (ct->HotRect(GetSubItemRect(item, subItem)).PtInRect(pt)) {
1045 this->SetCellCheckState(item, subItem, !GetCellCheckState(item, subItem));
1046 }
1047 } else if (ct->ClickToEdit()) {
1048 this->RequestEditItem(item, subItem);
1049 }
1050 }
1051 }
1052
1053
1054 bool CListControlHeaderImpl::AllowTypeFindInCell(size_t item, size_t subItem) const {
1055 auto cell = GetCellType( item, subItem );
1056 if ( cell == nullptr ) return false;
1057 return cell->AllowTypeFind();
1058 }
1059
1060 bool CListControlHeaderImpl::CellTypeReactsToMouseOver(cellType_t ct) {
1061 return ct != nullptr && ct->IsInteractive();
1062 }
1063
1064 CRect CListControlHeaderImpl::CellHotRect( size_t, size_t, cellType_t ct, CRect rcCell) {
1065 if ( ct != nullptr ) {
1066 return ct->HotRect(rcCell);
1067 }
1068 return rcCell;
1069 }
1070 CRect CListControlHeaderImpl::CellHotRect(size_t item, size_t subItem, cellType_t ct) {
1071 return CellHotRect( item, subItem, ct, GetSubItemRect( item, subItem ) );
1072 }
1073 void CListControlHeaderImpl::OnMouseMove(UINT nFlags, CPoint pt) {
1074 const DWORD maskButtons = MK_LBUTTON | MK_RBUTTON | MK_MBUTTON | MK_XBUTTON1 | MK_XBUTTON2;
1075 if (GetCellTypeSupported() && (nFlags & maskButtons) == 0 ) {
1076 size_t item; size_t subItem;
1077 if (this->GetItemAtPointAbsEx( PointClientToAbs(pt), item, subItem)) {
1078 auto ct = this->GetCellType( item, subItem );
1079 if (CellTypeReactsToMouseOver(ct) ) {
1080 auto rc = CellHotRect( item, subItem, ct );
1081 if ( PtInRect( rc, pt ) ) {
1082 const bool bTrack = ct != nullptr && ct->TracksMouseMove();
1083 SetHotItem(item, subItem);
1084
1085 if (bTrack) this->CellTrackMouseMove(item, subItem, WM_MOUSEMOVE, nFlags, pt);
1086
1087 mySetCapture([=](UINT msg, DWORD newStatus, CPoint pt) {
1088 if (msg == WM_MOUSELEAVE) {
1089 this->ClearHotItem();
1090 return false;
1091 }
1092
1093 if ((newStatus & maskButtons) != 0 || msg == WM_MOUSEWHEEL || msg == WM_MOUSEHWHEEL ) {
1094 // A button has been pressed or wheel has been moved
1095 this->ClearHotItem();
1096 mySetCaptureMsgHandled(FALSE);
1097 return false;
1098 }
1099
1100 if ( ! PtInRect( rc, pt ) || item >= GetItemCount() ) {
1101 // Left the rect
1102 this->ClearHotItem();
1103 this->mySetCaptureMsgHandled(FALSE);
1104 return false;
1105 }
1106 if (bTrack) {
1107 this->CellTrackMouseMove(item, subItem, msg, newStatus, pt);
1108 }
1109
1110 return true;
1111 });
1112 }
1113 }
1114 }
1115 }
1116 SetMsgHandled(FALSE);
1117 }
1118
1119 bool CListControlHeaderImpl::AllowScrollbar(bool vertical) const {
1120 if ( vertical ) {
1121 // vertical
1122 return true;
1123 } else {
1124 // horizontal
1125 if (! IsHeaderEnabled( ) ) return false; // no header?
1126 return true;
1127 }
1128 }
1129
1130 void CListControlHeaderImpl::OnDestroy() {
1131 m_colRuntime.clear();
1132 m_header = NULL;
1133 m_headerLine = NULL;
1134 m_headerDark = false;
1135 SetMsgHandled(FALSE);
1136 }
1137
1138
1139 uint32_t CListControlHeaderImpl::GetColumnsBlankWidth( size_t colExclude ) const {
1140 auto client = this->GetClientRectHook().Width();
1141 int item = GetItemWidth();
1142 if (colExclude) item -= GetSubItemWidth(colExclude);
1143 if ( item < 0 ) item = 0;
1144 if ( item < client ) return (uint32_t)( client - item );
1145 else return 0;
1146 }
1147
1148 void CListControlHeaderImpl::SizeColumnToContent( size_t which, uint32_t minWidth ) {
1149 auto width = this->GetOptimalColumnWidth( which );
1150 if ( width < minWidth ) width = minWidth;
1151 this->ResizeColumn( which, width );
1152 }
1153
1154 void CListControlHeaderImpl::SizeColumnToContentFillBlank( size_t which ) {
1155 this->SizeColumnToContent( which, this->GetColumnsBlankWidth(which) );
1156 }
1157
1158 HBRUSH CListControlHeaderImpl::OnCtlColorStatic(CDCHandle dc, CStatic wndStatic) {
1159 if ( wndStatic == m_headerLine ) {
1160 COLORREF col = GridColor();
1161 dc.SetDCBrushColor( col );
1162 return (HBRUSH) GetStockObject(DC_BRUSH);
1163 }
1164 SetMsgHandled(FALSE);
1165 return NULL;
1166 }
1167
1168 void CListControlHeaderImpl::ReloadData() {
1169 __super::ReloadData();
1170 if ( this->HaveAutoWidthContentColumns( ) ) {
1171 this->ColumnWidthFix();
1172 }
1173 }
1174
1175 void CListControlHeaderImpl::RenderGroupOverlay(size_t baseItem, const CRect& p_groupWhole, const CRect& p_updateRect, CDCHandle dc) {
1176 CRect subItemRect = p_groupWhole;
1177 t_uint32 xWalk = p_groupWhole.left;
1178 auto order = GetColumnOrderArray();
1179 const size_t cCount = order.size();
1180 for (t_size _walk = 0; _walk < cCount; ) {
1181 const t_size walk = order[_walk];
1182
1183 t_uint32 width = GetSubItemWidth(walk);
1184
1185 subItemRect.left = xWalk; subItemRect.right = xWalk + width;
1186 CRect subUpdate;
1187 if (subUpdate.IntersectRect(subItemRect, p_updateRect)) {
1188 DCStateScope scope(dc);
1189 if (dc.IntersectClipRect(subItemRect) != NULLREGION) {
1190 this->RenderGroupOverlayColumn(baseItem, walk, subItemRect, subUpdate, dc);
1191 }
1192 }
1193 xWalk += width;
1194
1195 _walk += 1;
1196 }
1197 }
1198
1199 void CListControlHeaderImpl::UpdateGroupOverlayColumnByID(groupID_t groupID, size_t subItem) {
1200 if (this->GetItemCount() == 0) return;
1201 CRect rc = this->GetSubItemRectAbs(0, subItem);
1202 this->UpdateGroupOverlayByID(groupID, rc.left, rc.right);
1203 }
1204
1205 void CListControlHeaderImpl::mySetCapture(CaptureProc_t proc) {
1206 this->m_captureProc = proc;
1207 this->TrackMouseLeave();
1208 }
1209
1210 void CListControlHeaderImpl::myReleaseCapture() {
1211 m_captureProc = nullptr;
1212 }
1213
1214 void CListControlHeaderImpl::TrackMouseLeave() {
1215 TRACKMOUSEEVENT tme = { sizeof(tme) };
1216 tme.dwFlags = TME_LEAVE;
1217 tme.hwndTrack = m_hWnd;
1218 TrackMouseEvent(&tme);
1219 }
1220
1221 LRESULT CListControlHeaderImpl::MousePassThru(UINT msg, WPARAM wp, LPARAM lp) {
1222 auto p = m_captureProc; // create local ref in case something in mid-captureproc clears it
1223 if (p) {
1224 CPoint pt(lp);
1225 if (!p(msg, (DWORD)wp, pt)) {
1226 myReleaseCapture();
1227 }
1228 return 0;
1229 }
1230
1231 SetMsgHandled(FALSE);
1232 return 0;
1233 }
1234
1235 void CListControlHeaderImpl::OnMouseLeave() {
1236 if (m_captureProc) {
1237 m_captureProc(WM_MOUSELEAVE, 0, -1);
1238 myReleaseCapture();
1239 }
1240 }
1241
1242 void CListControlHeaderImpl::OnKillFocus(CWindow) {
1243 myReleaseCapture();
1244 SetMsgHandled(FALSE);
1245 }
1246
1247 void CListControlHeaderImpl::OnWindowPosChanged(LPWINDOWPOS arg) {
1248 if (arg->flags & SWP_HIDEWINDOW) {
1249 myReleaseCapture();
1250 }
1251 SetMsgHandled(FALSE);
1252 }
1253
1254 void CListControlHeaderImpl::RequestEditItem(size_t, size_t) {
1255 PFC_ASSERT(!"Please enable CListControl_EditImpl");
1256 }
1257
1258 void CListControlHeaderImpl::OnLButtonDblClk(UINT nFlags, CPoint point) {
1259 (void)nFlags;
1260 if (this->GetCellTypeSupported()) {
1261 auto ptAbs = PointClientToAbs(point);
1262 size_t item = this->ItemFromPointAbs(ptAbs);
1263 size_t subItem = SubItemFromPointAbs(ptAbs);
1264 if (item != SIZE_MAX && subItem != SIZE_MAX) {
1265 auto ct = GetCellType(item, subItem);
1266 if (ct != nullptr) {
1267 if (ct->IsToggle()) {
1268 if (ct->HotRect(GetSubItemRect(item, subItem)).PtInRect(point)) {
1269 return; // disregard doubleclick on checkbox
1270 }
1271 }
1272 }
1273 }
1274 }
1275 SetMsgHandled(FALSE); // handle doubleclick
1276 }
1277
1278
1279 void CListControlHeaderImpl::RenderItemBackground(CDCHandle p_dc, const CRect& p_itemRect, size_t item, uint32_t bkColor) {
1280 if (!this->DelimitColumns()) {
1281 __super::RenderItemBackground(p_dc, p_itemRect, item, bkColor);
1282 } else {
1283 auto cnt = this->GetColumnCount();
1284 const auto order = this->GetColumnOrderArray();
1285 uint32_t x = p_itemRect.left;
1286 for (size_t walk = 0; walk < cnt; ) {
1287 const size_t sub = order[walk];
1288 auto span = this->GetSubItemSpan(item, sub);
1289 PFC_ASSERT(span > 0);
1290 uint32_t width = 0;
1291 for (size_t walk2 = 0; walk2 < span; ++walk2) {
1292 width += this->GetSubItemWidth(sub + walk2);
1293 }
1294 CRect rc = p_itemRect;
1295 rc.left = x;
1296 x += width;
1297 rc.right = x;
1298
1299 CRect test;
1300 if (test.IntersectRect(rc, p_itemRect)) {
1301 DCStateScope s(p_dc);
1302 if (p_dc.IntersectClipRect(rc) != NULLREGION) {
1303 __super::RenderItemBackground(p_dc, rc, item, bkColor);
1304 }
1305 }
1306 walk += span;
1307 }
1308 }
1309 }