[PATCH 0/1] MR10191: comctl32: Implement Tile view style
The Tile View in common controls offers a compact and pretty nice way to show some additional context within the list view, without resorting to the full reports mode. It's used a lot in explorer like programs where things like the disk size and file type are shown. But, it's been sitting unimplemented in wine for a long time. I've just spent a few days implementing this by: - Building a small test program in windows to get a feel for how it works - Hacking just enough to get something displaying - Writing a test suite to try and understand the ownership model, getting, and setting, and the various messages passed by windows. - Fixing up the implementation. - Adjusting my program to use I_COLUMNCALLBACK in windows and testing that. - Fixing my implementation to match the behaviour observed. Full disclosure, I wrote this implementation within the ReactOS build system then copied back the diff as I had that compiling already on my Windows box, and my Mac is an M1 so doesn't play very nicely with Wine. So while I built this code on my Mac in Wine, I can't actually run it there. I'll install a Linux virtual machine at some point and do some testing there too. All bugs and misunderstandings are my own; I used clean room techniques (read MSDN, write a test program, write tests, compare results, iterate). Interesting things: - I initially wrote layout code for tiling mode as well, but a few rounds of playing with my test program and various layouts and settings (things like `LVS_ALIGNTOP`, `LVS_AUTOARRANGE` and `LVS_ALIGNLEFT`) for both tile mode and icon mode convinced me that they same layout logic (or close enough to). - I noticed some differences in Wine's Icon mode layout as compared to Windows 10. - I didn't fix these, one thing at a time. - I noticed and fixed a bug in SetView, where `UpdateItemSize` was called _after_ `Arrange`; that doesn't work. - The Text colours are really odd, if set to black the sub items show up gray, but any other colour and they're the same as the main. - Layout is tricky, in windows it looks like the first item (file name for example) can flow to 2 lines, and subitems are restricted to 1 - But this is really subtle, using GetItemMetrics kind of assumes there's no 'interference' from other items. - If we're on subitem 2, I don't want to have to fetch 0 and 1 to render their sizes before calculating my own, it seems like an `O(N^2)` situation off the bat. - I used a sidechannel from DrawItems, I'm open to other ideas here. - I haven't tested message passing, many of the test failures for listview at the moment look like sequencing errors for messages. I didn't look into it. Next Steps ========== - Test properly in Linux - Hope for feedback here (particularly for messages) If Merged ========= Now that I have a pretty good grasp of how this component works and lays things out, once this is merged I'll see if I can do Groups as well. Pictures: ======== {width=400} {width=300} Test Program =========== ```C #include <windows.h> #include <commctrl.h> HINSTANCE g_hInst; HWND g_hLV; static void AddItems(HWND hLV) { static UINT puColumns[2] = {1,2}; LVITEMW it = {0}; it.mask = LVIF_TEXT | LVIF_COLUMNS; it.cColumns = I_COLUMNSCALLBACK; it.iItem = 0; it.iSubItem = 0; it.pszText = LPSTR_TEXTCALLBACK; ListView_InsertItem(hLV, &it); ListView_SetItemText(hLV, 0, 1, L"Static"); ListView_SetItemText(hLV, 0, 2, L"1 KB"); ListView_SetItemText(hLV, 0, 3, L"2026-02-23 12:00"); it.cColumns = 2; it.puColumns = puColumns; it.iItem = 1; it.iSubItem = 0; it.pszText = L"bravo.txt"; ListView_InsertItem(hLV, &it); ListView_SetItemText(hLV, 1, 1, L"Text"); ListView_SetItemText(hLV, 1, 2, L"12 KB"); ListView_SetItemText(hLV, 1, 3, L"2026-02-22 09:30"); } static void AddColumns(HWND hLV) { LVCOLUMNW col = {0}; col.mask = LVCF_TEXT | LVCF_WIDTH; col.cx = 160; col.pszText = L"Name"; ListView_InsertColumn(hLV, 0, &col); col.cx = 140; col.pszText = L"Type"; ListView_InsertColumn(hLV, 1, &col); col.cx = 100; col.pszText = L"Size"; ListView_InsertColumn(hLV, 2, &col); col.cx = 180; col.pszText = L"Modified"; ListView_InsertColumn(hLV, 3, &col); } static void EnableTileView(HWND hLV) { SIZE size = { 100, 70 }; LVTILEVIEWINFO tileViewInfo = {0}; tileViewInfo.cbSize = sizeof(tileViewInfo); tileViewInfo.dwFlags = LVTVIF_FIXEDSIZE; tileViewInfo.dwMask = LVTVIM_COLUMNS | LVTVIM_TILESIZE; tileViewInfo.cLines = 3; tileViewInfo.sizeTile = size; ListView_SetTileViewInfo(hLV, &tileViewInfo); ListView_SetView(hLV, LV_VIEW_TILE); } static void OnGetDispInfo(NMLVDISPINFO* pnmv) { if (pnmv->item.iItem < 0) { return; } if (pnmv->item.mask & LVIF_TEXT) { // Demonstrates that Windows is passing us a size 20 buffer to write to. pnmv->item.pszText = pnmv->item.cColumns == 20 ? L"Yes" : L"No"; } if (pnmv->item.mask & LVIF_IMAGE) { pnmv->item.iImage = -1; } if (pnmv->item.mask & LVIF_STATE) { pnmv->item.state = 0; } if (pnmv->item.mask & LVIF_COLUMNS) { pnmv->item.cColumns = 2; pnmv->item.puColumns[0] = 1; pnmv->item.puColumns[1] = 2; } } static LRESULT OnNotify(WPARAM wParam, LPARAM lParam) { switch (((LPNMHDR)lParam)->code) { case LVN_GETDISPINFO: OnGetDispInfo((NMLVDISPINFO*) lParam); break; } return TRUE; } static LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: { g_hLV = CreateWindowExW(WS_EX_CLIENTEDGE, WC_LISTVIEWW, L"", WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_ALIGNTOP | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_AUTOARRANGE, 0, 0, 0, 0, hWnd, (HMENU)1, g_hInst, NULL); ListView_SetExtendedListViewStyle(g_hLV, LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP); AddColumns(g_hLV); AddItems(g_hLV); EnableTileView(g_hLV); return 0; } case WM_SIZE: if (g_hLV) MoveWindow(g_hLV, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; case WM_NOTIFY: OnNotify(wParam, lParam); return 0; } return DefWindowProcW(hWnd, msg, wParam, lParam); } int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) { INITCOMMONCONTROLSEX iccx; g_hInst = hInstance; /* Initialize common controls */ iccx.dwSize = sizeof(iccx); iccx.dwICC = ICC_STANDARD_CLASSES | ICC_WIN95_CLASSES; InitCommonControlsEx(&iccx); WNDCLASSW wc = {0}; wc.lpfnWndProc = WndProc; wc.hInstance = hInstance; wc.lpszClassName = L"WineLvTileSmoke"; wc.hCursor = LoadCursor(NULL, IDC_ARROW); RegisterClassW(&wc); HWND hWnd = CreateWindowExW(0, wc.lpszClassName, L"ListView Tile puColumns Smoke Test", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 700, 450, NULL, NULL, hInstance, NULL); ShowWindow(hWnd, nCmdShow); MSG msg; while (GetMessageW(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessageW(&msg); } return (int)msg.wParam; } ``` -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10191
From: Huw Campbell <huw.campbell@gmail.com> --- dlls/comctl32/listview.c | 399 ++++++++++++++++++++++++++++----- dlls/comctl32/tests/listview.c | 200 +++++++++++++++++ 2 files changed, 547 insertions(+), 52 deletions(-) diff --git a/dlls/comctl32/listview.c b/dlls/comctl32/listview.c index f91e63f1f5b..b83bad11680 100644 --- a/dlls/comctl32/listview.c +++ b/dlls/comctl32/listview.c @@ -100,8 +100,6 @@ * -- LVM_GETNUMBEROFWORKAREAS * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA - * -- LVM_GETTILEINFO, LVM_SETTILEINFO - * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS * -- LVM_INSERTGROUPSORTED @@ -172,6 +170,8 @@ typedef struct tagITEM_INFO LPARAM lParam; INT iIndent; ITEM_ID *id; + UINT cColumns; + PUINT puColumns; } ITEM_INFO; struct tagITEM_ID @@ -253,6 +253,11 @@ typedef struct tagLISTVIEW_INFO INT nItemHeight; INT nItemWidth; + /* tile settings */ + INT tileWidth; + INT tileHeight; + INT tileLines; + /* style */ DWORD dwStyle; /* the cached window GWL_STYLE */ DWORD dwLvExStyle; /* extended listview style */ @@ -301,6 +306,7 @@ typedef struct tagLISTVIEW_INFO HFONT hDefaultFont; HFONT hFont; INT ntmHeight; /* Some cached metrics of the font used */ + INT ntmExternalLeading; /* Some cached metrics of the font used */ INT ntmMaxCharWidth; /* by the listview to draw items */ INT nEllipsisWidth; @@ -397,6 +403,8 @@ typedef struct tagLISTVIEW_INFO #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS) #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP) #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS) +/* DrawText flags for Tile View */ +#define LV_TL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS) /* Image index from state */ #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12) @@ -538,11 +546,41 @@ static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW) int r = bw ? lstrcmpW(aw, bw) : 1; textfreeT(bw, isW); return r; - } - + } + return 1; } + +/******** PUColumn ownership function *************************************/ + +/* Similar to Text entries, the list view owns puColumn data arrays. */ + +/* Helper function to do the allocation */ +static inline UINT* puColumnsDup(UINT cColumns, UINT* puColumns) +{ + UINT* ret = Alloc(cColumns * sizeof(UINT)); + if (ret) memcpy(ret, puColumns, cColumns * sizeof(UINT)); + return ret; +} + +/* + * 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. + */ +static inline void puColumnsSetPtr(UINT **dest, UINT* src, UINT cColumns) +{ + if (*dest) Free(*dest); + if (src && cColumns > 0 && cColumns < 20) + *dest = puColumnsDup(cColumns, src); + else + *dest = NULL; +} + + /******** Debugging functions *****************************************/ static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW) @@ -1054,6 +1092,10 @@ static int notify_odfinditem(const LISTVIEW_INFO *infoPtr, NMLVFINDITEMW *nmlv) return ret; } +static inline BOOL is_icon_like(const LISTVIEW_INFO *infoPtr) { + return infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_TILE; +} + static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *rcBounds, const LVITEMW *lplvItem) { @@ -1110,9 +1152,13 @@ static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, const NMLVCUS } } + /* Subitems in Tile mode are drawn gray if the colour is set to the default; + * otherwise, the colour of subitems is the same as the main text. */ if (backcolor == CLR_DEFAULT) backcolor = comctl32_color.clrWindow; - if (textcolor == CLR_DEFAULT) + if (textcolor == CLR_DEFAULT && infoPtr->uView == LV_VIEW_TILE && SubItem) + textcolor = comctl32_color.clrGrayText; + else if (textcolor == CLR_DEFAULT) textcolor = comctl32_color.clrWindowText; /* Set the text attributes */ @@ -1373,10 +1419,10 @@ static BOOL iterator_frameditems_absolute(ITERATOR* i, const LISTVIEW_INFO* info if (infoPtr->nItemCount == 0) return TRUE; - if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON) + if (is_icon_like(infoPtr)) { INT nItem; - + if (infoPtr->uView == LV_VIEW_ICON && infoPtr->nFocusedItem != -1) { LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem); @@ -1603,8 +1649,7 @@ static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg, static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr) { - return (infoPtr->dwStyle & LVS_AUTOARRANGE) && - (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON); + return (infoPtr->dwStyle & LVS_AUTOARRANGE) && is_icon_like(infoPtr); } static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem) @@ -1686,6 +1731,11 @@ static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr) if (infoPtr->hwndHeader) return 0; + /* initialise tile view defaults */ + infoPtr->tileWidth = 0; /* auto-calculate */ + infoPtr->tileHeight = 0; /* auto-calculate */ + infoPtr->tileLines = 3; /* Is there a default? */ + TRACE("Creating header for list %p\n", infoPtr->hwndSelf); /* setup creation flags */ @@ -1767,9 +1817,9 @@ static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT { POINT Origin, Position; RECT rcBox; - - if(!is_redrawing(infoPtr)) return; - assert (infoPtr->uView == LV_VIEW_DETAILS); + + if(!is_redrawing(infoPtr)) return; + assert (infoPtr->uView == LV_VIEW_DETAILS || infoPtr->uView == LV_VIEW_TILE); LISTVIEW_GetOrigin(infoPtr, &Origin); LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position); LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox); @@ -2063,7 +2113,7 @@ static INT LISTVIEW_UpdateHScroll(LISTVIEW_INFO *infoPtr) { horzInfo.nMax = infoPtr->nItemWidth; } - else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */ + else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON or LV_VIEW_TILE */ { RECT rcView; @@ -2287,7 +2337,7 @@ static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPO { assert(nItem >= 0 && nItem < infoPtr->nItemCount); - if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON)) + if (is_icon_like(infoPtr)) { lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem); lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem); @@ -2362,7 +2412,7 @@ static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE)); /* Be smart and try to figure out the minimum we have to do */ - if (lpLVItem->iSubItem) assert(infoPtr->uView == LV_VIEW_DETAILS); + if (lpLVItem->iSubItem) assert(infoPtr->uView == LV_VIEW_DETAILS || infoPtr->uView == LV_VIEW_TILE); if (infoPtr->uView == LV_VIEW_ICON && (lprcBox || lprcLabel)) { assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED)); @@ -2380,10 +2430,10 @@ static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW /************************************************************/ /* compute the box rectangle (it should be cheap to do) */ /************************************************************/ - if (lpLVItem->iSubItem || infoPtr->uView == LV_VIEW_DETAILS) + if (infoPtr->uView == LV_VIEW_DETAILS) lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem); - if (lpLVItem->iSubItem) + if (lpLVItem->iSubItem && infoPtr->uView == LV_VIEW_DETAILS) { Box = lpColumnInfo->rcHeader; } @@ -2419,6 +2469,18 @@ 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) + { + Icon.left = Box.left + state_width; + Icon.top = Box.top; + Icon.right = Icon.left; + Icon.bottom = Icon.top; + if (infoPtr->himlNormal) + { + Icon.right += infoPtr->iconSize.cx; + Icon.bottom += infoPtr->iconSize.cy; + } + } else /* LV_VIEW_SMALLICON, LV_VIEW_LIST or LV_VIEW_DETAILS */ { Icon.left = Box.left + state_width; @@ -2470,13 +2532,14 @@ static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW } } - if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && infoPtr->uView == LV_VIEW_DETAILS)) + /* for details mode we can use the column size for the label size. */ + if (infoPtr->uView == LV_VIEW_DETAILS && (lpLVItem->iSubItem || (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))) { labelSize.cx = infoPtr->nItemWidth; labelSize.cy = infoPtr->nItemHeight; goto calc_label; } - + /* we need the text in non owner draw mode */ assert(lpLVItem->mask & LVIF_TEXT); if (is_text(lpLVItem->pszText)) @@ -2497,9 +2560,11 @@ static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW /* now figure out the flags */ if (infoPtr->uView == LV_VIEW_ICON) uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS; + if (infoPtr->uView == LV_VIEW_TILE) + uFormat = LV_TL_DT_FLAGS; else uFormat = LV_SL_DT_FLAGS; - + DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT); if (rcText.right != rcText.left) @@ -2528,6 +2593,29 @@ calc_label: } Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING; } + else if (infoPtr->uView == LV_VIEW_TILE) + { + Label.left = Icon.right; + if (lpLVItem->iSubItem == 0) { + Label.top = Box.top; + Label.right = Label.left + labelSize.cx; + Label.bottom = Label.top + labelSize.cy;; + } + else if (infoPtr->tileLines && lpLVItem->cColumns) { + /* Cludgy side channel. Subitems can't have cColumns; and the fetch won't overwrite it, so put + * the index of the sub-item into the cColumns field. */ + Label.top = Box.top + lpLVItem->cColumns * (infoPtr->ntmHeight + infoPtr->ntmExternalLeading); + Label.right = Label.left + labelSize.cx; + Label.bottom = Label.top + labelSize.cy; + + /* Make sure we're not overflowing the box, if items don't fit it's ok to not draw them. */ + if (Label.bottom > Box.bottom) + Label.bottom = Label.top; + } else { + Label.top = Label.bottom = Box.top; + Label.right = Label.left; + } + } else if (infoPtr->uView == LV_VIEW_DETAILS) { Label.left = Icon.right; @@ -2806,8 +2894,8 @@ static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode) POINT pos; INT i; - if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON) return FALSE; - + if (!is_icon_like(infoPtr)) return FALSE; + TRACE("nAlignCode=%d\n", nAlignCode); if (nAlignCode == LVA_DEFAULT) @@ -2857,6 +2945,7 @@ static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView) { case LV_VIEW_ICON: case LV_VIEW_SMALLICON: + case LV_VIEW_TILE: for (i = 0; i < infoPtr->nItemCount; i++) { x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i); @@ -2975,6 +3064,12 @@ static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr) nItemWidth = rcHeader.right; } } + else if (infoPtr->uView == LV_VIEW_TILE) { + if (infoPtr->tileWidth > 0) + nItemWidth = infoPtr->tileWidth; + else + nItemWidth = infoPtr->iconSize.cx + ICON_LR_PADDING + DEFAULT_COLUMN_WIDTH; + } else /* LV_VIEW_SMALLICON, or LV_VIEW_LIST */ { WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' }; @@ -3021,6 +3116,12 @@ static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr) if (infoPtr->uView == LV_VIEW_ICON) nItemHeight = infoPtr->iconSpacing.cy; + else if (infoPtr->uView == LV_VIEW_TILE) { + if (infoPtr->tileHeight > 0) + nItemHeight = max(infoPtr->ntmHeight, infoPtr->tileHeight); + else + nItemHeight = max(infoPtr->ntmHeight, infoPtr->iconSize.cy) + HEIGHT_PADDING; + } else { nItemHeight = infoPtr->ntmHeight; @@ -4282,9 +4383,12 @@ 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))) + uChanged |= LVIF_COLUMNS; + TRACE("change mask=0x%x\n", uChanged); - + memset(&nmlv, 0, sizeof(NMLISTVIEW)); nmlv.iItem = lpLVItem->iItem; if (lpLVItem->mask & LVIF_STATE) @@ -4339,6 +4443,11 @@ static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL if (lpLVItem->mask & LVIF_INDENT) lpItem->iIndent = lpLVItem->iIndent; + if (lpLVItem->mask & LVIF_COLUMNS) { + lpItem->cColumns = lpLVItem->cColumns; + puColumnsSetPtr(&lpItem->puColumns, lpLVItem->puColumns, lpLVItem->cColumns); + } + if (uChanged & LVIF_STATE) { if (lpItem && (stateMask & ~infoPtr->uCallbackMask)) @@ -4546,6 +4655,101 @@ static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL is return bResult; } +/*** + * DESCRIPTION: + * Gets the Tile Info for an item in the list. + * + * PARAMETER(S): + * [I] hwnd : window handle + * [I] lParam : tile info data + * + * RETURN Not Used + */ +static int LISTVIEW_GetTileInfo(const LISTVIEW_INFO *infoPtr, PLVTILEINFO lParam) { + LVITEMW item = {0}; + item.iItem = lParam->iItem; + item.mask = LVIF_COLUMNS; + item.cColumns = lParam->cColumns; + item.puColumns = lParam->puColumns; + LISTVIEW_GetItemW(infoPtr, &item); + /* puColumns is a pointer and isn't changed, but we need to copy back the size. */ + lParam->cColumns = item.cColumns; + return TRUE; +} + +/*** + * DESCRIPTION: + * Gets the Tile Info for an item in the list. + * + * PARAMETER(S): + * [I] hwnd : window handle + * [I] lParam : tile info data + * + * RETURN Not Used + */ +static int LISTVIEW_GetTileViewInfo(const LISTVIEW_INFO *infoPtr, PLVTILEVIEWINFO lParam) { + if (lParam->dwMask & LVTVIM_TILESIZE) { + lParam->sizeTile.cx = infoPtr->tileWidth; + lParam->sizeTile.cy = infoPtr->tileHeight; + } + if (lParam->dwMask & LVTVIM_COLUMNS) { + lParam->cLines = infoPtr->tileLines; + } + + return TRUE; +} + +/*** + * DESCRIPTION: + * Sets the Tile Info for an item in the list. + * + * PARAMETER(S): + * [I] hwnd : window handle + * [I] lParam : tile info data + * + * RETURN: + * SUCCESS : TRUE + * FAILURE : FALSE + */ +static BOOL LISTVIEW_SetTileInfo(LISTVIEW_INFO *infoPtr, PLVTILEINFO lParam) +{ + LVITEMW lvItem; + if (lParam->iItem < 0 || lParam->iItem >= infoPtr->nItemCount) return FALSE; + if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE; + + lvItem.iItem = lParam->iItem; + lvItem.mask = LVIF_COLUMNS; + lvItem.cColumns = lParam->cColumns; + lvItem.puColumns = lParam->puColumns; + + TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", lParam->iItem, debuglvitem_t(&lvItem, TRUE), TRUE); + + return LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE); +} + + +/*** + * DESCRIPTION: + * Gets the Tile Info for an item in the list. + * + * PARAMETER(S): + * [I] hwnd : window handle + * [I] lParam : tile info data + * + * RETURN Not Used + */ +static int LISTVIEW_SetTileViewInfo(LISTVIEW_INFO *infoPtr, PLVTILEVIEWINFO lParam) { + if (lParam->dwMask & LVTVIM_TILESIZE) { + infoPtr->tileWidth = lParam->dwFlags & LVTVIF_FIXEDWIDTH ? lParam->sizeTile.cx : 0; + infoPtr->tileHeight = lParam->dwFlags & LVTVIF_FIXEDHEIGHT ? lParam->sizeTile.cy : 0; + } + if (lParam->dwMask & LVTVIM_COLUMNS) { + infoPtr->tileLines = lParam->cLines; + } + + return TRUE; +} + /*** * DESCRIPTION: * Retrieves the index of the item at coordinate (0, 0) of the client area. @@ -4719,7 +4923,7 @@ static void LISTVIEW_DrawItemPart(LISTVIEW_INFO *infoPtr, LVITEMW *item, const N } /* item icons */ - himl = (infoPtr->uView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall); + himl = ((infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_TILE) ? infoPtr->himlNormal : infoPtr->himlSmall); if (himl && item->iImage >= 0 && !IsRectEmpty(&rcIcon)) { UINT style; @@ -4741,10 +4945,15 @@ 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 */ - format = (infoPtr->uView == LV_VIEW_ICON ? (focus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS); + /* now figure out the flags */ if (infoPtr->uView == LV_VIEW_ICON) - format = (focus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS); - else if (item->iSubItem) + format = focus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS; + if (infoPtr->uView == LV_VIEW_TILE) + format = LV_TL_DT_FLAGS; + else + format = LV_SL_DT_FLAGS; + + if (infoPtr->uView == LV_VIEW_DETAILS && item->iSubItem) { switch (LISTVIEW_GetColumnInfo(infoPtr, item->iSubItem)->fmt & LVCFMT_JUSTIFYMASK) { @@ -4790,20 +4999,25 @@ static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, ITERAT DWORD cdsubitemmode = CDRF_DODEFAULT; RECT *focus, rcBox; NMLVCUSTOMDRAW nmlvcd; - LVITEMW lvItem; + LVITEMW lvItem = {0}; + UINT puColumns[20] = {0}; + INT numTileLines; TRACE("(hdc=%p, nItem=%d, subitems=%p, pos=%s)\n", hdc, nItem, subitems, wine_dbgstr_point(&pos)); /* get information needed for drawing the item */ lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_STATE; if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT; + if (infoPtr->uView == LV_VIEW_TILE) { + lvItem.mask |= LVIF_COLUMNS; + lvItem.cColumns = 20; + lvItem.puColumns = puColumns; + } lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK | LVIS_CUT | LVIS_OVERLAYMASK; lvItem.iItem = nItem; - lvItem.iSubItem = 0; - lvItem.state = 0; - lvItem.lParam = 0; lvItem.cchTextMax = DISP_TEXT_SIZE; lvItem.pszText = szDispText; + if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE; if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = callbackW; TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE)); @@ -4853,7 +5067,6 @@ 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); @@ -4885,6 +5098,41 @@ static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, ITERAT { prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE); LISTVIEW_DrawItemPart(infoPtr, &lvItem, &nmlvcd, &pos); + + /* Tile mode handling of subitems. + * + * Handle subitem and rendering here, as the call for DrawItems (LISTVIEW_RefreshList) didn't + * yet have access to the puColumns array, so didn't directly know which columns to render and + * in which order. + * + * 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 + * Because these are laid out in order, and their displacement depends on those preceding them, + * we pack some side channel information into lvItem.cColumns. + */ + + if (infoPtr->uView == LV_VIEW_TILE && infoPtr->tileLines) + { + lvItem.mask = LVIF_TEXT; + lvItem.puColumns = NULL; + + numTileLines = min(infoPtr->tileLines, lvItem.cColumns); + for (UINT subIx = 0; subIx < numTileLines; subIx++) + { + lvItem.cColumns = subIx + 1; + lvItem.iSubItem = puColumns[subIx]; + lvItem.cchTextMax = DISP_TEXT_SIZE; + lvItem.pszText = szDispText; + szDispText[0] = 0; + + if (LISTVIEW_GetItemW(infoPtr, &lvItem)) { + prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE); + LISTVIEW_DrawItemPart(infoPtr, &lvItem, &nmlvcd, &pos); + } + } + } } postpaint: @@ -5599,6 +5847,7 @@ static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy) if (is_text(hdrItem->pszText)) Free(hdrItem->pszText); Free(hdrItem); } + if (lpItem->puColumns) Free(lpItem->puColumns); DPA_Destroy(hdpaSubItems); DPA_DeletePtr(infoPtr->hdpaItems, i); } @@ -5855,13 +6104,13 @@ static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir) static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem) { LVITEMW item; - const BOOL is_icon = (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON); + const BOOL is_icon = is_icon_like(infoPtr); INT focus = infoPtr->nFocusedItem; TRACE("(nItem=%d)\n", nItem); if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE; - + /* remove selection, and focus */ item.state = 0; item.stateMask = LVIS_SELECTED | LVIS_FOCUSED; @@ -5896,6 +6145,7 @@ static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem) if (is_text(hdrItem->pszText)) Free(hdrItem->pszText); Free(hdrItem); } + if (lpItem->puColumns) Free(lpItem->puColumns); DPA_Destroy(hdpaSubItems); } @@ -6824,6 +7074,17 @@ static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, dispInfo.item.iIndent = I_INDENTCALLBACK; } + /* Only items support tileview columns descriptions */ + if ((lpLVItem->mask & LVIF_COLUMNS) && lpItem->cColumns == I_COLUMNSCALLBACK && !lpLVItem->iSubItem) + { + /* 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. */ + dispInfo.item.mask |= LVIF_COLUMNS; + dispInfo.item.cColumns = 20; + dispInfo.item.puColumns = Alloc(20 * sizeof(UINT)); + } + /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */ if ((lpLVItem->mask & LVIF_TEXT) && !(lpLVItem->mask & LVIF_NORECOMPUTE) && !is_text(pItemHdr->pszText)) @@ -6898,21 +7159,21 @@ static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, lpLVItem->state &= ~dispInfo.item.stateMask; lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask); } - if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED ) + if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED ) { lpLVItem->state &= ~LVIS_FOCUSED; if (infoPtr->nFocusedItem == lpLVItem->iItem) lpLVItem->state |= LVIS_FOCUSED; } - if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED ) + if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED ) { lpLVItem->state &= ~LVIS_SELECTED; if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem)) lpLVItem->state |= LVIS_SELECTED; - } + } } - /* and last, but not least, the indent field */ + /* Next is the indent field */ if (dispInfo.item.mask & LVIF_INDENT) { lpLVItem->iIndent = dispInfo.item.iIndent; @@ -6924,6 +7185,38 @@ static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, lpLVItem->iIndent = lpItem->iIndent; } + /* And then our Column Fields */ + if (dispInfo.item.mask & LVIF_COLUMNS) + { + lpLVItem->cColumns = dispInfo.item.cColumns; + /* The caller owns lpLVItem->puColumns, copy our data into it. */ + memcpy(lpLVItem->puColumns, dispInfo.item.puColumns, dispInfo.item.cColumns * sizeof(UINT)); + + if ((dispInfo.item.mask & LVIF_DI_SETITEM) && lpItem->cColumns == I_COLUMNSCALLBACK) { + lpItem->cColumns = dispInfo.item.cColumns; + + /* We allocated for dispInfo.item.puColumns, free then pass ownership. */ + if (lpItem->puColumns) Free(lpItem->puColumns); + lpItem->puColumns = dispInfo.item.puColumns; + } else { + /* Otherwise its our reponsibility to free it */ + Free(dispInfo.item.puColumns); + } + } + /* When one doesn't use a callback, they can query for the size by passing null for puColumns. */ + else if (lpLVItem->mask & LVIF_COLUMNS) { + if (lpLVItem->puColumns && lpLVItem->cColumns >= lpItem->cColumns) { + lpLVItem->cColumns = lpItem->cColumns; + // If there's no data we won't store an allocation. + if (lpItem->puColumns) + memcpy(lpLVItem->puColumns, lpItem->puColumns, lpItem->cColumns * sizeof(UINT)); + } else if (lpLVItem->puColumns) { + return FALSE; + } else { + lpLVItem->cColumns = lpItem->cColumns; + } + } + return TRUE; } @@ -7744,6 +8037,8 @@ static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) opt.x = lpht->pt.x - Origin.x; } + else if (infoPtr->uView == LV_VIEW_TILE) + rcBounds = rcBox; else { UnionRect(&rcBounds, &rcIcon, &rcLabel); @@ -7913,7 +8208,7 @@ static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo; /* make room for the position, if we are in the right mode */ - if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON)) + if (is_icon_like(infoPtr)) { if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1) goto undo; @@ -7933,7 +8228,7 @@ static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, return -1; /* align items (set position of each item) */ - if (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON) + if (is_icon_like(infoPtr)) { POINT pt; @@ -9312,11 +9607,6 @@ static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView) if (infoPtr->uView == nView) return 1; if ((INT)nView < 0 || nView > LV_VIEW_MAX) return -1; - if (nView == LV_VIEW_TILE) - { - FIXME("View LV_VIEW_TILE unimplemented\n"); - return -1; - } infoPtr->uView = nView; @@ -9328,11 +9618,13 @@ static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView) himl = (nView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall); set_icon_size(&infoPtr->iconSize, himl, nView != LV_VIEW_ICON); + LISTVIEW_UpdateItemSize(infoPtr); switch (nView) { case LV_VIEW_ICON: case LV_VIEW_SMALLICON: + case LV_VIEW_TILE: LISTVIEW_Arrange(infoPtr, LVA_DEFAULT); break; case LV_VIEW_DETAILS: @@ -9353,7 +9645,6 @@ static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView) break; } - LISTVIEW_UpdateItemSize(infoPtr); LISTVIEW_UpdateSize(infoPtr); LISTVIEW_UpdateScroll(infoPtr); LISTVIEW_InvalidateList(infoPtr); @@ -10810,7 +11101,7 @@ static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc) { infoPtr->bNoItemMetrics = FALSE; LISTVIEW_UpdateItemSize(infoPtr); - if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON) + if (is_icon_like(infoPtr)) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT); LISTVIEW_UpdateScroll(infoPtr); } @@ -11612,9 +11903,11 @@ LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) case LVM_GETTEXTCOLOR: return infoPtr->clrText; - /* case LVM_GETTILEINFO: */ + case LVM_GETTILEINFO: + return LISTVIEW_GetTileInfo(infoPtr, (PLVTILEINFO)lParam); - /* case LVM_GETTILEVIEWINFO: */ + case LVM_GETTILEVIEWINFO: + return LISTVIEW_GetTileViewInfo(infoPtr, (PLVTILEVIEWINFO)lParam); case LVM_GETTOOLTIPS: if( !infoPtr->hwndToolTip ) @@ -11778,9 +12071,11 @@ LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) case LVM_SETTEXTCOLOR: return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam); - /* case LVM_SETTILEINFO: */ + case LVM_SETTILEINFO: + return LISTVIEW_SetTileInfo(infoPtr, (PLVTILEINFO)lParam); - /* case LVM_SETTILEVIEWINFO: */ + case LVM_SETTILEVIEWINFO: + return LISTVIEW_SetTileViewInfo(infoPtr, (PLVTILEVIEWINFO)lParam); /* case LVM_SETTILEWIDTH: */ diff --git a/dlls/comctl32/tests/listview.c b/dlls/comctl32/tests/listview.c index 951def8fe0f..be04e3c9ade 100644 --- a/dlls/comctl32/tests/listview.c +++ b/dlls/comctl32/tests/listview.c @@ -2383,6 +2383,204 @@ static void test_customdraw_background(BOOL v6) } } +static void test_tileview_columns_info(BOOL v6) +{ + /* There are two ways to set and get tile info for columns, with LVM_GETITEMA + * messages and the LVIF_COLUMNS; and with LVM_GETTILEINFO messages. + * Both support passing NULL as the puColumns to request that the correct cColumns + * field is set; but only LVM_GETITEMA provides cromulent errors when the buffer + * is undersized. */ + HWND hwnd; + LVITEMA item; + DWORD r; + static UINT puColumns[2] = {1,2}; + static UINT puColumnsNext[3] = {3,2,1}; + + /* Providing a buffer large enough to hold all the values */ + static UINT OVERSIZED = 4; + UINT puColumnsReturn[4] = {9,9,9,9}; + + hwnd = create_listview_control(LVS_REPORT); + ok(hwnd != NULL, "failed to create a listview window\n"); + + memset(&item, 0, sizeof(item)); + item.mask = LVIF_COLUMNS; + item.iItem = 0; + item.cColumns = 2; + item.puColumns = puColumns; + r = SendMessageA(hwnd, LVM_INSERTITEMA, 0, (LPARAM)&item); + expect(0, r); + + flush_sequences(sequences, NUM_MSG_SEQUENCES); + + /* Ensure the List View has its own copy by changing puColumns */ + puColumns[0] = 8; + + memset(&item, 0, sizeof(item)); + item.iItem = 0; + item.mask = LVIF_COLUMNS; + item.cColumns = OVERSIZED; + item.puColumns = puColumnsReturn; + r = SendMessageA(hwnd, LVM_GETITEMA, 0, (LPARAM)&item); + ok(r, "LVM_GETITEMA should return OK for correct Column get.\n"); + expect(2, item.cColumns); + + ok(item.puColumns == puColumnsReturn, "Should write to passed pointer, not change the pointer.\n"); + if (item.puColumns != NULL) { + expect(1, item.puColumns[0]); + expect(2, item.puColumns[1]); + } + + flush_sequences(sequences, NUM_MSG_SEQUENCES); + + memset(&item, 0, sizeof(item)); + item.iItem = 0; + item.mask = LVIF_COLUMNS; + item.cColumns = 1; + item.puColumns = puColumnsReturn; + r = SendMessageA(hwnd, LVM_GETITEMA, 0, (LPARAM)&item); + ok(!r, "LVM_GETITEMA returns Error if buffer is undersized.\n"); + expect(1, item.cColumns); + + flush_sequences(sequences, NUM_MSG_SEQUENCES); + + memset(&item, 0, sizeof(item)); + item.iItem = 0; + item.mask = LVIF_COLUMNS; + item.cColumns = 0; + item.puColumns = NULL; + r = SendMessageA(hwnd, LVM_GETITEMA, 0, (LPARAM)&item); + ok(r, "LVM_GETITEMA return OK for Query Style (no buffer supplied).\n"); + expect(2, item.cColumns); + + flush_sequences(sequences, NUM_MSG_SEQUENCES); + + LVTILEINFO tvi = {0}; + memset(&tvi, 0, sizeof(tvi)); + + tvi.cbSize = sizeof(tvi); + tvi.iItem = 0; + tvi.cColumns = 3; + tvi.puColumns = puColumnsNext; + + r = SendMessageA(hwnd, LVM_SETTILEINFO, 0, (LPARAM)&tvi); + ok(r, "Failed to set item tile info.\n"); + flush_sequences(sequences, NUM_MSG_SEQUENCES); + + /* Again, ensure Ownership */ + puColumnsNext[0] = 8; + + /* Good LVM_GETTILEINFO get with buffer and large enough size */ + tvi.cColumns = OVERSIZED; + tvi.puColumns = puColumnsReturn; + r = SendMessageA(hwnd, LVM_GETTILEINFO, 0, (LPARAM)&tvi); + ok(r, "LVM_GETTILEINFO Should retrieve info when parameters are ok.\n"); + expect(3, tvi.cColumns); + + ok(tvi.puColumns == puColumnsReturn, "Should write to passed pointer, not change the pointer.\n"); + if (tvi.puColumns != NULL) { + expect(3, tvi.puColumns[0]); + expect(2, tvi.puColumns[1]); + expect(1, tvi.puColumns[2]); + // Shouldn't touch, beyound reach. + expect(9, tvi.puColumns[3]); + } + flush_sequences(sequences, NUM_MSG_SEQUENCES); + + /* Reset return buffer */ + puColumnsReturn[0] = 9; + puColumnsReturn[1] = 9; + puColumnsReturn[2] = 9; + + /* Undersized get with buffer */ + tvi.cColumns = 1; + tvi.puColumns = puColumnsReturn; + r = SendMessageA(hwnd, LVM_GETTILEINFO, 0, (LPARAM)&tvi); + ok(r, "LVM_GETTILEINFO returns OK with bad results\n"); + expect(1, tvi.cColumns); + ok(tvi.puColumns == puColumnsReturn, "Should write to passed pointer, not change the pointer.\n"); + if (tvi.puColumns != NULL) { + // Shouldn't touch + expect(9, tvi.puColumns[0]); + expect(9, tvi.puColumns[1]); + expect(9, tvi.puColumns[2]); + expect(9, tvi.puColumns[3]); + } + + flush_sequences(sequences, NUM_MSG_SEQUENCES); + + /* Undersized get without buffer */ + tvi.cColumns = 1; + tvi.puColumns = NULL; + r = SendMessageA(hwnd, LVM_GETTILEINFO, 0, (LPARAM)&tvi); + ok(r, "LVM_GETTILEINFO returns OK with size Query\n"); + expect(3, tvi.cColumns); + + flush_sequences(sequences, NUM_MSG_SEQUENCES); + + /* Set with Callback Question */ + tvi.cbSize = sizeof(tvi); + tvi.iItem = 0; + tvi.cColumns = I_COLUMNSCALLBACK; + tvi.puColumns = NULL; + + r = SendMessageA(hwnd, LVM_SETTILEINFO, 0, (LPARAM)&tvi); + ok(r, "Failed to set item tile info.\n"); + flush_sequences(sequences, NUM_MSG_SEQUENCES); + + /* Good LVM_GETTILEINFO get callback */ + tvi.cColumns = OVERSIZED; + tvi.puColumns = NULL; + r = SendMessageA(hwnd, LVM_GETTILEINFO, 0, (LPARAM)&tvi); + ok(r, "LVM_GETTILEINFO Should retrieve info from callback function.\n"); + expect(LVIF_COLUMNS, g_itema.mask); + expect(OVERSIZED, g_itema.cColumns); + ok(g_itema.puColumns != NULL, "Callback should receive write location.\n"); + expect(2, tvi.cColumns); + + ok_sequence(sequences, PARENT_SEQ_INDEX, single_getdispinfo_parent_seq, + "get cCallback dispinfo", FALSE); + + flush_sequences(sequences, NUM_MSG_SEQUENCES); + + DestroyWindow(hwnd); +} + +static void test_get_set_tileview_info(void) +{ + HWND hwnd; + DWORD r; + + hwnd = create_listview_control(LVS_REPORT); + ok(hwnd != NULL, "failed to create a listview window\n"); + + LVTILEVIEWINFO tvi = {0}; + SIZE size = { 100, 50 }; + memset(&tvi, 0, sizeof(tvi)); + + tvi.cbSize = sizeof(tvi); + tvi.cLines = 3; + tvi.sizeTile = size; + tvi.dwFlags = LVTVIF_FIXEDSIZE; + tvi.dwMask = LVTVIM_COLUMNS | LVTVIM_TILESIZE; + r = SendMessageA(hwnd, LVM_SETTILEVIEWINFO, 0, (LPARAM)&tvi); + ok(r, "Failed to set item tile info.\n"); + + tvi.cLines = 0; + tvi.sizeTile.cx = 0; + tvi.sizeTile.cy = 0; + + r = SendMessageA(hwnd, LVM_GETTILEVIEWINFO, 0, (LPARAM)&tvi); + ok(r, "Failed to set item tile info.\n"); + expect(3, tvi.cLines); + expect(100, tvi.sizeTile.cx); + expect(50, tvi.sizeTile.cy); + + flush_sequences(sequences, NUM_MSG_SEQUENCES); + DestroyWindow(hwnd); +} + + static void test_icon_spacing(void) { /* LVM_SETICONSPACING */ @@ -7916,6 +8114,8 @@ START_TEST(listview) test_LVM_GETHOTCURSOR(); test_LVM_GETORIGIN(TRUE); test_customdraw_background(TRUE); + test_tileview_columns_info(TRUE); + test_get_set_tileview_info(); test_WM_PAINT(); uninit_winevent_hook(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10191
participants (2)
-
Huw Campbell -
Huw Campbell (@HuwCampbell)