From: Huw Campbell <huw.campbell@gmail.com> --- dlls/comctl32/listview.c | 82 ++++++++++++++++++++-------------- dlls/comctl32/tests/listview.c | 7 +++ 2 files changed, 55 insertions(+), 34 deletions(-) diff --git a/dlls/comctl32/listview.c b/dlls/comctl32/listview.c index b83bad11680..ef6e2e83c73 100644 --- a/dlls/comctl32/listview.c +++ b/dlls/comctl32/listview.c @@ -554,7 +554,7 @@ static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW) /******** PUColumn ownership function *************************************/ -/* Similar to Text entries, the list view owns puColumn data arrays. */ +/* The list view owns puColumn data arrays. */ /* Helper function to do the allocation */ static inline UINT* puColumnsDup(UINT cColumns, UINT* puColumns) @@ -568,8 +568,8 @@ static inline UINT* puColumnsDup(UINT cColumns, UINT* puColumns) * Dest is a pointer to a managed puColumns we are copying an owned copy to. * Src is an existing puColumns passed from the user. * - * If cColumns is I_COLUMNCALLBACK, it won't be in the range and we won't - * try to copy the source data. + * If cColumns is I_COLUMNSCALLBACK, it won't be in the range and we won't + * try to copy from the pointer. */ static inline void puColumnsSetPtr(UINT **dest, UINT* src, UINT cColumns) { @@ -577,9 +577,20 @@ static inline void puColumnsSetPtr(UINT **dest, UINT* src, UINT cColumns) if (src && cColumns > 0 && cColumns < 20) *dest = puColumnsDup(cColumns, src); else - *dest = NULL; + *dest = NULL; } +/* + * Compare two puColumn entries for equality when their cColumns entries + * have the same value. + */ +static inline INT puColumnsCmp(PUINT at, PUINT bt, UINT cColumns) +{ + if (!cColumns || cColumns == I_COLUMNSCALLBACK) return 0; + if (!at) return bt ? -1 : 0; + if (!bt) return 1; + return memcmp(at, bt, sizeof(UINT) * cColumns); +} /******** Debugging functions *****************************************/ @@ -2469,9 +2480,9 @@ static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW Icon.bottom += infoPtr->iconSize.cy; } } - else if (lpLVItem->iSubItem == 0 && infoPtr->uView == LV_VIEW_TILE) + else if (infoPtr->uView == LV_VIEW_TILE) { - Icon.left = Box.left + state_width; + Icon.left = Box.left; Icon.top = Box.top; Icon.right = Icon.left; Icon.bottom = Icon.top; @@ -4384,7 +4395,9 @@ static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW)) uChanged |= LVIF_TEXT; - if ((lpLVItem->mask & LVIF_COLUMNS) && (lpItem->cColumns != lpLVItem->cColumns || (lpItem->puColumns != lpLVItem->puColumns))) + if (lpLVItem->mask & LVIF_COLUMNS && ( + lpItem->cColumns != lpLVItem->cColumns || puColumnsCmp(lpItem->puColumns, lpLVItem->puColumns, lpItem->cColumns)) + ) uChanged |= LVIF_COLUMNS; TRACE("change mask=0x%x\n", uChanged); @@ -4713,7 +4726,7 @@ static int LISTVIEW_GetTileViewInfo(const LISTVIEW_INFO *infoPtr, PLVTILEVIEWINF */ static BOOL LISTVIEW_SetTileInfo(LISTVIEW_INFO *infoPtr, PLVTILEINFO lParam) { - LVITEMW lvItem; + LVITEMW lvItem = {0}; if (lParam->iItem < 0 || lParam->iItem >= infoPtr->nItemCount) return FALSE; if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE; @@ -4839,6 +4852,7 @@ static void LISTVIEW_DrawItemPart(LISTVIEW_INFO *infoPtr, LVITEMW *item, const N HIMAGELIST himl; UINT format; RECT *focus; + BOOL isTileSubitem = infoPtr->uView == LV_VIEW_TILE && item->iSubItem; /* now check if we need to update the focus rectangle */ focus = infoPtr->bFocus && (item->state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0; @@ -4871,7 +4885,9 @@ static void LISTVIEW_DrawItemPart(LISTVIEW_INFO *infoPtr, LVITEMW *item, const N /* in detail mode, we want to paint background for label rect when * item is not selected or listview has full row select; otherwise paint * background for text only */ - if ( infoPtr->uView == LV_VIEW_ICON || + /* finally, for tile subitems, we don't want to blat other labels in the + * select area, just this label's space. */ + if ( infoPtr->uView == LV_VIEW_ICON || isTileSubitem || (infoPtr->uView == LV_VIEW_DETAILS && (!(item->state & LVIS_SELECTED) || (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)))) background = &rcLabel; @@ -4907,6 +4923,10 @@ static void LISTVIEW_DrawItemPart(LISTVIEW_INFO *infoPtr, LVITEMW *item, const N } infoPtr->rcFocus = rcSelect; } + else if (isTileSubitem) + /* in tile mode the focus box wraps across all the labels; as the subitems are drawn after + * the main item, that means we can take a union here. */ + UnionRect(&infoPtr->rcFocus, &infoPtr->rcFocus, &rcLabel); else infoPtr->rcFocus = rcLabel; } @@ -4924,7 +4944,8 @@ static void LISTVIEW_DrawItemPart(LISTVIEW_INFO *infoPtr, LVITEMW *item, const N /* item icons */ himl = ((infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_TILE) ? infoPtr->himlNormal : infoPtr->himlSmall); - if (himl && item->iImage >= 0 && !IsRectEmpty(&rcIcon)) + /* draw the icon if it exists and we're not the subitem in tile mode (the main item will draw it) */ + if (himl && item->iImage >= 0 && !IsRectEmpty(&rcIcon) && !isTileSubitem) { UINT style; @@ -4945,7 +4966,6 @@ static void LISTVIEW_DrawItemPart(LISTVIEW_INFO *infoPtr, LVITEMW *item, const N if (infoPtr->hwndEdit && item->iItem == infoPtr->nEditLabelItem && item->iSubItem == 0) return; /* figure out the text drawing flags */ - /* now figure out the flags */ if (infoPtr->uView == LV_VIEW_ICON) format = focus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS; if (infoPtr->uView == LV_VIEW_TILE) @@ -5067,9 +5087,10 @@ static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, ITERAT lvItem.lParam = 0; lvItem.cchTextMax = DISP_TEXT_SIZE; lvItem.pszText = szDispText; + szDispText[0] = 0; if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE; if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) - lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED); + lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED); if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = callbackW; TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE)); @@ -5108,7 +5129,7 @@ static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, ITERAT * Reports mode is a bit different there, as there can be many columns, so it has to be careful to * only pull the few required, as many could be off screen. * - * NOTE: TileView Positions + * NOTE: TileView Positions * Because these are laid out in order, and their displacement depends on those preceding them, * we pack some side channel information into lvItem.cColumns. */ @@ -7075,14 +7096,15 @@ static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, } /* Only items support tileview columns descriptions */ - if ((lpLVItem->mask & LVIF_COLUMNS) && lpItem->cColumns == I_COLUMNSCALLBACK && !lpLVItem->iSubItem) + if ((lpLVItem->mask & LVIF_COLUMNS) && lpItem->cColumns == I_COLUMNSCALLBACK && + !lpLVItem->iSubItem && lpLVItem->cColumns <= 20) { - /* Max size of cColumns is 20, allocate a buffer of this size to pass to the application. - * Its job is to set the max to the correct number and put the items into this buffer, - * we retain ownership of this buffer. */ + /* Max size of cColumns is 20, allocate a buffer of the requested size to pass to the + * application. Its job is to set the max to the correct number and put the items into, + * this buffer, while we retain ownership of it. */ dispInfo.item.mask |= LVIF_COLUMNS; - dispInfo.item.cColumns = 20; - dispInfo.item.puColumns = Alloc(20 * sizeof(UINT)); + dispInfo.item.cColumns = lpLVItem->cColumns; + dispInfo.item.puColumns = Alloc(lpLVItem->cColumns * sizeof(UINT)); } /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */ @@ -9603,12 +9625,14 @@ static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL unicode) static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView) { HIMAGELIST himl; + BOOL isNormal; if (infoPtr->uView == nView) return 1; if ((INT)nView < 0 || nView > LV_VIEW_MAX) return -1; infoPtr->uView = nView; + isNormal = nView == LV_VIEW_ICON || nView == LV_VIEW_TILE; SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0); ShowWindow(infoPtr->hwndHeader, SW_HIDE); @@ -9616,18 +9640,10 @@ static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView) ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE); SetRectEmpty(&infoPtr->rcFocus); - himl = (nView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall); - set_icon_size(&infoPtr->iconSize, himl, nView != LV_VIEW_ICON); - LISTVIEW_UpdateItemSize(infoPtr); + himl = isNormal ? infoPtr->himlNormal : infoPtr->himlSmall; + set_icon_size(&infoPtr->iconSize, himl, !isNormal); - switch (nView) - { - case LV_VIEW_ICON: - case LV_VIEW_SMALLICON: - case LV_VIEW_TILE: - LISTVIEW_Arrange(infoPtr, LVA_DEFAULT); - break; - case LV_VIEW_DETAILS: + if (nView == LV_VIEW_DETAILS) { HDLAYOUT hl; WINDOWPOS wp; @@ -9639,12 +9655,10 @@ static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView) SendMessageW(infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl); SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy, wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW)); - break; - } - case LV_VIEW_LIST: - break; } + LISTVIEW_UpdateItemSize(infoPtr); + LISTVIEW_Arrange(infoPtr, LVA_DEFAULT); LISTVIEW_UpdateSize(infoPtr); LISTVIEW_UpdateScroll(infoPtr); LISTVIEW_InvalidateList(infoPtr); diff --git a/dlls/comctl32/tests/listview.c b/dlls/comctl32/tests/listview.c index 1609c8d70cc..777a9dce153 100644 --- a/dlls/comctl32/tests/listview.c +++ b/dlls/comctl32/tests/listview.c @@ -770,6 +770,13 @@ static LRESULT WINAPI parent_wnd_proc(HWND hwnd, UINT message, WPARAM wParam, LP ok(dispinfo->item.cchTextMax == 260 || broken(dispinfo->item.cchTextMax == 264) /* NT4 reports aligned size */, "buffer size %d\n", dispinfo->item.cchTextMax); + + if (dispinfo->item.mask & LVIF_COLUMNS) + { + dispinfo->item.cColumns = 2; + dispinfo->item.puColumns[0] = 7; + dispinfo->item.puColumns[1] = 6; + } } break; case LVN_DELETEITEM: -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10191