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
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 * This code was audited for completeness against the documented features
27 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
29 * Unless otherwise noted, we believe this code to be complete, as per
30 * the specification mentioned above.
31 * If you discover missing features, or bugs, please note them below.
35 * Default Message Processing
36 * -- WM_CREATE: create the icon and small icon image lists at this point only if
37 * the LVS_SHAREIMAGELISTS style is not specified.
38 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
39 * or small icon and the LVS_AUTOARRANGE style is specified.
44 * -- Hot item handling, mouse hovering
45 * -- Workareas support
50 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
51 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
52 * -- LVA_SNAPTOGRID not implemented
53 * -- LISTVIEW_ApproximateViewRect partially implemented
54 * -- LISTVIEW_[GS]etColumnOrderArray stubs
55 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
56 * -- LISTVIEW_SetIconSpacing is incomplete
57 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
60 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
61 * linear in the number of items in the list, and this is
62 * unacceptable for large lists.
63 * -- if list is sorted by item text LISTVIEW_InsertItemT could use
64 * binary search to calculate item index (e.g. DPA_Search()).
65 * This requires sorted state to be reliably tracked in item modifiers.
66 * -- we should keep an ordered array of coordinates in iconic mode
67 * this would allow to frame items (iterator_frameditems),
68 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
75 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
82 * -- LVS_NOSCROLL (see Q137520)
84 * -- LVS_TYPESTYLEMASK
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_CANCELEDITLABEL
110 * -- LVM_ENABLEGROUPVIEW
111 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
112 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
113 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
114 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
115 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
116 * -- LVM_GETINSERTMARKRECT
117 * -- LVM_GETNUMBEROFWORKAREAS
118 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
119 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
120 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
121 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
122 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
123 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
124 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
125 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
126 * -- LVM_INSERTGROUPSORTED
127 * -- LVM_INSERTMARKHITTEST
128 * -- LVM_ISGROUPVIEWENABLED
129 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
131 * -- LVM_MOVEITEMTOGROUP
133 * -- LVM_SETTILEWIDTH
137 * -- ListView_GetHoverTime, ListView_SetHoverTime
138 * -- ListView_GetISearchString
139 * -- ListView_GetNumberOfWorkAreas
140 * -- ListView_GetUnicodeFormat, ListView_SetUnicodeFormat
141 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
146 * Known differences in message stream from native control (not known if
147 * these differences cause problems):
148 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
149 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
150 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
151 * processing for "USEDOUBLECLICKTIME".
155 #include "wine/port.h"
170 #include "commctrl.h"
171 #include "comctl32.h"
174 #include "wine/debug.h"
175 #include "wine/unicode.h"
177 WINE_DEFAULT_DEBUG_CHANNEL(listview);
179 /* make sure you set this to 0 for production use! */
180 #define DEBUG_RANGES 1
182 typedef struct tagCOLUMN_INFO
184 RECT rcHeader; /* tracks the header's rectangle */
185 int fmt; /* same as LVCOLUMN.fmt */
188 typedef struct tagITEMHDR
192 } ITEMHDR, *LPITEMHDR;
194 typedef struct tagSUBITEM_INFO
200 typedef struct tagITEM_INFO
208 typedef struct tagRANGE
214 typedef struct tagRANGES
219 typedef struct tagITERATOR
228 typedef struct tagDELAYED_ITEM_EDIT
234 typedef struct tagLISTVIEW_INFO
241 HIMAGELIST himlNormal;
242 HIMAGELIST himlSmall;
243 HIMAGELIST himlState;
247 POINT ptClickPos; /* point where the user clicked */
248 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
251 RANGES selectionRanges;
256 RECT rcList; /* This rectangle is really the window
257 * client rectangle possibly reduced by the
258 * horizontal scroll bar and/or header - see
259 * LISTVIEW_UpdateSize. This rectangle offset
260 * by the LISTVIEW_GetOrigin value is in
261 * client coordinates */
270 INT ntmHeight; /* Some cached metrics of the font used */
271 INT ntmMaxCharWidth; /* by the listview to draw items */
273 BOOL bRedraw; /* Turns on/off repaints & invalidations */
274 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
276 BOOL bDoChangeNotify; /* send change notification messages? */
279 DWORD dwStyle; /* the cached window GWL_STYLE */
280 DWORD dwLvExStyle; /* extended listview style */
281 DWORD uView; /* current view available through LVM_[G,S]ETVIEW */
282 INT nItemCount; /* the number of items in the list */
283 HDPA hdpaItems; /* array ITEM_INFO pointers */
284 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
285 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
286 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
287 POINT currIconPos; /* this is the position next icon will be placed */
288 PFNLVCOMPARE pfnCompare;
293 INT nLButtonDownItem; /* tracks item to reset multiselection on WM_LBUTTONUP */
297 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
299 DWORD lastKeyPressTimestamp;
301 INT nSearchParamLength;
302 WCHAR szSearchParam[ MAX_PATH ];
304 INT nMeasureItemHeight;
305 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
306 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
308 DWORD iVersion; /* CCM_[G,S]ETVERSION */
314 /* How many we debug buffer to allocate */
315 #define DEBUG_BUFFERS 20
316 /* The size of a single debug bbuffer */
317 #define DEBUG_BUFFER_SIZE 256
319 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
320 #define SB_INTERNAL -1
322 /* maximum size of a label */
323 #define DISP_TEXT_SIZE 512
325 /* padding for items in list and small icon display modes */
326 #define WIDTH_PADDING 12
328 /* padding for items in list, report and small icon display modes */
329 #define HEIGHT_PADDING 1
331 /* offset of items in report display mode */
332 #define REPORT_MARGINX 2
334 /* padding for icon in large icon display mode
335 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
336 * that HITTEST will see.
337 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
338 * ICON_TOP_PADDING - sum of the two above.
339 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
340 * LABEL_HOR_PADDING - between text and sides of box
341 * LABEL_VERT_PADDING - between bottom of text and end of box
343 * ICON_LR_PADDING - additional width above icon size.
344 * ICON_LR_HALF - half of the above value
346 #define ICON_TOP_PADDING_NOTHITABLE 2
347 #define ICON_TOP_PADDING_HITABLE 2
348 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
349 #define ICON_BOTTOM_PADDING 4
350 #define LABEL_HOR_PADDING 5
351 #define LABEL_VERT_PADDING 7
352 #define ICON_LR_PADDING 16
353 #define ICON_LR_HALF (ICON_LR_PADDING/2)
355 /* default label width for items in list and small icon display modes */
356 #define DEFAULT_LABEL_WIDTH 40
358 /* default column width for items in list display mode */
359 #define DEFAULT_COLUMN_WIDTH 128
361 /* Size of "line" scroll for V & H scrolls */
362 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
364 /* Padding between image and label */
365 #define IMAGE_PADDING 2
367 /* Padding behind the label */
368 #define TRAILING_LABEL_PADDING 12
369 #define TRAILING_HEADER_PADDING 11
371 /* Border for the icon caption */
372 #define CAPTION_BORDER 2
374 /* Standard DrawText flags */
375 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
376 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
377 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
379 /* Image index from state */
380 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
382 /* The time in milliseconds to reset the search in the list */
383 #define KEY_DELAY 450
385 /* Dump the LISTVIEW_INFO structure to the debug channel */
386 #define LISTVIEW_DUMP(iP) do { \
387 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
388 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
389 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
390 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
391 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
392 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
393 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
394 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
395 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
396 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
399 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
402 * forward declarations
404 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
405 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
406 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
407 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
408 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
409 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *, INT);
410 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
411 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
412 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
413 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
414 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
415 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
416 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
417 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
418 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
419 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
420 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
421 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, BOOL);
422 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
423 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
425 /******** Text handling functions *************************************/
427 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
428 * text string. The string may be ANSI or Unicode, in which case
429 * the boolean isW tells us the type of the string.
431 * The name of the function tell what type of strings it expects:
432 * W: Unicode, T: ANSI/Unicode - function of isW
435 static inline BOOL is_textW(LPCWSTR text)
437 return text != NULL && text != LPSTR_TEXTCALLBACKW;
440 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
442 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
443 return is_textW(text);
446 static inline int textlenT(LPCWSTR text, BOOL isW)
448 return !is_textT(text, isW) ? 0 :
449 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
452 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
455 if (isSrcW) lstrcpynW(dest, src, max);
456 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
458 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
459 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
462 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
464 LPWSTR wstr = (LPWSTR)text;
466 if (!isW && is_textT(text, isW))
468 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
469 wstr = Alloc(len * sizeof(WCHAR));
470 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
472 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
476 static inline void textfreeT(LPWSTR wstr, BOOL isW)
478 if (!isW && is_textT(wstr, isW)) Free (wstr);
482 * dest is a pointer to a Unicode string
483 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
485 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
489 if (src == LPSTR_TEXTCALLBACKW)
491 if (is_textW(*dest)) Free(*dest);
492 *dest = LPSTR_TEXTCALLBACKW;
496 LPWSTR pszText = textdupTtoW(src, isW);
497 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
498 bResult = Str_SetPtrW(dest, pszText);
499 textfreeT(pszText, isW);
505 * compares a Unicode to a Unicode/ANSI text string
507 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
509 if (!aw) return bt ? -1 : 0;
510 if (!bt) return aw ? 1 : 0;
511 if (aw == LPSTR_TEXTCALLBACKW)
512 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
513 if (bt != LPSTR_TEXTCALLBACKW)
515 LPWSTR bw = textdupTtoW(bt, isW);
516 int r = bw ? lstrcmpW(aw, bw) : 1;
524 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
528 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
529 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
530 return res ? res - sizeof(WCHAR) : res;
533 /******** Debugging functions *****************************************/
535 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
537 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
538 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
541 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
543 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
544 n = min(textlenT(text, isW), n);
545 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
548 static char* debug_getbuf(void)
550 static int index = 0;
551 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
552 return buffers[index++ % DEBUG_BUFFERS];
555 static inline const char* debugrange(const RANGE *lprng)
557 if (!lprng) return "(null)";
558 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
561 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
563 char* buf = debug_getbuf(), *text = buf;
564 int len, size = DEBUG_BUFFER_SIZE;
566 if (pScrollInfo == NULL) return "(null)";
567 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
568 if (len == -1) goto end; buf += len; size -= len;
569 if (pScrollInfo->fMask & SIF_RANGE)
570 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
572 if (len == -1) goto end; buf += len; size -= len;
573 if (pScrollInfo->fMask & SIF_PAGE)
574 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
576 if (len == -1) goto end; buf += len; size -= len;
577 if (pScrollInfo->fMask & SIF_POS)
578 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
580 if (len == -1) goto end; buf += len; size -= len;
581 if (pScrollInfo->fMask & SIF_TRACKPOS)
582 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
584 if (len == -1) goto end; buf += len; size -= len;
587 buf = text + strlen(text);
589 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
593 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
595 if (!plvnm) return "(null)";
596 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
597 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
598 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
599 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
602 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
604 char* buf = debug_getbuf(), *text = buf;
605 int len, size = DEBUG_BUFFER_SIZE;
607 if (lpLVItem == NULL) return "(null)";
608 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
609 if (len == -1) goto end; buf += len; size -= len;
610 if (lpLVItem->mask & LVIF_STATE)
611 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
613 if (len == -1) goto end; buf += len; size -= len;
614 if (lpLVItem->mask & LVIF_TEXT)
615 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
617 if (len == -1) goto end; buf += len; size -= len;
618 if (lpLVItem->mask & LVIF_IMAGE)
619 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
621 if (len == -1) goto end; buf += len; size -= len;
622 if (lpLVItem->mask & LVIF_PARAM)
623 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
625 if (len == -1) goto end; buf += len; size -= len;
626 if (lpLVItem->mask & LVIF_INDENT)
627 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
629 if (len == -1) goto end; buf += len; size -= len;
632 buf = text + strlen(text);
634 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
638 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
640 char* buf = debug_getbuf(), *text = buf;
641 int len, size = DEBUG_BUFFER_SIZE;
643 if (lpColumn == NULL) return "(null)";
644 len = snprintf(buf, size, "{");
645 if (len == -1) goto end; buf += len; size -= len;
646 if (lpColumn->mask & LVCF_SUBITEM)
647 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
649 if (len == -1) goto end; buf += len; size -= len;
650 if (lpColumn->mask & LVCF_FMT)
651 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
653 if (len == -1) goto end; buf += len; size -= len;
654 if (lpColumn->mask & LVCF_WIDTH)
655 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
657 if (len == -1) goto end; buf += len; size -= len;
658 if (lpColumn->mask & LVCF_TEXT)
659 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
661 if (len == -1) goto end; buf += len; size -= len;
662 if (lpColumn->mask & LVCF_IMAGE)
663 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
665 if (len == -1) goto end; buf += len; size -= len;
666 if (lpColumn->mask & LVCF_ORDER)
667 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
669 if (len == -1) goto end; buf += len; size -= len;
672 buf = text + strlen(text);
674 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
678 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
680 if (!lpht) return "(null)";
682 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
683 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
686 /* Return the corresponding text for a given scroll value */
687 static inline LPCSTR debugscrollcode(int nScrollCode)
691 case SB_LINELEFT: return "SB_LINELEFT";
692 case SB_LINERIGHT: return "SB_LINERIGHT";
693 case SB_PAGELEFT: return "SB_PAGELEFT";
694 case SB_PAGERIGHT: return "SB_PAGERIGHT";
695 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
696 case SB_THUMBTRACK: return "SB_THUMBTRACK";
697 case SB_ENDSCROLL: return "SB_ENDSCROLL";
698 case SB_INTERNAL: return "SB_INTERNAL";
699 default: return "unknown";
704 /******** Notification functions ************************************/
706 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
708 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
709 (WPARAM)lpnmh->hdr.idFrom, (LPARAM)lpnmh);
712 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
716 TRACE("(code=%d)\n", code);
718 pnmh->hwndFrom = infoPtr->hwndSelf;
719 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
721 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
723 TRACE(" <= %ld\n", result);
728 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
731 HWND hwnd = infoPtr->hwndSelf;
732 notify_hdr(infoPtr, code, &nmh);
733 return IsWindow(hwnd);
736 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
747 item.mask = LVIF_PARAM|LVIF_STATE;
748 item.iItem = htInfo->iItem;
750 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
751 nmia.lParam = item.lParam;
752 nmia.uOldState = item.state;
753 nmia.uNewState = item.state | LVIS_ACTIVATING;
754 nmia.uChanged = LVIF_STATE;
757 nmia.iItem = htInfo->iItem;
758 nmia.iSubItem = htInfo->iSubItem;
759 nmia.ptAction = htInfo->pt;
761 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
762 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
763 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
765 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
768 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
770 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
771 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
774 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
778 HWND hwnd = infoPtr->hwndSelf;
780 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
781 ZeroMemory(&nmia, sizeof(nmia));
782 nmia.iItem = lvht->iItem;
783 nmia.iSubItem = lvht->iSubItem;
784 nmia.ptAction = lvht->pt;
785 item.mask = LVIF_PARAM;
786 item.iItem = lvht->iItem;
788 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmia.lParam = item.lParam;
789 notify_hdr(infoPtr, code, (LPNMHDR)&nmia);
790 return IsWindow(hwnd);
793 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
797 HWND hwnd = infoPtr->hwndSelf;
799 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
801 item.mask = LVIF_PARAM;
804 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
805 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
806 return IsWindow(hwnd);
809 static int get_ansi_notification(UINT unicodeNotificationCode)
811 switch (unicodeNotificationCode)
813 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
814 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
815 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
816 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
817 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
818 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
820 ERR("unknown notification %x\n", unicodeNotificationCode);
826 Send notification. depends on dispinfoW having same
827 structure as dispinfoA.
828 infoPtr : listview struct
829 notificationCode : *Unicode* notification code
830 pdi : dispinfo structure (can be unicode or ansi)
831 isW : TRUE if dispinfo is Unicode
833 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
835 BOOL bResult = FALSE;
836 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
837 INT cchTempBufMax = 0, savCchTextMax = 0;
839 LPWSTR pszTempBuf = NULL, savPszText = NULL;
841 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
843 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
844 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
847 if (convertToAnsi || convertToUnicode)
849 if (notificationCode != LVN_GETDISPINFOW)
851 cchTempBufMax = convertToUnicode ?
852 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
853 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
857 cchTempBufMax = pdi->item.cchTextMax;
858 *pdi->item.pszText = 0; /* make sure we don't process garbage */
861 pszTempBuf = Alloc( (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
862 if (!pszTempBuf) return FALSE;
864 if (convertToUnicode)
865 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
866 pszTempBuf, cchTempBufMax);
868 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
869 cchTempBufMax, NULL, NULL);
871 savCchTextMax = pdi->item.cchTextMax;
872 savPszText = pdi->item.pszText;
873 pdi->item.pszText = pszTempBuf;
874 pdi->item.cchTextMax = cchTempBufMax;
877 if (infoPtr->notifyFormat == NFR_ANSI)
878 realNotifCode = get_ansi_notification(notificationCode);
880 realNotifCode = notificationCode;
881 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
882 bResult = notify_hdr(infoPtr, realNotifCode, &pdi->hdr);
884 if (convertToUnicode || convertToAnsi)
886 if (convertToUnicode) /* note : pointer can be changed by app ! */
887 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
888 savCchTextMax, NULL, NULL);
890 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
891 savPszText, savCchTextMax);
892 pdi->item.pszText = savPszText; /* restores our buffer */
893 pdi->item.cchTextMax = savCchTextMax;
899 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
900 const RECT *rcBounds, const LVITEMW *lplvItem)
902 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
903 lpnmlvcd->nmcd.hdc = hdc;
904 lpnmlvcd->nmcd.rc = *rcBounds;
905 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
906 lpnmlvcd->clrText = infoPtr->clrText;
907 if (!lplvItem) return;
908 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
909 lpnmlvcd->iSubItem = lplvItem->iSubItem;
910 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
911 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
912 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
913 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
916 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
918 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
921 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
922 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
923 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
924 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
925 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
926 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
930 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
932 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
933 lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
934 if (lpnmlvcd->clrText == CLR_DEFAULT)
935 lpnmlvcd->clrText = comctl32_color.clrWindowText;
937 /* apparently, for selected items, we have to override the returned values */
940 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
944 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
945 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
947 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
949 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
950 lpnmlvcd->clrText = comctl32_color.clrBtnText;
955 /* Set the text attributes */
956 if (lpnmlvcd->clrTextBk != CLR_NONE)
958 SetBkMode(hdc, OPAQUE);
959 SetBkColor(hdc,lpnmlvcd->clrTextBk);
962 SetBkMode(hdc, TRANSPARENT);
963 SetTextColor(hdc, lpnmlvcd->clrText);
966 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
968 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
971 /******** Item iterator functions **********************************/
973 static RANGES ranges_create(int count);
974 static void ranges_destroy(RANGES ranges);
975 static BOOL ranges_add(RANGES ranges, RANGE range);
976 static BOOL ranges_del(RANGES ranges, RANGE range);
977 static void ranges_dump(RANGES ranges);
979 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
981 RANGE range = { nItem, nItem + 1 };
983 return ranges_add(ranges, range);
986 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
988 RANGE range = { nItem, nItem + 1 };
990 return ranges_del(ranges, range);
994 * ITERATOR DOCUMENTATION
996 * The iterator functions allow for easy, and convenient iteration
997 * over items of interest in the list. Typically, you create a
998 * iterator, use it, and destroy it, as such:
1001 * iterator_xxxitems(&i, ...);
1002 * while (iterator_{prev,next}(&i)
1004 * //code which uses i.nItem
1006 * iterator_destroy(&i);
1008 * where xxx is either: framed, or visible.
1009 * Note that it is important that the code destroys the iterator
1010 * after it's done with it, as the creation of the iterator may
1011 * allocate memory, which thus needs to be freed.
1013 * You can iterate both forwards, and backwards through the list,
1014 * by using iterator_next or iterator_prev respectively.
1016 * Lower numbered items are draw on top of higher number items in
1017 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1018 * items may overlap). So, to test items, you should use
1020 * which lists the items top to bottom (in Z-order).
1021 * For drawing items, you should use
1023 * which lists the items bottom to top (in Z-order).
1024 * If you keep iterating over the items after the end-of-items
1025 * marker (-1) is returned, the iterator will start from the
1026 * beginning. Typically, you don't need to test for -1,
1027 * because iterator_{next,prev} will return TRUE if more items
1028 * are to be iterated over, or FALSE otherwise.
1030 * Note: the iterator is defined to be bidirectional. That is,
1031 * any number of prev followed by any number of next, or
1032 * five versa, should leave the iterator at the same item:
1033 * prev * n, next * n = next * n, prev * n
1035 * The iterator has a notion of an out-of-order, special item,
1036 * which sits at the start of the list. This is used in
1037 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1038 * which needs to be first, as it may overlap other items.
1040 * The code is a bit messy because we have:
1041 * - a special item to deal with
1042 * - simple range, or composite range
1044 * If you find bugs, or want to add features, please make sure you
1045 * always check/modify *both* iterator_prev, and iterator_next.
1049 * This function iterates through the items in increasing order,
1050 * but prefixed by the special item, then -1. That is:
1051 * special, 1, 2, 3, ..., n, -1.
1052 * Each item is listed only once.
1054 static inline BOOL iterator_next(ITERATOR* i)
1058 i->nItem = i->nSpecial;
1059 if (i->nItem != -1) return TRUE;
1061 if (i->nItem == i->nSpecial)
1063 if (i->ranges) i->index = 0;
1069 if (i->nItem == i->nSpecial) i->nItem++;
1070 if (i->nItem < i->range.upper) return TRUE;
1075 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1076 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1079 else if (i->nItem >= i->range.upper) goto end;
1081 i->nItem = i->range.lower;
1082 if (i->nItem >= 0) goto testitem;
1089 * This function iterates through the items in decreasing order,
1090 * followed by the special item, then -1. That is:
1091 * n, n-1, ..., 3, 2, 1, special, -1.
1092 * Each item is listed only once.
1094 static inline BOOL iterator_prev(ITERATOR* i)
1101 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1104 if (i->nItem == i->nSpecial)
1112 if (i->nItem == i->nSpecial) i->nItem--;
1113 if (i->nItem >= i->range.lower) return TRUE;
1119 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1122 else if (!start && i->nItem < i->range.lower) goto end;
1124 i->nItem = i->range.upper;
1125 if (i->nItem > 0) goto testitem;
1127 return (i->nItem = i->nSpecial) != -1;
1130 static RANGE iterator_range(const ITERATOR *i)
1134 if (!i->ranges) return i->range;
1136 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1138 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1139 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1141 else range.lower = range.upper = 0;
1147 * Releases resources associated with this ierator.
1149 static inline void iterator_destroy(const ITERATOR *i)
1151 ranges_destroy(i->ranges);
1155 * Create an empty iterator.
1157 static inline BOOL iterator_empty(ITERATOR* i)
1159 ZeroMemory(i, sizeof(*i));
1160 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1165 * Create an iterator over a range.
1167 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1175 * Create an iterator over a bunch of ranges.
1176 * Please note that the iterator will take ownership of the ranges,
1177 * and will free them upon destruction.
1179 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1187 * Creates an iterator over the items which intersect lprc.
1189 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1191 RECT frame = *lprc, rcItem, rcTemp;
1194 /* in case we fail, we want to return an empty iterator */
1195 if (!iterator_empty(i)) return FALSE;
1197 LISTVIEW_GetOrigin(infoPtr, &Origin);
1199 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1200 OffsetRect(&frame, -Origin.x, -Origin.y);
1202 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
1206 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->nFocusedItem != -1)
1208 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1209 if (IntersectRect(&rcTemp, &rcItem, lprc))
1210 i->nSpecial = infoPtr->nFocusedItem;
1212 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1213 /* to do better here, we need to have PosX, and PosY sorted */
1214 TRACE("building icon ranges:\n");
1215 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1217 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1218 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1219 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1220 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1221 if (IntersectRect(&rcTemp, &rcItem, &frame))
1222 ranges_additem(i->ranges, nItem);
1226 else if (infoPtr->uView == LV_VIEW_DETAILS)
1230 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1231 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1233 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1234 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1235 if (range.upper <= range.lower) return TRUE;
1236 if (!iterator_rangeitems(i, range)) return FALSE;
1237 TRACE(" report=%s\n", debugrange(&i->range));
1241 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1242 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1243 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1244 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1245 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1246 INT lower = nFirstCol * nPerCol + nFirstRow;
1250 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1251 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1253 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1255 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1256 TRACE("building list ranges:\n");
1257 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1259 item_range.lower = nCol * nPerCol + nFirstRow;
1260 if(item_range.lower >= infoPtr->nItemCount) break;
1261 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1262 TRACE(" list=%s\n", debugrange(&item_range));
1263 ranges_add(i->ranges, item_range);
1271 * Creates an iterator over the items which intersect the visible region of hdc.
1273 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1275 POINT Origin, Position;
1276 RECT rcItem, rcClip;
1279 rgntype = GetClipBox(hdc, &rcClip);
1280 if (rgntype == NULLREGION) return iterator_empty(i);
1281 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1282 if (rgntype == SIMPLEREGION) return TRUE;
1284 /* first deal with the special item */
1285 if (i->nSpecial != -1)
1287 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1288 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1291 /* if we can't deal with the region, we'll just go with the simple range */
1292 LISTVIEW_GetOrigin(infoPtr, &Origin);
1293 TRACE("building visible range:\n");
1294 if (!i->ranges && i->range.lower < i->range.upper)
1296 if (!(i->ranges = ranges_create(50))) return TRUE;
1297 if (!ranges_add(i->ranges, i->range))
1299 ranges_destroy(i->ranges);
1305 /* now delete the invisible items from the list */
1306 while(iterator_next(i))
1308 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1309 rcItem.left = Position.x + Origin.x;
1310 rcItem.top = Position.y + Origin.y;
1311 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1312 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1313 if (!RectVisible(hdc, &rcItem))
1314 ranges_delitem(i->ranges, i->nItem);
1316 /* the iterator should restart on the next iterator_next */
1322 /******** Misc helper functions ************************************/
1324 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1325 WPARAM wParam, LPARAM lParam, BOOL isW)
1327 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1328 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1331 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1333 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1334 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON);
1337 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1339 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1340 if(state == 1 || state == 2)
1344 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1345 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1346 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1350 /* this should be called after window style got updated,
1351 it used to reset view state to match current window style */
1352 static inline void map_style_view(LISTVIEW_INFO *infoPtr)
1354 switch (infoPtr->dwStyle & LVS_TYPEMASK)
1357 infoPtr->uView = LV_VIEW_ICON;
1360 infoPtr->uView = LV_VIEW_DETAILS;
1363 infoPtr->uView = LV_VIEW_SMALLICON;
1366 infoPtr->uView = LV_VIEW_LIST;
1370 /******** Internal API functions ************************************/
1372 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1374 static COLUMN_INFO mainItem;
1376 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1377 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1378 return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1381 static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr)
1383 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
1386 if (infoPtr->hwndHeader) return 0;
1388 TRACE("Creating header for list %p\n", infoPtr->hwndSelf);
1390 /* setup creation flags */
1391 dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
1392 dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
1394 hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
1397 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
1398 0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
1399 if (!infoPtr->hwndHeader) return -1;
1401 /* set header unicode format */
1402 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
1404 /* set header font */
1405 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
1407 LISTVIEW_UpdateSize(infoPtr);
1412 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1414 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1417 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1419 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1422 /* used to handle collapse main item column case */
1423 static inline BOOL LISTVIEW_DrawFocusRect(const LISTVIEW_INFO *infoPtr, HDC hdc)
1425 return (infoPtr->rcFocus.left < infoPtr->rcFocus.right) ?
1426 DrawFocusRect(hdc, &infoPtr->rcFocus) : FALSE;
1429 /* Listview invalidation functions: use _only_ these functions to invalidate */
1431 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1433 return infoPtr->bRedraw;
1436 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1438 if(!is_redrawing(infoPtr)) return;
1439 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1440 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1443 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1447 if(!is_redrawing(infoPtr)) return;
1448 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1449 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1452 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1454 POINT Origin, Position;
1457 if(!is_redrawing(infoPtr)) return;
1458 assert (infoPtr->uView == LV_VIEW_DETAILS);
1459 LISTVIEW_GetOrigin(infoPtr, &Origin);
1460 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1461 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1463 rcBox.bottom = infoPtr->nItemHeight;
1464 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1465 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1468 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1470 LISTVIEW_InvalidateRect(infoPtr, NULL);
1473 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1477 if(!is_redrawing(infoPtr)) return;
1478 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1479 rcCol.top = infoPtr->rcList.top;
1480 rcCol.bottom = infoPtr->rcList.bottom;
1481 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1486 * Retrieves the number of items that can fit vertically in the client area.
1489 * [I] infoPtr : valid pointer to the listview structure
1492 * Number of items per row.
1494 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1496 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1498 return max(nListWidth/infoPtr->nItemWidth, 1);
1503 * Retrieves the number of items that can fit horizontally in the client
1507 * [I] infoPtr : valid pointer to the listview structure
1510 * Number of items per column.
1512 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1514 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1516 return max(nListHeight / infoPtr->nItemHeight, 1);
1520 /*************************************************************************
1521 * LISTVIEW_ProcessLetterKeys
1523 * Processes keyboard messages generated by pressing the letter keys
1525 * What this does is perform a case insensitive search from the
1526 * current position with the following quirks:
1527 * - If two chars or more are pressed in quick succession we search
1528 * for the corresponding string (e.g. 'abc').
1529 * - If there is a delay we wipe away the current search string and
1530 * restart with just that char.
1531 * - If the user keeps pressing the same character, whether slowly or
1532 * fast, so that the search string is entirely composed of this
1533 * character ('aaaaa' for instance), then we search for first item
1534 * that starting with that character.
1535 * - If the user types the above character in quick succession, then
1536 * we must also search for the corresponding string ('aaaaa'), and
1537 * go to that string if there is a match.
1540 * [I] hwnd : handle to the window
1541 * [I] charCode : the character code, the actual character
1542 * [I] keyData : key data
1550 * - The current implementation has a list of characters it will
1551 * accept and it ignores everything else. In particular it will
1552 * ignore accentuated characters which seems to match what
1553 * Windows does. But I'm not sure it makes sense to follow
1555 * - We don't sound a beep when the search fails.
1559 * TREEVIEW_ProcessLetterKeys
1561 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1566 WCHAR buffer[MAX_PATH];
1567 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1569 /* simple parameter checking */
1570 if (!charCode || !keyData) return 0;
1572 /* only allow the valid WM_CHARs through */
1573 if (!isalnumW(charCode) &&
1574 charCode != '.' && charCode != '`' && charCode != '!' &&
1575 charCode != '@' && charCode != '#' && charCode != '$' &&
1576 charCode != '%' && charCode != '^' && charCode != '&' &&
1577 charCode != '*' && charCode != '(' && charCode != ')' &&
1578 charCode != '-' && charCode != '_' && charCode != '+' &&
1579 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1580 charCode != '}' && charCode != '[' && charCode != '{' &&
1581 charCode != '/' && charCode != '?' && charCode != '>' &&
1582 charCode != '<' && charCode != ',' && charCode != '~')
1585 /* if there's one item or less, there is no where to go */
1586 if (infoPtr->nItemCount <= 1) return 0;
1588 /* update the search parameters */
1589 infoPtr->lastKeyPressTimestamp = GetTickCount();
1590 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1591 if (infoPtr->nSearchParamLength < MAX_PATH-1)
1592 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1593 if (infoPtr->charCode != charCode)
1594 infoPtr->charCode = charCode = 0;
1596 infoPtr->charCode=charCode;
1597 infoPtr->szSearchParam[0]=charCode;
1598 infoPtr->nSearchParamLength=1;
1599 /* Redundant with the 1 char string */
1603 /* and search from the current position */
1605 if (infoPtr->nFocusedItem >= 0) {
1606 endidx=infoPtr->nFocusedItem;
1608 /* if looking for single character match,
1609 * then we must always move forward
1611 if (infoPtr->nSearchParamLength == 1)
1614 endidx=infoPtr->nItemCount;
1618 /* Let application handle this for virtual listview */
1619 if (infoPtr->dwStyle & LVS_OWNERDATA)
1624 ZeroMemory(&lvfi, sizeof(lvfi));
1625 lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1626 infoPtr->szSearchParam[infoPtr->nSearchParamLength] = '\0';
1627 lvfi.psz = infoPtr->szSearchParam;
1631 nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1634 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1640 if (idx == infoPtr->nItemCount) {
1641 if (endidx == infoPtr->nItemCount || endidx == 0)
1647 item.mask = LVIF_TEXT;
1650 item.pszText = buffer;
1651 item.cchTextMax = MAX_PATH;
1652 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1654 /* check for a match */
1655 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1658 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1659 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1660 /* This would work but we must keep looking for a longer match */
1664 } while (idx != endidx);
1667 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1672 /*************************************************************************
1673 * LISTVIEW_UpdateHeaderSize [Internal]
1675 * Function to resize the header control
1678 * [I] hwnd : handle to a window
1679 * [I] nNewScrollPos : scroll pos to set
1684 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1689 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1691 if (!infoPtr->hwndHeader) return;
1693 GetWindowRect(infoPtr->hwndHeader, &winRect);
1694 point[0].x = winRect.left;
1695 point[0].y = winRect.top;
1696 point[1].x = winRect.right;
1697 point[1].y = winRect.bottom;
1699 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1700 point[0].x = -nNewScrollPos;
1701 point[1].x += nNewScrollPos;
1703 SetWindowPos(infoPtr->hwndHeader,0,
1704 point[0].x,point[0].y,point[1].x,point[1].y,
1705 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
1706 SWP_NOZORDER | SWP_NOACTIVATE);
1711 * Update the scrollbars. This functions should be called whenever
1712 * the content, size or view changes.
1715 * [I] infoPtr : valid pointer to the listview structure
1720 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
1722 SCROLLINFO horzInfo, vertInfo;
1725 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1727 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1728 horzInfo.cbSize = sizeof(SCROLLINFO);
1729 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1731 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1732 if (infoPtr->uView == LV_VIEW_LIST)
1734 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1735 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1737 /* scroll by at least one column per page */
1738 if(horzInfo.nPage < infoPtr->nItemWidth)
1739 horzInfo.nPage = infoPtr->nItemWidth;
1741 horzInfo.nPage /= infoPtr->nItemWidth;
1743 else if (infoPtr->uView == LV_VIEW_DETAILS)
1745 horzInfo.nMax = infoPtr->nItemWidth;
1747 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
1751 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1754 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1755 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1756 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
1757 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1758 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1760 /* Setting the horizontal scroll can change the listview size
1761 * (and potentially everything else) so we need to recompute
1762 * everything again for the vertical scroll
1765 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1766 vertInfo.cbSize = sizeof(SCROLLINFO);
1767 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1769 if (infoPtr->uView == LV_VIEW_DETAILS)
1771 vertInfo.nMax = infoPtr->nItemCount;
1773 /* scroll by at least one page */
1774 if(vertInfo.nPage < infoPtr->nItemHeight)
1775 vertInfo.nPage = infoPtr->nItemHeight;
1777 if (infoPtr->nItemHeight > 0)
1778 vertInfo.nPage /= infoPtr->nItemHeight;
1780 else if (infoPtr->uView != LV_VIEW_LIST) /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
1784 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1787 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1788 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1789 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
1790 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1791 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1793 /* Change of the range may have changed the scroll pos. If so move the content */
1794 if (dx != 0 || dy != 0)
1797 listRect = infoPtr->rcList;
1798 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
1799 SW_ERASE | SW_INVALIDATE);
1802 /* Update the Header Control */
1803 if (infoPtr->uView == LV_VIEW_DETAILS)
1805 horzInfo.fMask = SIF_POS;
1806 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1807 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1814 * Shows/hides the focus rectangle.
1817 * [I] infoPtr : valid pointer to the listview structure
1818 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1823 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
1827 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1829 if (infoPtr->nFocusedItem < 0) return;
1831 /* we need some gymnastics in ICON mode to handle large items */
1832 if (infoPtr->uView == LV_VIEW_ICON)
1836 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1837 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1839 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1844 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1846 /* for some reason, owner draw should work only in report mode */
1847 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
1852 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1853 HFONT hOldFont = SelectObject(hdc, hFont);
1855 item.iItem = infoPtr->nFocusedItem;
1857 item.mask = LVIF_PARAM;
1858 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1860 ZeroMemory(&dis, sizeof(dis));
1861 dis.CtlType = ODT_LISTVIEW;
1862 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1863 dis.itemID = item.iItem;
1864 dis.itemAction = ODA_FOCUS;
1865 if (fShow) dis.itemState |= ODS_FOCUS;
1866 dis.hwndItem = infoPtr->hwndSelf;
1868 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1869 dis.itemData = item.lParam;
1871 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1873 SelectObject(hdc, hOldFont);
1877 LISTVIEW_DrawFocusRect(infoPtr, hdc);
1880 ReleaseDC(infoPtr->hwndSelf, hdc);
1884 * Invalidates all visible selected items.
1886 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
1890 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1891 while(iterator_next(&i))
1893 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1894 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1896 iterator_destroy(&i);
1901 * DESCRIPTION: [INTERNAL]
1902 * Computes an item's (left,top) corner, relative to rcView.
1903 * That is, the position has NOT been made relative to the Origin.
1904 * This is deliberate, to avoid computing the Origin over, and
1905 * over again, when this function is called in a loop. Instead,
1906 * one can factor the computation of the Origin before the loop,
1907 * and offset the value returned by this function, on every iteration.
1910 * [I] infoPtr : valid pointer to the listview structure
1911 * [I] nItem : item number
1912 * [O] lpptOrig : item top, left corner
1917 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1919 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1921 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
1923 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1924 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1926 else if (infoPtr->uView == LV_VIEW_LIST)
1928 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1929 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1930 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1932 else /* LV_VIEW_DETAILS */
1934 lpptPosition->x = REPORT_MARGINX;
1935 /* item is always at zero indexed column */
1936 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
1937 lpptPosition->x += LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
1938 lpptPosition->y = nItem * infoPtr->nItemHeight;
1943 * DESCRIPTION: [INTERNAL]
1944 * Compute the rectangles of an item. This is to localize all
1945 * the computations in one place. If you are not interested in some
1946 * of these values, simply pass in a NULL -- the function is smart
1947 * enough to compute only what's necessary. The function computes
1948 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1949 * one, the BOX rectangle. This rectangle is very cheap to compute,
1950 * and is guaranteed to contain all the other rectangles. Computing
1951 * the ICON rect is also cheap, but all the others are potentially
1952 * expensive. This gives an easy and effective optimization when
1953 * searching (like point inclusion, or rectangle intersection):
1954 * first test against the BOX, and if TRUE, test against the desired
1956 * If the function does not have all the necessary information
1957 * to computed the requested rectangles, will crash with a
1958 * failed assertion. This is done so we catch all programming
1959 * errors, given that the function is called only from our code.
1961 * We have the following 'special' meanings for a few fields:
1962 * * If LVIS_FOCUSED is set, we assume the item has the focus
1963 * This is important in ICON mode, where it might get a larger
1964 * then usual rectangle
1966 * Please note that subitem support works only in REPORT mode.
1969 * [I] infoPtr : valid pointer to the listview structure
1970 * [I] lpLVItem : item to compute the measures for
1971 * [O] lprcBox : ptr to Box rectangle
1972 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
1973 * [0] lprcSelectBox : ptr to select box rectangle
1974 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
1975 * [O] lprcIcon : ptr to Icon rectangle
1976 * Same as LVM_GETITEMRECT with LVIR_ICON
1977 * [O] lprcStateIcon: ptr to State Icon rectangle
1978 * [O] lprcLabel : ptr to Label rectangle
1979 * Same as LVM_GETITEMRECT with LVIR_LABEL
1984 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1985 LPRECT lprcBox, LPRECT lprcSelectBox,
1986 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
1988 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1989 RECT Box, SelectBox, Icon, Label;
1990 COLUMN_INFO *lpColumnInfo = NULL;
1991 SIZE labelSize = { 0, 0 };
1993 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1995 /* Be smart and try to figure out the minimum we have to do */
1996 if (lpLVItem->iSubItem) assert(infoPtr->uView == LV_VIEW_DETAILS);
1997 if (infoPtr->uView == LV_VIEW_ICON && (lprcBox || lprcLabel))
1999 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
2000 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
2002 if (lprcSelectBox) doSelectBox = TRUE;
2003 if (lprcLabel) doLabel = TRUE;
2004 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
2011 /************************************************************/
2012 /* compute the box rectangle (it should be cheap to do) */
2013 /************************************************************/
2014 if (lpLVItem->iSubItem || infoPtr->uView == LV_VIEW_DETAILS)
2015 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
2017 if (lpLVItem->iSubItem)
2019 Box = lpColumnInfo->rcHeader;
2024 Box.right = infoPtr->nItemWidth;
2027 Box.bottom = infoPtr->nItemHeight;
2029 /******************************************************************/
2030 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2031 /******************************************************************/
2034 LONG state_width = 0;
2036 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
2037 state_width = infoPtr->iconStateSize.cx;
2039 if (infoPtr->uView == LV_VIEW_ICON)
2041 Icon.left = Box.left + state_width;
2042 if (infoPtr->himlNormal)
2043 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2044 Icon.top = Box.top + ICON_TOP_PADDING;
2045 Icon.right = Icon.left;
2046 Icon.bottom = Icon.top;
2047 if (infoPtr->himlNormal)
2049 Icon.right += infoPtr->iconSize.cx;
2050 Icon.bottom += infoPtr->iconSize.cy;
2053 else /* LV_VIEW_SMALLICON, LV_VIEW_LIST or LV_VIEW_DETAILS */
2055 Icon.left = Box.left + state_width;
2057 if (infoPtr->uView == LV_VIEW_DETAILS && lpLVItem->iSubItem == 0)
2059 /* we need the indent in report mode */
2060 assert(lpLVItem->mask & LVIF_INDENT);
2061 Icon.left += infoPtr->iconSize.cx * lpLVItem->iIndent + REPORT_MARGINX;
2065 Icon.right = Icon.left;
2066 if (infoPtr->himlSmall &&
2067 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2068 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2069 Icon.right += infoPtr->iconSize.cx;
2070 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2072 if(lprcIcon) *lprcIcon = Icon;
2073 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2075 /* TODO: is this correct? */
2078 lprcStateIcon->left = Icon.left - state_width;
2079 lprcStateIcon->right = Icon.left;
2080 lprcStateIcon->top = Icon.top;
2081 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2082 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2085 else Icon.right = 0;
2087 /************************************************************/
2088 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2089 /************************************************************/
2092 /* calculate how far to the right can the label stretch */
2093 Label.right = Box.right;
2094 if (infoPtr->uView == LV_VIEW_DETAILS)
2096 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
2099 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && infoPtr->uView == LV_VIEW_DETAILS))
2101 labelSize.cx = infoPtr->nItemWidth;
2102 labelSize.cy = infoPtr->nItemHeight;
2106 /* we need the text in non owner draw mode */
2107 assert(lpLVItem->mask & LVIF_TEXT);
2108 if (is_textT(lpLVItem->pszText, TRUE))
2110 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2111 HDC hdc = GetDC(infoPtr->hwndSelf);
2112 HFONT hOldFont = SelectObject(hdc, hFont);
2116 /* compute rough rectangle where the label will go */
2117 SetRectEmpty(&rcText);
2118 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2119 rcText.bottom = infoPtr->nItemHeight;
2120 if (infoPtr->uView == LV_VIEW_ICON)
2121 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2123 /* now figure out the flags */
2124 if (infoPtr->uView == LV_VIEW_ICON)
2125 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2127 uFormat = LV_SL_DT_FLAGS;
2129 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2131 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2132 labelSize.cy = rcText.bottom - rcText.top;
2134 SelectObject(hdc, hOldFont);
2135 ReleaseDC(infoPtr->hwndSelf, hdc);
2139 if (infoPtr->uView == LV_VIEW_ICON)
2141 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2142 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2143 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2144 Label.right = Label.left + labelSize.cx;
2145 Label.bottom = Label.top + infoPtr->nItemHeight;
2146 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2148 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2149 labelSize.cy /= infoPtr->ntmHeight;
2150 labelSize.cy = max(labelSize.cy, 1);
2151 labelSize.cy *= infoPtr->ntmHeight;
2153 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2155 else if (infoPtr->uView == LV_VIEW_DETAILS)
2157 Label.left = Icon.right;
2158 Label.top = Box.top;
2159 Label.right = lpColumnInfo->rcHeader.right;
2160 Label.bottom = Label.top + infoPtr->nItemHeight;
2162 else /* LV_VIEW_SMALLICON or LV_VIEW_LIST */
2164 Label.left = Icon.right;
2165 Label.top = Box.top;
2166 Label.right = min(Label.left + labelSize.cx, Label.right);
2167 Label.bottom = Label.top + infoPtr->nItemHeight;
2170 if (lprcLabel) *lprcLabel = Label;
2171 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2174 /************************************************************/
2175 /* compute SELECT bounding box */
2176 /************************************************************/
2179 if (infoPtr->uView == LV_VIEW_DETAILS)
2181 SelectBox.left = Icon.left;
2182 SelectBox.top = Box.top;
2183 SelectBox.bottom = Box.bottom;
2184 SelectBox.right = min(Label.left + labelSize.cx, Label.right);
2188 UnionRect(&SelectBox, &Icon, &Label);
2190 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2191 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2194 /* Fix the Box if necessary */
2197 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2198 else *lprcBox = Box;
2200 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2204 * DESCRIPTION: [INTERNAL]
2207 * [I] infoPtr : valid pointer to the listview structure
2208 * [I] nItem : item number
2209 * [O] lprcBox : ptr to Box rectangle
2214 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2216 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2217 POINT Position, Origin;
2220 LISTVIEW_GetOrigin(infoPtr, &Origin);
2221 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2223 /* Be smart and try to figure out the minimum we have to do */
2225 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2226 lvItem.mask |= LVIF_TEXT;
2227 lvItem.iItem = nItem;
2228 lvItem.iSubItem = 0;
2229 lvItem.pszText = szDispText;
2230 lvItem.cchTextMax = DISP_TEXT_SIZE;
2231 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2232 if (infoPtr->uView == LV_VIEW_ICON)
2234 lvItem.mask |= LVIF_STATE;
2235 lvItem.stateMask = LVIS_FOCUSED;
2236 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2238 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2240 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2246 * Returns the current icon position, and advances it along the top.
2247 * The returned position is not offset by Origin.
2250 * [I] infoPtr : valid pointer to the listview structure
2251 * [O] lpPos : will get the current icon position
2256 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2258 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2260 *lpPos = infoPtr->currIconPos;
2262 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2263 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2265 infoPtr->currIconPos.x = 0;
2266 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2272 * Returns the current icon position, and advances it down the left edge.
2273 * The returned position is not offset by Origin.
2276 * [I] infoPtr : valid pointer to the listview structure
2277 * [O] lpPos : will get the current icon position
2282 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2284 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2286 *lpPos = infoPtr->currIconPos;
2288 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2289 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2291 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2292 infoPtr->currIconPos.y = 0;
2298 * Moves an icon to the specified position.
2299 * It takes care of invalidating the item, etc.
2302 * [I] infoPtr : valid pointer to the listview structure
2303 * [I] nItem : the item to move
2304 * [I] lpPos : the new icon position
2305 * [I] isNew : flags the item as being new
2311 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2317 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2318 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2320 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2321 LISTVIEW_InvalidateItem(infoPtr, nItem);
2324 /* Allocating a POINTER for every item is too resource intensive,
2325 * so we'll keep the (x,y) in different arrays */
2326 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2327 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2329 LISTVIEW_InvalidateItem(infoPtr, nItem);
2336 * Arranges listview items in icon display mode.
2339 * [I] infoPtr : valid pointer to the listview structure
2340 * [I] nAlignCode : alignment code
2346 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2348 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2352 if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON) return FALSE;
2354 TRACE("nAlignCode=%d\n", nAlignCode);
2356 if (nAlignCode == LVA_DEFAULT)
2358 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2359 else nAlignCode = LVA_ALIGNTOP;
2364 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2365 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2366 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2367 default: return FALSE;
2370 infoPtr->bAutoarrange = TRUE;
2371 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2372 for (i = 0; i < infoPtr->nItemCount; i++)
2374 next_pos(infoPtr, &pos);
2375 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2383 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2384 * For LVS_REPORT always returns empty rectangle.
2387 * [I] infoPtr : valid pointer to the listview structure
2388 * [O] lprcView : bounding rectangle
2394 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2398 SetRectEmpty(lprcView);
2400 switch (infoPtr->uView)
2403 case LV_VIEW_SMALLICON:
2404 for (i = 0; i < infoPtr->nItemCount; i++)
2406 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2407 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2408 lprcView->right = max(lprcView->right, x);
2409 lprcView->bottom = max(lprcView->bottom, y);
2411 if (infoPtr->nItemCount > 0)
2413 lprcView->right += infoPtr->nItemWidth;
2414 lprcView->bottom += infoPtr->nItemHeight;
2419 y = LISTVIEW_GetCountPerColumn(infoPtr);
2420 x = infoPtr->nItemCount / y;
2421 if (infoPtr->nItemCount % y) x++;
2422 lprcView->right = x * infoPtr->nItemWidth;
2423 lprcView->bottom = y * infoPtr->nItemHeight;
2430 * Retrieves the bounding rectangle of all the items.
2433 * [I] infoPtr : valid pointer to the listview structure
2434 * [O] lprcView : bounding rectangle
2440 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2444 TRACE("(lprcView=%p)\n", lprcView);
2446 if (!lprcView) return FALSE;
2448 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2450 if (infoPtr->uView != LV_VIEW_DETAILS)
2452 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2453 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2456 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2463 * Retrieves the subitem pointer associated with the subitem index.
2466 * [I] hdpaSubItems : DPA handle for a specific item
2467 * [I] nSubItem : index of subitem
2470 * SUCCESS : subitem pointer
2473 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2475 SUBITEM_INFO *lpSubItem;
2478 /* we should binary search here if need be */
2479 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2481 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2482 if (lpSubItem->iSubItem == nSubItem)
2492 * Calculates the desired item width.
2495 * [I] infoPtr : valid pointer to the listview structure
2498 * The desired item width.
2500 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2504 TRACE("uView=%d\n", infoPtr->uView);
2506 if (infoPtr->uView == LV_VIEW_ICON)
2507 nItemWidth = infoPtr->iconSpacing.cx;
2508 else if (infoPtr->uView == LV_VIEW_DETAILS)
2512 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2514 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2515 nItemWidth = rcHeader.right;
2518 else /* LV_VIEW_SMALLICON, or LV_VIEW_LIST */
2522 for (i = 0; i < infoPtr->nItemCount; i++)
2523 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2525 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2526 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2528 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2531 return max(nItemWidth, 1);
2536 * Calculates the desired item height.
2539 * [I] infoPtr : valid pointer to the listview structure
2542 * The desired item height.
2544 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2548 TRACE("uView=%d\n", infoPtr->uView);
2550 if (infoPtr->uView == LV_VIEW_ICON)
2551 nItemHeight = infoPtr->iconSpacing.cy;
2554 nItemHeight = infoPtr->ntmHeight;
2555 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
2557 if (infoPtr->himlState)
2558 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2559 if (infoPtr->himlSmall)
2560 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2561 if (infoPtr->himlState || infoPtr->himlSmall)
2562 nItemHeight += HEIGHT_PADDING;
2563 if (infoPtr->nMeasureItemHeight > 0)
2564 nItemHeight = infoPtr->nMeasureItemHeight;
2567 return max(nItemHeight, 1);
2572 * Updates the width, and height of an item.
2575 * [I] infoPtr : valid pointer to the listview structure
2580 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2582 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2583 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2589 * Retrieves and saves important text metrics info for the current
2593 * [I] infoPtr : valid pointer to the listview structure
2596 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2598 HDC hdc = GetDC(infoPtr->hwndSelf);
2599 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2600 HFONT hOldFont = SelectObject(hdc, hFont);
2604 if (GetTextMetricsW(hdc, &tm))
2606 infoPtr->ntmHeight = tm.tmHeight;
2607 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2610 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2611 infoPtr->nEllipsisWidth = sz.cx;
2613 SelectObject(hdc, hOldFont);
2614 ReleaseDC(infoPtr->hwndSelf, hdc);
2616 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2621 * A compare function for ranges
2624 * [I] range1 : pointer to range 1;
2625 * [I] range2 : pointer to range 2;
2629 * > 0 : if range 1 > range 2
2630 * < 0 : if range 2 > range 1
2631 * = 0 : if range intersects range 2
2633 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2637 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2639 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2644 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
2650 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2652 #define ranges_check(ranges, desc) do { } while(0)
2655 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2660 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2662 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2663 ranges_dump(ranges);
2664 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2666 prev = DPA_GetPtr(ranges->hdpa, 0);
2667 assert (prev->lower >= 0 && prev->lower < prev->upper);
2668 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2670 curr = DPA_GetPtr(ranges->hdpa, i);
2671 assert (prev->upper <= curr->lower);
2672 assert (curr->lower < curr->upper);
2676 TRACE("--- Done checking---\n");
2679 static RANGES ranges_create(int count)
2681 RANGES ranges = Alloc(sizeof(struct tagRANGES));
2682 if (!ranges) return NULL;
2683 ranges->hdpa = DPA_Create(count);
2684 if (ranges->hdpa) return ranges;
2689 static void ranges_clear(RANGES ranges)
2693 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2694 Free(DPA_GetPtr(ranges->hdpa, i));
2695 DPA_DeleteAllPtrs(ranges->hdpa);
2699 static void ranges_destroy(RANGES ranges)
2701 if (!ranges) return;
2702 ranges_clear(ranges);
2703 DPA_Destroy(ranges->hdpa);
2707 static RANGES ranges_clone(RANGES ranges)
2712 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2714 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2716 RANGE *newrng = Alloc(sizeof(RANGE));
2717 if (!newrng) goto fail;
2718 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2719 DPA_SetPtr(clone->hdpa, i, newrng);
2724 TRACE ("clone failed\n");
2725 ranges_destroy(clone);
2729 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2733 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2734 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2739 static void ranges_dump(RANGES ranges)
2743 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2744 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2747 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2749 RANGE srchrng = { nItem, nItem + 1 };
2751 TRACE("(nItem=%d)\n", nItem);
2752 ranges_check(ranges, "before contain");
2753 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2756 static INT ranges_itemcount(RANGES ranges)
2760 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2762 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2763 count += sel->upper - sel->lower;
2769 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2771 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2774 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2775 if (index == -1) return TRUE;
2777 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2779 chkrng = DPA_GetPtr(ranges->hdpa, index);
2780 if (chkrng->lower >= nItem)
2781 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2782 if (chkrng->upper > nItem)
2783 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2788 static BOOL ranges_add(RANGES ranges, RANGE range)
2793 TRACE("(%s)\n", debugrange(&range));
2794 ranges_check(ranges, "before add");
2796 /* try find overlapping regions first */
2797 srchrgn.lower = range.lower - 1;
2798 srchrgn.upper = range.upper + 1;
2799 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2805 TRACE("Adding new range\n");
2807 /* create the brand new range to insert */
2808 newrgn = Alloc(sizeof(RANGE));
2809 if(!newrgn) goto fail;
2812 /* figure out where to insert it */
2813 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2814 TRACE("index=%d\n", index);
2815 if (index == -1) index = 0;
2817 /* and get it over with */
2818 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2826 RANGE *chkrgn, *mrgrgn;
2827 INT fromindex, mergeindex;
2829 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2830 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2832 chkrgn->lower = min(range.lower, chkrgn->lower);
2833 chkrgn->upper = max(range.upper, chkrgn->upper);
2835 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2837 /* merge now common ranges */
2839 srchrgn.lower = chkrgn->lower - 1;
2840 srchrgn.upper = chkrgn->upper + 1;
2844 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2845 if (mergeindex == -1) break;
2846 if (mergeindex == index)
2848 fromindex = index + 1;
2852 TRACE("Merge with index %i\n", mergeindex);
2854 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2855 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2856 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2858 DPA_DeletePtr(ranges->hdpa, mergeindex);
2859 if (mergeindex < index) index --;
2863 ranges_check(ranges, "after add");
2867 ranges_check(ranges, "failed add");
2871 static BOOL ranges_del(RANGES ranges, RANGE range)
2876 TRACE("(%s)\n", debugrange(&range));
2877 ranges_check(ranges, "before del");
2879 /* we don't use DPAS_SORTED here, since we need *
2880 * to find the first overlapping range */
2881 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2884 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2886 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2888 /* case 1: Same range */
2889 if ( (chkrgn->upper == range.upper) &&
2890 (chkrgn->lower == range.lower) )
2892 DPA_DeletePtr(ranges->hdpa, index);
2895 /* case 2: engulf */
2896 else if ( (chkrgn->upper <= range.upper) &&
2897 (chkrgn->lower >= range.lower) )
2899 DPA_DeletePtr(ranges->hdpa, index);
2901 /* case 3: overlap upper */
2902 else if ( (chkrgn->upper <= range.upper) &&
2903 (chkrgn->lower < range.lower) )
2905 chkrgn->upper = range.lower;
2907 /* case 4: overlap lower */
2908 else if ( (chkrgn->upper > range.upper) &&
2909 (chkrgn->lower >= range.lower) )
2911 chkrgn->lower = range.upper;
2914 /* case 5: fully internal */
2917 RANGE tmprgn = *chkrgn, *newrgn;
2919 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
2920 newrgn->lower = chkrgn->lower;
2921 newrgn->upper = range.lower;
2922 chkrgn->lower = range.upper;
2923 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2932 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2935 ranges_check(ranges, "after del");
2939 ranges_check(ranges, "failed del");
2945 * Removes all selection ranges
2948 * [I] infoPtr : valid pointer to the listview structure
2949 * [I] toSkip : item range to skip removing the selection
2955 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2964 lvItem.stateMask = LVIS_SELECTED;
2966 /* need to clone the DPA because callbacks can change it */
2967 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2968 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2969 while(iterator_next(&i))
2970 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2971 /* note that the iterator destructor will free the cloned range */
2972 iterator_destroy(&i);
2977 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2981 if (!(toSkip = ranges_create(1))) return FALSE;
2982 if (nItem != -1) ranges_additem(toSkip, nItem);
2983 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2984 ranges_destroy(toSkip);
2988 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2990 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2995 * Retrieves the number of items that are marked as selected.
2998 * [I] infoPtr : valid pointer to the listview structure
3001 * Number of items selected.
3003 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
3005 INT nSelectedCount = 0;
3007 if (infoPtr->uCallbackMask & LVIS_SELECTED)
3010 for (i = 0; i < infoPtr->nItemCount; i++)
3012 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3017 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3019 TRACE("nSelectedCount=%d\n", nSelectedCount);
3020 return nSelectedCount;
3025 * Manages the item focus.
3028 * [I] infoPtr : valid pointer to the listview structure
3029 * [I] nItem : item index
3032 * TRUE : focused item changed
3033 * FALSE : focused item has NOT changed
3035 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3037 INT oldFocus = infoPtr->nFocusedItem;
3040 if (nItem == infoPtr->nFocusedItem) return FALSE;
3042 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3043 lvItem.stateMask = LVIS_FOCUSED;
3044 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3046 return oldFocus != infoPtr->nFocusedItem;
3049 /* Helper function for LISTVIEW_ShiftIndices *only* */
3050 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3052 if (nShiftItem < nItem) return nShiftItem;
3054 if (nShiftItem > nItem) return nShiftItem + direction;
3056 if (direction > 0) return nShiftItem + direction;
3058 return min(nShiftItem, infoPtr->nItemCount - 1);
3063 * Updates the various indices after an item has been inserted or deleted.
3066 * [I] infoPtr : valid pointer to the listview structure
3067 * [I] nItem : item index
3068 * [I] direction : Direction of shift, +1 or -1.
3073 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3078 /* temporarily disable change notification while shifting items */
3079 bOldChange = infoPtr->bDoChangeNotify;
3080 infoPtr->bDoChangeNotify = FALSE;
3082 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3084 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3086 assert(abs(direction) == 1);
3088 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3090 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3091 if (nNewFocus != infoPtr->nFocusedItem)
3092 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3094 /* But we are not supposed to modify nHotItem! */
3096 infoPtr->bDoChangeNotify = bOldChange;
3102 * Adds a block of selections.
3105 * [I] infoPtr : valid pointer to the listview structure
3106 * [I] nItem : item index
3109 * Whether the window is still valid.
3111 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3113 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3114 INT nLast = max(infoPtr->nSelectionMark, nItem);
3115 HWND hwndSelf = infoPtr->hwndSelf;
3116 NMLVODSTATECHANGE nmlv;
3121 /* Temporarily disable change notification
3122 * If the control is LVS_OWNERDATA, we need to send
3123 * only one LVN_ODSTATECHANGED notification.
3124 * See MSDN documentation for LVN_ITEMCHANGED.
3126 bOldChange = infoPtr->bDoChangeNotify;
3127 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3129 if (nFirst == -1) nFirst = nItem;
3131 item.state = LVIS_SELECTED;
3132 item.stateMask = LVIS_SELECTED;
3134 for (i = nFirst; i <= nLast; i++)
3135 LISTVIEW_SetItemState(infoPtr,i,&item);
3137 ZeroMemory(&nmlv, sizeof(nmlv));
3138 nmlv.iFrom = nFirst;
3141 nmlv.uOldState = item.state;
3143 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3144 if (!IsWindow(hwndSelf))
3146 infoPtr->bDoChangeNotify = bOldChange;
3153 * Sets a single group selection.
3156 * [I] infoPtr : valid pointer to the listview structure
3157 * [I] nItem : item index
3162 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3169 if (!(selection = ranges_create(100))) return;
3171 item.state = LVIS_SELECTED;
3172 item.stateMask = LVIS_SELECTED;
3174 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
3176 if (infoPtr->nSelectionMark == -1)
3178 infoPtr->nSelectionMark = nItem;
3179 ranges_additem(selection, nItem);
3185 sel.lower = min(infoPtr->nSelectionMark, nItem);
3186 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3187 ranges_add(selection, sel);
3192 RECT rcItem, rcSel, rcSelMark;
3195 rcItem.left = LVIR_BOUNDS;
3196 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3197 rcSelMark.left = LVIR_BOUNDS;
3198 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3199 UnionRect(&rcSel, &rcItem, &rcSelMark);
3200 iterator_frameditems(&i, infoPtr, &rcSel);
3201 while(iterator_next(&i))
3203 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3204 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3206 iterator_destroy(&i);
3209 /* disable per item notifications on LVS_OWNERDATA style
3210 FIXME: single LVN_ODSTATECHANGED should be used */
3211 bOldChange = infoPtr->bDoChangeNotify;
3212 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3214 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3217 iterator_rangesitems(&i, selection);
3218 while(iterator_next(&i))
3219 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3220 /* this will also destroy the selection */
3221 iterator_destroy(&i);
3223 infoPtr->bDoChangeNotify = bOldChange;
3225 LISTVIEW_SetItemFocus(infoPtr, nItem);
3230 * Sets a single selection.
3233 * [I] infoPtr : valid pointer to the listview structure
3234 * [I] nItem : item index
3239 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3243 TRACE("nItem=%d\n", nItem);
3245 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3247 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3248 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3249 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3251 infoPtr->nSelectionMark = nItem;
3256 * Set selection(s) with keyboard.
3259 * [I] infoPtr : valid pointer to the listview structure
3260 * [I] nItem : item index
3261 * [I] space : VK_SPACE code sent
3264 * SUCCESS : TRUE (needs to be repainted)
3265 * FAILURE : FALSE (nothing has changed)
3267 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem, BOOL space)
3269 /* FIXME: pass in the state */
3270 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3271 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3272 BOOL bResult = FALSE;
3274 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3275 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3279 if (infoPtr->dwStyle & LVS_SINGLESEL || (wShift == 0 && wCtrl == 0))
3280 LISTVIEW_SetSelection(infoPtr, nItem);
3284 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3288 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3289 lvItem.stateMask = LVIS_SELECTED;
3292 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3293 if (lvItem.state & LVIS_SELECTED)
3294 infoPtr->nSelectionMark = nItem;
3296 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3299 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3302 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3306 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3308 LVHITTESTINFO lvHitTestInfo;
3310 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3311 lvHitTestInfo.pt.x = pt.x;
3312 lvHitTestInfo.pt.y = pt.y;
3314 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3316 lpLVItem->mask = LVIF_PARAM;
3317 lpLVItem->iItem = lvHitTestInfo.iItem;
3318 lpLVItem->iSubItem = 0;
3320 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3323 static inline BOOL LISTVIEW_isHotTracking(const LISTVIEW_INFO *infoPtr)
3325 return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3326 (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3327 (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3332 * Called when the mouse is being actively tracked and has hovered for a specified
3336 * [I] infoPtr : valid pointer to the listview structure
3337 * [I] fwKeys : key indicator
3338 * [I] x,y : mouse position
3341 * 0 if the message was processed, non-zero if there was an error
3344 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3345 * over the item for a certain period of time.
3348 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3350 if (LISTVIEW_isHotTracking(infoPtr))
3358 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3359 LISTVIEW_SetSelection(infoPtr, item.iItem);
3367 * Called whenever WM_MOUSEMOVE is received.
3370 * [I] infoPtr : valid pointer to the listview structure
3371 * [I] fwKeys : key indicator
3372 * [I] x,y : mouse position
3375 * 0 if the message is processed, non-zero if there was an error
3377 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3379 TRACKMOUSEEVENT trackinfo;
3381 if (!(fwKeys & MK_LBUTTON))
3382 infoPtr->bLButtonDown = FALSE;
3384 if (infoPtr->bLButtonDown)
3388 LVHITTESTINFO lvHitTestInfo;
3389 WORD wDragWidth = GetSystemMetrics(SM_CXDRAG);
3390 WORD wDragHeight= GetSystemMetrics(SM_CYDRAG);
3392 rect.left = infoPtr->ptClickPos.x - wDragWidth;
3393 rect.right = infoPtr->ptClickPos.x + wDragWidth;
3394 rect.top = infoPtr->ptClickPos.y - wDragHeight;
3395 rect.bottom = infoPtr->ptClickPos.y + wDragHeight;
3400 lvHitTestInfo.pt = tmp;
3401 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3403 /* reset item marker */
3404 if (infoPtr->nLButtonDownItem != lvHitTestInfo.iItem)
3405 infoPtr->nLButtonDownItem = -1;
3407 if (!PtInRect(&rect, tmp))
3409 /* this path covers the following:
3410 1. WM_LBUTTONDOWN over selected item (sets focus on it)
3411 2. change focus with keys
3412 3. move mouse over item from step 1 selects it and moves focus on it */
3413 if (infoPtr->nLButtonDownItem != -1 &&
3414 !LISTVIEW_GetItemState(infoPtr, infoPtr->nLButtonDownItem, LVIS_SELECTED))
3418 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3419 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3421 LISTVIEW_SetItemState(infoPtr, infoPtr->nLButtonDownItem, &lvItem);
3422 infoPtr->nLButtonDownItem = -1;
3425 if (!infoPtr->bDragging)
3429 lvHitTestInfo.pt = infoPtr->ptClickPos;
3430 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3432 ZeroMemory(&nmlv, sizeof(nmlv));
3433 nmlv.iItem = lvHitTestInfo.iItem;
3434 nmlv.ptAction = infoPtr->ptClickPos;
3436 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3437 infoPtr->bDragging = TRUE;
3444 /* see if we are supposed to be tracking mouse hovering */
3445 if (LISTVIEW_isHotTracking(infoPtr)) {
3446 /* fill in the trackinfo struct */
3447 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3448 trackinfo.dwFlags = TME_QUERY;
3449 trackinfo.hwndTrack = infoPtr->hwndSelf;
3450 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3452 /* see if we are already tracking this hwnd */
3453 _TrackMouseEvent(&trackinfo);
3455 if(!(trackinfo.dwFlags & TME_HOVER)) {
3456 trackinfo.dwFlags = TME_HOVER;
3458 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3459 _TrackMouseEvent(&trackinfo);
3468 * Tests whether the item is assignable to a list with style lStyle
3470 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3472 if ( (lpLVItem->mask & LVIF_TEXT) &&
3473 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3474 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3482 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3485 * [I] infoPtr : valid pointer to the listview structure
3486 * [I] lpLVItem : valid pointer to new item attributes
3487 * [I] isNew : the item being set is being inserted
3488 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3489 * [O] bChanged : will be set to TRUE if the item really changed
3495 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3497 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3502 /* stateMask is ignored for LVM_INSERTITEM */
3503 UINT stateMask = isNew ? ~0 : lpLVItem->stateMask;
3507 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3509 if (lpLVItem->mask == 0) return TRUE;
3511 if (infoPtr->dwStyle & LVS_OWNERDATA)
3513 /* a virtual listview only stores selection and focus */
3514 if (lpLVItem->mask & ~LVIF_STATE)
3520 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3521 lpItem = DPA_GetPtr(hdpaSubItems, 0);
3525 /* we need to get the lParam and state of the item */
3526 item.iItem = lpLVItem->iItem;
3527 item.iSubItem = lpLVItem->iSubItem;
3528 item.mask = LVIF_STATE | LVIF_PARAM;
3529 item.stateMask = (infoPtr->dwStyle & LVS_OWNERDATA) ? LVIS_FOCUSED | LVIS_SELECTED : ~0;
3533 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3535 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3536 /* determine what fields will change */
3537 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & stateMask & ~infoPtr->uCallbackMask))
3538 uChanged |= LVIF_STATE;
3540 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3541 uChanged |= LVIF_IMAGE;
3543 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3544 uChanged |= LVIF_PARAM;
3546 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3547 uChanged |= LVIF_INDENT;
3549 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3550 uChanged |= LVIF_TEXT;
3552 TRACE("uChanged=0x%x\n", uChanged);
3553 if (!uChanged) return TRUE;
3556 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3557 nmlv.iItem = lpLVItem->iItem;
3558 nmlv.uNewState = (item.state & ~stateMask) | (lpLVItem->state & stateMask);
3559 nmlv.uOldState = item.state;
3560 nmlv.uChanged = uChanged;
3561 nmlv.lParam = item.lParam;
3563 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3564 /* and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications */
3566 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
3568 HWND hwndSelf = infoPtr->hwndSelf;
3570 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3572 if (!IsWindow(hwndSelf))
3576 /* copy information */
3577 if (lpLVItem->mask & LVIF_TEXT)
3578 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3580 if (lpLVItem->mask & LVIF_IMAGE)
3581 lpItem->hdr.iImage = lpLVItem->iImage;
3583 if (lpLVItem->mask & LVIF_PARAM)
3584 lpItem->lParam = lpLVItem->lParam;
3586 if (lpLVItem->mask & LVIF_INDENT)
3587 lpItem->iIndent = lpLVItem->iIndent;
3589 if (uChanged & LVIF_STATE)
3591 if (lpItem && (stateMask & ~infoPtr->uCallbackMask))
3593 lpItem->state &= ~stateMask;
3594 lpItem->state |= (lpLVItem->state & stateMask);
3596 if (lpLVItem->state & stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3598 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3599 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3601 else if (stateMask & LVIS_SELECTED)
3603 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3605 /* if we are asked to change focus, and we manage it, do it */
3606 if (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3608 if (lpLVItem->state & LVIS_FOCUSED)
3610 if (infoPtr->nFocusedItem != -1)
3612 /* remove current focus */
3613 item.mask = LVIF_STATE;
3615 item.stateMask = LVIS_FOCUSED;
3617 /* recurse with redrawing an item */
3618 LISTVIEW_SetItemState(infoPtr, infoPtr->nFocusedItem, &item);
3621 infoPtr->nFocusedItem = lpLVItem->iItem;
3622 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3624 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3626 infoPtr->nFocusedItem = -1;
3631 /* if we're inserting the item, we're done */
3632 if (isNew) return TRUE;
3634 /* send LVN_ITEMCHANGED notification */
3635 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3636 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3643 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3646 * [I] infoPtr : valid pointer to the listview structure
3647 * [I] lpLVItem : valid pointer to new subitem attributes
3648 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3649 * [O] bChanged : will be set to TRUE if the item really changed
3655 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3658 SUBITEM_INFO *lpSubItem;
3660 /* we do not support subitems for virtual listviews */
3661 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3663 /* set subitem only if column is present */
3664 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3666 /* First do some sanity checks */
3667 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
3668 particularly useful. We currently do not actually do anything with
3669 the flag on subitems.
3671 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) return FALSE;
3672 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
3674 /* get the subitem structure, and create it if not there */
3675 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3676 assert (hdpaSubItems);
3678 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3681 SUBITEM_INFO *tmpSubItem;
3684 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
3685 if (!lpSubItem) return FALSE;
3686 /* we could binary search here, if need be...*/
3687 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3689 tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
3690 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3692 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3697 lpSubItem->iSubItem = lpLVItem->iSubItem;
3698 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3702 if (lpLVItem->mask & LVIF_IMAGE)
3703 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3705 lpSubItem->hdr.iImage = lpLVItem->iImage;
3709 if (lpLVItem->mask & LVIF_TEXT)
3710 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3712 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3721 * Sets item attributes.
3724 * [I] infoPtr : valid pointer to the listview structure
3725 * [I] lpLVItem : new item attributes
3726 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3732 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
3734 HWND hwndSelf = infoPtr->hwndSelf;
3735 LPWSTR pszText = NULL;
3736 BOOL bResult, bChanged = FALSE;
3738 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3740 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3743 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3744 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3746 pszText = lpLVItem->pszText;
3747 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3750 /* actually set the fields */
3751 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3753 if (lpLVItem->iSubItem)
3754 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3756 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3757 if (!IsWindow(hwndSelf))
3760 /* redraw item, if necessary */
3761 if (bChanged && !infoPtr->bIsDrawing)
3763 /* this little optimization eliminates some nasty flicker */
3764 if ( infoPtr->uView == LV_VIEW_DETAILS && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3765 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
3766 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
3767 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3769 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3774 textfreeT(lpLVItem->pszText, isW);
3775 lpLVItem->pszText = pszText;
3783 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3786 * [I] infoPtr : valid pointer to the listview structure
3791 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
3794 SCROLLINFO scrollInfo;
3796 scrollInfo.cbSize = sizeof(SCROLLINFO);
3797 scrollInfo.fMask = SIF_POS;
3799 if (infoPtr->uView == LV_VIEW_LIST)
3801 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3802 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3804 else if (infoPtr->uView == LV_VIEW_DETAILS)
3806 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3807 nItem = scrollInfo.nPos;
3811 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3812 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3815 TRACE("nItem=%d\n", nItem);
3823 * Erases the background of the given rectangle
3826 * [I] infoPtr : valid pointer to the listview structure
3827 * [I] hdc : device context handle
3828 * [I] lprcBox : clipping rectangle
3834 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3836 if (!infoPtr->hBkBrush) return FALSE;
3838 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
3840 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3848 * [I] infoPtr : valid pointer to the listview structure
3849 * [I] hdc : device context handle
3850 * [I] nItem : item index
3851 * [I] nSubItem : subitem index
3852 * [I] pos : item position in client coordinates
3853 * [I] cdmode : custom draw mode
3859 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3862 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3863 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3864 DWORD cdsubitemmode = CDRF_DODEFAULT;
3866 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
3867 NMLVCUSTOMDRAW nmlvcd;
3872 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
3874 /* get information needed for drawing the item */
3875 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
3876 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3877 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
3878 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3879 lvItem.iItem = nItem;
3880 lvItem.iSubItem = nSubItem;
3883 lvItem.cchTextMax = DISP_TEXT_SIZE;
3884 lvItem.pszText = szDispText;
3885 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3886 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3887 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3888 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3889 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3891 /* now check if we need to update the focus rectangle */
3892 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3894 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3895 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
3896 OffsetRect(&rcBox, pos.x, pos.y);
3897 OffsetRect(&rcSelect, pos.x, pos.y);
3898 OffsetRect(&rcIcon, pos.x, pos.y);
3899 OffsetRect(&rcStateIcon, pos.x, pos.y);
3900 OffsetRect(&rcLabel, pos.x, pos.y);
3901 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
3902 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
3903 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
3905 /* fill in the custom draw structure */
3906 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3908 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
3909 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3910 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
3911 if (cdmode & CDRF_NOTIFYITEMDRAW)
3912 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3913 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3914 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3915 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3916 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3918 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3919 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3921 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3922 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
3923 else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
3924 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
3926 /* in full row select, subitems, will just use main item's colors */
3927 if (nSubItem && infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3928 nmlvcd.clrTextBk = CLR_NONE;
3931 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
3933 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
3936 TRACE("uStateImage=%d\n", uStateImage);
3937 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
3938 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
3943 himl = (infoPtr->uView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3944 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3946 TRACE("iImage=%d\n", lvItem.iImage);
3947 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3948 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk, CLR_DEFAULT,
3949 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3952 /* Don't bother painting item being edited */
3953 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3955 /* FIXME: temporary hack */
3956 rcSelect.left = rcLabel.left;
3958 /* draw the selection background, if we're drawing the main item */
3961 /* in icon mode, the label rect is really what we want to draw the
3963 if (infoPtr->uView == LV_VIEW_ICON)
3966 if (infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3967 rcSelect.right = rcBox.right;
3969 if (nmlvcd.clrTextBk != CLR_NONE)
3970 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3971 /* store new focus rectangle */
3972 if (infoPtr->nFocusedItem == nItem) infoPtr->rcFocus = rcSelect;
3975 /* figure out the text drawing flags */
3976 uFormat = (infoPtr->uView == LV_VIEW_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3977 if (infoPtr->uView == LV_VIEW_ICON)
3978 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3981 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3983 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3984 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3985 default: uFormat |= DT_LEFT;
3988 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3990 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3991 else rcLabel.left += LABEL_HOR_PADDING;
3993 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3995 /* for GRIDLINES reduce the bottom so the text formats correctly */
3996 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
3999 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
4002 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
4003 notify_postpaint(infoPtr, &nmlvcd);
4004 if (cdsubitemmode & CDRF_NEWFONT)
4005 SelectObject(hdc, hOldFont);
4011 * Draws listview items when in owner draw mode.
4014 * [I] infoPtr : valid pointer to the listview structure
4015 * [I] hdc : device context handle
4020 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4022 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
4023 DWORD cditemmode = CDRF_DODEFAULT;
4024 NMLVCUSTOMDRAW nmlvcd;
4025 POINT Origin, Position;
4031 ZeroMemory(&dis, sizeof(dis));
4033 /* Get scroll info once before loop */
4034 LISTVIEW_GetOrigin(infoPtr, &Origin);
4036 /* iterate through the invalidated rows */
4037 while(iterator_next(i))
4039 item.iItem = i->nItem;
4041 item.mask = LVIF_PARAM | LVIF_STATE;
4042 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4043 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
4045 dis.CtlType = ODT_LISTVIEW;
4047 dis.itemID = item.iItem;
4048 dis.itemAction = ODA_DRAWENTIRE;
4050 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
4051 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
4052 dis.hwndItem = infoPtr->hwndSelf;
4054 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
4055 dis.rcItem.left = Position.x + Origin.x;
4056 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
4057 dis.rcItem.top = Position.y + Origin.y;
4058 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
4059 dis.itemData = item.lParam;
4061 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
4064 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
4065 * structure for the rest. of the paint cycle
4067 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
4068 if (cdmode & CDRF_NOTIFYITEMDRAW)
4069 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4071 if (!(cditemmode & CDRF_SKIPDEFAULT))
4073 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
4074 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
4077 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
4078 notify_postpaint(infoPtr, &nmlvcd);
4084 * Draws listview items when in report display mode.
4087 * [I] infoPtr : valid pointer to the listview structure
4088 * [I] hdc : device context handle
4089 * [I] cdmode : custom draw mode
4094 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4097 RECT rcClip, rcItem;
4098 POINT Origin, Position;
4105 /* figure out what to draw */
4106 rgntype = GetClipBox(hdc, &rcClip);
4107 if (rgntype == NULLREGION) return;
4109 /* Get scroll info once before loop */
4110 LISTVIEW_GetOrigin(infoPtr, &Origin);
4112 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
4114 /* narrow down the columns we need to paint */
4115 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
4117 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
4119 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4120 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
4121 ranges_additem(colRanges, index);
4123 iterator_rangesitems(&j, colRanges);
4125 /* in full row select, we _have_ to draw the main item */
4126 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4129 /* iterate through the invalidated rows */
4130 while(iterator_next(i))
4132 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4133 Position.x += Origin.x;
4134 Position.y += Origin.y;
4136 /* iterate through the invalidated columns */
4137 while(iterator_next(&j))
4139 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4141 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4143 rcItem.bottom = infoPtr->nItemHeight;
4144 OffsetRect(&rcItem, Position.x, Position.y);
4145 if (!RectVisible(hdc, &rcItem)) continue;
4148 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4151 iterator_destroy(&j);
4156 * Draws the gridlines if necessary when in report display mode.
4159 * [I] infoPtr : valid pointer to the listview structure
4160 * [I] hdc : device context handle
4165 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
4171 RECT rcClip, rcItem = {0};
4179 /* figure out what to draw */
4180 rgntype = GetClipBox(hdc, &rcClip);
4181 if (rgntype == NULLREGION) return;
4183 /* Get scroll info once before loop */
4184 LISTVIEW_GetOrigin(infoPtr, &Origin);
4186 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
4188 /* narrow down the columns we need to paint */
4189 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
4191 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
4193 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4194 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
4195 ranges_additem(colRanges, index);
4198 /* is right most vertical line visible? */
4199 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
4201 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4202 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4203 rmost = (rcItem.right + Origin.x < rcClip.right);
4206 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
4208 hOldPen = SelectObject ( hdc, hPen );
4210 /* draw the vertical lines for the columns */
4211 iterator_rangesitems(&j, colRanges);
4212 while(iterator_next(&j))
4214 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4215 if (rcItem.left == 0) continue; /* skip leftmost column */
4216 rcItem.left += Origin.x;
4217 rcItem.right += Origin.x;
4218 rcItem.top = infoPtr->rcList.top;
4219 rcItem.bottom = infoPtr->rcList.bottom;
4220 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
4221 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4222 LineTo (hdc, rcItem.left, rcItem.bottom);
4224 iterator_destroy(&j);
4225 /* draw rightmost grid line if visible */
4228 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
4229 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4230 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4232 rcItem.right += Origin.x;
4234 MoveToEx (hdc, rcItem.right, infoPtr->rcList.top, NULL);
4235 LineTo (hdc, rcItem.right, infoPtr->rcList.bottom);
4238 /* draw the horizontial lines for the rows */
4239 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
4240 rcItem.left = infoPtr->rcList.left;
4241 rcItem.right = infoPtr->rcList.right;
4242 rcItem.bottom = rcItem.top = Origin.y - 1;
4243 MoveToEx(hdc, rcItem.left, rcItem.top, NULL);
4244 LineTo(hdc, rcItem.right, rcItem.top);
4245 for(y=itemheight-1+Origin.y; y<=infoPtr->rcList.bottom; y+=itemheight)
4247 rcItem.bottom = rcItem.top = y;
4248 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
4249 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4250 LineTo (hdc, rcItem.right, rcItem.top);
4253 SelectObject( hdc, hOldPen );
4254 DeleteObject( hPen );
4260 * Draws listview items when in list display mode.
4263 * [I] infoPtr : valid pointer to the listview structure
4264 * [I] hdc : device context handle
4265 * [I] cdmode : custom draw mode
4270 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4272 POINT Origin, Position;
4274 /* Get scroll info once before loop */
4275 LISTVIEW_GetOrigin(infoPtr, &Origin);
4277 while(iterator_prev(i))
4279 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4280 Position.x += Origin.x;
4281 Position.y += Origin.y;
4283 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4290 * Draws listview items.
4293 * [I] infoPtr : valid pointer to the listview structure
4294 * [I] hdc : device context handle
4295 * [I] prcErase : rect to be erased before refresh (may be NULL)
4300 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
4302 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
4303 NMLVCUSTOMDRAW nmlvcd;
4310 HBITMAP hbmp = NULL;
4313 LISTVIEW_DUMP(infoPtr);
4315 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4316 TRACE("double buffering\n");
4318 hdc = CreateCompatibleDC(hdcOrig);
4320 ERR("Failed to create DC for backbuffer\n");
4323 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
4324 infoPtr->rcList.bottom);
4326 ERR("Failed to create bitmap for backbuffer\n");
4331 SelectObject(hdc, hbmp);
4332 SelectObject(hdc, infoPtr->hFont);
4334 /* Save dc values we're gonna trash while drawing
4335 * FIXME: Should be done in LISTVIEW_DrawItem() */
4336 hOldFont = SelectObject(hdc, infoPtr->hFont);
4337 oldBkMode = GetBkMode(hdc);
4338 oldBkColor = GetBkColor(hdc);
4339 oldTextColor = GetTextColor(hdc);
4342 infoPtr->bIsDrawing = TRUE;
4345 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
4346 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4347 /* If no erasing was done (usually because RedrawWindow was called
4348 * with RDW_INVALIDATE only) we need to copy the old contents into
4349 * the backbuffer before continuing. */
4350 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
4351 infoPtr->rcList.right - infoPtr->rcList.left,
4352 infoPtr->rcList.bottom - infoPtr->rcList.top,
4353 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4356 /* FIXME: Shouldn't need to do this */
4357 oldClrTextBk = infoPtr->clrTextBk;
4358 oldClrText = infoPtr->clrText;
4360 infoPtr->cditemmode = CDRF_DODEFAULT;
4362 GetClientRect(infoPtr->hwndSelf, &rcClient);
4363 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
4364 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4365 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
4366 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4368 /* Use these colors to draw the items */
4369 infoPtr->clrTextBk = nmlvcd.clrTextBk;
4370 infoPtr->clrText = nmlvcd.clrText;
4372 /* nothing to draw */
4373 if(infoPtr->nItemCount == 0) goto enddraw;
4375 /* figure out what we need to draw */
4376 iterator_visibleitems(&i, infoPtr, hdc);
4377 range = iterator_range(&i);
4379 /* send cache hint notification */
4380 if (infoPtr->dwStyle & LVS_OWNERDATA)
4384 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4385 nmlv.iFrom = range.lower;
4386 nmlv.iTo = range.upper - 1;
4387 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4390 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
4391 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4394 if (infoPtr->uView == LV_VIEW_DETAILS)
4395 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4396 else /* LV_VIEW_LIST, LV_VIEW_ICON or LV_VIEW_SMALLICON */
4397 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4399 /* if we have a focus rect and it's visible, draw it */
4400 if (infoPtr->bFocus && range.lower <= infoPtr->nFocusedItem &&
4401 (range.upper - 1) >= infoPtr->nFocusedItem)
4402 LISTVIEW_DrawFocusRect(infoPtr, hdc);
4404 iterator_destroy(&i);
4407 /* For LVS_EX_GRIDLINES go and draw lines */
4408 /* This includes the case where there were *no* items */
4409 if ((infoPtr->uView == LV_VIEW_DETAILS) && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4410 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
4412 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4413 notify_postpaint(infoPtr, &nmlvcd);
4415 infoPtr->clrTextBk = oldClrTextBk;
4416 infoPtr->clrText = oldClrText;
4419 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
4420 infoPtr->rcList.right - infoPtr->rcList.left,
4421 infoPtr->rcList.bottom - infoPtr->rcList.top,
4422 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4427 SelectObject(hdc, hOldFont);
4428 SetBkMode(hdc, oldBkMode);
4429 SetBkColor(hdc, oldBkColor);
4430 SetTextColor(hdc, oldTextColor);
4433 infoPtr->bIsDrawing = FALSE;
4439 * Calculates the approximate width and height of a given number of items.
4442 * [I] infoPtr : valid pointer to the listview structure
4443 * [I] nItemCount : number of items
4444 * [I] wWidth : width
4445 * [I] wHeight : height
4448 * Returns a DWORD. The width in the low word and the height in high word.
4450 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
4451 WORD wWidth, WORD wHeight)
4453 INT nItemCountPerColumn = 1;
4454 INT nColumnCount = 0;
4455 DWORD dwViewRect = 0;
4457 if (nItemCount == -1)
4458 nItemCount = infoPtr->nItemCount;
4460 if (infoPtr->uView == LV_VIEW_LIST)
4462 if (wHeight == 0xFFFF)
4464 /* use current height */
4465 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4468 if (wHeight < infoPtr->nItemHeight)
4469 wHeight = infoPtr->nItemHeight;
4473 if (infoPtr->nItemHeight > 0)
4475 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4476 if (nItemCountPerColumn == 0)
4477 nItemCountPerColumn = 1;
4479 if (nItemCount % nItemCountPerColumn != 0)
4480 nColumnCount = nItemCount / nItemCountPerColumn;
4482 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4486 /* Microsoft padding magic */
4487 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4488 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4490 dwViewRect = MAKELONG(wWidth, wHeight);
4492 else if (infoPtr->uView == LV_VIEW_DETAILS)
4496 if (infoPtr->nItemCount > 0)
4498 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4499 wWidth = rcBox.right - rcBox.left;
4500 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4504 /* use current height and width */
4505 if (wHeight == 0xffff)
4506 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4507 if (wWidth == 0xffff)
4508 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4511 dwViewRect = MAKELONG(wWidth, wHeight);
4513 else if (infoPtr->uView == LV_VIEW_SMALLICON)
4514 FIXME("uView == LV_VIEW_SMALLICON: not implemented\n");
4515 else if (infoPtr->uView == LV_VIEW_ICON)
4516 FIXME("uView == LV_VIEW_ICON: not implemented\n");
4524 * Create a drag image list for the specified item.
4527 * [I] infoPtr : valid pointer to the listview structure
4528 * [I] iItem : index of item
4529 * [O] lppt : Upper-left corner of the image
4532 * Returns a handle to the image list if successful, NULL otherwise.
4534 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4540 HBITMAP hbmp, hOldbmp;
4541 HIMAGELIST dragList = 0;
4542 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
4544 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4547 rcItem.left = LVIR_BOUNDS;
4548 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4551 lppt->x = rcItem.left;
4552 lppt->y = rcItem.top;
4554 size.cx = rcItem.right - rcItem.left;
4555 size.cy = rcItem.bottom - rcItem.top;
4557 hdcOrig = GetDC(infoPtr->hwndSelf);
4558 hdc = CreateCompatibleDC(hdcOrig);
4559 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4560 hOldbmp = SelectObject(hdc, hbmp);
4562 rcItem.left = rcItem.top = 0;
4563 rcItem.right = size.cx;
4564 rcItem.bottom = size.cy;
4565 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4568 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4570 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4571 SelectObject(hdc, hOldbmp);
4572 ImageList_Add(dragList, hbmp, 0);
4575 SelectObject(hdc, hOldbmp);
4579 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4581 TRACE("ret=%p\n", dragList);
4589 * Removes all listview items and subitems.
4592 * [I] infoPtr : valid pointer to the listview structure
4598 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
4601 HDPA hdpaSubItems = NULL;
4608 /* we do it directly, to avoid notifications */
4609 ranges_clear(infoPtr->selectionRanges);
4610 infoPtr->nSelectionMark = -1;
4611 infoPtr->nFocusedItem = -1;
4612 SetRectEmpty(&infoPtr->rcFocus);
4613 /* But we are supposed to leave nHotItem as is! */
4616 /* send LVN_DELETEALLITEMS notification */
4617 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4619 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4621 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4623 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4625 /* send LVN_DELETEITEM notification, if not suppressed
4626 and if it is not a virtual listview */
4627 if (!bSuppress) notify_deleteitem(infoPtr, i);
4628 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
4629 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4631 hdrItem = DPA_GetPtr(hdpaSubItems, j);
4632 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4635 DPA_Destroy(hdpaSubItems);
4636 DPA_DeletePtr(infoPtr->hdpaItems, i);
4638 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4639 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4640 infoPtr->nItemCount --;
4645 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4646 LISTVIEW_UpdateScroll(infoPtr);
4648 LISTVIEW_InvalidateList(infoPtr);
4655 * Scrolls, and updates the columns, when a column is changing width.
4658 * [I] infoPtr : valid pointer to the listview structure
4659 * [I] nColumn : column to scroll
4660 * [I] dx : amount of scroll, in pixels
4665 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4667 COLUMN_INFO *lpColumnInfo;
4672 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4673 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4674 rcCol = lpColumnInfo->rcHeader;
4675 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4676 rcCol.left = rcCol.right;
4678 /* adjust the other columns */
4679 for (nCol = 0; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4681 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4682 if (lpColumnInfo->rcHeader.left >= rcCol.left)
4684 lpColumnInfo->rcHeader.left += dx;
4685 lpColumnInfo->rcHeader.right += dx;
4689 /* do not update screen if not in report mode */
4690 if (!is_redrawing(infoPtr) || infoPtr->uView != LV_VIEW_DETAILS) return;
4692 /* Need to reset the item width when inserting a new column */
4693 infoPtr->nItemWidth += dx;
4695 LISTVIEW_UpdateScroll(infoPtr);
4696 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
4698 /* scroll to cover the deleted column, and invalidate for redraw */
4699 rcOld = infoPtr->rcList;
4700 rcOld.left = ptOrigin.x + rcCol.left + dx;
4701 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4706 * Removes a column from the listview control.
4709 * [I] infoPtr : valid pointer to the listview structure
4710 * [I] nColumn : column index
4716 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4720 TRACE("nColumn=%d\n", nColumn);
4722 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4723 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4725 /* While the MSDN specifically says that column zero should not be deleted,
4726 what actually happens is that the column itself is deleted but no items or subitems
4730 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4732 if (!SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nColumn, 0))
4735 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4736 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4738 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4740 SUBITEM_INFO *lpSubItem, *lpDelItem;
4742 INT nItem, nSubItem, i;
4744 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4746 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
4749 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4751 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
4752 if (lpSubItem->iSubItem == nColumn)
4755 lpDelItem = lpSubItem;
4757 else if (lpSubItem->iSubItem > nColumn)
4759 lpSubItem->iSubItem--;
4763 /* if we found our subitem, zapp it */
4767 if (is_textW(lpDelItem->hdr.pszText))
4768 Free(lpDelItem->hdr.pszText);
4773 /* free dpa memory */
4774 DPA_DeletePtr(hdpaSubItems, nSubItem);
4779 /* update the other column info */
4780 LISTVIEW_UpdateItemSize(infoPtr);
4781 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4782 LISTVIEW_InvalidateList(infoPtr);
4784 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4791 * Invalidates the listview after an item's insertion or deletion.
4794 * [I] infoPtr : valid pointer to the listview structure
4795 * [I] nItem : item index
4796 * [I] dir : -1 if deleting, 1 if inserting
4801 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4803 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4804 INT nPerCol, nItemCol, nItemRow;
4808 /* if we don't refresh, what's the point of scrolling? */
4809 if (!is_redrawing(infoPtr)) return;
4811 assert (abs(dir) == 1);
4813 /* arrange icons if autoarrange is on */
4814 if (is_autoarrange(infoPtr))
4816 BOOL arrange = TRUE;
4817 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4818 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4819 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4822 /* scrollbars need updating */
4823 LISTVIEW_UpdateScroll(infoPtr);
4825 /* figure out the item's position */
4826 if (uView == LVS_REPORT)
4827 nPerCol = infoPtr->nItemCount + 1;
4828 else if (uView == LVS_LIST)
4829 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4830 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
4833 nItemCol = nItem / nPerCol;
4834 nItemRow = nItem % nPerCol;
4835 LISTVIEW_GetOrigin(infoPtr, &Origin);
4837 /* move the items below up a slot */
4838 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4839 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4840 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4841 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4842 OffsetRect(&rcScroll, Origin.x, Origin.y);
4843 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
4844 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4846 TRACE("Scrolling rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
4847 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4848 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4851 /* report has only that column, so we're done */
4852 if (uView == LVS_REPORT) return;
4854 /* now for LISTs, we have to deal with the columns to the right */
4855 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4857 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4858 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4859 OffsetRect(&rcScroll, Origin.x, Origin.y);
4860 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4861 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4862 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4867 * Removes an item from the listview control.
4870 * [I] infoPtr : valid pointer to the listview structure
4871 * [I] nItem : item index
4877 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4880 const BOOL is_icon = (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON);
4882 TRACE("(nItem=%d)\n", nItem);
4884 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4886 /* remove selection, and focus */
4888 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4889 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4891 /* send LVN_DELETEITEM notification. */
4892 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
4894 /* we need to do this here, because we'll be deleting stuff */
4896 LISTVIEW_InvalidateItem(infoPtr, nItem);
4898 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4904 hdpaSubItems = DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4905 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4907 hdrItem = DPA_GetPtr(hdpaSubItems, i);
4908 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4911 DPA_Destroy(hdpaSubItems);
4916 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4917 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4920 infoPtr->nItemCount--;
4921 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4923 /* now is the invalidation fun */
4925 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4932 * Callback implementation for editlabel control
4935 * [I] infoPtr : valid pointer to the listview structure
4936 * [I] storeText : store edit box text as item text
4937 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4943 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, BOOL storeText, BOOL isW)
4945 HWND hwndSelf = infoPtr->hwndSelf;
4946 NMLVDISPINFOW dispInfo;
4947 INT editedItem = infoPtr->nEditLabelItem;
4949 WCHAR *pszText = NULL;
4954 DWORD len = isW ? GetWindowTextLengthW(infoPtr->hwndEdit) : GetWindowTextLengthA(infoPtr->hwndEdit);
4958 if ((pszText = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))))
4960 if (isW) GetWindowTextW(infoPtr->hwndEdit, pszText, len+1);
4961 else GetWindowTextA(infoPtr->hwndEdit, (CHAR*)pszText, len+1);
4966 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4968 infoPtr->nEditLabelItem = -1;
4969 infoPtr->hwndEdit = 0;
4971 ZeroMemory(&dispInfo, sizeof(dispInfo));
4972 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4973 dispInfo.item.iItem = editedItem;
4974 dispInfo.item.iSubItem = 0;
4975 dispInfo.item.stateMask = ~0;
4976 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item))
4983 bSame = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
4986 LPWSTR tmp = textdupTtoW(pszText, FALSE);
4987 bSame = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
4988 textfreeT(tmp, FALSE);
4996 /* add the text from the edit in */
4997 dispInfo.item.mask |= LVIF_TEXT;
4998 dispInfo.item.pszText = pszText;
4999 dispInfo.item.cchTextMax = textlenT(pszText, isW);
5001 /* Do we need to update the Item Text */
5002 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW))
5007 if (!IsWindow(hwndSelf))
5012 if (!pszText) return TRUE;
5014 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5016 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, editedItem);
5017 ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
5018 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
5020 LISTVIEW_InvalidateItem(infoPtr, editedItem);
5026 ZeroMemory(&dispInfo, sizeof(dispInfo));
5027 dispInfo.item.mask = LVIF_TEXT;
5028 dispInfo.item.iItem = editedItem;
5029 dispInfo.item.iSubItem = 0;
5030 dispInfo.item.pszText = pszText;
5031 dispInfo.item.cchTextMax = textlenT(pszText, isW);
5032 res = LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
5042 * Begin in place editing of specified list view item
5045 * [I] infoPtr : valid pointer to the listview structure
5046 * [I] nItem : item index
5047 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
5053 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
5055 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
5056 NMLVDISPINFOW dispInfo;
5059 HWND hwndSelf = infoPtr->hwndSelf;
5061 HFONT hOldFont = NULL;
5062 TEXTMETRICW textMetric;
5064 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
5066 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
5068 /* Is the EditBox still there, if so remove it */
5069 if(infoPtr->hwndEdit != 0)
5071 SetFocus(infoPtr->hwndSelf);
5072 infoPtr->hwndEdit = 0;
5075 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5077 infoPtr->nEditLabelItem = nItem;
5079 LISTVIEW_SetSelection(infoPtr, nItem);
5080 LISTVIEW_SetItemFocus(infoPtr, nItem);
5081 LISTVIEW_InvalidateItem(infoPtr, nItem);
5083 rect.left = LVIR_LABEL;
5084 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
5086 ZeroMemory(&dispInfo, sizeof(dispInfo));
5087 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
5088 dispInfo.item.iItem = nItem;
5089 dispInfo.item.iSubItem = 0;
5090 dispInfo.item.stateMask = ~0;
5091 dispInfo.item.pszText = szDispText;
5092 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
5093 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
5095 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE, isW);
5096 if (!infoPtr->hwndEdit) return 0;
5098 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
5100 if (!IsWindow(hwndSelf))
5102 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
5103 infoPtr->hwndEdit = 0;
5107 /* Now position and display edit box */
5108 hdc = GetDC(infoPtr->hwndSelf);
5110 /* Select the font to get appropriate metric dimensions */
5111 if(infoPtr->hFont != 0)
5112 hOldFont = SelectObject(hdc, infoPtr->hFont);
5114 /* Get String Length in pixels */
5115 GetTextExtentPoint32W(hdc, dispInfo.item.pszText, lstrlenW(dispInfo.item.pszText), &sz);
5117 /* Add Extra spacing for the next character */
5118 GetTextMetricsW(hdc, &textMetric);
5119 sz.cx += (textMetric.tmMaxCharWidth * 2);
5121 if(infoPtr->hFont != 0)
5122 SelectObject(hdc, hOldFont);
5124 ReleaseDC(infoPtr->hwndSelf, hdc);
5126 MoveWindow(infoPtr->hwndEdit, rect.left - 2, rect.top - 1, sz.cx,
5127 rect.bottom - rect.top + 2, FALSE);
5128 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
5129 SetFocus(infoPtr->hwndEdit);
5130 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
5131 return infoPtr->hwndEdit;
5137 * Ensures the specified item is visible, scrolling into view if necessary.
5140 * [I] infoPtr : valid pointer to the listview structure
5141 * [I] nItem : item index
5142 * [I] bPartial : partially or entirely visible
5148 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
5150 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5151 INT nScrollPosHeight = 0;
5152 INT nScrollPosWidth = 0;
5153 INT nHorzAdjust = 0;
5154 INT nVertAdjust = 0;
5157 RECT rcItem, rcTemp;
5159 rcItem.left = LVIR_BOUNDS;
5160 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
5162 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
5164 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
5166 /* scroll left/right, but in LVS_REPORT mode */
5167 if (uView == LVS_LIST)
5168 nScrollPosWidth = infoPtr->nItemWidth;
5169 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5170 nScrollPosWidth = 1;
5172 if (rcItem.left < infoPtr->rcList.left)
5175 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
5180 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
5184 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
5186 /* scroll up/down, but not in LVS_LIST mode */
5187 if (uView == LVS_REPORT)
5188 nScrollPosHeight = infoPtr->nItemHeight;
5189 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
5190 nScrollPosHeight = 1;
5192 if (rcItem.top < infoPtr->rcList.top)
5195 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
5200 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
5204 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
5206 if (nScrollPosWidth)
5208 INT diff = nHorzDiff / nScrollPosWidth;
5209 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
5210 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
5213 if (nScrollPosHeight)
5215 INT diff = nVertDiff / nScrollPosHeight;
5216 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
5217 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
5225 * Searches for an item with specific characteristics.
5228 * [I] hwnd : window handle
5229 * [I] nStart : base item index
5230 * [I] lpFindInfo : item information to look for
5233 * SUCCESS : index of item
5236 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
5237 const LVFINDINFOW *lpFindInfo)
5239 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5240 BOOL bWrap = FALSE, bNearest = FALSE;
5241 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
5242 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
5243 POINT Position, Destination;
5246 /* Search in virtual listviews should be done by application, not by
5247 listview control, so we just send LVN_ODFINDITEMW and return the result */
5248 if (infoPtr->dwStyle & LVS_OWNERDATA)
5252 nmlv.iStart = nStart;
5253 nmlv.lvfi = *lpFindInfo;
5254 return notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
5257 if (!lpFindInfo || nItem < 0) return -1;
5260 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
5262 lvItem.mask |= LVIF_TEXT;
5263 lvItem.pszText = szDispText;
5264 lvItem.cchTextMax = DISP_TEXT_SIZE;
5267 if (lpFindInfo->flags & LVFI_WRAP)
5270 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
5271 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON))
5276 LISTVIEW_GetOrigin(infoPtr, &Origin);
5277 Destination.x = lpFindInfo->pt.x - Origin.x;
5278 Destination.y = lpFindInfo->pt.y - Origin.y;
5279 switch(lpFindInfo->vkDirection)
5281 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
5282 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
5283 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
5284 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
5285 case VK_HOME: Destination.x = Destination.y = 0; break;
5286 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5287 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5289 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
5290 Destination.x = rcArea.right;
5291 Destination.y = rcArea.bottom;
5293 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
5297 else Destination.x = Destination.y = 0;
5299 /* if LVFI_PARAM is specified, all other flags are ignored */
5300 if (lpFindInfo->flags & LVFI_PARAM)
5302 lvItem.mask |= LVIF_PARAM;
5304 lvItem.mask &= ~LVIF_TEXT;
5308 for (; nItem < nLast; nItem++)
5310 lvItem.iItem = nItem;
5311 lvItem.iSubItem = 0;
5312 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
5314 if (lvItem.mask & LVIF_PARAM)
5316 if (lpFindInfo->lParam == lvItem.lParam)
5322 if (lvItem.mask & LVIF_TEXT)
5324 if (lpFindInfo->flags & LVFI_PARTIAL)
5326 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
5330 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
5334 if (!bNearest) return nItem;
5336 /* This is very inefficient. To do a good job here,
5337 * we need a sorted array of (x,y) item positions */
5338 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5340 /* compute the distance^2 to the destination */
5341 xdist = Destination.x - Position.x;
5342 ydist = Destination.y - Position.y;
5343 dist = xdist * xdist + ydist * ydist;
5345 /* remember the distance, and item if it's closer */
5349 nNearestItem = nItem;
5356 nLast = min(nStart + 1, infoPtr->nItemCount);
5361 return nNearestItem;
5366 * Searches for an item with specific characteristics.
5369 * [I] hwnd : window handle
5370 * [I] nStart : base item index
5371 * [I] lpFindInfo : item information to look for
5374 * SUCCESS : index of item
5377 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
5378 const LVFINDINFOA *lpFindInfo)
5380 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
5385 memcpy(&fiw, lpFindInfo, sizeof(fiw));
5386 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
5387 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
5388 textfreeT(strW, FALSE);
5394 * Retrieves the background image of the listview control.
5397 * [I] infoPtr : valid pointer to the listview structure
5398 * [O] lpBkImage : background image attributes
5404 /* static BOOL LISTVIEW_GetBkImage(const LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
5406 /* FIXME (listview, "empty stub!\n"); */
5412 * Retrieves column attributes.
5415 * [I] infoPtr : valid pointer to the listview structure
5416 * [I] nColumn : column index
5417 * [IO] lpColumn : column information
5418 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
5419 * otherwise it is in fact a LPLVCOLUMNA
5425 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5427 COLUMN_INFO *lpColumnInfo;
5430 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5431 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
5433 /* initialize memory */
5434 ZeroMemory(&hdi, sizeof(hdi));
5436 if (lpColumn->mask & LVCF_TEXT)
5438 hdi.mask |= HDI_TEXT;
5439 hdi.pszText = lpColumn->pszText;
5440 hdi.cchTextMax = lpColumn->cchTextMax;
5443 if (lpColumn->mask & LVCF_IMAGE)
5444 hdi.mask |= HDI_IMAGE;
5446 if (lpColumn->mask & LVCF_ORDER)
5447 hdi.mask |= HDI_ORDER;
5449 if (lpColumn->mask & LVCF_SUBITEM)
5450 hdi.mask |= HDI_LPARAM;
5452 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
5454 if (lpColumn->mask & LVCF_FMT)
5455 lpColumn->fmt = lpColumnInfo->fmt;
5457 if (lpColumn->mask & LVCF_WIDTH)
5458 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
5460 if (lpColumn->mask & LVCF_IMAGE)
5461 lpColumn->iImage = hdi.iImage;
5463 if (lpColumn->mask & LVCF_ORDER)
5464 lpColumn->iOrder = hdi.iOrder;
5466 if (lpColumn->mask & LVCF_SUBITEM)
5467 lpColumn->iSubItem = hdi.lParam;
5473 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
5475 TRACE("iCount=%d, lpiArray=%p\n", iCount, lpiArray);
5480 return SendMessageW(infoPtr->hwndHeader, HDM_GETORDERARRAY, iCount, (LPARAM)lpiArray);
5485 * Retrieves the column width.
5488 * [I] infoPtr : valid pointer to the listview structure
5489 * [I] int : column index
5492 * SUCCESS : column width
5495 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
5497 INT nColumnWidth = 0;
5500 TRACE("nColumn=%d\n", nColumn);
5502 /* we have a 'column' in LIST and REPORT mode only */
5503 switch(infoPtr->uView)
5506 nColumnWidth = infoPtr->nItemWidth;
5508 case LV_VIEW_DETAILS:
5509 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDM_ITEMCHANGED.
5510 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
5511 * HDM_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
5513 * TODO: should we do the same in LVM_GETCOLUMN?
5515 hdItem.mask = HDI_WIDTH;
5516 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
5518 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
5521 nColumnWidth = hdItem.cxy;
5525 TRACE("nColumnWidth=%d\n", nColumnWidth);
5526 return nColumnWidth;
5531 * In list or report display mode, retrieves the number of items that can fit
5532 * vertically in the visible area. In icon or small icon display mode,
5533 * retrieves the total number of visible items.
5536 * [I] infoPtr : valid pointer to the listview structure
5539 * Number of fully visible items.
5541 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
5543 switch (infoPtr->uView)
5546 case LV_VIEW_SMALLICON:
5547 return infoPtr->nItemCount;
5548 case LV_VIEW_DETAILS:
5549 return LISTVIEW_GetCountPerColumn(infoPtr);
5551 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5559 * Retrieves an image list handle.
5562 * [I] infoPtr : valid pointer to the listview structure
5563 * [I] nImageList : image list identifier
5566 * SUCCESS : image list handle
5569 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
5573 case LVSIL_NORMAL: return infoPtr->himlNormal;
5574 case LVSIL_SMALL: return infoPtr->himlSmall;
5575 case LVSIL_STATE: return infoPtr->himlState;
5580 /* LISTVIEW_GetISearchString */
5584 * Retrieves item attributes.
5587 * [I] hwnd : window handle
5588 * [IO] lpLVItem : item info
5589 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5590 * if FALSE, then lpLVItem is a LPLVITEMA.
5593 * This is the internal 'GetItem' interface -- it tries to
5594 * be smart and avoid text copies, if possible, by modifying
5595 * lpLVItem->pszText to point to the text string. Please note
5596 * that this is not always possible (e.g. OWNERDATA), so on
5597 * entry you *must* supply valid values for pszText, and cchTextMax.
5598 * The only difference to the documented interface is that upon
5599 * return, you should use *only* the lpLVItem->pszText, rather than
5600 * the buffer pointer you provided on input. Most code already does
5601 * that, so it's not a problem.
5602 * For the two cases when the text must be copied (that is,
5603 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5609 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5611 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5612 NMLVDISPINFOW dispInfo;
5618 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5620 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5623 if (lpLVItem->mask == 0) return TRUE;
5625 /* make a local copy */
5626 isubitem = lpLVItem->iSubItem;
5628 /* a quick optimization if all we're asked is the focus state
5629 * these queries are worth optimising since they are common,
5630 * and can be answered in constant time, without the heavy accesses */
5631 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5632 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5634 lpLVItem->state = 0;
5635 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5636 lpLVItem->state |= LVIS_FOCUSED;
5640 ZeroMemory(&dispInfo, sizeof(dispInfo));
5642 /* if the app stores all the data, handle it separately */
5643 if (infoPtr->dwStyle & LVS_OWNERDATA)
5645 dispInfo.item.state = 0;
5647 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
5648 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) ||
5649 ((lpLVItem->mask & LVIF_STATE) && (infoPtr->uCallbackMask & lpLVItem->stateMask)))
5651 UINT mask = lpLVItem->mask;
5653 /* NOTE: copy only fields which we _know_ are initialized, some apps
5654 * depend on the uninitialized fields being 0 */
5655 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5656 dispInfo.item.iItem = lpLVItem->iItem;
5657 dispInfo.item.iSubItem = isubitem;
5658 if (lpLVItem->mask & LVIF_TEXT)
5660 if (lpLVItem->mask & LVIF_NORECOMPUTE)
5662 dispInfo.item.mask &= ~(LVIF_TEXT | LVIF_NORECOMPUTE);
5665 dispInfo.item.pszText = lpLVItem->pszText;
5666 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5669 if (lpLVItem->mask & LVIF_STATE)
5670 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5671 /* could be zeroed on LVIF_NORECOMPUTE case */
5672 if (dispInfo.item.mask != 0)
5674 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5675 dispInfo.item.stateMask = lpLVItem->stateMask;
5676 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5678 /* full size structure expected - _WIN32IE >= 0x560 */
5679 *lpLVItem = dispInfo.item;
5681 else if (lpLVItem->mask & LVIF_INDENT)
5683 /* indent member expected - _WIN32IE >= 0x300 */
5684 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5688 /* minimal structure expected */
5689 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5691 lpLVItem->mask = mask;
5692 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5696 /* make sure lParam is zeroed out */
5697 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5699 /* callback marked pointer required here */
5700 if ((lpLVItem->mask & LVIF_TEXT) && (lpLVItem->mask & LVIF_NORECOMPUTE))
5701 lpLVItem->pszText = LPSTR_TEXTCALLBACKW;
5703 /* we store only a little state, so if we're not asked, we're done */
5704 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5706 /* if focus is handled by us, report it */
5707 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5709 lpLVItem->state &= ~LVIS_FOCUSED;
5710 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5711 lpLVItem->state |= LVIS_FOCUSED;
5714 /* and do the same for selection, if we handle it */
5715 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5717 lpLVItem->state &= ~LVIS_SELECTED;
5718 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5719 lpLVItem->state |= LVIS_SELECTED;
5725 /* find the item and subitem structures before we proceed */
5726 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5727 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5732 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5733 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5736 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5741 pItemHdr = &lpItem->hdr;
5743 /* Do we need to query the state from the app? */
5744 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5746 dispInfo.item.mask |= LVIF_STATE;
5747 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5750 /* Do we need to enquire about the image? */
5751 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5752 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5754 dispInfo.item.mask |= LVIF_IMAGE;
5755 dispInfo.item.iImage = I_IMAGECALLBACK;
5758 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5759 if ((lpLVItem->mask & LVIF_TEXT) && !(lpLVItem->mask & LVIF_NORECOMPUTE) &&
5760 !is_textW(pItemHdr->pszText))
5762 dispInfo.item.mask |= LVIF_TEXT;
5763 dispInfo.item.pszText = lpLVItem->pszText;
5764 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5765 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5766 *dispInfo.item.pszText = '\0';
5769 /* If we don't have all the requested info, query the application */
5770 if (dispInfo.item.mask != 0)
5772 dispInfo.item.iItem = lpLVItem->iItem;
5773 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5774 dispInfo.item.lParam = lpItem->lParam;
5775 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5776 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5779 /* we should not store values for subitems */
5780 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5782 /* Now, handle the iImage field */
5783 if (dispInfo.item.mask & LVIF_IMAGE)
5785 lpLVItem->iImage = dispInfo.item.iImage;
5786 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5787 pItemHdr->iImage = dispInfo.item.iImage;
5789 else if (lpLVItem->mask & LVIF_IMAGE)
5791 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5792 lpLVItem->iImage = pItemHdr->iImage;
5794 lpLVItem->iImage = 0;
5797 /* The pszText field */
5798 if (dispInfo.item.mask & LVIF_TEXT)
5800 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5801 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5803 lpLVItem->pszText = dispInfo.item.pszText;
5805 else if (lpLVItem->mask & LVIF_TEXT)
5807 /* if LVN_GETDISPINFO's disabled with LVIF_NORECOMPUTE return callback placeholder */
5808 if (isW || !is_textW(pItemHdr->pszText)) lpLVItem->pszText = pItemHdr->pszText;
5809 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5812 /* Next is the lParam field */
5813 if (dispInfo.item.mask & LVIF_PARAM)
5815 lpLVItem->lParam = dispInfo.item.lParam;
5816 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5817 lpItem->lParam = dispInfo.item.lParam;
5819 else if (lpLVItem->mask & LVIF_PARAM)
5820 lpLVItem->lParam = lpItem->lParam;
5822 /* if this is a subitem, we're done */
5823 if (isubitem) return TRUE;
5825 /* ... the state field (this one is different due to uCallbackmask) */
5826 if (lpLVItem->mask & LVIF_STATE)
5828 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
5829 if (dispInfo.item.mask & LVIF_STATE)
5831 lpLVItem->state &= ~dispInfo.item.stateMask;
5832 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5834 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5836 lpLVItem->state &= ~LVIS_FOCUSED;
5837 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5838 lpLVItem->state |= LVIS_FOCUSED;
5840 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5842 lpLVItem->state &= ~LVIS_SELECTED;
5843 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5844 lpLVItem->state |= LVIS_SELECTED;
5848 /* and last, but not least, the indent field */
5849 if (lpLVItem->mask & LVIF_INDENT)
5850 lpLVItem->iIndent = lpItem->iIndent;
5857 * Retrieves item attributes.
5860 * [I] hwnd : window handle
5861 * [IO] lpLVItem : item info
5862 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5863 * if FALSE, then lpLVItem is a LPLVITEMA.
5866 * This is the external 'GetItem' interface -- it properly copies
5867 * the text in the provided buffer.
5873 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5878 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5881 pszText = lpLVItem->pszText;
5882 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5883 if (bResult && lpLVItem->pszText != pszText)
5885 if (lpLVItem->pszText != LPSTR_TEXTCALLBACKW)
5886 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5888 pszText = LPSTR_TEXTCALLBACKW;
5890 lpLVItem->pszText = pszText;
5898 * Retrieves the position (upper-left) of the listview control item.
5899 * Note that for LVS_ICON style, the upper-left is that of the icon
5900 * and not the bounding box.
5903 * [I] infoPtr : valid pointer to the listview structure
5904 * [I] nItem : item index
5905 * [O] lpptPosition : coordinate information
5911 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5915 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5917 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5919 LISTVIEW_GetOrigin(infoPtr, &Origin);
5920 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5922 if (infoPtr->uView == LV_VIEW_ICON)
5924 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5925 lpptPosition->y += ICON_TOP_PADDING;
5927 lpptPosition->x += Origin.x;
5928 lpptPosition->y += Origin.y;
5930 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
5937 * Retrieves the bounding rectangle for a listview control item.
5940 * [I] infoPtr : valid pointer to the listview structure
5941 * [I] nItem : item index
5942 * [IO] lprc : bounding rectangle coordinates
5943 * lprc->left specifies the portion of the item for which the bounding
5944 * rectangle will be retrieved.
5946 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5947 * including the icon and label.
5950 * * Experiment shows that native control returns:
5951 * * width = min (48, length of text line)
5952 * * .left = position.x - (width - iconsize.cx)/2
5953 * * .right = .left + width
5954 * * height = #lines of text * ntmHeight + icon height + 8
5955 * * .top = position.y - 2
5956 * * .bottom = .top + height
5957 * * separation between items .y = itemSpacing.cy - height
5958 * * .x = itemSpacing.cx - width
5959 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5962 * * Experiment shows that native control returns:
5963 * * width = iconSize.cx + 16
5964 * * .left = position.x - (width - iconsize.cx)/2
5965 * * .right = .left + width
5966 * * height = iconSize.cy + 4
5967 * * .top = position.y - 2
5968 * * .bottom = .top + height
5969 * * separation between items .y = itemSpacing.cy - height
5970 * * .x = itemSpacing.cx - width
5971 * LVIR_LABEL Returns the bounding rectangle of the item text.
5974 * * Experiment shows that native control returns:
5975 * * width = text length
5976 * * .left = position.x - width/2
5977 * * .right = .left + width
5978 * * height = ntmH * linecount + 2
5979 * * .top = position.y + iconSize.cy + 6
5980 * * .bottom = .top + height
5981 * * separation between items .y = itemSpacing.cy - height
5982 * * .x = itemSpacing.cx - width
5983 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5984 * rectangles, but excludes columns in report view.
5991 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5992 * upon whether the window has the focus currently and on whether the item
5993 * is the one with the focus. Ensure that the control's record of which
5994 * item has the focus agrees with the items' records.
5996 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5998 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5999 BOOL doLabel = TRUE, oversizedBox = FALSE;
6000 POINT Position, Origin;
6003 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
6005 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
6007 LISTVIEW_GetOrigin(infoPtr, &Origin);
6008 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
6010 /* Be smart and try to figure out the minimum we have to do */
6011 if (lprc->left == LVIR_ICON) doLabel = FALSE;
6012 if (infoPtr->uView == LV_VIEW_DETAILS && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
6013 if (infoPtr->uView == LV_VIEW_ICON && lprc->left != LVIR_ICON &&
6014 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
6015 oversizedBox = TRUE;
6017 /* get what we need from the item before hand, so we make
6018 * only one request. This can speed up things, if data
6019 * is stored on the app side */
6021 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
6022 if (doLabel) lvItem.mask |= LVIF_TEXT;
6023 lvItem.iItem = nItem;
6024 lvItem.iSubItem = 0;
6025 lvItem.pszText = szDispText;
6026 lvItem.cchTextMax = DISP_TEXT_SIZE;
6027 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
6028 /* we got the state already up, simulate it here, to avoid a reget */
6029 if (infoPtr->uView == LV_VIEW_ICON && (lprc->left != LVIR_ICON))
6031 lvItem.mask |= LVIF_STATE;
6032 lvItem.stateMask = LVIS_FOCUSED;
6033 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
6036 if (infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
6037 lprc->left = LVIR_BOUNDS;
6041 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
6045 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
6049 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
6052 case LVIR_SELECTBOUNDS:
6053 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
6057 WARN("Unknown value: %d\n", lprc->left);
6061 if (infoPtr->uView == LV_VIEW_DETAILS)
6062 OffsetRect(lprc, Origin.x, Position.y + Origin.y);
6064 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
6066 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
6073 * Retrieves the spacing between listview control items.
6076 * [I] infoPtr : valid pointer to the listview structure
6077 * [IO] lprc : rectangle to receive the output
6078 * on input, lprc->top = nSubItem
6079 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
6081 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
6082 * not only those of the first column.
6083 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
6089 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
6095 if (!lprc) return FALSE;
6097 nColumn = lprc->top;
6099 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
6100 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
6102 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
6104 if (infoPtr->uView != LV_VIEW_DETAILS) return FALSE;
6106 /* special case for header items */
6109 if (lprc->left != LVIR_BOUNDS)
6111 FIXME("Only LVIR_BOUNDS is implemented for header, got %d\n", lprc->left);
6115 if (infoPtr->hwndHeader)
6116 return SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, lprc->top, (LPARAM)lprc);
6119 memset(lprc, 0, sizeof(RECT));
6124 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
6126 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6129 lvItem.iItem = nItem;
6130 lvItem.iSubItem = nColumn;
6132 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
6136 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
6141 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
6145 ERR("Unknown bounds=%d\n", lprc->left);
6149 OffsetRect(lprc, 0, Position.y);
6156 * Retrieves the width of a label.
6159 * [I] infoPtr : valid pointer to the listview structure
6162 * SUCCESS : string width (in pixels)
6165 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *infoPtr, INT nItem)
6167 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6170 TRACE("(nItem=%d)\n", nItem);
6172 lvItem.mask = LVIF_TEXT;
6173 lvItem.iItem = nItem;
6174 lvItem.iSubItem = 0;
6175 lvItem.pszText = szDispText;
6176 lvItem.cchTextMax = DISP_TEXT_SIZE;
6177 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6179 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6184 * Retrieves the spacing between listview control items.
6187 * [I] infoPtr : valid pointer to the listview structure
6188 * [I] bSmall : flag for small or large icon
6191 * Horizontal + vertical spacing
6193 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
6199 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6203 if (infoPtr->uView == LV_VIEW_ICON)
6204 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
6206 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
6213 * Retrieves the state of a listview control item.
6216 * [I] infoPtr : valid pointer to the listview structure
6217 * [I] nItem : item index
6218 * [I] uMask : state mask
6221 * State specified by the mask.
6223 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
6227 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6229 lvItem.iItem = nItem;
6230 lvItem.iSubItem = 0;
6231 lvItem.mask = LVIF_STATE;
6232 lvItem.stateMask = uMask;
6233 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6235 return lvItem.state & uMask;
6240 * Retrieves the text of a listview control item or subitem.
6243 * [I] hwnd : window handle
6244 * [I] nItem : item index
6245 * [IO] lpLVItem : item information
6246 * [I] isW : TRUE if lpLVItem is Unicode
6249 * SUCCESS : string length
6252 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
6254 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6256 lpLVItem->mask = LVIF_TEXT;
6257 lpLVItem->iItem = nItem;
6258 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
6260 return textlenT(lpLVItem->pszText, isW);
6265 * Searches for an item based on properties + relationships.
6268 * [I] infoPtr : valid pointer to the listview structure
6269 * [I] nItem : item index
6270 * [I] uFlags : relationship flag
6273 * SUCCESS : item index
6276 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
6279 LVFINDINFOW lvFindInfo;
6280 INT nCountPerColumn;
6284 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
6285 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
6287 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
6289 if (uFlags & LVNI_CUT)
6292 if (uFlags & LVNI_DROPHILITED)
6293 uMask |= LVIS_DROPHILITED;
6295 if (uFlags & LVNI_FOCUSED)
6296 uMask |= LVIS_FOCUSED;
6298 if (uFlags & LVNI_SELECTED)
6299 uMask |= LVIS_SELECTED;
6301 /* if we're asked for the focused item, that's only one,
6302 * so it's worth optimizing */
6303 if (uFlags & LVNI_FOCUSED)
6305 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
6306 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
6309 if (uFlags & LVNI_ABOVE)
6311 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
6316 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6322 /* Special case for autoarrange - move 'til the top of a list */
6323 if (is_autoarrange(infoPtr))
6325 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6326 while (nItem - nCountPerRow >= 0)
6328 nItem -= nCountPerRow;
6329 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6334 lvFindInfo.flags = LVFI_NEARESTXY;
6335 lvFindInfo.vkDirection = VK_UP;
6336 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6337 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6339 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6344 else if (uFlags & LVNI_BELOW)
6346 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
6348 while (nItem < infoPtr->nItemCount)
6351 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6357 /* Special case for autoarrange - move 'til the bottom of a list */
6358 if (is_autoarrange(infoPtr))
6360 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6361 while (nItem + nCountPerRow < infoPtr->nItemCount )
6363 nItem += nCountPerRow;
6364 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6369 lvFindInfo.flags = LVFI_NEARESTXY;
6370 lvFindInfo.vkDirection = VK_DOWN;
6371 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6372 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6374 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6379 else if (uFlags & LVNI_TOLEFT)
6381 if (infoPtr->uView == LV_VIEW_LIST)
6383 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6384 while (nItem - nCountPerColumn >= 0)
6386 nItem -= nCountPerColumn;
6387 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6391 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
6393 /* Special case for autoarrange - move 'til the beginning of a row */
6394 if (is_autoarrange(infoPtr))
6396 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6397 while (nItem % nCountPerRow > 0)
6400 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6405 lvFindInfo.flags = LVFI_NEARESTXY;
6406 lvFindInfo.vkDirection = VK_LEFT;
6407 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6408 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6410 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6415 else if (uFlags & LVNI_TORIGHT)
6417 if (infoPtr->uView == LV_VIEW_LIST)
6419 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6420 while (nItem + nCountPerColumn < infoPtr->nItemCount)
6422 nItem += nCountPerColumn;
6423 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6427 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
6429 /* Special case for autoarrange - move 'til the end of a row */
6430 if (is_autoarrange(infoPtr))
6432 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6433 while (nItem % nCountPerRow < nCountPerRow - 1 )
6436 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6441 lvFindInfo.flags = LVFI_NEARESTXY;
6442 lvFindInfo.vkDirection = VK_RIGHT;
6443 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6444 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6446 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6455 /* search by index */
6456 for (i = nItem; i < infoPtr->nItemCount; i++)
6458 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
6466 /* LISTVIEW_GetNumberOfWorkAreas */
6470 * Retrieves the origin coordinates when in icon or small icon display mode.
6473 * [I] infoPtr : valid pointer to the listview structure
6474 * [O] lpptOrigin : coordinate information
6479 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
6481 INT nHorzPos = 0, nVertPos = 0;
6482 SCROLLINFO scrollInfo;
6484 scrollInfo.cbSize = sizeof(SCROLLINFO);
6485 scrollInfo.fMask = SIF_POS;
6487 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
6488 nHorzPos = scrollInfo.nPos;
6489 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
6490 nVertPos = scrollInfo.nPos;
6492 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
6494 lpptOrigin->x = infoPtr->rcList.left;
6495 lpptOrigin->y = infoPtr->rcList.top;
6496 if (infoPtr->uView == LV_VIEW_LIST)
6497 nHorzPos *= infoPtr->nItemWidth;
6498 else if (infoPtr->uView == LV_VIEW_DETAILS)
6499 nVertPos *= infoPtr->nItemHeight;
6501 lpptOrigin->x -= nHorzPos;
6502 lpptOrigin->y -= nVertPos;
6504 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
6509 * Retrieves the width of a string.
6512 * [I] hwnd : window handle
6513 * [I] lpszText : text string to process
6514 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
6517 * SUCCESS : string width (in pixels)
6520 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
6525 if (is_textT(lpszText, isW))
6527 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
6528 HDC hdc = GetDC(infoPtr->hwndSelf);
6529 HFONT hOldFont = SelectObject(hdc, hFont);
6532 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
6534 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
6535 SelectObject(hdc, hOldFont);
6536 ReleaseDC(infoPtr->hwndSelf, hdc);
6538 return stringSize.cx;
6543 * Determines which listview item is located at the specified position.
6546 * [I] infoPtr : valid pointer to the listview structure
6547 * [IO] lpht : hit test information
6548 * [I] subitem : fill out iSubItem.
6549 * [I] select : return the index only if the hit selects the item
6552 * (mm 20001022): We must not allow iSubItem to be touched, for
6553 * an app might pass only a structure with space up to iItem!
6554 * (MS Office 97 does that for instance in the file open dialog)
6557 * SUCCESS : item index
6560 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6562 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6563 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6564 POINT Origin, Position, opt;
6569 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
6573 if (subitem) lpht->iSubItem = 0;
6575 if (infoPtr->rcList.left > lpht->pt.x)
6576 lpht->flags |= LVHT_TOLEFT;
6577 else if (infoPtr->rcList.right < lpht->pt.x)
6578 lpht->flags |= LVHT_TORIGHT;
6580 if (infoPtr->rcList.top > lpht->pt.y)
6581 lpht->flags |= LVHT_ABOVE;
6582 else if (infoPtr->rcList.bottom < lpht->pt.y)
6583 lpht->flags |= LVHT_BELOW;
6585 TRACE("lpht->flags=0x%x\n", lpht->flags);
6586 if (lpht->flags) return -1;
6588 lpht->flags |= LVHT_NOWHERE;
6590 LISTVIEW_GetOrigin(infoPtr, &Origin);
6592 /* first deal with the large items */
6593 rcSearch.left = lpht->pt.x;
6594 rcSearch.top = lpht->pt.y;
6595 rcSearch.right = rcSearch.left + 1;
6596 rcSearch.bottom = rcSearch.top + 1;
6598 iterator_frameditems(&i, infoPtr, &rcSearch);
6599 iterator_next(&i); /* go to first item in the sequence */
6601 iterator_destroy(&i);
6603 TRACE("lpht->iItem=%d\n", iItem);
6604 if (iItem == -1) return -1;
6606 if (infoPtr->uView == LV_VIEW_DETAILS && subitem)
6608 RECT bounds, *pRect;
6611 /* for top/bottom only */
6612 bounds.left = LVIR_BOUNDS;
6613 LISTVIEW_GetItemRect(infoPtr, iItem, &bounds);
6615 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6617 pRect = &LISTVIEW_GetColumnInfo(infoPtr, j)->rcHeader;
6618 bounds.left = pRect->left;
6619 bounds.right = pRect->right;
6621 if (PtInRect(&bounds, lpht->pt))
6627 TRACE("lpht->iSubItem=%d\n", lpht->iSubItem);
6630 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6631 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
6632 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6633 if (infoPtr->uView == LV_VIEW_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6634 lvItem.iItem = iItem;
6635 lvItem.iSubItem = subitem ? lpht->iSubItem : 0;
6636 lvItem.pszText = szDispText;
6637 lvItem.cchTextMax = DISP_TEXT_SIZE;
6638 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6639 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6641 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6642 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6643 opt.x = lpht->pt.x - Position.x - Origin.x;
6644 opt.y = lpht->pt.y - Position.y - Origin.y;
6646 if (infoPtr->uView == LV_VIEW_DETAILS)
6650 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6651 UnionRect(&rcBounds, &rcBounds, &rcState);
6653 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
6654 if (!PtInRect(&rcBounds, opt)) return -1;
6656 if (PtInRect(&rcIcon, opt))
6657 lpht->flags |= LVHT_ONITEMICON;
6658 else if (PtInRect(&rcLabel, opt))
6659 lpht->flags |= LVHT_ONITEMLABEL;
6660 else if (infoPtr->himlState && PtInRect(&rcState, opt))
6661 lpht->flags |= LVHT_ONITEMSTATEICON;
6662 /* special case for LVS_EX_FULLROWSELECT */
6663 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
6664 !(lpht->flags & LVHT_ONITEM))
6666 lpht->flags = LVHT_ONITEM | LVHT_ABOVE;
6668 if (lpht->flags & LVHT_ONITEM)
6669 lpht->flags &= ~LVHT_NOWHERE;
6670 TRACE("lpht->flags=0x%x\n", lpht->flags);
6672 if (select && !(infoPtr->uView == LV_VIEW_DETAILS &&
6673 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6674 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6676 if (infoPtr->uView == LV_VIEW_DETAILS)
6678 /* get main item bounds */
6679 lvItem.iSubItem = 0;
6680 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6681 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6682 UnionRect(&rcBounds, &rcBounds, &rcState);
6684 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6686 return lpht->iItem = iItem;
6691 * Inserts a new item in the listview control.
6694 * [I] infoPtr : valid pointer to the listview structure
6695 * [I] lpLVItem : item information
6696 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6699 * SUCCESS : new item index
6702 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6704 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6709 BOOL is_sorted, has_changed;
6711 HWND hwndSelf = infoPtr->hwndSelf;
6713 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6715 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6717 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6718 if (!lpLVItem || lpLVItem->iSubItem) return -1;
6720 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6722 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
6724 /* insert item in listview control data structure */
6725 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6726 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6728 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6729 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6731 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
6733 /* calculate new item index */
6740 while (i < infoPtr->nItemCount)
6742 hItem = DPA_GetPtr( infoPtr->hdpaItems, i);
6743 item_s = (ITEM_INFO*)DPA_GetPtr(hItem, 0);
6745 cmpv = textcmpWT(item_s->hdr.pszText, lpLVItem->pszText, TRUE);
6746 if (infoPtr->dwStyle & LVS_SORTDESCENDING) cmpv *= -1;
6748 if (cmpv >= 0) break;
6754 nItem = min(lpLVItem->iItem, infoPtr->nItemCount);
6756 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6757 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6758 if (nItem == -1) goto fail;
6759 infoPtr->nItemCount++;
6761 /* shift indices first so they don't get tangled */
6762 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6764 /* set the item attributes */
6765 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6767 /* full size structure expected - _WIN32IE >= 0x560 */
6770 else if (lpLVItem->mask & LVIF_INDENT)
6772 /* indent member expected - _WIN32IE >= 0x300 */
6773 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6777 /* minimal structure expected */
6778 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6781 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6783 item.mask |= LVIF_STATE;
6784 item.stateMask |= LVIS_STATEIMAGEMASK;
6785 item.state &= ~LVIS_STATEIMAGEMASK;
6786 item.state |= INDEXTOSTATEIMAGEMASK(1);
6788 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6790 /* make room for the position, if we are in the right mode */
6791 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6793 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6795 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6797 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6802 /* send LVN_INSERTITEM notification */
6803 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6805 nmlv.lParam = lpItem->lParam;
6806 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6807 if (!IsWindow(hwndSelf))
6810 /* align items (set position of each item) */
6811 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6815 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6816 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6818 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6820 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6823 /* now is the invalidation fun */
6824 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6828 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6829 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6830 infoPtr->nItemCount--;
6832 DPA_DeletePtr(hdpaSubItems, 0);
6833 DPA_Destroy (hdpaSubItems);
6840 * Redraws a range of items.
6843 * [I] infoPtr : valid pointer to the listview structure
6844 * [I] nFirst : first item
6845 * [I] nLast : last item
6851 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6855 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6856 max(nFirst, nLast) >= infoPtr->nItemCount)
6859 for (i = nFirst; i <= nLast; i++)
6860 LISTVIEW_InvalidateItem(infoPtr, i);
6867 * Scroll the content of a listview.
6870 * [I] infoPtr : valid pointer to the listview structure
6871 * [I] dx : horizontal scroll amount in pixels
6872 * [I] dy : vertical scroll amount in pixels
6879 * If the control is in report view (LV_VIEW_DETAILS) the control can
6880 * be scrolled only in line increments. "dy" will be rounded to the
6881 * nearest number of pixels that are a whole line. Ex: if line height
6882 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6883 * is passed, then the scroll will be 0. (per MSDN 7/2002)
6885 * For: (per experimentation with native control and CSpy ListView)
6886 * LV_VIEW_ICON dy=1 = 1 pixel (vertical only)
6888 * LV_VIEW_SMALLICON dy=1 = 1 pixel (vertical only)
6890 * LV_VIEW_LIST dx=1 = 1 column (horizontal only)
6891 * but will only scroll 1 column per message
6892 * no matter what the value.
6893 * dy must be 0 or FALSE returned.
6894 * LV_VIEW_DETAILS dx=1 = 1 pixel
6898 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6900 switch(infoPtr->uView) {
6901 case LV_VIEW_DETAILS:
6902 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6903 dy /= infoPtr->nItemHeight;
6906 if (dy != 0) return FALSE;
6913 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6914 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6921 * Sets the background color.
6924 * [I] infoPtr : valid pointer to the listview structure
6925 * [I] clrBk : background color
6931 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6933 TRACE("(clrBk=%x)\n", clrBk);
6935 if(infoPtr->clrBk != clrBk) {
6936 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6937 infoPtr->clrBk = clrBk;
6938 if (clrBk == CLR_NONE)
6939 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6941 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6942 LISTVIEW_InvalidateList(infoPtr);
6948 /* LISTVIEW_SetBkImage */
6950 /*** Helper for {Insert,Set}ColumnT *only* */
6951 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
6952 const LVCOLUMNW *lpColumn, BOOL isW)
6954 if (lpColumn->mask & LVCF_FMT)
6956 /* format member is valid */
6957 lphdi->mask |= HDI_FORMAT;
6959 /* set text alignment (leftmost column must be left-aligned) */
6960 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6961 lphdi->fmt |= HDF_LEFT;
6962 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6963 lphdi->fmt |= HDF_RIGHT;
6964 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6965 lphdi->fmt |= HDF_CENTER;
6967 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6968 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6970 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6972 lphdi->fmt |= HDF_IMAGE;
6973 lphdi->iImage = I_IMAGECALLBACK;
6977 if (lpColumn->mask & LVCF_WIDTH)
6979 lphdi->mask |= HDI_WIDTH;
6980 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6982 /* make it fill the remainder of the controls width */
6986 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6988 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6989 lphdi->cxy += rcHeader.right - rcHeader.left;
6992 /* retrieve the layout of the header */
6993 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6994 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
6996 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6999 lphdi->cxy = lpColumn->cx;
7002 if (lpColumn->mask & LVCF_TEXT)
7004 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
7005 lphdi->fmt |= HDF_STRING;
7006 lphdi->pszText = lpColumn->pszText;
7007 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
7010 if (lpColumn->mask & LVCF_IMAGE)
7012 lphdi->mask |= HDI_IMAGE;
7013 lphdi->iImage = lpColumn->iImage;
7016 if (lpColumn->mask & LVCF_ORDER)
7018 lphdi->mask |= HDI_ORDER;
7019 lphdi->iOrder = lpColumn->iOrder;
7026 * Inserts a new column.
7029 * [I] infoPtr : valid pointer to the listview structure
7030 * [I] nColumn : column index
7031 * [I] lpColumn : column information
7032 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
7035 * SUCCESS : new column index
7038 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
7039 const LVCOLUMNW *lpColumn, BOOL isW)
7041 COLUMN_INFO *lpColumnInfo;
7045 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
7047 if (!lpColumn || nColumn < 0) return -1;
7048 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
7050 ZeroMemory(&hdi, sizeof(HDITEMW));
7051 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
7054 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
7055 * (can be seen in SPY) otherwise column never gets added.
7057 if (!(lpColumn->mask & LVCF_WIDTH)) {
7058 hdi.mask |= HDI_WIDTH;
7063 * when the iSubItem is available Windows copies it to the header lParam. It seems
7064 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
7066 if (lpColumn->mask & LVCF_SUBITEM)
7068 hdi.mask |= HDI_LPARAM;
7069 hdi.lParam = lpColumn->iSubItem;
7072 /* create header if not present */
7073 LISTVIEW_CreateHeader(infoPtr);
7074 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle) &&
7075 (infoPtr->uView == LV_VIEW_DETAILS) && (WS_VISIBLE & infoPtr->dwStyle))
7077 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
7080 /* insert item in header control */
7081 nNewColumn = SendMessageW(infoPtr->hwndHeader,
7082 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
7083 (WPARAM)nColumn, (LPARAM)&hdi);
7084 if (nNewColumn == -1) return -1;
7085 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
7087 /* create our own column info */
7088 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
7089 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
7091 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
7092 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, nNewColumn, (LPARAM)&lpColumnInfo->rcHeader))
7095 /* now we have to actually adjust the data */
7096 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
7098 SUBITEM_INFO *lpSubItem;
7102 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
7104 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
7105 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
7107 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
7108 if (lpSubItem->iSubItem >= nNewColumn)
7109 lpSubItem->iSubItem++;
7114 /* make space for the new column */
7115 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
7116 LISTVIEW_UpdateItemSize(infoPtr);
7121 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
7124 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
7132 * Sets the attributes of a header item.
7135 * [I] infoPtr : valid pointer to the listview structure
7136 * [I] nColumn : column index
7137 * [I] lpColumn : column attributes
7138 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
7144 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
7145 const LVCOLUMNW *lpColumn, BOOL isW)
7147 HDITEMW hdi, hdiget;
7150 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
7152 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7154 ZeroMemory(&hdi, sizeof(HDITEMW));
7155 if (lpColumn->mask & LVCF_FMT)
7157 hdi.mask |= HDI_FORMAT;
7158 hdiget.mask = HDI_FORMAT;
7159 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
7160 hdi.fmt = hdiget.fmt & HDF_STRING;
7162 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
7164 /* set header item attributes */
7165 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
7166 if (!bResult) return FALSE;
7168 if (lpColumn->mask & LVCF_FMT)
7170 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
7171 int oldFmt = lpColumnInfo->fmt;
7173 lpColumnInfo->fmt = lpColumn->fmt;
7174 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
7176 if (infoPtr->uView == LV_VIEW_DETAILS) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
7185 * Sets the column order array
7188 * [I] infoPtr : valid pointer to the listview structure
7189 * [I] iCount : number of elements in column order array
7190 * [I] lpiArray : pointer to column order array
7196 static BOOL LISTVIEW_SetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
7198 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
7209 * Sets the width of a column
7212 * [I] infoPtr : valid pointer to the listview structure
7213 * [I] nColumn : column index
7214 * [I] cx : column width
7220 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
7222 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
7226 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
7228 /* set column width only if in report or list mode */
7229 if (infoPtr->uView != LV_VIEW_DETAILS && infoPtr->uView != LV_VIEW_LIST) return FALSE;
7231 /* take care of invalid cx values */
7232 if(infoPtr->uView == LV_VIEW_DETAILS && cx < -2) cx = LVSCW_AUTOSIZE;
7233 else if (infoPtr->uView == LV_VIEW_LIST && cx < 1) return FALSE;
7235 /* resize all columns if in LV_VIEW_LIST mode */
7236 if(infoPtr->uView == LV_VIEW_LIST)
7238 infoPtr->nItemWidth = cx;
7239 LISTVIEW_InvalidateList(infoPtr);
7243 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7245 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
7250 lvItem.mask = LVIF_TEXT;
7252 lvItem.iSubItem = nColumn;
7253 lvItem.pszText = szDispText;
7254 lvItem.cchTextMax = DISP_TEXT_SIZE;
7255 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7257 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
7258 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
7259 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
7261 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
7262 max_cx += infoPtr->iconSize.cx;
7263 max_cx += TRAILING_LABEL_PADDING;
7266 /* autosize based on listview items width */
7267 if(cx == LVSCW_AUTOSIZE)
7269 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
7271 /* if iCol is the last column make it fill the remainder of the controls width */
7272 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
7277 LISTVIEW_GetOrigin(infoPtr, &Origin);
7278 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
7280 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
7284 /* Despite what the MS docs say, if this is not the last
7285 column, then MS resizes the column to the width of the
7286 largest text string in the column, including headers
7287 and items. This is different from LVSCW_AUTOSIZE in that
7288 LVSCW_AUTOSIZE ignores the header string length. */
7291 /* retrieve header text */
7292 hdi.mask = HDI_TEXT;
7293 hdi.cchTextMax = DISP_TEXT_SIZE;
7294 hdi.pszText = szDispText;
7295 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdi))
7297 HDC hdc = GetDC(infoPtr->hwndSelf);
7298 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
7301 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
7302 cx = size.cx + TRAILING_HEADER_PADDING;
7303 /* FIXME: Take into account the header image, if one is present */
7304 SelectObject(hdc, old_font);
7305 ReleaseDC(infoPtr->hwndSelf, hdc);
7307 cx = max (cx, max_cx);
7311 if (cx < 0) return FALSE;
7313 /* call header to update the column change */
7314 hdi.mask = HDI_WIDTH;
7316 TRACE("hdi.cxy=%d\n", hdi.cxy);
7317 return Header_SetItemW(infoPtr->hwndHeader, nColumn, &hdi);
7321 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
7324 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
7327 HBITMAP hbm_im, hbm_mask, hbm_orig;
7329 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
7330 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
7333 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
7334 ILC_COLOR | ILC_MASK, 2, 2);
7335 hdc_wnd = GetDC(infoPtr->hwndSelf);
7336 hdc = CreateCompatibleDC(hdc_wnd);
7337 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
7338 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
7339 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
7341 rc.left = rc.top = 0;
7342 rc.right = GetSystemMetrics(SM_CXSMICON);
7343 rc.bottom = GetSystemMetrics(SM_CYSMICON);
7345 hbm_orig = SelectObject(hdc, hbm_mask);
7346 FillRect(hdc, &rc, hbr_white);
7347 InflateRect(&rc, -2, -2);
7348 FillRect(hdc, &rc, hbr_black);
7350 SelectObject(hdc, hbm_im);
7351 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
7352 SelectObject(hdc, hbm_orig);
7353 ImageList_Add(himl, hbm_im, hbm_mask);
7355 SelectObject(hdc, hbm_im);
7356 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
7357 SelectObject(hdc, hbm_orig);
7358 ImageList_Add(himl, hbm_im, hbm_mask);
7360 DeleteObject(hbm_mask);
7361 DeleteObject(hbm_im);
7369 * Sets the extended listview style.
7372 * [I] infoPtr : valid pointer to the listview structure
7374 * [I] dwStyle : style
7377 * SUCCESS : previous style
7380 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwExStyle)
7382 DWORD dwOldExStyle = infoPtr->dwLvExStyle;
7386 infoPtr->dwLvExStyle = (dwOldExStyle & ~dwMask) | (dwExStyle & dwMask);
7388 infoPtr->dwLvExStyle = dwExStyle;
7390 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_CHECKBOXES)
7392 HIMAGELIST himl = 0;
7393 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7396 item.mask = LVIF_STATE;
7397 item.stateMask = LVIS_STATEIMAGEMASK;
7398 item.state = INDEXTOSTATEIMAGEMASK(1);
7399 LISTVIEW_SetItemState(infoPtr, -1, &item);
7401 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
7403 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
7406 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_HEADERDRAGDROP)
7410 /* if not already created */
7411 LISTVIEW_CreateHeader(infoPtr);
7413 dwStyle = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
7414 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
7415 dwStyle |= HDS_DRAGDROP;
7417 dwStyle &= ~HDS_DRAGDROP;
7418 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, dwStyle);
7421 /* GRIDLINES adds decoration at top so changes sizes */
7422 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_GRIDLINES)
7424 LISTVIEW_UpdateSize(infoPtr);
7428 LISTVIEW_InvalidateList(infoPtr);
7429 return dwOldExStyle;
7434 * Sets the new hot cursor used during hot tracking and hover selection.
7437 * [I] infoPtr : valid pointer to the listview structure
7438 * [I] hCursor : the new hot cursor handle
7441 * Returns the previous hot cursor
7443 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
7445 HCURSOR oldCursor = infoPtr->hHotCursor;
7447 infoPtr->hHotCursor = hCursor;
7455 * Sets the hot item index.
7458 * [I] infoPtr : valid pointer to the listview structure
7459 * [I] iIndex : index
7462 * SUCCESS : previous hot item index
7463 * FAILURE : -1 (no hot item)
7465 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
7467 INT iOldIndex = infoPtr->nHotItem;
7469 infoPtr->nHotItem = iIndex;
7477 * Sets the amount of time the cursor must hover over an item before it is selected.
7480 * [I] infoPtr : valid pointer to the listview structure
7481 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
7484 * Returns the previous hover time
7486 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
7488 DWORD oldHoverTime = infoPtr->dwHoverTime;
7490 infoPtr->dwHoverTime = dwHoverTime;
7492 return oldHoverTime;
7497 * Sets spacing for icons of LVS_ICON style.
7500 * [I] infoPtr : valid pointer to the listview structure
7501 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
7502 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
7505 * MAKELONG(oldcx, oldcy)
7507 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
7509 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7511 TRACE("requested=(%d,%d)\n", cx, cy);
7513 /* this is supported only for LVS_ICON style */
7514 if (infoPtr->uView != LV_VIEW_ICON) return oldspacing;
7516 /* set to defaults, if instructed to */
7517 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
7518 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
7520 /* if 0 then compute width
7521 * FIXME: Should scan each item and determine max width of
7522 * icon or label, then make that the width */
7524 cx = infoPtr->iconSpacing.cx;
7526 /* if 0 then compute height */
7528 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
7529 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
7532 infoPtr->iconSpacing.cx = cx;
7533 infoPtr->iconSpacing.cy = cy;
7535 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
7536 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
7537 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
7538 infoPtr->ntmHeight);
7540 /* these depend on the iconSpacing */
7541 LISTVIEW_UpdateItemSize(infoPtr);
7546 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
7550 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
7557 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
7558 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
7567 * [I] infoPtr : valid pointer to the listview structure
7568 * [I] nType : image list type
7569 * [I] himl : image list handle
7572 * SUCCESS : old image list
7575 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
7577 INT oldHeight = infoPtr->nItemHeight;
7578 HIMAGELIST himlOld = 0;
7580 TRACE("(nType=%d, himl=%p\n", nType, himl);
7585 himlOld = infoPtr->himlNormal;
7586 infoPtr->himlNormal = himl;
7587 if (infoPtr->uView == LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
7588 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
7592 himlOld = infoPtr->himlSmall;
7593 infoPtr->himlSmall = himl;
7594 if (infoPtr->uView != LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
7598 himlOld = infoPtr->himlState;
7599 infoPtr->himlState = himl;
7600 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
7601 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
7605 ERR("Unknown icon type=%d\n", nType);
7609 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
7610 if (infoPtr->nItemHeight != oldHeight)
7611 LISTVIEW_UpdateScroll(infoPtr);
7618 * Preallocates memory (does *not* set the actual count of items !)
7621 * [I] infoPtr : valid pointer to the listview structure
7622 * [I] nItems : item count (projected number of items to allocate)
7623 * [I] dwFlags : update flags
7629 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7631 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
7633 if (infoPtr->dwStyle & LVS_OWNERDATA)
7635 INT nOldCount = infoPtr->nItemCount;
7637 if (nItems < nOldCount)
7639 RANGE range = { nItems, nOldCount };
7640 ranges_del(infoPtr->selectionRanges, range);
7641 if (infoPtr->nFocusedItem >= nItems)
7643 LISTVIEW_SetItemFocus(infoPtr, -1);
7644 SetRectEmpty(&infoPtr->rcFocus);
7648 infoPtr->nItemCount = nItems;
7649 LISTVIEW_UpdateScroll(infoPtr);
7651 /* the flags are valid only in ownerdata report and list modes */
7652 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON) dwFlags = 0;
7654 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7655 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7657 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7658 LISTVIEW_InvalidateList(infoPtr);
7665 LISTVIEW_GetOrigin(infoPtr, &Origin);
7666 nFrom = min(nOldCount, nItems);
7667 nTo = max(nOldCount, nItems);
7669 if (infoPtr->uView == LV_VIEW_DETAILS)
7672 rcErase.top = nFrom * infoPtr->nItemHeight;
7673 rcErase.right = infoPtr->nItemWidth;
7674 rcErase.bottom = nTo * infoPtr->nItemHeight;
7675 OffsetRect(&rcErase, Origin.x, Origin.y);
7676 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7677 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7679 else /* LV_VIEW_LIST */
7681 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
7683 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
7684 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
7685 rcErase.right = rcErase.left + infoPtr->nItemWidth;
7686 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7687 OffsetRect(&rcErase, Origin.x, Origin.y);
7688 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7689 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7691 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
7693 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
7694 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7695 OffsetRect(&rcErase, Origin.x, Origin.y);
7696 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7697 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7703 /* According to MSDN for non-LVS_OWNERDATA this is just
7704 * a performance issue. The control allocates its internal
7705 * data structures for the number of items specified. It
7706 * cuts down on the number of memory allocations. Therefore
7707 * we will just issue a WARN here
7709 WARN("for non-ownerdata performance option not implemented.\n");
7717 * Sets the position of an item.
7720 * [I] infoPtr : valid pointer to the listview structure
7721 * [I] nItem : item index
7722 * [I] pt : coordinate
7728 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7732 TRACE("(nItem=%d, &pt=%s\n", nItem, wine_dbgstr_point(&pt));
7734 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7735 !(infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)) return FALSE;
7737 LISTVIEW_GetOrigin(infoPtr, &Origin);
7739 /* This point value seems to be an undocumented feature.
7740 * The best guess is that it means either at the origin,
7741 * or at true beginning of the list. I will assume the origin. */
7742 if ((pt.x == -1) && (pt.y == -1))
7745 if (infoPtr->uView == LV_VIEW_ICON)
7747 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7748 pt.y -= ICON_TOP_PADDING;
7753 infoPtr->bAutoarrange = FALSE;
7755 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7760 * Sets the state of one or many items.
7763 * [I] infoPtr : valid pointer to the listview structure
7764 * [I] nItem : item index
7765 * [I] lpLVItem : item or subitem info
7771 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7773 BOOL bResult = TRUE;
7776 lvItem.iItem = nItem;
7777 lvItem.iSubItem = 0;
7778 lvItem.mask = LVIF_STATE;
7779 lvItem.state = lpLVItem->state;
7780 lvItem.stateMask = lpLVItem->stateMask;
7781 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7785 /* select all isn't allowed in LVS_SINGLESEL */
7786 if ((lvItem.state & lvItem.stateMask & LVIS_SELECTED) && (infoPtr->dwStyle & LVS_SINGLESEL))
7789 /* apply to all items */
7790 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7791 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7794 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7797 * Update selection mark
7799 * Investigation on windows 2k showed that selection mark was updated
7800 * whenever a new selection was made, but if the selected item was
7801 * unselected it was not updated.
7803 * we are probably still not 100% accurate, but this at least sets the
7804 * proper selection mark when it is needed
7807 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7808 (infoPtr->nSelectionMark == -1))
7811 for (i = 0; i < infoPtr->nItemCount; i++)
7813 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7815 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7817 infoPtr->nSelectionMark = i;
7821 else if (ranges_contain(infoPtr->selectionRanges, i))
7823 infoPtr->nSelectionMark = i;
7834 * Sets the text of an item or subitem.
7837 * [I] hwnd : window handle
7838 * [I] nItem : item index
7839 * [I] lpLVItem : item or subitem info
7840 * [I] isW : TRUE if input is Unicode
7846 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7850 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7852 lvItem.iItem = nItem;
7853 lvItem.iSubItem = lpLVItem->iSubItem;
7854 lvItem.mask = LVIF_TEXT;
7855 lvItem.pszText = lpLVItem->pszText;
7856 lvItem.cchTextMax = lpLVItem->cchTextMax;
7858 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7860 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7865 * Set item index that marks the start of a multiple selection.
7868 * [I] infoPtr : valid pointer to the listview structure
7869 * [I] nIndex : index
7872 * Index number or -1 if there is no selection mark.
7874 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7876 INT nOldIndex = infoPtr->nSelectionMark;
7878 TRACE("(nIndex=%d)\n", nIndex);
7880 infoPtr->nSelectionMark = nIndex;
7887 * Sets the text background color.
7890 * [I] infoPtr : valid pointer to the listview structure
7891 * [I] clrTextBk : text background color
7897 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7899 TRACE("(clrTextBk=%x)\n", clrTextBk);
7901 if (infoPtr->clrTextBk != clrTextBk)
7903 infoPtr->clrTextBk = clrTextBk;
7904 LISTVIEW_InvalidateList(infoPtr);
7912 * Sets the text foreground color.
7915 * [I] infoPtr : valid pointer to the listview structure
7916 * [I] clrText : text color
7922 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7924 TRACE("(clrText=%x)\n", clrText);
7926 if (infoPtr->clrText != clrText)
7928 infoPtr->clrText = clrText;
7929 LISTVIEW_InvalidateList(infoPtr);
7937 * Sets new ToolTip window to ListView control.
7940 * [I] infoPtr : valid pointer to the listview structure
7941 * [I] hwndNewToolTip : handle to new ToolTip
7946 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7948 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7949 infoPtr->hwndToolTip = hwndNewToolTip;
7950 return hwndOldToolTip;
7955 * sets the Unicode character format flag for the control
7957 * [I] infoPtr :valid pointer to the listview structure
7958 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
7961 * Old Unicode Format
7963 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL fUnicode)
7965 SHORT rc = infoPtr->notifyFormat;
7966 infoPtr->notifyFormat = (fUnicode) ? NFR_UNICODE : NFR_ANSI;
7967 return rc == NFR_UNICODE;
7972 * sets the control view mode
7974 * [I] infoPtr :valid pointer to the listview structure
7975 * [I] nView :new view mode value
7981 static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView)
7983 SIZE oldIconSize = infoPtr->iconSize;
7986 if (infoPtr->uView == nView) return 1;
7988 if ((INT)nView < 0 || nView > LV_VIEW_MAX) return -1;
7989 if (nView == LV_VIEW_TILE)
7991 FIXME("View LV_VIEW_TILE unimplemented\n");
7995 infoPtr->uView = nView;
7997 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7998 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8000 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
8001 SetRectEmpty(&infoPtr->rcFocus);
8003 himl = (nView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
8004 set_icon_size(&infoPtr->iconSize, himl, nView != LV_VIEW_ICON);
8009 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
8011 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
8012 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
8013 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
8015 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8017 case LV_VIEW_SMALLICON:
8018 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8020 case LV_VIEW_DETAILS:
8025 LISTVIEW_CreateHeader( infoPtr );
8027 hl.prc = &infoPtr->rcList;
8029 SendMessageW(infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl);
8030 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
8031 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
8038 LISTVIEW_UpdateItemSize(infoPtr);
8039 LISTVIEW_UpdateSize(infoPtr);
8040 LISTVIEW_UpdateScroll(infoPtr);
8041 LISTVIEW_InvalidateList(infoPtr);
8043 TRACE("nView=%d\n", nView);
8048 /* LISTVIEW_SetWorkAreas */
8052 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMS
8055 * [I] first : pointer to first ITEM_INFO to compare
8056 * [I] second : pointer to second ITEM_INFO to compare
8057 * [I] lParam : HWND of control
8060 * if first comes before second : negative
8061 * if first comes after second : positive
8062 * if first and second are equivalent : zero
8064 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
8066 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
8067 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
8068 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
8070 /* Forward the call to the client defined callback */
8071 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
8076 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMSEX
8079 * [I] first : pointer to first ITEM_INFO to compare
8080 * [I] second : pointer to second ITEM_INFO to compare
8081 * [I] lParam : HWND of control
8084 * if first comes before second : negative
8085 * if first comes after second : positive
8086 * if first and second are equivalent : zero
8088 static INT WINAPI LISTVIEW_CallBackCompareEx(LPVOID first, LPVOID second, LPARAM lParam)
8090 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
8091 INT first_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, first );
8092 INT second_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, second );
8094 /* Forward the call to the client defined callback */
8095 return (infoPtr->pfnCompare)( first_idx, second_idx, infoPtr->lParamSort );
8100 * Sorts the listview items.
8103 * [I] infoPtr : valid pointer to the listview structure
8104 * [I] pfnCompare : application-defined value
8105 * [I] lParamSort : pointer to comparison callback
8106 * [I] IsEx : TRUE when LVM_SORTITEMSEX used
8112 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare,
8113 LPARAM lParamSort, BOOL IsEx)
8117 LPVOID selectionMarkItem = NULL;
8118 LPVOID focusedItem = NULL;
8121 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
8123 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
8125 if (!pfnCompare) return FALSE;
8126 if (!infoPtr->hdpaItems) return FALSE;
8128 /* if there are 0 or 1 items, there is no need to sort */
8129 if (infoPtr->nItemCount < 2) return TRUE;
8131 /* clear selection */
8132 ranges_clear(infoPtr->selectionRanges);
8134 /* save selection mark and focused item */
8135 if (infoPtr->nSelectionMark >= 0)
8136 selectionMarkItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark);
8137 if (infoPtr->nFocusedItem >= 0)
8138 focusedItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
8140 infoPtr->pfnCompare = pfnCompare;
8141 infoPtr->lParamSort = lParamSort;
8143 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompareEx, (LPARAM)infoPtr);
8145 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
8147 /* restore selection ranges */
8148 for (i=0; i < infoPtr->nItemCount; i++)
8150 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
8151 lpItem = DPA_GetPtr(hdpaSubItems, 0);
8153 if (lpItem->state & LVIS_SELECTED)
8154 ranges_additem(infoPtr->selectionRanges, i);
8156 /* restore selection mark and focused item */
8157 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
8158 infoPtr->nFocusedItem = DPA_GetPtrIndex(infoPtr->hdpaItems, focusedItem);
8160 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
8162 /* refresh the display */
8163 if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON)
8164 LISTVIEW_InvalidateList(infoPtr);
8171 * Update theme handle after a theme change.
8174 * [I] infoPtr : valid pointer to the listview structure
8178 * FAILURE : something else
8180 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
8182 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8183 CloseThemeData(theme);
8184 OpenThemeData(infoPtr->hwndSelf, themeClass);
8190 * Updates an items or rearranges the listview control.
8193 * [I] infoPtr : valid pointer to the listview structure
8194 * [I] nItem : item index
8200 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
8202 TRACE("(nItem=%d)\n", nItem);
8204 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
8206 /* rearrange with default alignment style */
8207 if (is_autoarrange(infoPtr))
8208 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8210 LISTVIEW_InvalidateItem(infoPtr, nItem);
8217 * Draw the track line at the place defined in the infoPtr structure.
8218 * The line is drawn with a XOR pen so drawing the line for the second time
8219 * in the same place erases the line.
8222 * [I] infoPtr : valid pointer to the listview structure
8228 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
8234 if (infoPtr->xTrackLine == -1)
8237 if (!(hdc = GetDC(infoPtr->hwndSelf)))
8239 hOldPen = SelectObject(hdc, GetStockObject(BLACK_PEN));
8240 oldROP = SetROP2(hdc, R2_XORPEN);
8241 MoveToEx(hdc, infoPtr->xTrackLine, infoPtr->rcList.top, NULL);
8242 LineTo(hdc, infoPtr->xTrackLine, infoPtr->rcList.bottom);
8243 SetROP2(hdc, oldROP);
8244 SelectObject(hdc, hOldPen);
8245 ReleaseDC(infoPtr->hwndSelf, hdc);
8251 * Called when an edit control should be displayed. This function is called after
8252 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
8255 * [I] hwnd : Handle to the listview
8256 * [I] uMsg : WM_TIMER (ignored)
8257 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
8258 * [I] dwTimer : The elapsed time (ignored)
8263 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
8265 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
8266 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8268 KillTimer(hwnd, idEvent);
8269 editItem->fEnabled = FALSE;
8270 /* check if the item is still selected */
8271 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
8272 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
8277 * Creates the listview control - the WM_NCCREATE phase.
8280 * [I] hwnd : window handle
8281 * [I] lpcs : the create parameters
8287 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
8289 LISTVIEW_INFO *infoPtr;
8292 TRACE("(lpcs=%p)\n", lpcs);
8294 /* initialize info pointer */
8295 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
8296 if (!infoPtr) return FALSE;
8298 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
8300 infoPtr->hwndSelf = hwnd;
8301 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
8302 map_style_view(infoPtr);
8303 /* determine the type of structures to use */
8304 infoPtr->hwndNotify = lpcs->hwndParent;
8305 /* infoPtr->notifyFormat will be filled in WM_CREATE */
8307 /* initialize color information */
8308 infoPtr->clrBk = CLR_NONE;
8309 infoPtr->clrText = CLR_DEFAULT;
8310 infoPtr->clrTextBk = CLR_DEFAULT;
8311 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
8313 /* set default values */
8314 infoPtr->nFocusedItem = -1;
8315 infoPtr->nSelectionMark = -1;
8316 infoPtr->nHotItem = -1;
8317 infoPtr->bRedraw = TRUE;
8318 infoPtr->bNoItemMetrics = TRUE;
8319 infoPtr->bDoChangeNotify = TRUE;
8320 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
8321 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
8322 infoPtr->nEditLabelItem = -1;
8323 infoPtr->nLButtonDownItem = -1;
8324 infoPtr->dwHoverTime = -1; /* default system hover time */
8325 infoPtr->nMeasureItemHeight = 0;
8326 infoPtr->xTrackLine = -1; /* no track line */
8327 infoPtr->itemEdit.fEnabled = FALSE;
8328 infoPtr->iVersion = COMCTL32_VERSION;
8330 /* get default font (icon title) */
8331 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
8332 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
8333 infoPtr->hFont = infoPtr->hDefaultFont;
8334 LISTVIEW_SaveTextMetrics(infoPtr);
8336 /* allocate memory for the data structure */
8337 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
8338 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
8339 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
8340 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
8341 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
8345 DestroyWindow(infoPtr->hwndHeader);
8346 ranges_destroy(infoPtr->selectionRanges);
8347 DPA_Destroy(infoPtr->hdpaItems);
8348 DPA_Destroy(infoPtr->hdpaPosX);
8349 DPA_Destroy(infoPtr->hdpaPosY);
8350 DPA_Destroy(infoPtr->hdpaColumns);
8357 * Creates the listview control - the WM_CREATE phase. Most of the data is
8358 * already set up in LISTVIEW_NCCreate
8361 * [I] hwnd : window handle
8362 * [I] lpcs : the create parameters
8368 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
8370 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8371 UINT uView = lpcs->style & LVS_TYPEMASK;
8373 TRACE("(lpcs=%p)\n", lpcs);
8375 infoPtr->dwStyle = lpcs->style;
8376 map_style_view(infoPtr);
8378 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
8379 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
8380 /* on error defaulting to ANSI notifications */
8381 if (infoPtr->notifyFormat == 0) infoPtr->notifyFormat = NFR_ANSI;
8383 if ((uView == LVS_REPORT) && (lpcs->style & WS_VISIBLE))
8385 if (LISTVIEW_CreateHeader(infoPtr) < 0) return -1;
8388 infoPtr->hwndHeader = 0;
8390 /* init item size to avoid division by 0 */
8391 LISTVIEW_UpdateItemSize (infoPtr);
8393 if (uView == LVS_REPORT)
8395 if (!(LVS_NOCOLUMNHEADER & lpcs->style) && (WS_VISIBLE & lpcs->style))
8397 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8399 LISTVIEW_UpdateScroll(infoPtr);
8402 OpenThemeData(hwnd, themeClass);
8404 /* initialize the icon sizes */
8405 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
8406 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
8412 * Destroys the listview control.
8415 * [I] infoPtr : valid pointer to the listview structure
8421 static LRESULT LISTVIEW_Destroy(const LISTVIEW_INFO *infoPtr)
8423 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8424 CloseThemeData(theme);
8430 * Enables the listview control.
8433 * [I] infoPtr : valid pointer to the listview structure
8434 * [I] bEnable : specifies whether to enable or disable the window
8440 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr, BOOL bEnable)
8442 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
8443 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
8449 * Erases the background of the listview control.
8452 * [I] infoPtr : valid pointer to the listview structure
8453 * [I] hdc : device context handle
8459 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
8463 TRACE("(hdc=%p)\n", hdc);
8465 if (!GetClipBox(hdc, &rc)) return FALSE;
8467 if (infoPtr->clrBk == CLR_NONE)
8468 return SendMessageW(infoPtr->hwndNotify, WM_ERASEBKGND, (WPARAM)hdc, 0);
8470 /* for double buffered controls we need to do this during refresh */
8471 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
8473 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
8479 * Helper function for LISTVIEW_[HV]Scroll *only*.
8480 * Performs vertical/horizontal scrolling by a give amount.
8483 * [I] infoPtr : valid pointer to the listview structure
8484 * [I] dx : amount of horizontal scroll
8485 * [I] dy : amount of vertical scroll
8487 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
8489 /* now we can scroll the list */
8490 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
8491 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
8492 /* if we have focus, adjust rect */
8493 OffsetRect(&infoPtr->rcFocus, dx, dy);
8494 UpdateWindow(infoPtr->hwndSelf);
8499 * Performs vertical scrolling.
8502 * [I] infoPtr : valid pointer to the listview structure
8503 * [I] nScrollCode : scroll code
8504 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8505 * [I] hScrollWnd : scrollbar control window handle
8511 * SB_LINEUP/SB_LINEDOWN:
8512 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
8513 * for LVS_REPORT is 1 line
8514 * for LVS_LIST cannot occur
8517 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8518 INT nScrollDiff, HWND hScrollWnd)
8520 INT nOldScrollPos, nNewScrollPos;
8521 SCROLLINFO scrollInfo;
8524 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8525 debugscrollcode(nScrollCode), nScrollDiff);
8527 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8529 scrollInfo.cbSize = sizeof(SCROLLINFO);
8530 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8532 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
8534 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
8536 nOldScrollPos = scrollInfo.nPos;
8537 switch (nScrollCode)
8543 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
8547 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
8551 nScrollDiff = -scrollInfo.nPage;
8555 nScrollDiff = scrollInfo.nPage;
8558 case SB_THUMBPOSITION:
8560 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8567 /* quit right away if pos isn't changing */
8568 if (nScrollDiff == 0) return 0;
8570 /* calculate new position, and handle overflows */
8571 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8572 if (nScrollDiff > 0) {
8573 if (nNewScrollPos < nOldScrollPos ||
8574 nNewScrollPos > scrollInfo.nMax)
8575 nNewScrollPos = scrollInfo.nMax;
8577 if (nNewScrollPos > nOldScrollPos ||
8578 nNewScrollPos < scrollInfo.nMin)
8579 nNewScrollPos = scrollInfo.nMin;
8582 /* set the new position, and reread in case it changed */
8583 scrollInfo.fMask = SIF_POS;
8584 scrollInfo.nPos = nNewScrollPos;
8585 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
8587 /* carry on only if it really changed */
8588 if (nNewScrollPos == nOldScrollPos) return 0;
8590 /* now adjust to client coordinates */
8591 nScrollDiff = nOldScrollPos - nNewScrollPos;
8592 if (infoPtr->uView == LV_VIEW_DETAILS) nScrollDiff *= infoPtr->nItemHeight;
8594 /* and scroll the window */
8595 scroll_list(infoPtr, 0, nScrollDiff);
8602 * Performs horizontal scrolling.
8605 * [I] infoPtr : valid pointer to the listview structure
8606 * [I] nScrollCode : scroll code
8607 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8608 * [I] hScrollWnd : scrollbar control window handle
8614 * SB_LINELEFT/SB_LINERIGHT:
8615 * for LVS_ICON, LVS_SMALLICON 1 pixel
8616 * for LVS_REPORT is 1 pixel
8617 * for LVS_LIST is 1 column --> which is a 1 because the
8618 * scroll is based on columns not pixels
8621 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8622 INT nScrollDiff, HWND hScrollWnd)
8624 INT nOldScrollPos, nNewScrollPos;
8625 SCROLLINFO scrollInfo;
8627 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8628 debugscrollcode(nScrollCode), nScrollDiff);
8630 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8632 scrollInfo.cbSize = sizeof(SCROLLINFO);
8633 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8635 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
8637 nOldScrollPos = scrollInfo.nPos;
8639 switch (nScrollCode)
8653 nScrollDiff = -scrollInfo.nPage;
8657 nScrollDiff = scrollInfo.nPage;
8660 case SB_THUMBPOSITION:
8662 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8669 /* quit right away if pos isn't changing */
8670 if (nScrollDiff == 0) return 0;
8672 /* calculate new position, and handle overflows */
8673 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8674 if (nScrollDiff > 0) {
8675 if (nNewScrollPos < nOldScrollPos ||
8676 nNewScrollPos > scrollInfo.nMax)
8677 nNewScrollPos = scrollInfo.nMax;
8679 if (nNewScrollPos > nOldScrollPos ||
8680 nNewScrollPos < scrollInfo.nMin)
8681 nNewScrollPos = scrollInfo.nMin;
8684 /* set the new position, and reread in case it changed */
8685 scrollInfo.fMask = SIF_POS;
8686 scrollInfo.nPos = nNewScrollPos;
8687 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
8689 /* carry on only if it really changed */
8690 if (nNewScrollPos == nOldScrollPos) return 0;
8692 if (infoPtr->uView == LV_VIEW_DETAILS)
8693 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
8695 /* now adjust to client coordinates */
8696 nScrollDiff = nOldScrollPos - nNewScrollPos;
8697 if (infoPtr->uView == LV_VIEW_LIST) nScrollDiff *= infoPtr->nItemWidth;
8699 /* and scroll the window */
8700 scroll_list(infoPtr, nScrollDiff, 0);
8705 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
8707 INT gcWheelDelta = 0;
8708 INT pulScrollLines = 3;
8709 SCROLLINFO scrollInfo;
8711 TRACE("(wheelDelta=%d)\n", wheelDelta);
8713 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
8714 gcWheelDelta -= wheelDelta;
8716 scrollInfo.cbSize = sizeof(SCROLLINFO);
8717 scrollInfo.fMask = SIF_POS;
8719 switch(infoPtr->uView)
8722 case LV_VIEW_SMALLICON:
8724 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
8725 * should be fixed in the future.
8727 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
8728 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
8731 case LV_VIEW_DETAILS:
8732 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
8734 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
8735 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
8736 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
8741 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
8752 * [I] infoPtr : valid pointer to the listview structure
8753 * [I] nVirtualKey : virtual key
8754 * [I] lKeyData : key data
8759 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
8761 HWND hwndSelf = infoPtr->hwndSelf;
8763 NMLVKEYDOWN nmKeyDown;
8765 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
8767 /* send LVN_KEYDOWN notification */
8768 nmKeyDown.wVKey = nVirtualKey;
8769 nmKeyDown.flags = 0;
8770 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
8771 if (!IsWindow(hwndSelf))
8774 switch (nVirtualKey)
8777 nItem = infoPtr->nFocusedItem;
8778 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8779 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
8783 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
8785 if (!notify(infoPtr, NM_RETURN)) return 0;
8786 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
8791 if (infoPtr->nItemCount > 0)
8796 if (infoPtr->nItemCount > 0)
8797 nItem = infoPtr->nItemCount - 1;
8801 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TOLEFT);
8805 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_ABOVE);
8809 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TORIGHT);
8813 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_BELOW);
8817 if (infoPtr->uView == LV_VIEW_DETAILS)
8819 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8820 if (infoPtr->nFocusedItem == topidx)
8821 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
8826 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
8827 * LISTVIEW_GetCountPerRow(infoPtr);
8828 if(nItem < 0) nItem = 0;
8832 if (infoPtr->uView == LV_VIEW_DETAILS)
8834 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8835 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
8836 if (infoPtr->nFocusedItem == topidx + cnt - 1)
8837 nItem = infoPtr->nFocusedItem + cnt - 1;
8839 nItem = topidx + cnt - 1;
8842 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
8843 * LISTVIEW_GetCountPerRow(infoPtr);
8844 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
8848 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
8849 LISTVIEW_KeySelection(infoPtr, nItem, nVirtualKey == VK_SPACE);
8859 * [I] infoPtr : valid pointer to the listview structure
8864 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
8868 /* if we did not have the focus, there's nothing to do */
8869 if (!infoPtr->bFocus) return 0;
8871 /* send NM_KILLFOCUS notification */
8872 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
8874 /* if we have a focus rectangle, get rid of it */
8875 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
8877 /* set window focus flag */
8878 infoPtr->bFocus = FALSE;
8880 /* invalidate the selected items before resetting focus flag */
8881 LISTVIEW_InvalidateSelectedItems(infoPtr);
8888 * Processes double click messages (left mouse button).
8891 * [I] infoPtr : valid pointer to the listview structure
8892 * [I] wKey : key flag
8893 * [I] x,y : mouse coordinate
8898 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8900 LVHITTESTINFO htInfo;
8902 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8904 /* Cancel the item edition if any */
8905 if (infoPtr->itemEdit.fEnabled)
8907 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
8908 infoPtr->itemEdit.fEnabled = FALSE;
8911 /* send NM_RELEASEDCAPTURE notification */
8912 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8917 /* send NM_DBLCLK notification */
8918 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
8919 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
8921 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
8922 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
8929 * Processes mouse down messages (left mouse button).
8932 * infoPtr [I ] valid pointer to the listview structure
8933 * wKey [I ] key flag
8934 * x,y [I ] mouse coordinate
8939 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8941 LVHITTESTINFO lvHitTestInfo;
8942 static BOOL bGroupSelect = TRUE;
8943 POINT pt = { x, y };
8946 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8948 /* send NM_RELEASEDCAPTURE notification */
8949 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8951 /* set left button down flag and record the click position */
8952 infoPtr->bLButtonDown = TRUE;
8953 infoPtr->ptClickPos = pt;
8954 infoPtr->bDragging = FALSE;
8956 lvHitTestInfo.pt.x = x;
8957 lvHitTestInfo.pt.y = y;
8959 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8960 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
8961 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8963 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8965 toggle_checkbox_state(infoPtr, nItem);
8969 if (infoPtr->dwStyle & LVS_SINGLESEL)
8971 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8972 infoPtr->nEditLabelItem = nItem;
8974 LISTVIEW_SetSelection(infoPtr, nItem);
8978 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8982 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
8983 LISTVIEW_SetItemFocus(infoPtr, nItem);
8984 infoPtr->nSelectionMark = nItem;
8990 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8991 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8993 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8994 infoPtr->nSelectionMark = nItem;
8997 else if (wKey & MK_CONTROL)
9001 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
9003 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
9004 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
9005 LISTVIEW_SetItemState(infoPtr, nItem, &item);
9006 infoPtr->nSelectionMark = nItem;
9008 else if (wKey & MK_SHIFT)
9010 LISTVIEW_SetGroupSelection(infoPtr, nItem);
9014 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9016 infoPtr->nEditLabelItem = nItem;
9017 infoPtr->nLButtonDownItem = nItem;
9019 LISTVIEW_SetItemFocus(infoPtr, nItem);
9022 /* set selection (clears other pre-existing selections) */
9023 LISTVIEW_SetSelection(infoPtr, nItem);
9027 if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
9028 if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
9032 /* remove all selections */
9033 if (!(wKey & MK_CONTROL) && !(wKey & MK_SHIFT))
9034 LISTVIEW_DeselectAll(infoPtr);
9043 * Processes mouse up messages (left mouse button).
9046 * infoPtr [I ] valid pointer to the listview structure
9047 * wKey [I ] key flag
9048 * x,y [I ] mouse coordinate
9053 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9055 LVHITTESTINFO lvHitTestInfo;
9057 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
9059 if (!infoPtr->bLButtonDown) return 0;
9061 lvHitTestInfo.pt.x = x;
9062 lvHitTestInfo.pt.y = y;
9064 /* send NM_CLICK notification */
9065 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9066 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
9068 /* set left button flag */
9069 infoPtr->bLButtonDown = FALSE;
9071 /* set a single selection, reset others */
9072 if(lvHitTestInfo.iItem == infoPtr->nLButtonDownItem && lvHitTestInfo.iItem != -1)
9073 LISTVIEW_SetSelection(infoPtr, infoPtr->nLButtonDownItem);
9074 infoPtr->nLButtonDownItem = -1;
9076 if (infoPtr->bDragging)
9078 infoPtr->bDragging = FALSE;
9082 /* if we clicked on a selected item, edit the label */
9083 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
9085 /* we want to make sure the user doesn't want to do a double click. So we will
9086 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
9088 infoPtr->itemEdit.fEnabled = TRUE;
9089 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
9090 SetTimer(infoPtr->hwndSelf,
9091 (UINT_PTR)&infoPtr->itemEdit,
9092 GetDoubleClickTime(),
9093 LISTVIEW_DelayedEditItem);
9096 if (!infoPtr->bFocus)
9097 SetFocus(infoPtr->hwndSelf);
9104 * Destroys the listview control (called after WM_DESTROY).
9107 * [I] infoPtr : valid pointer to the listview structure
9112 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
9116 /* delete all items */
9117 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
9119 /* destroy data structure */
9120 DPA_Destroy(infoPtr->hdpaItems);
9121 DPA_Destroy(infoPtr->hdpaPosX);
9122 DPA_Destroy(infoPtr->hdpaPosY);
9123 DPA_Destroy(infoPtr->hdpaColumns);
9124 ranges_destroy(infoPtr->selectionRanges);
9126 /* destroy image lists */
9127 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
9129 if (infoPtr->himlNormal)
9130 ImageList_Destroy(infoPtr->himlNormal);
9131 if (infoPtr->himlSmall)
9132 ImageList_Destroy(infoPtr->himlSmall);
9133 if (infoPtr->himlState)
9134 ImageList_Destroy(infoPtr->himlState);
9137 /* destroy font, bkgnd brush */
9139 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
9140 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
9142 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
9144 /* free listview info pointer*/
9152 * Handles notifications from header.
9155 * [I] infoPtr : valid pointer to the listview structure
9156 * [I] nCtrlId : control identifier
9157 * [I] lpnmh : notification information
9162 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
9164 HWND hwndSelf = infoPtr->hwndSelf;
9166 TRACE("(lpnmh=%p)\n", lpnmh);
9168 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
9170 switch (lpnmh->hdr.code)
9175 COLUMN_INFO *lpColumnInfo;
9179 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
9182 /* remove the old line (if any) */
9183 LISTVIEW_DrawTrackLine(infoPtr);
9185 /* compute & draw the new line */
9186 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
9187 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
9188 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
9189 infoPtr->xTrackLine = x + ptOrigin.x;
9190 LISTVIEW_DrawTrackLine(infoPtr);
9196 /* remove the track line (if any) */
9197 LISTVIEW_DrawTrackLine(infoPtr);
9198 infoPtr->xTrackLine = -1;
9202 FIXME("Changing column order not implemented\n");
9205 case HDN_ITEMCHANGINGW:
9206 case HDN_ITEMCHANGINGA:
9207 return notify_forward_header(infoPtr, lpnmh);
9209 case HDN_ITEMCHANGEDW:
9210 case HDN_ITEMCHANGEDA:
9212 COLUMN_INFO *lpColumnInfo;
9215 notify_forward_header(infoPtr, lpnmh);
9216 if (!IsWindow(hwndSelf))
9219 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
9223 hdi.mask = HDI_WIDTH;
9224 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, &hdi)) return 0;
9228 cxy = lpnmh->pitem->cxy;
9230 /* determine how much we change since the last know position */
9231 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
9232 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
9235 lpColumnInfo->rcHeader.right += dx;
9236 if (lpnmh->iItem + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
9237 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
9240 /* only needs to update the scrolls */
9241 infoPtr->nItemWidth += dx;
9242 LISTVIEW_UpdateScroll(infoPtr);
9244 LISTVIEW_UpdateItemSize(infoPtr);
9245 if (infoPtr->uView == LV_VIEW_DETAILS && is_redrawing(infoPtr))
9248 RECT rcCol = lpColumnInfo->rcHeader;
9250 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
9251 OffsetRect(&rcCol, ptOrigin.x, 0);
9253 rcCol.top = infoPtr->rcList.top;
9254 rcCol.bottom = infoPtr->rcList.bottom;
9256 /* resizing left-aligned columns leaves most of the left side untouched */
9257 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
9259 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
9262 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
9265 /* when shrinking the last column clear the now unused field */
9266 if (lpnmh->iItem == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
9272 /* deal with right from rightmost column area */
9273 right.left = rcCol.right;
9274 right.top = rcCol.top;
9275 right.bottom = rcCol.bottom;
9276 right.right = infoPtr->rcList.right;
9278 LISTVIEW_InvalidateRect(infoPtr, &right);
9281 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
9287 case HDN_ITEMCLICKW:
9288 case HDN_ITEMCLICKA:
9290 /* Handle sorting by Header Column */
9293 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
9295 nmlv.iSubItem = lpnmh->iItem;
9296 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
9297 notify_forward_header(infoPtr, lpnmh);
9301 case HDN_DIVIDERDBLCLICKW:
9302 case HDN_DIVIDERDBLCLICKA:
9303 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
9312 * Paint non-client area of control.
9315 * [I] infoPtr : valid pointer to the listview structureof the sender
9316 * [I] region : update region
9319 * TRUE - frame was painted
9320 * FALSE - call default window proc
9322 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
9324 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
9328 int cxEdge = GetSystemMetrics (SM_CXEDGE),
9329 cyEdge = GetSystemMetrics (SM_CYEDGE);
9331 if (!theme) return FALSE;
9333 GetWindowRect(infoPtr->hwndSelf, &r);
9335 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
9336 r.right - cxEdge, r.bottom - cyEdge);
9337 if (region != (HRGN)1)
9338 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
9339 OffsetRect(&r, -r.left, -r.top);
9341 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
9342 OffsetRect(&r, -r.left, -r.top);
9344 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
9345 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
9346 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
9347 ReleaseDC(infoPtr->hwndSelf, dc);
9349 /* Call default proc to get the scrollbars etc. painted */
9350 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
9357 * Determines the type of structure to use.
9360 * [I] infoPtr : valid pointer to the listview structureof the sender
9361 * [I] hwndFrom : listview window handle
9362 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
9367 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
9369 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
9371 if (nCommand == NF_REQUERY)
9372 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9374 return infoPtr->notifyFormat;
9379 * Paints/Repaints the listview control.
9382 * [I] infoPtr : valid pointer to the listview structure
9383 * [I] hdc : device context handle
9388 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
9390 TRACE("(hdc=%p)\n", hdc);
9392 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
9394 infoPtr->bNoItemMetrics = FALSE;
9395 LISTVIEW_UpdateItemSize(infoPtr);
9396 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
9397 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9398 LISTVIEW_UpdateScroll(infoPtr);
9401 if (infoPtr->hwndHeader) UpdateWindow(infoPtr->hwndHeader);
9404 LISTVIEW_Refresh(infoPtr, hdc, NULL);
9409 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
9411 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
9412 EndPaint(infoPtr->hwndSelf, &ps);
9421 * Paints/Repaints the listview control.
9424 * [I] infoPtr : valid pointer to the listview structure
9425 * [I] hdc : device context handle
9426 * [I] options : drawing options
9431 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
9433 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
9435 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
9438 if (options & PRF_ERASEBKGND)
9439 LISTVIEW_EraseBkgnd(infoPtr, hdc);
9441 if (options & PRF_CLIENT)
9442 LISTVIEW_Paint(infoPtr, hdc);
9450 * Processes double click messages (right mouse button).
9453 * [I] infoPtr : valid pointer to the listview structure
9454 * [I] wKey : key flag
9455 * [I] x,y : mouse coordinate
9460 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9462 LVHITTESTINFO lvHitTestInfo;
9464 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9466 /* send NM_RELEASEDCAPTURE notification */
9467 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9469 /* send NM_RDBLCLK notification */
9470 lvHitTestInfo.pt.x = x;
9471 lvHitTestInfo.pt.y = y;
9472 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9473 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
9480 * Processes mouse down messages (right mouse button).
9483 * [I] infoPtr : valid pointer to the listview structure
9484 * [I] wKey : key flag
9485 * [I] x,y : mouse coordinate
9490 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9492 LVHITTESTINFO lvHitTestInfo;
9495 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9497 /* send NM_RELEASEDCAPTURE notification */
9498 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9500 /* make sure the listview control window has the focus */
9501 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
9503 /* set right button down flag */
9504 infoPtr->bRButtonDown = TRUE;
9506 /* determine the index of the selected item */
9507 lvHitTestInfo.pt.x = x;
9508 lvHitTestInfo.pt.y = y;
9509 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
9511 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
9513 LISTVIEW_SetItemFocus(infoPtr, nItem);
9514 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
9515 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9516 LISTVIEW_SetSelection(infoPtr, nItem);
9520 LISTVIEW_DeselectAll(infoPtr);
9528 * Processes mouse up messages (right mouse button).
9531 * [I] infoPtr : valid pointer to the listview structure
9532 * [I] wKey : key flag
9533 * [I] x,y : mouse coordinate
9538 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9540 LVHITTESTINFO lvHitTestInfo;
9543 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9545 if (!infoPtr->bRButtonDown) return 0;
9547 /* set button flag */
9548 infoPtr->bRButtonDown = FALSE;
9550 /* Send NM_RCLICK notification */
9551 lvHitTestInfo.pt.x = x;
9552 lvHitTestInfo.pt.y = y;
9553 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9554 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
9556 /* Change to screen coordinate for WM_CONTEXTMENU */
9557 pt = lvHitTestInfo.pt;
9558 ClientToScreen(infoPtr->hwndSelf, &pt);
9560 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
9561 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
9562 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
9573 * [I] infoPtr : valid pointer to the listview structure
9574 * [I] hwnd : window handle of window containing the cursor
9575 * [I] nHittest : hit-test code
9576 * [I] wMouseMsg : ideintifier of the mouse message
9579 * TRUE if cursor is set
9582 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
9584 LVHITTESTINFO lvHitTestInfo;
9586 if(!(LISTVIEW_isHotTracking(infoPtr))) return FALSE;
9588 if(!infoPtr->hHotCursor) return FALSE;
9590 GetCursorPos(&lvHitTestInfo.pt);
9591 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
9593 SetCursor(infoPtr->hHotCursor);
9603 * [I] infoPtr : valid pointer to the listview structure
9604 * [I] hwndLoseFocus : handle of previously focused window
9609 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
9611 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
9613 /* if we have the focus already, there's nothing to do */
9614 if (infoPtr->bFocus) return 0;
9616 /* send NM_SETFOCUS notification */
9617 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
9619 /* set window focus flag */
9620 infoPtr->bFocus = TRUE;
9622 /* put the focus rect back on */
9623 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
9625 /* redraw all visible selected items */
9626 LISTVIEW_InvalidateSelectedItems(infoPtr);
9636 * [I] infoPtr : valid pointer to the listview structure
9637 * [I] fRedraw : font handle
9638 * [I] fRedraw : redraw flag
9643 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
9645 HFONT oldFont = infoPtr->hFont;
9647 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
9649 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
9650 if (infoPtr->hFont == oldFont) return 0;
9652 LISTVIEW_SaveTextMetrics(infoPtr);
9654 if (infoPtr->uView == LV_VIEW_DETAILS)
9656 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
9657 LISTVIEW_UpdateSize(infoPtr);
9658 LISTVIEW_UpdateScroll(infoPtr);
9661 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
9668 * Message handling for WM_SETREDRAW.
9669 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
9672 * [I] infoPtr : valid pointer to the listview structure
9673 * [I] bRedraw: state of redraw flag
9676 * DefWinProc return value
9678 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
9680 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
9682 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
9683 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
9685 infoPtr->bRedraw = bRedraw;
9687 if(!bRedraw) return 0;
9689 if (is_autoarrange(infoPtr))
9690 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9691 LISTVIEW_UpdateScroll(infoPtr);
9693 /* despite what the WM_SETREDRAW docs says, apps expect us
9694 * to invalidate the listview here... stupid! */
9695 LISTVIEW_InvalidateList(infoPtr);
9702 * Resizes the listview control. This function processes WM_SIZE
9703 * messages. At this time, the width and height are not used.
9706 * [I] infoPtr : valid pointer to the listview structure
9707 * [I] Width : new width
9708 * [I] Height : new height
9713 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
9715 RECT rcOld = infoPtr->rcList;
9717 TRACE("(width=%d, height=%d)\n", Width, Height);
9719 LISTVIEW_UpdateSize(infoPtr);
9720 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
9722 /* do not bother with display related stuff if we're not redrawing */
9723 if (!is_redrawing(infoPtr)) return 0;
9725 if (is_autoarrange(infoPtr))
9726 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9728 LISTVIEW_UpdateScroll(infoPtr);
9730 /* refresh all only for lists whose height changed significantly */
9731 if ((infoPtr->uView == LV_VIEW_LIST) &&
9732 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
9733 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
9734 LISTVIEW_InvalidateList(infoPtr);
9741 * Sets the size information.
9744 * [I] infoPtr : valid pointer to the listview structure
9749 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
9751 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9753 TRACE("uView=%d, rcList(old)=%s\n", uView, wine_dbgstr_rect(&infoPtr->rcList));
9755 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
9757 if (uView == LVS_LIST)
9759 /* Apparently the "LIST" style is supposed to have the same
9760 * number of items in a column even if there is no scroll bar.
9761 * Since if a scroll bar already exists then the bottom is already
9762 * reduced, only reduce if the scroll bar does not currently exist.
9763 * The "2" is there to mimic the native control. I think it may be
9764 * related to either padding or edges. (GLA 7/2002)
9766 if (!(infoPtr->dwStyle & WS_HSCROLL))
9767 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
9768 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
9770 else if (uView == LVS_REPORT)
9775 hl.prc = &infoPtr->rcList;
9777 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9778 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
9779 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy,
9780 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9781 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9782 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
9784 infoPtr->rcList.top = max(wp.cy, 0);
9785 infoPtr->rcList.top += (infoPtr->dwLvExStyle & LVS_EX_GRIDLINES) ? 2 : 0;
9788 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
9793 * Processes WM_STYLECHANGED messages.
9796 * [I] infoPtr : valid pointer to the listview structure
9797 * [I] wStyleType : window style type (normal or extended)
9798 * [I] lpss : window style information
9803 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9804 const STYLESTRUCT *lpss)
9806 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
9807 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
9810 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9811 wStyleType, lpss->styleOld, lpss->styleNew);
9813 if (wStyleType != GWL_STYLE) return 0;
9815 infoPtr->dwStyle = lpss->styleNew;
9816 map_style_view(infoPtr);
9818 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
9819 ((lpss->styleNew & WS_HSCROLL) == 0))
9820 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
9822 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
9823 ((lpss->styleNew & WS_VSCROLL) == 0))
9824 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
9826 if (uNewView != uOldView)
9828 SIZE oldIconSize = infoPtr->iconSize;
9831 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9832 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9834 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9835 SetRectEmpty(&infoPtr->rcFocus);
9837 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9838 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
9840 if (uNewView == LVS_ICON)
9842 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9844 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9845 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9846 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9849 else if (uNewView == LVS_REPORT)
9854 LISTVIEW_CreateHeader( infoPtr );
9856 hl.prc = &infoPtr->rcList;
9858 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9859 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9860 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9861 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9864 LISTVIEW_UpdateItemSize(infoPtr);
9867 if (uNewView == LVS_REPORT)
9869 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
9871 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
9873 /* Turn off the header control */
9874 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
9875 TRACE("Hide header control, was 0x%08x\n", style);
9876 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
9878 /* Turn on the header control */
9879 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
9881 TRACE("Show header control, was 0x%08x\n", style);
9882 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
9888 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
9889 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
9890 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9892 /* update the size of the client area */
9893 LISTVIEW_UpdateSize(infoPtr);
9895 /* add scrollbars if needed */
9896 LISTVIEW_UpdateScroll(infoPtr);
9898 /* invalidate client area + erase background */
9899 LISTVIEW_InvalidateList(infoPtr);
9906 * Processes WM_STYLECHANGING messages.
9909 * [I] infoPtr : valid pointer to the listview structure
9910 * [I] wStyleType : window style type (normal or extended)
9911 * [I0] lpss : window style information
9916 static INT LISTVIEW_StyleChanging(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9919 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9920 wStyleType, lpss->styleOld, lpss->styleNew);
9922 /* don't forward LVS_OWNERDATA only if not already set to */
9923 if ((lpss->styleNew ^ lpss->styleOld) & LVS_OWNERDATA)
9925 if (lpss->styleOld & LVS_OWNERDATA)
9926 lpss->styleNew |= LVS_OWNERDATA;
9928 lpss->styleNew &= ~LVS_OWNERDATA;
9936 * Processes WM_SHOWWINDOW messages.
9939 * [I] infoPtr : valid pointer to the listview structure
9940 * [I] bShown : window is being shown (FALSE when hidden)
9941 * [I] iStatus : window show status
9946 static LRESULT LISTVIEW_ShowWindow(LISTVIEW_INFO *infoPtr, BOOL bShown, INT iStatus)
9948 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9950 /* header delayed creation */
9951 if ((uView == LVS_REPORT) && bShown)
9953 LISTVIEW_CreateHeader(infoPtr);
9955 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle))
9956 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
9964 * Processes CCM_GETVERSION messages.
9967 * [I] infoPtr : valid pointer to the listview structure
9972 static inline LRESULT LISTVIEW_GetVersion(const LISTVIEW_INFO *infoPtr)
9974 return infoPtr->iVersion;
9979 * Processes CCM_SETVERSION messages.
9982 * [I] infoPtr : valid pointer to the listview structure
9983 * [I] iVersion : version to be set
9986 * -1 when requested version is greater than DLL version;
9987 * previous version otherwise
9989 static LRESULT LISTVIEW_SetVersion(LISTVIEW_INFO *infoPtr, DWORD iVersion)
9991 INT iOldVersion = infoPtr->iVersion;
9993 if (iVersion > COMCTL32_VERSION)
9996 infoPtr->iVersion = iVersion;
9998 TRACE("new version %d\n", iVersion);
10000 return iOldVersion;
10005 * Window procedure of the listview control.
10008 static LRESULT WINAPI
10009 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10011 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
10013 TRACE("(uMsg=%x wParam=%lx lParam=%lx)\n", uMsg, wParam, lParam);
10015 if (!infoPtr && (uMsg != WM_NCCREATE))
10016 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10020 case LVM_APPROXIMATEVIEWRECT:
10021 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
10022 LOWORD(lParam), HIWORD(lParam));
10024 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
10026 /* case LVM_CANCELEDITLABEL: */
10028 case LVM_CREATEDRAGIMAGE:
10029 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
10031 case LVM_DELETEALLITEMS:
10032 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
10034 case LVM_DELETECOLUMN:
10035 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
10037 case LVM_DELETEITEM:
10038 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
10040 case LVM_EDITLABELW:
10041 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
10043 case LVM_EDITLABELA:
10044 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
10046 /* case LVM_ENABLEGROUPVIEW: */
10048 case LVM_ENSUREVISIBLE:
10049 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
10051 case LVM_FINDITEMW:
10052 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
10054 case LVM_FINDITEMA:
10055 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
10057 case LVM_GETBKCOLOR:
10058 return infoPtr->clrBk;
10060 /* case LVM_GETBKIMAGE: */
10062 case LVM_GETCALLBACKMASK:
10063 return infoPtr->uCallbackMask;
10065 case LVM_GETCOLUMNA:
10066 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
10068 case LVM_GETCOLUMNW:
10069 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
10071 case LVM_GETCOLUMNORDERARRAY:
10072 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
10074 case LVM_GETCOLUMNWIDTH:
10075 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
10077 case LVM_GETCOUNTPERPAGE:
10078 return LISTVIEW_GetCountPerPage(infoPtr);
10080 case LVM_GETEDITCONTROL:
10081 return (LRESULT)infoPtr->hwndEdit;
10083 case LVM_GETEXTENDEDLISTVIEWSTYLE:
10084 return infoPtr->dwLvExStyle;
10086 /* case LVM_GETGROUPINFO: */
10088 /* case LVM_GETGROUPMETRICS: */
10090 case LVM_GETHEADER:
10091 return (LRESULT)infoPtr->hwndHeader;
10093 case LVM_GETHOTCURSOR:
10094 return (LRESULT)infoPtr->hHotCursor;
10096 case LVM_GETHOTITEM:
10097 return infoPtr->nHotItem;
10099 case LVM_GETHOVERTIME:
10100 return infoPtr->dwHoverTime;
10102 case LVM_GETIMAGELIST:
10103 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
10105 /* case LVM_GETINSERTMARK: */
10107 /* case LVM_GETINSERTMARKCOLOR: */
10109 /* case LVM_GETINSERTMARKRECT: */
10111 case LVM_GETISEARCHSTRINGA:
10112 case LVM_GETISEARCHSTRINGW:
10113 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
10117 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
10120 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
10122 case LVM_GETITEMCOUNT:
10123 return infoPtr->nItemCount;
10125 case LVM_GETITEMPOSITION:
10126 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
10128 case LVM_GETITEMRECT:
10129 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
10131 case LVM_GETITEMSPACING:
10132 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
10134 case LVM_GETITEMSTATE:
10135 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
10137 case LVM_GETITEMTEXTA:
10138 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
10140 case LVM_GETITEMTEXTW:
10141 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
10143 case LVM_GETNEXTITEM:
10144 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
10146 case LVM_GETNUMBEROFWORKAREAS:
10147 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
10150 case LVM_GETORIGIN:
10151 if (!lParam) return FALSE;
10152 if (infoPtr->uView == LV_VIEW_DETAILS ||
10153 infoPtr->uView == LV_VIEW_LIST) return FALSE;
10154 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
10157 /* case LVM_GETOUTLINECOLOR: */
10159 /* case LVM_GETSELECTEDCOLUMN: */
10161 case LVM_GETSELECTEDCOUNT:
10162 return LISTVIEW_GetSelectedCount(infoPtr);
10164 case LVM_GETSELECTIONMARK:
10165 return infoPtr->nSelectionMark;
10167 case LVM_GETSTRINGWIDTHA:
10168 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
10170 case LVM_GETSTRINGWIDTHW:
10171 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
10173 case LVM_GETSUBITEMRECT:
10174 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
10176 case LVM_GETTEXTBKCOLOR:
10177 return infoPtr->clrTextBk;
10179 case LVM_GETTEXTCOLOR:
10180 return infoPtr->clrText;
10182 /* case LVM_GETTILEINFO: */
10184 /* case LVM_GETTILEVIEWINFO: */
10186 case LVM_GETTOOLTIPS:
10187 if( !infoPtr->hwndToolTip )
10188 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
10189 return (LRESULT)infoPtr->hwndToolTip;
10191 case LVM_GETTOPINDEX:
10192 return LISTVIEW_GetTopIndex(infoPtr);
10194 case LVM_GETUNICODEFORMAT:
10195 return (infoPtr->notifyFormat == NFR_UNICODE);
10198 return infoPtr->uView;
10200 case LVM_GETVIEWRECT:
10201 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
10203 case LVM_GETWORKAREAS:
10204 FIXME("LVM_GETWORKAREAS: unimplemented\n");
10207 /* case LVM_HASGROUP: */
10210 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, TRUE);
10212 case LVM_INSERTCOLUMNA:
10213 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
10215 case LVM_INSERTCOLUMNW:
10216 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
10218 /* case LVM_INSERTGROUP: */
10220 /* case LVM_INSERTGROUPSORTED: */
10222 case LVM_INSERTITEMA:
10223 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
10225 case LVM_INSERTITEMW:
10226 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
10228 /* case LVM_INSERTMARKHITTEST: */
10230 /* case LVM_ISGROUPVIEWENABLED: */
10232 /* case LVM_MAPIDTOINDEX: */
10234 /* case LVM_MAPINDEXTOID: */
10236 /* case LVM_MOVEGROUP: */
10238 /* case LVM_MOVEITEMTOGROUP: */
10240 case LVM_REDRAWITEMS:
10241 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
10243 /* case LVM_REMOVEALLGROUPS: */
10245 /* case LVM_REMOVEGROUP: */
10248 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
10250 case LVM_SETBKCOLOR:
10251 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
10253 /* case LVM_SETBKIMAGE: */
10255 case LVM_SETCALLBACKMASK:
10256 infoPtr->uCallbackMask = (UINT)wParam;
10259 case LVM_SETCOLUMNA:
10260 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
10262 case LVM_SETCOLUMNW:
10263 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
10265 case LVM_SETCOLUMNORDERARRAY:
10266 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
10268 case LVM_SETCOLUMNWIDTH:
10269 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
10271 case LVM_SETEXTENDEDLISTVIEWSTYLE:
10272 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
10274 /* case LVM_SETGROUPINFO: */
10276 /* case LVM_SETGROUPMETRICS: */
10278 case LVM_SETHOTCURSOR:
10279 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
10281 case LVM_SETHOTITEM:
10282 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
10284 case LVM_SETHOVERTIME:
10285 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
10287 case LVM_SETICONSPACING:
10288 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10290 case LVM_SETIMAGELIST:
10291 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
10293 /* case LVM_SETINFOTIP: */
10295 /* case LVM_SETINSERTMARK: */
10297 /* case LVM_SETINSERTMARKCOLOR: */
10302 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
10303 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, (uMsg == LVM_SETITEMW));
10306 case LVM_SETITEMCOUNT:
10307 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
10309 case LVM_SETITEMPOSITION:
10312 pt.x = (short)LOWORD(lParam);
10313 pt.y = (short)HIWORD(lParam);
10314 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
10317 case LVM_SETITEMPOSITION32:
10318 if (lParam == 0) return FALSE;
10319 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
10321 case LVM_SETITEMSTATE:
10322 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
10324 case LVM_SETITEMTEXTA:
10325 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
10327 case LVM_SETITEMTEXTW:
10328 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
10330 /* case LVM_SETOUTLINECOLOR: */
10332 /* case LVM_SETSELECTEDCOLUMN: */
10334 case LVM_SETSELECTIONMARK:
10335 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
10337 case LVM_SETTEXTBKCOLOR:
10338 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
10340 case LVM_SETTEXTCOLOR:
10341 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
10343 /* case LVM_SETTILEINFO: */
10345 /* case LVM_SETTILEVIEWINFO: */
10347 /* case LVM_SETTILEWIDTH: */
10349 case LVM_SETTOOLTIPS:
10350 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
10352 case LVM_SETUNICODEFORMAT:
10353 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
10356 return LISTVIEW_SetView(infoPtr, wParam);
10358 /* case LVM_SETWORKAREAS: */
10360 /* case LVM_SORTGROUPS: */
10362 case LVM_SORTITEMS:
10363 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam, FALSE);
10365 case LVM_SORTITEMSEX:
10366 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam, TRUE);
10368 case LVM_SUBITEMHITTEST:
10369 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
10372 return LISTVIEW_Update(infoPtr, (INT)wParam);
10374 case CCM_GETVERSION:
10375 return LISTVIEW_GetVersion(infoPtr);
10377 case CCM_SETVERSION:
10378 return LISTVIEW_SetVersion(infoPtr, wParam);
10381 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
10384 return LISTVIEW_Command(infoPtr, wParam, lParam);
10387 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
10390 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
10393 return LISTVIEW_Destroy(infoPtr);
10396 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
10398 case WM_ERASEBKGND:
10399 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
10401 case WM_GETDLGCODE:
10402 return DLGC_WANTCHARS | DLGC_WANTARROWS;
10405 return (LRESULT)infoPtr->hFont;
10408 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10411 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
10414 return LISTVIEW_KillFocus(infoPtr);
10416 case WM_LBUTTONDBLCLK:
10417 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10419 case WM_LBUTTONDOWN:
10420 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10423 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10426 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10428 case WM_MOUSEHOVER:
10429 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10432 return LISTVIEW_NCDestroy(infoPtr);
10435 if (LISTVIEW_NCPaint(infoPtr, (HRGN)wParam))
10440 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
10441 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
10444 case WM_NOTIFYFORMAT:
10445 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
10447 case WM_PRINTCLIENT:
10448 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
10451 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
10453 case WM_RBUTTONDBLCLK:
10454 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10456 case WM_RBUTTONDOWN:
10457 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10460 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10463 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
10468 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
10471 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
10474 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
10476 case WM_SHOWWINDOW:
10477 LISTVIEW_ShowWindow(infoPtr, (BOOL)wParam, (INT)lParam);
10478 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10481 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10483 case WM_STYLECHANGED:
10484 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10486 case WM_STYLECHANGING:
10487 return LISTVIEW_StyleChanging(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10489 case WM_SYSCOLORCHANGE:
10490 COMCTL32_RefreshSysColors();
10493 /* case WM_TIMER: */
10494 case WM_THEMECHANGED:
10495 return LISTVIEW_ThemeChanged(infoPtr);
10498 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10500 case WM_MOUSEWHEEL:
10501 if (wParam & (MK_SHIFT | MK_CONTROL))
10502 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10503 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
10505 case WM_WINDOWPOSCHANGED:
10506 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
10508 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
10509 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
10511 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
10513 MEASUREITEMSTRUCT mis;
10514 mis.CtlType = ODT_LISTVIEW;
10515 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
10519 mis.itemHeight= infoPtr->nItemHeight;
10520 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
10521 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
10522 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
10525 LISTVIEW_UpdateSize(infoPtr);
10526 LISTVIEW_UpdateScroll(infoPtr);
10528 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10530 /* case WM_WININICHANGE: */
10533 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
10534 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
10537 /* call default window procedure */
10538 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10545 * Registers the window class.
10553 void LISTVIEW_Register(void)
10555 WNDCLASSW wndClass;
10557 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
10558 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
10559 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
10560 wndClass.cbClsExtra = 0;
10561 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
10562 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
10563 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
10564 wndClass.lpszClassName = WC_LISTVIEWW;
10565 RegisterClassW(&wndClass);
10570 * Unregisters the window class.
10578 void LISTVIEW_Unregister(void)
10580 UnregisterClassW(WC_LISTVIEWW, NULL);
10585 * Handle any WM_COMMAND messages
10588 * [I] infoPtr : valid pointer to the listview structure
10589 * [I] wParam : the first message parameter
10590 * [I] lParam : the second message parameter
10595 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10597 switch (HIWORD(wParam))
10602 * Adjust the edit window size
10604 WCHAR buffer[1024];
10605 HDC hdc = GetDC(infoPtr->hwndEdit);
10606 HFONT hFont, hOldFont = 0;
10610 if (!infoPtr->hwndEdit || !hdc) return 0;
10611 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
10612 GetWindowRect(infoPtr->hwndEdit, &rect);
10614 /* Select font to get the right dimension of the string */
10615 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
10618 hOldFont = SelectObject(hdc, hFont);
10621 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
10623 TEXTMETRICW textMetric;
10625 /* Add Extra spacing for the next character */
10626 GetTextMetricsW(hdc, &textMetric);
10627 sz.cx += (textMetric.tmMaxCharWidth * 2);
10635 rect.bottom - rect.top,
10636 SWP_DRAWFRAME|SWP_NOMOVE);
10639 SelectObject(hdc, hOldFont);
10641 ReleaseDC(infoPtr->hwndEdit, hdc);
10647 /* handle value will be lost after LISTVIEW_EndEditLabelT */
10648 HWND edit = infoPtr->hwndEdit;
10650 LISTVIEW_EndEditLabelT(infoPtr, TRUE, IsWindowUnicode(infoPtr->hwndEdit));
10651 SendMessageW(edit, WM_CLOSE, 0, 0);
10655 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
10664 * Subclassed edit control windproc function
10667 * [I] hwnd : the edit window handle
10668 * [I] uMsg : the message that is to be processed
10669 * [I] wParam : first message parameter
10670 * [I] lParam : second message parameter
10671 * [I] isW : TRUE if input is Unicode
10676 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
10678 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
10681 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
10682 hwnd, uMsg, wParam, lParam, isW);
10686 case WM_GETDLGCODE:
10687 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
10691 WNDPROC editProc = infoPtr->EditWndProc;
10692 infoPtr->EditWndProc = 0;
10693 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
10694 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
10698 if (VK_ESCAPE == (INT)wParam)
10703 else if (VK_RETURN == (INT)wParam)
10707 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
10710 /* kill the edit */
10711 if (infoPtr->hwndEdit)
10712 LISTVIEW_EndEditLabelT(infoPtr, save, isW);
10714 SendMessageW(hwnd, WM_CLOSE, 0, 0);
10720 * Subclassed edit control Unicode windproc function
10723 * [I] hwnd : the edit window handle
10724 * [I] uMsg : the message that is to be processed
10725 * [I] wParam : first message parameter
10726 * [I] lParam : second message parameter
10730 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10732 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
10737 * Subclassed edit control ANSI windproc function
10740 * [I] hwnd : the edit window handle
10741 * [I] uMsg : the message that is to be processed
10742 * [I] wParam : first message parameter
10743 * [I] lParam : second message parameter
10747 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10749 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
10754 * Creates a subclassed edit control
10757 * [I] infoPtr : valid pointer to the listview structure
10758 * [I] text : initial text for the edit
10759 * [I] style : the window style
10760 * [I] isW : TRUE if input is Unicode
10764 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style, BOOL isW)
10766 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
10768 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
10770 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
10772 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
10774 /* Window will be resized and positioned after LVN_BEGINLABELEDIT */
10776 hedit = CreateWindowW(editName, text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
10778 hedit = CreateWindowA("Edit", (LPCSTR)text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
10780 if (!hedit) return 0;
10782 infoPtr->EditWndProc = (WNDPROC)
10783 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
10784 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
10786 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);