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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 * -- EN_KILLFOCUS should be handled in WM_COMMAND
37 * -- WM_CREATE: create the icon and small icon image lists at this point only if
38 * the LVS_SHAREIMAGELISTS style is not specified.
39 * -- WM_ERASEBKGND: forward this message to the parent window if the bkgnd
41 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
42 * or small icon and the LVS_AUTOARRANGE style is specified.
47 * -- Hot item handling, mouse hovering
48 * -- Workareas support
53 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
54 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
55 * -- LVA_SNAPTOGRID not implemented
56 * -- LISTVIEW_ApproximateViewRect partially implemented
57 * -- LISTVIEW_[GS]etColumnOrderArray stubs
58 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
59 * -- LISTVIEW_SetIconSpacing is incomplete
60 * -- LISTVIEW_SortItems is broken
61 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
64 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
65 * linear in the number of items in the list, and this is
66 * unacceptable for large lists.
67 * -- in sorted mode, LISTVIEW_InsertItemT sorts the array,
68 * instead of inserting in the right spot
69 * -- we should keep an ordered array of coordinates in iconic mode
70 * this would allow to frame items (iterator_frameditems),
71 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
79 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
86 * -- LVS_NOSCROLL (see Q137520)
87 * -- LVS_SORTASCENDING, LVS_SORTDESCENDING
89 * -- LVS_TYPESTYLEMASK
92 * -- LVS_EX_BORDERSELECT
95 * -- LVS_EX_HEADERDRAGDROP
98 * -- LVS_EX_MULTIWORKAREAS
99 * -- LVS_EX_ONECLICKACTIVATE
101 * -- LVS_EX_SIMPLESELECT
102 * -- LVS_EX_TRACKSELECT
103 * -- LVS_EX_TWOCLICKACTIVATE
104 * -- LVS_EX_UNDERLINECOLD
105 * -- LVS_EX_UNDERLINEHOT
108 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
111 * -- LVN_MARQUEEBEGIN
118 * -- LVM_CANCELEDITLABEL
119 * -- LVM_ENABLEGROUPVIEW
120 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
121 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
122 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
123 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
124 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
125 * -- LVM_GETINSERTMARKRECT
126 * -- LVM_GETNUMBEROFWORKAREAS
127 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
128 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
129 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
130 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
131 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
132 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
133 * -- LVM_GETVIEW, LVM_SETVIEW
134 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
135 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
136 * -- LVM_INSERTGROUPSORTED
137 * -- LVM_INSERTMARKHITTEST
138 * -- LVM_ISGROUPVIEWENABLED
139 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
141 * -- LVM_MOVEITEMTOGROUP
143 * -- LVM_SETTILEWIDTH
148 * -- ListView_GetCheckSate, ListView_SetCheckState
149 * -- ListView_GetHoverTime, ListView_SetHoverTime
150 * -- ListView_GetISearchString
151 * -- ListView_GetNumberOfWorkAreas
152 * -- ListView_GetOrigin
153 * -- ListView_GetTextBkColor
154 * -- ListView_GetUnicodeFormat, ListView_SetUnicodeFormat
155 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
156 * -- ListView_SortItemsEx
161 * Known differences in message stream from native control (not known if
162 * these differences cause problems):
163 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
164 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
165 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
166 * processing for "USEDOUBLECLICKTIME".
170 #include "wine/port.h"
185 #include "commctrl.h"
186 #include "comctl32.h"
189 #include "wine/debug.h"
190 #include "wine/unicode.h"
192 WINE_DEFAULT_DEBUG_CHANNEL(listview);
194 /* make sure you set this to 0 for production use! */
195 #define DEBUG_RANGES 1
197 typedef struct tagCOLUMN_INFO
199 RECT rcHeader; /* tracks the header's rectangle */
200 int fmt; /* same as LVCOLUMN.fmt */
203 typedef struct tagITEMHDR
207 } ITEMHDR, *LPITEMHDR;
209 typedef struct tagSUBITEM_INFO
215 typedef struct tagITEM_INFO
223 typedef struct tagRANGE
229 typedef struct tagRANGES
234 typedef struct tagITERATOR
243 typedef struct tagLISTVIEW_INFO
250 COLORREF clrTextBkDefault;
251 HIMAGELIST himlNormal;
252 HIMAGELIST himlSmall;
253 HIMAGELIST himlState;
256 POINT ptClickPos; /* point where the user clicked */
257 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
260 RANGES selectionRanges;
265 RECT rcList; /* This rectangle is really the window
266 * client rectangle possibly reduced by the
267 * horizontal scroll bar and/or header - see
268 * LISTVIEW_UpdateSize. This rectangle offset
269 * by the LISTVIEW_GetOrigin value is in
270 * client coordinates */
279 INT ntmHeight; /* Some cached metrics of the font used */
280 INT ntmMaxCharWidth; /* by the listview to draw items */
282 BOOL bRedraw; /* Turns on/off repaints & invalidations */
283 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
285 BOOL bDoChangeNotify; /* send change notification messages? */
288 DWORD dwStyle; /* the cached window GWL_STYLE */
289 DWORD dwLvExStyle; /* extended listview style */
290 INT nItemCount; /* the number of items in the list */
291 HDPA hdpaItems; /* array ITEM_INFO pointers */
292 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
293 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
294 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
295 POINT currIconPos; /* this is the position next icon will be placed */
296 PFNLVCOMPARE pfnCompare;
304 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
306 DWORD lastKeyPressTimestamp;
308 INT nSearchParamLength;
309 WCHAR szSearchParam[ MAX_PATH ];
311 INT nMeasureItemHeight;
317 /* How many we debug buffer to allocate */
318 #define DEBUG_BUFFERS 20
319 /* The size of a single debug bbuffer */
320 #define DEBUG_BUFFER_SIZE 256
322 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
323 #define SB_INTERNAL -1
325 /* maximum size of a label */
326 #define DISP_TEXT_SIZE 512
328 /* padding for items in list and small icon display modes */
329 #define WIDTH_PADDING 12
331 /* padding for items in list, report and small icon display modes */
332 #define HEIGHT_PADDING 1
334 /* offset of items in report display mode */
335 #define REPORT_MARGINX 2
337 /* padding for icon in large icon display mode
338 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
339 * that HITTEST will see.
340 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
341 * ICON_TOP_PADDING - sum of the two above.
342 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
343 * LABEL_HOR_PADDING - between text and sides of box
344 * LABEL_VERT_PADDING - between bottom of text and end of box
346 * ICON_LR_PADDING - additional width above icon size.
347 * ICON_LR_HALF - half of the above value
349 #define ICON_TOP_PADDING_NOTHITABLE 2
350 #define ICON_TOP_PADDING_HITABLE 2
351 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
352 #define ICON_BOTTOM_PADDING 4
353 #define LABEL_HOR_PADDING 5
354 #define LABEL_VERT_PADDING 7
355 #define ICON_LR_PADDING 16
356 #define ICON_LR_HALF (ICON_LR_PADDING/2)
358 /* default label width for items in list and small icon display modes */
359 #define DEFAULT_LABEL_WIDTH 40
361 /* default column width for items in list display mode */
362 #define DEFAULT_COLUMN_WIDTH 128
364 /* Size of "line" scroll for V & H scrolls */
365 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
367 /* Padding betwen image and label */
368 #define IMAGE_PADDING 2
370 /* Padding behind the label */
371 #define TRAILING_LABEL_PADDING 12
372 #define TRAILING_HEADER_PADDING 11
374 /* Border for the icon caption */
375 #define CAPTION_BORDER 2
377 /* Standard DrawText flags */
378 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
379 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
380 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
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%06lx, clrText=0x%06lx, clrTextBk=0x%06lx, ItemHeight=%d, ItemWidth=%d, Style=0x%08lx\n", \
388 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
389 iP->nItemHeight, iP->nItemWidth, infoPtr->dwStyle); \
390 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08lx, 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=%ld, icSz.cy=%ld, icSp.cx=%ld, icSp.cy=%ld, 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, debugrect(&iP->rcList)); \
399 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
402 * forward declarations
404 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *, LPLVITEMW, BOOL);
405 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *, INT, LPRECT);
406 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *, INT, LPPOINT);
407 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *, INT, LPPOINT);
408 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *, INT, LPRECT);
409 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *, INT);
410 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *, LPPOINT);
411 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *, LPRECT);
412 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
413 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, const LVITEMW *, BOOL);
414 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *);
415 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
416 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
417 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
418 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
419 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *, PFNLVCOMPARE, LPARAM);
420 static INT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *, LPCWSTR, BOOL);
421 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT);
422 static UINT LISTVIEW_GetItemState(LISTVIEW_INFO *, INT, UINT);
423 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
424 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
425 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
426 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *);
427 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
428 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
429 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
430 static INT LISTVIEW_HitTest(LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
432 /******** Text handling functions *************************************/
434 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
435 * text string. The string may be ANSI or Unicode, in which case
436 * the boolean isW tells us the type of the string.
438 * The name of the function tell what type of strings it expects:
439 * W: Unicode, T: ANSI/Unicode - function of isW
442 static inline BOOL is_textW(LPCWSTR text)
444 return text != NULL && text != LPSTR_TEXTCALLBACKW;
447 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
449 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
450 return is_textW(text);
453 static inline int textlenT(LPCWSTR text, BOOL isW)
455 return !is_textT(text, isW) ? 0 :
456 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
459 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
462 if (isSrcW) lstrcpynW(dest, src, max);
463 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
465 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
466 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
469 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
471 LPWSTR wstr = (LPWSTR)text;
473 if (!isW && is_textT(text, isW))
475 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
476 wstr = Alloc(len * sizeof(WCHAR));
477 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
479 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
483 static inline void textfreeT(LPWSTR wstr, BOOL isW)
485 if (!isW && is_textT(wstr, isW)) Free (wstr);
489 * dest is a pointer to a Unicode string
490 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
492 static BOOL textsetptrT(LPWSTR *dest, LPWSTR src, BOOL isW)
496 if (src == LPSTR_TEXTCALLBACKW)
498 if (is_textW(*dest)) Free(*dest);
499 *dest = LPSTR_TEXTCALLBACKW;
503 LPWSTR pszText = textdupTtoW(src, isW);
504 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
505 bResult = Str_SetPtrW(dest, pszText);
506 textfreeT(pszText, isW);
512 * compares a Unicode to a Unicode/ANSI text string
514 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
516 if (!aw) return bt ? -1 : 0;
517 if (!bt) return aw ? 1 : 0;
518 if (aw == LPSTR_TEXTCALLBACKW)
519 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
520 if (bt != LPSTR_TEXTCALLBACKW)
522 LPWSTR bw = textdupTtoW(bt, isW);
523 int r = bw ? lstrcmpW(aw, bw) : 1;
531 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
535 n = min(min(n, strlenW(s1)), strlenW(s2));
536 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
537 return res ? res - sizeof(WCHAR) : res;
540 /******** Debugging functions *****************************************/
542 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
544 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
545 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
548 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
550 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
551 n = min(textlenT(text, isW), n);
552 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
555 static char* debug_getbuf(void)
557 static int index = 0;
558 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
559 return buffers[index++ % DEBUG_BUFFERS];
562 static inline const char* debugrange(const RANGE *lprng)
566 char* buf = debug_getbuf();
567 snprintf(buf, DEBUG_BUFFER_SIZE, "[%d, %d)", lprng->lower, lprng->upper);
569 } else return "(null)";
572 static inline const char* debugpoint(const POINT *lppt)
576 char* buf = debug_getbuf();
577 snprintf(buf, DEBUG_BUFFER_SIZE, "(%ld, %ld)", lppt->x, lppt->y);
579 } else return "(null)";
582 static inline const char* debugrect(const RECT *rect)
586 char* buf = debug_getbuf();
587 snprintf(buf, DEBUG_BUFFER_SIZE, "[(%ld, %ld);(%ld, %ld)]",
588 rect->left, rect->top, rect->right, rect->bottom);
590 } else return "(null)";
593 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
595 char* buf = debug_getbuf(), *text = buf;
596 int len, size = DEBUG_BUFFER_SIZE;
598 if (pScrollInfo == NULL) return "(null)";
599 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
600 if (len == -1) goto end; buf += len; size -= len;
601 if (pScrollInfo->fMask & SIF_RANGE)
602 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
604 if (len == -1) goto end; buf += len; size -= len;
605 if (pScrollInfo->fMask & SIF_PAGE)
606 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
608 if (len == -1) goto end; buf += len; size -= len;
609 if (pScrollInfo->fMask & SIF_POS)
610 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
612 if (len == -1) goto end; buf += len; size -= len;
613 if (pScrollInfo->fMask & SIF_TRACKPOS)
614 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
616 if (len == -1) goto end; buf += len; size -= len;
619 buf = text + strlen(text);
621 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
625 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
629 char* buf = debug_getbuf();
630 snprintf(buf, DEBUG_BUFFER_SIZE, "iItem=%d, iSubItem=%d, uNewState=0x%x,"
631 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld\n",
632 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
633 plvnm->uChanged, debugpoint(&plvnm->ptAction), plvnm->lParam);
635 } else return "(null)";
638 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
640 char* buf = debug_getbuf(), *text = buf;
641 int len, size = DEBUG_BUFFER_SIZE;
643 if (lpLVItem == NULL) return "(null)";
644 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
645 if (len == -1) goto end; buf += len; size -= len;
646 if (lpLVItem->mask & LVIF_STATE)
647 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
649 if (len == -1) goto end; buf += len; size -= len;
650 if (lpLVItem->mask & LVIF_TEXT)
651 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
653 if (len == -1) goto end; buf += len; size -= len;
654 if (lpLVItem->mask & LVIF_IMAGE)
655 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
657 if (len == -1) goto end; buf += len; size -= len;
658 if (lpLVItem->mask & LVIF_PARAM)
659 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
661 if (len == -1) goto end; buf += len; size -= len;
662 if (lpLVItem->mask & LVIF_INDENT)
663 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
665 if (len == -1) goto end; buf += len; size -= len;
668 buf = text + strlen(text);
670 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
674 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
676 char* buf = debug_getbuf(), *text = buf;
677 int len, size = DEBUG_BUFFER_SIZE;
679 if (lpColumn == NULL) return "(null)";
680 len = snprintf(buf, size, "{");
681 if (len == -1) goto end; buf += len; size -= len;
682 if (lpColumn->mask & LVCF_SUBITEM)
683 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
685 if (len == -1) goto end; buf += len; size -= len;
686 if (lpColumn->mask & LVCF_FMT)
687 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
689 if (len == -1) goto end; buf += len; size -= len;
690 if (lpColumn->mask & LVCF_WIDTH)
691 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
693 if (len == -1) goto end; buf += len; size -= len;
694 if (lpColumn->mask & LVCF_TEXT)
695 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
697 if (len == -1) goto end; buf += len; size -= len;
698 if (lpColumn->mask & LVCF_IMAGE)
699 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
701 if (len == -1) goto end; buf += len; size -= len;
702 if (lpColumn->mask & LVCF_ORDER)
703 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
705 if (len == -1) goto end; buf += len; size -= len;
708 buf = text + strlen(text);
710 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
714 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
718 char* buf = debug_getbuf();
719 snprintf(buf, DEBUG_BUFFER_SIZE, "{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
720 debugpoint(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
722 } else return "(null)";
725 /* Return the corresponding text for a given scroll value */
726 static inline LPCSTR debugscrollcode(int nScrollCode)
730 case SB_LINELEFT: return "SB_LINELEFT";
731 case SB_LINERIGHT: return "SB_LINERIGHT";
732 case SB_PAGELEFT: return "SB_PAGELEFT";
733 case SB_PAGERIGHT: return "SB_PAGERIGHT";
734 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
735 case SB_THUMBTRACK: return "SB_THUMBTRACK";
736 case SB_ENDSCROLL: return "SB_ENDSCROLL";
737 case SB_INTERNAL: return "SB_INTERNAL";
738 default: return "unknown";
743 /******** Notification functions i************************************/
745 static LRESULT notify_forward_header(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
747 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
748 (WPARAM)lpnmh->hdr.idFrom, (LPARAM)lpnmh);
751 static LRESULT notify_hdr(LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
755 TRACE("(code=%d)\n", code);
757 pnmh->hwndFrom = infoPtr->hwndSelf;
758 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
760 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
761 (WPARAM)pnmh->idFrom, (LPARAM)pnmh);
763 TRACE(" <= %ld\n", result);
768 static inline LRESULT notify(LISTVIEW_INFO *infoPtr, INT code)
771 return notify_hdr(infoPtr, code, &nmh);
774 static inline void notify_itemactivate(LISTVIEW_INFO *infoPtr, LVHITTESTINFO *htInfo)
785 item.mask = LVIF_PARAM|LVIF_STATE;
786 item.iItem = htInfo->iItem;
788 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
789 nmia.lParam = item.lParam;
790 nmia.uOldState = item.state;
791 nmia.uNewState = item.state | LVIS_ACTIVATING;
792 nmia.uChanged = LVIF_STATE;
795 nmia.iItem = htInfo->iItem;
796 nmia.iSubItem = htInfo->iSubItem;
797 nmia.ptAction = htInfo->pt;
799 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
800 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
801 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
803 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
806 static inline LRESULT notify_listview(LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
808 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
809 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
812 static LRESULT notify_click(LISTVIEW_INFO *infoPtr, INT code, LVHITTESTINFO *lvht)
817 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
818 ZeroMemory(&nmlv, sizeof(nmlv));
819 nmlv.iItem = lvht->iItem;
820 nmlv.iSubItem = lvht->iSubItem;
821 nmlv.ptAction = lvht->pt;
822 item.mask = LVIF_PARAM;
823 item.iItem = lvht->iItem;
825 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
826 return notify_listview(infoPtr, code, &nmlv);
829 static void notify_deleteitem(LISTVIEW_INFO *infoPtr, INT nItem)
834 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
836 item.mask = LVIF_PARAM;
839 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
840 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
843 static int get_ansi_notification(INT unicodeNotificationCode)
845 switch (unicodeNotificationCode)
847 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
848 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
849 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
850 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
851 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
852 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
854 ERR("unknown notification %x\n", unicodeNotificationCode);
860 Send notification. depends on dispinfoW having same
861 structure as dispinfoA.
862 infoPtr : listview struct
863 notificationCode : *Unicode* notification code
864 pdi : dispinfo structure (can be unicode or ansi)
865 isW : TRUE if dispinfo is Unicode
867 static BOOL notify_dispinfoT(LISTVIEW_INFO *infoPtr, INT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
869 BOOL bResult = FALSE;
870 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
871 INT cchTempBufMax = 0, savCchTextMax = 0, realNotifCode;
872 LPWSTR pszTempBuf = NULL, savPszText = NULL;
874 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
876 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
877 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
880 if (convertToAnsi || convertToUnicode)
882 if (notificationCode != LVN_GETDISPINFOW)
884 cchTempBufMax = convertToUnicode ?
885 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
886 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
890 cchTempBufMax = pdi->item.cchTextMax;
891 *pdi->item.pszText = 0; /* make sure we don't process garbage */
894 pszTempBuf = Alloc( (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
895 if (!pszTempBuf) return FALSE;
897 if (convertToUnicode)
898 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
899 pszTempBuf, cchTempBufMax);
901 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
902 cchTempBufMax, NULL, NULL);
904 savCchTextMax = pdi->item.cchTextMax;
905 savPszText = pdi->item.pszText;
906 pdi->item.pszText = pszTempBuf;
907 pdi->item.cchTextMax = cchTempBufMax;
910 if (infoPtr->notifyFormat == NFR_ANSI)
911 realNotifCode = get_ansi_notification(notificationCode);
913 realNotifCode = notificationCode;
914 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
915 bResult = notify_hdr(infoPtr, realNotifCode, &pdi->hdr);
917 if (convertToUnicode || convertToAnsi)
919 if (convertToUnicode) /* note : pointer can be changed by app ! */
920 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
921 savCchTextMax, NULL, NULL);
923 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
924 savPszText, savCchTextMax);
925 pdi->item.pszText = savPszText; /* restores our buffer */
926 pdi->item.cchTextMax = savCchTextMax;
932 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, LISTVIEW_INFO *infoPtr, HDC hdc,
933 const RECT *rcBounds, const LVITEMW *lplvItem)
935 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
936 lpnmlvcd->nmcd.hdc = hdc;
937 lpnmlvcd->nmcd.rc = *rcBounds;
938 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
939 lpnmlvcd->clrText = infoPtr->clrText;
940 if (!lplvItem) return;
941 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
942 lpnmlvcd->iSubItem = lplvItem->iSubItem;
943 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
944 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
945 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
946 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
949 static inline DWORD notify_customdraw (LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
951 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
954 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
955 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
956 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
957 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
958 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
959 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
963 static void prepaint_setup (LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd)
965 /* apprently, for selected items, we have to override the returned values */
966 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
970 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
971 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
973 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
975 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
976 lpnmlvcd->clrText = comctl32_color.clrBtnText;
980 /* Set the text attributes */
981 if (lpnmlvcd->clrTextBk != CLR_NONE)
983 SetBkMode(hdc, OPAQUE);
984 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
985 SetBkColor(hdc, infoPtr->clrTextBkDefault);
987 SetBkColor(hdc,lpnmlvcd->clrTextBk);
990 SetBkMode(hdc, TRANSPARENT);
991 SetTextColor(hdc, lpnmlvcd->clrText);
994 static inline DWORD notify_postpaint (LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
996 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
999 /******** Item iterator functions **********************************/
1001 static RANGES ranges_create(int count);
1002 static void ranges_destroy(RANGES ranges);
1003 static BOOL ranges_add(RANGES ranges, RANGE range);
1004 static BOOL ranges_del(RANGES ranges, RANGE range);
1005 static void ranges_dump(RANGES ranges);
1007 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
1009 RANGE range = { nItem, nItem + 1 };
1011 return ranges_add(ranges, range);
1014 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1016 RANGE range = { nItem, nItem + 1 };
1018 return ranges_del(ranges, range);
1022 * ITERATOR DOCUMENTATION
1024 * The iterator functions allow for easy, and convenient iteration
1025 * over items of iterest in the list. Typically, you create a
1026 * iterator, use it, and destroy it, as such:
1029 * iterator_xxxitems(&i, ...);
1030 * while (iterator_{prev,next}(&i)
1032 * //code which uses i.nItem
1034 * iterator_destroy(&i);
1036 * where xxx is either: framed, or visible.
1037 * Note that it is important that the code destroys the iterator
1038 * after it's done with it, as the creation of the iterator may
1039 * allocate memory, which thus needs to be freed.
1041 * You can iterate both forwards, and backwards through the list,
1042 * by using iterator_next or iterator_prev respectively.
1044 * Lower numbered items are draw on top of higher number items in
1045 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1046 * items may overlap). So, to test items, you should use
1048 * which lists the items top to bottom (in Z-order).
1049 * For drawing items, you should use
1051 * which lists the items bottom to top (in Z-order).
1052 * If you keep iterating over the items after the end-of-items
1053 * marker (-1) is returned, the iterator will start from the
1054 * beginning. Typically, you don't need to test for -1,
1055 * because iterator_{next,prev} will return TRUE if more items
1056 * are to be iterated over, or FALSE otherwise.
1058 * Note: the iterator is defined to be bidirectional. That is,
1059 * any number of prev followed by any number of next, or
1060 * five versa, should leave the iterator at the same item:
1061 * prev * n, next * n = next * n, prev * n
1063 * The iterator has a notion of an out-of-order, special item,
1064 * which sits at the start of the list. This is used in
1065 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1066 * which needs to be first, as it may overlap other items.
1068 * The code is a bit messy because we have:
1069 * - a special item to deal with
1070 * - simple range, or composite range
1072 * If you find bugs, or want to add features, please make sure you
1073 * always check/modify *both* iterator_prev, and iterator_next.
1077 * This function iterates through the items in increasing order,
1078 * but prefixed by the special item, then -1. That is:
1079 * special, 1, 2, 3, ..., n, -1.
1080 * Each item is listed only once.
1082 static inline BOOL iterator_next(ITERATOR* i)
1086 i->nItem = i->nSpecial;
1087 if (i->nItem != -1) return TRUE;
1089 if (i->nItem == i->nSpecial)
1091 if (i->ranges) i->index = 0;
1097 if (i->nItem == i->nSpecial) i->nItem++;
1098 if (i->nItem < i->range.upper) return TRUE;
1103 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1104 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1107 else if (i->nItem >= i->range.upper) goto end;
1109 i->nItem = i->range.lower;
1110 if (i->nItem >= 0) goto testitem;
1117 * This function iterates through the items in decreasing order,
1118 * followed by the special item, then -1. That is:
1119 * n, n-1, ..., 3, 2, 1, special, -1.
1120 * Each item is listed only once.
1122 static inline BOOL iterator_prev(ITERATOR* i)
1129 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1132 if (i->nItem == i->nSpecial)
1140 if (i->nItem == i->nSpecial) i->nItem--;
1141 if (i->nItem >= i->range.lower) return TRUE;
1147 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1150 else if (!start && i->nItem < i->range.lower) goto end;
1152 i->nItem = i->range.upper;
1153 if (i->nItem > 0) goto testitem;
1155 return (i->nItem = i->nSpecial) != -1;
1158 static RANGE iterator_range(ITERATOR* i)
1162 if (!i->ranges) return i->range;
1164 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1166 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1167 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1169 else range.lower = range.upper = 0;
1175 * Releases resources associated with this ierator.
1177 static inline void iterator_destroy(ITERATOR* i)
1179 ranges_destroy(i->ranges);
1183 * Create an empty iterator.
1185 static inline BOOL iterator_empty(ITERATOR* i)
1187 ZeroMemory(i, sizeof(*i));
1188 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1193 * Create an iterator over a range.
1195 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1203 * Create an iterator over a bunch of ranges.
1204 * Please note that the iterator will take ownership of the ranges,
1205 * and will free them upon destruction.
1207 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1215 * Creates an iterator over the items which intersect lprc.
1217 static BOOL iterator_frameditems(ITERATOR* i, LISTVIEW_INFO* infoPtr, const RECT *lprc)
1219 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1220 RECT frame = *lprc, rcItem, rcTemp;
1223 /* in case we fail, we want to return an empty iterator */
1224 if (!iterator_empty(i)) return FALSE;
1226 LISTVIEW_GetOrigin(infoPtr, &Origin);
1228 TRACE("(lprc=%s)\n", debugrect(lprc));
1229 OffsetRect(&frame, -Origin.x, -Origin.y);
1231 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1235 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1237 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1238 if (IntersectRect(&rcTemp, &rcItem, lprc))
1239 i->nSpecial = infoPtr->nFocusedItem;
1241 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1242 /* to do better here, we need to have PosX, and PosY sorted */
1243 TRACE("building icon ranges:\n");
1244 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1246 rcItem.left = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1247 rcItem.top = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1248 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1249 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1250 if (IntersectRect(&rcTemp, &rcItem, &frame))
1251 ranges_additem(i->ranges, nItem);
1255 else if (uView == LVS_REPORT)
1259 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1260 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1262 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1263 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1264 if (range.upper <= range.lower) return TRUE;
1265 if (!iterator_rangeitems(i, range)) return FALSE;
1266 TRACE(" report=%s\n", debugrange(&i->range));
1270 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1271 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1272 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1273 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1274 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1275 INT lower = nFirstCol * nPerCol + nFirstRow;
1279 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1280 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1282 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1284 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1285 TRACE("building list ranges:\n");
1286 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1288 item_range.lower = nCol * nPerCol + nFirstRow;
1289 if(item_range.lower >= infoPtr->nItemCount) break;
1290 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1291 TRACE(" list=%s\n", debugrange(&item_range));
1292 ranges_add(i->ranges, item_range);
1300 * Creates an iterator over the items which intersect the visible region of hdc.
1302 static BOOL iterator_visibleitems(ITERATOR *i, LISTVIEW_INFO *infoPtr, HDC hdc)
1304 POINT Origin, Position;
1305 RECT rcItem, rcClip;
1308 rgntype = GetClipBox(hdc, &rcClip);
1309 if (rgntype == NULLREGION) return iterator_empty(i);
1310 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1311 if (rgntype == SIMPLEREGION) return TRUE;
1313 /* first deal with the special item */
1314 if (i->nSpecial != -1)
1316 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1317 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1320 /* if we can't deal with the region, we'll just go with the simple range */
1321 LISTVIEW_GetOrigin(infoPtr, &Origin);
1322 TRACE("building visible range:\n");
1323 if (!i->ranges && i->range.lower < i->range.upper)
1325 if (!(i->ranges = ranges_create(50))) return TRUE;
1326 if (!ranges_add(i->ranges, i->range))
1328 ranges_destroy(i->ranges);
1334 /* now delete the invisible items from the list */
1335 while(iterator_next(i))
1337 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1338 rcItem.left = Position.x + Origin.x;
1339 rcItem.top = Position.y + Origin.y;
1340 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1341 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1342 if (!RectVisible(hdc, &rcItem))
1343 ranges_delitem(i->ranges, i->nItem);
1345 /* the iterator should restart on the next iterator_next */
1351 /******** Misc helper functions ************************************/
1353 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1354 WPARAM wParam, LPARAM lParam, BOOL isW)
1356 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1357 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1360 static inline BOOL is_autoarrange(LISTVIEW_INFO *infoPtr)
1362 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1364 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1365 (uView == LVS_ICON || uView == LVS_SMALLICON);
1368 /******** Internal API functions ************************************/
1370 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(LISTVIEW_INFO *infoPtr, INT nSubItem)
1372 static COLUMN_INFO mainItem;
1374 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1375 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1376 return (COLUMN_INFO *)DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1379 static inline void LISTVIEW_GetHeaderRect(LISTVIEW_INFO *infoPtr, INT nSubItem, RECT *lprc)
1381 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1384 static inline BOOL LISTVIEW_GetItemW(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1386 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1389 /* Listview invalidation functions: use _only_ these functions to invalidate */
1391 static inline BOOL is_redrawing(LISTVIEW_INFO *infoPtr)
1393 return infoPtr->bRedraw;
1396 static inline void LISTVIEW_InvalidateRect(LISTVIEW_INFO *infoPtr, const RECT* rect)
1398 if(!is_redrawing(infoPtr)) return;
1399 TRACE(" invalidating rect=%s\n", debugrect(rect));
1400 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1403 static inline void LISTVIEW_InvalidateItem(LISTVIEW_INFO *infoPtr, INT nItem)
1407 if(!is_redrawing(infoPtr)) return;
1408 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1409 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1412 static inline void LISTVIEW_InvalidateSubItem(LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1414 POINT Origin, Position;
1417 if(!is_redrawing(infoPtr)) return;
1418 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1419 LISTVIEW_GetOrigin(infoPtr, &Origin);
1420 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1421 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1423 rcBox.bottom = infoPtr->nItemHeight;
1424 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1425 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1428 static inline void LISTVIEW_InvalidateList(LISTVIEW_INFO *infoPtr)
1430 LISTVIEW_InvalidateRect(infoPtr, NULL);
1433 static inline void LISTVIEW_InvalidateColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
1437 if(!is_redrawing(infoPtr)) return;
1438 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1439 rcCol.top = infoPtr->rcList.top;
1440 rcCol.bottom = infoPtr->rcList.bottom;
1441 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1446 * Retrieves the number of items that can fit vertically in the client area.
1449 * [I] infoPtr : valid pointer to the listview structure
1452 * Number of items per row.
1454 static inline INT LISTVIEW_GetCountPerRow(LISTVIEW_INFO *infoPtr)
1456 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1458 return max(nListWidth/infoPtr->nItemWidth, 1);
1463 * Retrieves the number of items that can fit horizontally in the client
1467 * [I] infoPtr : valid pointer to the listview structure
1470 * Number of items per column.
1472 static inline INT LISTVIEW_GetCountPerColumn(LISTVIEW_INFO *infoPtr)
1474 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1476 return max(nListHeight / infoPtr->nItemHeight, 1);
1480 /*************************************************************************
1481 * LISTVIEW_ProcessLetterKeys
1483 * Processes keyboard messages generated by pressing the letter keys
1485 * What this does is perform a case insensitive search from the
1486 * current position with the following quirks:
1487 * - If two chars or more are pressed in quick succession we search
1488 * for the corresponding string (e.g. 'abc').
1489 * - If there is a delay we wipe away the current search string and
1490 * restart with just that char.
1491 * - If the user keeps pressing the same character, whether slowly or
1492 * fast, so that the search string is entirely composed of this
1493 * character ('aaaaa' for instance), then we search for first item
1494 * that starting with that character.
1495 * - If the user types the above character in quick succession, then
1496 * we must also search for the corresponding string ('aaaaa'), and
1497 * go to that string if there is a match.
1500 * [I] hwnd : handle to the window
1501 * [I] charCode : the character code, the actual character
1502 * [I] keyData : key data
1510 * - The current implementation has a list of characters it will
1511 * accept and it ignores averything else. In particular it will
1512 * ignore accentuated characters which seems to match what
1513 * Windows does. But I'm not sure it makes sense to follow
1515 * - We don't sound a beep when the search fails.
1519 * TREEVIEW_ProcessLetterKeys
1521 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1526 WCHAR buffer[MAX_PATH];
1527 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1529 /* simple parameter checking */
1530 if (!charCode || !keyData) return 0;
1532 /* only allow the valid WM_CHARs through */
1533 if (!isalnum(charCode) &&
1534 charCode != '.' && charCode != '`' && charCode != '!' &&
1535 charCode != '@' && charCode != '#' && charCode != '$' &&
1536 charCode != '%' && charCode != '^' && charCode != '&' &&
1537 charCode != '*' && charCode != '(' && charCode != ')' &&
1538 charCode != '-' && charCode != '_' && charCode != '+' &&
1539 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1540 charCode != '}' && charCode != '[' && charCode != '{' &&
1541 charCode != '/' && charCode != '?' && charCode != '>' &&
1542 charCode != '<' && charCode != ',' && charCode != '~')
1545 /* if there's one item or less, there is no where to go */
1546 if (infoPtr->nItemCount <= 1) return 0;
1548 /* update the search parameters */
1549 infoPtr->lastKeyPressTimestamp = GetTickCount();
1550 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1551 if (infoPtr->nSearchParamLength < MAX_PATH)
1552 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1553 if (infoPtr->charCode != charCode)
1554 infoPtr->charCode = charCode = 0;
1556 infoPtr->charCode=charCode;
1557 infoPtr->szSearchParam[0]=charCode;
1558 infoPtr->nSearchParamLength=1;
1559 /* Redundant with the 1 char string */
1563 /* and search from the current position */
1565 if (infoPtr->nFocusedItem >= 0) {
1566 endidx=infoPtr->nFocusedItem;
1568 /* if looking for single character match,
1569 * then we must always move forward
1571 if (infoPtr->nSearchParamLength == 1)
1574 endidx=infoPtr->nItemCount;
1578 if (idx == infoPtr->nItemCount) {
1579 if (endidx == infoPtr->nItemCount || endidx == 0)
1585 item.mask = LVIF_TEXT;
1588 item.pszText = buffer;
1589 item.cchTextMax = MAX_PATH;
1590 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1592 /* check for a match */
1593 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1596 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1597 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1598 /* This would work but we must keep looking for a longer match */
1602 } while (idx != endidx);
1605 LISTVIEW_KeySelection(infoPtr, nItem);
1610 /*************************************************************************
1611 * LISTVIEW_UpdateHeaderSize [Internal]
1613 * Function to resize the header control
1616 * [I] hwnd : handle to a window
1617 * [I] nNewScrollPos : scroll pos to set
1622 static void LISTVIEW_UpdateHeaderSize(LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1627 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1629 GetWindowRect(infoPtr->hwndHeader, &winRect);
1630 point[0].x = winRect.left;
1631 point[0].y = winRect.top;
1632 point[1].x = winRect.right;
1633 point[1].y = winRect.bottom;
1635 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1636 point[0].x = -nNewScrollPos;
1637 point[1].x += nNewScrollPos;
1639 SetWindowPos(infoPtr->hwndHeader,0,
1640 point[0].x,point[0].y,point[1].x,point[1].y,
1641 SWP_NOZORDER | SWP_NOACTIVATE);
1646 * Update the scrollbars. This functions should be called whenever
1647 * the content, size or view changes.
1650 * [I] infoPtr : valid pointer to the listview structure
1655 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *infoPtr)
1657 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1658 SCROLLINFO horzInfo, vertInfo;
1660 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1662 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1663 horzInfo.cbSize = sizeof(SCROLLINFO);
1664 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1666 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1667 if (uView == LVS_LIST)
1669 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1670 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1672 /* scroll by at least one column per page */
1673 if(horzInfo.nPage < infoPtr->nItemWidth)
1674 horzInfo.nPage = infoPtr->nItemWidth;
1676 horzInfo.nPage /= infoPtr->nItemWidth;
1678 else if (uView == LVS_REPORT)
1680 horzInfo.nMax = infoPtr->nItemWidth;
1682 else /* LVS_ICON, or LVS_SMALLICON */
1686 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1689 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1690 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1691 SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1692 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1694 /* Setting the horizontal scroll can change the listview size
1695 * (and potentially everything else) so we need to recompute
1696 * everything again for the vertical scroll
1699 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1700 vertInfo.cbSize = sizeof(SCROLLINFO);
1701 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1703 if (uView == LVS_REPORT)
1705 vertInfo.nMax = infoPtr->nItemCount;
1707 /* scroll by at least one page */
1708 if(vertInfo.nPage < infoPtr->nItemHeight)
1709 vertInfo.nPage = infoPtr->nItemHeight;
1711 vertInfo.nPage /= infoPtr->nItemHeight;
1713 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1717 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1720 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1721 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1722 SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1723 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1725 /* Update the Header Control */
1726 if (uView == LVS_REPORT)
1728 horzInfo.fMask = SIF_POS;
1729 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1730 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1737 * Shows/hides the focus rectangle.
1740 * [I] infoPtr : valid pointer to the listview structure
1741 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1746 static void LISTVIEW_ShowFocusRect(LISTVIEW_INFO *infoPtr, BOOL fShow)
1748 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1751 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1753 if (infoPtr->nFocusedItem < 0) return;
1755 /* we need some gymnastics in ICON mode to handle large items */
1756 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1760 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1761 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1763 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1768 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1770 /* for some reason, owner draw should work only in report mode */
1771 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1776 item.iItem = infoPtr->nFocusedItem;
1778 item.mask = LVIF_PARAM;
1779 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1781 ZeroMemory(&dis, sizeof(dis));
1782 dis.CtlType = ODT_LISTVIEW;
1783 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1784 dis.itemID = item.iItem;
1785 dis.itemAction = ODA_FOCUS;
1786 if (fShow) dis.itemState |= ODS_FOCUS;
1787 dis.hwndItem = infoPtr->hwndSelf;
1789 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1790 dis.itemData = item.lParam;
1792 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1796 DrawFocusRect(hdc, &infoPtr->rcFocus);
1799 ReleaseDC(infoPtr->hwndSelf, hdc);
1803 * Invalidates all visible selected items.
1805 static void LISTVIEW_InvalidateSelectedItems(LISTVIEW_INFO *infoPtr)
1809 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1810 while(iterator_next(&i))
1812 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1813 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1815 iterator_destroy(&i);
1820 * DESCRIPTION: [INTERNAL]
1821 * Computes an item's (left,top) corner, relative to rcView.
1822 * That is, the position has NOT been made relative to the Origin.
1823 * This is deliberate, to avoid computing the Origin over, and
1824 * over again, when this function is call in a loop. Instead,
1825 * one ca factor the computation of the Origin before the loop,
1826 * and offset the value retured by this function, on every iteration.
1829 * [I] infoPtr : valid pointer to the listview structure
1830 * [I] nItem : item number
1831 * [O] lpptOrig : item top, left corner
1836 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1838 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1840 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1842 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1844 lpptPosition->x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1845 lpptPosition->y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1847 else if (uView == LVS_LIST)
1849 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1850 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1851 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1853 else /* LVS_REPORT */
1855 lpptPosition->x = 0;
1856 lpptPosition->y = nItem * infoPtr->nItemHeight;
1861 * DESCRIPTION: [INTERNAL]
1862 * Compute the rectangles of an item. This is to localize all
1863 * the computations in one place. If you are not interested in some
1864 * of these values, simply pass in a NULL -- the fucntion is smart
1865 * enough to compute only what's necessary. The function computes
1866 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1867 * one, the BOX rectangle. This rectangle is very cheap to compute,
1868 * and is guaranteed to contain all the other rectangles. Computing
1869 * the ICON rect is also cheap, but all the others are potentaily
1870 * expensive. This gives an easy and effective optimization when
1871 * searching (like point inclusion, or rectangle intersection):
1872 * first test against the BOX, and if TRUE, test agains the desired
1874 * If the function does not have all the necessary information
1875 * to computed the requested rectangles, will crash with a
1876 * failed assertion. This is done so we catch all programming
1877 * errors, given that the function is called only from our code.
1879 * We have the following 'special' meanings for a few fields:
1880 * * If LVIS_FOCUSED is set, we assume the item has the focus
1881 * This is important in ICON mode, where it might get a larger
1882 * then usual rectange
1884 * Please note that subitem support works only in REPORT mode.
1887 * [I] infoPtr : valid pointer to the listview structure
1888 * [I] lpLVItem : item to compute the measures for
1889 * [O] lprcBox : ptr to Box rectangle
1890 * The internal LVIR_BOX rectangle
1891 * [0] lprcState : ptr to State icon rectangle
1892 * The internal LVIR_STATE rectangle
1893 * [O] lprcIcon : ptr to Icon rectangle
1894 * Same as LVM_GETITEMRECT with LVIR_ICON
1895 * [O] lprcLabel : ptr to Label rectangle
1896 * Same as LVM_GETITEMRECT with LVIR_LABEL
1901 static void LISTVIEW_GetItemMetrics(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1902 LPRECT lprcBox, LPRECT lprcState,
1903 LPRECT lprcIcon, LPRECT lprcLabel)
1905 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1906 BOOL doState = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1907 RECT Box, State, Icon, Label;
1908 COLUMN_INFO *lpColumnInfo = NULL;
1910 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1912 /* Be smart and try to figure out the minimum we have to do */
1913 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1914 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1916 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1917 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1919 if (lprcLabel) doLabel = TRUE;
1920 if (doLabel || lprcIcon) doIcon = TRUE;
1921 if (doIcon || lprcState) doState = TRUE;
1923 /************************************************************/
1924 /* compute the box rectangle (it should be cheap to do) */
1925 /************************************************************/
1926 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1927 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1929 if (lpLVItem->iSubItem)
1931 Box = lpColumnInfo->rcHeader;
1936 Box.right = infoPtr->nItemWidth;
1939 Box.bottom = infoPtr->nItemHeight;
1941 /************************************************************/
1942 /* compute STATEICON bounding box */
1943 /************************************************************/
1946 if (uView == LVS_ICON)
1948 State.left = Box.left - infoPtr->iconStateSize.cx - 2;
1949 if (infoPtr->himlNormal)
1950 State.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1951 State.top = Box.top + infoPtr->iconSize.cy - infoPtr->iconStateSize.cy + 4;
1955 /* we need the ident in report mode, if we don't have it, we fail */
1956 State.left = Box.left;
1957 if (uView == LVS_REPORT)
1959 if (lpLVItem->iSubItem == 0)
1961 State.left += REPORT_MARGINX;
1962 assert(lpLVItem->mask & LVIF_INDENT);
1963 State.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
1966 State.top = Box.top;
1968 State.right = State.left;
1969 State.bottom = State.top;
1970 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1972 State.right += infoPtr->iconStateSize.cx;
1973 State.bottom += infoPtr->iconStateSize.cy;
1975 if (lprcState) *lprcState = State;
1976 TRACE(" - state=%s\n", debugrect(&State));
1978 else State.right = 0;
1980 /************************************************************/
1981 /* compute ICON bounding box (ala LVM_GETITEMRECT) */
1982 /************************************************************/
1985 if (uView == LVS_ICON)
1987 Icon.left = Box.left;
1988 if (infoPtr->himlNormal)
1989 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1990 Icon.top = Box.top + ICON_TOP_PADDING;
1991 Icon.right = Icon.left;
1992 Icon.bottom = Icon.top;
1993 if (infoPtr->himlNormal)
1995 Icon.right += infoPtr->iconSize.cx;
1996 Icon.bottom += infoPtr->iconSize.cy;
1999 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2001 Icon.left = State.right;
2003 Icon.right = Icon.left;
2004 if (infoPtr->himlSmall &&
2005 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2006 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2007 Icon.right += infoPtr->iconSize.cx;
2008 Icon.bottom = Icon.top + infoPtr->nItemHeight;
2010 if(lprcIcon) *lprcIcon = Icon;
2011 TRACE(" - icon=%s\n", debugrect(&Icon));
2013 else Icon.right = 0;
2015 /************************************************************/
2016 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2017 /************************************************************/
2020 SIZE labelSize = { 0, 0 };
2022 /* calculate how far to the right can the label strech */
2023 Label.right = Box.right;
2024 if (uView == LVS_REPORT)
2026 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
2029 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
2031 labelSize.cx = infoPtr->nItemWidth;
2032 labelSize.cy = infoPtr->nItemHeight;
2036 /* we need the text in non owner draw mode */
2037 assert(lpLVItem->mask & LVIF_TEXT);
2038 if (is_textT(lpLVItem->pszText, TRUE))
2040 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2041 HDC hdc = GetDC(infoPtr->hwndSelf);
2042 HFONT hOldFont = SelectObject(hdc, hFont);
2046 /* compute rough rectangle where the label will go */
2047 SetRectEmpty(&rcText);
2048 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2049 rcText.bottom = infoPtr->nItemHeight;
2050 if (uView == LVS_ICON)
2051 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2053 /* now figure out the flags */
2054 if (uView == LVS_ICON)
2055 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2057 uFormat = LV_SL_DT_FLAGS;
2059 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2061 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2062 labelSize.cy = rcText.bottom - rcText.top;
2064 SelectObject(hdc, hOldFont);
2065 ReleaseDC(infoPtr->hwndSelf, hdc);
2069 if (uView == LVS_ICON)
2071 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2072 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2073 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2074 Label.right = Label.left + labelSize.cx;
2075 Label.bottom = Label.top + infoPtr->nItemHeight;
2076 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2078 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2079 labelSize.cy /= infoPtr->ntmHeight;
2080 labelSize.cy = max(labelSize.cy, 1);
2081 labelSize.cy *= infoPtr->ntmHeight;
2083 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2085 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2087 Label.left = Icon.right;
2088 Label.top = Box.top;
2089 Label.right = min(Label.left + labelSize.cx, Label.right);
2090 Label.bottom = Label.top + infoPtr->nItemHeight;
2093 if (lprcLabel) *lprcLabel = Label;
2094 TRACE(" - label=%s\n", debugrect(&Label));
2097 /* Fix the Box if necessary */
2100 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2101 else *lprcBox = Box;
2103 TRACE(" - box=%s\n", debugrect(&Box));
2107 * DESCRIPTION: [INTERNAL]
2110 * [I] infoPtr : valid pointer to the listview structure
2111 * [I] nItem : item number
2112 * [O] lprcBox : ptr to Box rectangle
2117 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2119 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2120 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2121 POINT Position, Origin;
2124 LISTVIEW_GetOrigin(infoPtr, &Origin);
2125 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2127 /* Be smart and try to figure out the minimum we have to do */
2129 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2130 lvItem.mask |= LVIF_TEXT;
2131 lvItem.iItem = nItem;
2132 lvItem.iSubItem = 0;
2133 lvItem.pszText = szDispText;
2134 lvItem.cchTextMax = DISP_TEXT_SIZE;
2135 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2136 if (uView == LVS_ICON)
2138 lvItem.mask |= LVIF_STATE;
2139 lvItem.stateMask = LVIS_FOCUSED;
2140 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2142 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0);
2144 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2150 * Returns the current icon position, and advances it along the top.
2151 * The returned position is not offset by Origin.
2154 * [I] infoPtr : valid pointer to the listview structure
2155 * [O] lpPos : will get the current icon position
2160 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2162 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2164 *lpPos = infoPtr->currIconPos;
2166 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2167 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2169 infoPtr->currIconPos.x = 0;
2170 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2176 * Returns the current icon position, and advances it down the left edge.
2177 * The returned position is not offset by Origin.
2180 * [I] infoPtr : valid pointer to the listview structure
2181 * [O] lpPos : will get the current icon position
2186 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2188 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2190 *lpPos = infoPtr->currIconPos;
2192 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2193 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2195 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2196 infoPtr->currIconPos.y = 0;
2202 * Moves an icon to the specified position.
2203 * It takes care of invalidating the item, etc.
2206 * [I] infoPtr : valid pointer to the listview structure
2207 * [I] nItem : the item to move
2208 * [I] lpPos : the new icon position
2209 * [I] isNew : flags the item as being new
2215 static BOOL LISTVIEW_MoveIconTo(LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2221 old.x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2222 old.y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2224 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2225 LISTVIEW_InvalidateItem(infoPtr, nItem);
2228 /* Allocating a POINTER for every item is too resource intensive,
2229 * so we'll keep the (x,y) in different arrays */
2230 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)lppt->x)) return FALSE;
2231 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)lppt->y)) return FALSE;
2233 LISTVIEW_InvalidateItem(infoPtr, nItem);
2240 * Arranges listview items in icon display mode.
2243 * [I] infoPtr : valid pointer to the listview structure
2244 * [I] nAlignCode : alignment code
2250 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2252 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2253 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2257 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2259 TRACE("nAlignCode=%d\n", nAlignCode);
2261 if (nAlignCode == LVA_DEFAULT)
2263 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2264 else nAlignCode = LVA_ALIGNTOP;
2269 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2270 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2271 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2272 default: return FALSE;
2275 infoPtr->bAutoarrange = TRUE;
2276 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2277 for (i = 0; i < infoPtr->nItemCount; i++)
2279 next_pos(infoPtr, &pos);
2280 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2288 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2291 * [I] infoPtr : valid pointer to the listview structure
2292 * [O] lprcView : bounding rectangle
2298 static void LISTVIEW_GetAreaRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2302 SetRectEmpty(lprcView);
2304 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2308 for (i = 0; i < infoPtr->nItemCount; i++)
2310 x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, i);
2311 y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, i);
2312 lprcView->right = max(lprcView->right, x);
2313 lprcView->bottom = max(lprcView->bottom, y);
2315 if (infoPtr->nItemCount > 0)
2317 lprcView->right += infoPtr->nItemWidth;
2318 lprcView->bottom += infoPtr->nItemHeight;
2323 y = LISTVIEW_GetCountPerColumn(infoPtr);
2324 x = infoPtr->nItemCount / y;
2325 if (infoPtr->nItemCount % y) x++;
2326 lprcView->right = x * infoPtr->nItemWidth;
2327 lprcView->bottom = y * infoPtr->nItemHeight;
2331 lprcView->right = infoPtr->nItemWidth;
2332 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2339 * Retrieves the bounding rectangle of all the items.
2342 * [I] infoPtr : valid pointer to the listview structure
2343 * [O] lprcView : bounding rectangle
2349 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2353 TRACE("(lprcView=%p)\n", lprcView);
2355 if (!lprcView) return FALSE;
2357 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2358 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2359 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2361 TRACE("lprcView=%s\n", debugrect(lprcView));
2368 * Retrieves the subitem pointer associated with the subitem index.
2371 * [I] hdpaSubItems : DPA handle for a specific item
2372 * [I] nSubItem : index of subitem
2375 * SUCCESS : subitem pointer
2378 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2380 SUBITEM_INFO *lpSubItem;
2383 /* we should binary search here if need be */
2384 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2386 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
2387 if (lpSubItem->iSubItem == nSubItem)
2397 * Caclulates the desired item width.
2400 * [I] infoPtr : valid pointer to the listview structure
2403 * The desired item width.
2405 static INT LISTVIEW_CalculateItemWidth(LISTVIEW_INFO *infoPtr)
2407 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2410 TRACE("uView=%d\n", uView);
2412 if (uView == LVS_ICON)
2413 nItemWidth = infoPtr->iconSpacing.cx;
2414 else if (uView == LVS_REPORT)
2418 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2420 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2421 nItemWidth = rcHeader.right;
2424 else /* LVS_SMALLICON, or LVS_LIST */
2428 for (i = 0; i < infoPtr->nItemCount; i++)
2429 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2431 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2432 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2434 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2437 return max(nItemWidth, 1);
2442 * Caclulates the desired item height.
2445 * [I] infoPtr : valid pointer to the listview structure
2448 * The desired item height.
2450 static INT LISTVIEW_CalculateItemHeight(LISTVIEW_INFO *infoPtr)
2452 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2455 TRACE("uView=%d\n", uView);
2457 if (uView == LVS_ICON)
2458 nItemHeight = infoPtr->iconSpacing.cy;
2461 nItemHeight = infoPtr->ntmHeight;
2462 if (infoPtr->himlState)
2463 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2464 if (infoPtr->himlSmall)
2465 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2466 if (infoPtr->himlState || infoPtr->himlSmall)
2467 nItemHeight += HEIGHT_PADDING;
2468 if (infoPtr->nMeasureItemHeight > 0)
2469 nItemHeight = infoPtr->nMeasureItemHeight;
2472 return max(nItemHeight, 1);
2477 * Updates the width, and height of an item.
2480 * [I] infoPtr : valid pointer to the listview structure
2485 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2487 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2488 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2494 * Retrieves and saves important text metrics info for the current
2498 * [I] infoPtr : valid pointer to the listview structure
2501 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2503 HDC hdc = GetDC(infoPtr->hwndSelf);
2504 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2505 HFONT hOldFont = SelectObject(hdc, hFont);
2509 if (GetTextMetricsW(hdc, &tm))
2511 infoPtr->ntmHeight = tm.tmHeight;
2512 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2515 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2516 infoPtr->nEllipsisWidth = sz.cx;
2518 SelectObject(hdc, hOldFont);
2519 ReleaseDC(infoPtr->hwndSelf, hdc);
2521 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2526 * A compare function for ranges
2529 * [I] range1 : pointer to range 1;
2530 * [I] range2 : pointer to range 2;
2534 * > 0 : if range 1 > range 2
2535 * < 0 : if range 2 > range 1
2536 * = 0 : if range intersects range 2
2538 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2542 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2544 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2549 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2555 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2557 #define ranges_check(ranges, desc) do { } while(0)
2560 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2565 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2567 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2568 ranges_dump(ranges);
2569 prev = (RANGE *)DPA_GetPtr(ranges->hdpa, 0);
2570 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2571 assert (prev->lower >= 0 && prev->lower < prev->upper);
2572 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2574 curr = (RANGE *)DPA_GetPtr(ranges->hdpa, i);
2575 assert (prev->upper <= curr->lower);
2576 assert (curr->lower < curr->upper);
2579 TRACE("--- Done checking---\n");
2582 static RANGES ranges_create(int count)
2584 RANGES ranges = (RANGES)Alloc(sizeof(struct tagRANGES));
2585 if (!ranges) return NULL;
2586 ranges->hdpa = DPA_Create(count);
2587 if (ranges->hdpa) return ranges;
2592 static void ranges_clear(RANGES ranges)
2596 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2597 Free(DPA_GetPtr(ranges->hdpa, i));
2598 DPA_DeleteAllPtrs(ranges->hdpa);
2602 static void ranges_destroy(RANGES ranges)
2604 if (!ranges) return;
2605 ranges_clear(ranges);
2606 DPA_Destroy(ranges->hdpa);
2610 static RANGES ranges_clone(RANGES ranges)
2615 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2617 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2619 RANGE *newrng = (RANGE *)Alloc(sizeof(RANGE));
2620 if (!newrng) goto fail;
2621 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2622 DPA_SetPtr(clone->hdpa, i, newrng);
2627 TRACE ("clone failed\n");
2628 ranges_destroy(clone);
2632 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2636 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2637 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2642 static void ranges_dump(RANGES ranges)
2646 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2647 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2650 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2652 RANGE srchrng = { nItem, nItem + 1 };
2654 TRACE("(nItem=%d)\n", nItem);
2655 ranges_check(ranges, "before contain");
2656 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2659 static INT ranges_itemcount(RANGES ranges)
2663 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2665 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2666 count += sel->upper - sel->lower;
2672 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2674 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2677 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2678 if (index == -1) return TRUE;
2680 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2682 chkrng = DPA_GetPtr(ranges->hdpa, index);
2683 if (chkrng->lower >= nItem)
2684 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2685 if (chkrng->upper > nItem)
2686 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2691 static BOOL ranges_add(RANGES ranges, RANGE range)
2696 TRACE("(%s)\n", debugrange(&range));
2697 ranges_check(ranges, "before add");
2699 /* try find overlapping regions first */
2700 srchrgn.lower = range.lower - 1;
2701 srchrgn.upper = range.upper + 1;
2702 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2708 TRACE("Adding new range\n");
2710 /* create the brand new range to insert */
2711 newrgn = (RANGE *)Alloc(sizeof(RANGE));
2712 if(!newrgn) goto fail;
2715 /* figure out where to insert it */
2716 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2717 TRACE("index=%d\n", index);
2718 if (index == -1) index = 0;
2720 /* and get it over with */
2721 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2729 RANGE *chkrgn, *mrgrgn;
2730 INT fromindex, mergeindex;
2732 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2733 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2735 chkrgn->lower = min(range.lower, chkrgn->lower);
2736 chkrgn->upper = max(range.upper, chkrgn->upper);
2738 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2740 /* merge now common anges */
2742 srchrgn.lower = chkrgn->lower - 1;
2743 srchrgn.upper = chkrgn->upper + 1;
2747 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2748 if (mergeindex == -1) break;
2749 if (mergeindex == index)
2751 fromindex = index + 1;
2755 TRACE("Merge with index %i\n", mergeindex);
2757 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2758 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2759 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2761 DPA_DeletePtr(ranges->hdpa, mergeindex);
2762 if (mergeindex < index) index --;
2766 ranges_check(ranges, "after add");
2770 ranges_check(ranges, "failed add");
2774 static BOOL ranges_del(RANGES ranges, RANGE range)
2779 TRACE("(%s)\n", debugrange(&range));
2780 ranges_check(ranges, "before del");
2782 /* we don't use DPAS_SORTED here, since we need *
2783 * to find the first overlapping range */
2784 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2787 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2789 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2791 /* case 1: Same range */
2792 if ( (chkrgn->upper == range.upper) &&
2793 (chkrgn->lower == range.lower) )
2795 DPA_DeletePtr(ranges->hdpa, index);
2798 /* case 2: engulf */
2799 else if ( (chkrgn->upper <= range.upper) &&
2800 (chkrgn->lower >= range.lower) )
2802 DPA_DeletePtr(ranges->hdpa, index);
2804 /* case 3: overlap upper */
2805 else if ( (chkrgn->upper <= range.upper) &&
2806 (chkrgn->lower < range.lower) )
2808 chkrgn->upper = range.lower;
2810 /* case 4: overlap lower */
2811 else if ( (chkrgn->upper > range.upper) &&
2812 (chkrgn->lower >= range.lower) )
2814 chkrgn->lower = range.upper;
2817 /* case 5: fully internal */
2820 RANGE tmprgn = *chkrgn, *newrgn;
2822 if (!(newrgn = (RANGE *)Alloc(sizeof(RANGE)))) goto fail;
2823 newrgn->lower = chkrgn->lower;
2824 newrgn->upper = range.lower;
2825 chkrgn->lower = range.upper;
2826 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2835 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2838 ranges_check(ranges, "after del");
2842 ranges_check(ranges, "failed del");
2848 * Removes all selection ranges
2851 * [I] infoPtr : valid pointer to the listview structure
2852 * [I] toSkip : item range to skip removing the selection
2858 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2867 lvItem.stateMask = LVIS_SELECTED;
2869 /* need to clone the DPA because callbacks can change it */
2870 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2871 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2872 while(iterator_next(&i))
2873 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2874 /* note that the iterator destructor will free the cloned range */
2875 iterator_destroy(&i);
2880 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2884 if (!(toSkip = ranges_create(1))) return FALSE;
2885 if (nItem != -1) ranges_additem(toSkip, nItem);
2886 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2887 ranges_destroy(toSkip);
2891 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2893 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2898 * Retrieves the number of items that are marked as selected.
2901 * [I] infoPtr : valid pointer to the listview structure
2904 * Number of items selected.
2906 static INT LISTVIEW_GetSelectedCount(LISTVIEW_INFO *infoPtr)
2908 INT nSelectedCount = 0;
2910 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2913 for (i = 0; i < infoPtr->nItemCount; i++)
2915 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2920 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2922 TRACE("nSelectedCount=%d\n", nSelectedCount);
2923 return nSelectedCount;
2928 * Manages the item focus.
2931 * [I] infoPtr : valid pointer to the listview structure
2932 * [I] nItem : item index
2935 * TRUE : focused item changed
2936 * FALSE : focused item has NOT changed
2938 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2940 INT oldFocus = infoPtr->nFocusedItem;
2943 if (nItem == infoPtr->nFocusedItem) return FALSE;
2945 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2946 lvItem.stateMask = LVIS_FOCUSED;
2947 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
2949 return oldFocus != infoPtr->nFocusedItem;
2952 /* Helper function for LISTVIEW_ShiftIndices *only* */
2953 static INT shift_item(LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2955 if (nShiftItem < nItem) return nShiftItem;
2957 if (nShiftItem > nItem) return nShiftItem + direction;
2959 if (direction > 0) return nShiftItem + direction;
2961 return min(nShiftItem, infoPtr->nItemCount - 1);
2966 * Updates the various indices after an item has been inserted or deleted.
2969 * [I] infoPtr : valid pointer to the listview structure
2970 * [I] nItem : item index
2971 * [I] direction : Direction of shift, +1 or -1.
2976 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
2981 /* temporarily disable change notification while shifting items */
2982 bOldChange = infoPtr->bDoChangeNotify;
2983 infoPtr->bDoChangeNotify = FALSE;
2985 TRACE("Shifting %iu, %i steps\n", nItem, direction);
2987 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
2989 assert(abs(direction) == 1);
2991 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
2993 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
2994 if (nNewFocus != infoPtr->nFocusedItem)
2995 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
2997 /* But we are not supposed to modify nHotItem! */
2999 infoPtr->bDoChangeNotify = bOldChange;
3005 * Adds a block of selections.
3008 * [I] infoPtr : valid pointer to the listview structure
3009 * [I] nItem : item index
3014 static void LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3016 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3017 INT nLast = max(infoPtr->nSelectionMark, nItem);
3018 NMLVODSTATECHANGE nmlv;
3023 /* Temporarily disable change notification
3024 * If the control is LVS_OWNERDATA, we need to send
3025 * only one LVN_ODSTATECHANGED notification.
3026 * See MSDN documentation for LVN_ITEMCHANGED.
3028 bOldChange = infoPtr->bDoChangeNotify;
3029 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3031 if (nFirst == -1) nFirst = nItem;
3033 item.state = LVIS_SELECTED;
3034 item.stateMask = LVIS_SELECTED;
3036 for (i = nFirst; i <= nLast; i++)
3037 LISTVIEW_SetItemState(infoPtr,i,&item);
3039 ZeroMemory(&nmlv, sizeof(nmlv));
3040 nmlv.iFrom = nFirst;
3043 nmlv.uOldState = item.state;
3045 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3046 infoPtr->bDoChangeNotify = bOldChange;
3052 * Sets a single group selection.
3055 * [I] infoPtr : valid pointer to the listview structure
3056 * [I] nItem : item index
3061 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3063 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3068 if (!(selection = ranges_create(100))) return;
3070 item.state = LVIS_SELECTED;
3071 item.stateMask = LVIS_SELECTED;
3073 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3075 if (infoPtr->nSelectionMark == -1)
3077 infoPtr->nSelectionMark = nItem;
3078 ranges_additem(selection, nItem);
3084 sel.lower = min(infoPtr->nSelectionMark, nItem);
3085 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3086 ranges_add(selection, sel);
3091 RECT rcItem, rcSel, rcSelMark;
3094 rcItem.left = LVIR_BOUNDS;
3095 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3096 rcSelMark.left = LVIR_BOUNDS;
3097 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3098 UnionRect(&rcSel, &rcItem, &rcSelMark);
3099 iterator_frameditems(&i, infoPtr, &rcSel);
3100 while(iterator_next(&i))
3102 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3103 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3105 iterator_destroy(&i);
3108 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3109 iterator_rangesitems(&i, selection);
3110 while(iterator_next(&i))
3111 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3112 /* this will also destroy the selection */
3113 iterator_destroy(&i);
3115 LISTVIEW_SetItemFocus(infoPtr, nItem);
3120 * Sets a single selection.
3123 * [I] infoPtr : valid pointer to the listview structure
3124 * [I] nItem : item index
3129 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3133 TRACE("nItem=%d\n", nItem);
3135 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3137 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3138 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3139 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3141 infoPtr->nSelectionMark = nItem;
3146 * Set selection(s) with keyboard.
3149 * [I] infoPtr : valid pointer to the listview structure
3150 * [I] nItem : item index
3153 * SUCCESS : TRUE (needs to be repainted)
3154 * FAILURE : FALSE (nothing has changed)
3156 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
3158 /* FIXME: pass in the state */
3159 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3160 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3161 BOOL bResult = FALSE;
3163 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3165 if (infoPtr->dwStyle & LVS_SINGLESEL)
3168 LISTVIEW_SetSelection(infoPtr, nItem);
3175 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3179 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3184 LISTVIEW_SetSelection(infoPtr, nItem);
3187 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3190 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3194 static BOOL LISTVIEW_GetItemAtPt(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3196 LVHITTESTINFO lvHitTestInfo;
3198 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3199 lvHitTestInfo.pt.x = pt.x;
3200 lvHitTestInfo.pt.y = pt.y;
3202 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3204 lpLVItem->mask = LVIF_PARAM;
3205 lpLVItem->iItem = lvHitTestInfo.iItem;
3206 lpLVItem->iSubItem = 0;
3208 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3213 * Called when the mouse is being actively tracked and has hovered for a specified
3217 * [I] infoPtr : valid pointer to the listview structure
3218 * [I] fwKeys : key indicator
3219 * [I] x,y : mouse position
3222 * 0 if the message was processed, non-zero if there was an error
3225 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3226 * over the item for a certain period of time.
3229 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKyes, INT x, INT y)
3231 if (infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
3239 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3240 LISTVIEW_SetSelection(infoPtr, item.iItem);
3248 * Called whenever WM_MOUSEMOVE is received.
3251 * [I] infoPtr : valid pointer to the listview structure
3252 * [I] fwKeys : key indicator
3253 * [I] x,y : mouse position
3256 * 0 if the message is processed, non-zero if there was an error
3258 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3260 TRACKMOUSEEVENT trackinfo;
3262 if (infoPtr->bLButtonDown && DragDetect(infoPtr->hwndSelf, infoPtr->ptClickPos))
3264 LVHITTESTINFO lvHitTestInfo;
3267 lvHitTestInfo.pt = infoPtr->ptClickPos;
3268 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3270 ZeroMemory(&nmlv, sizeof(nmlv));
3271 nmlv.iItem = lvHitTestInfo.iItem;
3272 nmlv.ptAction = infoPtr->ptClickPos;
3274 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3279 /* see if we are supposed to be tracking mouse hovering */
3280 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
3281 /* fill in the trackinfo struct */
3282 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3283 trackinfo.dwFlags = TME_QUERY;
3284 trackinfo.hwndTrack = infoPtr->hwndSelf;
3285 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3287 /* see if we are already tracking this hwnd */
3288 _TrackMouseEvent(&trackinfo);
3290 if(!(trackinfo.dwFlags & TME_HOVER)) {
3291 trackinfo.dwFlags = TME_HOVER;
3293 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3294 _TrackMouseEvent(&trackinfo);
3303 * Tests wheather the item is assignable to a list with style lStyle
3305 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3307 if ( (lpLVItem->mask & LVIF_TEXT) &&
3308 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3309 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3317 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3320 * [I] infoPtr : valid pointer to the listview structure
3321 * [I] lpLVItem : valid pointer to new item atttributes
3322 * [I] isNew : the item being set is being inserted
3323 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3324 * [O] bChanged : will be set to TRUE if the item really changed
3330 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3332 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3340 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3342 if (lpLVItem->mask == 0) return TRUE;
3344 if (infoPtr->dwStyle & LVS_OWNERDATA)
3346 /* a virtual listview we stores only selection and focus */
3347 if (lpLVItem->mask & ~LVIF_STATE)
3353 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3354 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
3358 /* we need to get the lParam and state of the item */
3359 item.iItem = lpLVItem->iItem;
3360 item.iSubItem = lpLVItem->iSubItem;
3361 item.mask = LVIF_STATE | LVIF_PARAM;
3362 item.stateMask = ~0;
3365 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3367 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3368 /* determine what fields will change */
3369 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
3370 uChanged |= LVIF_STATE;
3372 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3373 uChanged |= LVIF_IMAGE;
3375 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3376 uChanged |= LVIF_PARAM;
3378 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3379 uChanged |= LVIF_INDENT;
3381 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3382 uChanged |= LVIF_TEXT;
3384 TRACE("uChanged=0x%x\n", uChanged);
3385 if (!uChanged) return TRUE;
3388 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3389 nmlv.iItem = lpLVItem->iItem;
3390 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3391 nmlv.uOldState = item.state;
3392 nmlv.uChanged = uChanged;
3393 nmlv.lParam = item.lParam;
3395 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3396 /* and we are _NOT_ virtual (LVS_OWERNDATA), and change notifications */
3398 if(lpItem && !isNew && infoPtr->bDoChangeNotify &&
3399 notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3402 /* copy information */
3403 if (lpLVItem->mask & LVIF_TEXT)
3404 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3406 if (lpLVItem->mask & LVIF_IMAGE)
3407 lpItem->hdr.iImage = lpLVItem->iImage;
3409 if (lpLVItem->mask & LVIF_PARAM)
3410 lpItem->lParam = lpLVItem->lParam;
3412 if (lpLVItem->mask & LVIF_INDENT)
3413 lpItem->iIndent = lpLVItem->iIndent;
3415 if (uChanged & LVIF_STATE)
3417 if (lpItem && (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3419 lpItem->state &= ~lpLVItem->stateMask;
3420 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3422 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3424 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3425 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3427 else if (lpLVItem->stateMask & LVIS_SELECTED)
3428 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3430 /* if we are asked to change focus, and we manage it, do it */
3431 if (lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3433 if (lpLVItem->state & LVIS_FOCUSED)
3435 LISTVIEW_SetItemFocus(infoPtr, -1);
3436 infoPtr->nFocusedItem = lpLVItem->iItem;
3437 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3439 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3440 infoPtr->nFocusedItem = -1;
3444 /* if we're inserting the item, we're done */
3445 if (isNew) return TRUE;
3447 /* send LVN_ITEMCHANGED notification */
3448 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3449 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3456 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3459 * [I] infoPtr : valid pointer to the listview structure
3460 * [I] lpLVItem : valid pointer to new subitem atttributes
3461 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3462 * [O] bChanged : will be set to TRUE if the item really changed
3468 static BOOL set_sub_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3471 SUBITEM_INFO *lpSubItem;
3473 /* we do not support subitems for virtual listviews */
3474 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3476 /* set subitem only if column is present */
3477 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3479 /* First do some sanity checks */
3480 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE)) return FALSE;
3481 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE))) return TRUE;
3483 /* get the subitem structure, and create it if not there */
3484 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3485 assert (hdpaSubItems);
3487 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3490 SUBITEM_INFO *tmpSubItem;
3493 lpSubItem = (SUBITEM_INFO *)Alloc(sizeof(SUBITEM_INFO));
3494 if (!lpSubItem) return FALSE;
3495 /* we could binary search here, if need be...*/
3496 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3498 tmpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
3499 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3501 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3506 lpSubItem->iSubItem = lpLVItem->iSubItem;
3507 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3511 if (lpLVItem->mask & LVIF_IMAGE)
3512 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3514 lpSubItem->hdr.iImage = lpLVItem->iImage;
3518 if (lpLVItem->mask & LVIF_TEXT)
3519 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3521 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3530 * Sets item attributes.
3533 * [I] infoPtr : valid pointer to the listview structure
3534 * [I] lpLVItem : new item atttributes
3535 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3541 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
3543 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3544 LPWSTR pszText = NULL;
3545 BOOL bResult, bChanged = FALSE;
3547 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3549 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3552 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3553 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3555 pszText = lpLVItem->pszText;
3556 ((LVITEMW *)lpLVItem)->pszText = textdupTtoW(lpLVItem->pszText, isW);
3559 /* actually set the fields */
3560 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3562 if (lpLVItem->iSubItem)
3563 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3565 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3567 /* redraw item, if necessary */
3568 if (bChanged && !infoPtr->bIsDrawing)
3570 /* this little optimization eliminates some nasty flicker */
3571 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3572 (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) || lpLVItem->iSubItem) )
3573 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3575 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3580 textfreeT(lpLVItem->pszText, isW);
3581 ((LVITEMW *)lpLVItem)->pszText = pszText;
3589 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3592 * [I] infoPtr : valid pointer to the listview structure
3597 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *infoPtr)
3599 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3601 SCROLLINFO scrollInfo;
3603 scrollInfo.cbSize = sizeof(SCROLLINFO);
3604 scrollInfo.fMask = SIF_POS;
3606 if (uView == LVS_LIST)
3608 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3609 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3611 else if (uView == LVS_REPORT)
3613 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3614 nItem = scrollInfo.nPos;
3618 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3619 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3622 TRACE("nItem=%d\n", nItem);
3630 * Erases the background of the given rectangle
3633 * [I] infoPtr : valid pointer to the listview structure
3634 * [I] hdc : device context handle
3635 * [I] lprcBox : clipping rectangle
3641 static inline BOOL LISTVIEW_FillBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3643 if (!infoPtr->hBkBrush) return FALSE;
3645 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, debugrect(lprcBox), infoPtr->hBkBrush);
3647 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3655 * [I] infoPtr : valid pointer to the listview structure
3656 * [I] hdc : device context handle
3657 * [I] nItem : item index
3658 * [I] nSubItem : subitem index
3659 * [I] pos : item position in client coordinates
3660 * [I] cdmode : custom draw mode
3666 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3668 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3669 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3670 static const WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3671 DWORD cdsubitemmode = CDRF_DODEFAULT;
3672 RECT* lprcFocus, rcSelect, rcBox, rcState, rcIcon, rcLabel;
3673 NMLVCUSTOMDRAW nmlvcd;
3677 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, debugpoint(&pos));
3679 /* get information needed for drawing the item */
3680 lvItem.mask = LVIF_TEXT | LVIF_IMAGE;
3681 if (nSubItem == 0) lvItem.mask |= LVIF_STATE | LVIF_PARAM;
3682 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3683 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3684 lvItem.iItem = nItem;
3685 lvItem.iSubItem = nSubItem;
3688 lvItem.cchTextMax = DISP_TEXT_SIZE;
3689 lvItem.pszText = szDispText;
3690 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3691 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3692 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3693 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = (LPWSTR)szCallback;
3694 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3696 /* now check if we need to update the focus rectangle */
3697 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3699 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3700 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
3701 OffsetRect(&rcBox, pos.x, pos.y);
3702 OffsetRect(&rcState, pos.x, pos.y);
3703 OffsetRect(&rcIcon, pos.x, pos.y);
3704 OffsetRect(&rcLabel, pos.x, pos.y);
3705 TRACE(" rcBox=%s, rcState=%s, rcIcon=%s. rcLabel=%s\n",
3706 debugrect(&rcBox), debugrect(&rcState), debugrect(&rcIcon), debugrect(&rcLabel));
3708 /* fill in the custom draw structure */
3709 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3711 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3712 if (cdmode & CDRF_NOTIFYITEMDRAW)
3713 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3714 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3715 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3716 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3717 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3719 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3720 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3722 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3723 prepaint_setup(infoPtr, hdc, &nmlvcd);
3725 /* in full row select, subitems, will just use main item's colors */
3726 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3727 nmlvcd.clrTextBk = CLR_NONE;
3730 if (infoPtr->himlState && !IsRectEmpty(&rcState))
3732 UINT uStateImage = (lvItem.state & LVIS_STATEIMAGEMASK) >> 12;
3735 TRACE("uStateImage=%d\n", uStateImage);
3736 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc, rcState.left, rcState.top, ILD_NORMAL);
3741 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3742 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3744 TRACE("iImage=%d\n", lvItem.iImage);
3745 ImageList_Draw(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3746 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3749 /* Don't bother painting item being edited */
3750 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3752 /* draw the selection background, if we're drawing the main item */
3756 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3757 rcSelect.right = rcBox.right;
3759 if (nmlvcd.clrTextBk != CLR_NONE)
3760 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3761 if(lprcFocus) *lprcFocus = rcSelect;
3764 /* figure out the text drawing flags */
3765 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3766 if (uView == LVS_ICON)
3767 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3770 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3772 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3773 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3774 default: uFormat |= DT_LEFT;
3777 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3779 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3780 else rcLabel.left += LABEL_HOR_PADDING;
3782 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3783 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3786 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3787 notify_postpaint(infoPtr, &nmlvcd);
3793 * Draws listview items when in owner draw mode.
3796 * [I] infoPtr : valid pointer to the listview structure
3797 * [I] hdc : device context handle
3802 static void LISTVIEW_RefreshOwnerDraw(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3804 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
3805 DWORD cditemmode = CDRF_DODEFAULT;
3806 NMLVCUSTOMDRAW nmlvcd;
3807 POINT Origin, Position;
3813 ZeroMemory(&dis, sizeof(dis));
3815 /* Get scroll info once before loop */
3816 LISTVIEW_GetOrigin(infoPtr, &Origin);
3818 /* iterate through the invalidated rows */
3819 while(iterator_next(i))
3821 item.iItem = i->nItem;
3823 item.mask = LVIF_PARAM | LVIF_STATE;
3824 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3825 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3827 dis.CtlType = ODT_LISTVIEW;
3829 dis.itemID = item.iItem;
3830 dis.itemAction = ODA_DRAWENTIRE;
3832 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3833 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3834 dis.hwndItem = infoPtr->hwndSelf;
3836 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3837 dis.rcItem.left = Position.x + Origin.x;
3838 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3839 dis.rcItem.top = Position.y + Origin.y;
3840 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3841 dis.itemData = item.lParam;
3843 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), debugrect(&dis.rcItem));
3846 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
3847 * structure for the rest. of the paint cycle
3849 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
3850 if (cdmode & CDRF_NOTIFYITEMDRAW)
3851 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3853 if (!(cditemmode & CDRF_SKIPDEFAULT))
3855 prepaint_setup (infoPtr, hdc, &nmlvcd);
3856 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3859 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3860 notify_postpaint(infoPtr, &nmlvcd);
3866 * Draws listview items when in report display mode.
3869 * [I] infoPtr : valid pointer to the listview structure
3870 * [I] hdc : device context handle
3871 * [I] cdmode : custom draw mode
3876 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3879 RECT rcClip, rcItem;
3880 POINT Origin, Position;
3886 /* figure out what to draw */
3887 rgntype = GetClipBox(hdc, &rcClip);
3888 if (rgntype == NULLREGION) return;
3890 /* Get scroll info once before loop */
3891 LISTVIEW_GetOrigin(infoPtr, &Origin);
3893 /* narrow down the columns we need to paint */
3894 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
3896 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
3897 if (rcItem.right + Origin.x >= rcClip.left) break;
3899 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
3901 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
3902 if (rcItem.left + Origin.x < rcClip.right) break;
3904 iterator_rangeitems(&j, colRange);
3906 /* in full row select, we _have_ to draw the main item */
3907 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
3910 /* iterate through the invalidated rows */
3911 while(iterator_next(i))
3913 /* iterate through the invalidated columns */
3914 while(iterator_next(&j))
3916 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
3917 Position.x += Origin.x;
3918 Position.y += Origin.y;
3920 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
3922 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
3924 rcItem.bottom = infoPtr->nItemHeight;
3925 OffsetRect(&rcItem, Position.x, Position.y);
3926 if (!RectVisible(hdc, &rcItem)) continue;
3929 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
3932 iterator_destroy(&j);
3937 * Draws listview items when in list display mode.
3940 * [I] infoPtr : valid pointer to the listview structure
3941 * [I] hdc : device context handle
3942 * [I] cdmode : custom draw mode
3947 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3949 POINT Origin, Position;
3951 /* Get scroll info once before loop */
3952 LISTVIEW_GetOrigin(infoPtr, &Origin);
3954 while(iterator_prev(i))
3956 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
3957 Position.x += Origin.x;
3958 Position.y += Origin.y;
3960 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
3967 * Draws listview items.
3970 * [I] infoPtr : valid pointer to the listview structure
3971 * [I] hdc : device context handle
3976 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc)
3978 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3979 COLORREF oldTextColor, oldClrTextBk, oldClrText;
3980 NMLVCUSTOMDRAW nmlvcd;
3987 LISTVIEW_DUMP(infoPtr);
3989 infoPtr->bIsDrawing = TRUE;
3991 /* save dc values we're gonna trash while drawing */
3992 hOldFont = SelectObject(hdc, infoPtr->hFont);
3993 oldBkMode = GetBkMode(hdc);
3994 infoPtr->clrTextBkDefault = GetBkColor(hdc);
3995 oldTextColor = GetTextColor(hdc);
3997 oldClrTextBk = infoPtr->clrTextBk;
3998 oldClrText = infoPtr->clrText;
4000 infoPtr->cditemmode = CDRF_DODEFAULT;
4002 GetClientRect(infoPtr->hwndSelf, &rcClient);
4003 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
4004 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4005 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
4006 prepaint_setup(infoPtr, hdc, &nmlvcd);
4008 /* Use these colors to draw the items */
4009 infoPtr->clrTextBk = nmlvcd.clrTextBk;
4010 infoPtr->clrText = nmlvcd.clrText;
4012 /* nothing to draw */
4013 if(infoPtr->nItemCount == 0) goto enddraw;
4015 /* figure out what we need to draw */
4016 iterator_visibleitems(&i, infoPtr, hdc);
4018 /* send cache hint notification */
4019 if (infoPtr->dwStyle & LVS_OWNERDATA)
4021 RANGE range = iterator_range(&i);
4024 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4025 nmlv.iFrom = range.lower;
4026 nmlv.iTo = range.upper - 1;
4027 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4030 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
4031 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4034 if (uView == LVS_REPORT)
4035 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4036 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
4037 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4039 /* if we have a focus rect, draw it */
4040 if (infoPtr->bFocus)
4041 DrawFocusRect(hdc, &infoPtr->rcFocus);
4043 iterator_destroy(&i);
4046 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4047 notify_postpaint(infoPtr, &nmlvcd);
4049 infoPtr->clrTextBk = oldClrTextBk;
4050 infoPtr->clrText = oldClrText;
4052 SelectObject(hdc, hOldFont);
4053 SetBkMode(hdc, oldBkMode);
4054 SetBkColor(hdc, infoPtr->clrTextBkDefault);
4055 SetTextColor(hdc, oldTextColor);
4056 infoPtr->bIsDrawing = FALSE;
4062 * Calculates the approximate width and height of a given number of items.
4065 * [I] infoPtr : valid pointer to the listview structure
4066 * [I] nItemCount : number of items
4067 * [I] wWidth : width
4068 * [I] wHeight : height
4071 * Returns a DWORD. The width in the low word and the height in high word.
4073 static DWORD LISTVIEW_ApproximateViewRect(LISTVIEW_INFO *infoPtr, INT nItemCount,
4074 WORD wWidth, WORD wHeight)
4076 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4077 INT nItemCountPerColumn = 1;
4078 INT nColumnCount = 0;
4079 DWORD dwViewRect = 0;
4081 if (nItemCount == -1)
4082 nItemCount = infoPtr->nItemCount;
4084 if (uView == LVS_LIST)
4086 if (wHeight == 0xFFFF)
4088 /* use current height */
4089 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4092 if (wHeight < infoPtr->nItemHeight)
4093 wHeight = infoPtr->nItemHeight;
4097 if (infoPtr->nItemHeight > 0)
4099 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4100 if (nItemCountPerColumn == 0)
4101 nItemCountPerColumn = 1;
4103 if (nItemCount % nItemCountPerColumn != 0)
4104 nColumnCount = nItemCount / nItemCountPerColumn;
4106 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4110 /* Microsoft padding magic */
4111 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4112 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4114 dwViewRect = MAKELONG(wWidth, wHeight);
4116 else if (uView == LVS_REPORT)
4120 if (infoPtr->nItemCount > 0)
4122 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4123 wWidth = rcBox.right - rcBox.left;
4124 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4128 /* use current height and width */
4129 if (wHeight == 0xffff)
4130 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4131 if (wWidth == 0xffff)
4132 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4135 dwViewRect = MAKELONG(wWidth, wHeight);
4137 else if (uView == LVS_SMALLICON)
4138 FIXME("uView == LVS_SMALLICON: not implemented\n");
4139 else if (uView == LVS_ICON)
4140 FIXME("uView == LVS_ICON: not implemented\n");
4148 * Create a drag image list for the specified item.
4151 * [I] infoPtr : valid pointer to the listview structure
4152 * [I] iItem : index of item
4153 * [O] lppt : Upperr-left corner of the image
4156 * Returns a handle to the image list if successful, NULL otherwise.
4158 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4164 HBITMAP hbmp, hOldbmp;
4165 HIMAGELIST dragList = 0;
4166 TRACE("iItem=%d Count=%d \n", iItem, infoPtr->nItemCount);
4168 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4171 rcItem.left = LVIR_BOUNDS;
4172 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4175 lppt->x = rcItem.left;
4176 lppt->y = rcItem.top;
4178 size.cx = rcItem.right - rcItem.left;
4179 size.cy = rcItem.bottom - rcItem.top;
4181 hdcOrig = GetDC(infoPtr->hwndSelf);
4182 hdc = CreateCompatibleDC(hdcOrig);
4183 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4184 hOldbmp = SelectObject(hdc, hbmp);
4186 rcItem.left = rcItem.top = 0;
4187 rcItem.right = size.cx;
4188 rcItem.bottom = size.cy;
4189 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4192 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4194 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4195 SelectObject(hdc, hOldbmp);
4196 ImageList_Add(dragList, hbmp, 0);
4199 SelectObject(hdc, hOldbmp);
4203 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4205 TRACE("ret=%p\n", dragList);
4213 * Removes all listview items and subitems.
4216 * [I] infoPtr : valid pointer to the listview structure
4222 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr)
4225 HDPA hdpaSubItems = NULL;
4232 /* we do it directly, to avoid notifications */
4233 ranges_clear(infoPtr->selectionRanges);
4234 infoPtr->nSelectionMark = -1;
4235 infoPtr->nFocusedItem = -1;
4236 SetRectEmpty(&infoPtr->rcFocus);
4237 /* But we are supposed to leave nHotItem as is! */
4240 /* send LVN_DELETEALLITEMS notification */
4241 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4243 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4245 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4247 /* send LVN_DELETEITEM notification, if not suppressed */
4248 if (!bSuppress) notify_deleteitem(infoPtr, i);
4249 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4251 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
4252 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4254 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, j);
4255 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4258 DPA_Destroy(hdpaSubItems);
4259 DPA_DeletePtr(infoPtr->hdpaItems, i);
4261 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4262 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4263 infoPtr->nItemCount --;
4266 LISTVIEW_UpdateScroll(infoPtr);
4268 LISTVIEW_InvalidateList(infoPtr);
4275 * Scrolls, and updates the columns, when a column is changing width.
4278 * [I] infoPtr : valid pointer to the listview structure
4279 * [I] nColumn : column to scroll
4280 * [I] dx : amount of scroll, in pixels
4285 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4287 COLUMN_INFO *lpColumnInfo;
4292 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4293 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4294 rcCol = lpColumnInfo->rcHeader;
4295 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4296 rcCol.left = rcCol.right;
4298 /* ajust the other columns */
4299 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4301 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4302 lpColumnInfo->rcHeader.left += dx;
4303 lpColumnInfo->rcHeader.right += dx;
4306 /* do not update screen if not in report mode */
4307 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4309 /* if we have a focus, must first erase the focus rect */
4310 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4312 /* Need to reset the item width when inserting a new column */
4313 infoPtr->nItemWidth += dx;
4315 LISTVIEW_UpdateScroll(infoPtr);
4316 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
4318 /* scroll to cover the deleted column, and invalidate for redraw */
4319 rcOld = infoPtr->rcList;
4320 rcOld.left = ptOrigin.x + rcCol.left + dx;
4321 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4323 /* we can restore focus now */
4324 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4329 * Removes a column from the listview control.
4332 * [I] infoPtr : valid pointer to the listview structure
4333 * [I] nColumn : column index
4339 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4343 TRACE("nColumn=%d\n", nColumn);
4345 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4346 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4348 /* While the MSDN specifically says that column zero should not be deleted,
4349 what actually happens is that the column itself is deleted but no items or subitems
4353 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4355 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4358 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4359 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4361 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4363 SUBITEM_INFO *lpSubItem, *lpDelItem;
4365 INT nItem, nSubItem, i;
4367 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4369 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
4372 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4374 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
4375 if (lpSubItem->iSubItem == nColumn)
4378 lpDelItem = lpSubItem;
4380 else if (lpSubItem->iSubItem > nColumn)
4382 lpSubItem->iSubItem--;
4386 /* if we found our subitem, zapp it */
4390 if (is_textW(lpDelItem->hdr.pszText))
4391 Free(lpDelItem->hdr.pszText);
4396 /* free dpa memory */
4397 DPA_DeletePtr(hdpaSubItems, nSubItem);
4402 /* update the other column info */
4403 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4404 LISTVIEW_InvalidateList(infoPtr);
4406 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4413 * Invalidates the listview after an item's insertion or deletion.
4416 * [I] infoPtr : valid pointer to the listview structure
4417 * [I] nItem : item index
4418 * [I] dir : -1 if deleting, 1 if inserting
4423 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4425 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4426 INT nPerCol, nItemCol, nItemRow;
4430 /* if we don't refresh, what's the point of scrolling? */
4431 if (!is_redrawing(infoPtr)) return;
4433 assert (abs(dir) == 1);
4435 /* arrange icons if autoarrange is on */
4436 if (is_autoarrange(infoPtr))
4438 BOOL arrange = TRUE;
4439 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4440 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4441 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4444 /* scrollbars need updating */
4445 LISTVIEW_UpdateScroll(infoPtr);
4447 /* figure out the item's position */
4448 if (uView == LVS_REPORT)
4449 nPerCol = infoPtr->nItemCount + 1;
4450 else if (uView == LVS_LIST)
4451 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4452 else /* LVS_ICON, or LVS_SMALLICON */
4455 nItemCol = nItem / nPerCol;
4456 nItemRow = nItem % nPerCol;
4457 LISTVIEW_GetOrigin(infoPtr, &Origin);
4459 /* move the items below up a slot */
4460 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4461 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4462 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4463 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4464 OffsetRect(&rcScroll, Origin.x, Origin.y);
4465 TRACE("rcScroll=%s, dx=%d\n", debugrect(&rcScroll), dir * infoPtr->nItemHeight);
4466 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4468 TRACE("Scrolling rcScroll=%s, rcList=%s\n", debugrect(&rcScroll), debugrect(&infoPtr->rcList));
4469 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4470 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4473 /* report has only that column, so we're done */
4474 if (uView == LVS_REPORT) return;
4476 /* now for LISTs, we have to deal with the columns to the right */
4477 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4479 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4480 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4481 OffsetRect(&rcScroll, Origin.x, Origin.y);
4482 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4483 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4484 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4489 * Removes an item from the listview control.
4492 * [I] infoPtr : valid pointer to the listview structure
4493 * [I] nItem : item index
4499 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4501 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4504 TRACE("(nItem=%d)\n", nItem);
4506 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4508 /* remove selection, and focus */
4510 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4511 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4513 /* send LVN_DELETEITEM notification. */
4514 notify_deleteitem(infoPtr, nItem);
4516 /* we need to do this here, because we'll be deleting stuff */
4517 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4518 LISTVIEW_InvalidateItem(infoPtr, nItem);
4520 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4526 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4527 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4529 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, i);
4530 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4533 DPA_Destroy(hdpaSubItems);
4536 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4538 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4539 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4542 infoPtr->nItemCount--;
4543 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4545 /* now is the invalidation fun */
4546 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4553 * Callback implementation for editlabel control
4556 * [I] infoPtr : valid pointer to the listview structure
4557 * [I] pszText : modified text
4558 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4564 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4566 NMLVDISPINFOW dispInfo;
4568 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4570 ZeroMemory(&dispInfo, sizeof(dispInfo));
4571 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
4572 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4573 dispInfo.item.iSubItem = 0;
4574 dispInfo.item.stateMask = ~0;
4575 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4576 /* add the text from the edit in */
4577 dispInfo.item.mask |= LVIF_TEXT;
4578 dispInfo.item.pszText = pszText;
4579 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4581 /* Do we need to update the Item Text */
4582 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4583 if (!pszText) return TRUE;
4585 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4587 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nEditLabelItem);
4588 ITEM_INFO* lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
4589 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
4591 LISTVIEW_InvalidateItem(infoPtr, infoPtr->nEditLabelItem);
4596 ZeroMemory(&dispInfo, sizeof(dispInfo));
4597 dispInfo.item.mask = LVIF_TEXT;
4598 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4599 dispInfo.item.iSubItem = 0;
4600 dispInfo.item.pszText = pszText;
4601 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4602 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4607 * Begin in place editing of specified list view item
4610 * [I] infoPtr : valid pointer to the listview structure
4611 * [I] nItem : item index
4612 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4618 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4620 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4621 NMLVDISPINFOW dispInfo;
4624 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4626 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4627 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4629 infoPtr->nEditLabelItem = nItem;
4631 /* Is the EditBox still there, if so remove it */
4632 if(infoPtr->hwndEdit != 0)
4634 SetFocus(infoPtr->hwndSelf);
4635 infoPtr->hwndEdit = 0;
4638 LISTVIEW_SetSelection(infoPtr, nItem);
4639 LISTVIEW_SetItemFocus(infoPtr, nItem);
4640 LISTVIEW_InvalidateItem(infoPtr, nItem);
4642 rect.left = LVIR_LABEL;
4643 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4645 ZeroMemory(&dispInfo, sizeof(dispInfo));
4646 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4647 dispInfo.item.iItem = nItem;
4648 dispInfo.item.iSubItem = 0;
4649 dispInfo.item.stateMask = ~0;
4650 dispInfo.item.pszText = szDispText;
4651 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4652 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4654 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4655 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4656 if (!infoPtr->hwndEdit) return 0;
4658 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4660 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4661 infoPtr->hwndEdit = 0;
4665 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4666 SetFocus(infoPtr->hwndEdit);
4667 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4668 return infoPtr->hwndEdit;
4674 * Ensures the specified item is visible, scrolling into view if necessary.
4677 * [I] infoPtr : valid pointer to the listview structure
4678 * [I] nItem : item index
4679 * [I] bPartial : partially or entirely visible
4685 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4687 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4688 INT nScrollPosHeight = 0;
4689 INT nScrollPosWidth = 0;
4690 INT nHorzAdjust = 0;
4691 INT nVertAdjust = 0;
4694 RECT rcItem, rcTemp;
4696 rcItem.left = LVIR_BOUNDS;
4697 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4699 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4701 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4703 /* scroll left/right, but in LVS_REPORT mode */
4704 if (uView == LVS_LIST)
4705 nScrollPosWidth = infoPtr->nItemWidth;
4706 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4707 nScrollPosWidth = 1;
4709 if (rcItem.left < infoPtr->rcList.left)
4712 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
4717 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
4721 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
4723 /* scroll up/down, but not in LVS_LIST mode */
4724 if (uView == LVS_REPORT)
4725 nScrollPosHeight = infoPtr->nItemHeight;
4726 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
4727 nScrollPosHeight = 1;
4729 if (rcItem.top < infoPtr->rcList.top)
4732 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
4737 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
4741 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
4743 if (nScrollPosWidth)
4745 INT diff = nHorzDiff / nScrollPosWidth;
4746 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
4747 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
4750 if (nScrollPosHeight)
4752 INT diff = nVertDiff / nScrollPosHeight;
4753 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
4754 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
4762 * Searches for an item with specific characteristics.
4765 * [I] hwnd : window handle
4766 * [I] nStart : base item index
4767 * [I] lpFindInfo : item information to look for
4770 * SUCCESS : index of item
4773 static INT LISTVIEW_FindItemW(LISTVIEW_INFO *infoPtr, INT nStart,
4774 const LVFINDINFOW *lpFindInfo)
4776 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4777 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4778 BOOL bWrap = FALSE, bNearest = FALSE;
4779 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
4780 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
4781 POINT Position, Destination;
4784 if (!lpFindInfo || nItem < 0) return -1;
4787 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
4789 lvItem.mask |= LVIF_TEXT;
4790 lvItem.pszText = szDispText;
4791 lvItem.cchTextMax = DISP_TEXT_SIZE;
4794 if (lpFindInfo->flags & LVFI_WRAP)
4797 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
4798 (uView == LVS_ICON || uView ==LVS_SMALLICON))
4803 LISTVIEW_GetOrigin(infoPtr, &Origin);
4804 Destination.x = lpFindInfo->pt.x - Origin.x;
4805 Destination.y = lpFindInfo->pt.y - Origin.y;
4806 switch(lpFindInfo->vkDirection)
4808 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
4809 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
4810 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
4811 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
4812 case VK_HOME: Destination.x = Destination.y = 0; break;
4813 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4814 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4816 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
4817 Destination.x = rcArea.right;
4818 Destination.y = rcArea.bottom;
4820 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
4824 else Destination.x = Destination.y = 0;
4826 /* if LVFI_PARAM is specified, all other flags are ignored */
4827 if (lpFindInfo->flags & LVFI_PARAM)
4829 lvItem.mask |= LVIF_PARAM;
4831 lvItem.mask &= ~LVIF_TEXT;
4835 for (; nItem < nLast; nItem++)
4837 lvItem.iItem = nItem;
4838 lvItem.iSubItem = 0;
4839 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
4841 if (lvItem.mask & LVIF_PARAM)
4843 if (lpFindInfo->lParam == lvItem.lParam)
4849 if (lvItem.mask & LVIF_TEXT)
4851 if (lpFindInfo->flags & LVFI_PARTIAL)
4853 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
4857 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
4861 if (!bNearest) return nItem;
4863 /* This is very inefficient. To do a good job here,
4864 * we need a sorted array of (x,y) item positions */
4865 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
4867 /* compute the distance^2 to the destination */
4868 xdist = Destination.x - Position.x;
4869 ydist = Destination.y - Position.y;
4870 dist = xdist * xdist + ydist * ydist;
4872 /* remember the distance, and item if it's closer */
4876 nNearestItem = nItem;
4883 nLast = min(nStart + 1, infoPtr->nItemCount);
4888 return nNearestItem;
4893 * Searches for an item with specific characteristics.
4896 * [I] hwnd : window handle
4897 * [I] nStart : base item index
4898 * [I] lpFindInfo : item information to look for
4901 * SUCCESS : index of item
4904 static INT LISTVIEW_FindItemA(LISTVIEW_INFO *infoPtr, INT nStart,
4905 const LVFINDINFOA *lpFindInfo)
4907 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
4911 memcpy(&fiw, lpFindInfo, sizeof(fiw));
4912 if (hasText) fiw.psz = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
4913 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
4914 if (hasText) textfreeT((LPWSTR)fiw.psz, FALSE);
4920 * Retrieves the background image of the listview control.
4923 * [I] infoPtr : valid pointer to the listview structure
4924 * [O] lpBkImage : background image attributes
4930 /* static BOOL LISTVIEW_GetBkImage(LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
4932 /* FIXME (listview, "empty stub!\n"); */
4938 * Retrieves column attributes.
4941 * [I] infoPtr : valid pointer to the listview structure
4942 * [I] nColumn : column index
4943 * [IO] lpColumn : column information
4944 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
4945 * otherwise it is in fact a LPLVCOLUMNA
4951 static BOOL LISTVIEW_GetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
4953 COLUMN_INFO *lpColumnInfo;
4956 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4957 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
4959 /* initialize memory */
4960 ZeroMemory(&hdi, sizeof(hdi));
4962 if (lpColumn->mask & LVCF_TEXT)
4964 hdi.mask |= HDI_TEXT;
4965 hdi.pszText = lpColumn->pszText;
4966 hdi.cchTextMax = lpColumn->cchTextMax;
4969 if (lpColumn->mask & LVCF_IMAGE)
4970 hdi.mask |= HDI_IMAGE;
4972 if (lpColumn->mask & LVCF_ORDER)
4973 hdi.mask |= HDI_ORDER;
4975 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
4977 if (lpColumn->mask & LVCF_FMT)
4978 lpColumn->fmt = lpColumnInfo->fmt;
4980 if (lpColumn->mask & LVCF_WIDTH)
4981 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
4983 if (lpColumn->mask & LVCF_IMAGE)
4984 lpColumn->iImage = hdi.iImage;
4986 if (lpColumn->mask & LVCF_ORDER)
4987 lpColumn->iOrder = hdi.iOrder;
4993 static BOOL LISTVIEW_GetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
5000 /* FIXME: little hack */
5001 for (i = 0; i < iCount; i++)
5009 * Retrieves the column width.
5012 * [I] infoPtr : valid pointer to the listview structure
5013 * [I] int : column index
5016 * SUCCESS : column width
5019 static INT LISTVIEW_GetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn)
5021 INT nColumnWidth = 0;
5024 TRACE("nColumn=%d\n", nColumn);
5026 /* we have a 'column' in LIST and REPORT mode only */
5027 switch(infoPtr->dwStyle & LVS_TYPEMASK)
5030 nColumnWidth = infoPtr->nItemWidth;
5033 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
5034 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
5035 nColumnWidth = rcHeader.right - rcHeader.left;
5039 TRACE("nColumnWidth=%d\n", nColumnWidth);
5040 return nColumnWidth;
5045 * In list or report display mode, retrieves the number of items that can fit
5046 * vertically in the visible area. In icon or small icon display mode,
5047 * retrieves the total number of visible items.
5050 * [I] infoPtr : valid pointer to the listview structure
5053 * Number of fully visible items.
5055 static INT LISTVIEW_GetCountPerPage(LISTVIEW_INFO *infoPtr)
5057 switch (infoPtr->dwStyle & LVS_TYPEMASK)
5061 return infoPtr->nItemCount;
5063 return LISTVIEW_GetCountPerColumn(infoPtr);
5065 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5073 * Retrieves an image list handle.
5076 * [I] infoPtr : valid pointer to the listview structure
5077 * [I] nImageList : image list identifier
5080 * SUCCESS : image list handle
5083 static HIMAGELIST LISTVIEW_GetImageList(LISTVIEW_INFO *infoPtr, INT nImageList)
5087 case LVSIL_NORMAL: return infoPtr->himlNormal;
5088 case LVSIL_SMALL: return infoPtr->himlSmall;
5089 case LVSIL_STATE: return infoPtr->himlState;
5094 /* LISTVIEW_GetISearchString */
5098 * Retrieves item attributes.
5101 * [I] hwnd : window handle
5102 * [IO] lpLVItem : item info
5103 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5104 * if FALSE, the lpLVItem is a LPLVITEMA.
5107 * This is the internal 'GetItem' interface -- it tries to
5108 * be smart, and avoids text copies, if possible, by modifing
5109 * lpLVItem->pszText to point to the text string. Please note
5110 * that this is not always possible (e.g. OWNERDATA), so on
5111 * entry you *must* supply valid values for pszText, and cchTextMax.
5112 * The only difference to the documented interface is that upon
5113 * return, you should use *only* the lpLVItem->pszText, rather than
5114 * the buffer pointer you provided on input. Most code already does
5115 * that, so it's not a problem.
5116 * For the two cases when the text must be copied (that is,
5117 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5123 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5125 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5126 NMLVDISPINFOW dispInfo;
5132 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5134 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5137 if (lpLVItem->mask == 0) return TRUE;
5139 /* make a local copy */
5140 isubitem = lpLVItem->iSubItem;
5142 /* a quick optimization if all we're asked is the focus state
5143 * these queries are worth optimising since they are common,
5144 * and can be answered in constant time, without the heavy accesses */
5145 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5146 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5148 lpLVItem->state = 0;
5149 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5150 lpLVItem->state |= LVIS_FOCUSED;
5154 ZeroMemory(&dispInfo, sizeof(dispInfo));
5156 /* if the app stores all the data, handle it separately */
5157 if (infoPtr->dwStyle & LVS_OWNERDATA)
5159 dispInfo.item.state = 0;
5161 /* apprently, we should not callback for lParam in LVS_OWNERDATA */
5162 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5164 /* NOTE: copy only fields which we _know_ are initialized, some apps
5165 * depend on the uninitialized fields being 0 */
5166 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5167 dispInfo.item.iItem = lpLVItem->iItem;
5168 dispInfo.item.iSubItem = isubitem;
5169 if (lpLVItem->mask & LVIF_TEXT)
5171 dispInfo.item.pszText = lpLVItem->pszText;
5172 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5174 if (lpLVItem->mask & LVIF_STATE)
5175 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5176 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5177 dispInfo.item.stateMask = lpLVItem->stateMask;
5178 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5180 /* full size structure expected - _WIN32IE >= 0x560 */
5181 *lpLVItem = dispInfo.item;
5183 else if (lpLVItem->mask & LVIF_INDENT)
5185 /* indent member expected - _WIN32IE >= 0x300 */
5186 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5190 /* minimal structure expected */
5191 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5193 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5196 /* make sure lParam is zeroed out */
5197 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5199 /* we store only a little state, so if we're not asked, we're done */
5200 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5202 /* if focus is handled by us, report it */
5203 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5205 lpLVItem->state &= ~LVIS_FOCUSED;
5206 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5207 lpLVItem->state |= LVIS_FOCUSED;
5210 /* and do the same for selection, if we handle it */
5211 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5213 lpLVItem->state &= ~LVIS_SELECTED;
5214 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5215 lpLVItem->state |= LVIS_SELECTED;
5221 /* find the item and subitem structures before we proceed */
5222 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5223 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
5228 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5229 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5232 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5237 pItemHdr = &lpItem->hdr;
5239 /* Do we need to query the state from the app? */
5240 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5242 dispInfo.item.mask |= LVIF_STATE;
5243 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5246 /* Do we need to enquire about the image? */
5247 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5248 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5250 dispInfo.item.mask |= LVIF_IMAGE;
5251 dispInfo.item.iImage = I_IMAGECALLBACK;
5254 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5255 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
5257 dispInfo.item.mask |= LVIF_TEXT;
5258 dispInfo.item.pszText = lpLVItem->pszText;
5259 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5260 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5261 *dispInfo.item.pszText = '\0';
5264 /* If we don't have all the requested info, query the application */
5265 if (dispInfo.item.mask != 0)
5267 dispInfo.item.iItem = lpLVItem->iItem;
5268 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5269 dispInfo.item.lParam = lpItem->lParam;
5270 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5271 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5274 /* we should not store values for subitems */
5275 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5277 /* Now, handle the iImage field */
5278 if (dispInfo.item.mask & LVIF_IMAGE)
5280 lpLVItem->iImage = dispInfo.item.iImage;
5281 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5282 pItemHdr->iImage = dispInfo.item.iImage;
5284 else if (lpLVItem->mask & LVIF_IMAGE)
5286 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5287 lpLVItem->iImage = pItemHdr->iImage;
5289 lpLVItem->iImage = 0;
5292 /* The pszText field */
5293 if (dispInfo.item.mask & LVIF_TEXT)
5295 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5296 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5298 lpLVItem->pszText = dispInfo.item.pszText;
5300 else if (lpLVItem->mask & LVIF_TEXT)
5302 if (isW) lpLVItem->pszText = pItemHdr->pszText;
5303 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5306 /* if this is a subitem, we're done */
5307 if (isubitem) return TRUE;
5309 /* Next is the lParam field */
5310 if (dispInfo.item.mask & LVIF_PARAM)
5312 lpLVItem->lParam = dispInfo.item.lParam;
5313 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5314 lpItem->lParam = dispInfo.item.lParam;
5316 else if (lpLVItem->mask & LVIF_PARAM)
5317 lpLVItem->lParam = lpItem->lParam;
5319 /* ... the state field (this one is different due to uCallbackmask) */
5320 if (lpLVItem->mask & LVIF_STATE)
5322 lpLVItem->state = lpItem->state;
5323 if (dispInfo.item.mask & LVIF_STATE)
5325 lpLVItem->state &= ~dispInfo.item.stateMask;
5326 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5328 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5330 lpLVItem->state &= ~LVIS_FOCUSED;
5331 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5332 lpLVItem->state |= LVIS_FOCUSED;
5334 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5336 lpLVItem->state &= ~LVIS_SELECTED;
5337 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5338 lpLVItem->state |= LVIS_SELECTED;
5342 /* and last, but not least, the indent field */
5343 if (lpLVItem->mask & LVIF_INDENT)
5344 lpLVItem->iIndent = lpItem->iIndent;
5351 * Retrieves item attributes.
5354 * [I] hwnd : window handle
5355 * [IO] lpLVItem : item info
5356 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5357 * if FALSE, the lpLVItem is a LPLVITEMA.
5360 * This is the external 'GetItem' interface -- it properly copies
5361 * the text in the provided buffer.
5367 static BOOL LISTVIEW_GetItemExtT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5372 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5375 pszText = lpLVItem->pszText;
5376 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5377 if (bResult && lpLVItem->pszText != pszText)
5378 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5379 lpLVItem->pszText = pszText;
5387 * Retrieves the position (upper-left) of the listview control item.
5388 * Note that for LVS_ICON style, the upper-left is that of the icon
5389 * and not the bounding box.
5392 * [I] infoPtr : valid pointer to the listview structure
5393 * [I] nItem : item index
5394 * [O] lpptPosition : coordinate information
5400 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5402 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5405 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5407 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5409 LISTVIEW_GetOrigin(infoPtr, &Origin);
5410 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5412 if (uView == LVS_ICON)
5414 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5415 lpptPosition->y += ICON_TOP_PADDING;
5417 lpptPosition->x += Origin.x;
5418 lpptPosition->y += Origin.y;
5420 TRACE (" lpptPosition=%s\n", debugpoint(lpptPosition));
5427 * Retrieves the bounding rectangle for a listview control item.
5430 * [I] infoPtr : valid pointer to the listview structure
5431 * [I] nItem : item index
5432 * [IO] lprc : bounding rectangle coordinates
5433 * lprc->left specifies the portion of the item for which the bounding
5434 * rectangle will be retrieved.
5436 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5437 * including the icon and label.
5440 * * Experiment shows that native control returns:
5441 * * width = min (48, length of text line)
5442 * * .left = position.x - (width - iconsize.cx)/2
5443 * * .right = .left + width
5444 * * height = #lines of text * ntmHeight + icon height + 8
5445 * * .top = position.y - 2
5446 * * .bottom = .top + height
5447 * * separation between items .y = itemSpacing.cy - height
5448 * * .x = itemSpacing.cx - width
5449 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5452 * * Experiment shows that native control returns:
5453 * * width = iconSize.cx + 16
5454 * * .left = position.x - (width - iconsize.cx)/2
5455 * * .right = .left + width
5456 * * height = iconSize.cy + 4
5457 * * .top = position.y - 2
5458 * * .bottom = .top + height
5459 * * separation between items .y = itemSpacing.cy - height
5460 * * .x = itemSpacing.cx - width
5461 * LVIR_LABEL Returns the bounding rectangle of the item text.
5464 * * Experiment shows that native control returns:
5465 * * width = text length
5466 * * .left = position.x - width/2
5467 * * .right = .left + width
5468 * * height = ntmH * linecount + 2
5469 * * .top = position.y + iconSize.cy + 6
5470 * * .bottom = .top + height
5471 * * separation between items .y = itemSpacing.cy - height
5472 * * .x = itemSpacing.cx - width
5473 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5474 * rectangles, but excludes columns in report view.
5481 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5482 * upon whether the window has the focus currently and on whether the item
5483 * is the one with the focus. Ensure that the control's record of which
5484 * item has the focus agrees with the items' records.
5486 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5488 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5489 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5490 BOOL doLabel = TRUE, oversizedBox = FALSE;
5491 POINT Position, Origin;
5495 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5497 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5499 LISTVIEW_GetOrigin(infoPtr, &Origin);
5500 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5502 /* Be smart and try to figure out the minimum we have to do */
5503 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5504 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5505 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5506 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5507 oversizedBox = TRUE;
5509 /* get what we need from the item before hand, so we make
5510 * only one request. This can speed up things, if data
5511 * is stored on the app side */
5513 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5514 if (doLabel) lvItem.mask |= LVIF_TEXT;
5515 lvItem.iItem = nItem;
5516 lvItem.iSubItem = 0;
5517 lvItem.pszText = szDispText;
5518 lvItem.cchTextMax = DISP_TEXT_SIZE;
5519 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5520 /* we got the state already up, simulate it here, to avoid a reget */
5521 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5523 lvItem.mask |= LVIF_STATE;
5524 lvItem.stateMask = LVIS_FOCUSED;
5525 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5528 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5529 lprc->left = LVIR_BOUNDS;
5533 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5537 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, lprc);
5541 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5544 case LVIR_SELECTBOUNDS:
5545 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, &label_rect);
5546 UnionRect(lprc, lprc, &label_rect);
5550 WARN("Unknown value: %ld\n", lprc->left);
5554 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5556 TRACE(" rect=%s\n", debugrect(lprc));
5563 * Retrieves the spacing between listview control items.
5566 * [I] infoPtr : valid pointer to the listview structure
5567 * [IO] lprc : rectangle to receive the output
5568 * on input, lprc->top = nSubItem
5569 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5571 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5572 * not only those of the first column.
5573 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5579 static BOOL LISTVIEW_GetSubItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5584 if (!lprc) return FALSE;
5586 TRACE("(nItem=%d, nSubItem=%ld)\n", nItem, lprc->top);
5587 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5589 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5591 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5593 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5596 lvItem.iItem = nItem;
5597 lvItem.iSubItem = lprc->top;
5599 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5603 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5608 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5612 ERR("Unknown bounds=%ld\n", lprc->left);
5616 OffsetRect(lprc, Position.x, Position.y);
5623 * Retrieves the width of a label.
5626 * [I] infoPtr : valid pointer to the listview structure
5629 * SUCCESS : string width (in pixels)
5632 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *infoPtr, INT nItem)
5634 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5637 TRACE("(nItem=%d)\n", nItem);
5639 lvItem.mask = LVIF_TEXT;
5640 lvItem.iItem = nItem;
5641 lvItem.iSubItem = 0;
5642 lvItem.pszText = szDispText;
5643 lvItem.cchTextMax = DISP_TEXT_SIZE;
5644 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5646 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5651 * Retrieves the spacing between listview control items.
5654 * [I] infoPtr : valid pointer to the listview structure
5655 * [I] bSmall : flag for small or large icon
5658 * Horizontal + vertical spacing
5660 static LONG LISTVIEW_GetItemSpacing(LISTVIEW_INFO *infoPtr, BOOL bSmall)
5666 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5670 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5671 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5673 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5680 * Retrieves the state of a listview control item.
5683 * [I] infoPtr : valid pointer to the listview structure
5684 * [I] nItem : item index
5685 * [I] uMask : state mask
5688 * State specified by the mask.
5690 static UINT LISTVIEW_GetItemState(LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5694 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5696 lvItem.iItem = nItem;
5697 lvItem.iSubItem = 0;
5698 lvItem.mask = LVIF_STATE;
5699 lvItem.stateMask = uMask;
5700 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5702 return lvItem.state & uMask;
5707 * Retrieves the text of a listview control item or subitem.
5710 * [I] hwnd : window handle
5711 * [I] nItem : item index
5712 * [IO] lpLVItem : item information
5713 * [I] isW : TRUE if lpLVItem is Unicode
5716 * SUCCESS : string length
5719 static INT LISTVIEW_GetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
5721 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5723 lpLVItem->mask = LVIF_TEXT;
5724 lpLVItem->iItem = nItem;
5725 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
5727 return textlenT(lpLVItem->pszText, isW);
5732 * Searches for an item based on properties + relationships.
5735 * [I] infoPtr : valid pointer to the listview structure
5736 * [I] nItem : item index
5737 * [I] uFlags : relationship flag
5740 * SUCCESS : item index
5743 static INT LISTVIEW_GetNextItem(LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
5745 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5747 LVFINDINFOW lvFindInfo;
5748 INT nCountPerColumn;
5752 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
5753 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
5755 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
5757 if (uFlags & LVNI_CUT)
5760 if (uFlags & LVNI_DROPHILITED)
5761 uMask |= LVIS_DROPHILITED;
5763 if (uFlags & LVNI_FOCUSED)
5764 uMask |= LVIS_FOCUSED;
5766 if (uFlags & LVNI_SELECTED)
5767 uMask |= LVIS_SELECTED;
5769 /* if we're asked for the focused item, that's only one,
5770 * so it's worth optimizing */
5771 if (uFlags & LVNI_FOCUSED)
5773 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
5774 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
5777 if (uFlags & LVNI_ABOVE)
5779 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5784 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5790 /* Special case for autoarrange - move 'til the top of a list */
5791 if (is_autoarrange(infoPtr))
5793 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5794 while (nItem - nCountPerRow >= 0)
5796 nItem -= nCountPerRow;
5797 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5802 lvFindInfo.flags = LVFI_NEARESTXY;
5803 lvFindInfo.vkDirection = VK_UP;
5804 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5805 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5807 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5812 else if (uFlags & LVNI_BELOW)
5814 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5816 while (nItem < infoPtr->nItemCount)
5819 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5825 /* Special case for autoarrange - move 'til the bottom of a list */
5826 if (is_autoarrange(infoPtr))
5828 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5829 while (nItem + nCountPerRow < infoPtr->nItemCount )
5831 nItem += nCountPerRow;
5832 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5837 lvFindInfo.flags = LVFI_NEARESTXY;
5838 lvFindInfo.vkDirection = VK_DOWN;
5839 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5840 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5842 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5847 else if (uFlags & LVNI_TOLEFT)
5849 if (uView == LVS_LIST)
5851 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5852 while (nItem - nCountPerColumn >= 0)
5854 nItem -= nCountPerColumn;
5855 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5859 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5861 /* Special case for autoarrange - move 'ti the beginning of a row */
5862 if (is_autoarrange(infoPtr))
5864 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5865 while (nItem % nCountPerRow > 0)
5868 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5873 lvFindInfo.flags = LVFI_NEARESTXY;
5874 lvFindInfo.vkDirection = VK_LEFT;
5875 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5876 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5878 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5883 else if (uFlags & LVNI_TORIGHT)
5885 if (uView == LVS_LIST)
5887 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5888 while (nItem + nCountPerColumn < infoPtr->nItemCount)
5890 nItem += nCountPerColumn;
5891 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5895 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5897 /* Special case for autoarrange - move 'til the end of a row */
5898 if (is_autoarrange(infoPtr))
5900 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5901 while (nItem % nCountPerRow < nCountPerRow - 1 )
5904 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5909 lvFindInfo.flags = LVFI_NEARESTXY;
5910 lvFindInfo.vkDirection = VK_RIGHT;
5911 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5912 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5914 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5923 /* search by index */
5924 for (i = nItem; i < infoPtr->nItemCount; i++)
5926 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
5934 /* LISTVIEW_GetNumberOfWorkAreas */
5938 * Retrieves the origin coordinates when in icon or small icon display mode.
5941 * [I] infoPtr : valid pointer to the listview structure
5942 * [O] lpptOrigin : coordinate information
5947 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
5949 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5950 INT nHorzPos = 0, nVertPos = 0;
5951 SCROLLINFO scrollInfo;
5953 scrollInfo.cbSize = sizeof(SCROLLINFO);
5954 scrollInfo.fMask = SIF_POS;
5956 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
5957 nHorzPos = scrollInfo.nPos;
5958 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
5959 nVertPos = scrollInfo.nPos;
5961 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
5963 lpptOrigin->x = infoPtr->rcList.left;
5964 lpptOrigin->y = infoPtr->rcList.top;
5965 if (uView == LVS_LIST)
5966 nHorzPos *= infoPtr->nItemWidth;
5967 else if (uView == LVS_REPORT)
5968 nVertPos *= infoPtr->nItemHeight;
5970 lpptOrigin->x -= nHorzPos;
5971 lpptOrigin->y -= nVertPos;
5973 TRACE(" origin=%s\n", debugpoint(lpptOrigin));
5978 * Retrieves the width of a string.
5981 * [I] hwnd : window handle
5982 * [I] lpszText : text string to process
5983 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
5986 * SUCCESS : string width (in pixels)
5989 static INT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
5994 if (is_textT(lpszText, isW))
5996 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
5997 HDC hdc = GetDC(infoPtr->hwndSelf);
5998 HFONT hOldFont = SelectObject(hdc, hFont);
6001 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
6003 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
6004 SelectObject(hdc, hOldFont);
6005 ReleaseDC(infoPtr->hwndSelf, hdc);
6007 return stringSize.cx;
6012 * Determines which listview item is located at the specified position.
6015 * [I] infoPtr : valid pointer to the listview structure
6016 * [IO] lpht : hit test information
6017 * [I] subitem : fill out iSubItem.
6018 * [I] select : return the index only if the hit selects the item
6021 * (mm 20001022): We must not allow iSubItem to be touched, for
6022 * an app might pass only a structure with space up to iItem!
6023 * (MS Office 97 does that for instance in the file open dialog)
6026 * SUCCESS : item index
6029 static INT LISTVIEW_HitTest(LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6031 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6032 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6033 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6034 POINT Origin, Position, opt;
6039 TRACE("(pt=%s, subitem=%d, select=%d)\n", debugpoint(&lpht->pt), subitem, select);
6043 if (subitem) lpht->iSubItem = 0;
6045 if (infoPtr->rcList.left > lpht->pt.x)
6046 lpht->flags |= LVHT_TOLEFT;
6047 else if (infoPtr->rcList.right < lpht->pt.x)
6048 lpht->flags |= LVHT_TORIGHT;
6050 if (infoPtr->rcList.top > lpht->pt.y)
6051 lpht->flags |= LVHT_ABOVE;
6052 else if (infoPtr->rcList.bottom < lpht->pt.y)
6053 lpht->flags |= LVHT_BELOW;
6055 TRACE("lpht->flags=0x%x\n", lpht->flags);
6056 if (lpht->flags) return -1;
6058 lpht->flags |= LVHT_NOWHERE;
6060 LISTVIEW_GetOrigin(infoPtr, &Origin);
6062 /* first deal with the large items */
6063 rcSearch.left = lpht->pt.x;
6064 rcSearch.top = lpht->pt.y;
6065 rcSearch.right = rcSearch.left + 1;
6066 rcSearch.bottom = rcSearch.top + 1;
6068 iterator_frameditems(&i, infoPtr, &rcSearch);
6069 iterator_next(&i); /* go to first item in the sequence */
6071 iterator_destroy(&i);
6073 TRACE("lpht->iItem=%d\n", iItem);
6074 if (iItem == -1) return -1;
6076 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6077 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
6078 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6079 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6080 lvItem.iItem = iItem;
6081 lvItem.iSubItem = 0;
6082 lvItem.pszText = szDispText;
6083 lvItem.cchTextMax = DISP_TEXT_SIZE;
6084 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6085 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6087 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
6088 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6089 opt.x = lpht->pt.x - Position.x - Origin.x;
6090 opt.y = lpht->pt.y - Position.y - Origin.y;
6092 if (uView == LVS_REPORT)
6095 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6096 TRACE("rcBounds=%s\n", debugrect(&rcBounds));
6097 if (!PtInRect(&rcBounds, opt)) return -1;
6099 if (PtInRect(&rcIcon, opt))
6100 lpht->flags |= LVHT_ONITEMICON;
6101 else if (PtInRect(&rcLabel, opt))
6102 lpht->flags |= LVHT_ONITEMLABEL;
6103 else if (infoPtr->himlState && ((lvItem.state & LVIS_STATEIMAGEMASK) >> 12) && PtInRect(&rcState, opt))
6104 lpht->flags |= LVHT_ONITEMSTATEICON;
6105 if (lpht->flags & LVHT_ONITEM)
6106 lpht->flags &= ~LVHT_NOWHERE;
6108 TRACE("lpht->flags=0x%x\n", lpht->flags);
6109 if (uView == LVS_REPORT && subitem)
6113 rcBounds.right = rcBounds.left;
6114 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6116 rcBounds.left = rcBounds.right;
6117 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
6118 if (PtInRect(&rcBounds, opt))
6126 if (select && !(uView == LVS_REPORT &&
6127 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6128 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6130 if (uView == LVS_REPORT)
6132 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6133 UnionRect(&rcBounds, &rcBounds, &rcState);
6135 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6137 return lpht->iItem = iItem;
6141 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
6142 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
6143 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
6144 and not during the processing of a LVM_SORTITEMS message. Applications should provide
6145 their own sort proc. when sending LVM_SORTITEMS.
6148 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
6150 LVS_SORTXXX must be specified,
6151 LVS_OWNERDRAW is not set,
6152 <item>.pszText is not LPSTR_TEXTCALLBACK.
6154 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
6155 are sorted based on item text..."
6157 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
6159 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
6160 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
6161 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
6163 /* if we're sorting descending, negate the return value */
6164 return (((LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
6169 * Inserts a new item in the listview control.
6172 * [I] infoPtr : valid pointer to the listview structure
6173 * [I] lpLVItem : item information
6174 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6177 * SUCCESS : new item index
6180 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6182 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6187 BOOL is_sorted, has_changed;
6190 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6192 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6194 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6195 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iSubItem) return -1;
6197 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6199 if (!(lpItem = (ITEM_INFO *)Alloc(sizeof(ITEM_INFO)))) return -1;
6201 /* insert item in listview control data structure */
6202 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6203 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6205 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6206 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6208 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
6209 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6210 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6211 if (nItem == -1) goto fail;
6212 infoPtr->nItemCount++;
6214 /* shift indices first so they don't get tangled */
6215 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6217 /* set the item attributes */
6218 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6220 /* full size structure expected - _WIN32IE >= 0x560 */
6223 else if (lpLVItem->mask & LVIF_INDENT)
6225 /* indent member expected - _WIN32IE >= 0x300 */
6226 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6230 /* minimal structure expected */
6231 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6234 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) item.state &= ~LVIS_STATEIMAGEMASK;
6235 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6237 /* if we're sorted, sort the list, and update the index */
6240 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
6241 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
6242 assert(nItem != -1);
6245 /* make room for the position, if we are in the right mode */
6246 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6248 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6250 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6252 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6257 /* send LVN_INSERTITEM notification */
6258 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6260 nmlv.lParam = lpItem->lParam;
6261 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6263 /* align items (set position of each item) */
6264 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6268 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6269 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6271 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6273 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6276 /* now is the invalidation fun */
6277 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6281 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6282 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6283 infoPtr->nItemCount--;
6285 DPA_DeletePtr(hdpaSubItems, 0);
6286 DPA_Destroy (hdpaSubItems);
6293 * Redraws a range of items.
6296 * [I] infoPtr : valid pointer to the listview structure
6297 * [I] nFirst : first item
6298 * [I] nLast : last item
6304 static BOOL LISTVIEW_RedrawItems(LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6308 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6309 max(nFirst, nLast) >= infoPtr->nItemCount)
6312 for (i = nFirst; i <= nLast; i++)
6313 LISTVIEW_InvalidateItem(infoPtr, i);
6320 * Scroll the content of a listview.
6323 * [I] infoPtr : valid pointer to the listview structure
6324 * [I] dx : horizontal scroll amount in pixels
6325 * [I] dy : vertical scroll amount in pixels
6332 * If the control is in report mode (LVS_REPORT) the control can
6333 * be scrolled only in line increments. "dy" will be rounded to the
6334 * nearest number of pixels that are a whole line. Ex: if line height
6335 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6336 * is passed the the scroll will be 0. (per MSDN 7/2002)
6338 * For: (per experimentaion with native control and CSpy ListView)
6339 * LVS_ICON dy=1 = 1 pixel (vertical only)
6341 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6343 * LVS_LIST dx=1 = 1 column (horizontal only)
6344 * but will only scroll 1 column per message
6345 * no matter what the value.
6346 * dy must be 0 or FALSE returned.
6347 * LVS_REPORT dx=1 = 1 pixel
6351 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6353 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6355 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6356 dy /= infoPtr->nItemHeight;
6359 if (dy != 0) return FALSE;
6366 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6367 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6374 * Sets the background color.
6377 * [I] infoPtr : valid pointer to the listview structure
6378 * [I] clrBk : background color
6384 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6386 TRACE("(clrBk=%lx)\n", clrBk);
6388 if(infoPtr->clrBk != clrBk) {
6389 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6390 infoPtr->clrBk = clrBk;
6391 if (clrBk == CLR_NONE)
6392 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6394 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6395 LISTVIEW_InvalidateList(infoPtr);
6401 /* LISTVIEW_SetBkImage */
6403 /*** Helper for {Insert,Set}ColumnT *only* */
6404 static void column_fill_hditem(LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn, const LVCOLUMNW *lpColumn, BOOL isW)
6406 if (lpColumn->mask & LVCF_FMT)
6408 /* format member is valid */
6409 lphdi->mask |= HDI_FORMAT;
6411 /* set text alignment (leftmost column must be left-aligned) */
6412 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6413 lphdi->fmt |= HDF_LEFT;
6414 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6415 lphdi->fmt |= HDF_RIGHT;
6416 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6417 lphdi->fmt |= HDF_CENTER;
6419 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6420 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6422 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6424 lphdi->fmt |= HDF_IMAGE;
6425 lphdi->iImage = I_IMAGECALLBACK;
6429 if (lpColumn->mask & LVCF_WIDTH)
6431 lphdi->mask |= HDI_WIDTH;
6432 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6434 /* make it fill the remainder of the controls width */
6438 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6440 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6441 lphdi->cxy += rcHeader.right - rcHeader.left;
6444 /* retrieve the layout of the header */
6445 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6446 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, debugrect(&rcHeader));
6448 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6451 lphdi->cxy = lpColumn->cx;
6454 if (lpColumn->mask & LVCF_TEXT)
6456 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6457 lphdi->fmt |= HDF_STRING;
6458 lphdi->pszText = lpColumn->pszText;
6459 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6462 if (lpColumn->mask & LVCF_IMAGE)
6464 lphdi->mask |= HDI_IMAGE;
6465 lphdi->iImage = lpColumn->iImage;
6468 if (lpColumn->mask & LVCF_ORDER)
6470 lphdi->mask |= HDI_ORDER;
6471 lphdi->iOrder = lpColumn->iOrder;
6478 * Inserts a new column.
6481 * [I] infoPtr : valid pointer to the listview structure
6482 * [I] nColumn : column index
6483 * [I] lpColumn : column information
6484 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6487 * SUCCESS : new column index
6490 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6491 const LVCOLUMNW *lpColumn, BOOL isW)
6493 COLUMN_INFO *lpColumnInfo;
6497 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6499 if (!lpColumn || nColumn < 0) return -1;
6500 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6502 ZeroMemory(&hdi, sizeof(HDITEMW));
6503 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6505 /* insert item in header control */
6506 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6507 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6508 (WPARAM)nColumn, (LPARAM)&hdi);
6509 if (nNewColumn == -1) return -1;
6510 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6512 /* create our own column info */
6513 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6514 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6516 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6517 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6519 /* now we have to actually adjust the data */
6520 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6522 SUBITEM_INFO *lpSubItem;
6526 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6528 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
6529 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
6531 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
6532 if (lpSubItem->iSubItem >= nNewColumn)
6533 lpSubItem->iSubItem++;
6538 /* make space for the new column */
6539 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6544 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6547 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6555 * Sets the attributes of a header item.
6558 * [I] infoPtr : valid pointer to the listview structure
6559 * [I] nColumn : column index
6560 * [I] lpColumn : column attributes
6561 * [I] isW: if TRUE, the lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6567 static BOOL LISTVIEW_SetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6568 const LVCOLUMNW *lpColumn, BOOL isW)
6570 HDITEMW hdi, hdiget;
6573 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6575 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6577 ZeroMemory(&hdi, sizeof(HDITEMW));
6578 if (lpColumn->mask & LVCF_FMT)
6580 hdi.mask |= HDI_FORMAT;
6581 hdiget.mask = HDI_FORMAT;
6582 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6583 hdi.fmt = hdiget.fmt & HDF_STRING;
6585 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6587 /* set header item attributes */
6588 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6589 if (!bResult) return FALSE;
6591 if (lpColumn->mask & LVCF_FMT)
6593 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6594 int oldFmt = lpColumnInfo->fmt;
6596 lpColumnInfo->fmt = lpColumn->fmt;
6597 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
6599 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6600 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
6609 * Sets the column order array
6612 * [I] infoPtr : valid pointer to the listview structure
6613 * [I] iCount : number of elements in column order array
6614 * [I] lpiArray : pointer to column order array
6620 static BOOL LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
6622 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6633 * Sets the width of a column
6636 * [I] infoPtr : valid pointer to the listview structure
6637 * [I] nColumn : column index
6638 * [I] cx : column width
6644 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
6646 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6647 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6651 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
6653 /* set column width only if in report or list mode */
6654 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6656 /* take care of invalid cx values */
6657 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
6658 else if (uView == LVS_LIST && cx < 1) return FALSE;
6660 /* resize all columns if in LVS_LIST mode */
6661 if(uView == LVS_LIST)
6663 infoPtr->nItemWidth = cx;
6664 LISTVIEW_InvalidateList(infoPtr);
6668 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6670 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
6675 lvItem.mask = LVIF_TEXT;
6677 lvItem.iSubItem = nColumn;
6678 lvItem.pszText = szDispText;
6679 lvItem.cchTextMax = DISP_TEXT_SIZE;
6680 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6682 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6683 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6684 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
6686 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
6687 max_cx += infoPtr->iconSize.cx;
6688 max_cx += TRAILING_LABEL_PADDING;
6691 /* autosize based on listview items width */
6692 if(cx == LVSCW_AUTOSIZE)
6694 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
6696 /* if iCol is the last column make it fill the remainder of the controls width */
6697 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
6702 LISTVIEW_GetOrigin(infoPtr, &Origin);
6703 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
6705 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
6709 /* Despite what the MS docs say, if this is not the last
6710 column, then MS resizes the column to the width of the
6711 largest text string in the column, including headers
6712 and items. This is different from LVSCW_AUTOSIZE in that
6713 LVSCW_AUTOSIZE ignores the header string length. */
6716 /* retrieve header text */
6717 hdi.mask = HDI_TEXT;
6718 hdi.cchTextMax = DISP_TEXT_SIZE;
6719 hdi.pszText = szDispText;
6720 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, (LPARAM)&hdi))
6722 HDC hdc = GetDC(infoPtr->hwndSelf);
6723 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
6726 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
6727 cx = size.cx + TRAILING_HEADER_PADDING;
6728 /* FIXME: Take into account the header image, if one is present */
6729 SelectObject(hdc, old_font);
6730 ReleaseDC(infoPtr->hwndSelf, hdc);
6732 cx = max (cx, max_cx);
6736 if (cx < 0) return FALSE;
6738 /* call header to update the column change */
6739 hdi.mask = HDI_WIDTH;
6741 TRACE("hdi.cxy=%d\n", hdi.cxy);
6742 return Header_SetItemW(infoPtr->hwndHeader, nColumn, (LPARAM)&hdi);
6746 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
6749 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(LISTVIEW_INFO *infoPtr)
6752 HBITMAP hbm_im, hbm_mask, hbm_orig;
6754 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
6755 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
6758 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
6759 ILC_COLOR | ILC_MASK, 2, 2);
6760 hdc_wnd = GetDC(infoPtr->hwndSelf);
6761 hdc = CreateCompatibleDC(hdc_wnd);
6762 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
6763 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
6764 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
6766 rc.left = rc.top = 0;
6767 rc.right = GetSystemMetrics(SM_CXSMICON);
6768 rc.bottom = GetSystemMetrics(SM_CYSMICON);
6770 hbm_orig = SelectObject(hdc, hbm_mask);
6771 FillRect(hdc, &rc, hbr_white);
6772 InflateRect(&rc, -3, -3);
6773 FillRect(hdc, &rc, hbr_black);
6775 SelectObject(hdc, hbm_im);
6776 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
6777 SelectObject(hdc, hbm_orig);
6778 ImageList_Add(himl, hbm_im, hbm_mask);
6780 SelectObject(hdc, hbm_im);
6781 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
6782 SelectObject(hdc, hbm_orig);
6783 ImageList_Add(himl, hbm_im, hbm_mask);
6785 DeleteObject(hbm_mask);
6786 DeleteObject(hbm_im);
6794 * Sets the extended listview style.
6797 * [I] infoPtr : valid pointer to the listview structure
6799 * [I] dwStyle : style
6802 * SUCCESS : previous style
6805 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwStyle)
6807 DWORD dwOldStyle = infoPtr->dwLvExStyle;
6811 infoPtr->dwLvExStyle = (dwOldStyle & ~dwMask) | (dwStyle & dwMask);
6813 infoPtr->dwLvExStyle = dwStyle;
6815 if((infoPtr->dwLvExStyle ^ dwOldStyle) & LVS_EX_CHECKBOXES)
6817 HIMAGELIST himl = 0;
6818 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6819 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
6820 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
6828 * Sets the new hot cursor used during hot tracking and hover selection.
6831 * [I] infoPtr : valid pointer to the listview structure
6832 * [I} hCurosr : the new hot cursor handle
6835 * Returns the previous hot cursor
6837 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
6839 HCURSOR oldCursor = infoPtr->hHotCursor;
6841 infoPtr->hHotCursor = hCursor;
6849 * Sets the hot item index.
6852 * [I] infoPtr : valid pointer to the listview structure
6853 * [I] iIndex : index
6856 * SUCCESS : previous hot item index
6857 * FAILURE : -1 (no hot item)
6859 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
6861 INT iOldIndex = infoPtr->nHotItem;
6863 infoPtr->nHotItem = iIndex;
6871 * Sets the amount of time the cursor must hover over an item before it is selected.
6874 * [I] infoPtr : valid pointer to the listview structure
6875 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
6878 * Returns the previous hover time
6880 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
6882 DWORD oldHoverTime = infoPtr->dwHoverTime;
6884 infoPtr->dwHoverTime = dwHoverTime;
6886 return oldHoverTime;
6891 * Sets spacing for icons of LVS_ICON style.
6894 * [I] infoPtr : valid pointer to the listview structure
6895 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
6896 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
6899 * MAKELONG(oldcx, oldcy)
6901 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
6903 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6904 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6906 TRACE("requested=(%d,%d)\n", cx, cy);
6908 /* this is supported only for LVS_ICON style */
6909 if (uView != LVS_ICON) return oldspacing;
6911 /* set to defaults, if instructed to */
6912 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
6913 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
6915 /* if 0 then compute width
6916 * FIXME: Should scan each item and determine max width of
6917 * icon or label, then make that the width */
6919 cx = infoPtr->iconSpacing.cx;
6921 /* if 0 then compute height */
6923 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
6924 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
6927 infoPtr->iconSpacing.cx = cx;
6928 infoPtr->iconSpacing.cy = cy;
6930 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%ld,%ld), ntmH=%d\n",
6931 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
6932 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
6933 infoPtr->ntmHeight);
6935 /* these depend on the iconSpacing */
6936 LISTVIEW_UpdateItemSize(infoPtr);
6941 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
6945 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
6952 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
6953 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
6962 * [I] infoPtr : valid pointer to the listview structure
6963 * [I] nType : image list type
6964 * [I] himl : image list handle
6967 * SUCCESS : old image list
6970 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
6972 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6973 INT oldHeight = infoPtr->nItemHeight;
6974 HIMAGELIST himlOld = 0;
6976 TRACE("(nType=%d, himl=%p\n", nType, himl);
6981 himlOld = infoPtr->himlNormal;
6982 infoPtr->himlNormal = himl;
6983 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
6984 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
6988 himlOld = infoPtr->himlSmall;
6989 infoPtr->himlSmall = himl;
6990 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
6994 himlOld = infoPtr->himlState;
6995 infoPtr->himlState = himl;
6996 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
6997 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
7001 ERR("Unknown icon type=%d\n", nType);
7005 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
7006 if (infoPtr->nItemHeight != oldHeight)
7007 LISTVIEW_UpdateScroll(infoPtr);
7014 * Preallocates memory (does *not* set the actual count of items !)
7017 * [I] infoPtr : valid pointer to the listview structure
7018 * [I] nItems : item count (projected number of items to allocate)
7019 * [I] dwFlags : update flags
7025 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7027 TRACE("(nItems=%d, dwFlags=%lx)\n", nItems, dwFlags);
7029 if (infoPtr->dwStyle & LVS_OWNERDATA)
7031 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7032 INT nOldCount = infoPtr->nItemCount;
7034 if (nItems < nOldCount)
7036 RANGE range = { nItems, nOldCount };
7037 ranges_del(infoPtr->selectionRanges, range);
7038 if (infoPtr->nFocusedItem >= nItems)
7040 infoPtr->nFocusedItem = -1;
7041 SetRectEmpty(&infoPtr->rcFocus);
7045 infoPtr->nItemCount = nItems;
7046 LISTVIEW_UpdateScroll(infoPtr);
7048 /* the flags are valid only in ownerdata report and list modes */
7049 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
7051 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7052 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7054 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7055 LISTVIEW_InvalidateList(infoPtr);
7062 LISTVIEW_GetOrigin(infoPtr, &Origin);
7063 nFrom = min(nOldCount, nItems);
7064 nTo = max(nOldCount, nItems);
7066 if (uView == LVS_REPORT)
7069 rcErase.top = nFrom * infoPtr->nItemHeight;
7070 rcErase.right = infoPtr->nItemWidth;
7071 rcErase.bottom = nTo * infoPtr->nItemHeight;
7072 OffsetRect(&rcErase, Origin.x, Origin.y);
7073 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7074 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7078 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
7080 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
7081 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
7082 rcErase.right = rcErase.left + infoPtr->nItemWidth;
7083 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7084 OffsetRect(&rcErase, Origin.x, Origin.y);
7085 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7086 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7088 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
7090 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
7091 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7092 OffsetRect(&rcErase, Origin.x, Origin.y);
7093 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7094 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7100 /* According to MSDN for non-LVS_OWNERDATA this is just
7101 * a performance issue. The control allocates its internal
7102 * data structures for the number of items specified. It
7103 * cuts down on the number of memory allocations. Therefore
7104 * we will just issue a WARN here
7106 WARN("for non-ownerdata performance option not implemented.\n");
7114 * Sets the position of an item.
7117 * [I] infoPtr : valid pointer to the listview structure
7118 * [I] nItem : item index
7119 * [I] pt : coordinate
7125 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7127 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7130 TRACE("(nItem=%d, &pt=%s\n", nItem, debugpoint(&pt));
7132 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7133 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7135 LISTVIEW_GetOrigin(infoPtr, &Origin);
7137 /* This point value seems to be an undocumented feature.
7138 * The best guess is that it means either at the origin,
7139 * or at true beginning of the list. I will assume the origin. */
7140 if ((pt.x == -1) && (pt.y == -1))
7143 if (uView == LVS_ICON)
7145 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7146 pt.y -= ICON_TOP_PADDING;
7151 infoPtr->bAutoarrange = FALSE;
7153 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7158 * Sets the state of one or many items.
7161 * [I] infoPtr : valid pointer to the listview structure
7162 * [I] nItem : item index
7163 * [I] lpLVItem : item or subitem info
7169 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7171 BOOL bResult = TRUE;
7174 lvItem.iItem = nItem;
7175 lvItem.iSubItem = 0;
7176 lvItem.mask = LVIF_STATE;
7177 lvItem.state = lpLVItem->state;
7178 lvItem.stateMask = lpLVItem->stateMask;
7179 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7183 /* apply to all items */
7184 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7185 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7188 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7191 * Update selection mark
7193 * Investigation on windows 2k showed that selection mark was updated
7194 * whenever a new selection was made, but if the selected item was
7195 * unselected it was not updated.
7197 * we are probably still not 100% accurate, but this at least sets the
7198 * proper selection mark when it is needed
7201 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7202 ((infoPtr->nSelectionMark == -1) || (lvItem.iItem <= infoPtr->nSelectionMark)))
7205 infoPtr->nSelectionMark = -1;
7206 for (i = 0; i < infoPtr->nItemCount; i++)
7208 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7210 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7212 infoPtr->nSelectionMark = i;
7216 else if (ranges_contain(infoPtr->selectionRanges, i))
7218 infoPtr->nSelectionMark = i;
7229 * Sets the text of an item or subitem.
7232 * [I] hwnd : window handle
7233 * [I] nItem : item index
7234 * [I] lpLVItem : item or subitem info
7235 * [I] isW : TRUE if input is Unicode
7241 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7245 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7247 lvItem.iItem = nItem;
7248 lvItem.iSubItem = lpLVItem->iSubItem;
7249 lvItem.mask = LVIF_TEXT;
7250 lvItem.pszText = lpLVItem->pszText;
7251 lvItem.cchTextMax = lpLVItem->cchTextMax;
7253 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7255 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7260 * Set item index that marks the start of a multiple selection.
7263 * [I] infoPtr : valid pointer to the listview structure
7264 * [I] nIndex : index
7267 * Index number or -1 if there is no selection mark.
7269 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7271 INT nOldIndex = infoPtr->nSelectionMark;
7273 TRACE("(nIndex=%d)\n", nIndex);
7275 infoPtr->nSelectionMark = nIndex;
7282 * Sets the text background color.
7285 * [I] infoPtr : valid pointer to the listview structure
7286 * [I] clrTextBk : text background color
7292 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7294 TRACE("(clrTextBk=%lx)\n", clrTextBk);
7296 if (infoPtr->clrTextBk != clrTextBk)
7298 infoPtr->clrTextBk = clrTextBk;
7299 LISTVIEW_InvalidateList(infoPtr);
7307 * Sets the text foreground color.
7310 * [I] infoPtr : valid pointer to the listview structure
7311 * [I] clrText : text color
7317 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7319 TRACE("(clrText=%lx)\n", clrText);
7321 if (infoPtr->clrText != clrText)
7323 infoPtr->clrText = clrText;
7324 LISTVIEW_InvalidateList(infoPtr);
7332 * Determines which listview item is located at the specified position.
7335 * [I] infoPtr : valid pointer to the listview structure
7336 * [I] hwndNewToolTip : handle to new ToolTip
7341 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7343 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7344 infoPtr->hwndToolTip = hwndNewToolTip;
7345 return hwndOldToolTip;
7348 /* LISTVIEW_SetUnicodeFormat */
7349 /* LISTVIEW_SetWorkAreas */
7353 * Callback internally used by LISTVIEW_SortItems()
7356 * [I] first : pointer to first ITEM_INFO to compare
7357 * [I] second : pointer to second ITEM_INFO to compare
7358 * [I] lParam : HWND of control
7361 * if first comes before second : negative
7362 * if first comes after second : positive
7363 * if first and second are equivalent : zero
7365 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7367 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7368 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
7369 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
7371 /* Forward the call to the client defined callback */
7372 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7377 * Sorts the listview items.
7380 * [I] infoPtr : valid pointer to the listview structure
7381 * [I] pfnCompare : application-defined value
7382 * [I] lParamSort : pointer to comparision callback
7388 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
7390 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7393 LPVOID selectionMarkItem;
7397 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7399 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7401 if (!pfnCompare) return FALSE;
7402 if (!infoPtr->hdpaItems) return FALSE;
7404 /* if there are 0 or 1 items, there is no need to sort */
7405 if (infoPtr->nItemCount < 2) return TRUE;
7407 if (infoPtr->nFocusedItem >= 0)
7409 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7410 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7411 if (lpItem) lpItem->state |= LVIS_FOCUSED;
7413 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
7414 /* clear the lpItem->state for non-selected ones */
7415 /* remove the selection ranges */
7417 infoPtr->pfnCompare = pfnCompare;
7418 infoPtr->lParamSort = lParamSort;
7419 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
7421 /* Adjust selections and indices so that they are the way they should
7422 * be after the sort (otherwise, the list items move around, but
7423 * whatever is at the item's previous original position will be
7426 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
7427 for (i=0; i < infoPtr->nItemCount; i++)
7429 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
7430 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7432 if (lpItem->state & LVIS_SELECTED)
7434 item.state = LVIS_SELECTED;
7435 item.stateMask = LVIS_SELECTED;
7436 LISTVIEW_SetItemState(infoPtr, i, &item);
7438 if (lpItem->state & LVIS_FOCUSED)
7440 infoPtr->nFocusedItem = i;
7441 lpItem->state &= ~LVIS_FOCUSED;
7444 if (selectionMarkItem != NULL)
7445 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7446 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7448 /* refresh the display */
7449 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7450 LISTVIEW_InvalidateList(infoPtr);
7457 * Update theme handle after a theme change.
7460 * [I] infoPtr : valid pointer to the listview structure
7464 * FAILURE : something else
7466 static LRESULT LISTVIEW_ThemeChanged(LISTVIEW_INFO *infoPtr)
7468 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
7469 CloseThemeData(theme);
7470 OpenThemeData(infoPtr->hwndSelf, themeClass);
7476 * Updates an items or rearranges the listview control.
7479 * [I] infoPtr : valid pointer to the listview structure
7480 * [I] nItem : item index
7486 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
7488 TRACE("(nItem=%d)\n", nItem);
7490 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7492 /* rearrange with default alignment style */
7493 if (is_autoarrange(infoPtr))
7494 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7496 LISTVIEW_InvalidateItem(infoPtr, nItem);
7504 * Creates the listview control.
7507 * [I] hwnd : window handle
7508 * [I] lpcs : the create parameters
7514 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
7516 LISTVIEW_INFO *infoPtr;
7517 UINT uView = lpcs->style & LVS_TYPEMASK;
7519 BOOL themingActive = IsAppThemed() && IsThemeActive();
7521 TRACE("(lpcs=%p)\n", lpcs);
7523 /* initialize info pointer */
7524 infoPtr = (LISTVIEW_INFO *)Alloc(sizeof(LISTVIEW_INFO));
7525 if (!infoPtr) return -1;
7527 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
7529 infoPtr->hwndSelf = hwnd;
7530 infoPtr->dwStyle = lpcs->style;
7531 /* determine the type of structures to use */
7532 infoPtr->hwndNotify = lpcs->hwndParent;
7533 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
7534 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
7536 /* initialize color information */
7537 infoPtr->clrBk = CLR_NONE;
7538 infoPtr->clrText = comctl32_color.clrWindowText;
7539 infoPtr->clrTextBk = CLR_DEFAULT;
7540 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
7542 /* set default values */
7543 infoPtr->nFocusedItem = -1;
7544 infoPtr->nSelectionMark = -1;
7545 infoPtr->nHotItem = -1;
7546 infoPtr->bRedraw = TRUE;
7547 infoPtr->bNoItemMetrics = TRUE;
7548 infoPtr->bDoChangeNotify = TRUE;
7549 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
7550 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
7551 infoPtr->nEditLabelItem = -1;
7552 infoPtr->dwHoverTime = -1; /* default system hover time */
7553 infoPtr->nMeasureItemHeight = 0;
7555 /* get default font (icon title) */
7556 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
7557 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
7558 infoPtr->hFont = infoPtr->hDefaultFont;
7559 LISTVIEW_SaveTextMetrics(infoPtr);
7562 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL,
7563 WS_CHILD | HDS_HORZ | (DWORD)((LVS_NOSORTHEADER & lpcs->style)?0:HDS_BUTTONS),
7564 0, 0, 0, 0, hwnd, NULL,
7565 lpcs->hInstance, NULL);
7566 if (!infoPtr->hwndHeader) goto fail;
7568 /* set header unicode format */
7569 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)NULL);
7571 /* set header font */
7572 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
7574 /* allocate memory for the data structure */
7575 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
7576 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
7577 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
7578 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
7579 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
7581 /* initialize the icon sizes */
7582 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
7583 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
7585 /* init item size to avoid division by 0 */
7586 LISTVIEW_UpdateItemSize (infoPtr);
7588 if (uView == LVS_REPORT)
7590 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
7592 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
7596 /* set HDS_HIDDEN flag to hide the header bar */
7597 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE,
7598 GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE) | HDS_HIDDEN);
7602 if (themingActive) OpenThemeData(hwnd, themeClass);
7607 DestroyWindow(infoPtr->hwndHeader);
7608 ranges_destroy(infoPtr->selectionRanges);
7609 DPA_Destroy(infoPtr->hdpaItems);
7610 DPA_Destroy(infoPtr->hdpaPosX);
7611 DPA_Destroy(infoPtr->hdpaPosY);
7612 DPA_Destroy(infoPtr->hdpaColumns);
7619 * Destroys the listview control.
7622 * [I] infoPtr : valid pointer to the listview structure
7628 static LRESULT LISTVIEW_Destroy(LISTVIEW_INFO *infoPtr)
7630 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
7631 CloseThemeData(theme);
7637 * Enables the listview control.
7640 * [I] infoPtr : valid pointer to the listview structure
7641 * [I] bEnable : specifies whether to enable or disable the window
7647 static BOOL LISTVIEW_Enable(LISTVIEW_INFO *infoPtr, BOOL bEnable)
7649 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
7650 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
7656 * Erases the background of the listview control.
7659 * [I] infoPtr : valid pointer to the listview structure
7660 * [I] hdc : device context handle
7666 static inline BOOL LISTVIEW_EraseBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc)
7670 TRACE("(hdc=%p)\n", hdc);
7672 if (!GetClipBox(hdc, &rc)) return FALSE;
7674 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
7680 * Helper function for LISTVIEW_[HV]Scroll *only*.
7681 * Performs vertical/horizontal scrolling by a give amount.
7684 * [I] infoPtr : valid pointer to the listview structure
7685 * [I] dx : amount of horizontal scroll
7686 * [I] dy : amount of vertical scroll
7688 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7690 /* now we can scroll the list */
7691 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
7692 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
7693 /* if we have focus, adjust rect */
7694 OffsetRect(&infoPtr->rcFocus, dx, dy);
7695 UpdateWindow(infoPtr->hwndSelf);
7700 * Performs vertical scrolling.
7703 * [I] infoPtr : valid pointer to the listview structure
7704 * [I] nScrollCode : scroll code
7705 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7706 * [I] hScrollWnd : scrollbar control window handle
7712 * SB_LINEUP/SB_LINEDOWN:
7713 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
7714 * for LVS_REPORT is 1 line
7715 * for LVS_LIST cannot occur
7718 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7719 INT nScrollDiff, HWND hScrollWnd)
7721 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7722 INT nOldScrollPos, nNewScrollPos;
7723 SCROLLINFO scrollInfo;
7726 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
7727 debugscrollcode(nScrollCode), nScrollDiff);
7729 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7731 scrollInfo.cbSize = sizeof(SCROLLINFO);
7732 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7734 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
7736 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
7738 nOldScrollPos = scrollInfo.nPos;
7739 switch (nScrollCode)
7745 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
7749 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
7753 nScrollDiff = -scrollInfo.nPage;
7757 nScrollDiff = scrollInfo.nPage;
7760 case SB_THUMBPOSITION:
7762 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7769 /* quit right away if pos isn't changing */
7770 if (nScrollDiff == 0) return 0;
7772 /* calculate new position, and handle overflows */
7773 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7774 if (nScrollDiff > 0) {
7775 if (nNewScrollPos < nOldScrollPos ||
7776 nNewScrollPos > scrollInfo.nMax)
7777 nNewScrollPos = scrollInfo.nMax;
7779 if (nNewScrollPos > nOldScrollPos ||
7780 nNewScrollPos < scrollInfo.nMin)
7781 nNewScrollPos = scrollInfo.nMin;
7784 /* set the new position, and reread in case it changed */
7785 scrollInfo.fMask = SIF_POS;
7786 scrollInfo.nPos = nNewScrollPos;
7787 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
7789 /* carry on only if it really changed */
7790 if (nNewScrollPos == nOldScrollPos) return 0;
7792 /* now adjust to client coordinates */
7793 nScrollDiff = nOldScrollPos - nNewScrollPos;
7794 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
7796 /* and scroll the window */
7797 scroll_list(infoPtr, 0, nScrollDiff);
7804 * Performs horizontal scrolling.
7807 * [I] infoPtr : valid pointer to the listview structure
7808 * [I] nScrollCode : scroll code
7809 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7810 * [I] hScrollWnd : scrollbar control window handle
7816 * SB_LINELEFT/SB_LINERIGHT:
7817 * for LVS_ICON, LVS_SMALLICON 1 pixel
7818 * for LVS_REPORT is 1 pixel
7819 * for LVS_LIST is 1 column --> which is a 1 because the
7820 * scroll is based on columns not pixels
7823 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7824 INT nScrollDiff, HWND hScrollWnd)
7826 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7827 INT nOldScrollPos, nNewScrollPos;
7828 SCROLLINFO scrollInfo;
7830 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
7831 debugscrollcode(nScrollCode), nScrollDiff);
7833 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7835 scrollInfo.cbSize = sizeof(SCROLLINFO);
7836 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7838 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
7840 nOldScrollPos = scrollInfo.nPos;
7842 switch (nScrollCode)
7856 nScrollDiff = -scrollInfo.nPage;
7860 nScrollDiff = scrollInfo.nPage;
7863 case SB_THUMBPOSITION:
7865 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7872 /* quit right away if pos isn't changing */
7873 if (nScrollDiff == 0) return 0;
7875 /* calculate new position, and handle overflows */
7876 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7877 if (nScrollDiff > 0) {
7878 if (nNewScrollPos < nOldScrollPos ||
7879 nNewScrollPos > scrollInfo.nMax)
7880 nNewScrollPos = scrollInfo.nMax;
7882 if (nNewScrollPos > nOldScrollPos ||
7883 nNewScrollPos < scrollInfo.nMin)
7884 nNewScrollPos = scrollInfo.nMin;
7887 /* set the new position, and reread in case it changed */
7888 scrollInfo.fMask = SIF_POS;
7889 scrollInfo.nPos = nNewScrollPos;
7890 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
7892 /* carry on only if it really changed */
7893 if (nNewScrollPos == nOldScrollPos) return 0;
7895 if(uView == LVS_REPORT)
7896 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
7898 /* now adjust to client coordinates */
7899 nScrollDiff = nOldScrollPos - nNewScrollPos;
7900 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
7902 /* and scroll the window */
7903 scroll_list(infoPtr, nScrollDiff, 0);
7908 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
7910 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7911 INT gcWheelDelta = 0;
7912 INT pulScrollLines = 3;
7913 SCROLLINFO scrollInfo;
7915 TRACE("(wheelDelta=%d)\n", wheelDelta);
7917 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
7918 gcWheelDelta -= wheelDelta;
7920 scrollInfo.cbSize = sizeof(SCROLLINFO);
7921 scrollInfo.fMask = SIF_POS;
7928 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
7929 * should be fixed in the future.
7931 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
7932 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
7936 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
7938 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
7939 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
7940 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
7945 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
7956 * [I] infoPtr : valid pointer to the listview structure
7957 * [I] nVirtualKey : virtual key
7958 * [I] lKeyData : key data
7963 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
7965 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7967 NMLVKEYDOWN nmKeyDown;
7969 TRACE("(nVirtualKey=%d, lKeyData=%ld)\n", nVirtualKey, lKeyData);
7971 /* send LVN_KEYDOWN notification */
7972 nmKeyDown.wVKey = nVirtualKey;
7973 nmKeyDown.flags = 0;
7974 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
7976 switch (nVirtualKey)
7979 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
7981 notify(infoPtr, NM_RETURN);
7982 notify(infoPtr, LVN_ITEMACTIVATE);
7987 if (infoPtr->nItemCount > 0)
7992 if (infoPtr->nItemCount > 0)
7993 nItem = infoPtr->nItemCount - 1;
7997 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
8001 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
8005 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
8009 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
8013 if (uView == LVS_REPORT)
8014 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr);
8016 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
8017 * LISTVIEW_GetCountPerRow(infoPtr);
8018 if(nItem < 0) nItem = 0;
8022 if (uView == LVS_REPORT)
8023 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr);
8025 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
8026 * LISTVIEW_GetCountPerRow(infoPtr);
8027 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
8031 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem))
8032 LISTVIEW_KeySelection(infoPtr, nItem);
8042 * [I] infoPtr : valid pointer to the listview structure
8047 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
8051 /* if we did not have the focus, there's nothing to do */
8052 if (!infoPtr->bFocus) return 0;
8054 /* send NM_KILLFOCUS notification */
8055 notify(infoPtr, NM_KILLFOCUS);
8057 /* if we have a focus rectagle, get rid of it */
8058 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
8060 /* set window focus flag */
8061 infoPtr->bFocus = FALSE;
8063 /* invalidate the selected items before reseting focus flag */
8064 LISTVIEW_InvalidateSelectedItems(infoPtr);
8071 * Processes double click messages (left mouse button).
8074 * [I] infoPtr : valid pointer to the listview structure
8075 * [I] wKey : key flag
8076 * [I] x,y : mouse coordinate
8081 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8083 LVHITTESTINFO htInfo;
8085 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8087 /* send NM_RELEASEDCAPTURE notification */
8088 notify(infoPtr, NM_RELEASEDCAPTURE);
8093 /* send NM_DBLCLK notification */
8094 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
8095 notify_click(infoPtr, NM_DBLCLK, &htInfo);
8097 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
8098 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
8105 * Processes mouse down messages (left mouse button).
8108 * [I] infoPtr : valid pointer to the listview structure
8109 * [I] wKey : key flag
8110 * [I] x,y : mouse coordinate
8115 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8117 LVHITTESTINFO lvHitTestInfo;
8118 static BOOL bGroupSelect = TRUE;
8119 POINT pt = { x, y };
8122 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8124 /* send NM_RELEASEDCAPTURE notification */
8125 notify(infoPtr, NM_RELEASEDCAPTURE);
8127 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
8129 /* set left button down flag and record the click position */
8130 infoPtr->bLButtonDown = TRUE;
8131 infoPtr->ptClickPos = pt;
8133 lvHitTestInfo.pt.x = x;
8134 lvHitTestInfo.pt.y = y;
8136 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8137 TRACE("at %s, nItem=%d\n", debugpoint(&pt), nItem);
8138 infoPtr->nEditLabelItem = -1;
8139 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8141 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8143 DWORD state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK) >> 12;
8144 if(state == 1 || state == 2)
8148 lvitem.state = state << 12;
8149 lvitem.stateMask = LVIS_STATEIMAGEMASK;
8150 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
8155 if (infoPtr->dwStyle & LVS_SINGLESEL)
8157 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8158 infoPtr->nEditLabelItem = nItem;
8160 LISTVIEW_SetSelection(infoPtr, nItem);
8164 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8168 LISTVIEW_AddGroupSelection(infoPtr, nItem);
8169 LISTVIEW_SetItemFocus(infoPtr, nItem);
8170 infoPtr->nSelectionMark = nItem;
8176 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8177 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8179 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8180 infoPtr->nSelectionMark = nItem;
8183 else if (wKey & MK_CONTROL)
8187 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8189 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8190 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8191 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8192 infoPtr->nSelectionMark = nItem;
8194 else if (wKey & MK_SHIFT)
8196 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8200 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8201 infoPtr->nEditLabelItem = nItem;
8203 /* set selection (clears other pre-existing selections) */
8204 LISTVIEW_SetSelection(infoPtr, nItem);
8210 /* remove all selections */
8211 LISTVIEW_DeselectAll(infoPtr);
8220 * Processes mouse up messages (left mouse button).
8223 * [I] infoPtr : valid pointer to the listview structure
8224 * [I] wKey : key flag
8225 * [I] x,y : mouse coordinate
8230 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8232 LVHITTESTINFO lvHitTestInfo;
8234 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8236 if (!infoPtr->bLButtonDown) return 0;
8238 lvHitTestInfo.pt.x = x;
8239 lvHitTestInfo.pt.y = y;
8241 /* send NM_CLICK notification */
8242 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8243 notify_click(infoPtr, NM_CLICK, &lvHitTestInfo);
8245 /* set left button flag */
8246 infoPtr->bLButtonDown = FALSE;
8248 /* if we clicked on a selected item, edit the label */
8249 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8250 LISTVIEW_EditLabelT(infoPtr, lvHitTestInfo.iItem, TRUE);
8257 * Destroys the listview control (called after WM_DESTROY).
8260 * [I] infoPtr : valid pointer to the listview structure
8265 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8269 /* delete all items */
8270 LISTVIEW_DeleteAllItems(infoPtr);
8272 /* destroy data structure */
8273 DPA_Destroy(infoPtr->hdpaItems);
8274 DPA_Destroy(infoPtr->hdpaPosX);
8275 DPA_Destroy(infoPtr->hdpaPosY);
8276 DPA_Destroy(infoPtr->hdpaColumns);
8277 ranges_destroy(infoPtr->selectionRanges);
8279 /* destroy image lists */
8280 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8282 if (infoPtr->himlNormal)
8283 ImageList_Destroy(infoPtr->himlNormal);
8284 if (infoPtr->himlSmall)
8285 ImageList_Destroy(infoPtr->himlSmall);
8286 if (infoPtr->himlState)
8287 ImageList_Destroy(infoPtr->himlState);
8290 /* destroy font, bkgnd brush */
8292 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8293 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8295 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
8297 /* free listview info pointer*/
8305 * Handles notifications from header.
8308 * [I] infoPtr : valid pointer to the listview structure
8309 * [I] nCtrlId : control identifier
8310 * [I] lpnmh : notification information
8315 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8317 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8319 TRACE("(lpnmh=%p)\n", lpnmh);
8321 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8323 switch (lpnmh->hdr.code)
8325 case HDN_ITEMCHANGINGW:
8326 case HDN_ITEMCHANGINGA:
8327 return notify_forward_header(infoPtr, lpnmh);
8328 case HDN_ITEMCHANGEDW:
8329 case HDN_ITEMCHANGEDA:
8330 notify_forward_header(infoPtr, lpnmh);
8335 COLUMN_INFO *lpColumnInfo;
8338 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8342 hdi.mask = HDI_WIDTH;
8343 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, (LPARAM)&hdi)) return 0;
8347 cxy = lpnmh->pitem->cxy;
8349 /* determine how much we change since the last know position */
8350 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8351 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8354 lpColumnInfo->rcHeader.right += dx;
8355 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
8356 LISTVIEW_UpdateItemSize(infoPtr);
8357 if (uView == LVS_REPORT && is_redrawing(infoPtr))
8360 RECT rcCol = lpColumnInfo->rcHeader;
8362 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8363 OffsetRect(&rcCol, ptOrigin.x, 0);
8365 rcCol.top = infoPtr->rcList.top;
8366 rcCol.bottom = infoPtr->rcList.bottom;
8368 /* resizing left-aligned columns leaves most of the left side untouched */
8369 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8371 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth + dx;
8372 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
8375 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
8381 case HDN_ITEMCLICKW:
8382 case HDN_ITEMCLICKA:
8384 /* Handle sorting by Header Column */
8387 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
8389 nmlv.iSubItem = lpnmh->iItem;
8390 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
8394 case HDN_DIVIDERDBLCLICKW:
8395 case HDN_DIVIDERDBLCLICKA:
8396 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
8405 * Paint non-client area of control.
8408 * [I] infoPtr : valid pointer to the listview structureof the sender
8409 * [I] region : update region
8412 * TRUE - frame was painted
8413 * FALSE - call default window proc
8415 static BOOL LISTVIEW_NCPaint(LISTVIEW_INFO *infoPtr, HRGN region)
8417 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
8418 BOOL themingActive = IsAppThemed() && IsThemeActive();
8419 BOOL doTheming = themingActive && (theme != NULL);
8423 int cxEdge = GetSystemMetrics (SM_CXEDGE),
8424 cyEdge = GetSystemMetrics (SM_CYEDGE);
8426 if (!doTheming) return FALSE;
8428 GetWindowRect(infoPtr->hwndSelf, &r);
8430 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
8431 r.right - cxEdge, r.bottom - cyEdge);
8432 if (region != (HRGN)1)
8433 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
8434 OffsetRect(&r, -r.left, -r.top);
8436 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
8437 OffsetRect(&r, -r.left, -r.top);
8439 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
8440 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
8441 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
8442 ReleaseDC(infoPtr->hwndSelf, dc);
8444 /* Call default proc to get the scrollbars etc. painted */
8445 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
8452 * Determines the type of structure to use.
8455 * [I] infoPtr : valid pointer to the listview structureof the sender
8456 * [I] hwndFrom : listview window handle
8457 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
8462 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
8464 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
8466 if (nCommand != NF_REQUERY) return 0;
8468 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
8475 * Paints/Repaints the listview control.
8478 * [I] infoPtr : valid pointer to the listview structure
8479 * [I] hdc : device context handle
8484 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
8486 TRACE("(hdc=%p)\n", hdc);
8488 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
8490 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8492 infoPtr->bNoItemMetrics = FALSE;
8493 LISTVIEW_UpdateItemSize(infoPtr);
8494 if (uView == LVS_ICON || uView == LVS_SMALLICON)
8495 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8496 LISTVIEW_UpdateScroll(infoPtr);
8499 LISTVIEW_Refresh(infoPtr, hdc);
8504 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
8506 if (ps.fErase) LISTVIEW_FillBkgnd(infoPtr, hdc, &ps.rcPaint);
8507 LISTVIEW_Refresh(infoPtr, hdc);
8508 EndPaint(infoPtr->hwndSelf, &ps);
8517 * Paints/Repaints the listview control.
8520 * [I] infoPtr : valid pointer to the listview structure
8521 * [I] hdc : device context handle
8522 * [I] options : drawing options
8527 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
8529 FIXME("Partial Stub: (hdc=%p options=0x%08lx)\n", hdc, options);
8531 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
8534 if (options & PRF_ERASEBKGND)
8535 LISTVIEW_EraseBkgnd(infoPtr, hdc);
8537 if (options & PRF_CLIENT)
8538 LISTVIEW_Paint(infoPtr, hdc);
8546 * Processes double click messages (right mouse button).
8549 * [I] infoPtr : valid pointer to the listview structure
8550 * [I] wKey : key flag
8551 * [I] x,y : mouse coordinate
8556 static LRESULT LISTVIEW_RButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8558 LVHITTESTINFO lvHitTestInfo;
8560 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
8562 /* send NM_RELEASEDCAPTURE notification */
8563 notify(infoPtr, NM_RELEASEDCAPTURE);
8565 /* send NM_RDBLCLK notification */
8566 lvHitTestInfo.pt.x = x;
8567 lvHitTestInfo.pt.y = y;
8568 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8569 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
8576 * Processes mouse down messages (right mouse button).
8579 * [I] infoPtr : valid pointer to the listview structure
8580 * [I] wKey : key flag
8581 * [I] x,y : mouse coordinate
8586 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8588 LVHITTESTINFO lvHitTestInfo;
8591 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
8593 /* send NM_RELEASEDCAPTURE notification */
8594 notify(infoPtr, NM_RELEASEDCAPTURE);
8596 /* make sure the listview control window has the focus */
8597 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
8599 /* set right button down flag */
8600 infoPtr->bRButtonDown = TRUE;
8602 /* determine the index of the selected item */
8603 lvHitTestInfo.pt.x = x;
8604 lvHitTestInfo.pt.y = y;
8605 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8607 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8609 LISTVIEW_SetItemFocus(infoPtr, nItem);
8610 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
8611 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8612 LISTVIEW_SetSelection(infoPtr, nItem);
8616 LISTVIEW_DeselectAll(infoPtr);
8624 * Processes mouse up messages (right mouse button).
8627 * [I] infoPtr : valid pointer to the listview structure
8628 * [I] wKey : key flag
8629 * [I] x,y : mouse coordinate
8634 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8636 LVHITTESTINFO lvHitTestInfo;
8639 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
8641 if (!infoPtr->bRButtonDown) return 0;
8643 /* set button flag */
8644 infoPtr->bRButtonDown = FALSE;
8646 /* Send NM_RClICK notification */
8647 lvHitTestInfo.pt.x = x;
8648 lvHitTestInfo.pt.y = y;
8649 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8650 notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo);
8652 /* Change to screen coordinate for WM_CONTEXTMENU */
8653 pt = lvHitTestInfo.pt;
8654 ClientToScreen(infoPtr->hwndSelf, &pt);
8656 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
8657 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
8658 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
8669 * [I] infoPtr : valid pointer to the listview structure
8670 * [I] hwnd : window handle of window containing the cursor
8671 * [I] nHittest : hit-test code
8672 * [I] wMouseMsg : ideintifier of the mouse message
8675 * TRUE if cursor is set
8678 static BOOL LISTVIEW_SetCursor(LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
8680 LVHITTESTINFO lvHitTestInfo;
8682 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
8684 if(!infoPtr->hHotCursor) return FALSE;
8686 GetCursorPos(&lvHitTestInfo.pt);
8687 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
8689 SetCursor(infoPtr->hHotCursor);
8699 * [I] infoPtr : valid pointer to the listview structure
8700 * [I] hwndLoseFocus : handle of previously focused window
8705 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
8707 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
8709 /* if we have the focus already, there's nothing to do */
8710 if (infoPtr->bFocus) return 0;
8712 /* send NM_SETFOCUS notification */
8713 notify(infoPtr, NM_SETFOCUS);
8715 /* set window focus flag */
8716 infoPtr->bFocus = TRUE;
8718 /* put the focus rect back on */
8719 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
8721 /* redraw all visible selected items */
8722 LISTVIEW_InvalidateSelectedItems(infoPtr);
8732 * [I] infoPtr : valid pointer to the listview structure
8733 * [I] fRedraw : font handle
8734 * [I] fRedraw : redraw flag
8739 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
8741 HFONT oldFont = infoPtr->hFont;
8743 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
8745 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
8746 if (infoPtr->hFont == oldFont) return 0;
8748 LISTVIEW_SaveTextMetrics(infoPtr);
8750 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
8751 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
8753 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
8760 * Message handling for WM_SETREDRAW.
8761 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
8764 * [I] infoPtr : valid pointer to the listview structure
8765 * [I] bRedraw: state of redraw flag
8768 * DefWinProc return value
8770 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
8772 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
8774 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
8775 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
8777 infoPtr->bRedraw = bRedraw;
8779 if(!bRedraw) return 0;
8781 if (is_autoarrange(infoPtr))
8782 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8783 LISTVIEW_UpdateScroll(infoPtr);
8785 /* despite what the WM_SETREDRAW docs says, apps expect us
8786 * to invalidate the listview here... stupid! */
8787 LISTVIEW_InvalidateList(infoPtr);
8794 * Resizes the listview control. This function processes WM_SIZE
8795 * messages. At this time, the width and height are not used.
8798 * [I] infoPtr : valid pointer to the listview structure
8799 * [I] Width : new width
8800 * [I] Height : new height
8805 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
8807 RECT rcOld = infoPtr->rcList;
8809 TRACE("(width=%d, height=%d)\n", Width, Height);
8811 LISTVIEW_UpdateSize(infoPtr);
8812 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
8814 /* do not bother with display related stuff if we're not redrawing */
8815 if (!is_redrawing(infoPtr)) return 0;
8817 if (is_autoarrange(infoPtr))
8818 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8820 LISTVIEW_UpdateScroll(infoPtr);
8822 /* refresh all only for lists whose height changed significantly */
8823 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
8824 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
8825 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
8826 LISTVIEW_InvalidateList(infoPtr);
8833 * Sets the size information.
8836 * [I] infoPtr : valid pointer to the listview structure
8841 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
8843 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8845 TRACE("uView=%d, rcList(old)=%s\n", uView, debugrect(&infoPtr->rcList));
8847 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
8849 if (uView == LVS_LIST)
8851 /* Apparently the "LIST" style is supposed to have the same
8852 * number of items in a column even if there is no scroll bar.
8853 * Since if a scroll bar already exists then the bottom is already
8854 * reduced, only reduce if the scroll bar does not currently exist.
8855 * The "2" is there to mimic the native control. I think it may be
8856 * related to either padding or edges. (GLA 7/2002)
8858 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
8859 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
8860 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
8862 else if (uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER))
8867 hl.prc = &infoPtr->rcList;
8869 Header_Layout(infoPtr->hwndHeader, &hl);
8871 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8873 infoPtr->rcList.top = max(wp.cy, 0);
8876 TRACE(" rcList=%s\n", debugrect(&infoPtr->rcList));
8881 * Processes WM_STYLECHANGED messages.
8884 * [I] infoPtr : valid pointer to the listview structure
8885 * [I] wStyleType : window style type (normal or extended)
8886 * [I] lpss : window style information
8891 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
8892 const STYLESTRUCT *lpss)
8894 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
8895 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
8897 TRACE("(styletype=%x, styleOld=0x%08lx, styleNew=0x%08lx)\n",
8898 wStyleType, lpss->styleOld, lpss->styleNew);
8900 if (wStyleType != GWL_STYLE) return 0;
8902 /* FIXME: if LVS_NOSORTHEADER changed, update header */
8903 /* what if LVS_OWNERDATA changed? */
8904 /* or LVS_SINGLESEL */
8905 /* or LVS_SORT{AS,DES}CENDING */
8907 infoPtr->dwStyle = lpss->styleNew;
8909 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
8910 ((lpss->styleNew & WS_HSCROLL) == 0))
8911 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
8913 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
8914 ((lpss->styleNew & WS_VSCROLL) == 0))
8915 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
8917 if (uNewView != uOldView)
8919 SIZE oldIconSize = infoPtr->iconSize;
8922 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8923 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8925 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
8926 SetRectEmpty(&infoPtr->rcFocus);
8928 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
8929 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
8931 if (uNewView == LVS_ICON)
8933 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
8935 TRACE("icon old size=(%ld,%ld), new size=(%ld,%ld)\n",
8936 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
8937 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
8940 else if (uNewView == LVS_REPORT)
8945 hl.prc = &infoPtr->rcList;
8947 Header_Layout(infoPtr->hwndHeader, &hl);
8948 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8951 LISTVIEW_UpdateItemSize(infoPtr);
8954 if (uNewView == LVS_REPORT)
8955 ShowWindow(infoPtr->hwndHeader, (lpss->styleNew & LVS_NOCOLUMNHEADER) ? SW_HIDE : SW_SHOWNORMAL);
8957 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
8958 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
8959 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8961 /* update the size of the client area */
8962 LISTVIEW_UpdateSize(infoPtr);
8964 /* add scrollbars if needed */
8965 LISTVIEW_UpdateScroll(infoPtr);
8967 /* invalidate client area + erase background */
8968 LISTVIEW_InvalidateList(infoPtr);
8975 * Window procedure of the listview control.
8978 static LRESULT WINAPI
8979 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8981 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8983 TRACE("(uMsg=%x wParam=%x lParam=%lx)\n", uMsg, wParam, lParam);
8985 if (!infoPtr && (uMsg != WM_CREATE))
8986 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8990 case LVM_APPROXIMATEVIEWRECT:
8991 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
8992 LOWORD(lParam), HIWORD(lParam));
8994 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
8996 /* case LVM_CANCELEDITLABEL: */
8998 case LVM_CREATEDRAGIMAGE:
8999 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
9001 case LVM_DELETEALLITEMS:
9002 return LISTVIEW_DeleteAllItems(infoPtr);
9004 case LVM_DELETECOLUMN:
9005 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
9007 case LVM_DELETEITEM:
9008 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
9010 case LVM_EDITLABELW:
9011 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
9013 case LVM_EDITLABELA:
9014 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
9016 /* case LVM_ENABLEGROUPVIEW: */
9018 case LVM_ENSUREVISIBLE:
9019 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
9022 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
9025 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
9027 case LVM_GETBKCOLOR:
9028 return infoPtr->clrBk;
9030 /* case LVM_GETBKIMAGE: */
9032 case LVM_GETCALLBACKMASK:
9033 return infoPtr->uCallbackMask;
9035 case LVM_GETCOLUMNA:
9036 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9038 case LVM_GETCOLUMNW:
9039 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9041 case LVM_GETCOLUMNORDERARRAY:
9042 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9044 case LVM_GETCOLUMNWIDTH:
9045 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
9047 case LVM_GETCOUNTPERPAGE:
9048 return LISTVIEW_GetCountPerPage(infoPtr);
9050 case LVM_GETEDITCONTROL:
9051 return (LRESULT)infoPtr->hwndEdit;
9053 case LVM_GETEXTENDEDLISTVIEWSTYLE:
9054 return infoPtr->dwLvExStyle;
9056 /* case LVM_GETGROUPINFO: */
9058 /* case LVM_GETGROUPMETRICS: */
9061 return (LRESULT)infoPtr->hwndHeader;
9063 case LVM_GETHOTCURSOR:
9064 return (LRESULT)infoPtr->hHotCursor;
9066 case LVM_GETHOTITEM:
9067 return infoPtr->nHotItem;
9069 case LVM_GETHOVERTIME:
9070 return infoPtr->dwHoverTime;
9072 case LVM_GETIMAGELIST:
9073 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
9075 /* case LVM_GETINSERTMARK: */
9077 /* case LVM_GETINSERTMARKCOLOR: */
9079 /* case LVM_GETINSERTMARKRECT: */
9081 case LVM_GETISEARCHSTRINGA:
9082 case LVM_GETISEARCHSTRINGW:
9083 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
9087 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
9090 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
9092 case LVM_GETITEMCOUNT:
9093 return infoPtr->nItemCount;
9095 case LVM_GETITEMPOSITION:
9096 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
9098 case LVM_GETITEMRECT:
9099 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
9101 case LVM_GETITEMSPACING:
9102 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
9104 case LVM_GETITEMSTATE:
9105 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
9107 case LVM_GETITEMTEXTA:
9108 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9110 case LVM_GETITEMTEXTW:
9111 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9113 case LVM_GETNEXTITEM:
9114 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
9116 case LVM_GETNUMBEROFWORKAREAS:
9117 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
9121 if (!lParam) return FALSE;
9122 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
9125 /* case LVM_GETOUTLINECOLOR: */
9127 /* case LVM_GETSELECTEDCOLUMN: */
9129 case LVM_GETSELECTEDCOUNT:
9130 return LISTVIEW_GetSelectedCount(infoPtr);
9132 case LVM_GETSELECTIONMARK:
9133 return infoPtr->nSelectionMark;
9135 case LVM_GETSTRINGWIDTHA:
9136 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
9138 case LVM_GETSTRINGWIDTHW:
9139 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
9141 case LVM_GETSUBITEMRECT:
9142 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
9144 case LVM_GETTEXTBKCOLOR:
9145 return infoPtr->clrTextBk;
9147 case LVM_GETTEXTCOLOR:
9148 return infoPtr->clrText;
9150 /* case LVM_GETTILEINFO: */
9152 /* case LVM_GETTILEVIEWINFO: */
9154 case LVM_GETTOOLTIPS:
9155 if( !infoPtr->hwndToolTip )
9156 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
9157 return (LRESULT)infoPtr->hwndToolTip;
9159 case LVM_GETTOPINDEX:
9160 return LISTVIEW_GetTopIndex(infoPtr);
9162 /*case LVM_GETUNICODEFORMAT:
9163 FIXME("LVM_GETUNICODEFORMAT: unimplemented\n");
9166 /* case LVM_GETVIEW: */
9168 case LVM_GETVIEWRECT:
9169 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
9171 case LVM_GETWORKAREAS:
9172 FIXME("LVM_GETWORKAREAS: unimplemented\n");
9175 /* case LVM_HASGROUP: */
9178 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
9180 case LVM_INSERTCOLUMNA:
9181 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9183 case LVM_INSERTCOLUMNW:
9184 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9186 /* case LVM_INSERTGROUP: */
9188 /* case LVM_INSERTGROUPSORTED: */
9190 case LVM_INSERTITEMA:
9191 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9193 case LVM_INSERTITEMW:
9194 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9196 /* case LVM_INSERTMARKHITTEST: */
9198 /* case LVM_ISGROUPVIEWENABLED: */
9200 /* case LVM_MAPIDTOINDEX: */
9202 /* case LVM_MAPINDEXTOID: */
9204 /* case LVM_MOVEGROUP: */
9206 /* case LVM_MOVEITEMTOGROUP: */
9208 case LVM_REDRAWITEMS:
9209 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
9211 /* case LVM_REMOVEALLGROUPS: */
9213 /* case LVM_REMOVEGROUP: */
9216 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
9218 case LVM_SETBKCOLOR:
9219 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
9221 /* case LVM_SETBKIMAGE: */
9223 case LVM_SETCALLBACKMASK:
9224 infoPtr->uCallbackMask = (UINT)wParam;
9227 case LVM_SETCOLUMNA:
9228 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9230 case LVM_SETCOLUMNW:
9231 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9233 case LVM_SETCOLUMNORDERARRAY:
9234 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9236 case LVM_SETCOLUMNWIDTH:
9237 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
9239 case LVM_SETEXTENDEDLISTVIEWSTYLE:
9240 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
9242 /* case LVM_SETGROUPINFO: */
9244 /* case LVM_SETGROUPMETRICS: */
9246 case LVM_SETHOTCURSOR:
9247 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
9249 case LVM_SETHOTITEM:
9250 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
9252 case LVM_SETHOVERTIME:
9253 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
9255 case LVM_SETICONSPACING:
9256 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9258 case LVM_SETIMAGELIST:
9259 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
9261 /* case LVM_SETINFOTIP: */
9263 /* case LVM_SETINSERTMARK: */
9265 /* case LVM_SETINSERTMARKCOLOR: */
9268 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9271 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9273 case LVM_SETITEMCOUNT:
9274 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
9276 case LVM_SETITEMPOSITION:
9279 pt.x = (short)LOWORD(lParam);
9280 pt.y = (short)HIWORD(lParam);
9281 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
9284 case LVM_SETITEMPOSITION32:
9285 if (lParam == 0) return FALSE;
9286 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
9288 case LVM_SETITEMSTATE:
9289 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
9291 case LVM_SETITEMTEXTA:
9292 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9294 case LVM_SETITEMTEXTW:
9295 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9297 /* case LVM_SETOUTLINECOLOR: */
9299 /* case LVM_SETSELECTEDCOLUMN: */
9301 case LVM_SETSELECTIONMARK:
9302 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
9304 case LVM_SETTEXTBKCOLOR:
9305 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
9307 case LVM_SETTEXTCOLOR:
9308 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
9310 /* case LVM_SETTILEINFO: */
9312 /* case LVM_SETTILEVIEWINFO: */
9314 /* case LVM_SETTILEWIDTH: */
9316 case LVM_SETTOOLTIPS:
9317 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
9319 /* case LVM_SETUNICODEFORMAT: */
9321 /* case LVM_SETVIEW: */
9323 /* case LVM_SETWORKAREAS: */
9325 /* case LVM_SORTGROUPS: */
9328 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
9330 /* LVM_SORTITEMSEX: */
9332 case LVM_SUBITEMHITTEST:
9333 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
9336 return LISTVIEW_Update(infoPtr, (INT)wParam);
9339 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
9342 return LISTVIEW_Command(infoPtr, wParam, lParam);
9345 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
9348 return LISTVIEW_Destroy(infoPtr);
9351 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
9354 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
9357 return DLGC_WANTCHARS | DLGC_WANTARROWS;
9360 return (LRESULT)infoPtr->hFont;
9363 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9366 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
9369 return LISTVIEW_KillFocus(infoPtr);
9371 case WM_LBUTTONDBLCLK:
9372 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9374 case WM_LBUTTONDOWN:
9375 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9378 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9381 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9384 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9387 return LISTVIEW_NCDestroy(infoPtr);
9390 if (LISTVIEW_NCPaint(infoPtr, (HRGN)wParam))
9395 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
9396 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
9399 case WM_NOTIFYFORMAT:
9400 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
9402 case WM_PRINTCLIENT:
9403 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
9406 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
9408 case WM_RBUTTONDBLCLK:
9409 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9411 case WM_RBUTTONDOWN:
9412 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9415 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9418 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
9423 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
9426 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
9429 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
9432 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9434 case WM_STYLECHANGED:
9435 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
9437 case WM_SYSCOLORCHANGE:
9438 COMCTL32_RefreshSysColors();
9441 /* case WM_TIMER: */
9442 case WM_THEMECHANGED:
9443 return LISTVIEW_ThemeChanged(infoPtr);
9446 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9449 if (wParam & (MK_SHIFT | MK_CONTROL))
9450 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9451 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
9453 case WM_WINDOWPOSCHANGED:
9454 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
9456 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9457 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
9458 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
9460 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
9462 MEASUREITEMSTRUCT mis;
9463 mis.CtlType = ODT_LISTVIEW;
9464 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
9468 mis.itemHeight= infoPtr->nItemHeight;
9469 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
9470 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
9471 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
9474 LISTVIEW_UpdateSize(infoPtr);
9475 LISTVIEW_UpdateScroll(infoPtr);
9477 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9479 /* case WM_WININICHANGE: */
9482 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
9483 ERR("unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam);
9486 /* call default window procedure */
9487 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9494 * Registers the window class.
9502 void LISTVIEW_Register(void)
9506 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
9507 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
9508 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
9509 wndClass.cbClsExtra = 0;
9510 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
9511 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
9512 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
9513 wndClass.lpszClassName = WC_LISTVIEWW;
9514 RegisterClassW(&wndClass);
9519 * Unregisters the window class.
9527 void LISTVIEW_Unregister(void)
9529 UnregisterClassW(WC_LISTVIEWW, NULL);
9534 * Handle any WM_COMMAND messages
9537 * [I] infoPtr : valid pointer to the listview structure
9538 * [I] wParam : the first message parameter
9539 * [I] lParam : the second message parameter
9544 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
9546 switch (HIWORD(wParam))
9551 * Adjust the edit window size
9554 HDC hdc = GetDC(infoPtr->hwndEdit);
9555 HFONT hFont, hOldFont = 0;
9560 if (!infoPtr->hwndEdit || !hdc) return 0;
9561 len = GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
9562 GetWindowRect(infoPtr->hwndEdit, &rect);
9564 /* Select font to get the right dimension of the string */
9565 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
9568 hOldFont = SelectObject(hdc, hFont);
9571 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
9573 TEXTMETRICW textMetric;
9575 /* Add Extra spacing for the next character */
9576 GetTextMetricsW(hdc, &textMetric);
9577 sz.cx += (textMetric.tmMaxCharWidth * 2);
9585 rect.bottom - rect.top,
9586 SWP_DRAWFRAME|SWP_NOMOVE);
9589 SelectObject(hdc, hOldFont);
9591 ReleaseDC(infoPtr->hwndEdit, hdc);
9597 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
9606 * Subclassed edit control windproc function
9609 * [I] hwnd : the edit window handle
9610 * [I] uMsg : the message that is to be processed
9611 * [I] wParam : first message parameter
9612 * [I] lParam : second message parameter
9613 * [I] isW : TRUE if input is Unicode
9618 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
9620 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
9621 BOOL cancel = FALSE;
9623 TRACE("(hwnd=%p, uMsg=%x, wParam=%x, lParam=%lx, isW=%d)\n",
9624 hwnd, uMsg, wParam, lParam, isW);
9629 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
9636 WNDPROC editProc = infoPtr->EditWndProc;
9637 infoPtr->EditWndProc = 0;
9638 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
9639 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
9643 if (VK_ESCAPE == (INT)wParam)
9648 else if (VK_RETURN == (INT)wParam)
9652 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
9656 if (infoPtr->hwndEdit)
9658 LPWSTR buffer = NULL;
9660 infoPtr->hwndEdit = 0;
9663 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
9667 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
9669 if (isW) GetWindowTextW(hwnd, buffer, len+1);
9670 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
9674 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
9676 if (buffer) Free(buffer);
9680 SendMessageW(hwnd, WM_CLOSE, 0, 0);
9686 * Subclassed edit control Unicode windproc function
9689 * [I] hwnd : the edit window handle
9690 * [I] uMsg : the message that is to be processed
9691 * [I] wParam : first message parameter
9692 * [I] lParam : second message parameter
9696 LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9698 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
9703 * Subclassed edit control ANSI windproc function
9706 * [I] hwnd : the edit window handle
9707 * [I] uMsg : the message that is to be processed
9708 * [I] wParam : first message parameter
9709 * [I] lParam : second message parameter
9713 LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9715 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
9720 * Creates a subclassed edit cotrol
9723 * [I] infoPtr : valid pointer to the listview structure
9724 * [I] text : initial text for the edit
9725 * [I] style : the window style
9726 * [I] isW : TRUE if input is Unicode
9730 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
9731 INT x, INT y, INT width, INT height, BOOL isW)
9733 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
9738 TEXTMETRICW textMetric;
9739 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
9741 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
9743 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
9744 hdc = GetDC(infoPtr->hwndSelf);
9746 /* Select the font to get appropriate metric dimensions */
9747 if(infoPtr->hFont != 0)
9748 hOldFont = SelectObject(hdc, infoPtr->hFont);
9750 /*Get String Length in pixels */
9751 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
9753 /*Add Extra spacing for the next character */
9754 GetTextMetricsW(hdc, &textMetric);
9755 sz.cx += (textMetric.tmMaxCharWidth * 2);
9757 if(infoPtr->hFont != 0)
9758 SelectObject(hdc, hOldFont);
9760 ReleaseDC(infoPtr->hwndSelf, hdc);
9762 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
9764 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
9766 if (!hedit) return 0;
9768 infoPtr->EditWndProc = (WNDPROC)
9769 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
9770 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
9772 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);