You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1075 lines
38 KiB
1075 lines
38 KiB
// 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));
|
|
}
|
|
|
|
#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)
|
|
{
|
|
StringCchCopy(szBuffer, ARRAYSIZE(szBuffer), item.pszText);
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
StringCchCopy(szBuffer, ARRAYSIZE(szBuffer), lvi.pszText);
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
StringCchCopy(szLabel, ARRAYSIZE(szLabel), item.pszText);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|