// 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; }