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