windows-nt/Source/XPSP1/NT/shell/comctl32/v6/lvtile.c

1076 lines
37 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
// large icon view stuff
#include "ctlspriv.h"
#include "listview.h"
void ListView_TRecomputeLabelSizeInternal(LV* plv, LISTITEM* pitem, int i, HDC hdc, BOOL fUsepitem);
void ListView_TGetRectsInternal(LV* plv, LISTITEM* pitem, int i, RECT* prcIcon, RECT* prcLabel, LPRECT prcBounds);
void ListView_TGetRectsOwnerDataInternal( LV* plv, int iItem, RECT* prcIcon, RECT* prcLabel, LISTITEM* pitem, BOOL fUsepitem );
#define TILELABELRATIO 20
#define _GetStateCX(plv) \
((plv->himlState && !ListView_IsSimpleSelect(plv)) ? plv->cxState:0)
#define _GetStateCY(plv) \
(plv->himlState ? plv->cyState:0)
int _CalcDesiredIconHeight(LV* plv)
{
return max(plv->cyIcon, _GetStateCY(plv));
}
// Based on the icon height, and the number of columns showing
int _CalcDesiredTileHeight(LV* plv, LISTITEM* pitem)
{
return 2 * g_cyIconMargin + max (_CalcDesiredIconHeight(plv), pitem->cyFoldedLabel);
}
#define LIGHTENBYTE(percent, x) { x += (255 - x) * percent / 100;}
COLORREF GetBorderSelectColor(int iPercent, COLORREF clr)
{
//BOOL fAllowDesaturation;
BYTE r, g, b;
// Doing this is less expensive than Luminance adjustment
//fAllowDesaturation = FALSE;
r = GetRValue(clr);
g = GetGValue(clr);
b = GetBValue(clr);
// If all colors are above positive saturation, allow a desaturation
/*if (r > 0xF0 && g > 0xF0 && b > 0xF0)
{
fAllowDesaturation = TRUE;
}*/
LIGHTENBYTE(iPercent, r);
LIGHTENBYTE(iPercent, g);
LIGHTENBYTE(iPercent, b);
return RGB(r,g,b);
}
void _InitTileColumnsEnum(PLVTILECOLUMNSENUM plvtce, LV* plv, UINT cColumns, UINT *puColumns, BOOL fOneLessLine)
{
int iSortedColumn = (plv->iLastColSort < plv->cCol) ? plv->iLastColSort : -1;
if (cColumns == I_COLUMNSCALLBACK)
{
// We don't have column information yet.
plvtce->iTotalSpecifiedColumns = 0;
plvtce->iColumnsRemainingMax = 0;
}
else
{
int iSubtract = fOneLessLine ? 1 : 0;
// The total number of columns that we can use in the puColumns array
// (limited not just by cColumns, but also plv->cSubItems)
plvtce->iTotalSpecifiedColumns = min(plv->cSubItems - iSubtract, (int)cColumns);
// The total number of columns that we might use, including the sorted column,
// which may or may not be included in puColumns. This is also limited
// by plv->cSubItems
plvtce->iColumnsRemainingMax = min(plv->cSubItems - iSubtract, (int)cColumns + ((iSortedColumn >= 0) ? 1 : 0));
}
plvtce->puSpecifiedColumns = puColumns; // Array of specified columns
plvtce->iCurrentSpecifiedColumn = 0;
plvtce->iSortedColumn = iSortedColumn; // Sorted column (-1 if none, 0 if name - in these cases we ignore)
plvtce->bUsedSortedColumn = FALSE;
}
/*
* This is just like Str_Set, but for tile columns instead of strings.
* ppuColumns and pcColumns get set to puColumns and cColumns
*/
BOOL Tile_Set(UINT **ppuColumns, UINT *pcColumns, UINT *puColumns, UINT cColumns)
{
if ((cColumns == I_COLUMNSCALLBACK) || (cColumns == 0) || (puColumns == NULL))
{
// We're setting the columns to zero, or callback
// If there was already something there, free it.
if ((*pcColumns != I_COLUMNSCALLBACK) && (*pcColumns != 0))
{
if (*ppuColumns)
LocalFree(*ppuColumns);
}
*pcColumns = cColumns;
*ppuColumns = NULL;
}
else
{
// We're providing a bunch of new columns
UINT *puColumnsNew = *ppuColumns;
if ((*pcColumns == I_COLUMNSCALLBACK) || (*pcColumns == 0))
puColumnsNew = NULL; // There's nothing there to realloc.
// Reallocate the block of columns
puColumnsNew = CCLocalReAlloc(puColumnsNew, sizeof(UINT) * cColumns);
if (!puColumnsNew)
return FALSE;
*pcColumns = cColumns;
CopyMemory(puColumnsNew, puColumns, sizeof(UINT) * cColumns);
*ppuColumns = puColumnsNew;
}
return TRUE;
}
BOOL ListView_TDrawItem(PLVDRAWITEM plvdi)
{
RECT rcIcon;
RECT rcLabel;
RECT rcBounds;
RECT rcT;
RECT rcFocus={0};
TCHAR ach[CCHLABELMAX];
LV_ITEM item = {0};
int i = (int) plvdi->nmcd.nmcd.dwItemSpec;
int iStateImageOffset;
LV* plv = plvdi->plv;
LISTITEM* pitem;
LISTITEM litem;
UINT auColumns[CCMAX_TILE_COLUMNS];
COLORREF clrTextBk = plvdi->nmcd.clrTextBk;
if (ListView_IsOwnerData(plv))
{
// moved here to reduce call backs in OWNERDATA case
item.iItem = i;
item.iSubItem = 0;
item.mask = LVIF_TEXT | LVIF_STATE | LVIF_IMAGE | LVIF_COLUMNS;
item.stateMask = LVIS_ALL;
item.pszText = ach;
item.cchTextMax = ARRAYSIZE(ach);
item.cColumns = ARRAYSIZE(auColumns);
item.puColumns = auColumns;
ListView_OnGetItem(plv, &item);
litem.pszText = item.pszText;
ListView_TGetRectsOwnerDataInternal(plv, i, &rcIcon, &rcLabel, &litem, TRUE);
UnionRect(&rcBounds, &rcLabel, &rcIcon);
pitem = NULL;
}
else
{
pitem = ListView_GetItemPtr(plv, i);
if (pitem)
{
// NOTE this will do a GetItem LVIF_TEXT iff needed
ListView_TGetRects(plv, pitem, &rcIcon, &rcLabel, &rcBounds);
}
}
if (!plvdi->prcClip || IntersectRect(&rcT, &rcBounds, plvdi->prcClip))
{
UINT fText;
if (!ListView_IsOwnerData(plv))
{
item.iItem = i;
item.iSubItem = 0;
item.mask = LVIF_TEXT | LVIF_STATE | LVIF_IMAGE | LVIF_COLUMNS;
item.stateMask = LVIS_ALL;
item.pszText = ach;
item.cchTextMax = ARRAYSIZE(ach);
item.cColumns = ARRAYSIZE(auColumns);
item.puColumns = auColumns;
ListView_OnGetItem(plv, &item);
// Make sure the listview hasn't been altered during
// the callback to get the item info
if (pitem != ListView_GetItemPtr(plv, i))
{
return FALSE;
}
// Call this again. The bounds may have changed - ListView_OnGetItem may have retrieved new
// info via LVN_GETDISPINFO
ListView_TGetRectsInternal(plv, pitem, i, &rcIcon, &rcLabel, &rcBounds);
}
if (plvdi->lpptOrg)
{
OffsetRect(&rcIcon, plvdi->lpptOrg->x - rcBounds.left,
plvdi->lpptOrg->y - rcBounds.top);
OffsetRect(&rcLabel, plvdi->lpptOrg->x - rcBounds.left,
plvdi->lpptOrg->y - rcBounds.top);
OffsetRect(&rcBounds, plvdi->lpptOrg->x - rcBounds.left,
plvdi->lpptOrg->y - rcBounds.top);
}
fText = ListView_GetTextSelectionFlags(plv, &item, plvdi->flags);
plvdi->nmcd.iSubItem = 0;
if (plv->pImgCtx || plv->hbmpWatermark)
{
clrTextBk = CLR_NONE;
}
else
{
if (CLR_NONE != plvdi->nmcd.clrFace)
FillRectClr(plvdi->nmcd.nmcd.hdc, &rcBounds, plvdi->nmcd.clrFace);
}
iStateImageOffset = _GetStateCX(plv);
ListView_DrawImageEx2(plv, &item, plvdi->nmcd.nmcd.hdc,
rcIcon.left + iStateImageOffset + g_cxLabelMargin,
rcIcon.top + (rcIcon.bottom - rcIcon.top - _CalcDesiredIconHeight(plv))/2,
plvdi->nmcd.clrFace,
plvdi->flags, rcLabel.right, plvdi->nmcd.iIconEffect, plvdi->nmcd.iIconPhase);
// Don't draw label if it's being edited...
//
if (plv->iEdit != i)
{
RECT rcLine = rcLabel;
RECT rcDummy;
BOOL fLineWrap;
LISTSUBITEM lsi;
TCHAR szBuffer[CCHLABELMAX];
rcFocus = rcLabel;
// Apply any margins
rcLine.left += plv->rcTileLabelMargin.left;
rcLine.top += plv->rcTileLabelMargin.top;
rcLine.right -= plv->rcTileLabelMargin.right;
rcLine.bottom -= plv->rcTileLabelMargin.bottom;
// Center text lines vertically:
rcLine.top += (rcLine.bottom - rcLine.top - (pitem ? pitem->cyFoldedLabel : litem.cyFoldedLabel))/2;
rcFocus.top = rcLine.top;
// Make sure the text is in szBuffer
if (szBuffer != item.pszText)
lstrcpyn(szBuffer, item.pszText, ARRAYSIZE(szBuffer));
// Now get the bounds of the thing.
lsi.pszText = szBuffer;
lsi.iImage = -1;
lsi.state = 0;
fLineWrap = TCalculateSubItemRect(plv, NULL, &lsi, i, 0, plvdi->nmcd.nmcd.hdc, &rcDummy, NULL);
rcLine.bottom = rcLine.top + lsi.sizeText.cy;// + ((fLineWrap) ? lsi.sizeText.cy : 0);
fText |= SHDT_LEFT | SHDT_CLIPPED | SHDT_NOMARGIN; // Need line wrapping, potentially, so SHDT_DRAWTEXT. Need left alignment. Need to clip to rect.
if (plvdi->flags & LVDI_TRANSTEXT)
fText |= SHDT_TRANSPARENT;
if ((fText & SHDT_SELECTED) && (plvdi->flags & LVDI_HOTSELECTED))
fText |= SHDT_HOTSELECTED;
if (plvdi->dwCustom & LVCDRF_NOSELECT)
{
fText &= ~(SHDT_HOTSELECTED | SHDT_SELECTED);
}
if (item.pszText && (*item.pszText))
{
if(plv->dwExStyle & WS_EX_RTLREADING)
fText |= SHDT_RTLREADING;
SHDrawText(plvdi->nmcd.nmcd.hdc, item.pszText, &rcLine, LVCFMT_LEFT, SHDT_DRAWTEXT | fText,
RECTHEIGHT(rcLine), plv->cxEllipses,
plvdi->nmcd.clrText, clrTextBk);
}
if (plv->cCol > 0)
{
int fItemText = fText;
// Map CLR_DEFAULT to a real colorref before passing to GetSortColor.
COLORREF clrSubItemText = GetSortColor(10,
(plvdi->nmcd.clrText == CLR_DEFAULT) ? g_clrWindowText : plvdi->nmcd.clrText);
int iSubItem;
LVTILECOLUMNSENUM lvtce;
_InitTileColumnsEnum(&lvtce, plv, item.cColumns, item.puColumns, fLineWrap);
while (-1 != (iSubItem = _GetNextColumn(&lvtce)))
{
LVITEM lvi;
lvi.mask = LVIF_TEXT;
lvi.iItem = i;
lvi.iSubItem = iSubItem;
lvi.pszText = szBuffer;
lvi.cchTextMax = ARRAYSIZE(szBuffer);
if (ListView_IsOwnerData( plv ))
lvi.lParam = 0L;
else
lvi.lParam = pitem->lParam;
if (ListView_OnGetItem(plv, &lvi))
{
if (lvi.pszText)
{
// Make sure the text is in szBuffer
if (szBuffer != lvi.pszText)
lstrcpyn(szBuffer, lvi.pszText, ARRAYSIZE(szBuffer));
// Now get the bounds of the thing.
lsi.pszText = szBuffer;
lsi.iImage = -1;
lsi.state = 0;
plvdi->nmcd.clrText = clrSubItemText;
TCalculateSubItemRect(plv, NULL, &lsi, i, iSubItem, plvdi->nmcd.nmcd.hdc, &rcDummy, NULL);
// Now we should have the size of the text.
plvdi->nmcd.iSubItem = iSubItem;
CICustomDrawNotify(&plvdi->plv->ci, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &plvdi->nmcd.nmcd);
if (lsi.pszText != NULL && *lsi.pszText != 0)
{
rcLine.top = rcLine.bottom;
rcLine.bottom = rcLine.top + lsi.sizeText.cy;
SHDrawText(plvdi->nmcd.nmcd.hdc, lsi.pszText, &rcLine, LVCFMT_LEFT, fItemText | SHDT_ELLIPSES,
RECTHEIGHT(rcLine), plv->cxEllipses,
plvdi->nmcd.clrText, clrTextBk);
}
}
}
}
}
rcFocus.bottom = rcLine.bottom;
}
if ((plvdi->flags & LVDI_FOCUS) &&
(item.state & LVIS_FOCUSED) &&
!(CCGetUIState(&(plvdi->plv->ci)) & UISF_HIDEFOCUS))
{
rcFocus.top -= g_cyCompensateInternalLeading;
rcFocus.bottom += g_cyCompensateInternalLeading;
DrawFocusRect(plvdi->nmcd.nmcd.hdc, &rcFocus);
}
}
return TRUE;
}
int ListView_TItemHitTest(LV* plv, int x, int y, UINT* pflags, int *piSubItem)
{
int iHit;
UINT flags;
POINT pt;
RECT rcLabel;
RECT rcIcon;
int iStateImageOffset = 0;
if (piSubItem)
*piSubItem = 0;
// Map window-relative coordinates to view-relative coords...
//
pt.x = x + plv->ptOrigin.x;
pt.y = y + plv->ptOrigin.y;
// If there are any uncomputed items, recompute them now.
//
if (plv->rcView.left == RECOMPUTE)
ListView_Recompute(plv);
flags = 0;
if (ListView_IsOwnerData( plv ))
{
int cSlots;
POINT ptWnd;
LISTITEM item;
int iWidth = 0, iHeight = 0;
cSlots = ListView_GetSlotCount( plv, TRUE, &iWidth, &iHeight );
iHit = ListView_CalcHitSlot( plv, pt, cSlots, iWidth, iHeight );
if (iHit < ListView_Count(plv))
{
ListView_TGetRectsOwnerDataInternal( plv, iHit, &rcIcon, &rcLabel, &item, FALSE );
ptWnd.x = x;
ptWnd.y = y;
if (PtInRect(&rcIcon, ptWnd))
{
flags = LVHT_ONITEMICON;
}
else if (PtInRect(&rcLabel, ptWnd))
{
flags = LVHT_ONITEMLABEL;
}
}
}
else
{
iStateImageOffset = _GetStateCX(plv);
for (iHit = 0; (iHit < ListView_Count(plv)); iHit++)
{
LISTITEM* pitem = ListView_FastGetZItemPtr(plv, iHit);
POINT ptItem;
ptItem.x = pitem->pt.x;
ptItem.y = pitem->pt.y;
rcIcon.left = ptItem.x;
rcIcon.right = rcIcon.left + plv->cxIcon + 3 * g_cxLabelMargin + iStateImageOffset;
rcIcon.top = pitem->pt.y;
rcIcon.bottom = rcIcon.top + plv->sizeTile.cy - 2 * g_cyIconMargin;
rcLabel.left = rcIcon.right;
if (pitem->cyUnfoldedLabel != SRECOMPUTE)
{
rcLabel.right = rcLabel.left + pitem->cxSingleLabel;
}
else
{
rcLabel.right = rcLabel.left + plv->sizeTile.cx - RECTWIDTH(rcIcon) - 2 * g_cxLabelMargin;
}
rcLabel.top = rcIcon.top;
rcLabel.bottom = rcIcon.bottom;
// Max out bottoms
rcLabel.bottom = rcIcon.bottom = max(rcIcon.bottom, rcLabel.bottom);
if (PtInRect(&rcIcon, pt))
{
flags = LVHT_ONITEMICON;
}
else if (PtInRect(&rcLabel, pt))
{
flags = LVHT_ONITEMLABEL;
}
if (flags)
break;
}
}
if (flags == 0)
{
flags = LVHT_NOWHERE;
iHit = -1;
}
else
{
if (!ListView_IsOwnerData( plv ))
{
iHit = DPA_GetPtrIndex(plv->hdpa, ListView_FastGetZItemPtr(plv, iHit));
}
}
*pflags = flags;
return iHit;
}
// out:
// prcIcon icon bounds including icon margin area
void ListView_TGetRects(LV* plv, LISTITEM* pitem, RECT* prcIcon, RECT* prcLabel, LPRECT prcBounds)
{
RECT rcIcon;
RECT rcLabel;
int iStateImageOffset = 0;
if (!prcLabel)
prcLabel = &rcLabel;
if (!prcIcon)
prcIcon = &rcIcon;
if (pitem->pt.x == RECOMPUTE)
{
ListView_Recompute(plv);
}
if (pitem->pt.x == RECOMPUTE)
{
RECT rcZero = {0};
*prcIcon = *prcLabel = rcZero;
return;
}
iStateImageOffset = _GetStateCX(plv);
prcIcon->left = pitem->pt.x - plv->ptOrigin.x;
prcIcon->right = prcIcon->left + plv->cxIcon + 3 * g_cxLabelMargin + iStateImageOffset;
prcIcon->top = pitem->pt.y - plv->ptOrigin.y;
prcIcon->bottom = prcIcon->top + plv->sizeTile.cy - 2 * g_cyIconMargin;
prcLabel->left = prcIcon->right;
prcLabel->right = pitem->pt.x - plv->ptOrigin.x + plv->sizeTile.cx - 2 * g_cxLabelMargin; //2 in tile, 1 on right. pitem->pt.x takes care of left margin
prcLabel->top = prcIcon->top;
prcLabel->bottom = prcIcon->bottom;
if (prcBounds)
{
UnionRect(prcBounds, prcLabel, prcIcon);
}
}
void ListView_TGetRectsOwnerData( LV* plv,
int iItem,
RECT* prcIcon,
RECT* prcLabel,
LISTITEM* pitem,
BOOL fUsepitem )
{
int cSlots;
RECT rcIcon;
RECT rcLabel;
int iStateImageOffset = 0;
if (!prcLabel)
prcLabel = &rcLabel;
if (!prcIcon)
prcIcon = &rcIcon;
// calculate x, y from iItem
cSlots = ListView_GetSlotCount( plv, TRUE, NULL, NULL );
pitem->iWorkArea = 0; // OwnerData doesn't support workareas
ListView_SetIconPos( plv, pitem, iItem, cSlots );
// What else can we do?
pitem->cColumns = 0;
pitem->puColumns = NULL;
// End What else can we do?
iStateImageOffset = _GetStateCX(plv);
prcIcon->left = pitem->pt.x - plv->ptOrigin.x;
prcIcon->right = prcIcon->left + plv->cxIcon + 3 * g_cxLabelMargin + iStateImageOffset;
prcIcon->top = pitem->pt.y - plv->ptOrigin.y;
prcIcon->bottom = prcIcon->top + plv->sizeTile.cy - 2 * g_cyIconMargin;
prcLabel->left = prcIcon->right;
prcLabel->right = pitem->pt.x - plv->ptOrigin.x + plv->sizeTile.cx - 2 * g_cxLabelMargin;
prcLabel->top = prcIcon->top;
prcLabel->bottom = prcIcon->bottom;
}
// out:
// prcIcon icon bounds including icon margin area
void ListView_TGetRectsInternal(LV* plv, LISTITEM* pitem, int i, RECT* prcIcon, RECT* prcLabel, LPRECT prcBounds)
{
RECT rcIcon;
RECT rcLabel;
int iStateImageOffset = 0;
if (!prcLabel)
prcLabel = &rcLabel;
if (!prcIcon)
prcIcon = &rcIcon;
if (pitem->pt.x == RECOMPUTE)
{
ListView_Recompute(plv);
}
if (pitem->pt.x == RECOMPUTE)
{
RECT rcZero = {0};
*prcIcon = *prcLabel = rcZero;
return;
}
if (pitem->cyUnfoldedLabel == SRECOMPUTE)
{
ListView_TRecomputeLabelSizeInternal(plv, pitem, i, NULL, FALSE);
}
iStateImageOffset = _GetStateCX(plv);
prcIcon->left = pitem->pt.x - plv->ptOrigin.x;
prcIcon->right = prcIcon->left + plv->cxIcon + 3 * g_cxLabelMargin + iStateImageOffset;
prcIcon->top = pitem->pt.y - plv->ptOrigin.y;
prcIcon->bottom = prcIcon->top + plv->sizeTile.cy - 2 * g_cyIconMargin;
prcLabel->left = prcIcon->right;
if (ListView_FullRowSelect(plv)) // full-row-select means full-tile-select
{
prcLabel->right = pitem->pt.x - plv->ptOrigin.x + plv->sizeTile.cx - 2 * g_cxLabelMargin; //2 in tile, 1 on right. pitem->pt.x takes care of left margin
}
else
{
prcLabel->right = prcLabel->left + pitem->cxSingleLabel;
}
prcLabel->top = prcIcon->top;
prcLabel->bottom = prcIcon->bottom;
if (prcBounds)
{
UnionRect(prcBounds, prcLabel, prcIcon);
}
}
void ListView_TGetRectsOwnerDataInternal( LV* plv,
int iItem,
RECT* prcIcon,
RECT* prcLabel,
LISTITEM* pitem,
BOOL fUsepitem )
{
int cSlots;
RECT rcIcon;
RECT rcLabel;
int iStateImageOffset = 0;
if (!prcLabel)
prcLabel = &rcLabel;
if (!prcIcon)
prcIcon = &rcIcon;
// calculate x, y from iItem
cSlots = ListView_GetSlotCount( plv, TRUE, NULL, NULL );
pitem->iWorkArea = 0; // OwnerData doesn't support workareas
ListView_SetIconPos( plv, pitem, iItem, cSlots );
// What else can we do?
pitem->cColumns = 0;
pitem->puColumns = NULL;
// End What else can we do?
// calculate lable sizes from iItem
ListView_TRecomputeLabelSizeInternal( plv, pitem, iItem, NULL, fUsepitem);
iStateImageOffset = _GetStateCX(plv);
prcIcon->left = pitem->pt.x - plv->ptOrigin.x;
prcIcon->right = prcIcon->left + plv->cxIcon + 3 * g_cxLabelMargin + iStateImageOffset;
prcIcon->top = pitem->pt.y - plv->ptOrigin.y;
prcIcon->bottom = prcIcon->top + plv->sizeTile.cy - 2 * g_cyIconMargin;
prcLabel->left = prcIcon->right;
prcLabel->right = pitem->pt.x - plv->ptOrigin.x + plv->sizeTile.cx - 2 * g_cxLabelMargin;
prcLabel->top = prcIcon->top;
prcLabel->bottom = prcIcon->bottom;
}
// Note: still need to add rcTileLabelMargin to this diagram
//g_cxLabelMargin g_cxLabelMargin g_cxLabelMargin
// __|__ __|___ __|__ __|___ __|__ __|__
//| | | | | | | | |
// *************************************************************************************************
// * * ^ * * * * *
// * * |-- cyIconMargin * * * * *
// * * v * * * * *
// * ******************************** * * * *
// * * * * * * *
// * * Icon * * * * *
// * * Width: plv->cxIcon + * * * * *
// * * plv->cxState * * * * *
// * * * * * * *
// * * Height: max(plv->cyIcon, * * * Space left for label * *
// * * plv->cyState) * * * * *
// * * * * * * *
// * * * * * * *
// * * * * * * *
// * * * * * * *
// * * * * * * *
// * * * * * * *
// * * * * * * *
// * ******************************** * * * *
// * * ^ * * * * *
// * * |-- cyIconMargin * * * * *
// * * v * * * * *
// *************************************************************************************************
//
// The top and bottom margins of the tile are plv->cyIconMargin, the left and right are plv->cxLabelMargin
// (as shown in the diagram)
// Returns TRUE when iSubItem == 0, and the text wraps to a second line. FALSE otherwise.
// When the return value is TRUE, the height returned in pitem->rcTxtRgn/plsi->sizeText is the height of two lines.
BOOL TCalculateSubItemRect(LV* plv, LISTITEM *pitem, LISTSUBITEM* plsi, int i, int iSubItem, HDC hdc, RECT* prc, BOOL *pbUnfolded)
{
TCHAR szLabel[CCHLABELMAX + 4];
RECT rcSubItem = {0};
LVFAKEDRAW lvfd;
LV_ITEM item;
BOOL fLineWrap = FALSE;
int cchLabel;
if (pbUnfolded)
{
*pbUnfolded = TRUE;
}
// the following will use the passed in pitem text instead of calling
// GetItem. This would be two consecutive calls otherwise, in some cases.
//
if (pitem && (pitem->pszText != LPSTR_TEXTCALLBACK))
{
Str_GetPtr0(pitem->pszText, szLabel, ARRAYSIZE(szLabel));
item.lParam = pitem->lParam;
}
else if (plsi && (plsi->pszText != LPSTR_TEXTCALLBACK))
{
Str_GetPtr0(plsi->pszText, szLabel, ARRAYSIZE(szLabel));
}
else
{
item.mask = LVIF_TEXT | LVIF_PARAM;
item.iItem = i;
item.iSubItem = iSubItem;
item.pszText = szLabel;
item.cchTextMax = ARRAYSIZE(szLabel);
item.stateMask = 0;
szLabel[0] = TEXT('\0'); // In case the OnGetItem fails
ListView_OnGetItem(plv, &item);
if (!item.pszText)
{
SetRectEmpty(&rcSubItem);
goto Exit;
}
if (item.pszText != szLabel)
lstrcpyn(szLabel, item.pszText, ARRAYSIZE(szLabel));
}
cchLabel = lstrlen(szLabel);
if (cchLabel > 0)
{
int cxRoomForLabel = plv->sizeTile.cx
- 5 * g_cxLabelMargin
- plv->cxIcon
- _GetStateCX(plv)
- plv->rcTileLabelMargin.left
- plv->rcTileLabelMargin.right;
int align;
if (hdc)
{
lvfd.nmcd.nmcd.hdc = hdc; // Use the one the app gave us
}
else
{ // Set up fake customdraw
ListView_BeginFakeCustomDraw(plv, &lvfd, &item);
lvfd.nmcd.nmcd.dwItemSpec = i;
lvfd.nmcd.iSubItem = iSubItem;
CIFakeCustomDrawNotify(&plv->ci, CDDS_ITEMPREPAINT | ((iSubItem != 0)?CDDS_SUBITEM:0), &lvfd.nmcd.nmcd);
}
if (plv->dwExStyle & WS_EX_RTLREADING)
{
align = GetTextAlign(lvfd.nmcd.nmcd.hdc);
SetTextAlign(lvfd.nmcd.nmcd.hdc, align | TA_RTLREADING);
}
DrawText(lvfd.nmcd.nmcd.hdc, szLabel, cchLabel, &rcSubItem, (DT_LV | DT_CALCRECT));
if ((iSubItem == 0) && (plv->cSubItems > 0))
{
// Sub Item zero can wrap to two lines (but only if there is room for a second line, i.e. if
// cSubItems > 0. We need to pass this information (that we wrapped to a second
// line, in addition to passing the rect height back) to the caller. The way we determine if we have
// wrapped to a second line, is to call DrawText a second time with word wrapping enabled, and see if the
// RECTHEIGHT is bigger.
RECT rcSubItemWrapped = {0};
LONG lLineHeight = RECTHEIGHT(rcSubItem);
rcSubItemWrapped.right = cxRoomForLabel;
DrawText(lvfd.nmcd.nmcd.hdc, szLabel, cchLabel, &rcSubItemWrapped, (DT_LVTILEWRAP | DT_CALCRECT | DT_WORD_ELLIPSIS));
if (RECTHEIGHT(rcSubItemWrapped) > RECTHEIGHT(rcSubItem))
{
// We wrapped to multiple lines.
fLineWrap = TRUE;
// Don't let us go past two lines.
if (RECTHEIGHT(rcSubItemWrapped) > 2 * RECTHEIGHT(rcSubItem))
rcSubItemWrapped.bottom = rcSubItemWrapped.top + 2 * RECTHEIGHT(rcSubItem);
rcSubItem = rcSubItemWrapped;
}
// Did we asked if we're folded?
if (pbUnfolded)
{
// We need to call draw text again - this time without DT_WORD_ELLIPSES -
// to determine if anything was actually truncated.
RECT rcSubItemWrappedNoEllipse = {0};
int cLines = fLineWrap ? 2 : 1;
rcSubItemWrappedNoEllipse.right = cxRoomForLabel;
DrawText(lvfd.nmcd.nmcd.hdc, szLabel, cchLabel, &rcSubItemWrappedNoEllipse, (DT_LVTILEWRAP | DT_CALCRECT));
if (RECTHEIGHT(rcSubItemWrappedNoEllipse) > (cLines * lLineHeight))
{
*pbUnfolded = FALSE; // We're going to draw truncated.
}
}
}
else if (pbUnfolded)
{
// Are we truncated?
*pbUnfolded = (RECTWIDTH(rcSubItem) <= cxRoomForLabel);
}
if (plv->dwExStyle & WS_EX_RTLREADING)
{
SetTextAlign(lvfd.nmcd.nmcd.hdc, align);
}
// rcSubItem was calculated w/o margins. Now add in margins.
rcSubItem.left -= plv->rcTileLabelMargin.left;
rcSubItem.right += plv->rcTileLabelMargin.right;
// Top and bottom margins are left for the whole label - don't need to be applied here.
if (!hdc)
{ // Clean up fake customdraw
CIFakeCustomDrawNotify(&plv->ci, CDDS_ITEMPOSTPAINT | ((iSubItem != 0)?CDDS_SUBITEM:0), &lvfd.nmcd.nmcd);
ListView_EndFakeCustomDraw(&lvfd);
}
}
else
{
SetRectEmpty(&rcSubItem);
}
Exit:
if (pitem)
{
pitem->rcTextRgn = rcSubItem;
}
else if (plsi)
{
plsi->sizeText.cx = RECTWIDTH(rcSubItem);
plsi->sizeText.cy = RECTHEIGHT(rcSubItem);
}
if (prc)
{
if (rcSubItem.left < prc->left)
prc->left = rcSubItem.left;
if (rcSubItem.right > prc->right)
prc->right = rcSubItem.right;
prc->bottom += RECTHEIGHT(rcSubItem);
}
return fLineWrap;
}
void ListView_TRecomputeLabelSize(LV* plv, LISTITEM* pitem, int i, HDC hdc, BOOL fUsepitem)
{
if (pitem)
{
pitem->cxSingleLabel = 0;
pitem->cxMultiLabel = 0;
pitem->cyFoldedLabel = 0;
pitem->cyUnfoldedLabel = SRECOMPUTE;
}
}
void ListView_TRecomputeLabelSizeInternal(LV* plv, LISTITEM* pitem, int i, HDC hdc, BOOL fUsepitem)
{
RECT rcTotal = {0};
LONG iLastBottom;
LONG iLastDifference = 0; // last line's height
BOOL fLineWrap; // Does the first line of the label wrap to the second?
int iLabelLines = plv->cSubItems; // listview-wide number of lines per tile.
LV_ITEM item; // What to use if pitem is not to be used.
UINT cColumns = 0;
UINT rguColumns[CCMAX_TILE_COLUMNS] = {0};
UINT *puColumns = rguColumns;
// Determine the number of columns to show
if (fUsepitem && (pitem->cColumns != I_COLUMNSCALLBACK))
{
cColumns = pitem->cColumns;
puColumns = pitem->puColumns;
}
else
{
item.mask = LVIF_COLUMNS;
item.iItem = i;
item.iSubItem = 0;
item.stateMask = 0;
item.cColumns = ARRAYSIZE(rguColumns);
item.puColumns = rguColumns;
if (ListView_OnGetItem(plv, &item))
{
cColumns = item.cColumns; // and puColumns = rguColumns
}
}
iLastBottom = rcTotal.bottom;
// The text of the item is determined in TCalculateSubItemRect.
fLineWrap = TCalculateSubItemRect(plv, (fUsepitem ? pitem : NULL), NULL, i, 0, hdc, &rcTotal, NULL);
iLastDifference = rcTotal.bottom - iLastBottom;
if (fLineWrap)
{
iLabelLines--; // One less line for subitems.
// iLastDifference should represent a single line... in this case, it represents two lines. Chop it in half.
iLastDifference /= 2;
}
if (plv->cCol > 0)
{
int iSubItem;
LVTILECOLUMNSENUM lvtce;
_InitTileColumnsEnum(&lvtce, plv, cColumns, puColumns, fLineWrap);
while (-1 != (iSubItem = _GetNextColumn(&lvtce)))
{
LISTSUBITEM* plsi;
HDPA hdpa = ListView_IsOwnerData(plv) ? ListView_GetSubItemDPA(plv, iSubItem - 1) : NULL;
if (hdpa)
plsi = DPA_GetPtr(hdpa, i);
else
plsi = NULL;
iLastBottom = rcTotal.bottom;
TCalculateSubItemRect(plv, NULL, plsi, i, iSubItem, hdc, &rcTotal, NULL);
iLabelLines--;
}
}
// Add the top and bottom margins to rcTotal. Doesn't matter whether they're added to top or bottom,
// since we only consider RECTHEIGHT
rcTotal.bottom += (plv->rcTileLabelMargin.top + plv->rcTileLabelMargin.bottom);
if (pitem)
{
int iStateImageOffset = _GetStateCX(plv);
int cx = (plv->sizeTile.cx - 5 * g_cxLabelMargin - iStateImageOffset - plv->cxIcon);
if (ListView_FullRowSelect(plv)) // full-row-select means full-tile-select
{
pitem->cxSingleLabel = pitem->cxMultiLabel = (short) cx;
}
else
{
if (cx > RECTWIDTH(rcTotal))
cx = RECTWIDTH(rcTotal);
pitem->cxSingleLabel = pitem->cxMultiLabel = (short) cx;
}
pitem->cyFoldedLabel = pitem->cyUnfoldedLabel = (short)RECTHEIGHT(rcTotal);
}
}
/**
* This function calculates the tilesize for listview, based on the following:
* 1) Leave room for margins and padding
* 2) Take into account imagelist and stateimage list.
* 3) For the label portion, take into account
* a) The number of tile columns (plv->cSubItems)
* b) The height and width of a typical letter (leave space for 20 m's?)
*/
void ListView_RecalcTileSize(LV* plv)
{
RECT rcItem = {0};
int cLines;
LVFAKEDRAW lvfd;
LV_ITEM lvitem;
if (plv->dwTileFlags == (LVTVIF_FIXEDHEIGHT | LVTVIF_FIXEDWIDTH))
return; // Nothing to do.
ListView_BeginFakeCustomDraw(plv, &lvfd, &lvitem);
DrawText(lvfd.nmcd.nmcd.hdc, TEXT("m"), 1, &rcItem, (DT_LV | DT_CALCRECT));
// REVIEW: Custom draw functionality needs to be tested.
ListView_EndFakeCustomDraw(&lvfd);
cLines = plv->cSubItems + 1; // +1 because cSubItems doesn't include the main label.
if (!(plv->dwTileFlags & LVTVIF_FIXEDWIDTH))
{
// Here, we are attempting to determine a valid width for the tile, by assuming a typical number
// of chars... and the size is based on TILELABELRATIO * the width of the letter 'm' in the current font.
// This sucks. Without a genuine layout engine though, it is a difficult task. Other options include basing
// the tile width on:
// 1) some fraction of the client width
// 2) the longest label we've got (like the LIST view currently does - this sucks)
// 3) the height (via some ratio)
// After some experimentation, TILELABELRATIO seems to look alright. (Note that a client can always
// set tiles to be an explicit size too.)
plv->sizeTile.cx = 4 * g_cxLabelMargin +
_GetStateCX(plv) +
plv->cxIcon +
plv->rcTileLabelMargin.left +
RECTWIDTH(rcItem) * TILELABELRATIO +
plv->rcTileLabelMargin.right;
}
if (!(plv->dwTileFlags & LVTVIF_FIXEDHEIGHT))
{
int cyIcon = max(_GetStateCY(plv), plv->cyIcon);
int cyText = plv->rcTileLabelMargin.top +
RECTHEIGHT(rcItem) * cLines +
plv->rcTileLabelMargin.bottom;
plv->sizeTile.cy = 4 * g_cyIconMargin + max(cyIcon, cyText);
}
}
/**
* This gets the next tile column base on the LVTILECOLUMNSENUM struct.
* We don't just directly use the column information in LVITEM/LISTITEM structs,
* because we want to take the current sorted column into account. That column
* automatically gets prepended to the columns that are displayed for each item.
*/
int _GetNextColumn(PLVTILECOLUMNSENUM plvtce)
{
if (plvtce->iColumnsRemainingMax > 0)
{
plvtce->iColumnsRemainingMax--;
if (plvtce->bUsedSortedColumn || (plvtce->iSortedColumn <= 0))
{
// We've already used the sorted column, or we've got no sorted column
int iColumn;
do
{
if (plvtce->iCurrentSpecifiedColumn >= plvtce->iTotalSpecifiedColumns)
return -1;
iColumn = plvtce->puSpecifiedColumns[plvtce->iCurrentSpecifiedColumn];
plvtce->iCurrentSpecifiedColumn++;
} while (iColumn == plvtce->iSortedColumn);
return iColumn;
}
else
{
// We have a sorted column, and it has not been used - return it!
plvtce->bUsedSortedColumn = TRUE;
return plvtce->iSortedColumn;
}
}
return -1;
}