4 * Copyright 1998, 1999 Eric Kohl
5 * Copyright 1999 Luc Tourangeau
6 * Copyright 2000 Jason Mawdsley
7 * Copyright 2001 CodeWeavers Inc.
8 * Copyright 2002 Dimitrie O. Paun
9 * Copyright 2009 Nikolay Sivov
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public
13 * License as published by the Free Software Foundation; either
14 * version 2.1 of the License, or (at your option) any later version.
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public
22 * License along with this library; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
27 * This code was audited for completeness against the documented features
28 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
30 * Unless otherwise noted, we believe this code to be complete, as per
31 * the specification mentioned above.
32 * If you discover missing features, or bugs, please note them below.
36 * Default Message Processing
37 * -- WM_CREATE: create the icon and small icon image lists at this point only if
38 * the LVS_SHAREIMAGELISTS style is not specified.
39 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
40 * or small icon and the LVS_AUTOARRANGE style is specified.
45 * -- Hot item handling, mouse hovering
46 * -- Workareas support
51 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
52 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
53 * -- LVA_SNAPTOGRID not implemented
54 * -- LISTVIEW_ApproximateViewRect partially implemented
55 * -- LISTVIEW_[GS]etColumnOrderArray stubs
56 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
57 * -- LISTVIEW_SetIconSpacing is incomplete
58 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
61 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
62 * linear in the number of items in the list, and this is
63 * unacceptable for large lists.
64 * -- if list is sorted by item text LISTVIEW_InsertItemT could use
65 * binary search to calculate item index (e.g. DPA_Search()).
66 * This requires sorted state to be reliably tracked in item modifiers.
67 * -- we should keep an ordered array of coordinates in iconic mode
68 * this would allow to frame items (iterator_frameditems),
69 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
76 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
83 * -- LVS_NOSCROLL (see Q137520)
87 * -- LVS_EX_BORDERSELECT
89 * -- LVS_EX_HEADERDRAGDROP
92 * -- LVS_EX_MULTIWORKAREAS
94 * -- LVS_EX_SIMPLESELECT
95 * -- LVS_EX_TWOCLICKACTIVATE
96 * -- LVS_EX_UNDERLINECOLD
97 * -- LVS_EX_UNDERLINEHOT
100 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
103 * -- LVN_MARQUEEBEGIN
109 * -- LVM_ENABLEGROUPVIEW
110 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
111 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
112 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
113 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
114 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
115 * -- LVM_GETINSERTMARKRECT
116 * -- LVM_GETNUMBEROFWORKAREAS
117 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
118 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
119 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
120 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
121 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
122 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
123 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
124 * -- LVM_INSERTGROUPSORTED
125 * -- LVM_INSERTMARKHITTEST
126 * -- LVM_ISGROUPVIEWENABLED
128 * -- LVM_MOVEITEMTOGROUP
130 * -- LVM_SETTILEWIDTH
134 * -- ListView_GetHoverTime, ListView_SetHoverTime
135 * -- ListView_GetISearchString
136 * -- ListView_GetNumberOfWorkAreas
137 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
142 * Known differences in message stream from native control (not known if
143 * these differences cause problems):
144 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
145 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
146 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
147 * processing for "USEDOUBLECLICKTIME".
151 #include "wine/port.h"
166 #include "commctrl.h"
167 #include "comctl32.h"
170 #include "wine/debug.h"
171 #include "wine/unicode.h"
173 WINE_DEFAULT_DEBUG_CHANNEL(listview);
175 /* make sure you set this to 0 for production use! */
176 #define DEBUG_RANGES 1
178 typedef struct tagCOLUMN_INFO
180 RECT rcHeader; /* tracks the header's rectangle */
181 int fmt; /* same as LVCOLUMN.fmt */
184 typedef struct tagITEMHDR
188 } ITEMHDR, *LPITEMHDR;
190 typedef struct tagSUBITEM_INFO
196 typedef struct tagITEM_ID ITEM_ID;
198 typedef struct tagITEM_INFO
209 UINT id; /* item id */
210 HDPA item; /* link to item data */
213 typedef struct tagRANGE
219 typedef struct tagRANGES
224 typedef struct tagITERATOR
233 typedef struct tagDELAYED_ITEM_EDIT
239 typedef struct tagLISTVIEW_INFO
246 HIMAGELIST himlNormal;
247 HIMAGELIST himlSmall;
248 HIMAGELIST himlState;
252 POINT ptClickPos; /* point where the user clicked */
253 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
256 RANGES selectionRanges;
261 RECT rcList; /* This rectangle is really the window
262 * client rectangle possibly reduced by the
263 * horizontal scroll bar and/or header - see
264 * LISTVIEW_UpdateSize. This rectangle offset
265 * by the LISTVIEW_GetOrigin value is in
266 * client coordinates */
275 INT ntmHeight; /* Some cached metrics of the font used */
276 INT ntmMaxCharWidth; /* by the listview to draw items */
278 BOOL bRedraw; /* Turns on/off repaints & invalidations */
279 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
281 BOOL bDoChangeNotify; /* send change notification messages? */
284 DWORD dwStyle; /* the cached window GWL_STYLE */
285 DWORD dwLvExStyle; /* extended listview style */
286 DWORD uView; /* current view available through LVM_[G,S]ETVIEW */
287 INT nItemCount; /* the number of items in the list */
288 HDPA hdpaItems; /* array ITEM_INFO pointers */
289 HDPA hdpaItemIds; /* array of ITEM_ID pointers */
290 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
291 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
292 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
293 POINT currIconPos; /* this is the position next icon will be placed */
294 PFNLVCOMPARE pfnCompare;
299 INT nLButtonDownItem; /* tracks item to reset multiselection on WM_LBUTTONUP */
303 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
305 DWORD lastKeyPressTimestamp;
307 INT nSearchParamLength;
308 WCHAR szSearchParam[ MAX_PATH ];
310 INT nMeasureItemHeight;
311 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
312 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
314 DWORD iVersion; /* CCM_[G,S]ETVERSION */
320 /* How many we debug buffer to allocate */
321 #define DEBUG_BUFFERS 20
322 /* The size of a single debug buffer */
323 #define DEBUG_BUFFER_SIZE 256
325 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
326 #define SB_INTERNAL -1
328 /* maximum size of a label */
329 #define DISP_TEXT_SIZE 512
331 /* padding for items in list and small icon display modes */
332 #define WIDTH_PADDING 12
334 /* padding for items in list, report and small icon display modes */
335 #define HEIGHT_PADDING 1
337 /* offset of items in report display mode */
338 #define REPORT_MARGINX 2
340 /* padding for icon in large icon display mode
341 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
342 * that HITTEST will see.
343 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
344 * ICON_TOP_PADDING - sum of the two above.
345 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
346 * LABEL_HOR_PADDING - between text and sides of box
347 * LABEL_VERT_PADDING - between bottom of text and end of box
349 * ICON_LR_PADDING - additional width above icon size.
350 * ICON_LR_HALF - half of the above value
352 #define ICON_TOP_PADDING_NOTHITABLE 2
353 #define ICON_TOP_PADDING_HITABLE 2
354 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
355 #define ICON_BOTTOM_PADDING 4
356 #define LABEL_HOR_PADDING 5
357 #define LABEL_VERT_PADDING 7
358 #define ICON_LR_PADDING 16
359 #define ICON_LR_HALF (ICON_LR_PADDING/2)
361 /* default label width for items in list and small icon display modes */
362 #define DEFAULT_LABEL_WIDTH 40
363 /* maximum select rectangle width for empty text item in LV_VIEW_DETAILS */
364 #define MAX_EMPTYTEXT_SELECT_WIDTH 80
366 /* default column width for items in list display mode */
367 #define DEFAULT_COLUMN_WIDTH 128
369 /* Size of "line" scroll for V & H scrolls */
370 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
372 /* Padding between image and label */
373 #define IMAGE_PADDING 2
375 /* Padding behind the label */
376 #define TRAILING_LABEL_PADDING 12
377 #define TRAILING_HEADER_PADDING 11
379 /* Border for the icon caption */
380 #define CAPTION_BORDER 2
382 /* Standard DrawText flags */
383 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
384 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
385 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
387 /* Image index from state */
388 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
390 /* The time in milliseconds to reset the search in the list */
391 #define KEY_DELAY 450
393 /* Dump the LISTVIEW_INFO structure to the debug channel */
394 #define LISTVIEW_DUMP(iP) do { \
395 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
396 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
397 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
398 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
399 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
400 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
401 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
402 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
403 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
404 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
407 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
410 * forward declarations
412 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
413 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
414 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
415 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
416 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
417 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *, INT);
418 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
419 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
420 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
421 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
422 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
423 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
424 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
425 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
426 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
427 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
428 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
429 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, BOOL);
430 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
431 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
432 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *, BOOL, BOOL);
434 /******** Text handling functions *************************************/
436 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
437 * text string. The string may be ANSI or Unicode, in which case
438 * the boolean isW tells us the type of the string.
440 * The name of the function tell what type of strings it expects:
441 * W: Unicode, T: ANSI/Unicode - function of isW
444 static inline BOOL is_textW(LPCWSTR text)
446 return text != NULL && text != LPSTR_TEXTCALLBACKW;
449 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
451 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
452 return is_textW(text);
455 static inline int textlenT(LPCWSTR text, BOOL isW)
457 return !is_textT(text, isW) ? 0 :
458 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
461 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
464 if (isSrcW) lstrcpynW(dest, src, max);
465 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
467 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
468 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
471 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
473 LPWSTR wstr = (LPWSTR)text;
475 if (!isW && is_textT(text, isW))
477 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
478 wstr = Alloc(len * sizeof(WCHAR));
479 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
481 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
485 static inline void textfreeT(LPWSTR wstr, BOOL isW)
487 if (!isW && is_textT(wstr, isW)) Free (wstr);
491 * dest is a pointer to a Unicode string
492 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
494 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
498 if (src == LPSTR_TEXTCALLBACKW)
500 if (is_textW(*dest)) Free(*dest);
501 *dest = LPSTR_TEXTCALLBACKW;
505 LPWSTR pszText = textdupTtoW(src, isW);
506 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
507 bResult = Str_SetPtrW(dest, pszText);
508 textfreeT(pszText, isW);
514 * compares a Unicode to a Unicode/ANSI text string
516 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
518 if (!aw) return bt ? -1 : 0;
519 if (!bt) return aw ? 1 : 0;
520 if (aw == LPSTR_TEXTCALLBACKW)
521 return bt == LPSTR_TEXTCALLBACKW ? 1 : -1;
522 if (bt != LPSTR_TEXTCALLBACKW)
524 LPWSTR bw = textdupTtoW(bt, isW);
525 int r = bw ? lstrcmpW(aw, bw) : 1;
533 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
537 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
538 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
539 return res ? res - sizeof(WCHAR) : res;
542 /******** Debugging functions *****************************************/
544 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
546 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
547 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
550 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
552 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
553 n = min(textlenT(text, isW), n);
554 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
557 static char* debug_getbuf(void)
559 static int index = 0;
560 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
561 return buffers[index++ % DEBUG_BUFFERS];
564 static inline const char* debugrange(const RANGE *lprng)
566 if (!lprng) return "(null)";
567 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
570 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
572 char* buf = debug_getbuf(), *text = buf;
573 int len, size = DEBUG_BUFFER_SIZE;
575 if (pScrollInfo == NULL) return "(null)";
576 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
577 if (len == -1) goto end; buf += len; size -= len;
578 if (pScrollInfo->fMask & SIF_RANGE)
579 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
581 if (len == -1) goto end; buf += len; size -= len;
582 if (pScrollInfo->fMask & SIF_PAGE)
583 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
585 if (len == -1) goto end; buf += len; size -= len;
586 if (pScrollInfo->fMask & SIF_POS)
587 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
589 if (len == -1) goto end; buf += len; size -= len;
590 if (pScrollInfo->fMask & SIF_TRACKPOS)
591 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
593 if (len == -1) goto end; buf += len; size -= len;
596 buf = text + strlen(text);
598 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
602 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
604 if (!plvnm) return "(null)";
605 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
606 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
607 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
608 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
611 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
613 char* buf = debug_getbuf(), *text = buf;
614 int len, size = DEBUG_BUFFER_SIZE;
616 if (lpLVItem == NULL) return "(null)";
617 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
618 if (len == -1) goto end; buf += len; size -= len;
619 if (lpLVItem->mask & LVIF_STATE)
620 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
622 if (len == -1) goto end; buf += len; size -= len;
623 if (lpLVItem->mask & LVIF_TEXT)
624 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
626 if (len == -1) goto end; buf += len; size -= len;
627 if (lpLVItem->mask & LVIF_IMAGE)
628 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
630 if (len == -1) goto end; buf += len; size -= len;
631 if (lpLVItem->mask & LVIF_PARAM)
632 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
634 if (len == -1) goto end; buf += len; size -= len;
635 if (lpLVItem->mask & LVIF_INDENT)
636 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
638 if (len == -1) goto end; buf += len; size -= len;
641 buf = text + strlen(text);
643 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
647 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
649 char* buf = debug_getbuf(), *text = buf;
650 int len, size = DEBUG_BUFFER_SIZE;
652 if (lpColumn == NULL) return "(null)";
653 len = snprintf(buf, size, "{");
654 if (len == -1) goto end; buf += len; size -= len;
655 if (lpColumn->mask & LVCF_SUBITEM)
656 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
658 if (len == -1) goto end; buf += len; size -= len;
659 if (lpColumn->mask & LVCF_FMT)
660 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
662 if (len == -1) goto end; buf += len; size -= len;
663 if (lpColumn->mask & LVCF_WIDTH)
664 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
666 if (len == -1) goto end; buf += len; size -= len;
667 if (lpColumn->mask & LVCF_TEXT)
668 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
670 if (len == -1) goto end; buf += len; size -= len;
671 if (lpColumn->mask & LVCF_IMAGE)
672 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
674 if (len == -1) goto end; buf += len; size -= len;
675 if (lpColumn->mask & LVCF_ORDER)
676 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
678 if (len == -1) goto end; buf += len; size -= len;
681 buf = text + strlen(text);
683 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
687 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
689 if (!lpht) return "(null)";
691 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
692 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
695 /* Return the corresponding text for a given scroll value */
696 static inline LPCSTR debugscrollcode(int nScrollCode)
700 case SB_LINELEFT: return "SB_LINELEFT";
701 case SB_LINERIGHT: return "SB_LINERIGHT";
702 case SB_PAGELEFT: return "SB_PAGELEFT";
703 case SB_PAGERIGHT: return "SB_PAGERIGHT";
704 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
705 case SB_THUMBTRACK: return "SB_THUMBTRACK";
706 case SB_ENDSCROLL: return "SB_ENDSCROLL";
707 case SB_INTERNAL: return "SB_INTERNAL";
708 default: return "unknown";
713 /******** Notification functions ************************************/
715 static int get_ansi_notification(UINT unicodeNotificationCode)
717 switch (unicodeNotificationCode)
719 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
720 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
721 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
722 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
723 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
724 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
725 /* header forwards */
726 case HDN_TRACKW: return HDN_TRACKA;
727 case HDN_ENDTRACKW: return HDN_ENDTRACKA;
728 case HDN_BEGINDRAG: return HDN_BEGINDRAG;
729 case HDN_ENDDRAG: return HDN_ENDDRAG;
730 case HDN_ITEMCHANGINGW: return HDN_ITEMCHANGINGA;
731 case HDN_ITEMCHANGEDW: return HDN_ITEMCHANGEDA;
732 case HDN_ITEMCLICKW: return HDN_ITEMCLICKA;
733 case HDN_DIVIDERDBLCLICKW: return HDN_DIVIDERDBLCLICKA;
735 ERR("unknown notification %x\n", unicodeNotificationCode);
740 /* forwards header notifications to listview parent */
741 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
745 HD_TEXTFILTERA textfilter;
746 LPSTR text = NULL, filter = NULL;
749 /* on unicode format exit earlier */
750 if (infoPtr->notifyFormat == NFR_UNICODE)
751 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
752 (WPARAM)lpnmh->hdr.idFrom, (LPARAM)lpnmh);
754 /* header always supplies unicode notifications,
755 all we have to do is to convert strings to ANSI */
756 nmhA = *(NMHEADERA*)lpnmh;
759 hditema = *(HDITEMA*)lpnmh->pitem;
760 nmhA.pitem = &hditema;
761 /* convert item text */
762 if (lpnmh->pitem->mask & HDI_TEXT)
764 hditema.pszText = NULL;
765 Str_SetPtrWtoA(&hditema.pszText, lpnmh->pitem->pszText);
766 text = hditema.pszText;
768 /* convert filter text */
769 if ((lpnmh->pitem->mask & HDI_FILTER) && (lpnmh->pitem->type == HDFT_ISSTRING) &&
770 lpnmh->pitem->pvFilter)
772 hditema.pvFilter = &textfilter;
773 textfilter = *(HD_TEXTFILTERA*)(lpnmh->pitem->pvFilter);
774 textfilter.pszText = NULL;
775 Str_SetPtrWtoA(&textfilter.pszText, ((HD_TEXTFILTERW*)lpnmh->pitem->pvFilter)->pszText);
776 filter = textfilter.pszText;
779 nmhA.hdr.code = get_ansi_notification(lpnmh->hdr.code);
781 ret = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
782 (WPARAM)nmhA.hdr.idFrom, (LPARAM)&nmhA);
791 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
795 TRACE("(code=%d)\n", code);
797 pnmh->hwndFrom = infoPtr->hwndSelf;
798 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
800 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
802 TRACE(" <= %ld\n", result);
807 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
810 HWND hwnd = infoPtr->hwndSelf;
811 notify_hdr(infoPtr, code, &nmh);
812 return IsWindow(hwnd);
815 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
826 item.mask = LVIF_PARAM|LVIF_STATE;
827 item.iItem = htInfo->iItem;
829 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
830 nmia.lParam = item.lParam;
831 nmia.uOldState = item.state;
832 nmia.uNewState = item.state | LVIS_ACTIVATING;
833 nmia.uChanged = LVIF_STATE;
836 nmia.iItem = htInfo->iItem;
837 nmia.iSubItem = htInfo->iSubItem;
838 nmia.ptAction = htInfo->pt;
840 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
841 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
842 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
844 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
847 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
849 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
850 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
853 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
857 HWND hwnd = infoPtr->hwndSelf;
859 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
860 ZeroMemory(&nmia, sizeof(nmia));
861 nmia.iItem = lvht->iItem;
862 nmia.iSubItem = lvht->iSubItem;
863 nmia.ptAction = lvht->pt;
864 item.mask = LVIF_PARAM;
865 item.iItem = lvht->iItem;
867 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmia.lParam = item.lParam;
868 notify_hdr(infoPtr, code, (LPNMHDR)&nmia);
869 return IsWindow(hwnd);
872 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
876 HWND hwnd = infoPtr->hwndSelf;
878 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
880 item.mask = LVIF_PARAM;
883 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
884 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
885 return IsWindow(hwnd);
889 Send notification. depends on dispinfoW having same
890 structure as dispinfoA.
891 infoPtr : listview struct
892 notificationCode : *Unicode* notification code
893 pdi : dispinfo structure (can be unicode or ansi)
894 isW : TRUE if dispinfo is Unicode
896 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
898 BOOL bResult = FALSE;
899 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
900 INT cchTempBufMax = 0, savCchTextMax = 0;
902 LPWSTR pszTempBuf = NULL, savPszText = NULL;
904 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
906 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
907 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
910 if (convertToAnsi || convertToUnicode)
912 if (notificationCode != LVN_GETDISPINFOW)
914 cchTempBufMax = convertToUnicode ?
915 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
916 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
920 cchTempBufMax = pdi->item.cchTextMax;
921 *pdi->item.pszText = 0; /* make sure we don't process garbage */
924 pszTempBuf = Alloc( (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
925 if (!pszTempBuf) return FALSE;
927 if (convertToUnicode)
928 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
929 pszTempBuf, cchTempBufMax);
931 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
932 cchTempBufMax, NULL, NULL);
934 savCchTextMax = pdi->item.cchTextMax;
935 savPszText = pdi->item.pszText;
936 pdi->item.pszText = pszTempBuf;
937 pdi->item.cchTextMax = cchTempBufMax;
940 if (infoPtr->notifyFormat == NFR_ANSI)
941 realNotifCode = get_ansi_notification(notificationCode);
943 realNotifCode = notificationCode;
944 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
945 bResult = notify_hdr(infoPtr, realNotifCode, &pdi->hdr);
947 if (convertToUnicode || convertToAnsi)
949 if (convertToUnicode) /* note : pointer can be changed by app ! */
950 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
951 savCchTextMax, NULL, NULL);
953 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
954 savPszText, savCchTextMax);
955 pdi->item.pszText = savPszText; /* restores our buffer */
956 pdi->item.cchTextMax = savCchTextMax;
962 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
963 const RECT *rcBounds, const LVITEMW *lplvItem)
965 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
966 lpnmlvcd->nmcd.hdc = hdc;
967 lpnmlvcd->nmcd.rc = *rcBounds;
968 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
969 lpnmlvcd->clrText = infoPtr->clrText;
970 if (!lplvItem) return;
971 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
972 lpnmlvcd->iSubItem = lplvItem->iSubItem;
973 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
974 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
975 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
976 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
979 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
981 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
984 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
985 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
986 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
987 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
988 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
989 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
993 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
995 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
996 lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
997 if (lpnmlvcd->clrText == CLR_DEFAULT)
998 lpnmlvcd->clrText = comctl32_color.clrWindowText;
1000 /* apparently, for selected items, we have to override the returned values */
1003 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
1005 if (infoPtr->bFocus)
1007 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
1008 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
1010 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
1012 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
1013 lpnmlvcd->clrText = comctl32_color.clrBtnText;
1018 /* Set the text attributes */
1019 if (lpnmlvcd->clrTextBk != CLR_NONE)
1021 SetBkMode(hdc, OPAQUE);
1022 SetBkColor(hdc,lpnmlvcd->clrTextBk);
1025 SetBkMode(hdc, TRANSPARENT);
1026 SetTextColor(hdc, lpnmlvcd->clrText);
1029 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
1031 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
1034 /* returns TRUE when repaint needed, FALSE otherwise */
1035 static BOOL notify_measureitem(LISTVIEW_INFO *infoPtr)
1037 MEASUREITEMSTRUCT mis;
1038 mis.CtlType = ODT_LISTVIEW;
1039 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1043 mis.itemHeight= infoPtr->nItemHeight;
1044 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
1045 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
1047 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
1053 /******** Item iterator functions **********************************/
1055 static RANGES ranges_create(int count);
1056 static void ranges_destroy(RANGES ranges);
1057 static BOOL ranges_add(RANGES ranges, RANGE range);
1058 static BOOL ranges_del(RANGES ranges, RANGE range);
1059 static void ranges_dump(RANGES ranges);
1061 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
1063 RANGE range = { nItem, nItem + 1 };
1065 return ranges_add(ranges, range);
1068 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1070 RANGE range = { nItem, nItem + 1 };
1072 return ranges_del(ranges, range);
1076 * ITERATOR DOCUMENTATION
1078 * The iterator functions allow for easy, and convenient iteration
1079 * over items of interest in the list. Typically, you create a
1080 * iterator, use it, and destroy it, as such:
1083 * iterator_xxxitems(&i, ...);
1084 * while (iterator_{prev,next}(&i)
1086 * //code which uses i.nItem
1088 * iterator_destroy(&i);
1090 * where xxx is either: framed, or visible.
1091 * Note that it is important that the code destroys the iterator
1092 * after it's done with it, as the creation of the iterator may
1093 * allocate memory, which thus needs to be freed.
1095 * You can iterate both forwards, and backwards through the list,
1096 * by using iterator_next or iterator_prev respectively.
1098 * Lower numbered items are draw on top of higher number items in
1099 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1100 * items may overlap). So, to test items, you should use
1102 * which lists the items top to bottom (in Z-order).
1103 * For drawing items, you should use
1105 * which lists the items bottom to top (in Z-order).
1106 * If you keep iterating over the items after the end-of-items
1107 * marker (-1) is returned, the iterator will start from the
1108 * beginning. Typically, you don't need to test for -1,
1109 * because iterator_{next,prev} will return TRUE if more items
1110 * are to be iterated over, or FALSE otherwise.
1112 * Note: the iterator is defined to be bidirectional. That is,
1113 * any number of prev followed by any number of next, or
1114 * five versa, should leave the iterator at the same item:
1115 * prev * n, next * n = next * n, prev * n
1117 * The iterator has a notion of an out-of-order, special item,
1118 * which sits at the start of the list. This is used in
1119 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1120 * which needs to be first, as it may overlap other items.
1122 * The code is a bit messy because we have:
1123 * - a special item to deal with
1124 * - simple range, or composite range
1126 * If you find bugs, or want to add features, please make sure you
1127 * always check/modify *both* iterator_prev, and iterator_next.
1131 * This function iterates through the items in increasing order,
1132 * but prefixed by the special item, then -1. That is:
1133 * special, 1, 2, 3, ..., n, -1.
1134 * Each item is listed only once.
1136 static inline BOOL iterator_next(ITERATOR* i)
1140 i->nItem = i->nSpecial;
1141 if (i->nItem != -1) return TRUE;
1143 if (i->nItem == i->nSpecial)
1145 if (i->ranges) i->index = 0;
1151 if (i->nItem == i->nSpecial) i->nItem++;
1152 if (i->nItem < i->range.upper) return TRUE;
1157 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1158 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1161 else if (i->nItem >= i->range.upper) goto end;
1163 i->nItem = i->range.lower;
1164 if (i->nItem >= 0) goto testitem;
1171 * This function iterates through the items in decreasing order,
1172 * followed by the special item, then -1. That is:
1173 * n, n-1, ..., 3, 2, 1, special, -1.
1174 * Each item is listed only once.
1176 static inline BOOL iterator_prev(ITERATOR* i)
1183 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1186 if (i->nItem == i->nSpecial)
1194 if (i->nItem == i->nSpecial) i->nItem--;
1195 if (i->nItem >= i->range.lower) return TRUE;
1201 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1204 else if (!start && i->nItem < i->range.lower) goto end;
1206 i->nItem = i->range.upper;
1207 if (i->nItem > 0) goto testitem;
1209 return (i->nItem = i->nSpecial) != -1;
1212 static RANGE iterator_range(const ITERATOR *i)
1216 if (!i->ranges) return i->range;
1218 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1220 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1221 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1223 else range.lower = range.upper = 0;
1229 * Releases resources associated with this ierator.
1231 static inline void iterator_destroy(const ITERATOR *i)
1233 ranges_destroy(i->ranges);
1237 * Create an empty iterator.
1239 static inline BOOL iterator_empty(ITERATOR* i)
1241 ZeroMemory(i, sizeof(*i));
1242 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1247 * Create an iterator over a range.
1249 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1257 * Create an iterator over a bunch of ranges.
1258 * Please note that the iterator will take ownership of the ranges,
1259 * and will free them upon destruction.
1261 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1269 * Creates an iterator over the items which intersect lprc.
1271 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1273 RECT frame = *lprc, rcItem, rcTemp;
1276 /* in case we fail, we want to return an empty iterator */
1277 if (!iterator_empty(i)) return FALSE;
1279 LISTVIEW_GetOrigin(infoPtr, &Origin);
1281 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1282 OffsetRect(&frame, -Origin.x, -Origin.y);
1284 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
1288 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->nFocusedItem != -1)
1290 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1291 if (IntersectRect(&rcTemp, &rcItem, lprc))
1292 i->nSpecial = infoPtr->nFocusedItem;
1294 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1295 /* to do better here, we need to have PosX, and PosY sorted */
1296 TRACE("building icon ranges:\n");
1297 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1299 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1300 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1301 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1302 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1303 if (IntersectRect(&rcTemp, &rcItem, &frame))
1304 ranges_additem(i->ranges, nItem);
1308 else if (infoPtr->uView == LV_VIEW_DETAILS)
1312 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1313 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1315 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1316 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1317 if (range.upper <= range.lower) return TRUE;
1318 if (!iterator_rangeitems(i, range)) return FALSE;
1319 TRACE(" report=%s\n", debugrange(&i->range));
1323 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1324 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1325 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1332 if (infoPtr->nItemWidth)
1334 nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1335 nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1339 nFirstCol = max(frame.left, 0);
1340 nLastCol = min(frame.right - 1, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1343 lower = nFirstCol * nPerCol + nFirstRow;
1345 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1346 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1348 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1350 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1351 TRACE("building list ranges:\n");
1352 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1354 item_range.lower = nCol * nPerCol + nFirstRow;
1355 if(item_range.lower >= infoPtr->nItemCount) break;
1356 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1357 TRACE(" list=%s\n", debugrange(&item_range));
1358 ranges_add(i->ranges, item_range);
1366 * Creates an iterator over the items which intersect the visible region of hdc.
1368 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1370 POINT Origin, Position;
1371 RECT rcItem, rcClip;
1374 rgntype = GetClipBox(hdc, &rcClip);
1375 if (rgntype == NULLREGION) return iterator_empty(i);
1376 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1377 if (rgntype == SIMPLEREGION) return TRUE;
1379 /* first deal with the special item */
1380 if (i->nSpecial != -1)
1382 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1383 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1386 /* if we can't deal with the region, we'll just go with the simple range */
1387 LISTVIEW_GetOrigin(infoPtr, &Origin);
1388 TRACE("building visible range:\n");
1389 if (!i->ranges && i->range.lower < i->range.upper)
1391 if (!(i->ranges = ranges_create(50))) return TRUE;
1392 if (!ranges_add(i->ranges, i->range))
1394 ranges_destroy(i->ranges);
1400 /* now delete the invisible items from the list */
1401 while(iterator_next(i))
1403 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1404 rcItem.left = Position.x + Origin.x;
1405 rcItem.top = Position.y + Origin.y;
1406 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1407 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1408 if (!RectVisible(hdc, &rcItem))
1409 ranges_delitem(i->ranges, i->nItem);
1411 /* the iterator should restart on the next iterator_next */
1417 /******** Misc helper functions ************************************/
1419 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1420 WPARAM wParam, LPARAM lParam, BOOL isW)
1422 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1423 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1426 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1428 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1429 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON);
1432 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1434 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1435 if(state == 1 || state == 2)
1439 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1440 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1441 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1445 /* this should be called after window style got updated,
1446 it used to reset view state to match current window style */
1447 static inline void map_style_view(LISTVIEW_INFO *infoPtr)
1449 switch (infoPtr->dwStyle & LVS_TYPEMASK)
1452 infoPtr->uView = LV_VIEW_ICON;
1455 infoPtr->uView = LV_VIEW_DETAILS;
1458 infoPtr->uView = LV_VIEW_SMALLICON;
1461 infoPtr->uView = LV_VIEW_LIST;
1465 /* computes next item id value */
1466 static DWORD get_next_itemid(const LISTVIEW_INFO *infoPtr)
1468 INT count = DPA_GetPtrCount(infoPtr->hdpaItemIds);
1472 ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, count - 1);
1473 return lpID->id + 1;
1478 /******** Internal API functions ************************************/
1480 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1482 static COLUMN_INFO mainItem;
1484 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1485 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1486 return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1489 static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr)
1491 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
1494 if (infoPtr->hwndHeader) return 0;
1496 TRACE("Creating header for list %p\n", infoPtr->hwndSelf);
1498 /* setup creation flags */
1499 dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
1500 dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
1502 hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
1505 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
1506 0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
1507 if (!infoPtr->hwndHeader) return -1;
1509 /* set header unicode format */
1510 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
1512 /* set header font */
1513 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
1515 LISTVIEW_UpdateSize(infoPtr);
1520 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1522 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1525 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1527 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1530 /* used to handle collapse main item column case */
1531 static inline BOOL LISTVIEW_DrawFocusRect(const LISTVIEW_INFO *infoPtr, HDC hdc)
1533 return (infoPtr->rcFocus.left < infoPtr->rcFocus.right) ?
1534 DrawFocusRect(hdc, &infoPtr->rcFocus) : FALSE;
1537 /* Listview invalidation functions: use _only_ these functions to invalidate */
1539 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1541 return infoPtr->bRedraw;
1544 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1546 if(!is_redrawing(infoPtr)) return;
1547 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1548 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1551 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1555 if(!is_redrawing(infoPtr)) return;
1556 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1557 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1560 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1562 POINT Origin, Position;
1565 if(!is_redrawing(infoPtr)) return;
1566 assert (infoPtr->uView == LV_VIEW_DETAILS);
1567 LISTVIEW_GetOrigin(infoPtr, &Origin);
1568 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1569 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1571 rcBox.bottom = infoPtr->nItemHeight;
1572 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1573 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1576 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1578 LISTVIEW_InvalidateRect(infoPtr, NULL);
1581 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1585 if(!is_redrawing(infoPtr)) return;
1586 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1587 rcCol.top = infoPtr->rcList.top;
1588 rcCol.bottom = infoPtr->rcList.bottom;
1589 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1594 * Retrieves the number of items that can fit vertically in the client area.
1597 * [I] infoPtr : valid pointer to the listview structure
1600 * Number of items per row.
1602 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1604 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1606 return max(nListWidth/(infoPtr->nItemWidth ? infoPtr->nItemWidth : 1), 1);
1611 * Retrieves the number of items that can fit horizontally in the client
1615 * [I] infoPtr : valid pointer to the listview structure
1618 * Number of items per column.
1620 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1622 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1624 return max(nListHeight / infoPtr->nItemHeight, 1);
1628 /*************************************************************************
1629 * LISTVIEW_ProcessLetterKeys
1631 * Processes keyboard messages generated by pressing the letter keys
1633 * What this does is perform a case insensitive search from the
1634 * current position with the following quirks:
1635 * - If two chars or more are pressed in quick succession we search
1636 * for the corresponding string (e.g. 'abc').
1637 * - If there is a delay we wipe away the current search string and
1638 * restart with just that char.
1639 * - If the user keeps pressing the same character, whether slowly or
1640 * fast, so that the search string is entirely composed of this
1641 * character ('aaaaa' for instance), then we search for first item
1642 * that starting with that character.
1643 * - If the user types the above character in quick succession, then
1644 * we must also search for the corresponding string ('aaaaa'), and
1645 * go to that string if there is a match.
1648 * [I] hwnd : handle to the window
1649 * [I] charCode : the character code, the actual character
1650 * [I] keyData : key data
1658 * - The current implementation has a list of characters it will
1659 * accept and it ignores everything else. In particular it will
1660 * ignore accentuated characters which seems to match what
1661 * Windows does. But I'm not sure it makes sense to follow
1663 * - We don't sound a beep when the search fails.
1667 * TREEVIEW_ProcessLetterKeys
1669 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1674 WCHAR buffer[MAX_PATH];
1675 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1677 /* simple parameter checking */
1678 if (!charCode || !keyData) return 0;
1680 /* only allow the valid WM_CHARs through */
1681 if (!isalnumW(charCode) &&
1682 charCode != '.' && charCode != '`' && charCode != '!' &&
1683 charCode != '@' && charCode != '#' && charCode != '$' &&
1684 charCode != '%' && charCode != '^' && charCode != '&' &&
1685 charCode != '*' && charCode != '(' && charCode != ')' &&
1686 charCode != '-' && charCode != '_' && charCode != '+' &&
1687 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1688 charCode != '}' && charCode != '[' && charCode != '{' &&
1689 charCode != '/' && charCode != '?' && charCode != '>' &&
1690 charCode != '<' && charCode != ',' && charCode != '~')
1693 /* if there's one item or less, there is no where to go */
1694 if (infoPtr->nItemCount <= 1) return 0;
1696 /* update the search parameters */
1697 infoPtr->lastKeyPressTimestamp = GetTickCount();
1698 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1699 if (infoPtr->nSearchParamLength < MAX_PATH-1)
1700 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1701 if (infoPtr->charCode != charCode)
1702 infoPtr->charCode = charCode = 0;
1704 infoPtr->charCode=charCode;
1705 infoPtr->szSearchParam[0]=charCode;
1706 infoPtr->nSearchParamLength=1;
1707 /* Redundant with the 1 char string */
1711 /* and search from the current position */
1713 if (infoPtr->nFocusedItem >= 0) {
1714 endidx=infoPtr->nFocusedItem;
1716 /* if looking for single character match,
1717 * then we must always move forward
1719 if (infoPtr->nSearchParamLength == 1)
1722 endidx=infoPtr->nItemCount;
1726 /* Let application handle this for virtual listview */
1727 if (infoPtr->dwStyle & LVS_OWNERDATA)
1732 ZeroMemory(&lvfi, sizeof(lvfi));
1733 lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1734 infoPtr->szSearchParam[infoPtr->nSearchParamLength] = '\0';
1735 lvfi.psz = infoPtr->szSearchParam;
1739 nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1742 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1748 if (idx == infoPtr->nItemCount) {
1749 if (endidx == infoPtr->nItemCount || endidx == 0)
1755 item.mask = LVIF_TEXT;
1758 item.pszText = buffer;
1759 item.cchTextMax = MAX_PATH;
1760 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1762 /* check for a match */
1763 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1766 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1767 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1768 /* This would work but we must keep looking for a longer match */
1772 } while (idx != endidx);
1775 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1780 /*************************************************************************
1781 * LISTVIEW_UpdateHeaderSize [Internal]
1783 * Function to resize the header control
1786 * [I] hwnd : handle to a window
1787 * [I] nNewScrollPos : scroll pos to set
1792 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1797 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1799 if (!infoPtr->hwndHeader) return;
1801 GetWindowRect(infoPtr->hwndHeader, &winRect);
1802 point[0].x = winRect.left;
1803 point[0].y = winRect.top;
1804 point[1].x = winRect.right;
1805 point[1].y = winRect.bottom;
1807 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1808 point[0].x = -nNewScrollPos;
1809 point[1].x += nNewScrollPos;
1811 SetWindowPos(infoPtr->hwndHeader,0,
1812 point[0].x,point[0].y,point[1].x,point[1].y,
1813 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
1814 SWP_NOZORDER | SWP_NOACTIVATE);
1819 * Update the scrollbars. This functions should be called whenever
1820 * the content, size or view changes.
1823 * [I] infoPtr : valid pointer to the listview structure
1828 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
1830 SCROLLINFO horzInfo, vertInfo;
1833 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1835 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1836 horzInfo.cbSize = sizeof(SCROLLINFO);
1837 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1839 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1840 if (infoPtr->uView == LV_VIEW_LIST)
1842 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1843 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1845 /* scroll by at least one column per page */
1846 if(horzInfo.nPage < infoPtr->nItemWidth)
1847 horzInfo.nPage = infoPtr->nItemWidth;
1849 if (infoPtr->nItemWidth)
1850 horzInfo.nPage /= infoPtr->nItemWidth;
1852 else if (infoPtr->uView == LV_VIEW_DETAILS)
1854 horzInfo.nMax = infoPtr->nItemWidth;
1856 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
1860 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1863 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1864 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1865 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
1866 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1867 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1869 /* Setting the horizontal scroll can change the listview size
1870 * (and potentially everything else) so we need to recompute
1871 * everything again for the vertical scroll
1874 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1875 vertInfo.cbSize = sizeof(SCROLLINFO);
1876 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1878 if (infoPtr->uView == LV_VIEW_DETAILS)
1880 vertInfo.nMax = infoPtr->nItemCount;
1882 /* scroll by at least one page */
1883 if(vertInfo.nPage < infoPtr->nItemHeight)
1884 vertInfo.nPage = infoPtr->nItemHeight;
1886 if (infoPtr->nItemHeight > 0)
1887 vertInfo.nPage /= infoPtr->nItemHeight;
1889 else if (infoPtr->uView != LV_VIEW_LIST) /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
1893 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1896 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1897 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1898 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
1899 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1900 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1902 /* Change of the range may have changed the scroll pos. If so move the content */
1903 if (dx != 0 || dy != 0)
1906 listRect = infoPtr->rcList;
1907 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
1908 SW_ERASE | SW_INVALIDATE);
1911 /* Update the Header Control */
1912 if (infoPtr->uView == LV_VIEW_DETAILS)
1914 horzInfo.fMask = SIF_POS;
1915 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1916 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1923 * Shows/hides the focus rectangle.
1926 * [I] infoPtr : valid pointer to the listview structure
1927 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1932 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
1936 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1938 if (infoPtr->nFocusedItem < 0) return;
1940 /* we need some gymnastics in ICON mode to handle large items */
1941 if (infoPtr->uView == LV_VIEW_ICON)
1945 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1946 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1948 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1953 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1955 /* for some reason, owner draw should work only in report mode */
1956 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
1961 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1962 HFONT hOldFont = SelectObject(hdc, hFont);
1964 item.iItem = infoPtr->nFocusedItem;
1966 item.mask = LVIF_PARAM;
1967 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1969 ZeroMemory(&dis, sizeof(dis));
1970 dis.CtlType = ODT_LISTVIEW;
1971 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1972 dis.itemID = item.iItem;
1973 dis.itemAction = ODA_FOCUS;
1974 if (fShow) dis.itemState |= ODS_FOCUS;
1975 dis.hwndItem = infoPtr->hwndSelf;
1977 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1978 dis.itemData = item.lParam;
1980 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1982 SelectObject(hdc, hOldFont);
1986 LISTVIEW_DrawFocusRect(infoPtr, hdc);
1989 ReleaseDC(infoPtr->hwndSelf, hdc);
1993 * Invalidates all visible selected items.
1995 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
1999 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
2000 while(iterator_next(&i))
2002 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
2003 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
2005 iterator_destroy(&i);
2010 * DESCRIPTION: [INTERNAL]
2011 * Computes an item's (left,top) corner, relative to rcView.
2012 * That is, the position has NOT been made relative to the Origin.
2013 * This is deliberate, to avoid computing the Origin over, and
2014 * over again, when this function is called in a loop. Instead,
2015 * one can factor the computation of the Origin before the loop,
2016 * and offset the value returned by this function, on every iteration.
2019 * [I] infoPtr : valid pointer to the listview structure
2020 * [I] nItem : item number
2021 * [O] lpptOrig : item top, left corner
2026 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
2028 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
2030 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
2032 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2033 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2035 else if (infoPtr->uView == LV_VIEW_LIST)
2037 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
2038 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
2039 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
2041 else /* LV_VIEW_DETAILS */
2043 lpptPosition->x = REPORT_MARGINX;
2044 /* item is always at zero indexed column */
2045 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2046 lpptPosition->x += LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
2047 lpptPosition->y = nItem * infoPtr->nItemHeight;
2052 * DESCRIPTION: [INTERNAL]
2053 * Compute the rectangles of an item. This is to localize all
2054 * the computations in one place. If you are not interested in some
2055 * of these values, simply pass in a NULL -- the function is smart
2056 * enough to compute only what's necessary. The function computes
2057 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
2058 * one, the BOX rectangle. This rectangle is very cheap to compute,
2059 * and is guaranteed to contain all the other rectangles. Computing
2060 * the ICON rect is also cheap, but all the others are potentially
2061 * expensive. This gives an easy and effective optimization when
2062 * searching (like point inclusion, or rectangle intersection):
2063 * first test against the BOX, and if TRUE, test against the desired
2065 * If the function does not have all the necessary information
2066 * to computed the requested rectangles, will crash with a
2067 * failed assertion. This is done so we catch all programming
2068 * errors, given that the function is called only from our code.
2070 * We have the following 'special' meanings for a few fields:
2071 * * If LVIS_FOCUSED is set, we assume the item has the focus
2072 * This is important in ICON mode, where it might get a larger
2073 * then usual rectangle
2075 * Please note that subitem support works only in REPORT mode.
2078 * [I] infoPtr : valid pointer to the listview structure
2079 * [I] lpLVItem : item to compute the measures for
2080 * [O] lprcBox : ptr to Box rectangle
2081 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
2082 * [0] lprcSelectBox : ptr to select box rectangle
2083 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
2084 * [O] lprcIcon : ptr to Icon rectangle
2085 * Same as LVM_GETITEMRECT with LVIR_ICON
2086 * [O] lprcStateIcon: ptr to State Icon rectangle
2087 * [O] lprcLabel : ptr to Label rectangle
2088 * Same as LVM_GETITEMRECT with LVIR_LABEL
2093 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
2094 LPRECT lprcBox, LPRECT lprcSelectBox,
2095 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
2097 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
2098 RECT Box, SelectBox, Icon, Label;
2099 COLUMN_INFO *lpColumnInfo = NULL;
2100 SIZE labelSize = { 0, 0 };
2102 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
2104 /* Be smart and try to figure out the minimum we have to do */
2105 if (lpLVItem->iSubItem) assert(infoPtr->uView == LV_VIEW_DETAILS);
2106 if (infoPtr->uView == LV_VIEW_ICON && (lprcBox || lprcLabel))
2108 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
2109 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
2111 if (lprcSelectBox) doSelectBox = TRUE;
2112 if (lprcLabel) doLabel = TRUE;
2113 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
2120 /************************************************************/
2121 /* compute the box rectangle (it should be cheap to do) */
2122 /************************************************************/
2123 if (lpLVItem->iSubItem || infoPtr->uView == LV_VIEW_DETAILS)
2124 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
2126 if (lpLVItem->iSubItem)
2128 Box = lpColumnInfo->rcHeader;
2133 Box.right = infoPtr->nItemWidth;
2136 Box.bottom = infoPtr->nItemHeight;
2138 /******************************************************************/
2139 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2140 /******************************************************************/
2143 LONG state_width = 0;
2145 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
2146 state_width = infoPtr->iconStateSize.cx;
2148 if (infoPtr->uView == LV_VIEW_ICON)
2150 Icon.left = Box.left + state_width;
2151 if (infoPtr->himlNormal)
2152 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2153 Icon.top = Box.top + ICON_TOP_PADDING;
2154 Icon.right = Icon.left;
2155 Icon.bottom = Icon.top;
2156 if (infoPtr->himlNormal)
2158 Icon.right += infoPtr->iconSize.cx;
2159 Icon.bottom += infoPtr->iconSize.cy;
2162 else /* LV_VIEW_SMALLICON, LV_VIEW_LIST or LV_VIEW_DETAILS */
2164 Icon.left = Box.left + state_width;
2166 if (infoPtr->uView == LV_VIEW_DETAILS && lpLVItem->iSubItem == 0)
2168 /* we need the indent in report mode */
2169 assert(lpLVItem->mask & LVIF_INDENT);
2170 Icon.left += infoPtr->iconSize.cx * lpLVItem->iIndent + REPORT_MARGINX;
2174 Icon.right = Icon.left;
2175 if (infoPtr->himlSmall &&
2176 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2177 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2178 Icon.right += infoPtr->iconSize.cx;
2179 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2181 if(lprcIcon) *lprcIcon = Icon;
2182 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2184 /* TODO: is this correct? */
2187 lprcStateIcon->left = Icon.left - state_width;
2188 lprcStateIcon->right = Icon.left;
2189 lprcStateIcon->top = Icon.top;
2190 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2191 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2194 else Icon.right = 0;
2196 /************************************************************/
2197 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2198 /************************************************************/
2201 /* calculate how far to the right can the label stretch */
2202 Label.right = Box.right;
2203 if (infoPtr->uView == LV_VIEW_DETAILS)
2205 if (lpLVItem->iSubItem == 0)
2207 /* we need a zero based rect here */
2208 Label = lpColumnInfo->rcHeader;
2209 OffsetRect(&Label, -Label.left, 0);
2213 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && infoPtr->uView == LV_VIEW_DETAILS))
2215 labelSize.cx = infoPtr->nItemWidth;
2216 labelSize.cy = infoPtr->nItemHeight;
2220 /* we need the text in non owner draw mode */
2221 assert(lpLVItem->mask & LVIF_TEXT);
2222 if (is_textT(lpLVItem->pszText, TRUE))
2224 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2225 HDC hdc = GetDC(infoPtr->hwndSelf);
2226 HFONT hOldFont = SelectObject(hdc, hFont);
2230 /* compute rough rectangle where the label will go */
2231 SetRectEmpty(&rcText);
2232 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2233 rcText.bottom = infoPtr->nItemHeight;
2234 if (infoPtr->uView == LV_VIEW_ICON)
2235 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2237 /* now figure out the flags */
2238 if (infoPtr->uView == LV_VIEW_ICON)
2239 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2241 uFormat = LV_SL_DT_FLAGS;
2243 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2245 if (rcText.right != rcText.left)
2246 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2248 labelSize.cy = rcText.bottom - rcText.top;
2250 SelectObject(hdc, hOldFont);
2251 ReleaseDC(infoPtr->hwndSelf, hdc);
2255 if (infoPtr->uView == LV_VIEW_ICON)
2257 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2258 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2259 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2260 Label.right = Label.left + labelSize.cx;
2261 Label.bottom = Label.top + infoPtr->nItemHeight;
2262 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2264 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2265 labelSize.cy /= infoPtr->ntmHeight;
2266 labelSize.cy = max(labelSize.cy, 1);
2267 labelSize.cy *= infoPtr->ntmHeight;
2269 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2271 else if (infoPtr->uView == LV_VIEW_DETAILS)
2273 Label.left = Icon.right;
2274 Label.top = Box.top;
2275 Label.right = lpLVItem->iSubItem ? lpColumnInfo->rcHeader.right :
2276 lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
2277 Label.bottom = Label.top + infoPtr->nItemHeight;
2279 else /* LV_VIEW_SMALLICON or LV_VIEW_LIST */
2281 Label.left = Icon.right;
2282 Label.top = Box.top;
2283 Label.right = min(Label.left + labelSize.cx, Label.right);
2284 Label.bottom = Label.top + infoPtr->nItemHeight;
2287 if (lprcLabel) *lprcLabel = Label;
2288 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2291 /************************************************************/
2292 /* compute SELECT bounding box */
2293 /************************************************************/
2296 if (infoPtr->uView == LV_VIEW_DETAILS)
2298 SelectBox.left = Icon.left;
2299 SelectBox.top = Box.top;
2300 SelectBox.bottom = Box.bottom;
2303 SelectBox.right = min(Label.left + labelSize.cx, Label.right);
2305 SelectBox.right = min(Label.left + MAX_EMPTYTEXT_SELECT_WIDTH, Label.right);
2309 UnionRect(&SelectBox, &Icon, &Label);
2311 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2312 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2315 /* Fix the Box if necessary */
2318 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2319 else *lprcBox = Box;
2321 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2325 * DESCRIPTION: [INTERNAL]
2328 * [I] infoPtr : valid pointer to the listview structure
2329 * [I] nItem : item number
2330 * [O] lprcBox : ptr to Box rectangle
2335 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2337 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2338 POINT Position, Origin;
2341 LISTVIEW_GetOrigin(infoPtr, &Origin);
2342 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2344 /* Be smart and try to figure out the minimum we have to do */
2346 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2347 lvItem.mask |= LVIF_TEXT;
2348 lvItem.iItem = nItem;
2349 lvItem.iSubItem = 0;
2350 lvItem.pszText = szDispText;
2351 lvItem.cchTextMax = DISP_TEXT_SIZE;
2352 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2353 if (infoPtr->uView == LV_VIEW_ICON)
2355 lvItem.mask |= LVIF_STATE;
2356 lvItem.stateMask = LVIS_FOCUSED;
2357 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2359 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2361 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
2362 SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0))
2364 OffsetRect(lprcBox, Origin.x, Position.y + Origin.y);
2367 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2370 /* LISTVIEW_MapIdToIndex helper */
2371 static INT CALLBACK MapIdSearchCompare(LPVOID p1, LPVOID p2, LPARAM lParam)
2373 ITEM_ID *id1 = (ITEM_ID*)p1;
2374 ITEM_ID *id2 = (ITEM_ID*)p2;
2376 if (id1->id == id2->id) return 0;
2378 return (id1->id < id2->id) ? -1 : 1;
2383 * Returns the item index for id specified.
2386 * [I] infoPtr : valid pointer to the listview structure
2387 * [I] iID : item id to get index for
2390 * Item index, or -1 on failure.
2392 static INT LISTVIEW_MapIdToIndex(const LISTVIEW_INFO *infoPtr, UINT iID)
2397 TRACE("iID=%d\n", iID);
2399 if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2400 if (infoPtr->nItemCount == 0) return -1;
2403 index = DPA_Search(infoPtr->hdpaItemIds, &ID, -1, &MapIdSearchCompare, 0, DPAS_SORTED);
2407 ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, index);
2408 return DPA_GetPtrIndex(infoPtr->hdpaItems, lpID->item);
2416 * Returns the item id for index given.
2419 * [I] infoPtr : valid pointer to the listview structure
2420 * [I] iItem : item index to get id for
2425 static DWORD LISTVIEW_MapIndexToId(const LISTVIEW_INFO *infoPtr, INT iItem)
2430 TRACE("iItem=%d\n", iItem);
2432 if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2433 if (iItem < 0 || iItem >= infoPtr->nItemCount) return -1;
2435 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, iItem);
2436 lpItem = DPA_GetPtr(hdpaSubItems, 0);
2438 return lpItem->id->id;
2443 * Returns the current icon position, and advances it along the top.
2444 * The returned position is not offset by Origin.
2447 * [I] infoPtr : valid pointer to the listview structure
2448 * [O] lpPos : will get the current icon position
2453 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2455 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2457 *lpPos = infoPtr->currIconPos;
2459 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2460 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2462 infoPtr->currIconPos.x = 0;
2463 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2469 * Returns the current icon position, and advances it down the left edge.
2470 * The returned position is not offset by Origin.
2473 * [I] infoPtr : valid pointer to the listview structure
2474 * [O] lpPos : will get the current icon position
2479 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2481 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2483 *lpPos = infoPtr->currIconPos;
2485 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2486 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2488 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2489 infoPtr->currIconPos.y = 0;
2495 * Moves an icon to the specified position.
2496 * It takes care of invalidating the item, etc.
2499 * [I] infoPtr : valid pointer to the listview structure
2500 * [I] nItem : the item to move
2501 * [I] lpPos : the new icon position
2502 * [I] isNew : flags the item as being new
2508 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2514 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2515 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2517 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2518 LISTVIEW_InvalidateItem(infoPtr, nItem);
2521 /* Allocating a POINTER for every item is too resource intensive,
2522 * so we'll keep the (x,y) in different arrays */
2523 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2524 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2526 LISTVIEW_InvalidateItem(infoPtr, nItem);
2533 * Arranges listview items in icon display mode.
2536 * [I] infoPtr : valid pointer to the listview structure
2537 * [I] nAlignCode : alignment code
2543 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2545 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2549 if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON) return FALSE;
2551 TRACE("nAlignCode=%d\n", nAlignCode);
2553 if (nAlignCode == LVA_DEFAULT)
2555 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2556 else nAlignCode = LVA_ALIGNTOP;
2561 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2562 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2563 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2564 default: return FALSE;
2567 infoPtr->bAutoarrange = TRUE;
2568 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2569 for (i = 0; i < infoPtr->nItemCount; i++)
2571 next_pos(infoPtr, &pos);
2572 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2580 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2581 * For LVS_REPORT always returns empty rectangle.
2584 * [I] infoPtr : valid pointer to the listview structure
2585 * [O] lprcView : bounding rectangle
2591 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2595 SetRectEmpty(lprcView);
2597 switch (infoPtr->uView)
2600 case LV_VIEW_SMALLICON:
2601 for (i = 0; i < infoPtr->nItemCount; i++)
2603 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2604 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2605 lprcView->right = max(lprcView->right, x);
2606 lprcView->bottom = max(lprcView->bottom, y);
2608 if (infoPtr->nItemCount > 0)
2610 lprcView->right += infoPtr->nItemWidth;
2611 lprcView->bottom += infoPtr->nItemHeight;
2616 y = LISTVIEW_GetCountPerColumn(infoPtr);
2617 x = infoPtr->nItemCount / y;
2618 if (infoPtr->nItemCount % y) x++;
2619 lprcView->right = x * infoPtr->nItemWidth;
2620 lprcView->bottom = y * infoPtr->nItemHeight;
2627 * Retrieves the bounding rectangle of all the items.
2630 * [I] infoPtr : valid pointer to the listview structure
2631 * [O] lprcView : bounding rectangle
2637 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2641 TRACE("(lprcView=%p)\n", lprcView);
2643 if (!lprcView) return FALSE;
2645 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2647 if (infoPtr->uView != LV_VIEW_DETAILS)
2649 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2650 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2653 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2660 * Retrieves the subitem pointer associated with the subitem index.
2663 * [I] hdpaSubItems : DPA handle for a specific item
2664 * [I] nSubItem : index of subitem
2667 * SUCCESS : subitem pointer
2670 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2672 SUBITEM_INFO *lpSubItem;
2675 /* we should binary search here if need be */
2676 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2678 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2679 if (lpSubItem->iSubItem == nSubItem)
2689 * Calculates the desired item width.
2692 * [I] infoPtr : valid pointer to the listview structure
2695 * The desired item width.
2697 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2701 TRACE("uView=%d\n", infoPtr->uView);
2703 if (infoPtr->uView == LV_VIEW_ICON)
2704 nItemWidth = infoPtr->iconSpacing.cx;
2705 else if (infoPtr->uView == LV_VIEW_DETAILS)
2707 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2712 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2713 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2715 LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2716 nItemWidth = rcHeader.right;
2719 else /* LV_VIEW_SMALLICON, or LV_VIEW_LIST */
2723 for (i = 0; i < infoPtr->nItemCount; i++)
2724 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2726 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2727 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2729 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2737 * Calculates the desired item height.
2740 * [I] infoPtr : valid pointer to the listview structure
2743 * The desired item height.
2745 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2749 TRACE("uView=%d\n", infoPtr->uView);
2751 if (infoPtr->uView == LV_VIEW_ICON)
2752 nItemHeight = infoPtr->iconSpacing.cy;
2755 nItemHeight = infoPtr->ntmHeight;
2756 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
2758 if (infoPtr->himlState)
2759 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2760 if (infoPtr->himlSmall)
2761 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2762 if (infoPtr->himlState || infoPtr->himlSmall)
2763 nItemHeight += HEIGHT_PADDING;
2764 if (infoPtr->nMeasureItemHeight > 0)
2765 nItemHeight = infoPtr->nMeasureItemHeight;
2768 return max(nItemHeight, 1);
2773 * Updates the width, and height of an item.
2776 * [I] infoPtr : valid pointer to the listview structure
2781 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2783 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2784 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2790 * Retrieves and saves important text metrics info for the current
2794 * [I] infoPtr : valid pointer to the listview structure
2797 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2799 HDC hdc = GetDC(infoPtr->hwndSelf);
2800 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2801 HFONT hOldFont = SelectObject(hdc, hFont);
2805 if (GetTextMetricsW(hdc, &tm))
2807 infoPtr->ntmHeight = tm.tmHeight;
2808 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2811 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2812 infoPtr->nEllipsisWidth = sz.cx;
2814 SelectObject(hdc, hOldFont);
2815 ReleaseDC(infoPtr->hwndSelf, hdc);
2817 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2822 * A compare function for ranges
2825 * [I] range1 : pointer to range 1;
2826 * [I] range2 : pointer to range 2;
2830 * > 0 : if range 1 > range 2
2831 * < 0 : if range 2 > range 1
2832 * = 0 : if range intersects range 2
2834 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2838 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2840 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2845 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
2851 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2853 #define ranges_check(ranges, desc) do { } while(0)
2856 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2861 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2863 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2864 ranges_dump(ranges);
2865 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2867 prev = DPA_GetPtr(ranges->hdpa, 0);
2868 assert (prev->lower >= 0 && prev->lower < prev->upper);
2869 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2871 curr = DPA_GetPtr(ranges->hdpa, i);
2872 assert (prev->upper <= curr->lower);
2873 assert (curr->lower < curr->upper);
2877 TRACE("--- Done checking---\n");
2880 static RANGES ranges_create(int count)
2882 RANGES ranges = Alloc(sizeof(struct tagRANGES));
2883 if (!ranges) return NULL;
2884 ranges->hdpa = DPA_Create(count);
2885 if (ranges->hdpa) return ranges;
2890 static void ranges_clear(RANGES ranges)
2894 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2895 Free(DPA_GetPtr(ranges->hdpa, i));
2896 DPA_DeleteAllPtrs(ranges->hdpa);
2900 static void ranges_destroy(RANGES ranges)
2902 if (!ranges) return;
2903 ranges_clear(ranges);
2904 DPA_Destroy(ranges->hdpa);
2908 static RANGES ranges_clone(RANGES ranges)
2913 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2915 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2917 RANGE *newrng = Alloc(sizeof(RANGE));
2918 if (!newrng) goto fail;
2919 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2920 DPA_SetPtr(clone->hdpa, i, newrng);
2925 TRACE ("clone failed\n");
2926 ranges_destroy(clone);
2930 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2934 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2935 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2940 static void ranges_dump(RANGES ranges)
2944 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2945 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2948 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2950 RANGE srchrng = { nItem, nItem + 1 };
2952 TRACE("(nItem=%d)\n", nItem);
2953 ranges_check(ranges, "before contain");
2954 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2957 static INT ranges_itemcount(RANGES ranges)
2961 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2963 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2964 count += sel->upper - sel->lower;
2970 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2972 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2975 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2976 if (index == -1) return TRUE;
2978 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2980 chkrng = DPA_GetPtr(ranges->hdpa, index);
2981 if (chkrng->lower >= nItem)
2982 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2983 if (chkrng->upper > nItem)
2984 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2989 static BOOL ranges_add(RANGES ranges, RANGE range)
2994 TRACE("(%s)\n", debugrange(&range));
2995 ranges_check(ranges, "before add");
2997 /* try find overlapping regions first */
2998 srchrgn.lower = range.lower - 1;
2999 srchrgn.upper = range.upper + 1;
3000 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
3006 TRACE("Adding new range\n");
3008 /* create the brand new range to insert */
3009 newrgn = Alloc(sizeof(RANGE));
3010 if(!newrgn) goto fail;
3013 /* figure out where to insert it */
3014 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3015 TRACE("index=%d\n", index);
3016 if (index == -1) index = 0;
3018 /* and get it over with */
3019 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3027 RANGE *chkrgn, *mrgrgn;
3028 INT fromindex, mergeindex;
3030 chkrgn = DPA_GetPtr(ranges->hdpa, index);
3031 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
3033 chkrgn->lower = min(range.lower, chkrgn->lower);
3034 chkrgn->upper = max(range.upper, chkrgn->upper);
3036 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
3038 /* merge now common ranges */
3040 srchrgn.lower = chkrgn->lower - 1;
3041 srchrgn.upper = chkrgn->upper + 1;
3045 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
3046 if (mergeindex == -1) break;
3047 if (mergeindex == index)
3049 fromindex = index + 1;
3053 TRACE("Merge with index %i\n", mergeindex);
3055 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
3056 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
3057 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
3059 DPA_DeletePtr(ranges->hdpa, mergeindex);
3060 if (mergeindex < index) index --;
3064 ranges_check(ranges, "after add");
3068 ranges_check(ranges, "failed add");
3072 static BOOL ranges_del(RANGES ranges, RANGE range)
3077 TRACE("(%s)\n", debugrange(&range));
3078 ranges_check(ranges, "before del");
3080 /* we don't use DPAS_SORTED here, since we need *
3081 * to find the first overlapping range */
3082 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
3085 chkrgn = DPA_GetPtr(ranges->hdpa, index);
3087 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
3089 /* case 1: Same range */
3090 if ( (chkrgn->upper == range.upper) &&
3091 (chkrgn->lower == range.lower) )
3093 DPA_DeletePtr(ranges->hdpa, index);
3096 /* case 2: engulf */
3097 else if ( (chkrgn->upper <= range.upper) &&
3098 (chkrgn->lower >= range.lower) )
3100 DPA_DeletePtr(ranges->hdpa, index);
3102 /* case 3: overlap upper */
3103 else if ( (chkrgn->upper <= range.upper) &&
3104 (chkrgn->lower < range.lower) )
3106 chkrgn->upper = range.lower;
3108 /* case 4: overlap lower */
3109 else if ( (chkrgn->upper > range.upper) &&
3110 (chkrgn->lower >= range.lower) )
3112 chkrgn->lower = range.upper;
3115 /* case 5: fully internal */
3118 RANGE tmprgn = *chkrgn, *newrgn;
3120 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
3121 newrgn->lower = chkrgn->lower;
3122 newrgn->upper = range.lower;
3123 chkrgn->lower = range.upper;
3124 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3133 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
3136 ranges_check(ranges, "after del");
3140 ranges_check(ranges, "failed del");
3146 * Removes all selection ranges
3149 * [I] infoPtr : valid pointer to the listview structure
3150 * [I] toSkip : item range to skip removing the selection
3156 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
3165 lvItem.stateMask = LVIS_SELECTED;
3167 /* need to clone the DPA because callbacks can change it */
3168 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
3169 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
3170 while(iterator_next(&i))
3171 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
3172 /* note that the iterator destructor will free the cloned range */
3173 iterator_destroy(&i);
3178 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
3182 if (!(toSkip = ranges_create(1))) return FALSE;
3183 if (nItem != -1) ranges_additem(toSkip, nItem);
3184 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
3185 ranges_destroy(toSkip);
3189 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
3191 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
3196 * Retrieves the number of items that are marked as selected.
3199 * [I] infoPtr : valid pointer to the listview structure
3202 * Number of items selected.
3204 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
3206 INT nSelectedCount = 0;
3208 if (infoPtr->uCallbackMask & LVIS_SELECTED)
3211 for (i = 0; i < infoPtr->nItemCount; i++)
3213 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3218 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3220 TRACE("nSelectedCount=%d\n", nSelectedCount);
3221 return nSelectedCount;
3226 * Manages the item focus.
3229 * [I] infoPtr : valid pointer to the listview structure
3230 * [I] nItem : item index
3233 * TRUE : focused item changed
3234 * FALSE : focused item has NOT changed
3236 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3238 INT oldFocus = infoPtr->nFocusedItem;
3241 if (nItem == infoPtr->nFocusedItem) return FALSE;
3243 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3244 lvItem.stateMask = LVIS_FOCUSED;
3245 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3247 return oldFocus != infoPtr->nFocusedItem;
3250 /* Helper function for LISTVIEW_ShiftIndices *only* */
3251 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3253 if (nShiftItem < nItem) return nShiftItem;
3255 if (nShiftItem > nItem) return nShiftItem + direction;
3257 if (direction > 0) return nShiftItem + direction;
3259 return min(nShiftItem, infoPtr->nItemCount - 1);
3264 * Updates the various indices after an item has been inserted or deleted.
3267 * [I] infoPtr : valid pointer to the listview structure
3268 * [I] nItem : item index
3269 * [I] direction : Direction of shift, +1 or -1.
3274 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3279 /* temporarily disable change notification while shifting items */
3280 bOldChange = infoPtr->bDoChangeNotify;
3281 infoPtr->bDoChangeNotify = FALSE;
3283 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3285 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3287 assert(abs(direction) == 1);
3289 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3291 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3292 if (nNewFocus != infoPtr->nFocusedItem)
3293 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3295 /* But we are not supposed to modify nHotItem! */
3297 infoPtr->bDoChangeNotify = bOldChange;
3303 * Adds a block of selections.
3306 * [I] infoPtr : valid pointer to the listview structure
3307 * [I] nItem : item index
3310 * Whether the window is still valid.
3312 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3314 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3315 INT nLast = max(infoPtr->nSelectionMark, nItem);
3316 HWND hwndSelf = infoPtr->hwndSelf;
3317 NMLVODSTATECHANGE nmlv;
3322 /* Temporarily disable change notification
3323 * If the control is LVS_OWNERDATA, we need to send
3324 * only one LVN_ODSTATECHANGED notification.
3325 * See MSDN documentation for LVN_ITEMCHANGED.
3327 bOldChange = infoPtr->bDoChangeNotify;
3328 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3330 if (nFirst == -1) nFirst = nItem;
3332 item.state = LVIS_SELECTED;
3333 item.stateMask = LVIS_SELECTED;
3335 for (i = nFirst; i <= nLast; i++)
3336 LISTVIEW_SetItemState(infoPtr,i,&item);
3338 ZeroMemory(&nmlv, sizeof(nmlv));
3339 nmlv.iFrom = nFirst;
3342 nmlv.uOldState = item.state;
3344 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3345 if (!IsWindow(hwndSelf))
3347 infoPtr->bDoChangeNotify = bOldChange;
3354 * Sets a single group selection.
3357 * [I] infoPtr : valid pointer to the listview structure
3358 * [I] nItem : item index
3363 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3370 if (!(selection = ranges_create(100))) return;
3372 item.state = LVIS_SELECTED;
3373 item.stateMask = LVIS_SELECTED;
3375 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
3377 if (infoPtr->nSelectionMark == -1)
3379 infoPtr->nSelectionMark = nItem;
3380 ranges_additem(selection, nItem);
3386 sel.lower = min(infoPtr->nSelectionMark, nItem);
3387 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3388 ranges_add(selection, sel);
3393 RECT rcItem, rcSel, rcSelMark;
3396 rcItem.left = LVIR_BOUNDS;
3397 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3398 rcSelMark.left = LVIR_BOUNDS;
3399 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3400 UnionRect(&rcSel, &rcItem, &rcSelMark);
3401 iterator_frameditems(&i, infoPtr, &rcSel);
3402 while(iterator_next(&i))
3404 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3405 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3407 iterator_destroy(&i);
3410 /* disable per item notifications on LVS_OWNERDATA style
3411 FIXME: single LVN_ODSTATECHANGED should be used */
3412 bOldChange = infoPtr->bDoChangeNotify;
3413 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3415 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3418 iterator_rangesitems(&i, selection);
3419 while(iterator_next(&i))
3420 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3421 /* this will also destroy the selection */
3422 iterator_destroy(&i);
3424 infoPtr->bDoChangeNotify = bOldChange;
3426 LISTVIEW_SetItemFocus(infoPtr, nItem);
3431 * Sets a single selection.
3434 * [I] infoPtr : valid pointer to the listview structure
3435 * [I] nItem : item index
3440 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3444 TRACE("nItem=%d\n", nItem);
3446 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3448 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3449 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3450 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3452 infoPtr->nSelectionMark = nItem;
3457 * Set selection(s) with keyboard.
3460 * [I] infoPtr : valid pointer to the listview structure
3461 * [I] nItem : item index
3462 * [I] space : VK_SPACE code sent
3465 * SUCCESS : TRUE (needs to be repainted)
3466 * FAILURE : FALSE (nothing has changed)
3468 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem, BOOL space)
3470 /* FIXME: pass in the state */
3471 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3472 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3473 BOOL bResult = FALSE;
3475 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3476 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3480 if (infoPtr->dwStyle & LVS_SINGLESEL || (wShift == 0 && wCtrl == 0))
3481 LISTVIEW_SetSelection(infoPtr, nItem);
3485 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3489 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3490 lvItem.stateMask = LVIS_SELECTED;
3493 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3494 if (lvItem.state & LVIS_SELECTED)
3495 infoPtr->nSelectionMark = nItem;
3497 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3500 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3503 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3507 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3509 LVHITTESTINFO lvHitTestInfo;
3511 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3512 lvHitTestInfo.pt.x = pt.x;
3513 lvHitTestInfo.pt.y = pt.y;
3515 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3517 lpLVItem->mask = LVIF_PARAM;
3518 lpLVItem->iItem = lvHitTestInfo.iItem;
3519 lpLVItem->iSubItem = 0;
3521 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3524 static inline BOOL LISTVIEW_isHotTracking(const LISTVIEW_INFO *infoPtr)
3526 return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3527 (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3528 (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3533 * Called when the mouse is being actively tracked and has hovered for a specified
3537 * [I] infoPtr : valid pointer to the listview structure
3538 * [I] fwKeys : key indicator
3539 * [I] x,y : mouse position
3542 * 0 if the message was processed, non-zero if there was an error
3545 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3546 * over the item for a certain period of time.
3549 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3551 if (LISTVIEW_isHotTracking(infoPtr))
3559 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3560 LISTVIEW_SetSelection(infoPtr, item.iItem);
3568 * Called whenever WM_MOUSEMOVE is received.
3571 * [I] infoPtr : valid pointer to the listview structure
3572 * [I] fwKeys : key indicator
3573 * [I] x,y : mouse position
3576 * 0 if the message is processed, non-zero if there was an error
3578 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3580 TRACKMOUSEEVENT trackinfo;
3582 if (!(fwKeys & MK_LBUTTON))
3583 infoPtr->bLButtonDown = FALSE;
3585 if (infoPtr->bLButtonDown)
3589 LVHITTESTINFO lvHitTestInfo;
3590 WORD wDragWidth = GetSystemMetrics(SM_CXDRAG);
3591 WORD wDragHeight= GetSystemMetrics(SM_CYDRAG);
3593 rect.left = infoPtr->ptClickPos.x - wDragWidth;
3594 rect.right = infoPtr->ptClickPos.x + wDragWidth;
3595 rect.top = infoPtr->ptClickPos.y - wDragHeight;
3596 rect.bottom = infoPtr->ptClickPos.y + wDragHeight;
3601 lvHitTestInfo.pt = tmp;
3602 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3604 /* reset item marker */
3605 if (infoPtr->nLButtonDownItem != lvHitTestInfo.iItem)
3606 infoPtr->nLButtonDownItem = -1;
3608 if (!PtInRect(&rect, tmp))
3610 /* this path covers the following:
3611 1. WM_LBUTTONDOWN over selected item (sets focus on it)
3612 2. change focus with keys
3613 3. move mouse over item from step 1 selects it and moves focus on it */
3614 if (infoPtr->nLButtonDownItem != -1 &&
3615 !LISTVIEW_GetItemState(infoPtr, infoPtr->nLButtonDownItem, LVIS_SELECTED))
3619 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3620 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3622 LISTVIEW_SetItemState(infoPtr, infoPtr->nLButtonDownItem, &lvItem);
3623 infoPtr->nLButtonDownItem = -1;
3626 if (!infoPtr->bDragging)
3630 lvHitTestInfo.pt = infoPtr->ptClickPos;
3631 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3633 ZeroMemory(&nmlv, sizeof(nmlv));
3634 nmlv.iItem = lvHitTestInfo.iItem;
3635 nmlv.ptAction = infoPtr->ptClickPos;
3637 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3638 infoPtr->bDragging = TRUE;
3645 /* see if we are supposed to be tracking mouse hovering */
3646 if (LISTVIEW_isHotTracking(infoPtr)) {
3647 /* fill in the trackinfo struct */
3648 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3649 trackinfo.dwFlags = TME_QUERY;
3650 trackinfo.hwndTrack = infoPtr->hwndSelf;
3651 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3653 /* see if we are already tracking this hwnd */
3654 _TrackMouseEvent(&trackinfo);
3656 if(!(trackinfo.dwFlags & TME_HOVER)) {
3657 trackinfo.dwFlags = TME_HOVER;
3659 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3660 _TrackMouseEvent(&trackinfo);
3669 * Tests whether the item is assignable to a list with style lStyle
3671 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3673 if ( (lpLVItem->mask & LVIF_TEXT) &&
3674 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3675 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3683 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3686 * [I] infoPtr : valid pointer to the listview structure
3687 * [I] lpLVItem : valid pointer to new item attributes
3688 * [I] isNew : the item being set is being inserted
3689 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3690 * [O] bChanged : will be set to TRUE if the item really changed
3696 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3702 /* stateMask is ignored for LVM_INSERTITEM */
3703 UINT stateMask = isNew ? ~0 : lpLVItem->stateMask;
3707 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3709 if (lpLVItem->mask == 0) return TRUE;
3711 if (infoPtr->dwStyle & LVS_OWNERDATA)
3713 /* a virtual listview only stores selection and focus */
3714 if (lpLVItem->mask & ~LVIF_STATE)
3720 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3721 lpItem = DPA_GetPtr(hdpaSubItems, 0);
3725 /* we need to get the lParam and state of the item */
3726 item.iItem = lpLVItem->iItem;
3727 item.iSubItem = lpLVItem->iSubItem;
3728 item.mask = LVIF_STATE | LVIF_PARAM;
3729 item.stateMask = (infoPtr->dwStyle & LVS_OWNERDATA) ? LVIS_FOCUSED | LVIS_SELECTED : ~0;
3733 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3735 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3736 /* determine what fields will change */
3737 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & stateMask & ~infoPtr->uCallbackMask))
3738 uChanged |= LVIF_STATE;
3740 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3741 uChanged |= LVIF_IMAGE;
3743 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3744 uChanged |= LVIF_PARAM;
3746 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3747 uChanged |= LVIF_INDENT;
3749 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3750 uChanged |= LVIF_TEXT;
3752 TRACE("uChanged=0x%x\n", uChanged);
3753 if (!uChanged) return TRUE;
3756 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3757 nmlv.iItem = lpLVItem->iItem;
3758 nmlv.uNewState = (item.state & ~stateMask) | (lpLVItem->state & stateMask);
3759 nmlv.uOldState = item.state;
3760 nmlv.uChanged = uChanged;
3761 nmlv.lParam = item.lParam;
3763 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3764 /* and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications */
3766 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
3768 HWND hwndSelf = infoPtr->hwndSelf;
3770 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3772 if (!IsWindow(hwndSelf))
3776 /* copy information */
3777 if (lpLVItem->mask & LVIF_TEXT)
3778 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3780 if (lpLVItem->mask & LVIF_IMAGE)
3781 lpItem->hdr.iImage = lpLVItem->iImage;
3783 if (lpLVItem->mask & LVIF_PARAM)
3784 lpItem->lParam = lpLVItem->lParam;
3786 if (lpLVItem->mask & LVIF_INDENT)
3787 lpItem->iIndent = lpLVItem->iIndent;
3789 if (uChanged & LVIF_STATE)
3791 if (lpItem && (stateMask & ~infoPtr->uCallbackMask))
3793 lpItem->state &= ~stateMask;
3794 lpItem->state |= (lpLVItem->state & stateMask);
3796 if (lpLVItem->state & stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3798 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3799 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3801 else if (stateMask & LVIS_SELECTED)
3803 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3805 /* if we are asked to change focus, and we manage it, do it */
3806 if (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3808 if (lpLVItem->state & LVIS_FOCUSED)
3810 if (infoPtr->nFocusedItem != -1)
3812 /* remove current focus */
3813 item.mask = LVIF_STATE;
3815 item.stateMask = LVIS_FOCUSED;
3817 /* recurse with redrawing an item */
3818 LISTVIEW_SetItemState(infoPtr, infoPtr->nFocusedItem, &item);
3821 infoPtr->nFocusedItem = lpLVItem->iItem;
3822 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, infoPtr->uView == LV_VIEW_LIST);
3824 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3826 infoPtr->nFocusedItem = -1;
3831 /* if we're inserting the item, we're done */
3832 if (isNew) return TRUE;
3834 /* send LVN_ITEMCHANGED notification */
3835 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3836 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3843 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3846 * [I] infoPtr : valid pointer to the listview structure
3847 * [I] lpLVItem : valid pointer to new subitem attributes
3848 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3849 * [O] bChanged : will be set to TRUE if the item really changed
3855 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3858 SUBITEM_INFO *lpSubItem;
3860 /* we do not support subitems for virtual listviews */
3861 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3863 /* set subitem only if column is present */
3864 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3866 /* First do some sanity checks */
3867 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
3868 particularly useful. We currently do not actually do anything with
3869 the flag on subitems.
3871 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) return FALSE;
3872 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
3874 /* get the subitem structure, and create it if not there */
3875 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3876 assert (hdpaSubItems);
3878 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3881 SUBITEM_INFO *tmpSubItem;
3884 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
3885 if (!lpSubItem) return FALSE;
3886 /* we could binary search here, if need be...*/
3887 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3889 tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
3890 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3892 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3897 lpSubItem->iSubItem = lpLVItem->iSubItem;
3898 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3902 if (lpLVItem->mask & LVIF_IMAGE)
3903 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3905 lpSubItem->hdr.iImage = lpLVItem->iImage;
3909 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpSubItem->hdr.pszText, lpLVItem->pszText, isW))
3911 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3920 * Sets item attributes.
3923 * [I] infoPtr : valid pointer to the listview structure
3924 * [I] lpLVItem : new item attributes
3925 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3931 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
3933 HWND hwndSelf = infoPtr->hwndSelf;
3934 LPWSTR pszText = NULL;
3935 BOOL bResult, bChanged = FALSE;
3937 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3939 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3942 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3943 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3945 pszText = lpLVItem->pszText;
3946 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3949 /* actually set the fields */
3950 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3952 if (lpLVItem->iSubItem)
3953 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3955 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3956 if (!IsWindow(hwndSelf))
3959 /* redraw item, if necessary */
3960 if (bChanged && !infoPtr->bIsDrawing)
3962 /* this little optimization eliminates some nasty flicker */
3963 if ( infoPtr->uView == LV_VIEW_DETAILS && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3964 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
3965 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
3966 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3968 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3973 textfreeT(lpLVItem->pszText, isW);
3974 lpLVItem->pszText = pszText;
3982 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3985 * [I] infoPtr : valid pointer to the listview structure
3990 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
3993 SCROLLINFO scrollInfo;
3995 scrollInfo.cbSize = sizeof(SCROLLINFO);
3996 scrollInfo.fMask = SIF_POS;
3998 if (infoPtr->uView == LV_VIEW_LIST)
4000 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
4001 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
4003 else if (infoPtr->uView == LV_VIEW_DETAILS)
4005 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
4006 nItem = scrollInfo.nPos;
4010 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
4011 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
4014 TRACE("nItem=%d\n", nItem);
4022 * Erases the background of the given rectangle
4025 * [I] infoPtr : valid pointer to the listview structure
4026 * [I] hdc : device context handle
4027 * [I] lprcBox : clipping rectangle
4033 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
4035 if (!infoPtr->hBkBrush) return FALSE;
4037 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
4039 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
4047 * [I] infoPtr : valid pointer to the listview structure
4048 * [I] hdc : device context handle
4049 * [I] nItem : item index
4050 * [I] nSubItem : subitem index
4051 * [I] pos : item position in client coordinates
4052 * [I] cdmode : custom draw mode
4058 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
4061 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4062 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
4063 DWORD cdsubitemmode = CDRF_DODEFAULT;
4065 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
4066 NMLVCUSTOMDRAW nmlvcd;
4071 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
4073 /* get information needed for drawing the item */
4074 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
4075 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
4076 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
4077 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
4078 lvItem.iItem = nItem;
4079 lvItem.iSubItem = nSubItem;
4082 lvItem.cchTextMax = DISP_TEXT_SIZE;
4083 lvItem.pszText = szDispText;
4084 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
4085 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4086 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
4087 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
4088 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
4090 /* now check if we need to update the focus rectangle */
4091 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
4093 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
4094 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
4095 OffsetRect(&rcBox, pos.x, pos.y);
4096 OffsetRect(&rcSelect, pos.x, pos.y);
4097 OffsetRect(&rcIcon, pos.x, pos.y);
4098 OffsetRect(&rcStateIcon, pos.x, pos.y);
4099 OffsetRect(&rcLabel, pos.x, pos.y);
4100 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
4101 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
4102 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
4104 /* fill in the custom draw structure */
4105 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
4107 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
4108 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
4109 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
4110 if (cdmode & CDRF_NOTIFYITEMDRAW)
4111 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4112 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
4113 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
4114 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
4115 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
4117 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
4118 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
4120 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
4121 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4122 else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
4123 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
4125 /* in full row select, subitems, will just use main item's colors */
4126 if (nSubItem && infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4127 nmlvcd.clrTextBk = CLR_NONE;
4129 /* FIXME: temporary hack */
4130 rcSelect.left = rcLabel.left;
4132 /* draw the selection background, if we're drawing the main item */
4135 /* in icon mode, the label rect is really what we want to draw the
4137 if (infoPtr->uView == LV_VIEW_ICON)
4140 if (infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4142 /* we have to update left focus bound too if item isn't in leftmost column
4143 and reduce right box bound */
4144 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
4148 if ((leftmost = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0)))
4150 INT Originx = pos.x - LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
4151 INT index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
4152 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4154 rcBox.right = LISTVIEW_GetColumnInfo(infoPtr, index)->rcHeader.right + Originx;
4155 rcSelect.left = LISTVIEW_GetColumnInfo(infoPtr, leftmost)->rcHeader.left + Originx;
4159 rcSelect.right = rcBox.right;
4162 if (nmlvcd.clrTextBk != CLR_NONE)
4163 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, NULL, 0, NULL);
4164 /* store new focus rectangle */
4165 if (infoPtr->nFocusedItem == nItem) infoPtr->rcFocus = rcSelect;
4169 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
4171 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
4174 TRACE("uStateImage=%d\n", uStateImage);
4175 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
4176 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
4181 himl = (infoPtr->uView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
4182 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
4184 TRACE("iImage=%d\n", lvItem.iImage);
4185 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
4186 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk, CLR_DEFAULT,
4187 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
4190 /* Don't bother painting item being edited */
4191 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
4193 /* figure out the text drawing flags */
4194 uFormat = (infoPtr->uView == LV_VIEW_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
4195 if (infoPtr->uView == LV_VIEW_ICON)
4196 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
4199 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
4201 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
4202 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
4203 default: uFormat |= DT_LEFT;
4206 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
4208 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
4209 else rcLabel.left += LABEL_HOR_PADDING;
4211 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
4213 /* for GRIDLINES reduce the bottom so the text formats correctly */
4214 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4217 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
4220 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
4221 notify_postpaint(infoPtr, &nmlvcd);
4222 if (cdsubitemmode & CDRF_NEWFONT)
4223 SelectObject(hdc, hOldFont);
4229 * Draws listview items when in owner draw mode.
4232 * [I] infoPtr : valid pointer to the listview structure
4233 * [I] hdc : device context handle
4238 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4240 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
4241 DWORD cditemmode = CDRF_DODEFAULT;
4242 NMLVCUSTOMDRAW nmlvcd;
4243 POINT Origin, Position;
4249 ZeroMemory(&dis, sizeof(dis));
4251 /* Get scroll info once before loop */
4252 LISTVIEW_GetOrigin(infoPtr, &Origin);
4254 /* iterate through the invalidated rows */
4255 while(iterator_next(i))
4257 item.iItem = i->nItem;
4259 item.mask = LVIF_PARAM | LVIF_STATE;
4260 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4261 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
4263 dis.CtlType = ODT_LISTVIEW;
4265 dis.itemID = item.iItem;
4266 dis.itemAction = ODA_DRAWENTIRE;
4268 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
4269 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
4270 dis.hwndItem = infoPtr->hwndSelf;
4272 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
4273 dis.rcItem.left = Position.x + Origin.x;
4274 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
4275 dis.rcItem.top = Position.y + Origin.y;
4276 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
4277 dis.itemData = item.lParam;
4279 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
4282 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
4283 * structure for the rest. of the paint cycle
4285 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
4286 if (cdmode & CDRF_NOTIFYITEMDRAW)
4287 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4289 if (!(cditemmode & CDRF_SKIPDEFAULT))
4291 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
4292 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
4295 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
4296 notify_postpaint(infoPtr, &nmlvcd);
4302 * Draws listview items when in report display mode.
4305 * [I] infoPtr : valid pointer to the listview structure
4306 * [I] hdc : device context handle
4307 * [I] cdmode : custom draw mode
4312 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4315 RECT rcClip, rcItem;
4316 POINT Origin, Position;
4323 /* figure out what to draw */
4324 rgntype = GetClipBox(hdc, &rcClip);
4325 if (rgntype == NULLREGION) return;
4327 /* Get scroll info once before loop */
4328 LISTVIEW_GetOrigin(infoPtr, &Origin);
4330 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
4332 /* narrow down the columns we need to paint */
4333 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
4335 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
4337 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4338 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
4339 ranges_additem(colRanges, index);
4341 iterator_rangesitems(&j, colRanges);
4343 /* in full row select, we _have_ to draw the main item */
4344 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4347 /* iterate through the invalidated rows */
4348 while(iterator_next(i))
4350 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4351 Position.y += Origin.y;
4353 /* iterate through the invalidated columns */
4354 while(iterator_next(&j))
4356 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4357 Position.x = (j.nItem == 0) ? rcItem.left + Origin.x : Origin.x;
4359 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4362 rcItem.bottom = infoPtr->nItemHeight;
4363 OffsetRect(&rcItem, Position.x, Position.y);
4364 if (!RectVisible(hdc, &rcItem)) continue;
4367 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4370 iterator_destroy(&j);
4375 * Draws the gridlines if necessary when in report display mode.
4378 * [I] infoPtr : valid pointer to the listview structure
4379 * [I] hdc : device context handle
4384 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
4390 RECT rcClip, rcItem = {0};
4398 /* figure out what to draw */
4399 rgntype = GetClipBox(hdc, &rcClip);
4400 if (rgntype == NULLREGION) return;
4402 /* Get scroll info once before loop */
4403 LISTVIEW_GetOrigin(infoPtr, &Origin);
4405 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
4407 /* narrow down the columns we need to paint */
4408 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
4410 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
4412 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4413 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
4414 ranges_additem(colRanges, index);
4417 /* is right most vertical line visible? */
4418 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
4420 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4421 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4422 rmost = (rcItem.right + Origin.x < rcClip.right);
4425 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
4427 hOldPen = SelectObject ( hdc, hPen );
4429 /* draw the vertical lines for the columns */
4430 iterator_rangesitems(&j, colRanges);
4431 while(iterator_next(&j))
4433 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4434 if (rcItem.left == 0) continue; /* skip leftmost column */
4435 rcItem.left += Origin.x;
4436 rcItem.right += Origin.x;
4437 rcItem.top = infoPtr->rcList.top;
4438 rcItem.bottom = infoPtr->rcList.bottom;
4439 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
4440 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4441 LineTo (hdc, rcItem.left, rcItem.bottom);
4443 iterator_destroy(&j);
4444 /* draw rightmost grid line if visible */
4447 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
4448 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4449 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4451 rcItem.right += Origin.x;
4453 MoveToEx (hdc, rcItem.right, infoPtr->rcList.top, NULL);
4454 LineTo (hdc, rcItem.right, infoPtr->rcList.bottom);
4457 /* draw the horizontial lines for the rows */
4458 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
4459 rcItem.left = infoPtr->rcList.left;
4460 rcItem.right = infoPtr->rcList.right;
4461 rcItem.bottom = rcItem.top = Origin.y - 1;
4462 MoveToEx(hdc, rcItem.left, rcItem.top, NULL);
4463 LineTo(hdc, rcItem.right, rcItem.top);
4464 for(y=itemheight-1+Origin.y; y<=infoPtr->rcList.bottom; y+=itemheight)
4466 rcItem.bottom = rcItem.top = y;
4467 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
4468 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4469 LineTo (hdc, rcItem.right, rcItem.top);
4472 SelectObject( hdc, hOldPen );
4473 DeleteObject( hPen );
4479 * Draws listview items when in list display mode.
4482 * [I] infoPtr : valid pointer to the listview structure
4483 * [I] hdc : device context handle
4484 * [I] cdmode : custom draw mode
4489 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4491 POINT Origin, Position;
4493 /* Get scroll info once before loop */
4494 LISTVIEW_GetOrigin(infoPtr, &Origin);
4496 while(iterator_prev(i))
4498 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4499 Position.x += Origin.x;
4500 Position.y += Origin.y;
4502 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4509 * Draws listview items.
4512 * [I] infoPtr : valid pointer to the listview structure
4513 * [I] hdc : device context handle
4514 * [I] prcErase : rect to be erased before refresh (may be NULL)
4519 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
4521 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
4522 NMLVCUSTOMDRAW nmlvcd;
4529 HBITMAP hbmp = NULL;
4532 LISTVIEW_DUMP(infoPtr);
4534 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4535 TRACE("double buffering\n");
4537 hdc = CreateCompatibleDC(hdcOrig);
4539 ERR("Failed to create DC for backbuffer\n");
4542 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
4543 infoPtr->rcList.bottom);
4545 ERR("Failed to create bitmap for backbuffer\n");
4550 SelectObject(hdc, hbmp);
4551 SelectObject(hdc, infoPtr->hFont);
4553 /* Save dc values we're gonna trash while drawing
4554 * FIXME: Should be done in LISTVIEW_DrawItem() */
4555 hOldFont = SelectObject(hdc, infoPtr->hFont);
4556 oldBkMode = GetBkMode(hdc);
4557 oldBkColor = GetBkColor(hdc);
4558 oldTextColor = GetTextColor(hdc);
4561 infoPtr->bIsDrawing = TRUE;
4564 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
4565 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4566 /* If no erasing was done (usually because RedrawWindow was called
4567 * with RDW_INVALIDATE only) we need to copy the old contents into
4568 * the backbuffer before continuing. */
4569 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
4570 infoPtr->rcList.right - infoPtr->rcList.left,
4571 infoPtr->rcList.bottom - infoPtr->rcList.top,
4572 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4575 /* FIXME: Shouldn't need to do this */
4576 oldClrTextBk = infoPtr->clrTextBk;
4577 oldClrText = infoPtr->clrText;
4579 infoPtr->cditemmode = CDRF_DODEFAULT;
4581 GetClientRect(infoPtr->hwndSelf, &rcClient);
4582 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
4583 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4584 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
4585 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4587 /* Use these colors to draw the items */
4588 infoPtr->clrTextBk = nmlvcd.clrTextBk;
4589 infoPtr->clrText = nmlvcd.clrText;
4591 /* nothing to draw */
4592 if(infoPtr->nItemCount == 0) goto enddraw;
4594 /* figure out what we need to draw */
4595 iterator_visibleitems(&i, infoPtr, hdc);
4596 range = iterator_range(&i);
4598 /* send cache hint notification */
4599 if (infoPtr->dwStyle & LVS_OWNERDATA)
4603 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4604 nmlv.iFrom = range.lower;
4605 nmlv.iTo = range.upper - 1;
4606 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4609 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
4610 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4613 if (infoPtr->uView == LV_VIEW_DETAILS)
4614 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4615 else /* LV_VIEW_LIST, LV_VIEW_ICON or LV_VIEW_SMALLICON */
4616 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4618 /* if we have a focus rect and it's visible, draw it */
4619 if (infoPtr->bFocus && range.lower <= infoPtr->nFocusedItem &&
4620 (range.upper - 1) >= infoPtr->nFocusedItem)
4621 LISTVIEW_DrawFocusRect(infoPtr, hdc);
4623 iterator_destroy(&i);
4626 /* For LVS_EX_GRIDLINES go and draw lines */
4627 /* This includes the case where there were *no* items */
4628 if ((infoPtr->uView == LV_VIEW_DETAILS) && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4629 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
4631 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4632 notify_postpaint(infoPtr, &nmlvcd);
4634 infoPtr->clrTextBk = oldClrTextBk;
4635 infoPtr->clrText = oldClrText;
4638 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
4639 infoPtr->rcList.right - infoPtr->rcList.left,
4640 infoPtr->rcList.bottom - infoPtr->rcList.top,
4641 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4646 SelectObject(hdc, hOldFont);
4647 SetBkMode(hdc, oldBkMode);
4648 SetBkColor(hdc, oldBkColor);
4649 SetTextColor(hdc, oldTextColor);
4652 infoPtr->bIsDrawing = FALSE;
4658 * Calculates the approximate width and height of a given number of items.
4661 * [I] infoPtr : valid pointer to the listview structure
4662 * [I] nItemCount : number of items
4663 * [I] wWidth : width
4664 * [I] wHeight : height
4667 * Returns a DWORD. The width in the low word and the height in high word.
4669 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
4670 WORD wWidth, WORD wHeight)
4672 INT nItemCountPerColumn = 1;
4673 INT nColumnCount = 0;
4674 DWORD dwViewRect = 0;
4676 if (nItemCount == -1)
4677 nItemCount = infoPtr->nItemCount;
4679 if (infoPtr->uView == LV_VIEW_LIST)
4681 if (wHeight == 0xFFFF)
4683 /* use current height */
4684 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4687 if (wHeight < infoPtr->nItemHeight)
4688 wHeight = infoPtr->nItemHeight;
4692 if (infoPtr->nItemHeight > 0)
4694 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4695 if (nItemCountPerColumn == 0)
4696 nItemCountPerColumn = 1;
4698 if (nItemCount % nItemCountPerColumn != 0)
4699 nColumnCount = nItemCount / nItemCountPerColumn;
4701 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4705 /* Microsoft padding magic */
4706 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4707 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4709 dwViewRect = MAKELONG(wWidth, wHeight);
4711 else if (infoPtr->uView == LV_VIEW_DETAILS)
4715 if (infoPtr->nItemCount > 0)
4717 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4718 wWidth = rcBox.right - rcBox.left;
4719 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4723 /* use current height and width */
4724 if (wHeight == 0xffff)
4725 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4726 if (wWidth == 0xffff)
4727 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4730 dwViewRect = MAKELONG(wWidth, wHeight);
4732 else if (infoPtr->uView == LV_VIEW_SMALLICON)
4733 FIXME("uView == LV_VIEW_SMALLICON: not implemented\n");
4734 else if (infoPtr->uView == LV_VIEW_ICON)
4735 FIXME("uView == LV_VIEW_ICON: not implemented\n");
4742 * Cancel edit label with saving item text.
4745 * [I] infoPtr : valid pointer to the listview structure
4748 * Always returns TRUE.
4750 static LRESULT LISTVIEW_CancelEditLabel(LISTVIEW_INFO *infoPtr)
4752 /* handle value will be lost after LISTVIEW_EndEditLabelT */
4753 HWND edit = infoPtr->hwndEdit;
4755 LISTVIEW_EndEditLabelT(infoPtr, TRUE, IsWindowUnicode(infoPtr->hwndEdit));
4756 SendMessageW(edit, WM_CLOSE, 0, 0);
4763 * Create a drag image list for the specified item.
4766 * [I] infoPtr : valid pointer to the listview structure
4767 * [I] iItem : index of item
4768 * [O] lppt : Upper-left corner of the image
4771 * Returns a handle to the image list if successful, NULL otherwise.
4773 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4779 HBITMAP hbmp, hOldbmp;
4780 HIMAGELIST dragList = 0;
4781 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
4783 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4786 rcItem.left = LVIR_BOUNDS;
4787 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4790 lppt->x = rcItem.left;
4791 lppt->y = rcItem.top;
4793 size.cx = rcItem.right - rcItem.left;
4794 size.cy = rcItem.bottom - rcItem.top;
4796 hdcOrig = GetDC(infoPtr->hwndSelf);
4797 hdc = CreateCompatibleDC(hdcOrig);
4798 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4799 hOldbmp = SelectObject(hdc, hbmp);
4801 rcItem.left = rcItem.top = 0;
4802 rcItem.right = size.cx;
4803 rcItem.bottom = size.cy;
4804 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4807 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4809 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4810 SelectObject(hdc, hOldbmp);
4811 ImageList_Add(dragList, hbmp, 0);
4814 SelectObject(hdc, hOldbmp);
4818 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4820 TRACE("ret=%p\n", dragList);
4828 * Removes all listview items and subitems.
4831 * [I] infoPtr : valid pointer to the listview structure
4837 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
4840 HDPA hdpaSubItems = NULL;
4847 /* we do it directly, to avoid notifications */
4848 ranges_clear(infoPtr->selectionRanges);
4849 infoPtr->nSelectionMark = -1;
4850 infoPtr->nFocusedItem = -1;
4851 SetRectEmpty(&infoPtr->rcFocus);
4852 /* But we are supposed to leave nHotItem as is! */
4855 /* send LVN_DELETEALLITEMS notification */
4856 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4858 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4860 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4862 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4864 /* send LVN_DELETEITEM notification, if not suppressed
4865 and if it is not a virtual listview */
4866 if (!bSuppress) notify_deleteitem(infoPtr, i);
4867 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
4868 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4870 hdrItem = DPA_GetPtr(hdpaSubItems, j);
4871 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4874 DPA_Destroy(hdpaSubItems);
4875 DPA_DeletePtr(infoPtr->hdpaItems, i);
4877 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4878 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4879 infoPtr->nItemCount --;
4884 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4885 LISTVIEW_UpdateScroll(infoPtr);
4887 LISTVIEW_InvalidateList(infoPtr);
4894 * Scrolls, and updates the columns, when a column is changing width.
4897 * [I] infoPtr : valid pointer to the listview structure
4898 * [I] nColumn : column to scroll
4899 * [I] dx : amount of scroll, in pixels
4904 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4906 COLUMN_INFO *lpColumnInfo;
4911 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4912 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4913 rcCol = lpColumnInfo->rcHeader;
4914 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4915 rcCol.left = rcCol.right;
4917 /* adjust the other columns */
4918 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4920 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4921 lpColumnInfo->rcHeader.left += dx;
4922 lpColumnInfo->rcHeader.right += dx;
4925 /* do not update screen if not in report mode */
4926 if (!is_redrawing(infoPtr) || infoPtr->uView != LV_VIEW_DETAILS) return;
4928 /* Need to reset the item width when inserting a new column */
4929 infoPtr->nItemWidth += dx;
4931 LISTVIEW_UpdateScroll(infoPtr);
4932 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
4934 /* scroll to cover the deleted column, and invalidate for redraw */
4935 rcOld = infoPtr->rcList;
4936 rcOld.left = ptOrigin.x + rcCol.left + dx;
4937 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4942 * Removes a column from the listview control.
4945 * [I] infoPtr : valid pointer to the listview structure
4946 * [I] nColumn : column index
4952 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4956 TRACE("nColumn=%d\n", nColumn);
4958 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4959 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4961 /* While the MSDN specifically says that column zero should not be deleted,
4962 what actually happens is that the column itself is deleted but no items or subitems
4966 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4968 if (!SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nColumn, 0))
4971 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4972 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4974 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4976 SUBITEM_INFO *lpSubItem, *lpDelItem;
4978 INT nItem, nSubItem, i;
4980 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4982 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
4985 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4987 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
4988 if (lpSubItem->iSubItem == nColumn)
4991 lpDelItem = lpSubItem;
4993 else if (lpSubItem->iSubItem > nColumn)
4995 lpSubItem->iSubItem--;
4999 /* if we found our subitem, zapp it */
5003 if (is_textW(lpDelItem->hdr.pszText))
5004 Free(lpDelItem->hdr.pszText);
5009 /* free dpa memory */
5010 DPA_DeletePtr(hdpaSubItems, nSubItem);
5015 /* update the other column info */
5016 LISTVIEW_UpdateItemSize(infoPtr);
5017 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
5018 LISTVIEW_InvalidateList(infoPtr);
5020 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
5027 * Invalidates the listview after an item's insertion or deletion.
5030 * [I] infoPtr : valid pointer to the listview structure
5031 * [I] nItem : item index
5032 * [I] dir : -1 if deleting, 1 if inserting
5037 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
5039 INT nPerCol, nItemCol, nItemRow;
5043 /* if we don't refresh, what's the point of scrolling? */
5044 if (!is_redrawing(infoPtr)) return;
5046 assert (abs(dir) == 1);
5048 /* arrange icons if autoarrange is on */
5049 if (is_autoarrange(infoPtr))
5051 BOOL arrange = TRUE;
5052 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
5053 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
5054 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
5057 /* scrollbars need updating */
5058 LISTVIEW_UpdateScroll(infoPtr);
5060 /* figure out the item's position */
5061 if (infoPtr->uView == LV_VIEW_DETAILS)
5062 nPerCol = infoPtr->nItemCount + 1;
5063 else if (infoPtr->uView == LV_VIEW_LIST)
5064 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
5065 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
5068 nItemCol = nItem / nPerCol;
5069 nItemRow = nItem % nPerCol;
5070 LISTVIEW_GetOrigin(infoPtr, &Origin);
5072 /* move the items below up a slot */
5073 rcScroll.left = nItemCol * infoPtr->nItemWidth;
5074 rcScroll.top = nItemRow * infoPtr->nItemHeight;
5075 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
5076 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
5077 OffsetRect(&rcScroll, Origin.x, Origin.y);
5078 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
5079 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
5081 TRACE("Scrolling rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
5082 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
5083 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
5086 /* report has only that column, so we're done */
5087 if (infoPtr->uView == LV_VIEW_DETAILS) return;
5089 /* now for LISTs, we have to deal with the columns to the right */
5090 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
5092 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
5093 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
5094 OffsetRect(&rcScroll, Origin.x, Origin.y);
5095 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
5096 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
5097 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
5102 * Removes an item from the listview control.
5105 * [I] infoPtr : valid pointer to the listview structure
5106 * [I] nItem : item index
5112 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
5115 const BOOL is_icon = (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON);
5117 TRACE("(nItem=%d)\n", nItem);
5119 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5121 /* remove selection, and focus */
5123 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
5124 LISTVIEW_SetItemState(infoPtr, nItem, &item);
5126 /* send LVN_DELETEITEM notification. */
5127 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
5129 /* we need to do this here, because we'll be deleting stuff */
5131 LISTVIEW_InvalidateItem(infoPtr, nItem);
5133 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5141 hdpaSubItems = DPA_DeletePtr(infoPtr->hdpaItems, nItem);
5142 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5144 /* free id struct */
5145 i = DPA_GetPtrIndex(infoPtr->hdpaItemIds, lpItem->id);
5146 lpID = DPA_GetPtr(infoPtr->hdpaItemIds, i);
5147 DPA_DeletePtr(infoPtr->hdpaItemIds, i);
5149 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
5151 hdrItem = DPA_GetPtr(hdpaSubItems, i);
5152 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
5155 DPA_Destroy(hdpaSubItems);
5160 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
5161 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
5164 infoPtr->nItemCount--;
5165 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
5167 /* now is the invalidation fun */
5169 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
5176 * Callback implementation for editlabel control
5179 * [I] infoPtr : valid pointer to the listview structure
5180 * [I] storeText : store edit box text as item text
5181 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
5187 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, BOOL storeText, BOOL isW)
5189 HWND hwndSelf = infoPtr->hwndSelf;
5190 NMLVDISPINFOW dispInfo;
5191 INT editedItem = infoPtr->nEditLabelItem;
5193 WCHAR *pszText = NULL;
5198 DWORD len = isW ? GetWindowTextLengthW(infoPtr->hwndEdit) : GetWindowTextLengthA(infoPtr->hwndEdit);
5202 if ((pszText = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))))
5204 if (isW) GetWindowTextW(infoPtr->hwndEdit, pszText, len+1);
5205 else GetWindowTextA(infoPtr->hwndEdit, (CHAR*)pszText, len+1);
5210 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
5212 infoPtr->nEditLabelItem = -1;
5213 infoPtr->hwndEdit = 0;
5215 ZeroMemory(&dispInfo, sizeof(dispInfo));
5216 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
5217 dispInfo.item.iItem = editedItem;
5218 dispInfo.item.iSubItem = 0;
5219 dispInfo.item.stateMask = ~0;
5220 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item))
5227 bSame = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
5230 LPWSTR tmp = textdupTtoW(pszText, FALSE);
5231 bSame = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
5232 textfreeT(tmp, FALSE);
5240 /* add the text from the edit in */
5241 dispInfo.item.mask |= LVIF_TEXT;
5242 dispInfo.item.pszText = pszText;
5243 dispInfo.item.cchTextMax = textlenT(pszText, isW);
5245 /* Do we need to update the Item Text */
5246 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW))
5251 if (!IsWindow(hwndSelf))
5256 if (!pszText) return TRUE;
5258 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5260 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, editedItem);
5261 ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
5262 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
5264 LISTVIEW_InvalidateItem(infoPtr, editedItem);
5270 ZeroMemory(&dispInfo, sizeof(dispInfo));
5271 dispInfo.item.mask = LVIF_TEXT;
5272 dispInfo.item.iItem = editedItem;
5273 dispInfo.item.iSubItem = 0;
5274 dispInfo.item.pszText = pszText;
5275 dispInfo.item.cchTextMax = textlenT(pszText, isW);
5276 res = LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
5286 * Begin in place editing of specified list view item
5289 * [I] infoPtr : valid pointer to the listview structure
5290 * [I] nItem : item index
5291 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
5297 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
5299 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
5300 NMLVDISPINFOW dispInfo;
5303 HWND hwndSelf = infoPtr->hwndSelf;
5305 HFONT hOldFont = NULL;
5306 TEXTMETRICW textMetric;
5308 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
5310 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
5312 /* Is the EditBox still there, if so remove it */
5313 if(infoPtr->hwndEdit != 0)
5315 SetFocus(infoPtr->hwndSelf);
5316 infoPtr->hwndEdit = 0;
5319 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5321 infoPtr->nEditLabelItem = nItem;
5323 LISTVIEW_SetSelection(infoPtr, nItem);
5324 LISTVIEW_SetItemFocus(infoPtr, nItem);
5325 LISTVIEW_InvalidateItem(infoPtr, nItem);
5327 rect.left = LVIR_LABEL;
5328 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
5330 ZeroMemory(&dispInfo, sizeof(dispInfo));
5331 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
5332 dispInfo.item.iItem = nItem;
5333 dispInfo.item.iSubItem = 0;
5334 dispInfo.item.stateMask = ~0;
5335 dispInfo.item.pszText = szDispText;
5336 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
5337 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
5339 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE, isW);
5340 if (!infoPtr->hwndEdit) return 0;
5342 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
5344 if (!IsWindow(hwndSelf))
5346 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
5347 infoPtr->hwndEdit = 0;
5351 /* Now position and display edit box */
5352 hdc = GetDC(infoPtr->hwndSelf);
5354 /* Select the font to get appropriate metric dimensions */
5355 if(infoPtr->hFont != 0)
5356 hOldFont = SelectObject(hdc, infoPtr->hFont);
5358 /* Get String Length in pixels */
5359 GetTextExtentPoint32W(hdc, dispInfo.item.pszText, lstrlenW(dispInfo.item.pszText), &sz);
5361 /* Add Extra spacing for the next character */
5362 GetTextMetricsW(hdc, &textMetric);
5363 sz.cx += (textMetric.tmMaxCharWidth * 2);
5365 if(infoPtr->hFont != 0)
5366 SelectObject(hdc, hOldFont);
5368 ReleaseDC(infoPtr->hwndSelf, hdc);
5370 MoveWindow(infoPtr->hwndEdit, rect.left - 2, rect.top - 1, sz.cx,
5371 rect.bottom - rect.top + 2, FALSE);
5372 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
5373 SetFocus(infoPtr->hwndEdit);
5374 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
5375 return infoPtr->hwndEdit;
5381 * Ensures the specified item is visible, scrolling into view if necessary.
5384 * [I] infoPtr : valid pointer to the listview structure
5385 * [I] nItem : item index
5386 * [I] bPartial : partially or entirely visible
5392 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
5394 INT nScrollPosHeight = 0;
5395 INT nScrollPosWidth = 0;
5396 INT nHorzAdjust = 0;
5397 INT nVertAdjust = 0;
5400 RECT rcItem, rcTemp;
5402 rcItem.left = LVIR_BOUNDS;
5403 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
5405 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
5407 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
5409 /* scroll left/right, but in LV_VIEW_DETAILS mode */
5410 if (infoPtr->uView == LV_VIEW_LIST)
5411 nScrollPosWidth = infoPtr->nItemWidth;
5412 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
5413 nScrollPosWidth = 1;
5415 if (rcItem.left < infoPtr->rcList.left)
5418 if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.left - infoPtr->rcList.left;
5423 if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.right - infoPtr->rcList.right;
5427 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
5429 /* scroll up/down, but not in LVS_LIST mode */
5430 if (infoPtr->uView == LV_VIEW_DETAILS)
5431 nScrollPosHeight = infoPtr->nItemHeight;
5432 else if ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON))
5433 nScrollPosHeight = 1;
5435 if (rcItem.top < infoPtr->rcList.top)
5438 if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
5443 if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
5447 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
5449 if (nScrollPosWidth)
5451 INT diff = nHorzDiff / nScrollPosWidth;
5452 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
5453 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
5456 if (nScrollPosHeight)
5458 INT diff = nVertDiff / nScrollPosHeight;
5459 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
5460 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
5468 * Searches for an item with specific characteristics.
5471 * [I] hwnd : window handle
5472 * [I] nStart : base item index
5473 * [I] lpFindInfo : item information to look for
5476 * SUCCESS : index of item
5479 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
5480 const LVFINDINFOW *lpFindInfo)
5482 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5483 BOOL bWrap = FALSE, bNearest = FALSE;
5484 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
5485 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
5486 POINT Position, Destination;
5489 /* Search in virtual listviews should be done by application, not by
5490 listview control, so we just send LVN_ODFINDITEMW and return the result */
5491 if (infoPtr->dwStyle & LVS_OWNERDATA)
5495 nmlv.iStart = nStart;
5496 nmlv.lvfi = *lpFindInfo;
5497 return notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
5500 if (!lpFindInfo || nItem < 0) return -1;
5503 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
5505 lvItem.mask |= LVIF_TEXT;
5506 lvItem.pszText = szDispText;
5507 lvItem.cchTextMax = DISP_TEXT_SIZE;
5510 if (lpFindInfo->flags & LVFI_WRAP)
5513 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
5514 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON))
5519 LISTVIEW_GetOrigin(infoPtr, &Origin);
5520 Destination.x = lpFindInfo->pt.x - Origin.x;
5521 Destination.y = lpFindInfo->pt.y - Origin.y;
5522 switch(lpFindInfo->vkDirection)
5524 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
5525 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
5526 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
5527 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
5528 case VK_HOME: Destination.x = Destination.y = 0; break;
5529 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5530 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5532 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
5533 Destination.x = rcArea.right;
5534 Destination.y = rcArea.bottom;
5536 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
5540 else Destination.x = Destination.y = 0;
5542 /* if LVFI_PARAM is specified, all other flags are ignored */
5543 if (lpFindInfo->flags & LVFI_PARAM)
5545 lvItem.mask |= LVIF_PARAM;
5547 lvItem.mask &= ~LVIF_TEXT;
5551 for (; nItem < nLast; nItem++)
5553 lvItem.iItem = nItem;
5554 lvItem.iSubItem = 0;
5555 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
5557 if (lvItem.mask & LVIF_PARAM)
5559 if (lpFindInfo->lParam == lvItem.lParam)
5565 if (lvItem.mask & LVIF_TEXT)
5567 if (lpFindInfo->flags & LVFI_PARTIAL)
5569 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
5573 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
5577 if (!bNearest) return nItem;
5579 /* This is very inefficient. To do a good job here,
5580 * we need a sorted array of (x,y) item positions */
5581 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5583 /* compute the distance^2 to the destination */
5584 xdist = Destination.x - Position.x;
5585 ydist = Destination.y - Position.y;
5586 dist = xdist * xdist + ydist * ydist;
5588 /* remember the distance, and item if it's closer */
5592 nNearestItem = nItem;
5599 nLast = min(nStart + 1, infoPtr->nItemCount);
5604 return nNearestItem;
5609 * Searches for an item with specific characteristics.
5612 * [I] hwnd : window handle
5613 * [I] nStart : base item index
5614 * [I] lpFindInfo : item information to look for
5617 * SUCCESS : index of item
5620 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
5621 const LVFINDINFOA *lpFindInfo)
5623 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
5628 memcpy(&fiw, lpFindInfo, sizeof(fiw));
5629 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
5630 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
5631 textfreeT(strW, FALSE);
5637 * Retrieves the background image of the listview control.
5640 * [I] infoPtr : valid pointer to the listview structure
5641 * [O] lpBkImage : background image attributes
5647 /* static BOOL LISTVIEW_GetBkImage(const LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
5649 /* FIXME (listview, "empty stub!\n"); */
5655 * Retrieves column attributes.
5658 * [I] infoPtr : valid pointer to the listview structure
5659 * [I] nColumn : column index
5660 * [IO] lpColumn : column information
5661 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
5662 * otherwise it is in fact a LPLVCOLUMNA
5668 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5670 COLUMN_INFO *lpColumnInfo;
5673 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5674 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
5676 /* initialize memory */
5677 ZeroMemory(&hdi, sizeof(hdi));
5679 if (lpColumn->mask & LVCF_TEXT)
5681 hdi.mask |= HDI_TEXT;
5682 hdi.pszText = lpColumn->pszText;
5683 hdi.cchTextMax = lpColumn->cchTextMax;
5686 if (lpColumn->mask & LVCF_IMAGE)
5687 hdi.mask |= HDI_IMAGE;
5689 if (lpColumn->mask & LVCF_ORDER)
5690 hdi.mask |= HDI_ORDER;
5692 if (lpColumn->mask & LVCF_SUBITEM)
5693 hdi.mask |= HDI_LPARAM;
5695 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
5697 if (lpColumn->mask & LVCF_FMT)
5698 lpColumn->fmt = lpColumnInfo->fmt;
5700 if (lpColumn->mask & LVCF_WIDTH)
5701 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
5703 if (lpColumn->mask & LVCF_IMAGE)
5704 lpColumn->iImage = hdi.iImage;
5706 if (lpColumn->mask & LVCF_ORDER)
5707 lpColumn->iOrder = hdi.iOrder;
5709 if (lpColumn->mask & LVCF_SUBITEM)
5710 lpColumn->iSubItem = hdi.lParam;
5716 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
5718 TRACE("iCount=%d, lpiArray=%p\n", iCount, lpiArray);
5723 return SendMessageW(infoPtr->hwndHeader, HDM_GETORDERARRAY, iCount, (LPARAM)lpiArray);
5728 * Retrieves the column width.
5731 * [I] infoPtr : valid pointer to the listview structure
5732 * [I] int : column index
5735 * SUCCESS : column width
5738 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
5740 INT nColumnWidth = 0;
5743 TRACE("nColumn=%d\n", nColumn);
5745 /* we have a 'column' in LIST and REPORT mode only */
5746 switch(infoPtr->uView)
5749 nColumnWidth = infoPtr->nItemWidth;
5751 case LV_VIEW_DETAILS:
5752 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDM_ITEMCHANGED.
5753 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
5754 * HDM_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
5756 * TODO: should we do the same in LVM_GETCOLUMN?
5758 hdItem.mask = HDI_WIDTH;
5759 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
5761 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
5764 nColumnWidth = hdItem.cxy;
5768 TRACE("nColumnWidth=%d\n", nColumnWidth);
5769 return nColumnWidth;
5774 * In list or report display mode, retrieves the number of items that can fit
5775 * vertically in the visible area. In icon or small icon display mode,
5776 * retrieves the total number of visible items.
5779 * [I] infoPtr : valid pointer to the listview structure
5782 * Number of fully visible items.
5784 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
5786 switch (infoPtr->uView)
5789 case LV_VIEW_SMALLICON:
5790 return infoPtr->nItemCount;
5791 case LV_VIEW_DETAILS:
5792 return LISTVIEW_GetCountPerColumn(infoPtr);
5794 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5802 * Retrieves an image list handle.
5805 * [I] infoPtr : valid pointer to the listview structure
5806 * [I] nImageList : image list identifier
5809 * SUCCESS : image list handle
5812 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
5816 case LVSIL_NORMAL: return infoPtr->himlNormal;
5817 case LVSIL_SMALL: return infoPtr->himlSmall;
5818 case LVSIL_STATE: return infoPtr->himlState;
5823 /* LISTVIEW_GetISearchString */
5827 * Retrieves item attributes.
5830 * [I] hwnd : window handle
5831 * [IO] lpLVItem : item info
5832 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5833 * if FALSE, then lpLVItem is a LPLVITEMA.
5836 * This is the internal 'GetItem' interface -- it tries to
5837 * be smart and avoid text copies, if possible, by modifying
5838 * lpLVItem->pszText to point to the text string. Please note
5839 * that this is not always possible (e.g. OWNERDATA), so on
5840 * entry you *must* supply valid values for pszText, and cchTextMax.
5841 * The only difference to the documented interface is that upon
5842 * return, you should use *only* the lpLVItem->pszText, rather than
5843 * the buffer pointer you provided on input. Most code already does
5844 * that, so it's not a problem.
5845 * For the two cases when the text must be copied (that is,
5846 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5852 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5854 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5855 NMLVDISPINFOW dispInfo;
5861 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5863 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5866 if (lpLVItem->mask == 0) return TRUE;
5868 /* make a local copy */
5869 isubitem = lpLVItem->iSubItem;
5871 /* a quick optimization if all we're asked is the focus state
5872 * these queries are worth optimising since they are common,
5873 * and can be answered in constant time, without the heavy accesses */
5874 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5875 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5877 lpLVItem->state = 0;
5878 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5879 lpLVItem->state |= LVIS_FOCUSED;
5883 ZeroMemory(&dispInfo, sizeof(dispInfo));
5885 /* if the app stores all the data, handle it separately */
5886 if (infoPtr->dwStyle & LVS_OWNERDATA)
5888 dispInfo.item.state = 0;
5890 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
5891 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) ||
5892 ((lpLVItem->mask & LVIF_STATE) && (infoPtr->uCallbackMask & lpLVItem->stateMask)))
5894 UINT mask = lpLVItem->mask;
5896 /* NOTE: copy only fields which we _know_ are initialized, some apps
5897 * depend on the uninitialized fields being 0 */
5898 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5899 dispInfo.item.iItem = lpLVItem->iItem;
5900 dispInfo.item.iSubItem = isubitem;
5901 if (lpLVItem->mask & LVIF_TEXT)
5903 if (lpLVItem->mask & LVIF_NORECOMPUTE)
5905 dispInfo.item.mask &= ~(LVIF_TEXT | LVIF_NORECOMPUTE);
5908 dispInfo.item.pszText = lpLVItem->pszText;
5909 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5912 if (lpLVItem->mask & LVIF_STATE)
5913 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5914 /* could be zeroed on LVIF_NORECOMPUTE case */
5915 if (dispInfo.item.mask != 0)
5917 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5918 dispInfo.item.stateMask = lpLVItem->stateMask;
5919 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5921 /* full size structure expected - _WIN32IE >= 0x560 */
5922 *lpLVItem = dispInfo.item;
5924 else if (lpLVItem->mask & LVIF_INDENT)
5926 /* indent member expected - _WIN32IE >= 0x300 */
5927 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5931 /* minimal structure expected */
5932 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5934 lpLVItem->mask = mask;
5935 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5939 /* make sure lParam is zeroed out */
5940 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5942 /* callback marked pointer required here */
5943 if ((lpLVItem->mask & LVIF_TEXT) && (lpLVItem->mask & LVIF_NORECOMPUTE))
5944 lpLVItem->pszText = LPSTR_TEXTCALLBACKW;
5946 /* we store only a little state, so if we're not asked, we're done */
5947 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5949 /* if focus is handled by us, report it */
5950 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5952 lpLVItem->state &= ~LVIS_FOCUSED;
5953 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5954 lpLVItem->state |= LVIS_FOCUSED;
5957 /* and do the same for selection, if we handle it */
5958 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5960 lpLVItem->state &= ~LVIS_SELECTED;
5961 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5962 lpLVItem->state |= LVIS_SELECTED;
5968 /* find the item and subitem structures before we proceed */
5969 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5970 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5975 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5976 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5979 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5984 pItemHdr = &lpItem->hdr;
5986 /* Do we need to query the state from the app? */
5987 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5989 dispInfo.item.mask |= LVIF_STATE;
5990 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5993 /* Do we need to enquire about the image? */
5994 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5995 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5997 dispInfo.item.mask |= LVIF_IMAGE;
5998 dispInfo.item.iImage = I_IMAGECALLBACK;
6001 /* Only items support indentation */
6002 if ((lpLVItem->mask & LVIF_INDENT) && lpItem->iIndent == I_INDENTCALLBACK &&
6005 dispInfo.item.mask |= LVIF_INDENT;
6006 dispInfo.item.iIndent = I_INDENTCALLBACK;
6009 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
6010 if ((lpLVItem->mask & LVIF_TEXT) && !(lpLVItem->mask & LVIF_NORECOMPUTE) &&
6011 !is_textW(pItemHdr->pszText))
6013 dispInfo.item.mask |= LVIF_TEXT;
6014 dispInfo.item.pszText = lpLVItem->pszText;
6015 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
6016 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
6017 *dispInfo.item.pszText = '\0';
6020 /* If we don't have all the requested info, query the application */
6021 if (dispInfo.item.mask != 0)
6023 dispInfo.item.iItem = lpLVItem->iItem;
6024 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
6025 dispInfo.item.lParam = lpItem->lParam;
6026 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
6027 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
6030 /* we should not store values for subitems */
6031 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
6033 /* Now, handle the iImage field */
6034 if (dispInfo.item.mask & LVIF_IMAGE)
6036 lpLVItem->iImage = dispInfo.item.iImage;
6037 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
6038 pItemHdr->iImage = dispInfo.item.iImage;
6040 else if (lpLVItem->mask & LVIF_IMAGE)
6042 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
6043 lpLVItem->iImage = pItemHdr->iImage;
6045 lpLVItem->iImage = 0;
6048 /* The pszText field */
6049 if (dispInfo.item.mask & LVIF_TEXT)
6051 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
6052 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
6054 lpLVItem->pszText = dispInfo.item.pszText;
6056 else if (lpLVItem->mask & LVIF_TEXT)
6058 /* if LVN_GETDISPINFO's disabled with LVIF_NORECOMPUTE return callback placeholder */
6059 if (isW || !is_textW(pItemHdr->pszText)) lpLVItem->pszText = pItemHdr->pszText;
6060 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
6063 /* Next is the lParam field */
6064 if (dispInfo.item.mask & LVIF_PARAM)
6066 lpLVItem->lParam = dispInfo.item.lParam;
6067 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
6068 lpItem->lParam = dispInfo.item.lParam;
6070 else if (lpLVItem->mask & LVIF_PARAM)
6071 lpLVItem->lParam = lpItem->lParam;
6073 /* if this is a subitem, we're done */
6074 if (isubitem) return TRUE;
6076 /* ... the state field (this one is different due to uCallbackmask) */
6077 if (lpLVItem->mask & LVIF_STATE)
6079 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
6080 if (dispInfo.item.mask & LVIF_STATE)
6082 lpLVItem->state &= ~dispInfo.item.stateMask;
6083 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
6085 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
6087 lpLVItem->state &= ~LVIS_FOCUSED;
6088 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6089 lpLVItem->state |= LVIS_FOCUSED;
6091 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
6093 lpLVItem->state &= ~LVIS_SELECTED;
6094 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
6095 lpLVItem->state |= LVIS_SELECTED;
6099 /* and last, but not least, the indent field */
6100 if (dispInfo.item.mask & LVIF_INDENT)
6102 lpLVItem->iIndent = dispInfo.item.iIndent;
6103 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && lpItem->iIndent == I_INDENTCALLBACK)
6104 lpItem->iIndent = dispInfo.item.iIndent;
6106 else if (lpLVItem->mask & LVIF_INDENT)
6108 lpLVItem->iIndent = lpItem->iIndent;
6116 * Retrieves item attributes.
6119 * [I] hwnd : window handle
6120 * [IO] lpLVItem : item info
6121 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
6122 * if FALSE, then lpLVItem is a LPLVITEMA.
6125 * This is the external 'GetItem' interface -- it properly copies
6126 * the text in the provided buffer.
6132 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
6137 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
6140 pszText = lpLVItem->pszText;
6141 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
6142 if (bResult && lpLVItem->pszText != pszText)
6144 if (lpLVItem->pszText != LPSTR_TEXTCALLBACKW)
6145 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
6147 pszText = LPSTR_TEXTCALLBACKW;
6149 lpLVItem->pszText = pszText;
6157 * Retrieves the position (upper-left) of the listview control item.
6158 * Note that for LVS_ICON style, the upper-left is that of the icon
6159 * and not the bounding box.
6162 * [I] infoPtr : valid pointer to the listview structure
6163 * [I] nItem : item index
6164 * [O] lpptPosition : coordinate information
6170 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
6174 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
6176 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
6178 LISTVIEW_GetOrigin(infoPtr, &Origin);
6179 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
6181 if (infoPtr->uView == LV_VIEW_ICON)
6183 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
6184 lpptPosition->y += ICON_TOP_PADDING;
6186 lpptPosition->x += Origin.x;
6187 lpptPosition->y += Origin.y;
6189 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
6196 * Retrieves the bounding rectangle for a listview control item.
6199 * [I] infoPtr : valid pointer to the listview structure
6200 * [I] nItem : item index
6201 * [IO] lprc : bounding rectangle coordinates
6202 * lprc->left specifies the portion of the item for which the bounding
6203 * rectangle will be retrieved.
6205 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
6206 * including the icon and label.
6209 * * Experiment shows that native control returns:
6210 * * width = min (48, length of text line)
6211 * * .left = position.x - (width - iconsize.cx)/2
6212 * * .right = .left + width
6213 * * height = #lines of text * ntmHeight + icon height + 8
6214 * * .top = position.y - 2
6215 * * .bottom = .top + height
6216 * * separation between items .y = itemSpacing.cy - height
6217 * * .x = itemSpacing.cx - width
6218 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
6221 * * Experiment shows that native control returns:
6222 * * width = iconSize.cx + 16
6223 * * .left = position.x - (width - iconsize.cx)/2
6224 * * .right = .left + width
6225 * * height = iconSize.cy + 4
6226 * * .top = position.y - 2
6227 * * .bottom = .top + height
6228 * * separation between items .y = itemSpacing.cy - height
6229 * * .x = itemSpacing.cx - width
6230 * LVIR_LABEL Returns the bounding rectangle of the item text.
6233 * * Experiment shows that native control returns:
6234 * * width = text length
6235 * * .left = position.x - width/2
6236 * * .right = .left + width
6237 * * height = ntmH * linecount + 2
6238 * * .top = position.y + iconSize.cy + 6
6239 * * .bottom = .top + height
6240 * * separation between items .y = itemSpacing.cy - height
6241 * * .x = itemSpacing.cx - width
6242 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
6243 * rectangles, but excludes columns in report view.
6250 * Note that the bounding rectangle of the label in the LVS_ICON view depends
6251 * upon whether the window has the focus currently and on whether the item
6252 * is the one with the focus. Ensure that the control's record of which
6253 * item has the focus agrees with the items' records.
6255 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
6257 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6258 BOOL doLabel = TRUE, oversizedBox = FALSE;
6259 POINT Position, Origin;
6263 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
6265 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
6267 LISTVIEW_GetOrigin(infoPtr, &Origin);
6268 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
6270 /* Be smart and try to figure out the minimum we have to do */
6271 if (lprc->left == LVIR_ICON) doLabel = FALSE;
6272 if (infoPtr->uView == LV_VIEW_DETAILS && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
6273 if (infoPtr->uView == LV_VIEW_ICON && lprc->left != LVIR_ICON &&
6274 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
6275 oversizedBox = TRUE;
6277 /* get what we need from the item before hand, so we make
6278 * only one request. This can speed up things, if data
6279 * is stored on the app side */
6281 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
6282 if (doLabel) lvItem.mask |= LVIF_TEXT;
6283 lvItem.iItem = nItem;
6284 lvItem.iSubItem = 0;
6285 lvItem.pszText = szDispText;
6286 lvItem.cchTextMax = DISP_TEXT_SIZE;
6287 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
6288 /* we got the state already up, simulate it here, to avoid a reget */
6289 if (infoPtr->uView == LV_VIEW_ICON && (lprc->left != LVIR_ICON))
6291 lvItem.mask |= LVIF_STATE;
6292 lvItem.stateMask = LVIS_FOCUSED;
6293 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
6296 if (infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
6297 lprc->left = LVIR_BOUNDS;
6303 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
6307 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
6311 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
6314 case LVIR_SELECTBOUNDS:
6315 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
6319 WARN("Unknown value: %d\n", lprc->left);
6323 if (infoPtr->uView == LV_VIEW_DETAILS)
6325 if (mode != LVIR_BOUNDS)
6326 OffsetRect(lprc, Origin.x + LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left,
6327 Position.y + Origin.y);
6329 OffsetRect(lprc, Origin.x, Position.y + Origin.y);
6332 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
6334 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
6341 * Retrieves the spacing between listview control items.
6344 * [I] infoPtr : valid pointer to the listview structure
6345 * [IO] lprc : rectangle to receive the output
6346 * on input, lprc->top = nSubItem
6347 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
6349 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
6350 * not only those of the first column.
6351 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
6357 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
6363 if (!lprc) return FALSE;
6365 nColumn = lprc->top;
6367 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
6368 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
6370 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
6372 if (infoPtr->uView != LV_VIEW_DETAILS) return FALSE;
6374 /* special case for header items */
6377 if (lprc->left != LVIR_BOUNDS)
6379 FIXME("Only LVIR_BOUNDS is implemented for header, got %d\n", lprc->left);
6383 if (infoPtr->hwndHeader)
6384 return SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, lprc->top, (LPARAM)lprc);
6387 memset(lprc, 0, sizeof(RECT));
6392 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
6394 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6397 lvItem.iItem = nItem;
6398 lvItem.iSubItem = nColumn;
6400 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
6404 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
6409 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
6413 ERR("Unknown bounds=%d\n", lprc->left);
6417 OffsetRect(lprc, 0, Position.y);
6424 * Retrieves the width of a label.
6427 * [I] infoPtr : valid pointer to the listview structure
6430 * SUCCESS : string width (in pixels)
6433 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *infoPtr, INT nItem)
6435 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6438 TRACE("(nItem=%d)\n", nItem);
6440 lvItem.mask = LVIF_TEXT;
6441 lvItem.iItem = nItem;
6442 lvItem.iSubItem = 0;
6443 lvItem.pszText = szDispText;
6444 lvItem.cchTextMax = DISP_TEXT_SIZE;
6445 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6447 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6452 * Retrieves the spacing between listview control items.
6455 * [I] infoPtr : valid pointer to the listview structure
6456 * [I] bSmall : flag for small or large icon
6459 * Horizontal + vertical spacing
6461 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
6467 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6471 if (infoPtr->uView == LV_VIEW_ICON)
6472 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
6474 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
6481 * Retrieves the state of a listview control item.
6484 * [I] infoPtr : valid pointer to the listview structure
6485 * [I] nItem : item index
6486 * [I] uMask : state mask
6489 * State specified by the mask.
6491 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
6495 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6497 lvItem.iItem = nItem;
6498 lvItem.iSubItem = 0;
6499 lvItem.mask = LVIF_STATE;
6500 lvItem.stateMask = uMask;
6501 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6503 return lvItem.state & uMask;
6508 * Retrieves the text of a listview control item or subitem.
6511 * [I] hwnd : window handle
6512 * [I] nItem : item index
6513 * [IO] lpLVItem : item information
6514 * [I] isW : TRUE if lpLVItem is Unicode
6517 * SUCCESS : string length
6520 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
6522 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6524 lpLVItem->mask = LVIF_TEXT;
6525 lpLVItem->iItem = nItem;
6526 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
6528 return textlenT(lpLVItem->pszText, isW);
6533 * Searches for an item based on properties + relationships.
6536 * [I] infoPtr : valid pointer to the listview structure
6537 * [I] nItem : item index
6538 * [I] uFlags : relationship flag
6541 * SUCCESS : item index
6544 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
6547 LVFINDINFOW lvFindInfo;
6548 INT nCountPerColumn;
6552 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
6553 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
6555 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
6557 if (uFlags & LVNI_CUT)
6560 if (uFlags & LVNI_DROPHILITED)
6561 uMask |= LVIS_DROPHILITED;
6563 if (uFlags & LVNI_FOCUSED)
6564 uMask |= LVIS_FOCUSED;
6566 if (uFlags & LVNI_SELECTED)
6567 uMask |= LVIS_SELECTED;
6569 /* if we're asked for the focused item, that's only one,
6570 * so it's worth optimizing */
6571 if (uFlags & LVNI_FOCUSED)
6573 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
6574 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
6577 if (uFlags & LVNI_ABOVE)
6579 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
6584 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6590 /* Special case for autoarrange - move 'til the top of a list */
6591 if (is_autoarrange(infoPtr))
6593 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6594 while (nItem - nCountPerRow >= 0)
6596 nItem -= nCountPerRow;
6597 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6602 lvFindInfo.flags = LVFI_NEARESTXY;
6603 lvFindInfo.vkDirection = VK_UP;
6604 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6605 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6607 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6612 else if (uFlags & LVNI_BELOW)
6614 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
6616 while (nItem < infoPtr->nItemCount)
6619 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6625 /* Special case for autoarrange - move 'til the bottom of a list */
6626 if (is_autoarrange(infoPtr))
6628 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6629 while (nItem + nCountPerRow < infoPtr->nItemCount )
6631 nItem += nCountPerRow;
6632 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6637 lvFindInfo.flags = LVFI_NEARESTXY;
6638 lvFindInfo.vkDirection = VK_DOWN;
6639 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6640 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6642 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6647 else if (uFlags & LVNI_TOLEFT)
6649 if (infoPtr->uView == LV_VIEW_LIST)
6651 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6652 while (nItem - nCountPerColumn >= 0)
6654 nItem -= nCountPerColumn;
6655 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6659 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
6661 /* Special case for autoarrange - move 'til the beginning of a row */
6662 if (is_autoarrange(infoPtr))
6664 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6665 while (nItem % nCountPerRow > 0)
6668 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6673 lvFindInfo.flags = LVFI_NEARESTXY;
6674 lvFindInfo.vkDirection = VK_LEFT;
6675 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6676 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6678 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6683 else if (uFlags & LVNI_TORIGHT)
6685 if (infoPtr->uView == LV_VIEW_LIST)
6687 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6688 while (nItem + nCountPerColumn < infoPtr->nItemCount)
6690 nItem += nCountPerColumn;
6691 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6695 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
6697 /* Special case for autoarrange - move 'til the end of a row */
6698 if (is_autoarrange(infoPtr))
6700 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6701 while (nItem % nCountPerRow < nCountPerRow - 1 )
6704 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6709 lvFindInfo.flags = LVFI_NEARESTXY;
6710 lvFindInfo.vkDirection = VK_RIGHT;
6711 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6712 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6714 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6723 /* search by index */
6724 for (i = nItem; i < infoPtr->nItemCount; i++)
6726 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
6734 /* LISTVIEW_GetNumberOfWorkAreas */
6738 * Retrieves the origin coordinates when in icon or small icon display mode.
6741 * [I] infoPtr : valid pointer to the listview structure
6742 * [O] lpptOrigin : coordinate information
6747 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
6749 INT nHorzPos = 0, nVertPos = 0;
6750 SCROLLINFO scrollInfo;
6752 scrollInfo.cbSize = sizeof(SCROLLINFO);
6753 scrollInfo.fMask = SIF_POS;
6755 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
6756 nHorzPos = scrollInfo.nPos;
6757 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
6758 nVertPos = scrollInfo.nPos;
6760 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
6762 lpptOrigin->x = infoPtr->rcList.left;
6763 lpptOrigin->y = infoPtr->rcList.top;
6764 if (infoPtr->uView == LV_VIEW_LIST)
6765 nHorzPos *= infoPtr->nItemWidth;
6766 else if (infoPtr->uView == LV_VIEW_DETAILS)
6767 nVertPos *= infoPtr->nItemHeight;
6769 lpptOrigin->x -= nHorzPos;
6770 lpptOrigin->y -= nVertPos;
6772 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
6777 * Retrieves the width of a string.
6780 * [I] hwnd : window handle
6781 * [I] lpszText : text string to process
6782 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
6785 * SUCCESS : string width (in pixels)
6788 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
6793 if (is_textT(lpszText, isW))
6795 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
6796 HDC hdc = GetDC(infoPtr->hwndSelf);
6797 HFONT hOldFont = SelectObject(hdc, hFont);
6800 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
6802 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
6803 SelectObject(hdc, hOldFont);
6804 ReleaseDC(infoPtr->hwndSelf, hdc);
6806 return stringSize.cx;
6811 * Determines which listview item is located at the specified position.
6814 * [I] infoPtr : valid pointer to the listview structure
6815 * [IO] lpht : hit test information
6816 * [I] subitem : fill out iSubItem.
6817 * [I] select : return the index only if the hit selects the item
6820 * (mm 20001022): We must not allow iSubItem to be touched, for
6821 * an app might pass only a structure with space up to iItem!
6822 * (MS Office 97 does that for instance in the file open dialog)
6825 * SUCCESS : item index
6828 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6830 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6831 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6832 POINT Origin, Position, opt;
6837 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
6841 if (subitem) lpht->iSubItem = 0;
6843 LISTVIEW_GetOrigin(infoPtr, &Origin);
6845 /* set whole list relation flags */
6846 if (subitem && infoPtr->uView == LV_VIEW_DETAILS)
6848 /* LVM_SUBITEMHITTEST checks left bound of possible client area */
6849 if (infoPtr->rcList.left > lpht->pt.x && Origin.x < lpht->pt.x)
6850 lpht->flags |= LVHT_TOLEFT;
6852 if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
6853 opt.y = lpht->pt.y + infoPtr->rcList.top;
6857 if (infoPtr->rcList.bottom < opt.y)
6858 lpht->flags |= LVHT_BELOW;
6862 if (infoPtr->rcList.left > lpht->pt.x)
6863 lpht->flags |= LVHT_TOLEFT;
6864 else if (infoPtr->rcList.right < lpht->pt.x)
6865 lpht->flags |= LVHT_TORIGHT;
6867 if (infoPtr->rcList.top > lpht->pt.y)
6868 lpht->flags |= LVHT_ABOVE;
6869 else if (infoPtr->rcList.bottom < lpht->pt.y)
6870 lpht->flags |= LVHT_BELOW;
6873 /* even if item is invalid try to find subitem */
6874 if (infoPtr->uView == LV_VIEW_DETAILS && subitem)
6879 opt.x = lpht->pt.x - Origin.x;
6881 lpht->iSubItem = -1;
6882 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6884 pRect = &LISTVIEW_GetColumnInfo(infoPtr, j)->rcHeader;
6886 if ((opt.x >= pRect->left) && (opt.x < pRect->right))
6892 TRACE("lpht->iSubItem=%d\n", lpht->iSubItem);
6895 TRACE("lpht->flags=0x%x\n", lpht->flags);
6896 if (lpht->flags) return -1;
6898 lpht->flags |= LVHT_NOWHERE;
6900 /* first deal with the large items */
6901 rcSearch.left = lpht->pt.x;
6902 rcSearch.top = lpht->pt.y;
6903 rcSearch.right = rcSearch.left + 1;
6904 rcSearch.bottom = rcSearch.top + 1;
6906 iterator_frameditems(&i, infoPtr, &rcSearch);
6907 iterator_next(&i); /* go to first item in the sequence */
6909 iterator_destroy(&i);
6911 TRACE("lpht->iItem=%d\n", iItem);
6912 if (iItem == -1) return -1;
6914 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6915 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
6916 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6917 if (infoPtr->uView == LV_VIEW_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6918 lvItem.iItem = iItem;
6919 lvItem.iSubItem = subitem ? lpht->iSubItem : 0;
6920 lvItem.pszText = szDispText;
6921 lvItem.cchTextMax = DISP_TEXT_SIZE;
6922 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6923 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6925 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6926 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6927 opt.x = lpht->pt.x - Position.x - Origin.x;
6929 if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
6930 opt.y = lpht->pt.y - Position.y - Origin.y + infoPtr->rcList.top;
6932 opt.y = lpht->pt.y - Position.y - Origin.y;
6934 if (infoPtr->uView == LV_VIEW_DETAILS)
6937 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
6938 opt.x = lpht->pt.x - Origin.x;
6942 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6943 UnionRect(&rcBounds, &rcBounds, &rcState);
6945 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
6946 if (!PtInRect(&rcBounds, opt)) return -1;
6948 if (PtInRect(&rcIcon, opt))
6949 lpht->flags |= LVHT_ONITEMICON;
6950 else if (PtInRect(&rcLabel, opt))
6951 lpht->flags |= LVHT_ONITEMLABEL;
6952 else if (infoPtr->himlState && PtInRect(&rcState, opt))
6953 lpht->flags |= LVHT_ONITEMSTATEICON;
6954 /* special case for LVS_EX_FULLROWSELECT */
6955 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
6956 !(lpht->flags & LVHT_ONITEM))
6958 lpht->flags = LVHT_ONITEM | LVHT_ABOVE;
6960 if (lpht->flags & LVHT_ONITEM)
6961 lpht->flags &= ~LVHT_NOWHERE;
6962 TRACE("lpht->flags=0x%x\n", lpht->flags);
6964 if (select && !(infoPtr->uView == LV_VIEW_DETAILS &&
6965 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6966 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6968 if (infoPtr->uView == LV_VIEW_DETAILS)
6970 /* get main item bounds */
6971 lvItem.iSubItem = 0;
6972 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6973 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6974 UnionRect(&rcBounds, &rcBounds, &rcState);
6976 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6978 return lpht->iItem = iItem;
6983 * Inserts a new item in the listview control.
6986 * [I] infoPtr : valid pointer to the listview structure
6987 * [I] lpLVItem : item information
6988 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6991 * SUCCESS : new item index
6994 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
7001 BOOL is_sorted, has_changed;
7003 HWND hwndSelf = infoPtr->hwndSelf;
7005 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
7007 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
7009 /* make sure it's an item, and not a subitem; cannot insert a subitem */
7010 if (!lpLVItem || lpLVItem->iSubItem) return -1;
7012 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
7014 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
7016 /* insert item in listview control data structure */
7017 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
7018 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
7020 /* link with id struct */
7021 if (!(lpID = Alloc(sizeof(ITEM_ID)))) goto fail;
7023 lpID->item = hdpaSubItems;
7024 lpID->id = get_next_itemid(infoPtr);
7025 if ( DPA_InsertPtr(infoPtr->hdpaItemIds, infoPtr->nItemCount, lpID) == -1) goto fail;
7027 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
7028 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
7030 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
7032 /* calculate new item index */
7039 while (i < infoPtr->nItemCount)
7041 hItem = DPA_GetPtr( infoPtr->hdpaItems, i);
7042 item_s = (ITEM_INFO*)DPA_GetPtr(hItem, 0);
7044 cmpv = textcmpWT(item_s->hdr.pszText, lpLVItem->pszText, TRUE);
7045 if (infoPtr->dwStyle & LVS_SORTDESCENDING) cmpv *= -1;
7047 if (cmpv >= 0) break;
7053 nItem = min(lpLVItem->iItem, infoPtr->nItemCount);
7055 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
7056 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
7057 if (nItem == -1) goto fail;
7058 infoPtr->nItemCount++;
7060 /* shift indices first so they don't get tangled */
7061 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
7063 /* set the item attributes */
7064 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
7066 /* full size structure expected - _WIN32IE >= 0x560 */
7069 else if (lpLVItem->mask & LVIF_INDENT)
7071 /* indent member expected - _WIN32IE >= 0x300 */
7072 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
7076 /* minimal structure expected */
7077 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
7080 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7082 item.mask |= LVIF_STATE;
7083 item.stateMask |= LVIS_STATEIMAGEMASK;
7084 item.state &= ~LVIS_STATEIMAGEMASK;
7085 item.state |= INDEXTOSTATEIMAGEMASK(1);
7087 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
7089 /* make room for the position, if we are in the right mode */
7090 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7092 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
7094 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
7096 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
7101 /* send LVN_INSERTITEM notification */
7102 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
7104 nmlv.lParam = lpItem->lParam;
7105 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
7106 if (!IsWindow(hwndSelf))
7109 /* align items (set position of each item) */
7110 if (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON)
7114 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
7115 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
7117 LISTVIEW_NextIconPosTop(infoPtr, &pt);
7119 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
7122 /* now is the invalidation fun */
7123 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
7127 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
7128 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
7129 infoPtr->nItemCount--;
7131 DPA_DeletePtr(hdpaSubItems, 0);
7132 DPA_Destroy (hdpaSubItems);
7139 * Checks item visibility.
7142 * [I] infoPtr : valid pointer to the listview structure
7143 * [I] nFirst : item index to check for
7146 * Item visible : TRUE
7147 * Item invisible or failure : FALSE
7149 static BOOL LISTVIEW_IsItemVisible(const LISTVIEW_INFO *infoPtr, INT nItem)
7151 POINT Origin, Position;
7156 TRACE("nItem=%d\n", nItem);
7158 if (nItem < 0 || nItem >= DPA_GetPtrCount(infoPtr->hdpaItems)) return FALSE;
7160 LISTVIEW_GetOrigin(infoPtr, &Origin);
7161 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
7162 rcItem.left = Position.x + Origin.x;
7163 rcItem.top = Position.y + Origin.y;
7164 rcItem.right = rcItem.left + infoPtr->nItemWidth;
7165 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
7167 hdc = GetDC(infoPtr->hwndSelf);
7168 if (!hdc) return FALSE;
7169 ret = RectVisible(hdc, &rcItem);
7170 ReleaseDC(infoPtr->hwndSelf, hdc);
7177 * Redraws a range of items.
7180 * [I] infoPtr : valid pointer to the listview structure
7181 * [I] nFirst : first item
7182 * [I] nLast : last item
7188 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
7192 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
7193 max(nFirst, nLast) >= infoPtr->nItemCount)
7196 for (i = nFirst; i <= nLast; i++)
7197 LISTVIEW_InvalidateItem(infoPtr, i);
7204 * Scroll the content of a listview.
7207 * [I] infoPtr : valid pointer to the listview structure
7208 * [I] dx : horizontal scroll amount in pixels
7209 * [I] dy : vertical scroll amount in pixels
7216 * If the control is in report view (LV_VIEW_DETAILS) the control can
7217 * be scrolled only in line increments. "dy" will be rounded to the
7218 * nearest number of pixels that are a whole line. Ex: if line height
7219 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
7220 * is passed, then the scroll will be 0. (per MSDN 7/2002)
7222 * For: (per experimentation with native control and CSpy ListView)
7223 * LV_VIEW_ICON dy=1 = 1 pixel (vertical only)
7225 * LV_VIEW_SMALLICON dy=1 = 1 pixel (vertical only)
7227 * LV_VIEW_LIST dx=1 = 1 column (horizontal only)
7228 * but will only scroll 1 column per message
7229 * no matter what the value.
7230 * dy must be 0 or FALSE returned.
7231 * LV_VIEW_DETAILS dx=1 = 1 pixel
7235 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7237 switch(infoPtr->uView) {
7238 case LV_VIEW_DETAILS:
7239 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
7240 dy /= infoPtr->nItemHeight;
7243 if (dy != 0) return FALSE;
7250 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
7251 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
7258 * Sets the background color.
7261 * [I] infoPtr : valid pointer to the listview structure
7262 * [I] clrBk : background color
7268 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
7270 TRACE("(clrBk=%x)\n", clrBk);
7272 if(infoPtr->clrBk != clrBk) {
7273 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
7274 infoPtr->clrBk = clrBk;
7275 if (clrBk == CLR_NONE)
7276 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
7278 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
7279 LISTVIEW_InvalidateList(infoPtr);
7285 /* LISTVIEW_SetBkImage */
7287 /*** Helper for {Insert,Set}ColumnT *only* */
7288 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
7289 const LVCOLUMNW *lpColumn, BOOL isW)
7291 if (lpColumn->mask & LVCF_FMT)
7293 /* format member is valid */
7294 lphdi->mask |= HDI_FORMAT;
7296 /* set text alignment (leftmost column must be left-aligned) */
7297 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
7298 lphdi->fmt |= HDF_LEFT;
7299 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
7300 lphdi->fmt |= HDF_RIGHT;
7301 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
7302 lphdi->fmt |= HDF_CENTER;
7304 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
7305 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
7307 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
7309 lphdi->fmt |= HDF_IMAGE;
7310 lphdi->iImage = I_IMAGECALLBACK;
7314 if (lpColumn->mask & LVCF_WIDTH)
7316 lphdi->mask |= HDI_WIDTH;
7317 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
7319 /* make it fill the remainder of the controls width */
7323 for(item_index = 0; item_index < (nColumn - 1); item_index++)
7325 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
7326 lphdi->cxy += rcHeader.right - rcHeader.left;
7329 /* retrieve the layout of the header */
7330 GetClientRect(infoPtr->hwndSelf, &rcHeader);
7331 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
7333 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
7336 lphdi->cxy = lpColumn->cx;
7339 if (lpColumn->mask & LVCF_TEXT)
7341 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
7342 lphdi->fmt |= HDF_STRING;
7343 lphdi->pszText = lpColumn->pszText;
7344 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
7347 if (lpColumn->mask & LVCF_IMAGE)
7349 lphdi->mask |= HDI_IMAGE;
7350 lphdi->iImage = lpColumn->iImage;
7353 if (lpColumn->mask & LVCF_ORDER)
7355 lphdi->mask |= HDI_ORDER;
7356 lphdi->iOrder = lpColumn->iOrder;
7363 * Inserts a new column.
7366 * [I] infoPtr : valid pointer to the listview structure
7367 * [I] nColumn : column index
7368 * [I] lpColumn : column information
7369 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
7372 * SUCCESS : new column index
7375 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
7376 const LVCOLUMNW *lpColumn, BOOL isW)
7378 COLUMN_INFO *lpColumnInfo;
7382 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
7384 if (!lpColumn || nColumn < 0) return -1;
7385 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
7387 ZeroMemory(&hdi, sizeof(HDITEMW));
7388 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
7391 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
7392 * (can be seen in SPY) otherwise column never gets added.
7394 if (!(lpColumn->mask & LVCF_WIDTH)) {
7395 hdi.mask |= HDI_WIDTH;
7400 * when the iSubItem is available Windows copies it to the header lParam. It seems
7401 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
7403 if (lpColumn->mask & LVCF_SUBITEM)
7405 hdi.mask |= HDI_LPARAM;
7406 hdi.lParam = lpColumn->iSubItem;
7409 /* create header if not present */
7410 LISTVIEW_CreateHeader(infoPtr);
7411 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle) &&
7412 (infoPtr->uView == LV_VIEW_DETAILS) && (WS_VISIBLE & infoPtr->dwStyle))
7414 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
7417 /* insert item in header control */
7418 nNewColumn = SendMessageW(infoPtr->hwndHeader,
7419 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
7420 (WPARAM)nColumn, (LPARAM)&hdi);
7421 if (nNewColumn == -1) return -1;
7422 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
7424 /* create our own column info */
7425 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
7426 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
7428 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
7429 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, nNewColumn, (LPARAM)&lpColumnInfo->rcHeader))
7432 /* now we have to actually adjust the data */
7433 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
7435 SUBITEM_INFO *lpSubItem;
7439 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
7441 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
7442 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
7444 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
7445 if (lpSubItem->iSubItem >= nNewColumn)
7446 lpSubItem->iSubItem++;
7451 /* make space for the new column */
7452 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
7453 LISTVIEW_UpdateItemSize(infoPtr);
7458 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
7461 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
7469 * Sets the attributes of a header item.
7472 * [I] infoPtr : valid pointer to the listview structure
7473 * [I] nColumn : column index
7474 * [I] lpColumn : column attributes
7475 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
7481 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
7482 const LVCOLUMNW *lpColumn, BOOL isW)
7484 HDITEMW hdi, hdiget;
7487 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
7489 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7491 ZeroMemory(&hdi, sizeof(HDITEMW));
7492 if (lpColumn->mask & LVCF_FMT)
7494 hdi.mask |= HDI_FORMAT;
7495 hdiget.mask = HDI_FORMAT;
7496 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdiget))
7497 hdi.fmt = hdiget.fmt & HDF_STRING;
7499 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
7501 /* set header item attributes */
7502 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
7503 if (!bResult) return FALSE;
7505 if (lpColumn->mask & LVCF_FMT)
7507 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
7508 int oldFmt = lpColumnInfo->fmt;
7510 lpColumnInfo->fmt = lpColumn->fmt;
7511 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
7513 if (infoPtr->uView == LV_VIEW_DETAILS) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
7522 * Sets the column order array
7525 * [I] infoPtr : valid pointer to the listview structure
7526 * [I] iCount : number of elements in column order array
7527 * [I] lpiArray : pointer to column order array
7533 static BOOL LISTVIEW_SetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
7535 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
7546 * Sets the width of a column
7549 * [I] infoPtr : valid pointer to the listview structure
7550 * [I] nColumn : column index
7551 * [I] cx : column width
7557 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
7559 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
7563 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
7565 /* set column width only if in report or list mode */
7566 if (infoPtr->uView != LV_VIEW_DETAILS && infoPtr->uView != LV_VIEW_LIST) return FALSE;
7568 /* take care of invalid cx values */
7569 if(infoPtr->uView == LV_VIEW_DETAILS && cx < -2) cx = LVSCW_AUTOSIZE;
7570 else if (infoPtr->uView == LV_VIEW_LIST && cx < 1) return FALSE;
7572 /* resize all columns if in LV_VIEW_LIST mode */
7573 if(infoPtr->uView == LV_VIEW_LIST)
7575 infoPtr->nItemWidth = cx;
7576 LISTVIEW_InvalidateList(infoPtr);
7580 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7582 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
7587 lvItem.mask = LVIF_TEXT;
7589 lvItem.iSubItem = nColumn;
7590 lvItem.pszText = szDispText;
7591 lvItem.cchTextMax = DISP_TEXT_SIZE;
7592 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7594 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
7595 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
7596 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
7598 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
7599 max_cx += infoPtr->iconSize.cx;
7600 max_cx += TRAILING_LABEL_PADDING;
7603 /* autosize based on listview items width */
7604 if(cx == LVSCW_AUTOSIZE)
7606 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
7608 /* if iCol is the last column make it fill the remainder of the controls width */
7609 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
7614 LISTVIEW_GetOrigin(infoPtr, &Origin);
7615 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
7617 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
7621 /* Despite what the MS docs say, if this is not the last
7622 column, then MS resizes the column to the width of the
7623 largest text string in the column, including headers
7624 and items. This is different from LVSCW_AUTOSIZE in that
7625 LVSCW_AUTOSIZE ignores the header string length. */
7628 /* retrieve header text */
7629 hdi.mask = HDI_TEXT;
7630 hdi.cchTextMax = DISP_TEXT_SIZE;
7631 hdi.pszText = szDispText;
7632 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdi))
7634 HDC hdc = GetDC(infoPtr->hwndSelf);
7635 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
7638 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
7639 cx = size.cx + TRAILING_HEADER_PADDING;
7640 /* FIXME: Take into account the header image, if one is present */
7641 SelectObject(hdc, old_font);
7642 ReleaseDC(infoPtr->hwndSelf, hdc);
7644 cx = max (cx, max_cx);
7648 if (cx < 0) return FALSE;
7650 /* call header to update the column change */
7651 hdi.mask = HDI_WIDTH;
7653 TRACE("hdi.cxy=%d\n", hdi.cxy);
7654 return SendMessageW(infoPtr->hwndHeader, HDM_SETITEMW, nColumn, (LPARAM)&hdi);
7658 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
7661 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
7664 HBITMAP hbm_im, hbm_mask, hbm_orig;
7666 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
7667 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
7670 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
7671 ILC_COLOR | ILC_MASK, 2, 2);
7672 hdc_wnd = GetDC(infoPtr->hwndSelf);
7673 hdc = CreateCompatibleDC(hdc_wnd);
7674 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
7675 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
7676 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
7678 rc.left = rc.top = 0;
7679 rc.right = GetSystemMetrics(SM_CXSMICON);
7680 rc.bottom = GetSystemMetrics(SM_CYSMICON);
7682 hbm_orig = SelectObject(hdc, hbm_mask);
7683 FillRect(hdc, &rc, hbr_white);
7684 InflateRect(&rc, -2, -2);
7685 FillRect(hdc, &rc, hbr_black);
7687 SelectObject(hdc, hbm_im);
7688 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
7689 SelectObject(hdc, hbm_orig);
7690 ImageList_Add(himl, hbm_im, hbm_mask);
7692 SelectObject(hdc, hbm_im);
7693 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
7694 SelectObject(hdc, hbm_orig);
7695 ImageList_Add(himl, hbm_im, hbm_mask);
7697 DeleteObject(hbm_mask);
7698 DeleteObject(hbm_im);
7706 * Sets the extended listview style.
7709 * [I] infoPtr : valid pointer to the listview structure
7711 * [I] dwStyle : style
7714 * SUCCESS : previous style
7717 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwExStyle)
7719 DWORD dwOldExStyle = infoPtr->dwLvExStyle;
7723 infoPtr->dwLvExStyle = (dwOldExStyle & ~dwMask) | (dwExStyle & dwMask);
7725 infoPtr->dwLvExStyle = dwExStyle;
7727 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_CHECKBOXES)
7729 HIMAGELIST himl = 0;
7730 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7733 item.mask = LVIF_STATE;
7734 item.stateMask = LVIS_STATEIMAGEMASK;
7735 item.state = INDEXTOSTATEIMAGEMASK(1);
7736 LISTVIEW_SetItemState(infoPtr, -1, &item);
7738 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
7740 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
7743 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_HEADERDRAGDROP)
7747 /* if not already created */
7748 LISTVIEW_CreateHeader(infoPtr);
7750 dwStyle = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
7751 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
7752 dwStyle |= HDS_DRAGDROP;
7754 dwStyle &= ~HDS_DRAGDROP;
7755 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, dwStyle);
7758 /* GRIDLINES adds decoration at top so changes sizes */
7759 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_GRIDLINES)
7761 LISTVIEW_UpdateSize(infoPtr);
7765 LISTVIEW_InvalidateList(infoPtr);
7766 return dwOldExStyle;
7771 * Sets the new hot cursor used during hot tracking and hover selection.
7774 * [I] infoPtr : valid pointer to the listview structure
7775 * [I] hCursor : the new hot cursor handle
7778 * Returns the previous hot cursor
7780 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
7782 HCURSOR oldCursor = infoPtr->hHotCursor;
7784 infoPtr->hHotCursor = hCursor;
7792 * Sets the hot item index.
7795 * [I] infoPtr : valid pointer to the listview structure
7796 * [I] iIndex : index
7799 * SUCCESS : previous hot item index
7800 * FAILURE : -1 (no hot item)
7802 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
7804 INT iOldIndex = infoPtr->nHotItem;
7806 infoPtr->nHotItem = iIndex;
7814 * Sets the amount of time the cursor must hover over an item before it is selected.
7817 * [I] infoPtr : valid pointer to the listview structure
7818 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
7821 * Returns the previous hover time
7823 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
7825 DWORD oldHoverTime = infoPtr->dwHoverTime;
7827 infoPtr->dwHoverTime = dwHoverTime;
7829 return oldHoverTime;
7834 * Sets spacing for icons of LVS_ICON style.
7837 * [I] infoPtr : valid pointer to the listview structure
7838 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
7839 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
7842 * MAKELONG(oldcx, oldcy)
7844 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
7846 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7848 TRACE("requested=(%d,%d)\n", cx, cy);
7850 /* this is supported only for LVS_ICON style */
7851 if (infoPtr->uView != LV_VIEW_ICON) return oldspacing;
7853 /* set to defaults, if instructed to */
7854 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
7855 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
7857 /* if 0 then compute width
7858 * FIXME: Should scan each item and determine max width of
7859 * icon or label, then make that the width */
7861 cx = infoPtr->iconSpacing.cx;
7863 /* if 0 then compute height */
7865 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
7866 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
7869 infoPtr->iconSpacing.cx = cx;
7870 infoPtr->iconSpacing.cy = cy;
7872 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
7873 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
7874 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
7875 infoPtr->ntmHeight);
7877 /* these depend on the iconSpacing */
7878 LISTVIEW_UpdateItemSize(infoPtr);
7883 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
7887 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
7894 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
7895 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
7904 * [I] infoPtr : valid pointer to the listview structure
7905 * [I] nType : image list type
7906 * [I] himl : image list handle
7909 * SUCCESS : old image list
7912 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
7914 INT oldHeight = infoPtr->nItemHeight;
7915 HIMAGELIST himlOld = 0;
7917 TRACE("(nType=%d, himl=%p\n", nType, himl);
7922 himlOld = infoPtr->himlNormal;
7923 infoPtr->himlNormal = himl;
7924 if (infoPtr->uView == LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
7925 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
7929 himlOld = infoPtr->himlSmall;
7930 infoPtr->himlSmall = himl;
7931 if (infoPtr->uView != LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
7935 himlOld = infoPtr->himlState;
7936 infoPtr->himlState = himl;
7937 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
7938 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
7942 ERR("Unknown icon type=%d\n", nType);
7946 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
7947 if (infoPtr->nItemHeight != oldHeight)
7948 LISTVIEW_UpdateScroll(infoPtr);
7955 * Preallocates memory (does *not* set the actual count of items !)
7958 * [I] infoPtr : valid pointer to the listview structure
7959 * [I] nItems : item count (projected number of items to allocate)
7960 * [I] dwFlags : update flags
7966 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7968 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
7970 if (infoPtr->dwStyle & LVS_OWNERDATA)
7972 INT nOldCount = infoPtr->nItemCount;
7974 if (nItems < nOldCount)
7976 RANGE range = { nItems, nOldCount };
7977 ranges_del(infoPtr->selectionRanges, range);
7978 if (infoPtr->nFocusedItem >= nItems)
7980 LISTVIEW_SetItemFocus(infoPtr, -1);
7981 SetRectEmpty(&infoPtr->rcFocus);
7985 infoPtr->nItemCount = nItems;
7986 LISTVIEW_UpdateScroll(infoPtr);
7988 /* the flags are valid only in ownerdata report and list modes */
7989 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON) dwFlags = 0;
7991 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7992 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7994 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7995 LISTVIEW_InvalidateList(infoPtr);
8002 LISTVIEW_GetOrigin(infoPtr, &Origin);
8003 nFrom = min(nOldCount, nItems);
8004 nTo = max(nOldCount, nItems);
8006 if (infoPtr->uView == LV_VIEW_DETAILS)
8009 rcErase.top = nFrom * infoPtr->nItemHeight;
8010 rcErase.right = infoPtr->nItemWidth;
8011 rcErase.bottom = nTo * infoPtr->nItemHeight;
8012 OffsetRect(&rcErase, Origin.x, Origin.y);
8013 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8014 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8016 else /* LV_VIEW_LIST */
8018 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
8020 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
8021 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
8022 rcErase.right = rcErase.left + infoPtr->nItemWidth;
8023 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
8024 OffsetRect(&rcErase, Origin.x, Origin.y);
8025 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8026 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8028 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
8030 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
8031 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
8032 OffsetRect(&rcErase, Origin.x, Origin.y);
8033 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8034 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8040 /* According to MSDN for non-LVS_OWNERDATA this is just
8041 * a performance issue. The control allocates its internal
8042 * data structures for the number of items specified. It
8043 * cuts down on the number of memory allocations. Therefore
8044 * we will just issue a WARN here
8046 WARN("for non-ownerdata performance option not implemented.\n");
8054 * Sets the position of an item.
8057 * [I] infoPtr : valid pointer to the listview structure
8058 * [I] nItem : item index
8059 * [I] pt : coordinate
8065 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
8069 TRACE("(nItem=%d, &pt=%s\n", nItem, wine_dbgstr_point(&pt));
8071 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
8072 !(infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)) return FALSE;
8074 LISTVIEW_GetOrigin(infoPtr, &Origin);
8076 /* This point value seems to be an undocumented feature.
8077 * The best guess is that it means either at the origin,
8078 * or at true beginning of the list. I will assume the origin. */
8079 if ((pt.x == -1) && (pt.y == -1))
8082 if (infoPtr->uView == LV_VIEW_ICON)
8084 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
8085 pt.y -= ICON_TOP_PADDING;
8090 infoPtr->bAutoarrange = FALSE;
8092 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
8097 * Sets the state of one or many items.
8100 * [I] infoPtr : valid pointer to the listview structure
8101 * [I] nItem : item index
8102 * [I] lpLVItem : item or subitem info
8108 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
8110 BOOL bResult = TRUE;
8113 lvItem.iItem = nItem;
8114 lvItem.iSubItem = 0;
8115 lvItem.mask = LVIF_STATE;
8116 lvItem.state = lpLVItem->state;
8117 lvItem.stateMask = lpLVItem->stateMask;
8118 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
8122 /* select all isn't allowed in LVS_SINGLESEL */
8123 if ((lvItem.state & lvItem.stateMask & LVIS_SELECTED) && (infoPtr->dwStyle & LVS_SINGLESEL))
8126 /* focus all isn't allowed */
8127 if (lvItem.state & lvItem.stateMask & LVIS_FOCUSED) return FALSE;
8129 /* apply to all items */
8130 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
8131 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
8134 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
8141 * Sets the text of an item or subitem.
8144 * [I] hwnd : window handle
8145 * [I] nItem : item index
8146 * [I] lpLVItem : item or subitem info
8147 * [I] isW : TRUE if input is Unicode
8153 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
8157 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
8159 lvItem.iItem = nItem;
8160 lvItem.iSubItem = lpLVItem->iSubItem;
8161 lvItem.mask = LVIF_TEXT;
8162 lvItem.pszText = lpLVItem->pszText;
8163 lvItem.cchTextMax = lpLVItem->cchTextMax;
8165 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
8167 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
8172 * Set item index that marks the start of a multiple selection.
8175 * [I] infoPtr : valid pointer to the listview structure
8176 * [I] nIndex : index
8179 * Index number or -1 if there is no selection mark.
8181 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
8183 INT nOldIndex = infoPtr->nSelectionMark;
8185 TRACE("(nIndex=%d)\n", nIndex);
8187 infoPtr->nSelectionMark = nIndex;
8194 * Sets the text background color.
8197 * [I] infoPtr : valid pointer to the listview structure
8198 * [I] clrTextBk : text background color
8204 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
8206 TRACE("(clrTextBk=%x)\n", clrTextBk);
8208 if (infoPtr->clrTextBk != clrTextBk)
8210 infoPtr->clrTextBk = clrTextBk;
8211 LISTVIEW_InvalidateList(infoPtr);
8219 * Sets the text foreground color.
8222 * [I] infoPtr : valid pointer to the listview structure
8223 * [I] clrText : text color
8229 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
8231 TRACE("(clrText=%x)\n", clrText);
8233 if (infoPtr->clrText != clrText)
8235 infoPtr->clrText = clrText;
8236 LISTVIEW_InvalidateList(infoPtr);
8244 * Sets new ToolTip window to ListView control.
8247 * [I] infoPtr : valid pointer to the listview structure
8248 * [I] hwndNewToolTip : handle to new ToolTip
8253 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
8255 HWND hwndOldToolTip = infoPtr->hwndToolTip;
8256 infoPtr->hwndToolTip = hwndNewToolTip;
8257 return hwndOldToolTip;
8262 * sets the Unicode character format flag for the control
8264 * [I] infoPtr :valid pointer to the listview structure
8265 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
8268 * Old Unicode Format
8270 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL fUnicode)
8272 SHORT rc = infoPtr->notifyFormat;
8273 infoPtr->notifyFormat = (fUnicode) ? NFR_UNICODE : NFR_ANSI;
8274 return rc == NFR_UNICODE;
8279 * sets the control view mode
8281 * [I] infoPtr :valid pointer to the listview structure
8282 * [I] nView :new view mode value
8288 static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView)
8290 SIZE oldIconSize = infoPtr->iconSize;
8293 if (infoPtr->uView == nView) return 1;
8295 if ((INT)nView < 0 || nView > LV_VIEW_MAX) return -1;
8296 if (nView == LV_VIEW_TILE)
8298 FIXME("View LV_VIEW_TILE unimplemented\n");
8302 infoPtr->uView = nView;
8304 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8305 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8307 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
8308 SetRectEmpty(&infoPtr->rcFocus);
8310 himl = (nView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
8311 set_icon_size(&infoPtr->iconSize, himl, nView != LV_VIEW_ICON);
8316 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
8318 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
8319 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
8320 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
8322 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8324 case LV_VIEW_SMALLICON:
8325 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8327 case LV_VIEW_DETAILS:
8332 LISTVIEW_CreateHeader( infoPtr );
8334 hl.prc = &infoPtr->rcList;
8336 SendMessageW(infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl);
8337 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
8338 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
8345 LISTVIEW_UpdateItemSize(infoPtr);
8346 LISTVIEW_UpdateSize(infoPtr);
8347 LISTVIEW_UpdateScroll(infoPtr);
8348 LISTVIEW_InvalidateList(infoPtr);
8350 TRACE("nView=%d\n", nView);
8355 /* LISTVIEW_SetWorkAreas */
8359 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMS
8362 * [I] first : pointer to first ITEM_INFO to compare
8363 * [I] second : pointer to second ITEM_INFO to compare
8364 * [I] lParam : HWND of control
8367 * if first comes before second : negative
8368 * if first comes after second : positive
8369 * if first and second are equivalent : zero
8371 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
8373 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
8374 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
8375 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
8377 /* Forward the call to the client defined callback */
8378 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
8383 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMSEX
8386 * [I] first : pointer to first ITEM_INFO to compare
8387 * [I] second : pointer to second ITEM_INFO to compare
8388 * [I] lParam : HWND of control
8391 * if first comes before second : negative
8392 * if first comes after second : positive
8393 * if first and second are equivalent : zero
8395 static INT WINAPI LISTVIEW_CallBackCompareEx(LPVOID first, LPVOID second, LPARAM lParam)
8397 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
8398 INT first_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, first );
8399 INT second_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, second );
8401 /* Forward the call to the client defined callback */
8402 return (infoPtr->pfnCompare)( first_idx, second_idx, infoPtr->lParamSort );
8407 * Sorts the listview items.
8410 * [I] infoPtr : valid pointer to the listview structure
8411 * [I] pfnCompare : application-defined value
8412 * [I] lParamSort : pointer to comparison callback
8413 * [I] IsEx : TRUE when LVM_SORTITEMSEX used
8419 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare,
8420 LPARAM lParamSort, BOOL IsEx)
8424 LPVOID selectionMarkItem = NULL;
8425 LPVOID focusedItem = NULL;
8428 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
8430 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
8432 if (!pfnCompare) return FALSE;
8433 if (!infoPtr->hdpaItems) return FALSE;
8435 /* if there are 0 or 1 items, there is no need to sort */
8436 if (infoPtr->nItemCount < 2) return TRUE;
8438 /* clear selection */
8439 ranges_clear(infoPtr->selectionRanges);
8441 /* save selection mark and focused item */
8442 if (infoPtr->nSelectionMark >= 0)
8443 selectionMarkItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark);
8444 if (infoPtr->nFocusedItem >= 0)
8445 focusedItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
8447 infoPtr->pfnCompare = pfnCompare;
8448 infoPtr->lParamSort = lParamSort;
8450 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompareEx, (LPARAM)infoPtr);
8452 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
8454 /* restore selection ranges */
8455 for (i=0; i < infoPtr->nItemCount; i++)
8457 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
8458 lpItem = DPA_GetPtr(hdpaSubItems, 0);
8460 if (lpItem->state & LVIS_SELECTED)
8461 ranges_additem(infoPtr->selectionRanges, i);
8463 /* restore selection mark and focused item */
8464 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
8465 infoPtr->nFocusedItem = DPA_GetPtrIndex(infoPtr->hdpaItems, focusedItem);
8467 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
8469 /* refresh the display */
8470 if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON)
8471 LISTVIEW_InvalidateList(infoPtr);
8478 * Update theme handle after a theme change.
8481 * [I] infoPtr : valid pointer to the listview structure
8485 * FAILURE : something else
8487 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
8489 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8490 CloseThemeData(theme);
8491 OpenThemeData(infoPtr->hwndSelf, themeClass);
8497 * Updates an items or rearranges the listview control.
8500 * [I] infoPtr : valid pointer to the listview structure
8501 * [I] nItem : item index
8507 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
8509 TRACE("(nItem=%d)\n", nItem);
8511 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
8513 /* rearrange with default alignment style */
8514 if (is_autoarrange(infoPtr))
8515 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8517 LISTVIEW_InvalidateItem(infoPtr, nItem);
8524 * Draw the track line at the place defined in the infoPtr structure.
8525 * The line is drawn with a XOR pen so drawing the line for the second time
8526 * in the same place erases the line.
8529 * [I] infoPtr : valid pointer to the listview structure
8535 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
8541 if (infoPtr->xTrackLine == -1)
8544 if (!(hdc = GetDC(infoPtr->hwndSelf)))
8546 hOldPen = SelectObject(hdc, GetStockObject(BLACK_PEN));
8547 oldROP = SetROP2(hdc, R2_XORPEN);
8548 MoveToEx(hdc, infoPtr->xTrackLine, infoPtr->rcList.top, NULL);
8549 LineTo(hdc, infoPtr->xTrackLine, infoPtr->rcList.bottom);
8550 SetROP2(hdc, oldROP);
8551 SelectObject(hdc, hOldPen);
8552 ReleaseDC(infoPtr->hwndSelf, hdc);
8558 * Called when an edit control should be displayed. This function is called after
8559 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
8562 * [I] hwnd : Handle to the listview
8563 * [I] uMsg : WM_TIMER (ignored)
8564 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
8565 * [I] dwTimer : The elapsed time (ignored)
8570 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
8572 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
8573 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8575 KillTimer(hwnd, idEvent);
8576 editItem->fEnabled = FALSE;
8577 /* check if the item is still selected */
8578 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
8579 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
8584 * Creates the listview control - the WM_NCCREATE phase.
8587 * [I] hwnd : window handle
8588 * [I] lpcs : the create parameters
8594 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
8596 LISTVIEW_INFO *infoPtr;
8599 TRACE("(lpcs=%p)\n", lpcs);
8601 /* initialize info pointer */
8602 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
8603 if (!infoPtr) return FALSE;
8605 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
8607 infoPtr->hwndSelf = hwnd;
8608 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
8609 map_style_view(infoPtr);
8610 /* determine the type of structures to use */
8611 infoPtr->hwndNotify = lpcs->hwndParent;
8612 /* infoPtr->notifyFormat will be filled in WM_CREATE */
8614 /* initialize color information */
8615 infoPtr->clrBk = CLR_NONE;
8616 infoPtr->clrText = CLR_DEFAULT;
8617 infoPtr->clrTextBk = CLR_DEFAULT;
8618 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
8620 /* set default values */
8621 infoPtr->nFocusedItem = -1;
8622 infoPtr->nSelectionMark = -1;
8623 infoPtr->nHotItem = -1;
8624 infoPtr->bRedraw = TRUE;
8625 infoPtr->bNoItemMetrics = TRUE;
8626 infoPtr->bDoChangeNotify = TRUE;
8627 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
8628 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
8629 infoPtr->nEditLabelItem = -1;
8630 infoPtr->nLButtonDownItem = -1;
8631 infoPtr->dwHoverTime = -1; /* default system hover time */
8632 infoPtr->nMeasureItemHeight = 0;
8633 infoPtr->xTrackLine = -1; /* no track line */
8634 infoPtr->itemEdit.fEnabled = FALSE;
8635 infoPtr->iVersion = COMCTL32_VERSION;
8637 /* get default font (icon title) */
8638 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
8639 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
8640 infoPtr->hFont = infoPtr->hDefaultFont;
8641 LISTVIEW_SaveTextMetrics(infoPtr);
8643 /* allocate memory for the data structure */
8644 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
8645 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
8646 if (!(infoPtr->hdpaItemIds = DPA_Create(10))) goto fail;
8647 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
8648 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
8649 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
8653 DestroyWindow(infoPtr->hwndHeader);
8654 ranges_destroy(infoPtr->selectionRanges);
8655 DPA_Destroy(infoPtr->hdpaItems);
8656 DPA_Destroy(infoPtr->hdpaItemIds);
8657 DPA_Destroy(infoPtr->hdpaPosX);
8658 DPA_Destroy(infoPtr->hdpaPosY);
8659 DPA_Destroy(infoPtr->hdpaColumns);
8666 * Creates the listview control - the WM_CREATE phase. Most of the data is
8667 * already set up in LISTVIEW_NCCreate
8670 * [I] hwnd : window handle
8671 * [I] lpcs : the create parameters
8677 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
8679 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8681 TRACE("(lpcs=%p)\n", lpcs);
8683 infoPtr->dwStyle = lpcs->style;
8684 map_style_view(infoPtr);
8686 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
8687 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
8688 /* on error defaulting to ANSI notifications */
8689 if (infoPtr->notifyFormat == 0) infoPtr->notifyFormat = NFR_ANSI;
8691 if ((infoPtr->uView == LV_VIEW_DETAILS) && (lpcs->style & WS_VISIBLE))
8693 if (LISTVIEW_CreateHeader(infoPtr) < 0) return -1;
8696 infoPtr->hwndHeader = 0;
8698 /* init item size to avoid division by 0 */
8699 LISTVIEW_UpdateItemSize (infoPtr);
8701 if (infoPtr->uView == LV_VIEW_DETAILS)
8703 if (!(LVS_NOCOLUMNHEADER & lpcs->style) && (WS_VISIBLE & lpcs->style))
8705 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8707 LISTVIEW_UpdateScroll(infoPtr);
8708 /* send WM_MEASUREITEM notification */
8709 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED) notify_measureitem(infoPtr);
8712 OpenThemeData(hwnd, themeClass);
8714 /* initialize the icon sizes */
8715 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, infoPtr->uView != LV_VIEW_ICON);
8716 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
8722 * Destroys the listview control.
8725 * [I] infoPtr : valid pointer to the listview structure
8731 static LRESULT LISTVIEW_Destroy(const LISTVIEW_INFO *infoPtr)
8733 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8734 CloseThemeData(theme);
8740 * Enables the listview control.
8743 * [I] infoPtr : valid pointer to the listview structure
8744 * [I] bEnable : specifies whether to enable or disable the window
8750 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr, BOOL bEnable)
8752 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
8753 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
8759 * Erases the background of the listview control.
8762 * [I] infoPtr : valid pointer to the listview structure
8763 * [I] hdc : device context handle
8769 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
8773 TRACE("(hdc=%p)\n", hdc);
8775 if (!GetClipBox(hdc, &rc)) return FALSE;
8777 if (infoPtr->clrBk == CLR_NONE)
8778 return SendMessageW(infoPtr->hwndNotify, WM_ERASEBKGND, (WPARAM)hdc, 0);
8780 /* for double buffered controls we need to do this during refresh */
8781 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
8783 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
8789 * Helper function for LISTVIEW_[HV]Scroll *only*.
8790 * Performs vertical/horizontal scrolling by a give amount.
8793 * [I] infoPtr : valid pointer to the listview structure
8794 * [I] dx : amount of horizontal scroll
8795 * [I] dy : amount of vertical scroll
8797 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
8799 /* now we can scroll the list */
8800 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
8801 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
8802 /* if we have focus, adjust rect */
8803 OffsetRect(&infoPtr->rcFocus, dx, dy);
8804 UpdateWindow(infoPtr->hwndSelf);
8809 * Performs vertical scrolling.
8812 * [I] infoPtr : valid pointer to the listview structure
8813 * [I] nScrollCode : scroll code
8814 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8815 * [I] hScrollWnd : scrollbar control window handle
8821 * SB_LINEUP/SB_LINEDOWN:
8822 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
8823 * for LVS_REPORT is 1 line
8824 * for LVS_LIST cannot occur
8827 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8828 INT nScrollDiff, HWND hScrollWnd)
8830 INT nOldScrollPos, nNewScrollPos;
8831 SCROLLINFO scrollInfo;
8834 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8835 debugscrollcode(nScrollCode), nScrollDiff);
8837 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8839 scrollInfo.cbSize = sizeof(SCROLLINFO);
8840 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8842 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
8844 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
8846 nOldScrollPos = scrollInfo.nPos;
8847 switch (nScrollCode)
8853 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
8857 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
8861 nScrollDiff = -scrollInfo.nPage;
8865 nScrollDiff = scrollInfo.nPage;
8868 case SB_THUMBPOSITION:
8870 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8877 /* quit right away if pos isn't changing */
8878 if (nScrollDiff == 0) return 0;
8880 /* calculate new position, and handle overflows */
8881 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8882 if (nScrollDiff > 0) {
8883 if (nNewScrollPos < nOldScrollPos ||
8884 nNewScrollPos > scrollInfo.nMax)
8885 nNewScrollPos = scrollInfo.nMax;
8887 if (nNewScrollPos > nOldScrollPos ||
8888 nNewScrollPos < scrollInfo.nMin)
8889 nNewScrollPos = scrollInfo.nMin;
8892 /* set the new position, and reread in case it changed */
8893 scrollInfo.fMask = SIF_POS;
8894 scrollInfo.nPos = nNewScrollPos;
8895 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
8897 /* carry on only if it really changed */
8898 if (nNewScrollPos == nOldScrollPos) return 0;
8900 /* now adjust to client coordinates */
8901 nScrollDiff = nOldScrollPos - nNewScrollPos;
8902 if (infoPtr->uView == LV_VIEW_DETAILS) nScrollDiff *= infoPtr->nItemHeight;
8904 /* and scroll the window */
8905 scroll_list(infoPtr, 0, nScrollDiff);
8912 * Performs horizontal scrolling.
8915 * [I] infoPtr : valid pointer to the listview structure
8916 * [I] nScrollCode : scroll code
8917 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8918 * [I] hScrollWnd : scrollbar control window handle
8924 * SB_LINELEFT/SB_LINERIGHT:
8925 * for LVS_ICON, LVS_SMALLICON 1 pixel
8926 * for LVS_REPORT is 1 pixel
8927 * for LVS_LIST is 1 column --> which is a 1 because the
8928 * scroll is based on columns not pixels
8931 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8932 INT nScrollDiff, HWND hScrollWnd)
8934 INT nOldScrollPos, nNewScrollPos;
8935 SCROLLINFO scrollInfo;
8937 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8938 debugscrollcode(nScrollCode), nScrollDiff);
8940 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8942 scrollInfo.cbSize = sizeof(SCROLLINFO);
8943 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8945 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
8947 nOldScrollPos = scrollInfo.nPos;
8949 switch (nScrollCode)
8963 nScrollDiff = -scrollInfo.nPage;
8967 nScrollDiff = scrollInfo.nPage;
8970 case SB_THUMBPOSITION:
8972 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8979 /* quit right away if pos isn't changing */
8980 if (nScrollDiff == 0) return 0;
8982 /* calculate new position, and handle overflows */
8983 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8984 if (nScrollDiff > 0) {
8985 if (nNewScrollPos < nOldScrollPos ||
8986 nNewScrollPos > scrollInfo.nMax)
8987 nNewScrollPos = scrollInfo.nMax;
8989 if (nNewScrollPos > nOldScrollPos ||
8990 nNewScrollPos < scrollInfo.nMin)
8991 nNewScrollPos = scrollInfo.nMin;
8994 /* set the new position, and reread in case it changed */
8995 scrollInfo.fMask = SIF_POS;
8996 scrollInfo.nPos = nNewScrollPos;
8997 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
8999 /* carry on only if it really changed */
9000 if (nNewScrollPos == nOldScrollPos) return 0;
9002 if (infoPtr->uView == LV_VIEW_DETAILS)
9003 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
9005 /* now adjust to client coordinates */
9006 nScrollDiff = nOldScrollPos - nNewScrollPos;
9007 if (infoPtr->uView == LV_VIEW_LIST) nScrollDiff *= infoPtr->nItemWidth;
9009 /* and scroll the window */
9010 scroll_list(infoPtr, nScrollDiff, 0);
9015 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
9017 INT gcWheelDelta = 0;
9018 INT pulScrollLines = 3;
9019 SCROLLINFO scrollInfo;
9021 TRACE("(wheelDelta=%d)\n", wheelDelta);
9023 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
9024 gcWheelDelta -= wheelDelta;
9026 scrollInfo.cbSize = sizeof(SCROLLINFO);
9027 scrollInfo.fMask = SIF_POS;
9029 switch(infoPtr->uView)
9032 case LV_VIEW_SMALLICON:
9034 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
9035 * should be fixed in the future.
9037 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
9038 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
9041 case LV_VIEW_DETAILS:
9042 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
9044 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
9045 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
9046 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
9051 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
9062 * [I] infoPtr : valid pointer to the listview structure
9063 * [I] nVirtualKey : virtual key
9064 * [I] lKeyData : key data
9069 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
9071 HWND hwndSelf = infoPtr->hwndSelf;
9073 NMLVKEYDOWN nmKeyDown;
9075 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
9077 /* send LVN_KEYDOWN notification */
9078 nmKeyDown.wVKey = nVirtualKey;
9079 nmKeyDown.flags = 0;
9080 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
9081 if (!IsWindow(hwndSelf))
9084 switch (nVirtualKey)
9087 nItem = infoPtr->nFocusedItem;
9088 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
9089 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
9093 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
9095 if (!notify(infoPtr, NM_RETURN)) return 0;
9096 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
9101 if (infoPtr->nItemCount > 0)
9106 if (infoPtr->nItemCount > 0)
9107 nItem = infoPtr->nItemCount - 1;
9111 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TOLEFT);
9115 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_ABOVE);
9119 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TORIGHT);
9123 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_BELOW);
9127 if (infoPtr->uView == LV_VIEW_DETAILS)
9129 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
9130 if (infoPtr->nFocusedItem == topidx)
9131 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
9136 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
9137 * LISTVIEW_GetCountPerRow(infoPtr);
9138 if(nItem < 0) nItem = 0;
9142 if (infoPtr->uView == LV_VIEW_DETAILS)
9144 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
9145 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
9146 if (infoPtr->nFocusedItem == topidx + cnt - 1)
9147 nItem = infoPtr->nFocusedItem + cnt - 1;
9149 nItem = topidx + cnt - 1;
9152 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
9153 * LISTVIEW_GetCountPerRow(infoPtr);
9154 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
9158 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
9159 LISTVIEW_KeySelection(infoPtr, nItem, nVirtualKey == VK_SPACE);
9169 * [I] infoPtr : valid pointer to the listview structure
9174 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
9178 /* if we did not have the focus, there's nothing to do */
9179 if (!infoPtr->bFocus) return 0;
9181 /* send NM_KILLFOCUS notification */
9182 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
9184 /* if we have a focus rectangle, get rid of it */
9185 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
9187 /* set window focus flag */
9188 infoPtr->bFocus = FALSE;
9190 /* invalidate the selected items before resetting focus flag */
9191 LISTVIEW_InvalidateSelectedItems(infoPtr);
9198 * Processes double click messages (left mouse button).
9201 * [I] infoPtr : valid pointer to the listview structure
9202 * [I] wKey : key flag
9203 * [I] x,y : mouse coordinate
9208 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9210 LVHITTESTINFO htInfo;
9212 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
9214 /* Cancel the item edition if any */
9215 if (infoPtr->itemEdit.fEnabled)
9217 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
9218 infoPtr->itemEdit.fEnabled = FALSE;
9221 /* send NM_RELEASEDCAPTURE notification */
9222 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9227 /* send NM_DBLCLK notification */
9228 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
9229 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
9231 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
9232 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
9239 * Processes mouse down messages (left mouse button).
9242 * infoPtr [I ] valid pointer to the listview structure
9243 * wKey [I ] key flag
9244 * x,y [I ] mouse coordinate
9249 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9251 LVHITTESTINFO lvHitTestInfo;
9252 static BOOL bGroupSelect = TRUE;
9253 POINT pt = { x, y };
9256 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
9258 /* send NM_RELEASEDCAPTURE notification */
9259 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9261 /* set left button down flag and record the click position */
9262 infoPtr->bLButtonDown = TRUE;
9263 infoPtr->ptClickPos = pt;
9264 infoPtr->bDragging = FALSE;
9266 lvHitTestInfo.pt.x = x;
9267 lvHitTestInfo.pt.y = y;
9269 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
9270 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
9271 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
9273 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
9275 toggle_checkbox_state(infoPtr, nItem);
9279 if (infoPtr->dwStyle & LVS_SINGLESEL)
9281 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9282 infoPtr->nEditLabelItem = nItem;
9284 LISTVIEW_SetSelection(infoPtr, nItem);
9288 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
9292 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
9293 LISTVIEW_SetItemFocus(infoPtr, nItem);
9294 infoPtr->nSelectionMark = nItem;
9300 item.state = LVIS_SELECTED | LVIS_FOCUSED;
9301 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
9303 LISTVIEW_SetItemState(infoPtr,nItem,&item);
9304 infoPtr->nSelectionMark = nItem;
9307 else if (wKey & MK_CONTROL)
9311 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
9313 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
9314 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
9315 LISTVIEW_SetItemState(infoPtr, nItem, &item);
9316 infoPtr->nSelectionMark = nItem;
9318 else if (wKey & MK_SHIFT)
9320 LISTVIEW_SetGroupSelection(infoPtr, nItem);
9324 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9326 infoPtr->nEditLabelItem = nItem;
9327 infoPtr->nLButtonDownItem = nItem;
9329 LISTVIEW_SetItemFocus(infoPtr, nItem);
9332 /* set selection (clears other pre-existing selections) */
9333 LISTVIEW_SetSelection(infoPtr, nItem);
9337 if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
9338 if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
9342 /* remove all selections */
9343 if (!(wKey & MK_CONTROL) && !(wKey & MK_SHIFT))
9344 LISTVIEW_DeselectAll(infoPtr);
9353 * Processes mouse up messages (left mouse button).
9356 * infoPtr [I ] valid pointer to the listview structure
9357 * wKey [I ] key flag
9358 * x,y [I ] mouse coordinate
9363 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9365 LVHITTESTINFO lvHitTestInfo;
9367 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
9369 if (!infoPtr->bLButtonDown) return 0;
9371 lvHitTestInfo.pt.x = x;
9372 lvHitTestInfo.pt.y = y;
9374 /* send NM_CLICK notification */
9375 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9376 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
9378 /* set left button flag */
9379 infoPtr->bLButtonDown = FALSE;
9381 /* set a single selection, reset others */
9382 if(lvHitTestInfo.iItem == infoPtr->nLButtonDownItem && lvHitTestInfo.iItem != -1)
9383 LISTVIEW_SetSelection(infoPtr, infoPtr->nLButtonDownItem);
9384 infoPtr->nLButtonDownItem = -1;
9386 if (infoPtr->bDragging)
9388 infoPtr->bDragging = FALSE;
9392 /* if we clicked on a selected item, edit the label */
9393 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
9395 /* we want to make sure the user doesn't want to do a double click. So we will
9396 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
9398 infoPtr->itemEdit.fEnabled = TRUE;
9399 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
9400 SetTimer(infoPtr->hwndSelf,
9401 (UINT_PTR)&infoPtr->itemEdit,
9402 GetDoubleClickTime(),
9403 LISTVIEW_DelayedEditItem);
9406 if (!infoPtr->bFocus)
9407 SetFocus(infoPtr->hwndSelf);
9414 * Destroys the listview control (called after WM_DESTROY).
9417 * [I] infoPtr : valid pointer to the listview structure
9422 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
9426 /* delete all items */
9427 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
9429 /* destroy data structure */
9430 DPA_Destroy(infoPtr->hdpaItems);
9431 DPA_Destroy(infoPtr->hdpaItemIds);
9432 DPA_Destroy(infoPtr->hdpaPosX);
9433 DPA_Destroy(infoPtr->hdpaPosY);
9434 DPA_Destroy(infoPtr->hdpaColumns);
9435 ranges_destroy(infoPtr->selectionRanges);
9437 /* destroy image lists */
9438 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
9440 if (infoPtr->himlNormal)
9441 ImageList_Destroy(infoPtr->himlNormal);
9442 if (infoPtr->himlSmall)
9443 ImageList_Destroy(infoPtr->himlSmall);
9444 if (infoPtr->himlState)
9445 ImageList_Destroy(infoPtr->himlState);
9448 /* destroy font, bkgnd brush */
9450 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
9451 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
9453 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
9455 /* free listview info pointer*/
9463 * Handles notifications from header.
9466 * [I] infoPtr : valid pointer to the listview structure
9467 * [I] nCtrlId : control identifier
9468 * [I] lpnmh : notification information
9473 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
9475 HWND hwndSelf = infoPtr->hwndSelf;
9477 TRACE("(lpnmh=%p)\n", lpnmh);
9479 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
9481 switch (lpnmh->hdr.code)
9486 COLUMN_INFO *lpColumnInfo;
9490 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
9493 /* remove the old line (if any) */
9494 LISTVIEW_DrawTrackLine(infoPtr);
9496 /* compute & draw the new line */
9497 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
9498 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
9499 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
9500 infoPtr->xTrackLine = x + ptOrigin.x;
9501 LISTVIEW_DrawTrackLine(infoPtr);
9507 /* remove the track line (if any) */
9508 LISTVIEW_DrawTrackLine(infoPtr);
9509 infoPtr->xTrackLine = -1;
9513 notify_forward_header(infoPtr, lpnmh);
9514 return (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP) == 0;
9517 FIXME("Changing column order not implemented\n");
9518 notify_forward_header(infoPtr, lpnmh);
9521 case HDN_ITEMCHANGINGW:
9522 case HDN_ITEMCHANGINGA:
9523 return notify_forward_header(infoPtr, lpnmh);
9525 case HDN_ITEMCHANGEDW:
9526 case HDN_ITEMCHANGEDA:
9528 COLUMN_INFO *lpColumnInfo;
9531 notify_forward_header(infoPtr, lpnmh);
9532 if (!IsWindow(hwndSelf))
9535 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
9539 hdi.mask = HDI_WIDTH;
9540 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi)) return 0;
9544 cxy = lpnmh->pitem->cxy;
9546 /* determine how much we change since the last know position */
9547 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
9548 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
9551 lpColumnInfo->rcHeader.right += dx;
9552 if (lpnmh->iItem + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
9553 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
9556 /* only needs to update the scrolls */
9557 infoPtr->nItemWidth += dx;
9558 LISTVIEW_UpdateScroll(infoPtr);
9560 LISTVIEW_UpdateItemSize(infoPtr);
9561 if (infoPtr->uView == LV_VIEW_DETAILS && is_redrawing(infoPtr))
9564 RECT rcCol = lpColumnInfo->rcHeader;
9566 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
9567 OffsetRect(&rcCol, ptOrigin.x, 0);
9569 rcCol.top = infoPtr->rcList.top;
9570 rcCol.bottom = infoPtr->rcList.bottom;
9572 /* resizing left-aligned columns leaves most of the left side untouched */
9573 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
9575 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
9578 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
9581 /* when shrinking the last column clear the now unused field */
9582 if (lpnmh->iItem == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
9588 /* deal with right from rightmost column area */
9589 right.left = rcCol.right;
9590 right.top = rcCol.top;
9591 right.bottom = rcCol.bottom;
9592 right.right = infoPtr->rcList.right;
9594 LISTVIEW_InvalidateRect(infoPtr, &right);
9597 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
9603 case HDN_ITEMCLICKW:
9604 case HDN_ITEMCLICKA:
9606 /* Handle sorting by Header Column */
9609 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
9611 nmlv.iSubItem = lpnmh->iItem;
9612 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
9613 notify_forward_header(infoPtr, lpnmh);
9617 case HDN_DIVIDERDBLCLICKW:
9618 case HDN_DIVIDERDBLCLICKA:
9619 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
9620 notify_forward_header(infoPtr, lpnmh);
9629 * Paint non-client area of control.
9632 * [I] infoPtr : valid pointer to the listview structureof the sender
9633 * [I] region : update region
9636 * TRUE - frame was painted
9637 * FALSE - call default window proc
9639 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
9641 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
9645 int cxEdge = GetSystemMetrics (SM_CXEDGE),
9646 cyEdge = GetSystemMetrics (SM_CYEDGE);
9648 if (!theme) return FALSE;
9650 GetWindowRect(infoPtr->hwndSelf, &r);
9652 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
9653 r.right - cxEdge, r.bottom - cyEdge);
9654 if (region != (HRGN)1)
9655 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
9656 OffsetRect(&r, -r.left, -r.top);
9658 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
9659 OffsetRect(&r, -r.left, -r.top);
9661 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
9662 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
9663 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
9664 ReleaseDC(infoPtr->hwndSelf, dc);
9666 /* Call default proc to get the scrollbars etc. painted */
9667 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
9674 * Determines the type of structure to use.
9677 * [I] infoPtr : valid pointer to the listview structureof the sender
9678 * [I] hwndFrom : listview window handle
9679 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
9684 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
9686 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
9688 if (nCommand == NF_REQUERY)
9689 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9691 return infoPtr->notifyFormat;
9696 * Paints/Repaints the listview control.
9699 * [I] infoPtr : valid pointer to the listview structure
9700 * [I] hdc : device context handle
9705 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
9707 TRACE("(hdc=%p)\n", hdc);
9709 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
9711 infoPtr->bNoItemMetrics = FALSE;
9712 LISTVIEW_UpdateItemSize(infoPtr);
9713 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
9714 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9715 LISTVIEW_UpdateScroll(infoPtr);
9718 if (infoPtr->hwndHeader) UpdateWindow(infoPtr->hwndHeader);
9721 LISTVIEW_Refresh(infoPtr, hdc, NULL);
9726 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
9728 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
9729 EndPaint(infoPtr->hwndSelf, &ps);
9738 * Paints/Repaints the listview control.
9741 * [I] infoPtr : valid pointer to the listview structure
9742 * [I] hdc : device context handle
9743 * [I] options : drawing options
9748 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
9750 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
9752 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
9755 if (options & PRF_ERASEBKGND)
9756 LISTVIEW_EraseBkgnd(infoPtr, hdc);
9758 if (options & PRF_CLIENT)
9759 LISTVIEW_Paint(infoPtr, hdc);
9767 * Processes double click messages (right mouse button).
9770 * [I] infoPtr : valid pointer to the listview structure
9771 * [I] wKey : key flag
9772 * [I] x,y : mouse coordinate
9777 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9779 LVHITTESTINFO lvHitTestInfo;
9781 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9783 /* send NM_RELEASEDCAPTURE notification */
9784 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9786 /* send NM_RDBLCLK notification */
9787 lvHitTestInfo.pt.x = x;
9788 lvHitTestInfo.pt.y = y;
9789 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9790 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
9797 * Processes mouse down messages (right mouse button).
9800 * [I] infoPtr : valid pointer to the listview structure
9801 * [I] wKey : key flag
9802 * [I] x,y : mouse coordinate
9807 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9809 LVHITTESTINFO lvHitTestInfo;
9812 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9814 /* send NM_RELEASEDCAPTURE notification */
9815 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9817 /* make sure the listview control window has the focus */
9818 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
9820 /* set right button down flag */
9821 infoPtr->bRButtonDown = TRUE;
9823 /* determine the index of the selected item */
9824 lvHitTestInfo.pt.x = x;
9825 lvHitTestInfo.pt.y = y;
9826 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
9828 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
9830 LISTVIEW_SetItemFocus(infoPtr, nItem);
9831 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
9832 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9833 LISTVIEW_SetSelection(infoPtr, nItem);
9837 LISTVIEW_DeselectAll(infoPtr);
9845 * Processes mouse up messages (right mouse button).
9848 * [I] infoPtr : valid pointer to the listview structure
9849 * [I] wKey : key flag
9850 * [I] x,y : mouse coordinate
9855 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9857 LVHITTESTINFO lvHitTestInfo;
9860 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9862 if (!infoPtr->bRButtonDown) return 0;
9864 /* set button flag */
9865 infoPtr->bRButtonDown = FALSE;
9867 /* Send NM_RCLICK notification */
9868 lvHitTestInfo.pt.x = x;
9869 lvHitTestInfo.pt.y = y;
9870 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9871 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
9873 /* Change to screen coordinate for WM_CONTEXTMENU */
9874 pt = lvHitTestInfo.pt;
9875 ClientToScreen(infoPtr->hwndSelf, &pt);
9877 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
9878 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
9879 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
9890 * [I] infoPtr : valid pointer to the listview structure
9891 * [I] hwnd : window handle of window containing the cursor
9892 * [I] nHittest : hit-test code
9893 * [I] wMouseMsg : ideintifier of the mouse message
9896 * TRUE if cursor is set
9899 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
9901 LVHITTESTINFO lvHitTestInfo;
9903 if(!(LISTVIEW_isHotTracking(infoPtr))) return FALSE;
9905 if(!infoPtr->hHotCursor) return FALSE;
9907 GetCursorPos(&lvHitTestInfo.pt);
9908 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
9910 SetCursor(infoPtr->hHotCursor);
9920 * [I] infoPtr : valid pointer to the listview structure
9921 * [I] hwndLoseFocus : handle of previously focused window
9926 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
9928 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
9930 /* if we have the focus already, there's nothing to do */
9931 if (infoPtr->bFocus) return 0;
9933 /* send NM_SETFOCUS notification */
9934 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
9936 /* set window focus flag */
9937 infoPtr->bFocus = TRUE;
9939 /* put the focus rect back on */
9940 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
9942 /* redraw all visible selected items */
9943 LISTVIEW_InvalidateSelectedItems(infoPtr);
9953 * [I] infoPtr : valid pointer to the listview structure
9954 * [I] fRedraw : font handle
9955 * [I] fRedraw : redraw flag
9960 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
9962 HFONT oldFont = infoPtr->hFont;
9964 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
9966 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
9967 if (infoPtr->hFont == oldFont) return 0;
9969 LISTVIEW_SaveTextMetrics(infoPtr);
9971 if (infoPtr->uView == LV_VIEW_DETAILS)
9973 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
9974 LISTVIEW_UpdateSize(infoPtr);
9975 LISTVIEW_UpdateScroll(infoPtr);
9978 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
9985 * Message handling for WM_SETREDRAW.
9986 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
9989 * [I] infoPtr : valid pointer to the listview structure
9990 * [I] bRedraw: state of redraw flag
9993 * DefWinProc return value
9995 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
9997 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
9999 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
10000 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
10002 infoPtr->bRedraw = bRedraw;
10004 if(!bRedraw) return 0;
10006 if (is_autoarrange(infoPtr))
10007 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10008 LISTVIEW_UpdateScroll(infoPtr);
10010 /* despite what the WM_SETREDRAW docs says, apps expect us
10011 * to invalidate the listview here... stupid! */
10012 LISTVIEW_InvalidateList(infoPtr);
10019 * Resizes the listview control. This function processes WM_SIZE
10020 * messages. At this time, the width and height are not used.
10023 * [I] infoPtr : valid pointer to the listview structure
10024 * [I] Width : new width
10025 * [I] Height : new height
10030 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
10032 RECT rcOld = infoPtr->rcList;
10034 TRACE("(width=%d, height=%d)\n", Width, Height);
10036 LISTVIEW_UpdateSize(infoPtr);
10037 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
10039 /* do not bother with display related stuff if we're not redrawing */
10040 if (!is_redrawing(infoPtr)) return 0;
10042 if (is_autoarrange(infoPtr))
10043 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10045 LISTVIEW_UpdateScroll(infoPtr);
10047 /* refresh all only for lists whose height changed significantly */
10048 if ((infoPtr->uView == LV_VIEW_LIST) &&
10049 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
10050 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
10051 LISTVIEW_InvalidateList(infoPtr);
10058 * Sets the size information.
10061 * [I] infoPtr : valid pointer to the listview structure
10066 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
10068 TRACE("uView=%d, rcList(old)=%s\n", infoPtr->uView, wine_dbgstr_rect(&infoPtr->rcList));
10070 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
10072 if (infoPtr->uView == LV_VIEW_LIST)
10074 /* Apparently the "LIST" style is supposed to have the same
10075 * number of items in a column even if there is no scroll bar.
10076 * Since if a scroll bar already exists then the bottom is already
10077 * reduced, only reduce if the scroll bar does not currently exist.
10078 * The "2" is there to mimic the native control. I think it may be
10079 * related to either padding or edges. (GLA 7/2002)
10081 if (!(infoPtr->dwStyle & WS_HSCROLL))
10082 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
10083 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
10085 else if (infoPtr->uView == LV_VIEW_DETAILS)
10090 hl.prc = &infoPtr->rcList;
10092 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
10093 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
10094 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy,
10095 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
10096 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
10097 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
10099 infoPtr->rcList.top = max(wp.cy, 0);
10100 infoPtr->rcList.top += (infoPtr->dwLvExStyle & LVS_EX_GRIDLINES) ? 2 : 0;
10103 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
10108 * Processes WM_STYLECHANGED messages.
10111 * [I] infoPtr : valid pointer to the listview structure
10112 * [I] wStyleType : window style type (normal or extended)
10113 * [I] lpss : window style information
10118 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
10119 const STYLESTRUCT *lpss)
10121 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
10122 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
10125 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
10126 wStyleType, lpss->styleOld, lpss->styleNew);
10128 if (wStyleType != GWL_STYLE) return 0;
10130 infoPtr->dwStyle = lpss->styleNew;
10131 map_style_view(infoPtr);
10133 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
10134 ((lpss->styleNew & WS_HSCROLL) == 0))
10135 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
10137 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
10138 ((lpss->styleNew & WS_VSCROLL) == 0))
10139 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
10141 if (uNewView != uOldView)
10143 SIZE oldIconSize = infoPtr->iconSize;
10146 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
10147 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
10149 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
10150 SetRectEmpty(&infoPtr->rcFocus);
10152 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
10153 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
10155 if (uNewView == LVS_ICON)
10157 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
10159 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
10160 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
10161 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
10164 else if (uNewView == LVS_REPORT)
10169 LISTVIEW_CreateHeader( infoPtr );
10171 hl.prc = &infoPtr->rcList;
10173 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
10174 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
10175 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
10176 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
10179 LISTVIEW_UpdateItemSize(infoPtr);
10182 if (uNewView == LVS_REPORT)
10184 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
10186 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
10188 /* Turn off the header control */
10189 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
10190 TRACE("Hide header control, was 0x%08x\n", style);
10191 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
10193 /* Turn on the header control */
10194 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
10196 TRACE("Show header control, was 0x%08x\n", style);
10197 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
10203 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
10204 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
10205 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10207 /* update the size of the client area */
10208 LISTVIEW_UpdateSize(infoPtr);
10210 /* add scrollbars if needed */
10211 LISTVIEW_UpdateScroll(infoPtr);
10213 /* invalidate client area + erase background */
10214 LISTVIEW_InvalidateList(infoPtr);
10221 * Processes WM_STYLECHANGING messages.
10224 * [I] infoPtr : valid pointer to the listview structure
10225 * [I] wStyleType : window style type (normal or extended)
10226 * [I0] lpss : window style information
10231 static INT LISTVIEW_StyleChanging(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
10234 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
10235 wStyleType, lpss->styleOld, lpss->styleNew);
10237 /* don't forward LVS_OWNERDATA only if not already set to */
10238 if ((lpss->styleNew ^ lpss->styleOld) & LVS_OWNERDATA)
10240 if (lpss->styleOld & LVS_OWNERDATA)
10241 lpss->styleNew |= LVS_OWNERDATA;
10243 lpss->styleNew &= ~LVS_OWNERDATA;
10251 * Processes WM_SHOWWINDOW messages.
10254 * [I] infoPtr : valid pointer to the listview structure
10255 * [I] bShown : window is being shown (FALSE when hidden)
10256 * [I] iStatus : window show status
10261 static LRESULT LISTVIEW_ShowWindow(LISTVIEW_INFO *infoPtr, BOOL bShown, INT iStatus)
10263 /* header delayed creation */
10264 if ((infoPtr->uView == LV_VIEW_DETAILS) && bShown)
10266 LISTVIEW_CreateHeader(infoPtr);
10268 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle))
10269 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
10277 * Processes CCM_GETVERSION messages.
10280 * [I] infoPtr : valid pointer to the listview structure
10285 static inline LRESULT LISTVIEW_GetVersion(const LISTVIEW_INFO *infoPtr)
10287 return infoPtr->iVersion;
10292 * Processes CCM_SETVERSION messages.
10295 * [I] infoPtr : valid pointer to the listview structure
10296 * [I] iVersion : version to be set
10299 * -1 when requested version is greater than DLL version;
10300 * previous version otherwise
10302 static LRESULT LISTVIEW_SetVersion(LISTVIEW_INFO *infoPtr, DWORD iVersion)
10304 INT iOldVersion = infoPtr->iVersion;
10306 if (iVersion > COMCTL32_VERSION)
10309 infoPtr->iVersion = iVersion;
10311 TRACE("new version %d\n", iVersion);
10313 return iOldVersion;
10318 * Window procedure of the listview control.
10321 static LRESULT WINAPI
10322 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10324 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
10326 TRACE("(uMsg=%x wParam=%lx lParam=%lx)\n", uMsg, wParam, lParam);
10328 if (!infoPtr && (uMsg != WM_NCCREATE))
10329 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10333 case LVM_APPROXIMATEVIEWRECT:
10334 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
10335 LOWORD(lParam), HIWORD(lParam));
10337 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
10339 case LVM_CANCELEDITLABEL:
10340 return LISTVIEW_CancelEditLabel(infoPtr);
10342 case LVM_CREATEDRAGIMAGE:
10343 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
10345 case LVM_DELETEALLITEMS:
10346 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
10348 case LVM_DELETECOLUMN:
10349 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
10351 case LVM_DELETEITEM:
10352 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
10354 case LVM_EDITLABELW:
10355 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
10357 case LVM_EDITLABELA:
10358 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
10360 /* case LVM_ENABLEGROUPVIEW: */
10362 case LVM_ENSUREVISIBLE:
10363 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
10365 case LVM_FINDITEMW:
10366 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
10368 case LVM_FINDITEMA:
10369 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
10371 case LVM_GETBKCOLOR:
10372 return infoPtr->clrBk;
10374 /* case LVM_GETBKIMAGE: */
10376 case LVM_GETCALLBACKMASK:
10377 return infoPtr->uCallbackMask;
10379 case LVM_GETCOLUMNA:
10380 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
10382 case LVM_GETCOLUMNW:
10383 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
10385 case LVM_GETCOLUMNORDERARRAY:
10386 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
10388 case LVM_GETCOLUMNWIDTH:
10389 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
10391 case LVM_GETCOUNTPERPAGE:
10392 return LISTVIEW_GetCountPerPage(infoPtr);
10394 case LVM_GETEDITCONTROL:
10395 return (LRESULT)infoPtr->hwndEdit;
10397 case LVM_GETEXTENDEDLISTVIEWSTYLE:
10398 return infoPtr->dwLvExStyle;
10400 /* case LVM_GETGROUPINFO: */
10402 /* case LVM_GETGROUPMETRICS: */
10404 case LVM_GETHEADER:
10405 return (LRESULT)infoPtr->hwndHeader;
10407 case LVM_GETHOTCURSOR:
10408 return (LRESULT)infoPtr->hHotCursor;
10410 case LVM_GETHOTITEM:
10411 return infoPtr->nHotItem;
10413 case LVM_GETHOVERTIME:
10414 return infoPtr->dwHoverTime;
10416 case LVM_GETIMAGELIST:
10417 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
10419 /* case LVM_GETINSERTMARK: */
10421 /* case LVM_GETINSERTMARKCOLOR: */
10423 /* case LVM_GETINSERTMARKRECT: */
10425 case LVM_GETISEARCHSTRINGA:
10426 case LVM_GETISEARCHSTRINGW:
10427 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
10431 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
10434 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
10436 case LVM_GETITEMCOUNT:
10437 return infoPtr->nItemCount;
10439 case LVM_GETITEMPOSITION:
10440 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
10442 case LVM_GETITEMRECT:
10443 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
10445 case LVM_GETITEMSPACING:
10446 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
10448 case LVM_GETITEMSTATE:
10449 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
10451 case LVM_GETITEMTEXTA:
10452 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
10454 case LVM_GETITEMTEXTW:
10455 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
10457 case LVM_GETNEXTITEM:
10458 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
10460 case LVM_GETNUMBEROFWORKAREAS:
10461 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
10464 case LVM_GETORIGIN:
10465 if (!lParam) return FALSE;
10466 if (infoPtr->uView == LV_VIEW_DETAILS ||
10467 infoPtr->uView == LV_VIEW_LIST) return FALSE;
10468 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
10471 /* case LVM_GETOUTLINECOLOR: */
10473 /* case LVM_GETSELECTEDCOLUMN: */
10475 case LVM_GETSELECTEDCOUNT:
10476 return LISTVIEW_GetSelectedCount(infoPtr);
10478 case LVM_GETSELECTIONMARK:
10479 return infoPtr->nSelectionMark;
10481 case LVM_GETSTRINGWIDTHA:
10482 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
10484 case LVM_GETSTRINGWIDTHW:
10485 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
10487 case LVM_GETSUBITEMRECT:
10488 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
10490 case LVM_GETTEXTBKCOLOR:
10491 return infoPtr->clrTextBk;
10493 case LVM_GETTEXTCOLOR:
10494 return infoPtr->clrText;
10496 /* case LVM_GETTILEINFO: */
10498 /* case LVM_GETTILEVIEWINFO: */
10500 case LVM_GETTOOLTIPS:
10501 if( !infoPtr->hwndToolTip )
10502 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
10503 return (LRESULT)infoPtr->hwndToolTip;
10505 case LVM_GETTOPINDEX:
10506 return LISTVIEW_GetTopIndex(infoPtr);
10508 case LVM_GETUNICODEFORMAT:
10509 return (infoPtr->notifyFormat == NFR_UNICODE);
10512 return infoPtr->uView;
10514 case LVM_GETVIEWRECT:
10515 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
10517 case LVM_GETWORKAREAS:
10518 FIXME("LVM_GETWORKAREAS: unimplemented\n");
10521 /* case LVM_HASGROUP: */
10524 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, TRUE);
10526 case LVM_INSERTCOLUMNA:
10527 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
10529 case LVM_INSERTCOLUMNW:
10530 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
10532 /* case LVM_INSERTGROUP: */
10534 /* case LVM_INSERTGROUPSORTED: */
10536 case LVM_INSERTITEMA:
10537 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
10539 case LVM_INSERTITEMW:
10540 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
10542 /* case LVM_INSERTMARKHITTEST: */
10544 /* case LVM_ISGROUPVIEWENABLED: */
10546 case LVM_ISITEMVISIBLE:
10547 return LISTVIEW_IsItemVisible(infoPtr, (INT)wParam);
10549 case LVM_MAPIDTOINDEX:
10550 return LISTVIEW_MapIdToIndex(infoPtr, (UINT)wParam);
10552 case LVM_MAPINDEXTOID:
10553 return LISTVIEW_MapIndexToId(infoPtr, (INT)wParam);
10555 /* case LVM_MOVEGROUP: */
10557 /* case LVM_MOVEITEMTOGROUP: */
10559 case LVM_REDRAWITEMS:
10560 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
10562 /* case LVM_REMOVEALLGROUPS: */
10564 /* case LVM_REMOVEGROUP: */
10567 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
10569 case LVM_SETBKCOLOR:
10570 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
10572 /* case LVM_SETBKIMAGE: */
10574 case LVM_SETCALLBACKMASK:
10575 infoPtr->uCallbackMask = (UINT)wParam;
10578 case LVM_SETCOLUMNA:
10579 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
10581 case LVM_SETCOLUMNW:
10582 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
10584 case LVM_SETCOLUMNORDERARRAY:
10585 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
10587 case LVM_SETCOLUMNWIDTH:
10588 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
10590 case LVM_SETEXTENDEDLISTVIEWSTYLE:
10591 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
10593 /* case LVM_SETGROUPINFO: */
10595 /* case LVM_SETGROUPMETRICS: */
10597 case LVM_SETHOTCURSOR:
10598 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
10600 case LVM_SETHOTITEM:
10601 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
10603 case LVM_SETHOVERTIME:
10604 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
10606 case LVM_SETICONSPACING:
10607 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10609 case LVM_SETIMAGELIST:
10610 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
10612 /* case LVM_SETINFOTIP: */
10614 /* case LVM_SETINSERTMARK: */
10616 /* case LVM_SETINSERTMARKCOLOR: */
10621 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
10622 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, (uMsg == LVM_SETITEMW));
10625 case LVM_SETITEMCOUNT:
10626 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
10628 case LVM_SETITEMPOSITION:
10631 pt.x = (short)LOWORD(lParam);
10632 pt.y = (short)HIWORD(lParam);
10633 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
10636 case LVM_SETITEMPOSITION32:
10637 if (lParam == 0) return FALSE;
10638 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
10640 case LVM_SETITEMSTATE:
10641 if (lParam == 0) return FALSE;
10642 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
10644 case LVM_SETITEMTEXTA:
10645 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
10647 case LVM_SETITEMTEXTW:
10648 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
10650 /* case LVM_SETOUTLINECOLOR: */
10652 /* case LVM_SETSELECTEDCOLUMN: */
10654 case LVM_SETSELECTIONMARK:
10655 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
10657 case LVM_SETTEXTBKCOLOR:
10658 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
10660 case LVM_SETTEXTCOLOR:
10661 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
10663 /* case LVM_SETTILEINFO: */
10665 /* case LVM_SETTILEVIEWINFO: */
10667 /* case LVM_SETTILEWIDTH: */
10669 case LVM_SETTOOLTIPS:
10670 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
10672 case LVM_SETUNICODEFORMAT:
10673 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
10676 return LISTVIEW_SetView(infoPtr, wParam);
10678 /* case LVM_SETWORKAREAS: */
10680 /* case LVM_SORTGROUPS: */
10682 case LVM_SORTITEMS:
10683 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam, FALSE);
10685 case LVM_SORTITEMSEX:
10686 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam, TRUE);
10688 case LVM_SUBITEMHITTEST:
10689 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
10692 return LISTVIEW_Update(infoPtr, (INT)wParam);
10694 case CCM_GETVERSION:
10695 return LISTVIEW_GetVersion(infoPtr);
10697 case CCM_SETVERSION:
10698 return LISTVIEW_SetVersion(infoPtr, wParam);
10701 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
10704 return LISTVIEW_Command(infoPtr, wParam, lParam);
10707 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
10710 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
10713 return LISTVIEW_Destroy(infoPtr);
10716 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
10718 case WM_ERASEBKGND:
10719 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
10721 case WM_GETDLGCODE:
10722 return DLGC_WANTCHARS | DLGC_WANTARROWS;
10725 return (LRESULT)infoPtr->hFont;
10728 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10731 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
10734 return LISTVIEW_KillFocus(infoPtr);
10736 case WM_LBUTTONDBLCLK:
10737 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10739 case WM_LBUTTONDOWN:
10740 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10743 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10746 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10748 case WM_MOUSEHOVER:
10749 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10752 return LISTVIEW_NCDestroy(infoPtr);
10755 if (LISTVIEW_NCPaint(infoPtr, (HRGN)wParam))
10760 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
10761 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
10764 case WM_NOTIFYFORMAT:
10765 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
10767 case WM_PRINTCLIENT:
10768 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
10771 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
10773 case WM_RBUTTONDBLCLK:
10774 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10776 case WM_RBUTTONDOWN:
10777 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10780 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10783 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
10788 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
10791 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
10794 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
10796 case WM_SHOWWINDOW:
10797 LISTVIEW_ShowWindow(infoPtr, (BOOL)wParam, (INT)lParam);
10798 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10801 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10803 case WM_STYLECHANGED:
10804 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10806 case WM_STYLECHANGING:
10807 return LISTVIEW_StyleChanging(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10809 case WM_SYSCOLORCHANGE:
10810 COMCTL32_RefreshSysColors();
10813 /* case WM_TIMER: */
10814 case WM_THEMECHANGED:
10815 return LISTVIEW_ThemeChanged(infoPtr);
10818 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10820 case WM_MOUSEWHEEL:
10821 if (wParam & (MK_SHIFT | MK_CONTROL))
10822 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10823 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
10825 case WM_WINDOWPOSCHANGED:
10826 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
10828 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
10829 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
10831 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
10833 if (notify_measureitem(infoPtr)) LISTVIEW_InvalidateList(infoPtr);
10836 LISTVIEW_UpdateSize(infoPtr);
10837 LISTVIEW_UpdateScroll(infoPtr);
10839 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10841 /* case WM_WININICHANGE: */
10844 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
10845 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
10848 /* call default window procedure */
10849 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10856 * Registers the window class.
10864 void LISTVIEW_Register(void)
10866 WNDCLASSW wndClass;
10868 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
10869 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
10870 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
10871 wndClass.cbClsExtra = 0;
10872 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
10873 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
10874 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
10875 wndClass.lpszClassName = WC_LISTVIEWW;
10876 RegisterClassW(&wndClass);
10881 * Unregisters the window class.
10889 void LISTVIEW_Unregister(void)
10891 UnregisterClassW(WC_LISTVIEWW, NULL);
10896 * Handle any WM_COMMAND messages
10899 * [I] infoPtr : valid pointer to the listview structure
10900 * [I] wParam : the first message parameter
10901 * [I] lParam : the second message parameter
10906 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10908 switch (HIWORD(wParam))
10913 * Adjust the edit window size
10915 WCHAR buffer[1024];
10916 HDC hdc = GetDC(infoPtr->hwndEdit);
10917 HFONT hFont, hOldFont = 0;
10921 if (!infoPtr->hwndEdit || !hdc) return 0;
10922 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
10923 GetWindowRect(infoPtr->hwndEdit, &rect);
10925 /* Select font to get the right dimension of the string */
10926 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
10929 hOldFont = SelectObject(hdc, hFont);
10932 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
10934 TEXTMETRICW textMetric;
10936 /* Add Extra spacing for the next character */
10937 GetTextMetricsW(hdc, &textMetric);
10938 sz.cx += (textMetric.tmMaxCharWidth * 2);
10946 rect.bottom - rect.top,
10947 SWP_DRAWFRAME|SWP_NOMOVE);
10950 SelectObject(hdc, hOldFont);
10952 ReleaseDC(infoPtr->hwndEdit, hdc);
10958 LISTVIEW_CancelEditLabel(infoPtr);
10962 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
10971 * Subclassed edit control windproc function
10974 * [I] hwnd : the edit window handle
10975 * [I] uMsg : the message that is to be processed
10976 * [I] wParam : first message parameter
10977 * [I] lParam : second message parameter
10978 * [I] isW : TRUE if input is Unicode
10983 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
10985 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
10988 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
10989 hwnd, uMsg, wParam, lParam, isW);
10993 case WM_GETDLGCODE:
10994 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
10998 WNDPROC editProc = infoPtr->EditWndProc;
10999 infoPtr->EditWndProc = 0;
11000 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
11001 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
11005 if (VK_ESCAPE == (INT)wParam)
11010 else if (VK_RETURN == (INT)wParam)
11014 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
11017 /* kill the edit */
11018 if (infoPtr->hwndEdit)
11019 LISTVIEW_EndEditLabelT(infoPtr, save, isW);
11021 SendMessageW(hwnd, WM_CLOSE, 0, 0);
11027 * Subclassed edit control Unicode windproc function
11030 * [I] hwnd : the edit window handle
11031 * [I] uMsg : the message that is to be processed
11032 * [I] wParam : first message parameter
11033 * [I] lParam : second message parameter
11037 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
11039 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
11044 * Subclassed edit control ANSI windproc function
11047 * [I] hwnd : the edit window handle
11048 * [I] uMsg : the message that is to be processed
11049 * [I] wParam : first message parameter
11050 * [I] lParam : second message parameter
11054 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
11056 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
11061 * Creates a subclassed edit control
11064 * [I] infoPtr : valid pointer to the listview structure
11065 * [I] text : initial text for the edit
11066 * [I] style : the window style
11067 * [I] isW : TRUE if input is Unicode
11071 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style, BOOL isW)
11073 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
11075 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
11077 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
11079 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
11081 /* Window will be resized and positioned after LVN_BEGINLABELEDIT */
11083 hedit = CreateWindowW(editName, text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
11085 hedit = CreateWindowA("Edit", (LPCSTR)text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
11087 if (!hedit) return 0;
11089 infoPtr->EditWndProc = (WNDPROC)
11090 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
11091 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
11093 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);