// OwnerDrawnListControl.cpp - Implementation of COwnerDrawnListItem and COwnerDrawnListControl // // WinDirStat - Directory Statistics // Copyright (C) 2003-2005 Bernhard Seifert // Copyright (C) 2004-2017 WinDirStat Team (windirstat.net) // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // #include "stdafx.h" #include "windirstat.h" #include "treemap.h" // CColorSpace #include "selectobject.h" #include "ownerdrawnlistcontrol.h" #ifdef _DEBUG #define new DEBUG_NEW #endif namespace { const int TEXT_X_MARGIN = 6; // Horizontal distance of the text from the edge of the item rectangle const UINT LABEL_INFLATE_CX = 3;// How much the label is enlarged, to get the selection and focus rectangle const UINT LABEL_Y_MARGIN = 2; const UINT GENERAL_INDENT = 5; } ///////////////////////////////////////////////////////////////////////////// COwnerDrawnListItem::COwnerDrawnListItem() { } COwnerDrawnListItem::~COwnerDrawnListItem() { } // Draws an item label (icon, text) in all parts of the WinDirStat view // the rest is drawn by DrawItem() void COwnerDrawnListItem::DrawLabel(COwnerDrawnListControl *list, CImageList *il, CDC *pdc, CRect& rc, UINT state, int *width, int *focusLeft, bool indent) const { CRect rcRest = rc; // Increase indentation according to tree-level if(indent) { rcRest.left += GENERAL_INDENT; } // Prepare to draw the file/folder icon ASSERT(GetImage() < il->GetImageCount()); IMAGEINFO ii; il->GetImageInfo(GetImage(), &ii); CRect rcImage(ii.rcImage); if(width == NULL) { // Draw the color with transparent background CPoint pt(rcRest.left, rcRest.top + rcRest.Height() / 2 - rcImage.Height() / 2); il->SetBkColor(CLR_NONE); il->Draw(pdc, GetImage(), pt, ILD_NORMAL); } // Decrease size of the remainder rectangle from left rcRest.left += rcImage.Width(); CSelectObject sofont(pdc, list->GetFont()); rcRest.DeflateRect(list->GetTextXMargin(), 0); CRect rcLabel = rcRest; pdc->DrawText(GetText(0), rcLabel, DT_SINGLELINE | DT_VCENTER | DT_WORD_ELLIPSIS | DT_CALCRECT | DT_NOPREFIX); rcLabel.InflateRect(LABEL_INFLATE_CX, 0); rcLabel.top = rcRest.top + LABEL_Y_MARGIN; rcLabel.bottom = rcRest.bottom - LABEL_Y_MARGIN; CSetBkMode bk(pdc, TRANSPARENT); COLORREF textColor = ::GetSysColor(COLOR_WINDOWTEXT); if(width == NULL && (state & ODS_SELECTED) != 0 && (list->HasFocus() || list->IsShowSelectionAlways())) { // Color for the text in a highlighted item (usually white) textColor = list->GetHighlightTextColor(); CRect selection = rcLabel; // Depending on "FullRowSelection" style if(list->IsFullRowSelection()) { selection.right = rc.right; } // Fill the selection rectangle background (usually dark blue) pdc->FillSolidRect(selection, list->GetHighlightColor()); } else { // Use the color designated for this item // This is currently only for encrypted and compressed items textColor = GetItemTextColor(); } // Set text color for device context CSetTextColor stc(pdc, textColor); if(width == NULL) { // Draw the actual text pdc->DrawText(GetText(0), rcRest, DT_SINGLELINE | DT_VCENTER | DT_WORD_ELLIPSIS | DT_NOPREFIX); } rcLabel.InflateRect(1, 1); *focusLeft = rcLabel.left; if((state & ODS_FOCUS) != 0 && list->HasFocus() && width == NULL && !list->IsFullRowSelection()) { pdc->DrawFocusRect(rcLabel); } if(width == NULL) { DrawAdditionalState(pdc, rcLabel); } rcLabel.left = rc.left; rc = rcLabel; if(width != NULL) { *width = rcLabel.Width() + 5; // Don't know, why +5 } } void COwnerDrawnListItem::DrawSelection(COwnerDrawnListControl *list, CDC *pdc, CRect rc, UINT state) const { if(!list->IsFullRowSelection()) { return; } if(!list->HasFocus() && !list->IsShowSelectionAlways()) { return; } if((state & ODS_SELECTED) == 0) { return; } rc.DeflateRect(0, LABEL_Y_MARGIN); pdc->FillSolidRect(rc, list->GetHighlightColor()); } void COwnerDrawnListItem::DrawPercentage(CDC *pdc, CRect rc, double fraction, COLORREF color) const { const int LIGHT = 198; // light edge const int DARK = 118; // dark edge const int BG = 225; // background (lighter than light edge) const COLORREF light = RGB(LIGHT, LIGHT, LIGHT); const COLORREF dark = RGB(DARK, DARK, DARK); const COLORREF bg = RGB(BG, BG, BG); CRect rcLeft = rc; rcLeft.right = (int)(rcLeft.left + rc.Width() * fraction); CRect rcRight = rc; rcRight.left = rcLeft.right; if(rcLeft.right > rcLeft.left) { pdc->Draw3dRect(rcLeft, light, dark); } rcLeft.DeflateRect(1, 1); if(rcLeft.right > rcLeft.left) { pdc->FillSolidRect(rcLeft, color); } if(rcRight.right > rcRight.left) { pdc->Draw3dRect(rcRight, light, light); } rcRight.DeflateRect(1, 1); if(rcRight.right > rcRight.left) { pdc->FillSolidRect(rcRight, bg); } } ///////////////////////////////////////////////////////////////////////////// IMPLEMENT_DYNAMIC(COwnerDrawnListControl, CSortingListControl) COwnerDrawnListControl::COwnerDrawnListControl(LPCTSTR name, int rowHeight) : CSortingListControl(name) , m_rowHeight(rowHeight) , m_showGrid(false) , m_showStripes(false) , m_showFullRowSelection(false) , m_yFirstItem(-1) , m_windowColor(CLR_NONE) , m_stripeColor(CLR_NONE) { ASSERT(rowHeight > 0); InitializeColors(); } COwnerDrawnListControl::~COwnerDrawnListControl() { } // This method MUST be called before the Control is shown. void COwnerDrawnListControl::OnColumnsInserted() { // The pacman shall not draw over our header control. ModifyStyle(0, WS_CLIPCHILDREN); // Where does the 1st item begin vertically? if(GetItemCount() > 0) { CRect rc; GetItemRect(0, rc, LVIR_BOUNDS); m_yFirstItem = rc.top; } else { InsertItem(0, _T("_tmp"), 0); CRect rc; GetItemRect(0, rc, LVIR_BOUNDS); DeleteItem(0); m_yFirstItem = rc.top; } LoadPersistentAttributes(); } void COwnerDrawnListControl::SysColorChanged() { InitializeColors(); } int COwnerDrawnListControl::GetRowHeight() { return m_rowHeight; } void COwnerDrawnListControl::ShowGrid(bool show) { m_showGrid = show; if(::IsWindow(m_hWnd)) { InvalidateRect(NULL); } } void COwnerDrawnListControl::ShowStripes(bool show) { m_showStripes = show; if(::IsWindow(m_hWnd)) { InvalidateRect(NULL); } } void COwnerDrawnListControl::ShowFullRowSelection(bool show) { m_showFullRowSelection = show; if(::IsWindow(m_hWnd)) { InvalidateRect(NULL); } } bool COwnerDrawnListControl::IsFullRowSelection() { return m_showFullRowSelection; } // Normal window background color COLORREF COwnerDrawnListControl::GetWindowColor() { return m_windowColor; } // Shaded window background color (for stripes) COLORREF COwnerDrawnListControl::GetStripeColor() { return m_stripeColor; } // Highlight color if we have no focus COLORREF COwnerDrawnListControl::GetNonFocusHighlightColor() { return RGB(190,190,190); //RGB(120, 120, 120): more contrast } // Highlight text color if we have no focus COLORREF COwnerDrawnListControl::GetNonFocusHighlightTextColor() { return RGB(0,0,0); // RGB(255,255,255): more contrast } COLORREF COwnerDrawnListControl::GetHighlightColor() { if(HasFocus()) { return ::GetSysColor(COLOR_HIGHLIGHT); } else { return GetNonFocusHighlightColor(); } } COLORREF COwnerDrawnListControl::GetHighlightTextColor() { if(HasFocus()) { return ::GetSysColor(COLOR_HIGHLIGHTTEXT); } else { return GetNonFocusHighlightTextColor(); } } bool COwnerDrawnListControl::IsItemStripeColor(int i) { return (m_showStripes && (i % 2 != 0)); } bool COwnerDrawnListControl::IsItemStripeColor(const COwnerDrawnListItem *item) { return IsItemStripeColor(FindListItem(item)); } COLORREF COwnerDrawnListControl::GetItemBackgroundColor(int i) { return (IsItemStripeColor(i) ? GetStripeColor() : GetWindowColor()); } COLORREF COwnerDrawnListControl::GetItemBackgroundColor(const COwnerDrawnListItem *item) { return GetItemBackgroundColor(FindListItem(item)); } COLORREF COwnerDrawnListControl::GetItemSelectionBackgroundColor(int i) { bool selected = (GetItemState(i, LVIS_SELECTED) & LVIS_SELECTED) != 0; if(selected && IsFullRowSelection() && (HasFocus() || IsShowSelectionAlways())) { return GetHighlightColor(); } else { return GetItemBackgroundColor(i); } } COLORREF COwnerDrawnListControl::GetItemSelectionBackgroundColor(const COwnerDrawnListItem *item) { return GetItemSelectionBackgroundColor(FindListItem(item)); } COLORREF COwnerDrawnListControl::GetItemSelectionTextColor(int i) { bool selected = (GetItemState(i, LVIS_SELECTED) & LVIS_SELECTED) != 0; if(selected && IsFullRowSelection() && (HasFocus() || IsShowSelectionAlways())) { return GetHighlightTextColor(); } else { return ::GetSysColor(COLOR_WINDOWTEXT); } } int COwnerDrawnListControl::GetTextXMargin() { return TEXT_X_MARGIN; } int COwnerDrawnListControl::GetGeneralLeftIndent() { return GENERAL_INDENT; } COwnerDrawnListItem *COwnerDrawnListControl::GetItem(int i) { COwnerDrawnListItem *item = (COwnerDrawnListItem *)GetItemData(i); return item; } int COwnerDrawnListControl::FindListItem(const COwnerDrawnListItem *item) { LVFINDINFO fi; ZeroMemory(&fi, sizeof(fi)); fi.flags = LVFI_PARAM; fi.lParam = (LPARAM)item; int i = FindItem(&fi); return i; } void COwnerDrawnListControl::InitializeColors() { // I try to find a good contrast to COLOR_WINDOW (usually white or light grey). // This is a result of experiments. const double diff = 0.07; // Try to alter the brightness by diff. const double threshold = 1.04; // If result would be brighter, make color darker. m_windowColor = ::GetSysColor(COLOR_WINDOW); double b = CColorSpace::GetColorBrightness(m_windowColor); if(b + diff > threshold) { b -= diff; } else { b += diff; if(b > 1.0) { b = 1.0; } } m_stripeColor = CColorSpace::MakeBrightColor(m_windowColor, b); } void COwnerDrawnListControl::DrawItem(LPDRAWITEMSTRUCT pdis) { COwnerDrawnListItem *item = (COwnerDrawnListItem *)(pdis->itemData); CDC *pdc = CDC::FromHandle(pdis->hDC); CRect rcItem(pdis->rcItem); if(m_showGrid) { rcItem.bottom--; rcItem.right--; } CDC dcmem; dcmem.CreateCompatibleDC(pdc); CBitmap bm; bm.CreateCompatibleBitmap(pdc, rcItem.Width(), rcItem.Height()); CSelectObject sobm(&dcmem, &bm); dcmem.FillSolidRect(rcItem - rcItem.TopLeft(), GetItemBackgroundColor(pdis->itemID)); bool drawFocus = (pdis->itemState & ODS_FOCUS) != 0 && HasFocus() && IsFullRowSelection(); CArray order; order.SetSize(GetHeaderCtrl()->GetItemCount()); GetHeaderCtrl()->GetOrderArray(order.GetData(), int(order.GetSize())); CRect rcFocus = rcItem; rcFocus.DeflateRect(0, LABEL_Y_MARGIN - 1); for(int i = 0; i < order.GetSize(); i++) { int subitem = order[i]; CRect rc = GetWholeSubitemRect(pdis->itemID, subitem); CRect rcDraw = rc - rcItem.TopLeft(); int focusLeft = rcDraw.left; if(!item->DrawSubitem(subitem, &dcmem, rcDraw, pdis->itemState, NULL, &focusLeft)) { item->DrawSelection(this, &dcmem, rcDraw, pdis->itemState); CRect rcText = rcDraw; rcText.DeflateRect(TEXT_X_MARGIN, 0); CSetBkMode bk(&dcmem, TRANSPARENT); CSelectObject sofont(&dcmem, GetFont()); CString s = item->GetText(subitem); UINT align = IsColumnRightAligned(subitem) ? DT_RIGHT : DT_LEFT; // Get the correct color in case of compressed or encrypted items COLORREF textColor = item->GetItemTextColor(); // Except if the item is selected - in this case just use standard colors if((pdis->itemState & ODS_SELECTED) && (HasFocus() || IsShowSelectionAlways()) && (IsFullRowSelection())) { textColor = GetItemSelectionTextColor(pdis->itemID); } // Set the text color CSetTextColor tc(&dcmem, textColor); // Draw the (sub)item text dcmem.DrawText(s, rcText, DT_SINGLELINE | DT_VCENTER | DT_WORD_ELLIPSIS | DT_NOPREFIX | align); // Test: dcmem.FillSolidRect(rcDraw, 0); } if(focusLeft > rcDraw.left) { if(drawFocus && i > 0) { pdc->DrawFocusRect(rcFocus); } rcFocus.left = focusLeft; } rcFocus.right = rcDraw.right; pdc->BitBlt(rcItem.left + rcDraw.left, rcItem.top + rcDraw.top, rcDraw.Width(), rcDraw.Height(), &dcmem, rcDraw.left, rcDraw.top, SRCCOPY); } if(drawFocus) { pdc->DrawFocusRect(rcFocus); } } bool COwnerDrawnListControl::IsColumnRightAligned(int col) { HDITEM hditem; ZeroMemory(&hditem, sizeof(hditem)); hditem.mask = HDI_FORMAT; GetHeaderCtrl()->GetItem(col, &hditem); return (hditem.fmt & HDF_RIGHT) != 0; } CRect COwnerDrawnListControl::GetWholeSubitemRect(int item, int subitem) { CRect rc; if(subitem == 0) { // Special case column 0: // If we did GetSubItemRect(item 0, LVIR_LABEL, rc) // and we have an image list, then we would get the rectangle // excluding the image. HDITEM hditem; hditem.mask = HDI_WIDTH; GetHeaderCtrl()->GetItem(0, &hditem); VERIFY(GetItemRect(item, rc, LVIR_LABEL)); rc.left = rc.right - hditem.cxy; } else { VERIFY(GetSubItemRect(item, subitem, LVIR_LABEL, rc)); } if(m_showGrid) { rc.right--; rc.bottom--; } return rc; } bool COwnerDrawnListControl::HasFocus() { return ::GetFocus() == m_hWnd; } bool COwnerDrawnListControl::IsShowSelectionAlways() { return (GetStyle() & LVS_SHOWSELALWAYS) != 0; } int COwnerDrawnListControl::GetSubItemWidth(COwnerDrawnListItem *item, int subitem) { int width; CClientDC dc(this); CRect rc(0, 0, 1000, 1000); int dummy = rc.left; if(item->DrawSubitem(subitem, &dc, rc, 0, &width, &dummy)) { return width; } CString s = item->GetText(subitem); if(s.IsEmpty()) { // DrawText(..DT_CALCRECT) seems to stumble about empty strings return 0; } CSelectObject sofont(&dc, GetFont()); UINT align = IsColumnRightAligned(subitem) ? DT_RIGHT : DT_LEFT; dc.DrawText(s, rc, DT_SINGLELINE | DT_VCENTER | DT_CALCRECT | DT_NOPREFIX | align); rc.InflateRect(TEXT_X_MARGIN, 0); return rc.Width(); } BEGIN_MESSAGE_MAP(COwnerDrawnListControl, CSortingListControl) ON_WM_ERASEBKGND() #pragma warning(suppress: 26454) ON_NOTIFY(HDN_DIVIDERDBLCLICKA, 0, OnHdnDividerdblclick) #pragma warning(suppress: 26454) ON_NOTIFY(HDN_DIVIDERDBLCLICKW, 0, OnHdnDividerdblclick) ON_WM_VSCROLL() #pragma warning(suppress: 26454) ON_NOTIFY(HDN_ITEMCHANGINGA, 0, OnHdnItemchanging) #pragma warning(suppress: 26454) ON_NOTIFY(HDN_ITEMCHANGINGW, 0, OnHdnItemchanging) ON_WM_SHOWWINDOW() END_MESSAGE_MAP() BOOL COwnerDrawnListControl::OnEraseBkgnd(CDC* pDC) { int i = 0; ASSERT(GetHeaderCtrl()->GetItemCount() > 0); // We should recalculate m_yFirstItem here (could have changed e.g. when // the XP-Theme changed). if(GetItemCount() > 0) { CRect rc; GetItemRect(GetTopIndex(), rc, LVIR_BOUNDS); m_yFirstItem = rc.top; } // else: if we did the same thing as in OnColumnsCreated(), we get // repaint problems. const COLORREF gridColor = RGB(212,208,200); CRect rcClient; GetClientRect(rcClient); CRect rcHeader; GetHeaderCtrl()->GetWindowRect(rcHeader); ScreenToClient(rcHeader); CRect rcBetween = rcClient; // between header and first item rcBetween.top = rcHeader.bottom; rcBetween.bottom = m_yFirstItem; pDC->FillSolidRect(rcBetween, gridColor); CArray columnOrder; columnOrder.SetSize(GetHeaderCtrl()->GetItemCount()); GetColumnOrderArray(columnOrder.GetData(), static_cast(columnOrder.GetSize())); CArray vertical; vertical.SetSize(GetHeaderCtrl()->GetItemCount()); int x = - GetScrollPos(SB_HORZ); HDITEM hdi; ZeroMemory(&hdi, sizeof(hdi)); hdi.mask = HDI_WIDTH; for(i = 0; i < GetHeaderCtrl()->GetItemCount(); i++) { GetHeaderCtrl()->GetItem(columnOrder[i], &hdi); x += hdi.cxy; vertical[i]= x; } if(m_showGrid) { CPen pen(PS_SOLID, 1, gridColor); CSelectObject sopen(pDC, &pen); for(int y = m_yFirstItem + GetRowHeight() - 1; y < rcClient.bottom; y += GetRowHeight()) { pDC->MoveTo(rcClient.left, y); pDC->LineTo(rcClient.right, y); } // BUGBUG: re-using i could be a potential bug! for(i = 0; i < vertical.GetSize(); i++) { pDC->MoveTo(vertical[i] - 1, rcClient.top); pDC->LineTo(vertical[i] - 1, rcClient.bottom); } } const int gridWidth = m_showGrid ? 1 : 0; const COLORREF bgcolor = ::GetSysColor(COLOR_WINDOW); const int lineCount = GetCountPerPage() + 1; const int firstItem = GetTopIndex(); const int lastItem = min(firstItem + lineCount, GetItemCount()) - 1; ASSERT(GetItemCount() == 0 || firstItem < GetItemCount()); ASSERT(GetItemCount() == 0 || lastItem < GetItemCount()); ASSERT(GetItemCount() == 0 || lastItem >= firstItem); const int itemCount = lastItem - firstItem + 1; CRect fill; fill.left = vertical[vertical.GetSize() - 1]; fill.right = rcClient.right; fill.top = m_yFirstItem; fill.bottom = fill.top + GetRowHeight() - gridWidth; for(i = 0; i < itemCount; i++) { pDC->FillSolidRect(fill, bgcolor); fill.OffsetRect(0, GetRowHeight()); } int top = fill.top; while(top < rcClient.bottom) { fill.top = top; fill.bottom = top + GetRowHeight() - gridWidth; int left = 0; for(int j = 0; j < vertical.GetSize(); j++) { fill.left = left; fill.right = vertical[j] - gridWidth; pDC->FillSolidRect(fill, bgcolor); left = vertical[j]; } fill.left = left; fill.right = rcClient.right; pDC->FillSolidRect(fill, bgcolor); top += GetRowHeight(); } return true; } void COwnerDrawnListControl::OnHdnDividerdblclick(NMHDR *pNMHDR, LRESULT *pResult) { CWaitCursor wc; LPNMHEADER phdr = reinterpret_cast(pNMHDR); int subitem = phdr->iItem; AdjustColumnWidth(subitem); *pResult = 0; } void COwnerDrawnListControl::AdjustColumnWidth(int col) { CWaitCursor wc; int width = 10; for(int i = 0; i < GetItemCount(); i++) { int w = GetSubItemWidth(GetItem(i), col); if(w > width) { width = w; } } SetColumnWidth(col, width + 5); } void COwnerDrawnListControl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) { CListCtrl::OnVScroll(nSBCode, nPos, pScrollBar); // Owner drawn list controls with LVS_EX_GRIDLINES don't repaint correctly // when scrolled (under Windows XP). So we force a complete repaint here. InvalidateRect(NULL); } void COwnerDrawnListControl::OnHdnItemchanging(NMHDR * /*pNMHDR*/, LRESULT *pResult) { // Unused: LPNMHEADER phdr = reinterpret_cast(pNMHDR); Default(); InvalidateRect(NULL); *pResult = 0; }