#include "stdafx.h" #include "CListControl.h" #include "CListControlHeaderImpl.h" // redundant but makes intelisense quit showing false errors #include "CListControl-Cells.h" #include "PaintUtils.h" #include "GDIUtils.h" #include "win32_utility.h" enum { lineBelowHeaderCY = 1 }; static bool testDrawLineBelowHeader() { // Win10 return GetOSVersionCode() >= 0xA00; } void CListControlHeaderImpl::InitializeHeaderCtrl(DWORD flags) { PFC_ASSERT(!IsHeaderEnabled()); WIN32_OP_D( m_header.Create(*this,NULL,NULL,WS_CHILD | flags) != NULL ); m_header.SetFont( GetFont() ); if (testDrawLineBelowHeader()) { WIN32_OP_D( m_headerLine.Create( *this, NULL, NULL, WS_CHILD ) != NULL ); } UpdateHeaderLayout(); } void CListControlHeaderImpl::UpdateHeaderLayout() { CRect client; WIN32_OP_D( GetClientRect(client) ); m_clientWidth = client.Width(); if (IsHeaderEnabled()) { auto rc = client; rc.left -= GetViewOffset().x; WINDOWPOS wPos = {}; HDLAYOUT layout = {&rc, &wPos}; if (m_header.Layout(&layout)) { m_header.SetWindowPos(wPos.hwndInsertAfter,wPos.x,wPos.y,wPos.cx,wPos.cy,wPos.flags | SWP_SHOWWINDOW); if (m_headerLine != NULL) m_headerLine.SetWindowPos(m_header, wPos.x, wPos.y + wPos.cy, wPos.cx, lineBelowHeaderCY, wPos.flags | SWP_SHOWWINDOW); } else { m_header.ShowWindow(SW_HIDE); if (m_headerLine != NULL) m_headerLine.ShowWindow(SW_HIDE); } } } int CListControlHeaderImpl::GetItemWidth() const { if (IsHeaderEnabled()) return m_itemWidth; else return m_clientWidth; } LRESULT CListControlHeaderImpl::OnSizePassThru(UINT,WPARAM,LPARAM p_lp) { UpdateHeaderLayout(); ProcessAutoWidth(); SetMsgHandled(FALSE); return 0; } void CListControlHeaderImpl::OnViewOriginChange(CPoint p_delta) { TParent::OnViewOriginChange(p_delta); if (p_delta.x != 0) UpdateHeaderLayout(); } void CListControlHeaderImpl::SetHeaderFont(HFONT font) { if (IsHeaderEnabled()) { m_header.SetFont(font); UpdateHeaderLayout(); } } LRESULT CListControlHeaderImpl::OnDividerDoubleClick(int,LPNMHDR hdr,BOOL&) { const NMHEADER * info = (const NMHEADER *) hdr; if (info->iButton == 0) { AutoColumnWidth((t_size)info->iItem); } return 0; } LRESULT CListControlHeaderImpl::OnHeaderItemClick(int,LPNMHDR p_hdr,BOOL&) { const NMHEADER * info = (const NMHEADER *) p_hdr; if (info->iButton == 0) { OnColumnHeaderClick((t_uint32)info->iItem); } return 0; } LRESULT CListControlHeaderImpl::OnHeaderItemChanged(int,LPNMHDR p_hdr,BOOL&) { const NMHEADER * info = (const NMHEADER*) p_hdr; if (info->pitem->mask & (HDI_WIDTH | HDI_ORDER)) { if(!m_ownColumnsChange) ProcessColumnsChange(); } return 0; } LRESULT CListControlHeaderImpl::OnHeaderEndDrag(int,LPNMHDR hdr,BOOL&) { NMHEADER * info = (NMHEADER*) hdr; return OnColumnHeaderDrag(info->iItem,info->pitem->iOrder) ? TRUE : FALSE; } bool CListControlHeaderImpl::OnColumnHeaderDrag(t_size index, t_size newPos) { index = GetSubItemOrder(index); const t_size count = this->GetColumnCount(); if ( count == 0 ) return false; std::vector perm; perm.resize(count); pfc::create_move_items_permutation(&perm[0],count, pfc::bit_array_one(index), (int) newPos - (int) index ); std::vector order, newOrder; order.resize(count); newOrder.resize(count); WIN32_OP_D(m_header.GetOrderArray((int)count, &order[0])); for(t_size walk = 0; walk < count; ++walk) newOrder[walk] = order[perm[walk]]; WIN32_OP_D(m_header.SetOrderArray((int)count, &newOrder[0])); OnColumnsChanged(); return true; } t_size CListControlHeaderImpl::SubItemFromPointAbs(CPoint pt) const { auto order = GetColumnOrderArray(); const t_size colCount = order.size(); size_t item; if (! ItemFromPointAbs(pt, item ) ) item = pfc_infinite; long xWalk = 0; for(t_size _walk = 0; _walk < colCount; ) { const t_size walk = (t_size) order[_walk]; size_t span = 1; if (item != pfc_infinite) span = this->GetSubItemSpan(item, walk); PFC_ASSERT( span == 1 || _walk == walk ); if ( walk + span > colCount ) span = colCount - walk; long width = 0; for( size_t sub = 0; sub < span; ++ sub ) { width += (long)this->GetSubItemWidth(walk + sub); } if (xWalk + width > pt.x) return walk; xWalk += width; _walk += span; } return pfc_infinite; } bool CListControlHeaderImpl::OnClickedSpecial(DWORD status, CPoint pt) { const DWORD maskButtons = MK_LBUTTON | MK_RBUTTON | MK_MBUTTON | MK_XBUTTON1 | MK_XBUTTON2; if ( (status & maskButtons) != MK_LBUTTON ) return false; if (!GetCellTypeSupported()) return false; size_t item; size_t subItem; if (! this->GetItemAtPointAbsEx( pt + GetViewOffset(), item, subItem ) ) { return false; } bool bCapture = false; auto cellType = GetCellType(item, subItem); if ( !CellTypeReactsToMouseOver( cellType ) ) return false; auto rcHot = CellHotRect( item, subItem, cellType ); if (!rcHot.PtInRect( pt )) return false; SetPressedItem(item, subItem); SetCaptureEx([=](UINT, DWORD newStatus, CPoint pt) { { CRect rc = this->GetClientRectHook(); if (!rc.PtInRect(pt)) { ClearPressedItem(); return false; } } size_t newItem, newSubItem; if (!this->GetItemAtPointAbsEx(pt + GetViewOffset(), newItem, newSubItem) || newItem != item || newSubItem != subItem) { ClearPressedItem(); return false; } DWORD buttons = newStatus & maskButtons; if (buttons == 0) { // button released? this->defer( [=] { OnSubItemClicked(item, subItem, pt); } ); ClearPressedItem(); return false; } if (buttons != MK_LBUTTON) { // another button pressed? ClearPressedItem(); return false; } return true; }); return true; } bool CListControlHeaderImpl::CellTypeUsesSpecialHitTests( cellType_t ct ) { if ( ct == nullptr ) return false; return ct->SuppressRowSelect(); } bool CListControlHeaderImpl::OnClickedSpecialHitTest(CPoint pt) { if ( ! GetCellTypeSupported() ) return false; auto ct = GetCellTypeAtPointAbs( pt + GetViewOffset() ); return CellTypeUsesSpecialHitTests(ct); } void CListControlHeaderImpl::OnItemClicked(t_size item, CPoint pt) { t_size subItem = SubItemFromPointAbs(pt + GetViewOffset()); if (subItem != ~0) { if ( this->GetCellTypeSupported() ) { auto ct = this->GetCellType(item, subItem ); // we don't handle hyperlink & button clicks thru here if (CellTypeUsesSpecialHitTests(ct)) return; } OnSubItemClicked(item, subItem, pt); } } std::vector CListControlHeaderImpl::GetColumnOrderArray() const { const size_t cCount = this->GetColumnCount(); std::vector order; if ( cCount > 0 ) { order.resize(cCount); if (IsHeaderEnabled()) { WIN32_OP_D(m_header.GetOrderArray((int)cCount, &order[0])); } else { for (size_t c = 0; c < cCount; ++c) order[c] = (int)c; } } return order; } void CListControlHeaderImpl::RenderItemText(t_size item,const CRect & itemRect,const CRect & updateRect,CDCHandle dc, bool allowColors) { t_uint32 xWalk = itemRect.left; CRect subItemRect(itemRect); auto order = GetColumnOrderArray(); const size_t cCount = order.size(); SelectObjectScope fontScope(dc,GetFont()); for(t_size _walk = 0; _walk < cCount; ) { const t_size walk = order[_walk]; size_t span = GetSubItemSpan(item, walk); PFC_ASSERT( walk == _walk || span == 1 ); t_uint32 width = GetSubItemWidth(walk); if ( span > 1 ) { if ( walk + span > cCount ) span = cCount - walk; for( size_t extraWalk = 1; extraWalk < span; ++ extraWalk ) { width += GetSubItemWidth(walk + extraWalk); } } subItemRect.left = xWalk; subItemRect.right = xWalk + width; CRect subUpdate; if (subUpdate.IntersectRect(subItemRect, updateRect)) { DCStateScope scope(dc); if (dc.IntersectClipRect(subItemRect) != NULLREGION) { RenderSubItemText(item,walk,subItemRect,subUpdate,dc, allowColors); } } xWalk += width; _walk += span; } } t_size CListControlHeaderImpl::GetSubItemOrder(t_size subItem) const { if ( ! IsHeaderEnabled( ) ) return subItem; HDITEM hditem = {}; hditem.mask = HDI_ORDER; WIN32_OP_D( m_header.GetItem( (int) subItem, &hditem ) ); return (t_size) hditem.iOrder; } size_t CListControlHeaderImpl::GetSubItemSpan(size_t row, size_t column) const { return 1; } uint32_t CListControlHeaderImpl::GetSubItemWidth(t_size subItem) const { if ( ! IsHeaderEnabled( ) ) { // Should be overridden for custom columns layout PFC_ASSERT( GetColumnCount() == 1 ); PFC_ASSERT( subItem == 0 ); return GetItemWidth(); } if ( subItem < m_colRuntime.size() ) return m_colRuntime[subItem].m_widthPixels; PFC_ASSERT( !"bad column idx"); return 0; } int CListControlHeaderImpl::GetHeaderItemWidth( int which ) { HDITEM hditem = {}; hditem.mask = HDI_WIDTH; WIN32_OP_D( m_header.GetItem( which, &hditem) ); return hditem.cxy; } void CListControlHeaderImpl::OnColumnsChanged() { if ( IsHeaderEnabled() ) { for( size_t walk = 0; walk < m_colRuntime.size(); ++ walk ) { m_colRuntime[walk].m_widthPixels = GetHeaderItemWidth( (int) walk ); } RecalcItemWidth(); } this->OnViewAreaChanged(); } void CListControlHeaderImpl::ResetColumns(bool update) { m_colRuntime.clear(); m_itemWidth = 0; PFC_ASSERT(IsHeaderEnabled()); for(;;) { int count = m_header.GetItemCount(); if (count <= 0) break; m_header.DeleteItem(count - 1); } if (update) OnColumnsChanged(); } void CListControlHeaderImpl::SetColumn( size_t which, const char * label, DWORD fmtFlags, bool updateView) { PFC_ASSERT( IsHeaderEnabled() ); pfc::stringcvt::string_os_from_utf8 labelOS(label); HDITEM item = {}; item.mask = HDI_TEXT | HDI_FORMAT; item.fmt = fmtFlags | HDF_STRING; item.pszText = const_cast(labelOS.get_ptr()); m_header.SetItem( (int) which, &item ); if (which < m_colRuntime.size()) m_colRuntime[which].m_text = label; if (updateView) OnColumnsChanged(); } void CListControlHeaderImpl::GetColumnText(size_t which, pfc::string_base & out) const { if (which < m_colRuntime.size()) { out = m_colRuntime[which].m_text.c_str(); } else { out = ""; } } void CListControlHeaderImpl::ResizeColumn(t_size index, t_uint32 widthPixels, bool updateView) { PFC_ASSERT( IsHeaderEnabled() ); PFC_ASSERT( index < m_colRuntime.size() ); HDITEM item = {}; item.mask = HDI_WIDTH; item.cxy = widthPixels; { pfc::vartoggle_t scope(m_ownColumnsChange, true); m_header.SetItem( (int) index, &item ); } m_colRuntime[index].m_widthPixels = widthPixels; RecalcItemWidth(); if (updateView) OnColumnsChanged(); } void CListControlHeaderImpl::DeleteColumns( pfc::bit_array const & mask, bool updateView ) { int nDeleted = 0; const size_t oldCount = GetColumnCount(); mask.for_each(true, 0, oldCount, [&] (size_t idx) { int iDelete = (int) idx - nDeleted; bool bDeleted = m_header.DeleteItem( iDelete ); PFC_ASSERT( bDeleted ); if ( bDeleted ) ++ nDeleted; } ); pfc::remove_mask_t( m_colRuntime, mask ); ColumnWidthFix(); if (updateView) { OnColumnsChanged(); } } bool CListControlHeaderImpl::DeleteColumn(size_t index, bool update) { PFC_ASSERT( IsHeaderEnabled() ); if (!m_header.DeleteItem( (int) index )) return false; pfc::remove_mask_t( m_colRuntime, pfc::bit_array_one( index ) ); ColumnWidthFix(); if (update) { OnColumnsChanged(); } return true; } void CListControlHeaderImpl::SetSortIndicator( size_t whichColumn, bool isUp ) { HeaderControl_SetSortIndicator( GetHeaderCtrl(), (int) whichColumn, isUp ); } void CListControlHeaderImpl::ClearSortIndicator() { HeaderControl_SetSortIndicator(GetHeaderCtrl(), -1, false); } bool CListControlHeaderImpl::HaveAutoWidthContentColumns() const { for( auto i = m_colRuntime.begin(); i != m_colRuntime.end(); ++ i ) { if ( i->autoWidthContent() ) return true; } return false; } bool CListControlHeaderImpl::HaveAutoWidthColumns() const { for( auto i = m_colRuntime.begin(); i != m_colRuntime.end(); ++ i ) { if ( i->autoWidth() ) return true; } return false; } void CListControlHeaderImpl::AddColumnEx( const char * label, uint32_t widthPixelsAt96DPI, DWORD fmtFlags, bool update ) { uint32_t w = widthPixelsAt96DPI; if ( w <= columnWidthMax ) { w = MulDiv( w, m_dpi.cx, 96 ); } AddColumn( label, w, fmtFlags, update ); } void CListControlHeaderImpl::AddColumnDLU( const char * label, uint32_t widthDLU, DWORD fmtFlags, bool update ) { uint32_t w = widthDLU; if ( w <= columnWidthMax ) { w = ::MapDialogWidth( GetParent(), w ); } AddColumn( label, w, fmtFlags, update ); } void CListControlHeaderImpl::AddColumnF( const char * label, float widthF, DWORD fmtFlags, bool update) { uint32_t w = columnWidthMax; if ( widthF >= 0 ) { w = pfc::rint32( widthF * m_dpi.cx / 96.0f ); } AddColumn( label, w, fmtFlags, update ); } void CListControlHeaderImpl::AddColumn(const char * label, uint32_t width, DWORD fmtFlags,bool update) { if (! IsHeaderEnabled( ) ) InitializeHeaderCtrl(); pfc::stringcvt::string_os_from_utf8 labelOS(label); HDITEM item = {}; item.mask = HDI_TEXT | HDI_FORMAT; if ( width != UINT32_MAX ) { item.cxy = width; item.mask |= HDI_WIDTH; } item.pszText = const_cast(labelOS.get_ptr()); item.fmt = HDF_STRING | fmtFlags; int iColumn; WIN32_OP_D( (iColumn = m_header.InsertItem(m_header.GetItemCount(),&item) ) >= 0 ); colRuntime_t rt; rt.m_text = label; rt.m_userWidth = width; if ( width <= columnWidthMax ) { m_itemWidth += width; rt.m_widthPixels = width; } m_colRuntime.push_back( std::move(rt) ); if (update) OnColumnsChanged(); ProcessAutoWidth(); } float CListControlHeaderImpl::GetColumnWidthF(size_t subItem) const { auto w = GetSubItemWidth(subItem); return (float) w * 96.0f / (float)m_dpi.cx; } void CListControlHeaderImpl::RenderBackground(CDCHandle dc, CRect const& rc) { __super::RenderBackground(dc,rc); #if 0 if ( m_drawLineBelowHeader && IsHeaderEnabled()) { CRect rcHeader; if (m_header.GetWindowRect(rcHeader)) { // Draw a grid line below header int y = rcHeader.Height(); if ( y >= rc.top && y < rc.bottom ) { CDCPen pen(dc, GridColor()); SelectObjectScope scope(dc, pen); dc.MoveTo(rc.left, y); dc.LineTo(rc.right, y); } } } #endif } CRect CListControlHeaderImpl::GetClientRectHook() const { CRect rcClient = __super::GetClientRectHook(); if (m_header != NULL) { PFC_ASSERT( m_header.IsWindow() ); CRect rcHeader; if (m_header.GetWindowRect(rcHeader)) { int h = rcHeader.Height(); if ( m_headerLine != NULL ) h += lineBelowHeaderCY; rcClient.top = pfc::min_t(rcClient.bottom,rcClient.top + h); } } return rcClient; } CRect CListControlHeaderImpl::GetItemTextRectHook(size_t, size_t, CRect const & rc) const { return GetItemTextRect( rc ); } CRect CListControlHeaderImpl::GetItemTextRect(const CRect & itemRect) const { CRect rc ( itemRect ); rc.DeflateRect(GetColumnSpacing(),0); return rc; } void CListControlHeaderImpl::SetColumnSort(t_size which, bool isUp) { HeaderControl_SetSortIndicator(m_header,(int)which,isUp); } void CListControlHeaderImpl::SetColumnFormat(t_size which, DWORD format) { HDITEM item = {}; item.mask = HDI_FORMAT; item.fmt = HDF_STRING | format; WIN32_OP_D( m_header.SetItem((int)which,&item) ); } DWORD CListControlHeaderImpl::GetColumnFormat(t_size which) const { if (!IsHeaderEnabled()) return HDF_LEFT; HDITEM hditem = {}; hditem.mask = HDI_FORMAT; WIN32_OP_D( m_header.GetItem( (int) which, &hditem) ); return hditem.fmt; } BOOL CListControlHeaderImpl::OnSetCursor(CWindow wnd, UINT nHitTest, UINT message) { #if 0 // no longer meaningful since SetCapture on mouse over was added to track hot status if ( message != 0 && GetCellTypeSupported() ) { CPoint pt( (LPARAM) GetMessagePos() ); WIN32_OP_D( ScreenToClient( &pt ) ); if ( GetCellTypeAtPointAbs( pt + GetViewOffset() ) == cell_hyperlink ) { SetCursor(LoadCursor(NULL, IDC_HAND)); return TRUE; } } #endif SetMsgHandled(FALSE); return FALSE; } bool CListControlHeaderImpl::GetItemAtPointAbsEx(CPoint pt, size_t & outItem, size_t & outSubItem) const { size_t item, subItem; if (ItemFromPointAbs(pt, item)) { subItem = SubItemFromPointAbs(pt); if (subItem != pfc_infinite) { outItem = item; outSubItem = subItem; return true; } } return false; } CListControlHeaderImpl::cellType_t CListControlHeaderImpl::GetCellTypeAtPointAbs(CPoint pt) const { size_t item, subItem; if ( GetItemAtPointAbsEx( pt, item, subItem) ) { return GetCellType( item, subItem ); } return nullptr; } void CListControlHeaderImpl::RenderSubItemTextInternal(t_size subItem, const CRect & subItemRect, CDCHandle dc, const char * text, bool allowColors) { pfc::stringcvt::string_os_from_utf8 cvt(text); CRect clip = GetItemTextRect(subItemRect); const t_uint32 format = PaintUtils::DrawText_TranslateHeaderAlignment(GetColumnFormat(subItem)); if (true) { const t_uint32 bk = dc.GetBkColor(); const t_uint32 fg = dc.GetTextColor(); const t_uint32 hl = (allowColors ? GetSysColorHook(colorHighlight) : fg); const t_uint32 colors[3] = {PaintUtils::BlendColor(bk, fg, 33), fg, hl}; PaintUtils::TextOutColorsEx(dc, cvt, clip, format, colors); dc.SetTextColor(fg); } else { dc.DrawText(cvt,(int)cvt.length(),clip,DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER | format ); } } CListCell * CListControlHeaderImpl::GetCellType( size_t item, size_t subItem ) const { return &PFC_SINGLETON(CListCell_Text); } void CListControlHeaderImpl::RenderSubItemText(t_size item, t_size subItem,const CRect & subItemRect,const CRect & updateRect,CDCHandle dc, bool allowColors) { const auto cellType = GetCellType( item, subItem ); if ( cellType == nullptr ) { PFC_ASSERT( !"Should not get here" ); return; } pfc::string_formatter label; const bool bHaveText = GetSubItemText(item,subItem,label); if (! bHaveText ) { label = ""; //sanity } pfc::stringcvt::string_os_from_utf8_fast labelOS ( label ); CListCell::DrawContentArg_t arg; arg.hdrFormat = GetColumnFormat( subItem ); arg.subItemRect = subItemRect; arg.dc = dc; arg.text = labelOS.get_ptr(); arg.allowColors = allowColors; bool bPressed; if ( cellType->IsToggle() ) bPressed = this->GetCellCheckState(item, subItem); else bPressed = (item == m_pressedItem) && (subItem == m_pressedSubItem); bool bHot = (item == m_hotItem) && ( subItem == m_hotSubItem ); if ( bPressed ) arg.cellState |= CListCell::cellState_pressed; if ( bHot ) arg.cellState|= CListCell::cellState_hot; arg.rcText = GetItemTextRectHook(item, subItem, subItemRect); arg.rcHot = CellHotRect(item, subItem, cellType, subItemRect); auto strTheme = cellType->Theme(); if ( strTheme != nullptr ) { arg.theme = themeFor( strTheme ).m_theme; } arg.colorHighlight = GetSysColorHook(colorHighlight); arg.thisWnd = m_hWnd; if (this->IsSubItemGrayed( item, subItem ) ) { arg.cellState |= CListCell::cellState_disabled; } CFontHandle fontRestore; CFont fontOverride; LOGFONT data; if (dc.GetCurrentFont().GetLogFont(&data)) { if ( cellType->ApplyTextStyle( data, CellTextScale(item, subItem ), arg.cellState ) ) { if (fontOverride.CreateFontIndirect( & data )) { fontRestore = dc.SelectFont( fontOverride ); } } } cellType->DrawContent( arg ); if ( fontRestore != NULL ) dc.SelectFont( fontRestore ); } void CListControlHeaderImpl::RenderGroupHeaderText(int id,const CRect & headerRect,const CRect & updateRect,CDCHandle dc) { pfc::string_formatter label; if (GetGroupHeaderText(id,label)) { SelectObjectScope fontScope(dc,GetGroupHeaderFont()); pfc::stringcvt::string_os_from_utf8 cvt(label); CRect contentRect(GetItemTextRect(headerRect)); dc.DrawText(cvt,(int)cvt.length(),contentRect,DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER | DT_LEFT ); SIZE txSize; const int lineSpacing = contentRect.Height() / 2; if (dc.GetTextExtent(cvt,(int)cvt.length(),&txSize)) { if (txSize.cx + lineSpacing < contentRect.Width()) { const CPoint center = contentRect.CenterPoint(); const CPoint pt1(contentRect.left + txSize.cx + lineSpacing, center.y), pt2(contentRect.right, center.y); const COLORREF lineColor = PaintUtils::BlendColor(dc.GetTextColor(),dc.GetBkColor(),25); #ifndef CListControl_ScrollWindowFix #error FIXME CMemoryDC needed #endif PaintUtils::DrawSmoothedLine(dc, pt1, pt2, lineColor, 1.0 * (double)m_dpi.cy / 96.0); } } } } uint32_t CListControlHeaderImpl::GetOptimalColumnWidthFixed(const char * fixedText) const { CWindowDC dc(*this); SelectObjectScope fontScope(dc, GetFont()); GetOptimalWidth_Cache cache; cache.m_dc = dc; cache.m_stringTemp = fixedText; return cache.GetStringTempWidth() + this->GetColumnSpacing() * 2; } t_uint32 CListControlHeaderImpl::GetOptimalSubItemWidth(t_size item, t_size subItem, GetOptimalWidth_Cache & cache) const { const t_uint32 base = this->GetColumnSpacing() * 2; if (GetSubItemText(item,subItem,cache.m_stringTemp)) { return base + cache.GetStringTempWidth(); } else { return base; } } t_uint32 CListControlHeaderImpl::GetOptimalWidth_Cache::GetStringTempWidth() { if (m_stringTemp.replace_string_ex(m_stringTempUnfuckAmpersands, "&", "&&") > 0) { m_convertTemp.convert(m_stringTempUnfuckAmpersands); } else { m_convertTemp.convert(m_stringTemp); } return PaintUtils::TextOutColors_CalcWidth(m_dc, m_convertTemp); } t_uint32 CListControlHeaderImpl::GetOptimalColumnWidth(t_size which, GetOptimalWidth_Cache & cache) const { const t_size totalItems = GetItemCount(); t_uint32 val = 0; for(t_size item = 0; item < totalItems; ++item) { pfc::max_acc( val, GetOptimalSubItemWidth( item, which, cache ) ); } return val; } t_uint32 CListControlHeaderImpl::GetOptimalSubItemWidthSimple(t_size item, t_size subItem) const { CWindowDC dc(*this); SelectObjectScope fontScope(dc, GetFont() ); GetOptimalWidth_Cache cache; cache.m_dc = dc; return GetOptimalSubItemWidth(item, subItem, cache); } LRESULT CListControlHeaderImpl::OnKeyDown(UINT,WPARAM wp,LPARAM,BOOL& bHandled) { switch(wp) { case VK_ADD: if (IsKeyPressed(VK_CONTROL)) { AutoColumnWidths(); return 0; } break; } bHandled = FALSE; return 0; } uint32_t CListControlHeaderImpl::GetOptimalColumnWidth(size_t colIndex) const { CWindowDC dc(*this); SelectObjectScope fontScope(dc, GetFont()); GetOptimalWidth_Cache cache; cache.m_dc = dc; uint32_t ret = 0; const auto itemCount = GetItemCount(); for (t_size walk = 0; walk < itemCount; ++walk) { pfc::max_acc(ret, GetOptimalSubItemWidth(walk, colIndex, cache)); } return ret; } void CListControlHeaderImpl::AutoColumnWidths(const pfc::bit_array & mask, bool expandLast) { PFC_ASSERT( IsHeaderEnabled() ); if (!IsHeaderEnabled()) return; const t_size itemCount = GetItemCount(); if (itemCount == 0) return; const t_size columnCount = (t_size) m_header.GetItemCount(); if (columnCount == 0) return; pfc::array_t widths; widths.set_size(columnCount); widths.fill_null(); { CWindowDC dc(*this); SelectObjectScope fontScope(dc,GetFont()); GetOptimalWidth_Cache cache; cache.m_dc = dc; for(t_size walk = 0; walk < itemCount; ++walk) { for(t_size colWalk = mask.find_first(true,0,columnCount); colWalk < columnCount; colWalk = mask.find_next(true,colWalk,columnCount)) { pfc::max_acc(widths[colWalk], GetOptimalSubItemWidth(walk,colWalk,cache)); } } } if (expandLast) { uint32_t usedWidth = 0; size_t lastCol = SIZE_MAX; pfc::array_t order; order.set_size(columnCount); WIN32_OP_D( m_header.GetOrderArray((int)columnCount,order.get_ptr()) ); for(size_t _walk = 0; _walk < columnCount; ++_walk) { const size_t colWalk = (size_t) order[_walk]; PFC_ASSERT( colWalk < columnCount ); if (mask[colWalk]) { lastCol = colWalk; usedWidth += widths[colWalk]; } else { usedWidth += GetSubItemWidth(colWalk); } } if (lastCol != SIZE_MAX) { t_uint32 clientWidth = this->GetClientRectHook().Width(); if (clientWidth > 0) --clientWidth; // $!@# scrollbar hack if (usedWidth < clientWidth) { widths[lastCol] += clientWidth - usedWidth; } } } for(t_size colWalk = mask.find_first(true,0,columnCount); colWalk < columnCount; colWalk = mask.find_next(true,colWalk,columnCount)) { ResizeColumn(colWalk,widths[colWalk],false); } ProcessColumnsChange(); } t_uint32 CListControlHeaderImpl::GetOptimalGroupHeaderWidth(int which) const { CWindowDC dc(*this); SelectObjectScope fontScope(dc,GetGroupHeaderFont()); GetOptimalWidth_Cache cache; cache.m_dc = dc; const t_uint32 base = this->GetColumnSpacing() * 2; if (GetGroupHeaderText(which,cache.m_stringTemp)) { return base + cache.GetStringTempWidth(); } else { return base; } } size_t CListControlHeaderImpl::GetColumnCount() const { if ( ! IsHeaderEnabled() ) return 1; #if PFC_DEBUG int iHeaderCount = m_header.GetItemCount(); PFC_ASSERT( m_colRuntime.size() == (size_t) iHeaderCount ); #endif return m_colRuntime.size(); } void CListControlHeaderImpl::ColumnWidthFix() { if ( this->HaveAutoWidthColumns() ) { ProcessAutoWidth(); } else { RecalcItemWidth(); } } void CListControlHeaderImpl::ProcessAutoWidth() { if ( HaveAutoWidthColumns() ) { const int clientWidth = this->GetClientRectHook().Width(); if ( ! this->HaveAutoWidthContentColumns( ) && clientWidth == m_itemWidth) return; const size_t count = GetColumnCount(); uint32_t totalNonAuto = 0; size_t numAutoWidth = 0; for(size_t walk = 0; walk < count; ++ walk ) { if ( m_colRuntime[walk].autoWidth() ) { ++ numAutoWidth; } else { totalNonAuto += GetSubItemWidth(walk); } } int toDivide = clientWidth - totalNonAuto; if ( toDivide < 0 ) toDivide = 0; size_t numLeft = numAutoWidth; auto worker = [&] ( size_t iCol ) { auto & rt = m_colRuntime[iCol]; int lo = this->GetOptimalColumnWidthFixed( rt.m_text.c_str() ); if ( rt.autoWidthContent() ) { int lo2 = this->GetOptimalColumnWidth( iCol ); if ( lo < lo2 ) lo = lo2; } int width = (int)(toDivide / numLeft); if ( width < lo ) width = lo; HDITEM item = {}; item.mask = HDI_WIDTH; item.cxy = width; WIN32_OP_D( m_header.SetItem( iCol, &item ) ); rt.m_widthPixels = width; if ( toDivide > width ) { toDivide -= width; } else { toDivide = 0; } -- numLeft; }; for( size_t iCol = 0; iCol < count; ++ iCol ) { if (m_colRuntime[iCol].autoWidthContent() ) worker(iCol); } for( size_t iCol = 0; iCol < count; ++ iCol ) { if ( m_colRuntime[iCol].autoWidthPlain() ) worker(iCol); } RecalcItemWidth(); OnColumnsChanged(); m_header.Invalidate(); } } void CListControlHeaderImpl::RecalcItemWidth() { int total = 0; const t_size count = GetColumnCount(); for(t_size walk = 0; walk < count; ++walk) total += GetSubItemWidth(walk); m_itemWidth = total; } CRect CListControlHeaderImpl::GetSubItemRectAbs(t_size item,t_size subItem) const { CRect rc = GetItemRectAbs(item); auto order = GetColumnOrderArray(); const t_size colCount = order.size(); for(t_size _walk = 0; _walk < colCount; ++_walk) { const t_size walk = (t_size) order[_walk]; t_size width = this->GetSubItemWidth(walk); if (subItem == walk) { size_t span = GetSubItemSpan(item, walk); if ( walk + span > colCount ) span = colCount - walk; for( size_t extra = 1; extra < span; ++ extra ) { width += GetSubItemWidth( walk + extra); } rc.right = rc.left + (long)width; return rc; } else { rc.left += (long)width; } } throw pfc::exception_invalid_params(); } CRect CListControlHeaderImpl::GetSubItemRect(t_size item,t_size subItem) const { CRect rc = GetSubItemRectAbs(item,subItem); rc.OffsetRect(-GetViewOffset()); return rc; } void CListControlHeaderImpl::SetHotItem(size_t row, size_t column) { if ( m_hotItem != row || m_hotSubItem != column ) { if (m_hotItem != pfc_infinite) InvalidateRect(GetSubItemRect(m_hotItem, m_hotSubItem)); m_hotItem = row; m_hotSubItem = column; if (m_hotItem != pfc_infinite) InvalidateRect(GetSubItemRect(m_hotItem, m_hotSubItem)); } } void CListControlHeaderImpl::SetPressedItem(size_t row, size_t column) { if (m_pressedItem != row || m_pressedSubItem != column) { if (m_pressedItem != pfc_infinite) InvalidateRect(GetSubItemRect(m_pressedItem, m_pressedSubItem)); m_pressedItem = row; m_pressedSubItem = column; if (m_pressedItem != pfc_infinite) InvalidateRect(GetSubItemRect(m_pressedItem, m_pressedSubItem)); } } void CListControlHeaderImpl::SetCellCheckState(size_t item, size_t subItem, bool value) { ReloadItem(item); (void)subItem; (void)value; // Subclass deals with keeping track of state } bool CListControlHeaderImpl::ToggleSelectedItemsHook(const pfc::bit_array & mask) { if (this->GetCellTypeSupported() ) { bool handled = false; bool setTo = true; mask.walk(GetItemCount(), [&](size_t idx) { auto ct = this->GetCellType(idx, 0); if ( ct != nullptr && ct->IsToggle() ) { if ( ct->IsRadioToggle() ) { if (!handled) { handled = true; setTo = !this->GetCellCheckState(idx, 0); this->SetCellCheckState(idx, 0, setTo); } } else { if (!handled) { handled = true; setTo = ! this->GetCellCheckState(idx,0); } this->SetCellCheckState(idx,0,setTo); } } }); if (handled) return true; } return __super::ToggleSelectedItemsHook(mask); } void CListControlHeaderImpl::OnSubItemClicked(t_size item, t_size subItem, CPoint pt) { auto ct = GetCellType(item, subItem); if ( ct != nullptr && ct->IsToggle() ) { if ( ct->HotRect(GetSubItemRect(item, subItem)).PtInRect(pt) ) { this->SetCellCheckState( item, subItem, ! GetCellCheckState( item, subItem ) ); } } } bool CListControlHeaderImpl::AllowTypeFindInCell(size_t item, size_t subItem) const { auto cell = GetCellType( item, subItem ); if ( cell == nullptr ) return false; return cell->AllowTypeFind(); } bool CListControlHeaderImpl::CellTypeReactsToMouseOver(cellType_t ct) { return ct != nullptr && ct->IsInteractive(); } CRect CListControlHeaderImpl::CellHotRect( size_t, size_t, cellType_t ct, CRect rcCell) { if ( ct != nullptr ) { return ct->HotRect(rcCell); } return rcCell; } CRect CListControlHeaderImpl::CellHotRect(size_t item, size_t subItem, cellType_t ct) { return CellHotRect( item, subItem, ct, GetSubItemRect( item, subItem ) ); } void CListControlHeaderImpl::OnMouseMove(UINT nFlags, CPoint pt) { const DWORD maskButtons = MK_LBUTTON | MK_RBUTTON | MK_MBUTTON | MK_XBUTTON1 | MK_XBUTTON2; if (GetCellTypeSupported() && (nFlags & maskButtons) == 0 ) { size_t item; size_t subItem; if (this->GetItemAtPointAbsEx(pt + GetViewOffset(), item, subItem)) { auto ct = this->GetCellType( item, subItem ); if (CellTypeReactsToMouseOver(ct) ) { auto rc = CellHotRect( item, subItem, ct ); if ( PtInRect( rc, pt ) ) { { auto c = ct->HotCursor(); if ( c != NULL ) SetCursor(c); } SetHotItem(item, subItem); SetCaptureEx([=](UINT msg, DWORD newStatus, CPoint pt) { if ((newStatus & maskButtons) != 0 || msg == WM_MOUSEWHEEL || msg == WM_MOUSEHWHEEL ) { // A button has been pressed or wheel has been moved this->ClearHotItem(); SetCaptureMsgHandled(FALSE); return false; } if ( ! PtInRect( rc, pt ) ) { // Left the rect this->ClearHotItem(); SetCaptureMsgHandled(FALSE); return false; } return true; }); } } } } SetMsgHandled(FALSE); } bool CListControlHeaderImpl::AllowScrollbar(bool vertical) const { if ( vertical ) { // vertical return true; } else { // horizontal if (! IsHeaderEnabled( ) ) return false; // no header? return true; } } void CListControlHeaderImpl::OnDestroy() { m_colRuntime.clear(); m_header = NULL; m_headerLine = NULL; SetMsgHandled(FALSE); } uint32_t CListControlHeaderImpl::GetColumnsBlankWidth( size_t colExclude ) const { auto client = this->GetClientRectHook().Width(); int item = GetItemWidth(); if (colExclude) item -= GetSubItemWidth(colExclude); if ( item < 0 ) item = 0; if ( item < client ) return (uint32_t)( client - item ); else return 0; } void CListControlHeaderImpl::SizeColumnToContent( size_t which, uint32_t minWidth ) { auto width = this->GetOptimalColumnWidth( which ); if ( width < minWidth ) width = minWidth; this->ResizeColumn( which, width ); } void CListControlHeaderImpl::SizeColumnToContentFillBlank( size_t which ) { this->SizeColumnToContent( which, this->GetColumnsBlankWidth(which) ); } HBRUSH CListControlHeaderImpl::OnCtlColorStatic(CDCHandle dc, CStatic wndStatic) { if ( wndStatic == m_headerLine ) { COLORREF col = GridColor(); dc.SetDCBrushColor( col ); return (HBRUSH) GetStockObject(DC_BRUSH); } SetMsgHandled(FALSE); return NULL; } void CListControlHeaderImpl::ReloadData() { __super::ReloadData(); if ( this->HaveAutoWidthContentColumns( ) ) { this->ColumnWidthFix(); } }