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 Oct. 21, 2002, by Dimitrie O. Paun.
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.
36 * -- Hot item handling, mouse hovering
37 * -- Workareas support
42 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
43 * -- Support CustonDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs.
44 * -- in LISTVIEW_AddGroupSelection, we would send LVN_ODSTATECHANGED
45 * -- LVA_SNAPTOGRID not implemented
46 * -- LISTVIEW_ApproximateViewRect partially implemented
47 * -- LISTVIEW_[GS]etColumnOrderArray stubs
48 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
49 * -- LISTVIEW_SetIconSpacing is incomplete
50 * -- LISTVIEW_SortItems is broken
51 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
54 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
55 * linear in the number of items in the list, and this is
56 * unacceptable for large lists.
57 * -- in sorted mode, LISTVIEW_InsertItemT sorts the array,
58 * instead of inserting in the right spot
59 * -- we should keep an ordered array of coordinates in iconic mode
60 * this would allow to frame items (iterator_frameditems),
61 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
69 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
76 * -- LVS_NOSCROLL (see Q137520)
77 * -- LVS_SORTASCENDING, LVS_SORTDESCENDING
80 * -- LVS_EX_BORDERSELECT
83 * -- LVS_EX_HEADERDRAGDROP
86 * -- LVS_EX_MULTIWORKAREAS
87 * -- LVS_EX_ONECLICKACTIVATE
89 * -- LVS_EX_SIMPLESELECT
90 * -- LVS_EX_TRACKSELECT
91 * -- LVS_EX_TWOCLICKACTIVATE
92 * -- LVS_EX_UNDERLINECOLD
93 * -- LVS_EX_UNDERLINEHOT
96 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
101 * -- LVN_ODSTATECHANGED
106 * -- LVM_CANCELEDITLABEL
107 * -- LVM_ENABLEGROUPVIEW
108 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
109 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
110 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
111 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
112 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
113 * -- LVM_GETINSERTMARKRECT
114 * -- LVM_GETNUMBEROFWORKAREAS
115 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
116 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
117 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
118 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
119 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
120 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
121 * -- LVM_GETVIEW, LVM_SETVIEW
122 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
123 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
124 * -- LVM_INSERTGROUPSORTED
125 * -- LVM_INSERTMARKHITTEST
126 * -- LVM_ISGROUPVIEWENABLED
127 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
129 * -- LVM_MOVEITEMTOGROUP
131 * -- LVM_SETTILEWIDTH
135 * Known differences in message stream from native control (not known if
136 * these differences cause problems):
137 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
138 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
139 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
140 * processing for "USEDOUBLECLICKTIME".
144 #include "wine/port.h"
159 #include "commctrl.h"
160 #include "comctl32.h"
162 #include "wine/debug.h"
163 #include "wine/unicode.h"
165 WINE_DEFAULT_DEBUG_CHANNEL(listview);
167 /* make sure you set this to 0 for production use! */
168 #define DEBUG_RANGES 1
170 typedef struct tagCOLUMN_INFO
172 RECT rcHeader; /* tracks the header's rectangle */
173 int fmt; /* same as LVCOLUMN.fmt */
176 typedef struct tagITEMHDR
180 } ITEMHDR, *LPITEMHDR;
182 typedef struct tagSUBITEM_INFO
188 typedef struct tagITEM_INFO
196 typedef struct tagRANGE
202 typedef struct tagRANGES
207 typedef struct tagITERATOR
216 typedef struct tagLISTVIEW_INFO
223 COLORREF clrTextBkDefault;
224 HIMAGELIST himlNormal;
225 HIMAGELIST himlSmall;
226 HIMAGELIST himlState;
229 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
232 RANGES selectionRanges;
237 RECT rcList; /* This rectangle is really the window
238 * client rectangle possibly reduced by the
239 * horizontal scroll bar and/or header - see
240 * LISTVIEW_UpdateSize. This rectangle offset
241 * by the LISTVIEW_GetOrigin value is in
242 * client coordinates */
251 INT ntmHeight; /* Some cached metrics of the font used */
252 INT ntmAveCharWidth; /* by the listview to draw items */
253 BOOL bRedraw; /* Turns on/off repaints & invalidations */
254 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
256 BOOL bDoChangeNotify; /* send change notification messages? */
259 DWORD dwStyle; /* the cached window GWL_STYLE */
260 DWORD dwLvExStyle; /* extended listview style */
261 INT nItemCount; /* the number of items in the list */
262 HDPA hdpaItems; /* array ITEM_INFO pointers */
263 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
264 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
265 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
266 POINT currIconPos; /* this is the position next icon will be placed */
267 PFNLVCOMPARE pfnCompare;
275 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
277 DWORD lastKeyPressTimestamp;
279 INT nSearchParamLength;
280 WCHAR szSearchParam[ MAX_PATH ];
287 /* How many we debug buffer to allocate */
288 #define DEBUG_BUFFERS 20
289 /* The size of a single debug bbuffer */
290 #define DEBUG_BUFFER_SIZE 256
292 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
293 #define SB_INTERNAL -1
295 /* maximum size of a label */
296 #define DISP_TEXT_SIZE 512
298 /* padding for items in list and small icon display modes */
299 #define WIDTH_PADDING 12
301 /* padding for items in list, report and small icon display modes */
302 #define HEIGHT_PADDING 1
304 /* offset of items in report display mode */
305 #define REPORT_MARGINX 2
307 /* padding for icon in large icon display mode
308 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
309 * that HITTEST will see.
310 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
311 * ICON_TOP_PADDING - sum of the two above.
312 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
313 * LABEL_HOR_PADDING - between text and sides of box
314 * LABEL_VERT_PADDING - between bottom of text and end of box
316 * ICON_LR_PADDING - additional width above icon size.
317 * ICON_LR_HALF - half of the above value
319 #define ICON_TOP_PADDING_NOTHITABLE 2
320 #define ICON_TOP_PADDING_HITABLE 2
321 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
322 #define ICON_BOTTOM_PADDING 4
323 #define LABEL_HOR_PADDING 5
324 #define LABEL_VERT_PADDING 7
325 #define ICON_LR_PADDING 16
326 #define ICON_LR_HALF (ICON_LR_PADDING/2)
328 /* default label width for items in list and small icon display modes */
329 #define DEFAULT_LABEL_WIDTH 40
331 /* default column width for items in list display mode */
332 #define DEFAULT_COLUMN_WIDTH 128
334 /* Size of "line" scroll for V & H scrolls */
335 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
337 /* Padding betwen image and label */
338 #define IMAGE_PADDING 2
340 /* Padding behind the label */
341 #define TRAILING_LABEL_PADDING 12
342 #define TRAILING_HEADER_PADDING 11
344 /* Border for the icon caption */
345 #define CAPTION_BORDER 2
347 /* Standard DrawText flags */
348 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
349 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
350 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
352 /* The time in milliseconds to reset the search in the list */
353 #define KEY_DELAY 450
355 /* Dump the LISTVIEW_INFO structure to the debug channel */
356 #define LISTVIEW_DUMP(iP) do { \
357 TRACE("hwndSelf=%p, clrBk=0x%06lx, clrText=0x%06lx, clrTextBk=0x%06lx, ItemHeight=%d, ItemWidth=%d, Style=0x%08lx\n", \
358 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
359 iP->nItemHeight, iP->nItemWidth, infoPtr->dwStyle); \
360 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08lx, Focus=%d\n", \
361 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
362 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
363 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%ld, icSz.cy=%ld, icSp.cx=%ld, icSp.cy=%ld, notifyFmt=%d\n", \
364 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
365 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
366 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, debugrect(&iP->rcList)); \
370 * forward declarations
372 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *, LPLVITEMW, BOOL);
373 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *, INT, LPRECT);
374 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *, INT, LPPOINT);
375 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *, INT, LPPOINT);
376 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *, INT, LPRECT);
377 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *, INT);
378 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *, LPPOINT);
379 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *, LPRECT);
380 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
381 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, const LVITEMW *, BOOL);
382 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *);
383 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
384 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
385 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
386 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
387 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *, PFNLVCOMPARE, LPARAM);
388 static INT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *, LPCWSTR, BOOL);
389 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT);
390 static UINT LISTVIEW_GetItemState(LISTVIEW_INFO *, INT, UINT);
391 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
392 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
393 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
394 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *);
395 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
396 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
397 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
399 /******** Text handling functions *************************************/
401 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
402 * text string. The string may be ANSI or Unicode, in which case
403 * the boolean isW tells us the type of the string.
405 * The name of the function tell what type of strings it expects:
406 * W: Unicode, T: ANSI/Unicode - function of isW
409 static inline BOOL is_textW(LPCWSTR text)
411 return text != NULL && text != LPSTR_TEXTCALLBACKW;
414 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
416 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
417 return is_textW(text);
420 static inline int textlenT(LPCWSTR text, BOOL isW)
422 return !is_textT(text, isW) ? 0 :
423 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
426 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
429 if (isSrcW) lstrcpynW(dest, src, max);
430 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
432 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
433 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
436 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
438 LPWSTR wstr = (LPWSTR)text;
440 if (!isW && is_textT(text, isW))
442 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
443 wstr = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
444 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
446 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
450 static inline void textfreeT(LPWSTR wstr, BOOL isW)
452 if (!isW && is_textT(wstr, isW)) HeapFree(GetProcessHeap(), 0, wstr);
456 * dest is a pointer to a Unicode string
457 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
459 static BOOL textsetptrT(LPWSTR *dest, LPWSTR src, BOOL isW)
463 if (src == LPSTR_TEXTCALLBACKW)
465 if (is_textW(*dest)) Free(*dest);
466 *dest = LPSTR_TEXTCALLBACKW;
470 LPWSTR pszText = textdupTtoW(src, isW);
471 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
472 bResult = Str_SetPtrW(dest, pszText);
473 textfreeT(pszText, isW);
479 * compares a Unicode to a Unicode/ANSI text string
481 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
483 if (!aw) return bt ? -1 : 0;
484 if (!bt) return aw ? 1 : 0;
485 if (aw == LPSTR_TEXTCALLBACKW)
486 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
487 if (bt != LPSTR_TEXTCALLBACKW)
489 LPWSTR bw = textdupTtoW(bt, isW);
490 int r = bw ? lstrcmpW(aw, bw) : 1;
498 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
502 n = min(min(n, strlenW(s1)), strlenW(s2));
503 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
504 return res ? res - sizeof(WCHAR) : res;
507 /******** Debugging functions *****************************************/
509 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
511 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
512 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
515 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
517 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
518 n = min(textlenT(text, isW), n);
519 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
522 static char* debug_getbuf()
524 static int index = 0;
525 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
526 return buffers[index++ % DEBUG_BUFFERS];
529 static inline const char* debugrange(const RANGE *lprng)
533 char* buf = debug_getbuf();
534 snprintf(buf, DEBUG_BUFFER_SIZE, "[%d, %d)", lprng->lower, lprng->upper);
536 } else return "(null)";
539 static inline const char* debugpoint(const POINT *lppt)
543 char* buf = debug_getbuf();
544 snprintf(buf, DEBUG_BUFFER_SIZE, "(%ld, %ld)", lppt->x, lppt->y);
546 } else return "(null)";
549 static inline const char* debugrect(const RECT *rect)
553 char* buf = debug_getbuf();
554 snprintf(buf, DEBUG_BUFFER_SIZE, "[(%ld, %ld);(%ld, %ld)]",
555 rect->left, rect->top, rect->right, rect->bottom);
557 } else return "(null)";
560 static const char * debugscrollinfo(const SCROLLINFO *pScrollInfo)
562 char* buf = debug_getbuf(), *text = buf;
563 int len, size = DEBUG_BUFFER_SIZE;
565 if (pScrollInfo == NULL) return "(null)";
566 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
567 if (len == -1) goto end; buf += len; size -= len;
568 if (pScrollInfo->fMask & SIF_RANGE)
569 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
571 if (len == -1) goto end; buf += len; size -= len;
572 if (pScrollInfo->fMask & SIF_PAGE)
573 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
575 if (len == -1) goto end; buf += len; size -= len;
576 if (pScrollInfo->fMask & SIF_POS)
577 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
579 if (len == -1) goto end; buf += len; size -= len;
580 if (pScrollInfo->fMask & SIF_TRACKPOS)
581 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
583 if (len == -1) goto end; buf += len; size -= len;
586 buf = text + strlen(text);
588 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
592 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
596 char* buf = debug_getbuf();
597 snprintf(buf, DEBUG_BUFFER_SIZE, "iItem=%d, iSubItem=%d, uNewState=0x%x,"
598 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld\n",
599 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
600 plvnm->uChanged, debugpoint(&plvnm->ptAction), plvnm->lParam);
602 } else return "(null)";
605 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
607 char* buf = debug_getbuf(), *text = buf;
608 int len, size = DEBUG_BUFFER_SIZE;
610 if (lpLVItem == NULL) return "(null)";
611 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
612 if (len == -1) goto end; buf += len; size -= len;
613 if (lpLVItem->mask & LVIF_STATE)
614 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
616 if (len == -1) goto end; buf += len; size -= len;
617 if (lpLVItem->mask & LVIF_TEXT)
618 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
620 if (len == -1) goto end; buf += len; size -= len;
621 if (lpLVItem->mask & LVIF_IMAGE)
622 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
624 if (len == -1) goto end; buf += len; size -= len;
625 if (lpLVItem->mask & LVIF_PARAM)
626 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
628 if (len == -1) goto end; buf += len; size -= len;
629 if (lpLVItem->mask & LVIF_INDENT)
630 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
632 if (len == -1) goto end; buf += len; size -= len;
635 buf = text + strlen(text);
637 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
641 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
643 char* buf = debug_getbuf(), *text = buf;
644 int len, size = DEBUG_BUFFER_SIZE;
646 if (lpColumn == NULL) return "(null)";
647 len = snprintf(buf, size, "{");
648 if (len == -1) goto end; buf += len; size -= len;
649 if (lpColumn->mask & LVCF_SUBITEM)
650 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
652 if (len == -1) goto end; buf += len; size -= len;
653 if (lpColumn->mask & LVCF_FMT)
654 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
656 if (len == -1) goto end; buf += len; size -= len;
657 if (lpColumn->mask & LVCF_WIDTH)
658 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
660 if (len == -1) goto end; buf += len; size -= len;
661 if (lpColumn->mask & LVCF_TEXT)
662 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
664 if (len == -1) goto end; buf += len; size -= len;
665 if (lpColumn->mask & LVCF_IMAGE)
666 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
668 if (len == -1) goto end; buf += len; size -= len;
669 if (lpColumn->mask & LVCF_ORDER)
670 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
672 if (len == -1) goto end; buf += len; size -= len;
675 buf = text + strlen(text);
677 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
681 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
685 char* buf = debug_getbuf();
686 snprintf(buf, DEBUG_BUFFER_SIZE, "{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
687 debugpoint(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
689 } else return "(null)";
692 /* Return the corresponding text for a given scroll value */
693 static inline LPCSTR debugscrollcode(int nScrollCode)
697 case SB_LINELEFT: return "SB_LINELEFT";
698 case SB_LINERIGHT: return "SB_LINERIGHT";
699 case SB_PAGELEFT: return "SB_PAGELEFT";
700 case SB_PAGERIGHT: return "SB_PAGERIGHT";
701 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
702 case SB_THUMBTRACK: return "SB_THUMBTRACK";
703 case SB_ENDSCROLL: return "SB_ENDSCROLL";
704 case SB_INTERNAL: return "SB_INTERNAL";
705 default: return "unknown";
710 /******** Notification functions i************************************/
712 static LRESULT notify_hdr(LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
716 TRACE("(code=%d)\n", code);
718 pnmh->hwndFrom = infoPtr->hwndSelf;
719 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
721 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
722 (WPARAM)pnmh->idFrom, (LPARAM)pnmh);
724 TRACE(" <= %ld\n", result);
729 static inline LRESULT notify(LISTVIEW_INFO *infoPtr, INT code)
732 return notify_hdr(infoPtr, code, &nmh);
735 static inline void notify_itemactivate(LISTVIEW_INFO *infoPtr, LVHITTESTINFO *htInfo)
746 item.mask = LVIF_PARAM|LVIF_STATE;
747 item.iItem = htInfo->iItem;
749 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
750 nmia.lParam = item.lParam;
751 nmia.uOldState = item.state;
752 nmia.uNewState = item.state | LVIS_ACTIVATING;
753 nmia.uChanged = LVIF_STATE;
756 nmia.iItem = htInfo->iItem;
757 nmia.iSubItem = htInfo->iSubItem;
758 nmia.ptAction = htInfo->pt;
760 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
761 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
762 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
764 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
767 static inline LRESULT notify_listview(LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
769 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
770 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
773 static LRESULT notify_click(LISTVIEW_INFO *infoPtr, INT code, LVHITTESTINFO *lvht)
778 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
779 ZeroMemory(&nmlv, sizeof(nmlv));
780 nmlv.iItem = lvht->iItem;
781 nmlv.iSubItem = lvht->iSubItem;
782 nmlv.ptAction = lvht->pt;
783 item.mask = LVIF_PARAM;
784 item.iItem = lvht->iItem;
786 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
787 return notify_listview(infoPtr, code, &nmlv);
790 static void notify_deleteitem(LISTVIEW_INFO *infoPtr, INT nItem)
795 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
797 item.mask = LVIF_PARAM;
800 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
801 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
804 static int get_ansi_notification(INT unicodeNotificationCode)
806 switch (unicodeNotificationCode)
808 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
809 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
810 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
811 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
812 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
813 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
815 ERR("unknown notification %x\n", unicodeNotificationCode);
821 Send notification. depends on dispinfoW having same
822 structure as dispinfoA.
823 infoPtr : listview struct
824 notificationCode : *Unicode* notification code
825 pdi : dispinfo structure (can be unicode or ansi)
826 isW : TRUE if dispinfo is Unicode
828 static BOOL notify_dispinfoT(LISTVIEW_INFO *infoPtr, INT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
830 BOOL bResult = FALSE;
831 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
832 INT cchTempBufMax = 0, savCchTextMax = 0, realNotifCode;
833 LPWSTR pszTempBuf = NULL, savPszText = NULL;
835 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
837 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
838 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
841 if (convertToAnsi || convertToUnicode)
843 if (notificationCode != LVN_GETDISPINFOW)
845 cchTempBufMax = convertToUnicode ?
846 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
847 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
851 cchTempBufMax = pdi->item.cchTextMax;
852 *pdi->item.pszText = 0; /* make sure we don't process garbage */
855 pszTempBuf = HeapAlloc(GetProcessHeap(), 0,
856 (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
857 if (!pszTempBuf) return FALSE;
859 if (convertToUnicode)
860 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
861 pszTempBuf, cchTempBufMax);
863 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
864 cchTempBufMax, NULL, NULL);
866 savCchTextMax = pdi->item.cchTextMax;
867 savPszText = pdi->item.pszText;
868 pdi->item.pszText = pszTempBuf;
869 pdi->item.cchTextMax = cchTempBufMax;
872 if (infoPtr->notifyFormat == NFR_ANSI)
873 realNotifCode = get_ansi_notification(notificationCode);
875 realNotifCode = notificationCode;
876 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
877 bResult = notify_hdr(infoPtr, realNotifCode, &pdi->hdr);
879 if (convertToUnicode || convertToAnsi)
881 if (convertToUnicode) /* note : pointer can be changed by app ! */
882 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
883 savCchTextMax, NULL, NULL);
885 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
886 savPszText, savCchTextMax);
887 pdi->item.pszText = savPszText; /* restores our buffer */
888 pdi->item.cchTextMax = savCchTextMax;
889 HeapFree(GetProcessHeap(), 0, pszTempBuf);
894 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, LISTVIEW_INFO *infoPtr, HDC hdc,
895 const RECT *rcBounds, const LVITEMW *lplvItem)
897 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
898 lpnmlvcd->nmcd.hdc = hdc;
899 lpnmlvcd->nmcd.rc = *rcBounds;
900 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
901 lpnmlvcd->clrText = infoPtr->clrText;
902 if (!lplvItem) return;
903 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
904 lpnmlvcd->iSubItem = lplvItem->iSubItem;
905 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
906 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
907 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
908 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
911 static inline DWORD notify_customdraw (LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
913 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
916 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
917 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
918 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
919 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
920 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
921 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
925 static void prepaint_setup (LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd)
927 /* apprently, for selected items, we have to override the returned values */
928 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
932 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
933 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
935 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
937 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
938 lpnmlvcd->clrText = comctl32_color.clrBtnText;
942 /* Set the text attributes */
943 if (lpnmlvcd->clrTextBk != CLR_NONE)
945 SetBkMode(hdc, OPAQUE);
946 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
947 SetBkColor(hdc, infoPtr->clrTextBkDefault);
949 SetBkColor(hdc,lpnmlvcd->clrTextBk);
952 SetBkMode(hdc, TRANSPARENT);
953 SetTextColor(hdc, lpnmlvcd->clrText);
956 static inline DWORD notify_postpaint (LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
958 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
961 /******** Item iterator functions **********************************/
963 static RANGES ranges_create(int count);
964 static void ranges_destroy(RANGES ranges);
965 static BOOL ranges_add(RANGES ranges, RANGE range);
966 static BOOL ranges_del(RANGES ranges, RANGE range);
967 static void ranges_dump(RANGES ranges);
969 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
971 RANGE range = { nItem, nItem + 1 };
973 return ranges_add(ranges, range);
976 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
978 RANGE range = { nItem, nItem + 1 };
980 return ranges_del(ranges, range);
984 * ITERATOR DOCUMENTATION
986 * The iterator functions allow for easy, and convenient iteration
987 * over items of iterest in the list. Typically, you create a
988 * iterator, use it, and destroy it, as such:
991 * iterator_xxxitems(&i, ...);
992 * while (iterator_{prev,next}(&i)
994 * //code which uses i.nItem
996 * iterator_destroy(&i);
998 * where xxx is either: framed, or visible.
999 * Note that it is important that the code destroys the iterator
1000 * after it's done with it, as the creation of the iterator may
1001 * allocate memory, which thus needs to be freed.
1003 * You can iterate both forwards, and backwards through the list,
1004 * by using iterator_next or iterator_prev respectively.
1006 * Lower numbered items are draw on top of higher number items in
1007 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1008 * items may overlap). So, to test items, you should use
1010 * which lists the items top to bottom (in Z-order).
1011 * For drawing items, you should use
1013 * which lists the items bottom to top (in Z-order).
1014 * If you keep iterating over the items after the end-of-items
1015 * marker (-1) is returned, the iterator will start from the
1016 * beginning. Typically, you don't need to test for -1,
1017 * because iterator_{next,prev} will return TRUE if more items
1018 * are to be iterated over, or FALSE otherwise.
1020 * Note: the iterator is defined to be bidirectional. That is,
1021 * any number of prev followed by any number of next, or
1022 * five versa, should leave the iterator at the same item:
1023 * prev * n, next * n = next * n, prev * n
1025 * The iterator has a notion of a out-of-order, special item,
1026 * which sits at the start of the list. This is used in
1027 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1028 * which needs to be first, as it may overlap other items.
1030 * The code is a bit messy because we have:
1031 * - a special item to deal with
1032 * - simple range, or composite range
1034 * If you find bugs, or want to add features, please make sure you
1035 * always check/modify *both* iterator_prev, and iterator_next.
1039 * This function iterates through the items in increasing order,
1040 * but prefixed by the special item, then -1. That is:
1041 * special, 1, 2, 3, ..., n, -1.
1042 * Each item is listed only once.
1044 static inline BOOL iterator_next(ITERATOR* i)
1048 i->nItem = i->nSpecial;
1049 if (i->nItem != -1) return TRUE;
1051 if (i->nItem == i->nSpecial)
1053 if (i->ranges) i->index = 0;
1059 if (i->nItem == i->nSpecial) i->nItem++;
1060 if (i->nItem < i->range.upper) return TRUE;
1065 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1066 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1069 else if (i->nItem >= i->range.upper) goto end;
1071 i->nItem = i->range.lower;
1072 if (i->nItem >= 0) goto testitem;
1079 * This function iterates through the items in decreasing order,
1080 * followed by the special item, then -1. That is:
1081 * n, n-1, ..., 3, 2, 1, special, -1.
1082 * Each item is listed only once.
1084 static inline BOOL iterator_prev(ITERATOR* i)
1091 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1094 if (i->nItem == i->nSpecial)
1102 if (i->nItem == i->nSpecial) i->nItem--;
1103 if (i->nItem >= i->range.lower) return TRUE;
1109 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1112 else if (!start && i->nItem < i->range.lower) goto end;
1114 i->nItem = i->range.upper;
1115 if (i->nItem > 0) goto testitem;
1117 return (i->nItem = i->nSpecial) != -1;
1120 static RANGE iterator_range(ITERATOR* i)
1124 if (!i->ranges) return i->range;
1126 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1128 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1129 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1131 else range.lower = range.upper = 0;
1137 * Releases resources associated with this ierator.
1139 static inline void iterator_destroy(ITERATOR* i)
1141 ranges_destroy(i->ranges);
1145 * Create an empty iterator.
1147 static inline BOOL iterator_empty(ITERATOR* i)
1149 ZeroMemory(i, sizeof(*i));
1150 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1155 * Create an iterator over a range.
1157 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1165 * Create an iterator over a bunch of ranges.
1166 * Please note that the iterator will take ownership of the ranges,
1167 * and will free them upon destruction.
1169 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1177 * Creates an iterator over the items which intersect lprc.
1179 static BOOL iterator_frameditems(ITERATOR* i, LISTVIEW_INFO* infoPtr, const RECT *lprc)
1181 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1182 RECT frame = *lprc, rcItem, rcTemp;
1185 /* in case we fail, we want to return an empty iterator */
1186 if (!iterator_empty(i)) return FALSE;
1188 LISTVIEW_GetOrigin(infoPtr, &Origin);
1190 TRACE("(lprc=%s)\n", debugrect(lprc));
1191 OffsetRect(&frame, -Origin.x, -Origin.y);
1193 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1197 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1199 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1200 if (IntersectRect(&rcTemp, &rcItem, lprc))
1201 i->nSpecial = infoPtr->nFocusedItem;
1203 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1204 /* to do better here, we need to have PosX, and PosY sorted */
1205 TRACE("building icon ranges:\n");
1206 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1208 rcItem.left = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1209 rcItem.top = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1210 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1211 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1212 if (IntersectRect(&rcTemp, &rcItem, &frame))
1213 ranges_additem(i->ranges, nItem);
1217 else if (uView == LVS_REPORT)
1221 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1222 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1224 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1225 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1226 if (range.upper <= range.lower) return TRUE;
1227 if (!iterator_rangeitems(i, range)) return FALSE;
1228 TRACE(" report=%s\n", debugrange(&i->range));
1232 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1233 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1234 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1235 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1236 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1237 INT lower = nFirstCol * nPerCol + nFirstRow;
1241 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1242 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1244 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1246 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1247 TRACE("building list ranges:\n");
1248 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1250 item_range.lower = nCol * nPerCol + nFirstRow;
1251 if(item_range.lower >= infoPtr->nItemCount) break;
1252 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1253 TRACE(" list=%s\n", debugrange(&item_range));
1254 ranges_add(i->ranges, item_range);
1262 * Creates an iterator over the items which intersect the visible region of hdc.
1264 static BOOL iterator_visibleitems(ITERATOR *i, LISTVIEW_INFO *infoPtr, HDC hdc)
1266 POINT Origin, Position;
1267 RECT rcItem, rcClip;
1270 rgntype = GetClipBox(hdc, &rcClip);
1271 if (rgntype == NULLREGION) return iterator_empty(i);
1272 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1273 if (rgntype == SIMPLEREGION) return TRUE;
1275 /* first deal with the special item */
1276 if (i->nSpecial != -1)
1278 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1279 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1282 /* if we can't deal with the region, we'll just go with the simple range */
1283 LISTVIEW_GetOrigin(infoPtr, &Origin);
1284 TRACE("building visible range:\n");
1285 if (!i->ranges && i->range.lower < i->range.upper)
1287 if (!(i->ranges = ranges_create(50))) return TRUE;
1288 if (!ranges_add(i->ranges, i->range))
1290 ranges_destroy(i->ranges);
1296 /* now delete the invisible items from the list */
1297 while(iterator_next(i))
1299 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1300 rcItem.left = Position.x + Origin.x;
1301 rcItem.top = Position.y + Origin.y;
1302 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1303 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1304 if (!RectVisible(hdc, &rcItem))
1305 ranges_delitem(i->ranges, i->nItem);
1307 /* the iterator should restart on the next iterator_next */
1313 /******** Misc helper functions ************************************/
1315 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1316 WPARAM wParam, LPARAM lParam, BOOL isW)
1318 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1319 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1322 static inline BOOL is_autoarrange(LISTVIEW_INFO *infoPtr)
1324 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1326 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1327 (uView == LVS_ICON || uView == LVS_SMALLICON);
1330 /******** Internal API functions ************************************/
1332 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(LISTVIEW_INFO *infoPtr, INT nSubItem)
1334 static COLUMN_INFO mainItem;
1336 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1337 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1338 return (COLUMN_INFO *)DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1341 static inline void LISTVIEW_GetHeaderRect(LISTVIEW_INFO *infoPtr, INT nSubItem, RECT *lprc)
1343 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1346 static inline BOOL LISTVIEW_GetItemW(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1348 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1351 /* Listview invalidation functions: use _only_ these functions to invalidate */
1353 static inline BOOL is_redrawing(LISTVIEW_INFO *infoPtr)
1355 return infoPtr->bRedraw;
1358 static inline void LISTVIEW_InvalidateRect(LISTVIEW_INFO *infoPtr, const RECT* rect)
1360 if(!is_redrawing(infoPtr)) return;
1361 TRACE(" invalidating rect=%s\n", debugrect(rect));
1362 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1365 static inline void LISTVIEW_InvalidateItem(LISTVIEW_INFO *infoPtr, INT nItem)
1369 if(!is_redrawing(infoPtr)) return;
1370 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1371 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1374 static inline void LISTVIEW_InvalidateSubItem(LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1376 POINT Origin, Position;
1379 if(!is_redrawing(infoPtr)) return;
1380 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1381 LISTVIEW_GetOrigin(infoPtr, &Origin);
1382 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1383 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1385 rcBox.bottom = infoPtr->nItemHeight;
1386 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1387 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1390 static inline void LISTVIEW_InvalidateList(LISTVIEW_INFO *infoPtr)
1392 LISTVIEW_InvalidateRect(infoPtr, NULL);
1395 static inline void LISTVIEW_InvalidateColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
1399 if(!is_redrawing(infoPtr)) return;
1400 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1401 rcCol.top = infoPtr->rcList.top;
1402 rcCol.bottom = infoPtr->rcList.bottom;
1403 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1408 * Retrieves the number of items that can fit vertically in the client area.
1411 * [I] infoPtr : valid pointer to the listview structure
1414 * Number of items per row.
1416 static inline INT LISTVIEW_GetCountPerRow(LISTVIEW_INFO *infoPtr)
1418 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1420 return max(nListWidth/infoPtr->nItemWidth, 1);
1425 * Retrieves the number of items that can fit horizontally in the client
1429 * [I] infoPtr : valid pointer to the listview structure
1432 * Number of items per column.
1434 static inline INT LISTVIEW_GetCountPerColumn(LISTVIEW_INFO *infoPtr)
1436 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1438 return max(nListHeight / infoPtr->nItemHeight, 1);
1442 /*************************************************************************
1443 * LISTVIEW_ProcessLetterKeys
1445 * Processes keyboard messages generated by pressing the letter keys
1447 * What this does is perform a case insensitive search from the
1448 * current position with the following quirks:
1449 * - If two chars or more are pressed in quick succession we search
1450 * for the corresponding string (e.g. 'abc').
1451 * - If there is a delay we wipe away the current search string and
1452 * restart with just that char.
1453 * - If the user keeps pressing the same character, whether slowly or
1454 * fast, so that the search string is entirely composed of this
1455 * character ('aaaaa' for instance), then we search for first item
1456 * that starting with that character.
1457 * - If the user types the above character in quick succession, then
1458 * we must also search for the corresponding string ('aaaaa'), and
1459 * go to that string if there is a match.
1462 * [I] hwnd : handle to the window
1463 * [I] charCode : the character code, the actual character
1464 * [I] keyData : key data
1472 * - The current implementation has a list of characters it will
1473 * accept and it ignores averything else. In particular it will
1474 * ignore accentuated characters which seems to match what
1475 * Windows does. But I'm not sure it makes sense to follow
1477 * - We don't sound a beep when the search fails.
1481 * TREEVIEW_ProcessLetterKeys
1483 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1488 WCHAR buffer[MAX_PATH];
1489 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1491 /* simple parameter checking */
1492 if (!charCode || !keyData) return 0;
1494 /* only allow the valid WM_CHARs through */
1495 if (!isalnum(charCode) &&
1496 charCode != '.' && charCode != '`' && charCode != '!' &&
1497 charCode != '@' && charCode != '#' && charCode != '$' &&
1498 charCode != '%' && charCode != '^' && charCode != '&' &&
1499 charCode != '*' && charCode != '(' && charCode != ')' &&
1500 charCode != '-' && charCode != '_' && charCode != '+' &&
1501 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1502 charCode != '}' && charCode != '[' && charCode != '{' &&
1503 charCode != '/' && charCode != '?' && charCode != '>' &&
1504 charCode != '<' && charCode != ',' && charCode != '~')
1507 /* if there's one item or less, there is no where to go */
1508 if (infoPtr->nItemCount <= 1) return 0;
1510 /* update the search parameters */
1511 infoPtr->lastKeyPressTimestamp = GetTickCount();
1512 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1513 if (infoPtr->nSearchParamLength < MAX_PATH)
1514 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1515 if (infoPtr->charCode != charCode)
1516 infoPtr->charCode = charCode = 0;
1518 infoPtr->charCode=charCode;
1519 infoPtr->szSearchParam[0]=charCode;
1520 infoPtr->nSearchParamLength=1;
1521 /* Redundant with the 1 char string */
1525 /* and search from the current position */
1527 if (infoPtr->nFocusedItem >= 0) {
1528 endidx=infoPtr->nFocusedItem;
1530 /* if looking for single character match,
1531 * then we must always move forward
1533 if (infoPtr->nSearchParamLength == 1)
1536 endidx=infoPtr->nItemCount;
1540 if (idx == infoPtr->nItemCount) {
1541 if (endidx == infoPtr->nItemCount || endidx == 0)
1547 item.mask = LVIF_TEXT;
1550 item.pszText = buffer;
1551 item.cchTextMax = MAX_PATH;
1552 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1554 /* check for a match */
1555 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1558 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1559 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1560 /* This would work but we must keep looking for a longer match */
1564 } while (idx != endidx);
1567 LISTVIEW_KeySelection(infoPtr, nItem);
1572 /*************************************************************************
1573 * LISTVIEW_UpdateHeaderSize [Internal]
1575 * Function to resize the header control
1578 * [I] hwnd : handle to a window
1579 * [I] nNewScrollPos : scroll pos to set
1584 static void LISTVIEW_UpdateHeaderSize(LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1589 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1591 GetWindowRect(infoPtr->hwndHeader, &winRect);
1592 point[0].x = winRect.left;
1593 point[0].y = winRect.top;
1594 point[1].x = winRect.right;
1595 point[1].y = winRect.bottom;
1597 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1598 point[0].x = -nNewScrollPos;
1599 point[1].x += nNewScrollPos;
1601 SetWindowPos(infoPtr->hwndHeader,0,
1602 point[0].x,point[0].y,point[1].x,point[1].y,
1603 SWP_NOZORDER | SWP_NOACTIVATE);
1608 * Update the scrollbars. This functions should be called whenever
1609 * the content, size or view changes.
1612 * [I] infoPtr : valid pointer to the listview structure
1617 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *infoPtr)
1619 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1620 SCROLLINFO horzInfo, vertInfo;
1622 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1624 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1625 horzInfo.cbSize = sizeof(SCROLLINFO);
1626 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1628 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1629 if (uView == LVS_LIST)
1631 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1632 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1634 /* scroll by at least one column per page */
1635 if(horzInfo.nPage < infoPtr->nItemWidth)
1636 horzInfo.nPage = infoPtr->nItemWidth;
1638 horzInfo.nPage /= infoPtr->nItemWidth;
1640 else if (uView == LVS_REPORT)
1642 horzInfo.nMax = infoPtr->nItemWidth;
1644 else /* LVS_ICON, or LVS_SMALLICON */
1648 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1651 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1652 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1653 SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1654 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1656 /* Setting the horizontal scroll can change the listview size
1657 * (and potentially everything else) so we need to recompute
1658 * everything again for the vertical scroll
1661 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1662 vertInfo.cbSize = sizeof(SCROLLINFO);
1663 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1665 if (uView == LVS_REPORT)
1667 vertInfo.nMax = infoPtr->nItemCount;
1669 /* scroll by at least one page */
1670 if(vertInfo.nPage < infoPtr->nItemHeight)
1671 vertInfo.nPage = infoPtr->nItemHeight;
1673 vertInfo.nPage /= infoPtr->nItemHeight;
1675 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1679 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1682 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1683 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1684 SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1685 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1687 /* Update the Header Control */
1688 if (uView == LVS_REPORT)
1690 horzInfo.fMask = SIF_POS;
1691 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1692 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1699 * Shows/hides the focus rectangle.
1702 * [I] infoPtr : valid pointer to the listview structure
1703 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1708 static void LISTVIEW_ShowFocusRect(LISTVIEW_INFO *infoPtr, BOOL fShow)
1710 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1713 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1715 if (infoPtr->nFocusedItem < 0) return;
1717 /* we need some gymnastics in ICON mode to handle large items */
1718 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1722 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1723 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1725 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1730 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1732 /* for some reason, owner draw should work only in report mode */
1733 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1738 item.iItem = infoPtr->nFocusedItem;
1740 item.mask = LVIF_PARAM;
1741 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1743 ZeroMemory(&dis, sizeof(dis));
1744 dis.CtlType = ODT_LISTVIEW;
1745 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1746 dis.itemID = item.iItem;
1747 dis.itemAction = ODA_FOCUS;
1748 if (fShow) dis.itemState |= ODS_FOCUS;
1749 dis.hwndItem = infoPtr->hwndSelf;
1751 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1752 dis.itemData = item.lParam;
1754 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1758 DrawFocusRect(hdc, &infoPtr->rcFocus);
1761 ReleaseDC(infoPtr->hwndSelf, hdc);
1765 * Invalidates all visible selected items.
1767 static void LISTVIEW_InvalidateSelectedItems(LISTVIEW_INFO *infoPtr)
1771 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1772 while(iterator_next(&i))
1774 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1775 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1777 iterator_destroy(&i);
1782 * DESCRIPTION: [INTERNAL]
1783 * Computes an item's (left,top) corner, relative to rcView.
1784 * That is, the position has NOT been made relative to the Origin.
1785 * This is deliberate, to avoid computing the Origin over, and
1786 * over again, when this function is call in a loop. Instead,
1787 * one ca factor the computation of the Origin before the loop,
1788 * and offset the value retured by this function, on every iteration.
1791 * [I] infoPtr : valid pointer to the listview structure
1792 * [I] nItem : item number
1793 * [O] lpptOrig : item top, left corner
1798 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1800 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1802 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1804 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1806 lpptPosition->x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1807 lpptPosition->y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1809 else if (uView == LVS_LIST)
1811 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1812 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1813 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1815 else /* LVS_REPORT */
1817 lpptPosition->x = 0;
1818 lpptPosition->y = nItem * infoPtr->nItemHeight;
1823 * DESCRIPTION: [INTERNAL]
1824 * Compute the rectangles of an item. This is to localize all
1825 * the computations in one place. If you are not interested in some
1826 * of these values, simply pass in a NULL -- the fucntion is smart
1827 * enough to compute only what's necessary. The function computes
1828 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1829 * one, the BOX rectangle. This rectangle is very cheap to compute,
1830 * and is guaranteed to contain all the other rectangles. Computing
1831 * the ICON rect is also cheap, but all the others are potentaily
1832 * expensive. This gives an easy and effective optimization when
1833 * searching (like point inclusion, or rectangle intersection):
1834 * first test against the BOX, and if TRUE, test agains the desired
1836 * If the function does not have all the necessary information
1837 * to computed the requested rectangles, will crash with a
1838 * failed assertion. This is done so we catch all programming
1839 * errors, given that the function is called only from our code.
1841 * We have the following 'special' meanings for a few fields:
1842 * * If LVIS_FOCUSED is set, we assume the item has the focus
1843 * This is important in ICON mode, where it might get a larger
1844 * then usual rectange
1846 * Please note that subitem support works only in REPORT mode.
1849 * [I] infoPtr : valid pointer to the listview structure
1850 * [I] lpLVItem : item to compute the measures for
1851 * [O] lprcBox : ptr to Box rectangle
1852 * The internal LVIR_BOX rectangle
1853 * [0] lprcState : ptr to State icon rectangle
1854 * The internal LVIR_STATE rectangle
1855 * [O] lprcIcon : ptr to Icon rectangle
1856 * Same as LVM_GETITEMRECT with LVIR_ICON
1857 * [O] lprcLabel : ptr to Label rectangle
1858 * Same as LVM_GETITEMRECT with LVIR_LABEL
1863 static void LISTVIEW_GetItemMetrics(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1864 LPRECT lprcBox, LPRECT lprcState,
1865 LPRECT lprcIcon, LPRECT lprcLabel)
1867 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1868 BOOL doState = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1869 RECT Box, State, Icon, Label;
1870 COLUMN_INFO *lpColumnInfo = NULL;
1872 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1874 /* Be smart and try to figure out the minimum we have to do */
1875 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1876 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1878 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1879 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1881 if (lprcLabel) doLabel = TRUE;
1882 if (doLabel || lprcIcon) doIcon = TRUE;
1883 if (doIcon || lprcState) doState = TRUE;
1885 /************************************************************/
1886 /* compute the box rectangle (it should be cheap to do) */
1887 /************************************************************/
1888 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1889 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1891 if (lpLVItem->iSubItem)
1893 Box = lpColumnInfo->rcHeader;
1898 Box.right = infoPtr->nItemWidth;
1901 Box.bottom = infoPtr->nItemHeight;
1903 /************************************************************/
1904 /* compute STATEICON bounding box */
1905 /************************************************************/
1908 if (uView == LVS_ICON)
1910 State.left = Box.left - infoPtr->iconStateSize.cx - 2;
1911 if (infoPtr->himlNormal)
1912 State.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1913 State.top = Box.top + infoPtr->iconSize.cy - infoPtr->iconStateSize.cy + 4;
1917 /* we need the ident in report mode, if we don't have it, we fail */
1918 State.left = Box.left;
1919 if (uView == LVS_REPORT)
1921 if (lpLVItem->iSubItem == 0)
1923 State.left += REPORT_MARGINX;
1924 assert(lpLVItem->mask & LVIF_INDENT);
1925 State.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
1928 State.top = Box.top;
1930 State.right = State.left;
1931 State.bottom = State.top;
1932 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1934 State.right += infoPtr->iconStateSize.cx;
1935 State.bottom += infoPtr->iconStateSize.cy;
1937 if (lprcState) *lprcState = State;
1938 TRACE(" - state=%s\n", debugrect(&State));
1941 /************************************************************/
1942 /* compute ICON bounding box (ala LVM_GETITEMRECT) */
1943 /************************************************************/
1946 if (uView == LVS_ICON)
1948 Icon.left = Box.left;
1949 if (infoPtr->himlNormal)
1950 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1951 Icon.top = Box.top + ICON_TOP_PADDING;
1952 Icon.right = Icon.left;
1953 Icon.bottom = Icon.top;
1954 if (infoPtr->himlNormal)
1956 Icon.right += infoPtr->iconSize.cx;
1957 Icon.bottom += infoPtr->iconSize.cy;
1960 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1962 Icon.left = State.right;
1964 Icon.right = Icon.left;
1965 if (infoPtr->himlSmall &&
1966 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
1967 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
1968 Icon.right += infoPtr->iconSize.cx;
1969 Icon.bottom = Icon.top + infoPtr->nItemHeight;
1971 if(lprcIcon) *lprcIcon = Icon;
1972 TRACE(" - icon=%s\n", debugrect(&Icon));
1975 /************************************************************/
1976 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
1977 /************************************************************/
1980 SIZE labelSize = { 0, 0 };
1982 /* calculate how far to the right can the label strech */
1983 Label.right = Box.right;
1984 if (uView == LVS_REPORT)
1986 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
1989 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
1991 labelSize.cx = infoPtr->nItemWidth;
1992 labelSize.cy = infoPtr->nItemHeight;
1996 /* we need the text in non owner draw mode */
1997 assert(lpLVItem->mask & LVIF_TEXT);
1998 if (is_textT(lpLVItem->pszText, TRUE))
2000 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2001 HDC hdc = GetDC(infoPtr->hwndSelf);
2002 HFONT hOldFont = SelectObject(hdc, hFont);
2006 /* compute rough rectangle where the label will go */
2007 SetRectEmpty(&rcText);
2008 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2009 rcText.bottom = infoPtr->nItemHeight;
2010 if (uView == LVS_ICON)
2011 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2013 /* now figure out the flags */
2014 if (uView == LVS_ICON)
2015 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2017 uFormat = LV_SL_DT_FLAGS;
2019 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2021 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2022 labelSize.cy = rcText.bottom - rcText.top;
2024 SelectObject(hdc, hOldFont);
2025 ReleaseDC(infoPtr->hwndSelf, hdc);
2029 if (uView == LVS_ICON)
2031 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2032 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2033 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2034 Label.right = Label.left + labelSize.cx;
2035 Label.bottom = Label.top + infoPtr->nItemHeight;
2036 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2038 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2039 labelSize.cy /= infoPtr->ntmHeight;
2040 labelSize.cy = max(labelSize.cy, 1);
2041 labelSize.cy *= infoPtr->ntmHeight;
2043 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2045 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2047 Label.left = Icon.right;
2048 Label.top = Box.top;
2049 Label.right = min(Label.left + labelSize.cx, Label.right);
2050 Label.bottom = Label.top + infoPtr->nItemHeight;
2053 if (lprcLabel) *lprcLabel = Label;
2054 TRACE(" - label=%s\n", debugrect(&Label));
2057 /* Fix the Box if necessary */
2060 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2061 else *lprcBox = Box;
2063 TRACE(" - box=%s\n", debugrect(&Box));
2067 * DESCRIPTION: [INTERNAL]
2070 * [I] infoPtr : valid pointer to the listview structure
2071 * [I] nItem : item number
2072 * [O] lprcBox : ptr to Box rectangle
2077 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2079 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2080 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2081 POINT Position, Origin;
2084 LISTVIEW_GetOrigin(infoPtr, &Origin);
2085 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2087 /* Be smart and try to figure out the minimum we have to do */
2089 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2090 lvItem.mask |= LVIF_TEXT;
2091 lvItem.iItem = nItem;
2092 lvItem.iSubItem = 0;
2093 lvItem.pszText = szDispText;
2094 lvItem.cchTextMax = DISP_TEXT_SIZE;
2095 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2096 if (uView == LVS_ICON)
2098 lvItem.mask |= LVIF_STATE;
2099 lvItem.stateMask = LVIS_FOCUSED;
2100 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2102 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0);
2104 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2110 * Returns the current icon position, and advances it along the top.
2111 * The returned position is not offset by Origin.
2114 * [I] infoPtr : valid pointer to the listview structure
2115 * [O] lpPos : will get the current icon position
2120 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2122 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2124 *lpPos = infoPtr->currIconPos;
2126 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2127 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2129 infoPtr->currIconPos.x = 0;
2130 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2136 * Returns the current icon position, and advances it down the left edge.
2137 * The returned position is not offset by Origin.
2140 * [I] infoPtr : valid pointer to the listview structure
2141 * [O] lpPos : will get the current icon position
2146 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2148 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2150 *lpPos = infoPtr->currIconPos;
2152 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2153 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2155 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2156 infoPtr->currIconPos.y = 0;
2162 * Moves an icon to the specified position.
2163 * It takes care of invalidating the item, etc.
2166 * [I] infoPtr : valid pointer to the listview structure
2167 * [I] nItem : the item to move
2168 * [I] lpPos : the new icon position
2169 * [I] isNew : flags the item as being new
2175 static BOOL LISTVIEW_MoveIconTo(LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2181 old.x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2182 old.y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2184 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2185 LISTVIEW_InvalidateItem(infoPtr, nItem);
2188 /* Allocating a POINTER for every item is too resource intensive,
2189 * so we'll keep the (x,y) in different arrays */
2190 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)lppt->x)) return FALSE;
2191 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)lppt->y)) return FALSE;
2193 LISTVIEW_InvalidateItem(infoPtr, nItem);
2200 * Arranges listview items in icon display mode.
2203 * [I] infoPtr : valid pointer to the listview structure
2204 * [I] nAlignCode : alignment code
2210 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2212 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2213 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2217 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2219 TRACE("nAlignCode=%d\n", nAlignCode);
2221 if (nAlignCode == LVA_DEFAULT)
2223 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2224 else nAlignCode = LVA_ALIGNTOP;
2229 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2230 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2231 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2232 default: return FALSE;
2235 infoPtr->bAutoarrange = TRUE;
2236 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2237 for (i = 0; i < infoPtr->nItemCount; i++)
2239 next_pos(infoPtr, &pos);
2240 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2248 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2251 * [I] infoPtr : valid pointer to the listview structure
2252 * [O] lprcView : bounding rectangle
2258 static void LISTVIEW_GetAreaRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2262 SetRectEmpty(lprcView);
2264 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2268 for (i = 0; i < infoPtr->nItemCount; i++)
2270 x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, i);
2271 y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, i);
2272 lprcView->right = max(lprcView->right, x);
2273 lprcView->bottom = max(lprcView->bottom, y);
2275 if (infoPtr->nItemCount > 0)
2277 lprcView->right += infoPtr->nItemWidth;
2278 lprcView->bottom += infoPtr->nItemHeight;
2283 y = LISTVIEW_GetCountPerColumn(infoPtr);
2284 x = infoPtr->nItemCount / y;
2285 if (infoPtr->nItemCount % y) x++;
2286 lprcView->right = x * infoPtr->nItemWidth;
2287 lprcView->bottom = y * infoPtr->nItemHeight;
2291 lprcView->right = infoPtr->nItemWidth;
2292 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2299 * Retrieves the bounding rectangle of all the items.
2302 * [I] infoPtr : valid pointer to the listview structure
2303 * [O] lprcView : bounding rectangle
2309 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2313 TRACE("(lprcView=%p)\n", lprcView);
2315 if (!lprcView) return FALSE;
2317 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2318 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2319 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2321 TRACE("lprcView=%s\n", debugrect(lprcView));
2328 * Retrieves the subitem pointer associated with the subitem index.
2331 * [I] hdpaSubItems : DPA handle for a specific item
2332 * [I] nSubItem : index of subitem
2335 * SUCCESS : subitem pointer
2338 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2340 SUBITEM_INFO *lpSubItem;
2343 /* we should binary search here if need be */
2344 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2346 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
2347 if (lpSubItem->iSubItem == nSubItem)
2357 * Caclulates the desired item width.
2360 * [I] infoPtr : valid pointer to the listview structure
2363 * The desired item width.
2365 static INT LISTVIEW_CalculateItemWidth(LISTVIEW_INFO *infoPtr)
2367 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2370 TRACE("uView=%d\n", uView);
2372 if (uView == LVS_ICON)
2373 nItemWidth = infoPtr->iconSpacing.cx;
2374 else if (uView == LVS_REPORT)
2378 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2380 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2381 nItemWidth = rcHeader.right;
2384 else /* LVS_SMALLICON, or LVS_LIST */
2388 for (i = 0; i < infoPtr->nItemCount; i++)
2389 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2391 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2392 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2394 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2397 return max(nItemWidth, 1);
2402 * Caclulates the desired item height.
2405 * [I] infoPtr : valid pointer to the listview structure
2408 * The desired item height.
2410 static INT LISTVIEW_CalculateItemHeight(LISTVIEW_INFO *infoPtr)
2412 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2415 TRACE("uView=%d\n", uView);
2417 if (uView == LVS_ICON)
2418 nItemHeight = infoPtr->iconSpacing.cy;
2421 nItemHeight = infoPtr->ntmHeight;
2422 if (infoPtr->himlState)
2423 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2424 if (infoPtr->himlSmall)
2425 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2426 if (infoPtr->himlState || infoPtr->himlSmall)
2427 nItemHeight += HEIGHT_PADDING;
2430 return max(nItemHeight, 1);
2435 * Updates the width, and height of an item.
2438 * [I] infoPtr : valid pointer to the listview structure
2443 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2445 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2446 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2452 * Retrieves and saves important text metrics info for the current
2456 * [I] infoPtr : valid pointer to the listview structure
2459 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2461 HDC hdc = GetDC(infoPtr->hwndSelf);
2462 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2463 HFONT hOldFont = SelectObject(hdc, hFont);
2466 if (GetTextMetricsW(hdc, &tm))
2468 infoPtr->ntmHeight = tm.tmHeight;
2469 infoPtr->ntmAveCharWidth = tm.tmAveCharWidth;
2471 SelectObject(hdc, hOldFont);
2472 ReleaseDC(infoPtr->hwndSelf, hdc);
2474 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2479 * A compare function for ranges
2482 * [I] range1 : pointer to range 1;
2483 * [I] range2 : pointer to range 2;
2487 * > 0 : if range 1 > range 2
2488 * < 0 : if range 2 > range 1
2489 * = 0 : if range intersects range 2
2491 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2495 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2497 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2502 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2508 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2510 #define ranges_check(ranges, desc) do { } while(0)
2513 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2518 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2520 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2521 ranges_dump(ranges);
2522 prev = (RANGE *)DPA_GetPtr(ranges->hdpa, 0);
2523 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2524 assert (prev->lower >= 0 && prev->lower < prev->upper);
2525 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2527 curr = (RANGE *)DPA_GetPtr(ranges->hdpa, i);
2528 assert (prev->upper <= curr->lower);
2529 assert (curr->lower < curr->upper);
2532 TRACE("--- Done checking---\n");
2535 static RANGES ranges_create(int count)
2537 RANGES ranges = (RANGES)Alloc(sizeof(struct tagRANGES));
2538 if (!ranges) return NULL;
2539 ranges->hdpa = DPA_Create(count);
2540 if (ranges->hdpa) return ranges;
2545 static void ranges_clear(RANGES ranges)
2549 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2550 Free(DPA_GetPtr(ranges->hdpa, i));
2551 DPA_DeleteAllPtrs(ranges->hdpa);
2555 static void ranges_destroy(RANGES ranges)
2557 if (!ranges) return;
2558 ranges_clear(ranges);
2559 DPA_Destroy(ranges->hdpa);
2563 static RANGES ranges_clone(RANGES ranges)
2568 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2570 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2572 RANGE *newrng = (RANGE *)Alloc(sizeof(RANGE));
2573 if (!newrng) goto fail;
2574 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2575 DPA_SetPtr(clone->hdpa, i, newrng);
2580 TRACE ("clone failed\n");
2581 ranges_destroy(clone);
2585 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2589 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2590 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2595 static void ranges_dump(RANGES ranges)
2599 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2600 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2603 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2605 RANGE srchrng = { nItem, nItem + 1 };
2607 TRACE("(nItem=%d)\n", nItem);
2608 ranges_check(ranges, "before contain");
2609 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2612 static INT ranges_itemcount(RANGES ranges)
2616 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2618 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2619 count += sel->upper - sel->lower;
2625 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2627 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2630 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2631 if (index == -1) return TRUE;
2633 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2635 chkrng = DPA_GetPtr(ranges->hdpa, index);
2636 if (chkrng->lower >= nItem)
2637 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2638 if (chkrng->upper > nItem)
2639 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2644 static BOOL ranges_add(RANGES ranges, RANGE range)
2649 TRACE("(%s)\n", debugrange(&range));
2650 ranges_check(ranges, "before add");
2652 /* try find overlapping regions first */
2653 srchrgn.lower = range.lower - 1;
2654 srchrgn.upper = range.upper + 1;
2655 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2661 TRACE("Adding new range\n");
2663 /* create the brand new range to insert */
2664 newrgn = (RANGE *)Alloc(sizeof(RANGE));
2665 if(!newrgn) goto fail;
2668 /* figure out where to insert it */
2669 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2670 TRACE("index=%d\n", index);
2671 if (index == -1) index = 0;
2673 /* and get it over with */
2674 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2682 RANGE *chkrgn, *mrgrgn;
2683 INT fromindex, mergeindex;
2685 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2686 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2688 chkrgn->lower = min(range.lower, chkrgn->lower);
2689 chkrgn->upper = max(range.upper, chkrgn->upper);
2691 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2693 /* merge now common anges */
2695 srchrgn.lower = chkrgn->lower - 1;
2696 srchrgn.upper = chkrgn->upper + 1;
2700 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2701 if (mergeindex == -1) break;
2702 if (mergeindex == index)
2704 fromindex = index + 1;
2708 TRACE("Merge with index %i\n", mergeindex);
2710 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2711 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2712 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2714 DPA_DeletePtr(ranges->hdpa, mergeindex);
2715 if (mergeindex < index) index --;
2719 ranges_check(ranges, "after add");
2723 ranges_check(ranges, "failed add");
2727 static BOOL ranges_del(RANGES ranges, RANGE range)
2732 TRACE("(%s)\n", debugrange(&range));
2733 ranges_check(ranges, "before del");
2735 /* we don't use DPAS_SORTED here, since we need *
2736 * to find the first overlapping range */
2737 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2740 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2742 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2744 /* case 1: Same range */
2745 if ( (chkrgn->upper == range.upper) &&
2746 (chkrgn->lower == range.lower) )
2748 DPA_DeletePtr(ranges->hdpa, index);
2751 /* case 2: engulf */
2752 else if ( (chkrgn->upper <= range.upper) &&
2753 (chkrgn->lower >= range.lower) )
2755 DPA_DeletePtr(ranges->hdpa, index);
2757 /* case 3: overlap upper */
2758 else if ( (chkrgn->upper <= range.upper) &&
2759 (chkrgn->lower < range.lower) )
2761 chkrgn->upper = range.lower;
2763 /* case 4: overlap lower */
2764 else if ( (chkrgn->upper > range.upper) &&
2765 (chkrgn->lower >= range.lower) )
2767 chkrgn->lower = range.upper;
2770 /* case 5: fully internal */
2773 RANGE tmprgn = *chkrgn, *newrgn;
2775 if (!(newrgn = (RANGE *)Alloc(sizeof(RANGE)))) goto fail;
2776 newrgn->lower = chkrgn->lower;
2777 newrgn->upper = range.lower;
2778 chkrgn->lower = range.upper;
2779 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2788 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2791 ranges_check(ranges, "after del");
2795 ranges_check(ranges, "failed del");
2801 * Removes all selection ranges
2804 * [I] infoPtr : valid pointer to the listview structure
2805 * [I] toSkip : item range to skip removing the selection
2811 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2820 lvItem.stateMask = LVIS_SELECTED;
2822 /* need to clone the DPA because callbacks can change it */
2823 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2824 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2825 while(iterator_next(&i))
2826 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2827 /* note that the iterator destructor will free the cloned range */
2828 iterator_destroy(&i);
2833 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2837 if (!(toSkip = ranges_create(1))) return FALSE;
2838 if (nItem != -1) ranges_additem(toSkip, nItem);
2839 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2840 ranges_destroy(toSkip);
2844 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2846 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2851 * Retrieves the number of items that are marked as selected.
2854 * [I] infoPtr : valid pointer to the listview structure
2857 * Number of items selected.
2859 static INT LISTVIEW_GetSelectedCount(LISTVIEW_INFO *infoPtr)
2861 INT nSelectedCount = 0;
2863 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2866 for (i = 0; i < infoPtr->nItemCount; i++)
2868 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2873 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2875 TRACE("nSelectedCount=%d\n", nSelectedCount);
2876 return nSelectedCount;
2881 * Manages the item focus.
2884 * [I] infoPtr : valid pointer to the listview structure
2885 * [I] nItem : item index
2888 * TRUE : focused item changed
2889 * FALSE : focused item has NOT changed
2891 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2893 INT oldFocus = infoPtr->nFocusedItem;
2896 if (nItem == infoPtr->nFocusedItem) return FALSE;
2898 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2899 lvItem.stateMask = LVIS_FOCUSED;
2900 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
2902 return oldFocus != infoPtr->nFocusedItem;
2905 /* Helper function for LISTVIEW_ShiftIndices *only* */
2906 static INT shift_item(LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2908 if (nShiftItem < nItem) return nShiftItem;
2910 if (nShiftItem > nItem) return nShiftItem + direction;
2912 if (direction > 0) return nShiftItem + direction;
2914 return min(nShiftItem, infoPtr->nItemCount - 1);
2919 * Updates the various indices after an item has been inserted or deleted.
2922 * [I] infoPtr : valid pointer to the listview structure
2923 * [I] nItem : item index
2924 * [I] direction : Direction of shift, +1 or -1.
2929 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
2934 /* temporarily disable change notification while shifting items */
2935 bOldChange = infoPtr->bDoChangeNotify;
2936 infoPtr->bDoChangeNotify = FALSE;
2938 TRACE("Shifting %iu, %i steps\n", nItem, direction);
2940 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
2942 assert(abs(direction) == 1);
2944 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
2946 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
2947 if (nNewFocus != infoPtr->nFocusedItem)
2948 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
2950 /* But we are not supposed to modify nHotItem! */
2952 infoPtr->bDoChangeNotify = bOldChange;
2958 * Adds a block of selections.
2961 * [I] infoPtr : valid pointer to the listview structure
2962 * [I] nItem : item index
2967 static void LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2969 INT nFirst = min(infoPtr->nSelectionMark, nItem);
2970 INT nLast = max(infoPtr->nSelectionMark, nItem);
2974 if (nFirst == -1) nFirst = nItem;
2976 item.state = LVIS_SELECTED;
2977 item.stateMask = LVIS_SELECTED;
2979 /* FIXME: this is not correct LVS_OWNERDATA
2980 * setting the item states individually will generate
2981 * a LVN_ITEMCHANGED notification for each one. Instead,
2982 * we have to send a LVN_ODSTATECHANGED notification.
2983 * See MSDN documentation for LVN_ITEMCHANGED.
2985 for (i = nFirst; i <= nLast; i++)
2986 LISTVIEW_SetItemState(infoPtr,i,&item);
2992 * Sets a single group selection.
2995 * [I] infoPtr : valid pointer to the listview structure
2996 * [I] nItem : item index
3001 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3003 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3008 if (!(selection = ranges_create(100))) return;
3010 item.state = LVIS_SELECTED;
3011 item.stateMask = LVIS_SELECTED;
3013 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3015 if (infoPtr->nSelectionMark == -1)
3017 infoPtr->nSelectionMark = nItem;
3018 ranges_additem(selection, nItem);
3024 sel.lower = min(infoPtr->nSelectionMark, nItem);
3025 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3026 ranges_add(selection, sel);
3031 RECT rcItem, rcSel, rcSelMark;
3034 rcItem.left = LVIR_BOUNDS;
3035 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3036 rcSelMark.left = LVIR_BOUNDS;
3037 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3038 UnionRect(&rcSel, &rcItem, &rcSelMark);
3039 iterator_frameditems(&i, infoPtr, &rcSel);
3040 while(iterator_next(&i))
3042 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3043 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3045 iterator_destroy(&i);
3048 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3049 iterator_rangesitems(&i, selection);
3050 while(iterator_next(&i))
3051 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3052 /* this will also destroy the selection */
3053 iterator_destroy(&i);
3055 LISTVIEW_SetItemFocus(infoPtr, nItem);
3060 * Sets a single selection.
3063 * [I] infoPtr : valid pointer to the listview structure
3064 * [I] nItem : item index
3069 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3073 TRACE("nItem=%d\n", nItem);
3075 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3077 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3078 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3079 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3081 infoPtr->nSelectionMark = nItem;
3086 * Set selection(s) with keyboard.
3089 * [I] infoPtr : valid pointer to the listview structure
3090 * [I] nItem : item index
3093 * SUCCESS : TRUE (needs to be repainted)
3094 * FAILURE : FALSE (nothing has changed)
3096 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
3098 /* FIXME: pass in the state */
3099 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3100 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3101 BOOL bResult = FALSE;
3103 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3105 if (infoPtr->dwStyle & LVS_SINGLESEL)
3108 LISTVIEW_SetSelection(infoPtr, nItem);
3115 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3119 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3124 LISTVIEW_SetSelection(infoPtr, nItem);
3127 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3130 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3137 * Called when the mouse is being actively tracked and has hovered for a specified
3141 * [I] infoPtr : valid pointer to the listview structure
3142 * [I] fwKeys : key indicator
3143 * [I] x,y : mouse position
3146 * 0 if the message was processed, non-zero if there was an error
3149 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3150 * over the item for a certain period of time.
3153 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKyes, INT x, INT y)
3155 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
3156 /* FIXME: select the item!!! */
3157 /*LISTVIEW_GetItemAtPt(infoPtr, pt)*/;
3164 * Called whenever WM_MOUSEMOVE is received.
3167 * [I] infoPtr : valid pointer to the listview structure
3168 * [I] fwKeys : key indicator
3169 * [I] x,y : mouse position
3172 * 0 if the message is processed, non-zero if there was an error
3174 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3176 TRACKMOUSEEVENT trackinfo;
3178 /* see if we are supposed to be tracking mouse hovering */
3179 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
3180 /* fill in the trackinfo struct */
3181 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3182 trackinfo.dwFlags = TME_QUERY;
3183 trackinfo.hwndTrack = infoPtr->hwndSelf;
3184 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3186 /* see if we are already tracking this hwnd */
3187 _TrackMouseEvent(&trackinfo);
3189 if(!(trackinfo.dwFlags & TME_HOVER)) {
3190 trackinfo.dwFlags = TME_HOVER;
3192 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3193 _TrackMouseEvent(&trackinfo);
3202 * Tests wheather the item is assignable to a list with style lStyle
3204 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3206 if ( (lpLVItem->mask & LVIF_TEXT) &&
3207 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3208 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3216 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3219 * [I] infoPtr : valid pointer to the listview structure
3220 * [I] lpLVItem : valid pointer to new item atttributes
3221 * [I] isNew : the item being set is being inserted
3222 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3223 * [O] bChanged : will be set to TRUE if the item really changed
3229 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3231 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3239 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3241 if (lpLVItem->mask == 0) return TRUE;
3243 if (infoPtr->dwStyle & LVS_OWNERDATA)
3245 /* a virtual listview we stores only selection and focus */
3246 if (lpLVItem->mask & ~LVIF_STATE)
3252 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3253 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
3257 /* we need to get the lParam and state of the item */
3258 item.iItem = lpLVItem->iItem;
3259 item.iSubItem = lpLVItem->iSubItem;
3260 item.mask = LVIF_STATE | LVIF_PARAM;
3261 item.stateMask = ~0;
3264 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3266 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3267 /* determine what fields will change */
3268 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
3269 uChanged |= LVIF_STATE;
3271 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3272 uChanged |= LVIF_IMAGE;
3274 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3275 uChanged |= LVIF_PARAM;
3277 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3278 uChanged |= LVIF_INDENT;
3280 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3281 uChanged |= LVIF_TEXT;
3283 TRACE("uChanged=0x%x\n", uChanged);
3284 if (!uChanged) return TRUE;
3287 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3288 nmlv.iItem = lpLVItem->iItem;
3289 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3290 nmlv.uOldState = item.state;
3291 nmlv.uChanged = uChanged;
3292 nmlv.lParam = item.lParam;
3294 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3295 /* and we are _NOT_ virtual (LVS_OWERNDATA), and change notifications */
3297 if(lpItem && !isNew && infoPtr->bDoChangeNotify &&
3298 notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3301 /* copy information */
3302 if (lpLVItem->mask & LVIF_TEXT)
3303 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3305 if (lpLVItem->mask & LVIF_IMAGE)
3306 lpItem->hdr.iImage = lpLVItem->iImage;
3308 if (lpLVItem->mask & LVIF_PARAM)
3309 lpItem->lParam = lpLVItem->lParam;
3311 if (lpLVItem->mask & LVIF_INDENT)
3312 lpItem->iIndent = lpLVItem->iIndent;
3314 if (uChanged & LVIF_STATE)
3316 if (lpItem && (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3318 lpItem->state &= ~lpLVItem->stateMask;
3319 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3321 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3323 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3324 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3326 else if (lpLVItem->stateMask & LVIS_SELECTED)
3327 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3329 /* if we are asked to change focus, and we manage it, do it */
3330 if (lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3332 if (lpLVItem->state & LVIS_FOCUSED)
3334 LISTVIEW_SetItemFocus(infoPtr, -1);
3335 infoPtr->nFocusedItem = lpLVItem->iItem;
3336 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3338 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3339 infoPtr->nFocusedItem = -1;
3343 /* if we're inserting the item, we're done */
3344 if (isNew) return TRUE;
3346 /* send LVN_ITEMCHANGED notification */
3347 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3348 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3355 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3358 * [I] infoPtr : valid pointer to the listview structure
3359 * [I] lpLVItem : valid pointer to new subitem atttributes
3360 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3361 * [O] bChanged : will be set to TRUE if the item really changed
3367 static BOOL set_sub_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3370 SUBITEM_INFO *lpSubItem;
3372 /* we do not support subitems for virtual listviews */
3373 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3375 /* set subitem only if column is present */
3376 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3378 /* First do some sanity checks */
3379 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE)) return FALSE;
3380 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE))) return TRUE;
3382 /* get the subitem structure, and create it if not there */
3383 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3384 assert (hdpaSubItems);
3386 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3389 SUBITEM_INFO *tmpSubItem;
3392 lpSubItem = (SUBITEM_INFO *)Alloc(sizeof(SUBITEM_INFO));
3393 if (!lpSubItem) return FALSE;
3394 /* we could binary search here, if need be...*/
3395 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3397 tmpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
3398 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3400 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3405 lpSubItem->iSubItem = lpLVItem->iSubItem;
3406 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3410 if (lpLVItem->mask & LVIF_IMAGE)
3411 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3413 lpSubItem->hdr.iImage = lpLVItem->iImage;
3417 if (lpLVItem->mask & LVIF_TEXT)
3418 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3420 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3429 * Sets item attributes.
3432 * [I] infoPtr : valid pointer to the listview structure
3433 * [I] lpLVItem : new item atttributes
3434 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3440 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
3442 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3443 LPWSTR pszText = NULL;
3444 BOOL bResult, bChanged = FALSE;
3446 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3448 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3451 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3452 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3454 pszText = lpLVItem->pszText;
3455 ((LVITEMW *)lpLVItem)->pszText = textdupTtoW(lpLVItem->pszText, isW);
3458 /* actually set the fields */
3459 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3461 if (lpLVItem->iSubItem)
3462 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3464 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3466 /* redraw item, if necessary */
3467 if (bChanged && !infoPtr->bIsDrawing)
3469 /* this little optimization eliminates some nasty flicker */
3470 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3471 (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) || lpLVItem->iSubItem) )
3472 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3474 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3479 textfreeT(lpLVItem->pszText, isW);
3480 ((LVITEMW *)lpLVItem)->pszText = pszText;
3488 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3491 * [I] infoPtr : valid pointer to the listview structure
3496 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *infoPtr)
3498 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3500 SCROLLINFO scrollInfo;
3502 scrollInfo.cbSize = sizeof(SCROLLINFO);
3503 scrollInfo.fMask = SIF_POS;
3505 if (uView == LVS_LIST)
3507 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3508 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3510 else if (uView == LVS_REPORT)
3512 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3513 nItem = scrollInfo.nPos;
3517 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3518 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3521 TRACE("nItem=%d\n", nItem);
3529 * Erases the background of the given rectangle
3532 * [I] infoPtr : valid pointer to the listview structure
3533 * [I] hdc : device context handle
3534 * [I] lprcBox : clipping rectangle
3540 static inline BOOL LISTVIEW_FillBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3542 if (!infoPtr->hBkBrush) return FALSE;
3544 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, debugrect(lprcBox), infoPtr->hBkBrush);
3546 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3554 * [I] infoPtr : valid pointer to the listview structure
3555 * [I] hdc : device context handle
3556 * [I] nItem : item index
3557 * [I] nSubItem : subitem index
3558 * [I] pos : item position in client coordinates
3559 * [I] cdmode : custom draw mode
3565 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3567 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3568 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3569 static const WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3570 DWORD cdsubitemmode = CDRF_DODEFAULT;
3571 RECT* lprcFocus, rcSelect, rcBox, rcState, rcIcon, rcLabel;
3572 NMLVCUSTOMDRAW nmlvcd;
3576 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, debugpoint(&pos));
3578 /* get information needed for drawing the item */
3579 lvItem.mask = LVIF_TEXT | LVIF_IMAGE;
3580 if (nSubItem == 0) lvItem.mask |= LVIF_STATE | LVIF_PARAM;
3581 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3582 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3583 lvItem.iItem = nItem;
3584 lvItem.iSubItem = nSubItem;
3587 lvItem.cchTextMax = DISP_TEXT_SIZE;
3588 lvItem.pszText = szDispText;
3589 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3590 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3591 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3592 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = (LPWSTR)szCallback;
3593 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3595 /* now check if we need to update the focus rectangle */
3596 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3598 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3599 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
3600 OffsetRect(&rcBox, pos.x, pos.y);
3601 OffsetRect(&rcState, pos.x, pos.y);
3602 OffsetRect(&rcIcon, pos.x, pos.y);
3603 OffsetRect(&rcLabel, pos.x, pos.y);
3604 TRACE(" rcBox=%s, rcState=%s, rcIcon=%s. rcLabel=%s\n",
3605 debugrect(&rcBox), debugrect(&rcState), debugrect(&rcIcon), debugrect(&rcLabel));
3607 /* fill in the custom draw structure */
3608 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3610 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3611 if (cdmode & CDRF_NOTIFYITEMDRAW)
3612 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3613 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3614 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3615 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3616 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3618 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3619 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3621 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3622 prepaint_setup(infoPtr, hdc, &nmlvcd);
3624 /* in full row select, subitems, will just use main item's colors */
3625 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3626 nmlvcd.clrTextBk = CLR_NONE;
3629 if (infoPtr->himlState && !IsRectEmpty(&rcState))
3631 UINT uStateImage = (lvItem.state & LVIS_STATEIMAGEMASK) >> 12;
3634 TRACE("uStateImage=%d\n", uStateImage);
3635 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc, rcState.left, rcState.top, ILD_NORMAL);
3640 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3641 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3643 TRACE("iImage=%d\n", lvItem.iImage);
3644 ImageList_Draw(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3645 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3648 /* Don't bother painting item being edited */
3649 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3651 /* draw the selection background, if we're drawing the main item */
3655 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3656 rcSelect.right = rcBox.right;
3658 if (nmlvcd.clrTextBk != CLR_NONE)
3659 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3660 if(lprcFocus) *lprcFocus = rcSelect;
3663 /* figure out the text drawing flags */
3664 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3665 if (uView == LVS_ICON)
3666 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3669 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3671 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3672 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3673 default: uFormat |= DT_LEFT;
3676 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3678 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3679 else rcLabel.left += LABEL_HOR_PADDING;
3681 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3682 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3685 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3686 notify_postpaint(infoPtr, &nmlvcd);
3692 * Draws listview items when in owner draw mode.
3695 * [I] infoPtr : valid pointer to the listview structure
3696 * [I] hdc : device context handle
3701 static void LISTVIEW_RefreshOwnerDraw(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3703 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
3704 DWORD cditemmode = CDRF_DODEFAULT;
3705 NMLVCUSTOMDRAW nmlvcd;
3706 POINT Origin, Position;
3712 ZeroMemory(&dis, sizeof(dis));
3714 /* Get scroll info once before loop */
3715 LISTVIEW_GetOrigin(infoPtr, &Origin);
3717 /* iterate through the invalidated rows */
3718 while(iterator_next(i))
3720 item.iItem = i->nItem;
3722 item.mask = LVIF_PARAM | LVIF_STATE;
3723 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3724 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3726 dis.CtlType = ODT_LISTVIEW;
3728 dis.itemID = item.iItem;
3729 dis.itemAction = ODA_DRAWENTIRE;
3731 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3732 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3733 dis.hwndItem = infoPtr->hwndSelf;
3735 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3736 dis.rcItem.left = Position.x + Origin.x;
3737 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3738 dis.rcItem.top = Position.y + Origin.y;
3739 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3740 dis.itemData = item.lParam;
3742 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), debugrect(&dis.rcItem));
3745 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
3746 * structure for the rest. of the paint cycle
3748 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
3749 if (cdmode & CDRF_NOTIFYITEMDRAW)
3750 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3752 if (!(cditemmode & CDRF_SKIPDEFAULT))
3754 prepaint_setup (infoPtr, hdc, &nmlvcd);
3755 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3758 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3759 notify_postpaint(infoPtr, &nmlvcd);
3765 * Draws listview items when in report display mode.
3768 * [I] infoPtr : valid pointer to the listview structure
3769 * [I] hdc : device context handle
3770 * [I] cdmode : custom draw mode
3775 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3778 RECT rcClip, rcItem;
3779 POINT Origin, Position;
3785 /* figure out what to draw */
3786 rgntype = GetClipBox(hdc, &rcClip);
3787 if (rgntype == NULLREGION) return;
3789 /* Get scroll info once before loop */
3790 LISTVIEW_GetOrigin(infoPtr, &Origin);
3792 /* narrow down the columns we need to paint */
3793 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
3795 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
3796 if (rcItem.right + Origin.x >= rcClip.left) break;
3798 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
3800 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
3801 if (rcItem.left + Origin.x < rcClip.right) break;
3803 iterator_rangeitems(&j, colRange);
3805 /* in full row select, we _have_ to draw the main item */
3806 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
3809 /* iterate through the invalidated rows */
3810 while(iterator_next(i))
3812 /* iterate through the invalidated columns */
3813 while(iterator_next(&j))
3815 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
3816 Position.x += Origin.x;
3817 Position.y += Origin.y;
3819 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
3821 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
3823 rcItem.bottom = infoPtr->nItemHeight;
3824 OffsetRect(&rcItem, Position.x, Position.y);
3825 if (!RectVisible(hdc, &rcItem)) continue;
3828 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
3831 iterator_destroy(&j);
3836 * Draws listview items when in list display mode.
3839 * [I] infoPtr : valid pointer to the listview structure
3840 * [I] hdc : device context handle
3841 * [I] cdmode : custom draw mode
3846 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3848 POINT Origin, Position;
3850 /* Get scroll info once before loop */
3851 LISTVIEW_GetOrigin(infoPtr, &Origin);
3853 while(iterator_prev(i))
3855 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
3856 Position.x += Origin.x;
3857 Position.y += Origin.y;
3859 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
3866 * Draws listview items.
3869 * [I] infoPtr : valid pointer to the listview structure
3870 * [I] hdc : device context handle
3875 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc)
3877 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3878 COLORREF oldTextColor, oldClrTextBk, oldClrText;
3879 NMLVCUSTOMDRAW nmlvcd;
3886 LISTVIEW_DUMP(infoPtr);
3888 infoPtr->bIsDrawing = TRUE;
3890 /* save dc values we're gonna trash while drawing */
3891 hOldFont = SelectObject(hdc, infoPtr->hFont);
3892 oldBkMode = GetBkMode(hdc);
3893 infoPtr->clrTextBkDefault = GetBkColor(hdc);
3894 oldTextColor = GetTextColor(hdc);
3896 oldClrTextBk = infoPtr->clrTextBk;
3897 oldClrText = infoPtr->clrText;
3899 infoPtr->cditemmode = CDRF_DODEFAULT;
3901 GetClientRect(infoPtr->hwndSelf, &rcClient);
3902 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
3903 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3904 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
3905 prepaint_setup(infoPtr, hdc, &nmlvcd);
3907 /* Use these colors to draw the items */
3908 infoPtr->clrTextBk = nmlvcd.clrTextBk;
3909 infoPtr->clrText = nmlvcd.clrText;
3911 /* nothing to draw */
3912 if(infoPtr->nItemCount == 0) goto enddraw;
3914 /* figure out what we need to draw */
3915 iterator_visibleitems(&i, infoPtr, hdc);
3917 /* send cache hint notification */
3918 if (infoPtr->dwStyle & LVS_OWNERDATA)
3920 RANGE range = iterator_range(&i);
3923 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
3924 nmlv.iFrom = range.lower;
3925 nmlv.iTo = range.upper - 1;
3926 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
3929 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
3930 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
3933 if (uView == LVS_REPORT)
3934 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
3935 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
3936 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
3938 /* if we have a focus rect, draw it */
3939 if (infoPtr->bFocus)
3940 DrawFocusRect(hdc, &infoPtr->rcFocus);
3942 iterator_destroy(&i);
3945 if (cdmode & CDRF_NOTIFYPOSTPAINT)
3946 notify_postpaint(infoPtr, &nmlvcd);
3948 infoPtr->clrTextBk = oldClrTextBk;
3949 infoPtr->clrText = oldClrText;
3951 SelectObject(hdc, hOldFont);
3952 SetBkMode(hdc, oldBkMode);
3953 SetBkColor(hdc, infoPtr->clrTextBkDefault);
3954 SetTextColor(hdc, oldTextColor);
3955 infoPtr->bIsDrawing = FALSE;
3961 * Calculates the approximate width and height of a given number of items.
3964 * [I] infoPtr : valid pointer to the listview structure
3965 * [I] nItemCount : number of items
3966 * [I] wWidth : width
3967 * [I] wHeight : height
3970 * Returns a DWORD. The width in the low word and the height in high word.
3972 static DWORD LISTVIEW_ApproximateViewRect(LISTVIEW_INFO *infoPtr, INT nItemCount,
3973 WORD wWidth, WORD wHeight)
3975 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3976 INT nItemCountPerColumn = 1;
3977 INT nColumnCount = 0;
3978 DWORD dwViewRect = 0;
3980 if (nItemCount == -1)
3981 nItemCount = infoPtr->nItemCount;
3983 if (uView == LVS_LIST)
3985 if (wHeight == 0xFFFF)
3987 /* use current height */
3988 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
3991 if (wHeight < infoPtr->nItemHeight)
3992 wHeight = infoPtr->nItemHeight;
3996 if (infoPtr->nItemHeight > 0)
3998 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
3999 if (nItemCountPerColumn == 0)
4000 nItemCountPerColumn = 1;
4002 if (nItemCount % nItemCountPerColumn != 0)
4003 nColumnCount = nItemCount / nItemCountPerColumn;
4005 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4009 /* Microsoft padding magic */
4010 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4011 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4013 dwViewRect = MAKELONG(wWidth, wHeight);
4015 else if (uView == LVS_REPORT)
4019 if (infoPtr->nItemCount > 0)
4021 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4022 wWidth = rcBox.right - rcBox.left;
4023 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4027 /* use current height and width */
4028 if (wHeight == 0xffff)
4029 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4030 if (wWidth == 0xffff)
4031 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4034 dwViewRect = MAKELONG(wWidth, wHeight);
4036 else if (uView == LVS_SMALLICON)
4037 FIXME("uView == LVS_SMALLICON: not implemented\n");
4038 else if (uView == LVS_ICON)
4039 FIXME("uView == LVS_ICON: not implemented\n");
4047 * Create a drag image list for the specified item.
4050 * [I] infoPtr : valid pointer to the listview structure
4051 * [I] iItem : index of item
4052 * [O] lppt : Upperr-left corner of the image
4055 * Returns a handle to the image list if successful, NULL otherwise.
4057 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4063 HBITMAP hbmp, hOldbmp;
4064 HIMAGELIST dragList = 0;
4065 TRACE("iItem=%d Count=%d \n", iItem, infoPtr->nItemCount);
4067 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4070 rcItem.left = LVIR_BOUNDS;
4071 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4074 lppt->x = rcItem.left;
4075 lppt->y = rcItem.top;
4077 size.cx = rcItem.right - rcItem.left;
4078 size.cy = rcItem.bottom - rcItem.top;
4080 hdcOrig = GetDC(infoPtr->hwndSelf);
4081 hdc = CreateCompatibleDC(hdcOrig);
4082 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4083 hOldbmp = SelectObject(hdc, hbmp);
4085 rcItem.left = rcItem.top = 0;
4086 rcItem.right = size.cx;
4087 rcItem.bottom = size.cy;
4088 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4091 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4093 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4094 SelectObject(hdc, hOldbmp);
4095 ImageList_Add(dragList, hbmp, 0);
4098 SelectObject(hdc, hOldbmp);
4102 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4104 TRACE("ret=%p\n", dragList);
4112 * Removes all listview items and subitems.
4115 * [I] infoPtr : valid pointer to the listview structure
4121 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr)
4124 HDPA hdpaSubItems = NULL;
4131 /* we do it directly, to avoid notifications */
4132 ranges_clear(infoPtr->selectionRanges);
4133 infoPtr->nSelectionMark = -1;
4134 infoPtr->nFocusedItem = -1;
4135 SetRectEmpty(&infoPtr->rcFocus);
4136 /* But we are supposed to leave nHotItem as is! */
4139 /* send LVN_DELETEALLITEMS notification */
4140 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4142 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4144 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4146 /* send LVN_DELETEITEM notification, if not suppressed */
4147 if (!bSuppress) notify_deleteitem(infoPtr, i);
4148 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4150 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
4151 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4153 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, j);
4154 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4157 DPA_Destroy(hdpaSubItems);
4158 DPA_DeletePtr(infoPtr->hdpaItems, i);
4160 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4161 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4162 infoPtr->nItemCount --;
4165 LISTVIEW_UpdateScroll(infoPtr);
4167 LISTVIEW_InvalidateList(infoPtr);
4174 * Scrolls, and updates the columns, when a column is changing width.
4177 * [I] infoPtr : valid pointer to the listview structure
4178 * [I] nColumn : column to scroll
4179 * [I] dx : amount of scroll, in pixels
4184 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4186 COLUMN_INFO *lpColumnInfo;
4190 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4191 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4192 rcCol = lpColumnInfo->rcHeader;
4193 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4194 rcCol.left = rcCol.right;
4196 /* ajust the other columns */
4197 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4199 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4200 lpColumnInfo->rcHeader.left += dx;
4201 lpColumnInfo->rcHeader.right += dx;
4204 /* do not update screen if not in report mode */
4205 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4207 /* if we have a focus, must first erase the focus rect */
4208 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4210 /* Need to reset the item width when inserting a new column */
4211 infoPtr->nItemWidth += dx;
4213 LISTVIEW_UpdateScroll(infoPtr);
4215 /* scroll to cover the deleted column, and invalidate for redraw */
4216 rcOld = infoPtr->rcList;
4217 rcOld.left = rcCol.left;
4218 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4220 /* we can restore focus now */
4221 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4226 * Removes a column from the listview control.
4229 * [I] infoPtr : valid pointer to the listview structure
4230 * [I] nColumn : column index
4236 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4240 TRACE("nColumn=%d\n", nColumn);
4242 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4243 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4245 /* While the MSDN specifically says that column zero should not be deleted,
4246 what actually happens is that the column itself is deleted but no items or subitems
4250 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4252 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4255 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4256 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4258 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4260 SUBITEM_INFO *lpSubItem, *lpDelItem;
4262 INT nItem, nSubItem, i;
4264 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4266 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
4269 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4271 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
4272 if (lpSubItem->iSubItem == nColumn)
4275 lpDelItem = lpSubItem;
4277 else if (lpSubItem->iSubItem > nColumn)
4279 lpSubItem->iSubItem--;
4283 /* if we found our subitem, zapp it */
4287 if (is_textW(lpDelItem->hdr.pszText))
4288 Free(lpDelItem->hdr.pszText);
4293 /* free dpa memory */
4294 DPA_DeletePtr(hdpaSubItems, nSubItem);
4299 /* update the other column info */
4300 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4301 LISTVIEW_InvalidateList(infoPtr);
4303 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4310 * Invalidates the listview after an item's insertion or deletion.
4313 * [I] infoPtr : valid pointer to the listview structure
4314 * [I] nItem : item index
4315 * [I] dir : -1 if deleting, 1 if inserting
4320 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4322 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4323 INT nPerCol, nItemCol, nItemRow;
4327 /* if we don't refresh, what's the point of scrolling? */
4328 if (!is_redrawing(infoPtr)) return;
4330 assert (abs(dir) == 1);
4332 /* arrange icons if autoarrange is on */
4333 if (is_autoarrange(infoPtr))
4335 BOOL arrange = TRUE;
4336 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4337 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4338 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4341 /* scrollbars need updating */
4342 LISTVIEW_UpdateScroll(infoPtr);
4344 /* figure out the item's position */
4345 if (uView == LVS_REPORT)
4346 nPerCol = infoPtr->nItemCount + 1;
4347 else if (uView == LVS_LIST)
4348 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4349 else /* LVS_ICON, or LVS_SMALLICON */
4352 nItemCol = nItem / nPerCol;
4353 nItemRow = nItem % nPerCol;
4354 LISTVIEW_GetOrigin(infoPtr, &Origin);
4356 /* move the items below up a slot */
4357 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4358 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4359 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4360 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4361 OffsetRect(&rcScroll, Origin.x, Origin.y);
4362 TRACE("rcScroll=%s, dx=%d\n", debugrect(&rcScroll), dir * infoPtr->nItemHeight);
4363 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4365 TRACE("Scrolling rcScroll=%s, rcList=%s\n", debugrect(&rcScroll), debugrect(&infoPtr->rcList));
4366 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4367 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4370 /* report has only that column, so we're done */
4371 if (uView == LVS_REPORT) return;
4373 /* now for LISTs, we have to deal with the columns to the right */
4374 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4376 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4377 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4378 OffsetRect(&rcScroll, Origin.x, Origin.y);
4379 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4380 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4381 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4386 * Removes an item from the listview control.
4389 * [I] infoPtr : valid pointer to the listview structure
4390 * [I] nItem : item index
4396 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4398 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4401 TRACE("(nItem=%d)\n", nItem);
4403 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4405 /* remove selection, and focus */
4407 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4408 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4410 /* send LVN_DELETEITEM notification. */
4411 notify_deleteitem(infoPtr, nItem);
4413 /* we need to do this here, because we'll be deleting stuff */
4414 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4415 LISTVIEW_InvalidateItem(infoPtr, nItem);
4417 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4423 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4424 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4426 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, i);
4427 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4430 DPA_Destroy(hdpaSubItems);
4433 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4435 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4436 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4439 infoPtr->nItemCount--;
4440 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4442 /* now is the invalidation fun */
4443 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4450 * Callback implementation for editlabel control
4453 * [I] infoPtr : valid pointer to the listview structure
4454 * [I] pszText : modified text
4455 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4461 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4463 NMLVDISPINFOW dispInfo;
4465 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4467 ZeroMemory(&dispInfo, sizeof(dispInfo));
4468 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
4469 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4470 dispInfo.item.iSubItem = 0;
4471 dispInfo.item.stateMask = ~0;
4472 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4473 /* add the text from the edit in */
4474 dispInfo.item.mask |= LVIF_TEXT;
4475 dispInfo.item.pszText = pszText;
4476 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4478 /* Do we need to update the Item Text */
4479 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4480 if (!pszText) return TRUE;
4482 ZeroMemory(&dispInfo, sizeof(dispInfo));
4483 dispInfo.item.mask = LVIF_TEXT;
4484 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4485 dispInfo.item.iSubItem = 0;
4486 dispInfo.item.pszText = pszText;
4487 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4488 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4493 * Begin in place editing of specified list view item
4496 * [I] infoPtr : valid pointer to the listview structure
4497 * [I] nItem : item index
4498 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4504 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4506 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4507 NMLVDISPINFOW dispInfo;
4510 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4512 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4513 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4515 infoPtr->nEditLabelItem = nItem;
4517 /* Is the EditBox still there, if so remove it */
4518 if(infoPtr->hwndEdit != 0)
4520 SetFocus(infoPtr->hwndSelf);
4521 infoPtr->hwndEdit = 0;
4524 LISTVIEW_SetSelection(infoPtr, nItem);
4525 LISTVIEW_SetItemFocus(infoPtr, nItem);
4526 LISTVIEW_InvalidateItem(infoPtr, nItem);
4528 rect.left = LVIR_LABEL;
4529 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4531 ZeroMemory(&dispInfo, sizeof(dispInfo));
4532 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4533 dispInfo.item.iItem = nItem;
4534 dispInfo.item.iSubItem = 0;
4535 dispInfo.item.stateMask = ~0;
4536 dispInfo.item.pszText = szDispText;
4537 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4538 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4540 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4541 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4542 if (!infoPtr->hwndEdit) return 0;
4544 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4546 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4547 infoPtr->hwndEdit = 0;
4551 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4552 SetFocus(infoPtr->hwndEdit);
4553 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4554 return infoPtr->hwndEdit;
4560 * Ensures the specified item is visible, scrolling into view if necessary.
4563 * [I] infoPtr : valid pointer to the listview structure
4564 * [I] nItem : item index
4565 * [I] bPartial : partially or entirely visible
4571 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4573 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4574 INT nScrollPosHeight = 0;
4575 INT nScrollPosWidth = 0;
4576 INT nHorzAdjust = 0;
4577 INT nVertAdjust = 0;
4580 RECT rcItem, rcTemp;
4582 rcItem.left = LVIR_BOUNDS;
4583 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4585 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4587 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4589 /* scroll left/right, but in LVS_REPORT mode */
4590 if (uView == LVS_LIST)
4591 nScrollPosWidth = infoPtr->nItemWidth;
4592 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4593 nScrollPosWidth = 1;
4595 if (rcItem.left < infoPtr->rcList.left)
4598 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
4603 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
4607 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
4609 /* scroll up/down, but not in LVS_LIST mode */
4610 if (uView == LVS_REPORT)
4611 nScrollPosHeight = infoPtr->nItemHeight;
4612 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
4613 nScrollPosHeight = 1;
4615 if (rcItem.top < infoPtr->rcList.top)
4618 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
4623 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
4627 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
4629 if (nScrollPosWidth)
4631 INT diff = nHorzDiff / nScrollPosWidth;
4632 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
4633 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
4636 if (nScrollPosHeight)
4638 INT diff = nVertDiff / nScrollPosHeight;
4639 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
4640 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
4648 * Searches for an item with specific characteristics.
4651 * [I] hwnd : window handle
4652 * [I] nStart : base item index
4653 * [I] lpFindInfo : item information to look for
4656 * SUCCESS : index of item
4659 static INT LISTVIEW_FindItemW(LISTVIEW_INFO *infoPtr, INT nStart,
4660 const LVFINDINFOW *lpFindInfo)
4662 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4663 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4664 BOOL bWrap = FALSE, bNearest = FALSE;
4665 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
4666 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
4667 POINT Position, Destination;
4670 if (!lpFindInfo || nItem < 0) return -1;
4673 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
4675 lvItem.mask |= LVIF_TEXT;
4676 lvItem.pszText = szDispText;
4677 lvItem.cchTextMax = DISP_TEXT_SIZE;
4680 if (lpFindInfo->flags & LVFI_WRAP)
4683 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
4684 (uView == LVS_ICON || uView ==LVS_SMALLICON))
4689 LISTVIEW_GetOrigin(infoPtr, &Origin);
4690 Destination.x = lpFindInfo->pt.x - Origin.x;
4691 Destination.y = lpFindInfo->pt.y - Origin.y;
4692 switch(lpFindInfo->vkDirection)
4694 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
4695 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
4696 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
4697 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
4698 case VK_HOME: Destination.x = Destination.y = 0; break;
4699 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4700 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4702 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
4703 Destination.x = rcArea.right;
4704 Destination.y = rcArea.bottom;
4706 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
4711 /* if LVFI_PARAM is specified, all other flags are ignored */
4712 if (lpFindInfo->flags & LVFI_PARAM)
4714 lvItem.mask |= LVIF_PARAM;
4716 lvItem.mask &= ~LVIF_TEXT;
4720 for (; nItem < nLast; nItem++)
4722 lvItem.iItem = nItem;
4723 lvItem.iSubItem = 0;
4724 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
4726 if (lvItem.mask & LVIF_PARAM)
4728 if (lpFindInfo->lParam == lvItem.lParam)
4734 if (lvItem.mask & LVIF_TEXT)
4736 if (lpFindInfo->flags & LVFI_PARTIAL)
4738 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
4742 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
4746 if (!bNearest) return nItem;
4748 /* This is very inefficient. To do a good job here,
4749 * we need a sorted array of (x,y) item positions */
4750 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
4752 /* compute the distance^2 to the destination */
4753 xdist = Destination.x - Position.x;
4754 ydist = Destination.y - Position.y;
4755 dist = xdist * xdist + ydist * ydist;
4757 /* remember the distance, and item if it's closer */
4761 nNearestItem = nItem;
4768 nLast = min(nStart + 1, infoPtr->nItemCount);
4773 return nNearestItem;
4778 * Searches for an item with specific characteristics.
4781 * [I] hwnd : window handle
4782 * [I] nStart : base item index
4783 * [I] lpFindInfo : item information to look for
4786 * SUCCESS : index of item
4789 static INT LISTVIEW_FindItemA(LISTVIEW_INFO *infoPtr, INT nStart,
4790 const LVFINDINFOA *lpFindInfo)
4792 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
4796 memcpy(&fiw, lpFindInfo, sizeof(fiw));
4797 if (hasText) fiw.psz = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
4798 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
4799 if (hasText) textfreeT((LPWSTR)fiw.psz, FALSE);
4805 * Retrieves the background image of the listview control.
4808 * [I] infoPtr : valid pointer to the listview structure
4809 * [O] lpBkImage : background image attributes
4815 /* static BOOL LISTVIEW_GetBkImage(LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
4817 /* FIXME (listview, "empty stub!\n"); */
4823 * Retrieves column attributes.
4826 * [I] infoPtr : valid pointer to the listview structure
4827 * [I] nColumn : column index
4828 * [IO] lpColumn : column information
4829 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
4830 * otherwise it is in fact a LPLVCOLUMNA
4836 static BOOL LISTVIEW_GetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
4838 COLUMN_INFO *lpColumnInfo;
4841 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4842 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
4844 /* initialize memory */
4845 ZeroMemory(&hdi, sizeof(hdi));
4847 if (lpColumn->mask & LVCF_TEXT)
4849 hdi.mask |= HDI_TEXT;
4850 hdi.pszText = lpColumn->pszText;
4851 hdi.cchTextMax = lpColumn->cchTextMax;
4854 if (lpColumn->mask & LVCF_IMAGE)
4855 hdi.mask |= HDI_IMAGE;
4857 if (lpColumn->mask & LVCF_ORDER)
4858 hdi.mask |= HDI_ORDER;
4860 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
4862 if (lpColumn->mask & LVCF_FMT)
4863 lpColumn->fmt = lpColumnInfo->fmt;
4865 if (lpColumn->mask & LVCF_WIDTH)
4866 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
4868 if (lpColumn->mask & LVCF_IMAGE)
4869 lpColumn->iImage = hdi.iImage;
4871 if (lpColumn->mask & LVCF_ORDER)
4872 lpColumn->iOrder = hdi.iOrder;
4878 static BOOL LISTVIEW_GetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
4885 /* FIXME: little hack */
4886 for (i = 0; i < iCount; i++)
4894 * Retrieves the column width.
4897 * [I] infoPtr : valid pointer to the listview structure
4898 * [I] int : column index
4901 * SUCCESS : column width
4904 static INT LISTVIEW_GetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn)
4906 INT nColumnWidth = 0;
4909 TRACE("nColumn=%d\n", nColumn);
4911 /* we have a 'column' in LIST and REPORT mode only */
4912 switch(infoPtr->dwStyle & LVS_TYPEMASK)
4915 nColumnWidth = infoPtr->nItemWidth;
4918 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
4919 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
4920 nColumnWidth = rcHeader.right - rcHeader.left;
4924 TRACE("nColumnWidth=%d\n", nColumnWidth);
4925 return nColumnWidth;
4930 * In list or report display mode, retrieves the number of items that can fit
4931 * vertically in the visible area. In icon or small icon display mode,
4932 * retrieves the total number of visible items.
4935 * [I] infoPtr : valid pointer to the listview structure
4938 * Number of fully visible items.
4940 static INT LISTVIEW_GetCountPerPage(LISTVIEW_INFO *infoPtr)
4942 switch (infoPtr->dwStyle & LVS_TYPEMASK)
4946 return infoPtr->nItemCount;
4948 return LISTVIEW_GetCountPerColumn(infoPtr);
4950 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
4958 * Retrieves an image list handle.
4961 * [I] infoPtr : valid pointer to the listview structure
4962 * [I] nImageList : image list identifier
4965 * SUCCESS : image list handle
4968 static HIMAGELIST LISTVIEW_GetImageList(LISTVIEW_INFO *infoPtr, INT nImageList)
4972 case LVSIL_NORMAL: return infoPtr->himlNormal;
4973 case LVSIL_SMALL: return infoPtr->himlSmall;
4974 case LVSIL_STATE: return infoPtr->himlState;
4979 /* LISTVIEW_GetISearchString */
4983 * Retrieves item attributes.
4986 * [I] hwnd : window handle
4987 * [IO] lpLVItem : item info
4988 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
4989 * if FALSE, the lpLVItem is a LPLVITEMA.
4992 * This is the internal 'GetItem' interface -- it tries to
4993 * be smart, and avoids text copies, if possible, by modifing
4994 * lpLVItem->pszText to point to the text string. Please note
4995 * that this is not always possible (e.g. OWNERDATA), so on
4996 * entry you *must* supply valid values for pszText, and cchTextMax.
4997 * The only difference to the documented interface is that upon
4998 * return, you should use *only* the lpLVItem->pszText, rather than
4999 * the buffer pointer you provided on input. Most code already does
5000 * that, so it's not a problem.
5001 * For the two cases when the text must be copied (that is,
5002 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5008 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5010 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5011 NMLVDISPINFOW dispInfo;
5017 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5019 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5022 if (lpLVItem->mask == 0) return TRUE;
5024 /* make a local copy */
5025 isubitem = lpLVItem->iSubItem;
5027 /* a quick optimization if all we're asked is the focus state
5028 * these queries are worth optimising since they are common,
5029 * and can be answered in constant time, without the heavy accesses */
5030 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5031 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5033 lpLVItem->state = 0;
5034 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5035 lpLVItem->state |= LVIS_FOCUSED;
5039 ZeroMemory(&dispInfo, sizeof(dispInfo));
5041 /* if the app stores all the data, handle it separately */
5042 if (infoPtr->dwStyle & LVS_OWNERDATA)
5044 dispInfo.item.state = 0;
5046 /* apprently, we should not callback for lParam in LVS_OWNERDATA */
5047 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5049 /* NOTE: copy only fields which we _know_ are initialized, some apps
5050 * depend on the uninitialized fields being 0 */
5051 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5052 dispInfo.item.iItem = lpLVItem->iItem;
5053 dispInfo.item.iSubItem = isubitem;
5054 if (lpLVItem->mask & LVIF_TEXT)
5056 dispInfo.item.pszText = lpLVItem->pszText;
5057 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5059 if (lpLVItem->mask & LVIF_STATE)
5060 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5061 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5062 dispInfo.item.stateMask = lpLVItem->stateMask;
5063 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5065 /* full size structure expected - _WIN32IE >= 0x560 */
5066 *lpLVItem = dispInfo.item;
5068 else if (lpLVItem->mask & LVIF_INDENT)
5070 /* indent member expected - _WIN32IE >= 0x300 */
5071 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5075 /* minimal structure expected */
5076 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5078 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5081 /* make sure lParam is zeroed out */
5082 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5084 /* we store only a little state, so if we're not asked, we're done */
5085 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5087 /* if focus is handled by us, report it */
5088 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5090 lpLVItem->state &= ~LVIS_FOCUSED;
5091 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5092 lpLVItem->state |= LVIS_FOCUSED;
5095 /* and do the same for selection, if we handle it */
5096 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5098 lpLVItem->state &= ~LVIS_SELECTED;
5099 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5100 lpLVItem->state |= LVIS_SELECTED;
5106 /* find the item and subitem structures before we proceed */
5107 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5108 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
5113 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5114 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5117 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5122 pItemHdr = &lpItem->hdr;
5124 /* Do we need to query the state from the app? */
5125 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5127 dispInfo.item.mask |= LVIF_STATE;
5128 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5131 /* Do we need to enquire about the image? */
5132 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5133 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5135 dispInfo.item.mask |= LVIF_IMAGE;
5136 dispInfo.item.iImage = I_IMAGECALLBACK;
5139 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5140 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
5142 dispInfo.item.mask |= LVIF_TEXT;
5143 dispInfo.item.pszText = lpLVItem->pszText;
5144 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5145 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5146 *dispInfo.item.pszText = '\0';
5149 /* If we don't have all the requested info, query the application */
5150 if (dispInfo.item.mask != 0)
5152 dispInfo.item.iItem = lpLVItem->iItem;
5153 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5154 dispInfo.item.lParam = lpItem->lParam;
5155 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5156 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5159 /* we should not store values for subitems */
5160 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5162 /* Now, handle the iImage field */
5163 if (dispInfo.item.mask & LVIF_IMAGE)
5165 lpLVItem->iImage = dispInfo.item.iImage;
5166 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5167 pItemHdr->iImage = dispInfo.item.iImage;
5169 else if (lpLVItem->mask & LVIF_IMAGE)
5171 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5172 lpLVItem->iImage = pItemHdr->iImage;
5174 lpLVItem->iImage = 0;
5177 /* The pszText field */
5178 if (dispInfo.item.mask & LVIF_TEXT)
5180 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5181 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5183 lpLVItem->pszText = dispInfo.item.pszText;
5185 else if (lpLVItem->mask & LVIF_TEXT)
5187 if (isW) lpLVItem->pszText = pItemHdr->pszText;
5188 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5191 /* if this is a subitem, we're done */
5192 if (isubitem) return TRUE;
5194 /* Next is the lParam field */
5195 if (dispInfo.item.mask & LVIF_PARAM)
5197 lpLVItem->lParam = dispInfo.item.lParam;
5198 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5199 lpItem->lParam = dispInfo.item.lParam;
5201 else if (lpLVItem->mask & LVIF_PARAM)
5202 lpLVItem->lParam = lpItem->lParam;
5204 /* ... the state field (this one is different due to uCallbackmask) */
5205 if (lpLVItem->mask & LVIF_STATE)
5207 lpLVItem->state = lpItem->state;
5208 if (dispInfo.item.mask & LVIF_STATE)
5210 lpLVItem->state &= ~dispInfo.item.stateMask;
5211 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5213 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5215 lpLVItem->state &= ~LVIS_FOCUSED;
5216 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5217 lpLVItem->state |= LVIS_FOCUSED;
5219 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5221 lpLVItem->state &= ~LVIS_SELECTED;
5222 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5223 lpLVItem->state |= LVIS_SELECTED;
5227 /* and last, but not least, the indent field */
5228 if (lpLVItem->mask & LVIF_INDENT)
5229 lpLVItem->iIndent = lpItem->iIndent;
5236 * Retrieves item attributes.
5239 * [I] hwnd : window handle
5240 * [IO] lpLVItem : item info
5241 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5242 * if FALSE, the lpLVItem is a LPLVITEMA.
5245 * This is the external 'GetItem' interface -- it properly copies
5246 * the text in the provided buffer.
5252 static BOOL LISTVIEW_GetItemExtT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5257 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5260 pszText = lpLVItem->pszText;
5261 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5262 if (bResult && lpLVItem->pszText != pszText)
5263 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5264 lpLVItem->pszText = pszText;
5272 * Retrieves the position (upper-left) of the listview control item.
5273 * Note that for LVS_ICON style, the upper-left is that of the icon
5274 * and not the bounding box.
5277 * [I] infoPtr : valid pointer to the listview structure
5278 * [I] nItem : item index
5279 * [O] lpptPosition : coordinate information
5285 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5287 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5290 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5292 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5294 LISTVIEW_GetOrigin(infoPtr, &Origin);
5295 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5297 if (uView == LVS_ICON)
5299 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5300 lpptPosition->y += ICON_TOP_PADDING;
5302 lpptPosition->x += Origin.x;
5303 lpptPosition->y += Origin.y;
5305 TRACE (" lpptPosition=%s\n", debugpoint(lpptPosition));
5312 * Retrieves the bounding rectangle for a listview control item.
5315 * [I] infoPtr : valid pointer to the listview structure
5316 * [I] nItem : item index
5317 * [IO] lprc : bounding rectangle coordinates
5318 * lprc->left specifies the portion of the item for which the bounding
5319 * rectangle will be retrieved.
5321 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5322 * including the icon and label.
5325 * * Experiment shows that native control returns:
5326 * * width = min (48, length of text line)
5327 * * .left = position.x - (width - iconsize.cx)/2
5328 * * .right = .left + width
5329 * * height = #lines of text * ntmHeight + icon height + 8
5330 * * .top = position.y - 2
5331 * * .bottom = .top + height
5332 * * separation between items .y = itemSpacing.cy - height
5333 * * .x = itemSpacing.cx - width
5334 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5337 * * Experiment shows that native control returns:
5338 * * width = iconSize.cx + 16
5339 * * .left = position.x - (width - iconsize.cx)/2
5340 * * .right = .left + width
5341 * * height = iconSize.cy + 4
5342 * * .top = position.y - 2
5343 * * .bottom = .top + height
5344 * * separation between items .y = itemSpacing.cy - height
5345 * * .x = itemSpacing.cx - width
5346 * LVIR_LABEL Returns the bounding rectangle of the item text.
5349 * * Experiment shows that native control returns:
5350 * * width = text length
5351 * * .left = position.x - width/2
5352 * * .right = .left + width
5353 * * height = ntmH * linecount + 2
5354 * * .top = position.y + iconSize.cy + 6
5355 * * .bottom = .top + height
5356 * * separation between items .y = itemSpacing.cy - height
5357 * * .x = itemSpacing.cx - width
5358 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5359 * rectangles, but excludes columns in report view.
5366 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5367 * upon whether the window has the focus currently and on whether the item
5368 * is the one with the focus. Ensure that the control's record of which
5369 * item has the focus agrees with the items' records.
5371 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5373 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5374 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5375 BOOL doLabel = TRUE, oversizedBox = FALSE;
5376 POINT Position, Origin;
5380 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5382 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5384 LISTVIEW_GetOrigin(infoPtr, &Origin);
5385 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5387 /* Be smart and try to figure out the minimum we have to do */
5388 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5389 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5390 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5391 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5392 oversizedBox = TRUE;
5394 /* get what we need from the item before hand, so we make
5395 * only one request. This can speed up things, if data
5396 * is stored on the app side */
5398 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5399 if (doLabel) lvItem.mask |= LVIF_TEXT;
5400 lvItem.iItem = nItem;
5401 lvItem.iSubItem = 0;
5402 lvItem.pszText = szDispText;
5403 lvItem.cchTextMax = DISP_TEXT_SIZE;
5404 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5405 /* we got the state already up, simulate it here, to avoid a reget */
5406 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5408 lvItem.mask |= LVIF_STATE;
5409 lvItem.stateMask = LVIS_FOCUSED;
5410 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5413 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5414 lprc->left = LVIR_BOUNDS;
5418 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5422 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, lprc);
5426 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5429 case LVIR_SELECTBOUNDS:
5430 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, &label_rect);
5431 UnionRect(lprc, lprc, &label_rect);
5435 WARN("Unknown value: %ld\n", lprc->left);
5439 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5441 TRACE(" rect=%s\n", debugrect(lprc));
5448 * Retrieves the spacing between listview control items.
5451 * [I] infoPtr : valid pointer to the listview structure
5452 * [IO] lprc : rectangle to receive the output
5453 * on input, lprc->top = nSubItem
5454 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5456 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5457 * not only those of the first column.
5458 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5464 static BOOL LISTVIEW_GetSubItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5469 if (!lprc) return FALSE;
5471 TRACE("(nItem=%d, nSubItem=%ld)\n", nItem, lprc->top);
5472 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5474 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5476 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5478 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5481 lvItem.iItem = nItem;
5482 lvItem.iSubItem = lprc->top;
5484 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5488 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5493 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5497 ERR("Unknown bounds=%ld\n", lprc->left);
5501 OffsetRect(lprc, Position.x, Position.y);
5508 * Retrieves the width of a label.
5511 * [I] infoPtr : valid pointer to the listview structure
5514 * SUCCESS : string width (in pixels)
5517 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *infoPtr, INT nItem)
5519 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5522 TRACE("(nItem=%d)\n", nItem);
5524 lvItem.mask = LVIF_TEXT;
5525 lvItem.iItem = nItem;
5526 lvItem.iSubItem = 0;
5527 lvItem.pszText = szDispText;
5528 lvItem.cchTextMax = DISP_TEXT_SIZE;
5529 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5531 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5536 * Retrieves the spacing between listview control items.
5539 * [I] infoPtr : valid pointer to the listview structure
5540 * [I] bSmall : flag for small or large icon
5543 * Horizontal + vertical spacing
5545 static LONG LISTVIEW_GetItemSpacing(LISTVIEW_INFO *infoPtr, BOOL bSmall)
5551 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5555 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5556 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5558 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5565 * Retrieves the state of a listview control item.
5568 * [I] infoPtr : valid pointer to the listview structure
5569 * [I] nItem : item index
5570 * [I] uMask : state mask
5573 * State specified by the mask.
5575 static UINT LISTVIEW_GetItemState(LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5579 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5581 lvItem.iItem = nItem;
5582 lvItem.iSubItem = 0;
5583 lvItem.mask = LVIF_STATE;
5584 lvItem.stateMask = uMask;
5585 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5587 return lvItem.state & uMask;
5592 * Retrieves the text of a listview control item or subitem.
5595 * [I] hwnd : window handle
5596 * [I] nItem : item index
5597 * [IO] lpLVItem : item information
5598 * [I] isW : TRUE if lpLVItem is Unicode
5601 * SUCCESS : string length
5604 static INT LISTVIEW_GetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
5606 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5608 lpLVItem->mask = LVIF_TEXT;
5609 lpLVItem->iItem = nItem;
5610 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
5612 return textlenT(lpLVItem->pszText, isW);
5617 * Searches for an item based on properties + relationships.
5620 * [I] infoPtr : valid pointer to the listview structure
5621 * [I] nItem : item index
5622 * [I] uFlags : relationship flag
5625 * SUCCESS : item index
5628 static INT LISTVIEW_GetNextItem(LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
5630 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5632 LVFINDINFOW lvFindInfo;
5633 INT nCountPerColumn;
5637 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
5638 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
5640 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
5642 if (uFlags & LVNI_CUT)
5645 if (uFlags & LVNI_DROPHILITED)
5646 uMask |= LVIS_DROPHILITED;
5648 if (uFlags & LVNI_FOCUSED)
5649 uMask |= LVIS_FOCUSED;
5651 if (uFlags & LVNI_SELECTED)
5652 uMask |= LVIS_SELECTED;
5654 /* if we're asked for the focused item, that's only one,
5655 * so it's worth optimizing */
5656 if (uFlags & LVNI_FOCUSED)
5658 if (!(LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) == uMask) return -1;
5659 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
5662 if (uFlags & LVNI_ABOVE)
5664 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5669 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5675 /* Special case for autoarrange - move 'til the top of a list */
5676 if (is_autoarrange(infoPtr))
5678 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5679 while (nItem - nCountPerRow >= 0)
5681 nItem -= nCountPerRow;
5682 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5687 lvFindInfo.flags = LVFI_NEARESTXY;
5688 lvFindInfo.vkDirection = VK_UP;
5689 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5690 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5692 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5697 else if (uFlags & LVNI_BELOW)
5699 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5701 while (nItem < infoPtr->nItemCount)
5704 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5710 /* Special case for autoarrange - move 'til the bottom of a list */
5711 if (is_autoarrange(infoPtr))
5713 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5714 while (nItem + nCountPerRow < infoPtr->nItemCount )
5716 nItem += nCountPerRow;
5717 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5722 lvFindInfo.flags = LVFI_NEARESTXY;
5723 lvFindInfo.vkDirection = VK_DOWN;
5724 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5725 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5727 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5732 else if (uFlags & LVNI_TOLEFT)
5734 if (uView == LVS_LIST)
5736 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5737 while (nItem - nCountPerColumn >= 0)
5739 nItem -= nCountPerColumn;
5740 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5744 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5746 /* Special case for autoarrange - move 'ti the beginning of a row */
5747 if (is_autoarrange(infoPtr))
5749 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5750 while (nItem % nCountPerRow > 0)
5753 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5758 lvFindInfo.flags = LVFI_NEARESTXY;
5759 lvFindInfo.vkDirection = VK_LEFT;
5760 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5761 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5763 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5768 else if (uFlags & LVNI_TORIGHT)
5770 if (uView == LVS_LIST)
5772 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5773 while (nItem + nCountPerColumn < infoPtr->nItemCount)
5775 nItem += nCountPerColumn;
5776 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5780 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5782 /* Special case for autoarrange - move 'til the end of a row */
5783 if (is_autoarrange(infoPtr))
5785 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5786 while (nItem % nCountPerRow < nCountPerRow - 1 )
5789 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5794 lvFindInfo.flags = LVFI_NEARESTXY;
5795 lvFindInfo.vkDirection = VK_RIGHT;
5796 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5797 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5799 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5808 /* search by index */
5809 for (i = nItem; i < infoPtr->nItemCount; i++)
5811 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
5819 /* LISTVIEW_GetNumberOfWorkAreas */
5823 * Retrieves the origin coordinates when in icon or small icon display mode.
5826 * [I] infoPtr : valid pointer to the listview structure
5827 * [O] lpptOrigin : coordinate information
5832 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
5834 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5835 INT nHorzPos = 0, nVertPos = 0;
5836 SCROLLINFO scrollInfo;
5838 scrollInfo.cbSize = sizeof(SCROLLINFO);
5839 scrollInfo.fMask = SIF_POS;
5841 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
5842 nHorzPos = scrollInfo.nPos;
5843 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
5844 nVertPos = scrollInfo.nPos;
5846 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
5848 lpptOrigin->x = infoPtr->rcList.left;
5849 lpptOrigin->y = infoPtr->rcList.top;
5850 if (uView == LVS_LIST)
5851 nHorzPos *= infoPtr->nItemWidth;
5852 else if (uView == LVS_REPORT)
5853 nVertPos *= infoPtr->nItemHeight;
5855 lpptOrigin->x -= nHorzPos;
5856 lpptOrigin->y -= nVertPos;
5858 TRACE(" origin=%s\n", debugpoint(lpptOrigin));
5863 * Retrieves the width of a string.
5866 * [I] hwnd : window handle
5867 * [I] lpszText : text string to process
5868 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
5871 * SUCCESS : string width (in pixels)
5874 static INT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
5879 if (is_textT(lpszText, isW))
5881 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
5882 HDC hdc = GetDC(infoPtr->hwndSelf);
5883 HFONT hOldFont = SelectObject(hdc, hFont);
5886 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
5888 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
5889 SelectObject(hdc, hOldFont);
5890 ReleaseDC(infoPtr->hwndSelf, hdc);
5892 return stringSize.cx;
5897 * Determines which listview item is located at the specified position.
5900 * [I] infoPtr : valid pointer to the listview structure
5901 * [IO] lpht : hit test information
5902 * [I] subitem : fill out iSubItem.
5903 * [I] select : return the index only if the hit selects the item
5906 * (mm 20001022): We must not allow iSubItem to be touched, for
5907 * an app might pass only a structure with space up to iItem!
5908 * (MS Office 97 does that for instance in the file open dialog)
5911 * SUCCESS : item index
5914 static INT LISTVIEW_HitTest(LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
5916 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5917 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5918 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
5919 POINT Origin, Position, opt;
5924 TRACE("(pt=%s, subitem=%d, select=%d)\n", debugpoint(&lpht->pt), subitem, select);
5928 if (subitem) lpht->iSubItem = 0;
5930 if (infoPtr->rcList.left > lpht->pt.x)
5931 lpht->flags |= LVHT_TOLEFT;
5932 else if (infoPtr->rcList.right < lpht->pt.x)
5933 lpht->flags |= LVHT_TORIGHT;
5935 if (infoPtr->rcList.top > lpht->pt.y)
5936 lpht->flags |= LVHT_ABOVE;
5937 else if (infoPtr->rcList.bottom < lpht->pt.y)
5938 lpht->flags |= LVHT_BELOW;
5940 TRACE("lpht->flags=0x%x\n", lpht->flags);
5941 if (lpht->flags) return -1;
5943 lpht->flags |= LVHT_NOWHERE;
5945 LISTVIEW_GetOrigin(infoPtr, &Origin);
5947 /* first deal with the large items */
5948 rcSearch.left = lpht->pt.x;
5949 rcSearch.top = lpht->pt.y;
5950 rcSearch.right = rcSearch.left + 1;
5951 rcSearch.bottom = rcSearch.top + 1;
5953 iterator_frameditems(&i, infoPtr, &rcSearch);
5954 iterator_next(&i); /* go to first item in the sequence */
5956 iterator_destroy(&i);
5958 TRACE("lpht->iItem=%d\n", iItem);
5959 if (iItem == -1) return -1;
5961 lvItem.mask = LVIF_STATE | LVIF_TEXT;
5962 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5963 lvItem.stateMask = LVIS_STATEIMAGEMASK;
5964 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
5965 lvItem.iItem = iItem;
5966 lvItem.iSubItem = 0;
5967 lvItem.pszText = szDispText;
5968 lvItem.cchTextMax = DISP_TEXT_SIZE;
5969 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
5970 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
5972 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
5973 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
5974 opt.x = lpht->pt.x - Position.x - Origin.x;
5975 opt.y = lpht->pt.y - Position.y - Origin.y;
5977 if (uView == LVS_REPORT)
5980 UnionRect(&rcBounds, &rcIcon, &rcLabel);
5981 TRACE("rcBounds=%s\n", debugrect(&rcBounds));
5982 if (!PtInRect(&rcBounds, opt)) return -1;
5984 if (PtInRect(&rcIcon, opt))
5985 lpht->flags |= LVHT_ONITEMICON;
5986 else if (PtInRect(&rcLabel, opt))
5987 lpht->flags |= LVHT_ONITEMLABEL;
5988 else if (infoPtr->himlState && ((lvItem.state & LVIS_STATEIMAGEMASK) >> 12) && PtInRect(&rcState, opt))
5989 lpht->flags |= LVHT_ONITEMSTATEICON;
5990 if (lpht->flags & LVHT_ONITEM)
5991 lpht->flags &= ~LVHT_NOWHERE;
5993 TRACE("lpht->flags=0x%x\n", lpht->flags);
5994 if (uView == LVS_REPORT && subitem)
5998 rcBounds.right = rcBounds.left;
5999 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6001 rcBounds.left = rcBounds.right;
6002 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
6003 if (PtInRect(&rcBounds, opt))
6011 if (select && !(uView == LVS_REPORT &&
6012 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6013 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6015 if (uView == LVS_REPORT)
6017 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6018 UnionRect(&rcBounds, &rcBounds, &rcState);
6020 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6022 return lpht->iItem = iItem;
6026 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
6027 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
6028 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
6029 and not during the processing of a LVM_SORTITEMS message. Applications should provide
6030 their own sort proc. when sending LVM_SORTITEMS.
6033 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
6035 LVS_SORTXXX must be specified,
6036 LVS_OWNERDRAW is not set,
6037 <item>.pszText is not LPSTR_TEXTCALLBACK.
6039 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
6040 are sorted based on item text..."
6042 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
6044 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
6045 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
6046 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
6048 /* if we're sorting descending, negate the return value */
6049 return (((LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
6054 * Inserts a new item in the listview control.
6057 * [I] infoPtr : valid pointer to the listview structure
6058 * [I] lpLVItem : item information
6059 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6062 * SUCCESS : new item index
6065 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6067 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6072 BOOL is_sorted, has_changed;
6075 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6077 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6079 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6080 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iSubItem) return -1;
6082 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6084 if (!(lpItem = (ITEM_INFO *)Alloc(sizeof(ITEM_INFO)))) return -1;
6086 /* insert item in listview control data structure */
6087 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6088 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6090 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6091 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6093 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
6094 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6095 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6096 if (nItem == -1) goto fail;
6097 infoPtr->nItemCount++;
6099 /* shift indices first so they don't get tangled */
6100 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6102 /* set the item attributes */
6103 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6105 /* full size structure expected - _WIN32IE >= 0x560 */
6108 else if (lpLVItem->mask & LVIF_INDENT)
6110 /* indent member expected - _WIN32IE >= 0x300 */
6111 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6115 /* minimal structure expected */
6116 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6119 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) item.state &= ~LVIS_STATEIMAGEMASK;
6120 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6122 /* if we're sorted, sort the list, and update the index */
6125 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
6126 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
6127 assert(nItem != -1);
6130 /* make room for the position, if we are in the right mode */
6131 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6133 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6135 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6137 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6142 /* send LVN_INSERTITEM notification */
6143 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6145 nmlv.lParam = lpItem->lParam;
6146 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6148 /* align items (set position of each item) */
6149 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6153 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6154 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6156 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6158 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6161 /* now is the invalidation fun */
6162 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6166 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6167 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6168 infoPtr->nItemCount--;
6170 DPA_DeletePtr(hdpaSubItems, 0);
6171 DPA_Destroy (hdpaSubItems);
6178 * Redraws a range of items.
6181 * [I] infoPtr : valid pointer to the listview structure
6182 * [I] nFirst : first item
6183 * [I] nLast : last item
6189 static BOOL LISTVIEW_RedrawItems(LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6193 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6194 max(nFirst, nLast) >= infoPtr->nItemCount)
6197 for (i = nFirst; i <= nLast; i++)
6198 LISTVIEW_InvalidateItem(infoPtr, i);
6205 * Scroll the content of a listview.
6208 * [I] infoPtr : valid pointer to the listview structure
6209 * [I] dx : horizontal scroll amount in pixels
6210 * [I] dy : vertical scroll amount in pixels
6217 * If the control is in report mode (LVS_REPORT) the control can
6218 * be scrolled only in line increments. "dy" will be rounded to the
6219 * nearest number of pixels that are a whole line. Ex: if line height
6220 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6221 * is passed the the scroll will be 0. (per MSDN 7/2002)
6223 * For: (per experimentaion with native control and CSpy ListView)
6224 * LVS_ICON dy=1 = 1 pixel (vertical only)
6226 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6228 * LVS_LIST dx=1 = 1 column (horizontal only)
6229 * but will only scroll 1 column per message
6230 * no matter what the value.
6231 * dy must be 0 or FALSE returned.
6232 * LVS_REPORT dx=1 = 1 pixel
6236 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6238 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6240 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6241 dy /= infoPtr->nItemHeight;
6244 if (dy != 0) return FALSE;
6251 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6252 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6259 * Sets the background color.
6262 * [I] infoPtr : valid pointer to the listview structure
6263 * [I] clrBk : background color
6269 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6271 TRACE("(clrBk=%lx)\n", clrBk);
6273 if(infoPtr->clrBk != clrBk) {
6274 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6275 infoPtr->clrBk = clrBk;
6276 if (clrBk == CLR_NONE)
6277 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6279 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6280 LISTVIEW_InvalidateList(infoPtr);
6286 /* LISTVIEW_SetBkImage */
6288 /*** Helper for {Insert,Set}ColumnT *only* */
6289 static void column_fill_hditem(LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn, const LVCOLUMNW *lpColumn, BOOL isW)
6291 if (lpColumn->mask & LVCF_FMT)
6293 /* format member is valid */
6294 lphdi->mask |= HDI_FORMAT;
6296 /* set text alignment (leftmost column must be left-aligned) */
6297 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6298 lphdi->fmt |= HDF_LEFT;
6299 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6300 lphdi->fmt |= HDF_RIGHT;
6301 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6302 lphdi->fmt |= HDF_CENTER;
6304 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6305 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6307 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6309 lphdi->fmt |= HDF_IMAGE;
6310 lphdi->iImage = I_IMAGECALLBACK;
6314 if (lpColumn->mask & LVCF_WIDTH)
6316 lphdi->mask |= HDI_WIDTH;
6317 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6319 /* make it fill the remainder of the controls width */
6323 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6325 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6326 lphdi->cxy += rcHeader.right - rcHeader.left;
6329 /* retrieve the layout of the header */
6330 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6331 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, debugrect(&rcHeader));
6333 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6336 lphdi->cxy = lpColumn->cx;
6339 if (lpColumn->mask & LVCF_TEXT)
6341 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6342 lphdi->fmt |= HDF_STRING;
6343 lphdi->pszText = lpColumn->pszText;
6344 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6347 if (lpColumn->mask & LVCF_IMAGE)
6349 lphdi->mask |= HDI_IMAGE;
6350 lphdi->iImage = lpColumn->iImage;
6353 if (lpColumn->mask & LVCF_ORDER)
6355 lphdi->mask |= HDI_ORDER;
6356 lphdi->iOrder = lpColumn->iOrder;
6363 * Inserts a new column.
6366 * [I] infoPtr : valid pointer to the listview structure
6367 * [I] nColumn : column index
6368 * [I] lpColumn : column information
6369 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6372 * SUCCESS : new column index
6375 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6376 const LVCOLUMNW *lpColumn, BOOL isW)
6378 COLUMN_INFO *lpColumnInfo;
6382 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6384 if (!lpColumn || nColumn < 0) return -1;
6385 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6387 ZeroMemory(&hdi, sizeof(HDITEMW));
6388 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6390 /* insert item in header control */
6391 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6392 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6393 (WPARAM)nColumn, (LPARAM)&hdi);
6394 if (nNewColumn == -1) return -1;
6395 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6397 /* create our own column info */
6398 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6399 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6401 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6402 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6404 /* now we have to actually adjust the data */
6405 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6407 SUBITEM_INFO *lpSubItem;
6411 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6413 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
6414 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
6416 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
6417 if (lpSubItem->iSubItem >= nNewColumn)
6418 lpSubItem->iSubItem++;
6423 /* make space for the new column */
6424 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6429 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6432 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6440 * Sets the attributes of a header item.
6443 * [I] infoPtr : valid pointer to the listview structure
6444 * [I] nColumn : column index
6445 * [I] lpColumn : column attributes
6446 * [I] isW: if TRUE, the lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6452 static BOOL LISTVIEW_SetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6453 const LVCOLUMNW *lpColumn, BOOL isW)
6455 HDITEMW hdi, hdiget;
6458 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6460 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6462 ZeroMemory(&hdi, sizeof(HDITEMW));
6463 if (lpColumn->mask & LVCF_FMT)
6465 hdi.mask |= HDI_FORMAT;
6466 hdiget.mask = HDI_FORMAT;
6467 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6468 hdi.fmt = hdiget.fmt & HDF_STRING;
6470 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6472 /* set header item attributes */
6473 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6474 if (!bResult) return FALSE;
6476 if (lpColumn->mask & LVCF_FMT)
6478 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6479 int oldFmt = lpColumnInfo->fmt;
6481 lpColumnInfo->fmt = lpColumn->fmt;
6482 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
6484 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6485 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
6494 * Sets the column order array
6497 * [I] infoPtr : valid pointer to the listview structure
6498 * [I] iCount : number of elements in column order array
6499 * [I] lpiArray : pointer to column order array
6505 static BOOL LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
6507 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6518 * Sets the width of a column
6521 * [I] infoPtr : valid pointer to the listview structure
6522 * [I] nColumn : column index
6523 * [I] cx : column width
6529 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
6531 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6532 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6536 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
6538 /* set column width only if in report or list mode */
6539 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6541 /* take care of invalid cx values */
6542 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
6543 else if (uView == LVS_LIST && cx < 1) return FALSE;
6545 /* resize all columns if in LVS_LIST mode */
6546 if(uView == LVS_LIST)
6548 infoPtr->nItemWidth = cx;
6549 LISTVIEW_InvalidateList(infoPtr);
6553 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6555 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
6560 lvItem.mask = LVIF_TEXT;
6562 lvItem.iSubItem = nColumn;
6563 lvItem.pszText = szDispText;
6564 lvItem.cchTextMax = DISP_TEXT_SIZE;
6565 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6567 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6568 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6569 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
6571 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
6572 max_cx += infoPtr->iconSize.cx;
6573 max_cx += TRAILING_LABEL_PADDING;
6576 /* autosize based on listview items width */
6577 if(cx == LVSCW_AUTOSIZE)
6579 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
6581 /* if iCol is the last column make it fill the remainder of the controls width */
6582 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
6587 LISTVIEW_GetOrigin(infoPtr, &Origin);
6588 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
6590 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
6594 /* Despite what the MS docs say, if this is not the last
6595 column, then MS resizes the column to the width of the
6596 largest text string in the column, including headers
6597 and items. This is different from LVSCW_AUTOSIZE in that
6598 LVSCW_AUTOSIZE ignores the header string length. */
6601 /* retrieve header text */
6602 hdi.mask = HDI_TEXT;
6603 hdi.cchTextMax = DISP_TEXT_SIZE;
6604 hdi.pszText = szDispText;
6605 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, (LPARAM)&hdi))
6607 HDC hdc = GetDC(infoPtr->hwndSelf);
6608 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
6611 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
6612 cx = size.cx + TRAILING_HEADER_PADDING;
6613 /* FIXME: Take into account the header image, if one is present */
6614 SelectObject(hdc, old_font);
6615 ReleaseDC(infoPtr->hwndSelf, hdc);
6617 cx = max (cx, max_cx);
6621 if (cx < 0) return FALSE;
6623 /* call header to update the column change */
6624 hdi.mask = HDI_WIDTH;
6626 TRACE("hdi.cxy=%d\n", hdi.cxy);
6627 return Header_SetItemW(infoPtr->hwndHeader, nColumn, (LPARAM)&hdi);
6631 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
6634 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(LISTVIEW_INFO *infoPtr)
6637 HBITMAP hbm_im, hbm_mask, hbm_orig;
6639 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
6640 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
6643 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
6644 ILC_COLOR | ILC_MASK, 2, 2);
6645 hdc_wnd = GetDC(infoPtr->hwndSelf);
6646 hdc = CreateCompatibleDC(hdc_wnd);
6647 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
6648 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
6649 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
6651 rc.left = rc.top = 0;
6652 rc.right = GetSystemMetrics(SM_CXSMICON);
6653 rc.bottom = GetSystemMetrics(SM_CYSMICON);
6655 hbm_orig = SelectObject(hdc, hbm_mask);
6656 FillRect(hdc, &rc, hbr_white);
6657 InflateRect(&rc, -3, -3);
6658 FillRect(hdc, &rc, hbr_black);
6660 SelectObject(hdc, hbm_im);
6661 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
6662 SelectObject(hdc, hbm_orig);
6663 ImageList_Add(himl, hbm_im, hbm_mask);
6665 SelectObject(hdc, hbm_im);
6666 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
6667 SelectObject(hdc, hbm_orig);
6668 ImageList_Add(himl, hbm_im, hbm_mask);
6670 DeleteObject(hbm_mask);
6671 DeleteObject(hbm_im);
6679 * Sets the extended listview style.
6682 * [I] infoPtr : valid pointer to the listview structure
6684 * [I] dwStyle : style
6687 * SUCCESS : previous style
6690 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwStyle)
6692 DWORD dwOldStyle = infoPtr->dwLvExStyle;
6696 infoPtr->dwLvExStyle = (dwOldStyle & ~dwMask) | (dwStyle & dwMask);
6698 infoPtr->dwLvExStyle = dwStyle;
6700 if((infoPtr->dwLvExStyle ^ dwOldStyle) & LVS_EX_CHECKBOXES)
6702 HIMAGELIST himl = 0;
6703 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6704 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
6705 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
6713 * Sets the new hot cursor used during hot tracking and hover selection.
6716 * [I] infoPtr : valid pointer to the listview structure
6717 * [I} hCurosr : the new hot cursor handle
6720 * Returns the previous hot cursor
6722 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
6724 HCURSOR oldCursor = infoPtr->hHotCursor;
6726 infoPtr->hHotCursor = hCursor;
6734 * Sets the hot item index.
6737 * [I] infoPtr : valid pointer to the listview structure
6738 * [I] iIndex : index
6741 * SUCCESS : previous hot item index
6742 * FAILURE : -1 (no hot item)
6744 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
6746 INT iOldIndex = infoPtr->nHotItem;
6748 infoPtr->nHotItem = iIndex;
6756 * Sets the amount of time the cursor must hover over an item before it is selected.
6759 * [I] infoPtr : valid pointer to the listview structure
6760 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
6763 * Returns the previous hover time
6765 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
6767 DWORD oldHoverTime = infoPtr->dwHoverTime;
6769 infoPtr->dwHoverTime = dwHoverTime;
6771 return oldHoverTime;
6776 * Sets spacing for icons of LVS_ICON style.
6779 * [I] infoPtr : valid pointer to the listview structure
6780 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
6781 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
6784 * MAKELONG(oldcx, oldcy)
6786 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
6788 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6789 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6791 TRACE("requested=(%d,%d)\n", cx, cy);
6793 /* this is supported only for LVS_ICON style */
6794 if (uView != LVS_ICON) return oldspacing;
6796 /* set to defaults, if instructed to */
6797 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
6798 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
6800 /* if 0 then compute width
6801 * FIXME: Should scan each item and determine max width of
6802 * icon or label, then make that the width */
6804 cx = infoPtr->iconSpacing.cx;
6806 /* if 0 then compute height */
6808 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
6809 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
6812 infoPtr->iconSpacing.cx = cx;
6813 infoPtr->iconSpacing.cy = cy;
6815 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%ld,%ld), ntmH=%d\n",
6816 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
6817 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
6818 infoPtr->ntmHeight);
6820 /* these depend on the iconSpacing */
6821 LISTVIEW_UpdateItemSize(infoPtr);
6826 inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
6830 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
6837 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
6838 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
6847 * [I] infoPtr : valid pointer to the listview structure
6848 * [I] nType : image list type
6849 * [I] himl : image list handle
6852 * SUCCESS : old image list
6855 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
6857 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6858 INT oldHeight = infoPtr->nItemHeight;
6859 HIMAGELIST himlOld = 0;
6861 TRACE("(nType=%d, himl=%p\n", nType, himl);
6866 himlOld = infoPtr->himlNormal;
6867 infoPtr->himlNormal = himl;
6868 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
6869 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
6873 himlOld = infoPtr->himlSmall;
6874 infoPtr->himlSmall = himl;
6875 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
6879 himlOld = infoPtr->himlState;
6880 infoPtr->himlState = himl;
6881 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
6882 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
6886 ERR("Unknown icon type=%d\n", nType);
6890 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
6891 if (infoPtr->nItemHeight != oldHeight)
6892 LISTVIEW_UpdateScroll(infoPtr);
6899 * Preallocates memory (does *not* set the actual count of items !)
6902 * [I] infoPtr : valid pointer to the listview structure
6903 * [I] nItems : item count (projected number of items to allocate)
6904 * [I] dwFlags : update flags
6910 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
6912 TRACE("(nItems=%d, dwFlags=%lx)\n", nItems, dwFlags);
6914 if (infoPtr->dwStyle & LVS_OWNERDATA)
6916 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6917 INT nOldCount = infoPtr->nItemCount;
6919 if (nItems < nOldCount)
6921 RANGE range = { nItems, nOldCount };
6922 ranges_del(infoPtr->selectionRanges, range);
6923 if (infoPtr->nFocusedItem >= nItems)
6925 infoPtr->nFocusedItem = -1;
6926 SetRectEmpty(&infoPtr->rcFocus);
6930 infoPtr->nItemCount = nItems;
6931 LISTVIEW_UpdateScroll(infoPtr);
6933 /* the flags are valid only in ownerdata report and list modes */
6934 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
6936 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
6937 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
6939 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
6940 LISTVIEW_InvalidateList(infoPtr);
6947 LISTVIEW_GetOrigin(infoPtr, &Origin);
6948 nFrom = min(nOldCount, nItems);
6949 nTo = max(nOldCount, nItems);
6951 if (uView == LVS_REPORT)
6954 rcErase.top = nFrom * infoPtr->nItemHeight;
6955 rcErase.right = infoPtr->nItemWidth;
6956 rcErase.bottom = nTo * infoPtr->nItemHeight;
6957 OffsetRect(&rcErase, Origin.x, Origin.y);
6958 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6959 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6963 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
6965 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
6966 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
6967 rcErase.right = rcErase.left + infoPtr->nItemWidth;
6968 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
6969 OffsetRect(&rcErase, Origin.x, Origin.y);
6970 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6971 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6973 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
6975 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
6976 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
6977 OffsetRect(&rcErase, Origin.x, Origin.y);
6978 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6979 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6985 /* According to MSDN for non-LVS_OWNERDATA this is just
6986 * a performance issue. The control allocates its internal
6987 * data structures for the number of items specified. It
6988 * cuts down on the number of memory allocations. Therefore
6989 * we will just issue a WARN here
6991 WARN("for non-ownerdata performance option not implemented.\n");
6999 * Sets the position of an item.
7002 * [I] infoPtr : valid pointer to the listview structure
7003 * [I] nItem : item index
7004 * [I] pt : coordinate
7010 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7012 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7015 TRACE("(nItem=%d, &pt=%s\n", nItem, debugpoint(&pt));
7017 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7018 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7020 LISTVIEW_GetOrigin(infoPtr, &Origin);
7022 /* This point value seems to be an undocumented feature.
7023 * The best guess is that it means either at the origin,
7024 * or at true beginning of the list. I will assume the origin. */
7025 if ((pt.x == -1) && (pt.y == -1))
7028 if (uView == LVS_ICON)
7030 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7031 pt.y -= ICON_TOP_PADDING;
7036 infoPtr->bAutoarrange = FALSE;
7038 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7043 * Sets the state of one or many items.
7046 * [I] infoPtr : valid pointer to the listview structure
7047 * [I] nItem : item index
7048 * [I] lpLVItem : item or subitem info
7054 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7056 BOOL bResult = TRUE;
7059 lvItem.iItem = nItem;
7060 lvItem.iSubItem = 0;
7061 lvItem.mask = LVIF_STATE;
7062 lvItem.state = lpLVItem->state;
7063 lvItem.stateMask = lpLVItem->stateMask;
7064 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7068 /* apply to all items */
7069 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7070 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7073 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7076 *update selection mark
7078 * Investigation on windows 2k showed that selection mark was updated
7079 * whenever a new selection was made, but if the selected item was
7080 * unselected it was not updated.
7082 * we are probably still not 100% accurate, but this at least sets the
7083 * proper selection mark when it is needed
7086 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7087 ((infoPtr->nSelectionMark == -1) || (lvItem.iItem <= infoPtr->nSelectionMark)))
7090 infoPtr->nSelectionMark = -1;
7091 for (i = 0; i < infoPtr->nItemCount; i++)
7093 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7095 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7097 infoPtr->nSelectionMark = i;
7101 else if (ranges_contain(infoPtr->selectionRanges, i))
7103 infoPtr->nSelectionMark = i;
7114 * Sets the text of an item or subitem.
7117 * [I] hwnd : window handle
7118 * [I] nItem : item index
7119 * [I] lpLVItem : item or subitem info
7120 * [I] isW : TRUE if input is Unicode
7126 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7130 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7132 lvItem.iItem = nItem;
7133 lvItem.iSubItem = lpLVItem->iSubItem;
7134 lvItem.mask = LVIF_TEXT;
7135 lvItem.pszText = lpLVItem->pszText;
7136 lvItem.cchTextMax = lpLVItem->cchTextMax;
7138 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7140 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7145 * Set item index that marks the start of a multiple selection.
7148 * [I] infoPtr : valid pointer to the listview structure
7149 * [I] nIndex : index
7152 * Index number or -1 if there is no selection mark.
7154 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7156 INT nOldIndex = infoPtr->nSelectionMark;
7158 TRACE("(nIndex=%d)\n", nIndex);
7160 infoPtr->nSelectionMark = nIndex;
7167 * Sets the text background color.
7170 * [I] infoPtr : valid pointer to the listview structure
7171 * [I] clrTextBk : text background color
7177 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7179 TRACE("(clrTextBk=%lx)\n", clrTextBk);
7181 if (infoPtr->clrTextBk != clrTextBk)
7183 infoPtr->clrTextBk = clrTextBk;
7184 LISTVIEW_InvalidateList(infoPtr);
7192 * Sets the text foreground color.
7195 * [I] infoPtr : valid pointer to the listview structure
7196 * [I] clrText : text color
7202 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7204 TRACE("(clrText=%lx)\n", clrText);
7206 if (infoPtr->clrText != clrText)
7208 infoPtr->clrText = clrText;
7209 LISTVIEW_InvalidateList(infoPtr);
7217 * Determines which listview item is located at the specified position.
7220 * [I] infoPtr : valid pointer to the listview structure
7221 * [I] hwndNewToolTip : handle to new ToolTip
7226 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7228 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7229 infoPtr->hwndToolTip = hwndNewToolTip;
7230 return hwndOldToolTip;
7233 /* LISTVIEW_SetUnicodeFormat */
7234 /* LISTVIEW_SetWorkAreas */
7238 * Callback internally used by LISTVIEW_SortItems()
7241 * [I] first : pointer to first ITEM_INFO to compare
7242 * [I] second : pointer to second ITEM_INFO to compare
7243 * [I] lParam : HWND of control
7246 * if first comes before second : negative
7247 * if first comes after second : positive
7248 * if first and second are equivalent : zero
7250 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7252 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7253 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
7254 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
7256 /* Forward the call to the client defined callback */
7257 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7262 * Sorts the listview items.
7265 * [I] infoPtr : valid pointer to the listview structure
7266 * [I] pfnCompare : application-defined value
7267 * [I] lParamSort : pointer to comparision callback
7273 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
7275 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7278 LPVOID selectionMarkItem;
7282 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7284 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7286 if (!pfnCompare) return FALSE;
7287 if (!infoPtr->hdpaItems) return FALSE;
7289 /* if there are 0 or 1 items, there is no need to sort */
7290 if (infoPtr->nItemCount < 2) return TRUE;
7292 if (infoPtr->nFocusedItem >= 0)
7294 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7295 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7296 if (lpItem) lpItem->state |= LVIS_FOCUSED;
7298 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
7299 /* clear the lpItem->state for non-selected ones */
7300 /* remove the selection ranges */
7302 infoPtr->pfnCompare = pfnCompare;
7303 infoPtr->lParamSort = lParamSort;
7304 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
7306 /* Adjust selections and indices so that they are the way they should
7307 * be after the sort (otherwise, the list items move around, but
7308 * whatever is at the item's previous original position will be
7311 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
7312 for (i=0; i < infoPtr->nItemCount; i++)
7314 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
7315 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7317 if (lpItem->state & LVIS_SELECTED)
7319 item.state = LVIS_SELECTED;
7320 item.stateMask = LVIS_SELECTED;
7321 LISTVIEW_SetItemState(infoPtr, i, &item);
7323 if (lpItem->state & LVIS_FOCUSED)
7325 infoPtr->nFocusedItem = i;
7326 lpItem->state &= ~LVIS_FOCUSED;
7329 if (selectionMarkItem != NULL)
7330 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7331 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7333 /* refresh the display */
7334 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7335 LISTVIEW_InvalidateList(infoPtr);
7342 * Updates an items or rearranges the listview control.
7345 * [I] infoPtr : valid pointer to the listview structure
7346 * [I] nItem : item index
7352 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
7354 TRACE("(nItem=%d)\n", nItem);
7356 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7358 /* rearrange with default alignment style */
7359 if (is_autoarrange(infoPtr))
7360 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7362 LISTVIEW_InvalidateItem(infoPtr, nItem);
7370 * Creates the listview control.
7373 * [I] hwnd : window handle
7374 * [I] lpcs : the create parameters
7380 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
7382 LISTVIEW_INFO *infoPtr;
7383 UINT uView = lpcs->style & LVS_TYPEMASK;
7386 TRACE("(lpcs=%p)\n", lpcs);
7388 /* initialize info pointer */
7389 infoPtr = (LISTVIEW_INFO *)Alloc(sizeof(LISTVIEW_INFO));
7390 if (!infoPtr) return -1;
7392 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
7394 infoPtr->hwndSelf = hwnd;
7395 infoPtr->dwStyle = lpcs->style;
7396 /* determine the type of structures to use */
7397 infoPtr->hwndNotify = lpcs->hwndParent;
7398 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
7399 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
7401 /* initialize color information */
7402 infoPtr->clrBk = CLR_NONE;
7403 infoPtr->clrText = comctl32_color.clrWindowText;
7404 infoPtr->clrTextBk = CLR_DEFAULT;
7405 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
7407 /* set default values */
7408 infoPtr->nFocusedItem = -1;
7409 infoPtr->nSelectionMark = -1;
7410 infoPtr->nHotItem = -1;
7411 infoPtr->bRedraw = TRUE;
7412 infoPtr->bNoItemMetrics = TRUE;
7413 infoPtr->bDoChangeNotify = TRUE;
7414 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
7415 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
7416 infoPtr->nEditLabelItem = -1;
7417 infoPtr->dwHoverTime = -1; /* default system hover time */
7419 /* get default font (icon title) */
7420 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
7421 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
7422 infoPtr->hFont = infoPtr->hDefaultFont;
7423 LISTVIEW_SaveTextMetrics(infoPtr);
7426 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL,
7427 WS_CHILD | HDS_HORZ | (DWORD)((LVS_NOSORTHEADER & lpcs->style)?0:HDS_BUTTONS),
7428 0, 0, 0, 0, hwnd, NULL,
7429 lpcs->hInstance, NULL);
7430 if (!infoPtr->hwndHeader) goto fail;
7432 /* set header unicode format */
7433 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)NULL);
7435 /* set header font */
7436 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
7438 /* allocate memory for the data structure */
7439 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
7440 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
7441 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
7442 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
7443 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
7445 /* initialize the icon sizes */
7446 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
7447 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
7449 /* init item size to avoid division by 0 */
7450 LISTVIEW_UpdateItemSize (infoPtr);
7452 if (uView == LVS_REPORT)
7454 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
7456 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
7460 /* set HDS_HIDDEN flag to hide the header bar */
7461 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE,
7462 GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE) | HDS_HIDDEN);
7469 DestroyWindow(infoPtr->hwndHeader);
7470 ranges_destroy(infoPtr->selectionRanges);
7471 DPA_Destroy(infoPtr->hdpaItems);
7472 DPA_Destroy(infoPtr->hdpaPosX);
7473 DPA_Destroy(infoPtr->hdpaPosY);
7474 DPA_Destroy(infoPtr->hdpaColumns);
7481 * Erases the background of the listview control.
7484 * [I] infoPtr : valid pointer to the listview structure
7485 * [I] hdc : device context handle
7491 static inline BOOL LISTVIEW_EraseBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc)
7495 TRACE("(hdc=%p)\n", hdc);
7497 if (!GetClipBox(hdc, &rc)) return FALSE;
7499 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
7505 * Helper function for LISTVIEW_[HV]Scroll *only*.
7506 * Performs vertical/horizontal scrolling by a give amount.
7509 * [I] infoPtr : valid pointer to the listview structure
7510 * [I] dx : amount of horizontal scroll
7511 * [I] dy : amount of vertical scroll
7513 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7515 /* now we can scroll the list */
7516 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
7517 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
7518 /* if we have focus, adjust rect */
7519 OffsetRect(&infoPtr->rcFocus, dx, dy);
7520 UpdateWindow(infoPtr->hwndSelf);
7525 * Performs vertical scrolling.
7528 * [I] infoPtr : valid pointer to the listview structure
7529 * [I] nScrollCode : scroll code
7530 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7531 * [I] hScrollWnd : scrollbar control window handle
7537 * SB_LINEUP/SB_LINEDOWN:
7538 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
7539 * for LVS_REPORT is 1 line
7540 * for LVS_LIST cannot occur
7543 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7544 INT nScrollDiff, HWND hScrollWnd)
7546 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7547 INT nOldScrollPos, nNewScrollPos;
7548 SCROLLINFO scrollInfo;
7551 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
7552 debugscrollcode(nScrollCode), nScrollDiff);
7554 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7556 scrollInfo.cbSize = sizeof(SCROLLINFO);
7557 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7559 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
7561 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
7563 nOldScrollPos = scrollInfo.nPos;
7564 switch (nScrollCode)
7570 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
7574 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
7578 nScrollDiff = -scrollInfo.nPage;
7582 nScrollDiff = scrollInfo.nPage;
7585 case SB_THUMBPOSITION:
7587 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7594 /* quit right away if pos isn't changing */
7595 if (nScrollDiff == 0) return 0;
7597 /* calculate new position, and handle overflows */
7598 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7599 if (nScrollDiff > 0) {
7600 if (nNewScrollPos < nOldScrollPos ||
7601 nNewScrollPos > scrollInfo.nMax)
7602 nNewScrollPos = scrollInfo.nMax;
7604 if (nNewScrollPos > nOldScrollPos ||
7605 nNewScrollPos < scrollInfo.nMin)
7606 nNewScrollPos = scrollInfo.nMin;
7609 /* set the new position, and reread in case it changed */
7610 scrollInfo.fMask = SIF_POS;
7611 scrollInfo.nPos = nNewScrollPos;
7612 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
7614 /* carry on only if it really changed */
7615 if (nNewScrollPos == nOldScrollPos) return 0;
7617 /* now adjust to client coordinates */
7618 nScrollDiff = nOldScrollPos - nNewScrollPos;
7619 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
7621 /* and scroll the window */
7622 scroll_list(infoPtr, 0, nScrollDiff);
7629 * Performs horizontal scrolling.
7632 * [I] infoPtr : valid pointer to the listview structure
7633 * [I] nScrollCode : scroll code
7634 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7635 * [I] hScrollWnd : scrollbar control window handle
7641 * SB_LINELEFT/SB_LINERIGHT:
7642 * for LVS_ICON, LVS_SMALLICON 1 pixel
7643 * for LVS_REPORT is 1 pixel
7644 * for LVS_LIST is 1 column --> which is a 1 because the
7645 * scroll is based on columns not pixels
7648 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7649 INT nScrollDiff, HWND hScrollWnd)
7651 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7652 INT nOldScrollPos, nNewScrollPos;
7653 SCROLLINFO scrollInfo;
7655 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
7656 debugscrollcode(nScrollCode), nScrollDiff);
7658 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7660 scrollInfo.cbSize = sizeof(SCROLLINFO);
7661 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7663 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
7665 nOldScrollPos = scrollInfo.nPos;
7667 switch (nScrollCode)
7681 nScrollDiff = -scrollInfo.nPage;
7685 nScrollDiff = scrollInfo.nPage;
7688 case SB_THUMBPOSITION:
7690 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7697 /* quit right away if pos isn't changing */
7698 if (nScrollDiff == 0) return 0;
7700 /* calculate new position, and handle overflows */
7701 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7702 if (nScrollDiff > 0) {
7703 if (nNewScrollPos < nOldScrollPos ||
7704 nNewScrollPos > scrollInfo.nMax)
7705 nNewScrollPos = scrollInfo.nMax;
7707 if (nNewScrollPos > nOldScrollPos ||
7708 nNewScrollPos < scrollInfo.nMin)
7709 nNewScrollPos = scrollInfo.nMin;
7712 /* set the new position, and reread in case it changed */
7713 scrollInfo.fMask = SIF_POS;
7714 scrollInfo.nPos = nNewScrollPos;
7715 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
7717 /* carry on only if it really changed */
7718 if (nNewScrollPos == nOldScrollPos) return 0;
7720 if(uView == LVS_REPORT)
7721 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
7723 /* now adjust to client coordinates */
7724 nScrollDiff = nOldScrollPos - nNewScrollPos;
7725 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
7727 /* and scroll the window */
7728 scroll_list(infoPtr, nScrollDiff, 0);
7733 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
7735 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7736 INT gcWheelDelta = 0;
7737 INT pulScrollLines = 3;
7738 SCROLLINFO scrollInfo;
7740 TRACE("(wheelDelta=%d)\n", wheelDelta);
7742 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
7743 gcWheelDelta -= wheelDelta;
7745 scrollInfo.cbSize = sizeof(SCROLLINFO);
7746 scrollInfo.fMask = SIF_POS;
7753 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
7754 * should be fixed in the future.
7756 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
7757 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
7761 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
7763 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
7764 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
7765 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
7770 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
7781 * [I] infoPtr : valid pointer to the listview structure
7782 * [I] nVirtualKey : virtual key
7783 * [I] lKeyData : key data
7788 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
7790 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7792 NMLVKEYDOWN nmKeyDown;
7794 TRACE("(nVirtualKey=%d, lKeyData=%ld)\n", nVirtualKey, lKeyData);
7796 /* send LVN_KEYDOWN notification */
7797 nmKeyDown.wVKey = nVirtualKey;
7798 nmKeyDown.flags = 0;
7799 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
7801 switch (nVirtualKey)
7804 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
7806 notify(infoPtr, NM_RETURN);
7807 notify(infoPtr, LVN_ITEMACTIVATE);
7812 if (infoPtr->nItemCount > 0)
7817 if (infoPtr->nItemCount > 0)
7818 nItem = infoPtr->nItemCount - 1;
7822 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
7826 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
7830 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
7834 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
7838 if (uView == LVS_REPORT)
7839 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr);
7841 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
7842 * LISTVIEW_GetCountPerRow(infoPtr);
7843 if(nItem < 0) nItem = 0;
7847 if (uView == LVS_REPORT)
7848 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr);
7850 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
7851 * LISTVIEW_GetCountPerRow(infoPtr);
7852 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
7856 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem))
7857 LISTVIEW_KeySelection(infoPtr, nItem);
7867 * [I] infoPtr : valid pointer to the listview structure
7872 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
7876 /* if we did not have the focus, there's nothing to do */
7877 if (!infoPtr->bFocus) return 0;
7879 /* send NM_KILLFOCUS notification */
7880 notify(infoPtr, NM_KILLFOCUS);
7882 /* if we have a focus rectagle, get rid of it */
7883 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
7885 /* set window focus flag */
7886 infoPtr->bFocus = FALSE;
7888 /* invalidate the selected items before reseting focus flag */
7889 LISTVIEW_InvalidateSelectedItems(infoPtr);
7897 * Track mouse/dragging
7900 * [I] infoPtr : valid pointer to the listview structure
7901 * [I] pt : mouse coordinate
7906 static LRESULT LISTVIEW_TrackMouse(LISTVIEW_INFO *infoPtr, POINT pt)
7908 INT cxDrag = GetSystemMetrics(SM_CXDRAG);
7909 INT cyDrag = GetSystemMetrics(SM_CYDRAG);
7915 r.top = pt.y - cyDrag;
7916 r.left = pt.x - cxDrag;
7917 r.bottom = pt.y + cyDrag;
7918 r.right = pt.x + cxDrag;
7920 SetCapture(infoPtr->hwndSelf);
7924 if (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD))
7926 if (msg.message == WM_MOUSEMOVE)
7928 pt.x = (short)LOWORD(msg.lParam);
7929 pt.y = (short)HIWORD(msg.lParam);
7930 if (PtInRect(&r, pt))
7938 else if (msg.message >= WM_LBUTTONDOWN &&
7939 msg.message <= WM_RBUTTONDBLCLK)
7944 DispatchMessageW(&msg);
7947 if (GetCapture() != infoPtr->hwndSelf)
7958 * Processes double click messages (left mouse button).
7961 * [I] infoPtr : valid pointer to the listview structure
7962 * [I] wKey : key flag
7963 * [I] x,y : mouse coordinate
7968 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
7970 LVHITTESTINFO htInfo;
7972 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
7974 /* send NM_RELEASEDCAPTURE notification */
7975 notify(infoPtr, NM_RELEASEDCAPTURE);
7980 /* send NM_DBLCLK notification */
7981 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
7982 notify_click(infoPtr, NM_DBLCLK, &htInfo);
7984 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
7985 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
7992 * Processes mouse down messages (left mouse button).
7995 * [I] infoPtr : valid pointer to the listview structure
7996 * [I] wKey : key flag
7997 * [I] x,y : mouse coordinate
8002 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8004 LVHITTESTINFO lvHitTestInfo;
8005 static BOOL bGroupSelect = TRUE;
8006 POINT pt = { x, y };
8009 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8011 /* send NM_RELEASEDCAPTURE notification */
8012 notify(infoPtr, NM_RELEASEDCAPTURE);
8014 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
8016 /* set left button down flag */
8017 infoPtr->bLButtonDown = TRUE;
8019 lvHitTestInfo.pt.x = x;
8020 lvHitTestInfo.pt.y = y;
8022 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8023 TRACE("at %s, nItem=%d\n", debugpoint(&pt), nItem);
8024 infoPtr->nEditLabelItem = -1;
8025 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8027 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8029 DWORD state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK) >> 12;
8030 if(state == 1 || state == 2)
8034 lvitem.state = state << 12;
8035 lvitem.stateMask = LVIS_STATEIMAGEMASK;
8036 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
8040 if (LISTVIEW_TrackMouse(infoPtr, lvHitTestInfo.pt))
8044 ZeroMemory(&nmlv, sizeof(nmlv));
8046 nmlv.ptAction.x = lvHitTestInfo.pt.x;
8047 nmlv.ptAction.y = lvHitTestInfo.pt.y;
8049 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
8054 if (infoPtr->dwStyle & LVS_SINGLESEL)
8056 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8057 infoPtr->nEditLabelItem = nItem;
8059 LISTVIEW_SetSelection(infoPtr, nItem);
8063 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8067 LISTVIEW_AddGroupSelection(infoPtr, nItem);
8068 LISTVIEW_SetItemFocus(infoPtr, nItem);
8069 infoPtr->nSelectionMark = nItem;
8075 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8076 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8078 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8079 infoPtr->nSelectionMark = nItem;
8082 else if (wKey & MK_CONTROL)
8086 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8088 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8089 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8090 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8091 infoPtr->nSelectionMark = nItem;
8093 else if (wKey & MK_SHIFT)
8095 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8099 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8100 infoPtr->nEditLabelItem = nItem;
8102 /* set selection (clears other pre-existing selections) */
8103 LISTVIEW_SetSelection(infoPtr, nItem);
8109 /* remove all selections */
8110 LISTVIEW_DeselectAll(infoPtr);
8119 * Processes mouse up messages (left mouse button).
8122 * [I] infoPtr : valid pointer to the listview structure
8123 * [I] wKey : key flag
8124 * [I] x,y : mouse coordinate
8129 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8131 LVHITTESTINFO lvHitTestInfo;
8133 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8135 if (!infoPtr->bLButtonDown) return 0;
8137 lvHitTestInfo.pt.x = x;
8138 lvHitTestInfo.pt.y = y;
8140 /* send NM_CLICK notification */
8141 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8142 notify_click(infoPtr, NM_CLICK, &lvHitTestInfo);
8144 /* set left button flag */
8145 infoPtr->bLButtonDown = FALSE;
8147 /* if we clicked on a selected item, edit the label */
8148 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8149 LISTVIEW_EditLabelT(infoPtr, lvHitTestInfo.iItem, TRUE);
8156 * Destroys the listview control (called after WM_DESTROY).
8159 * [I] infoPtr : valid pointer to the listview structure
8164 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8168 /* delete all items */
8169 LISTVIEW_DeleteAllItems(infoPtr);
8171 /* destroy data structure */
8172 DPA_Destroy(infoPtr->hdpaItems);
8173 DPA_Destroy(infoPtr->hdpaPosX);
8174 DPA_Destroy(infoPtr->hdpaPosY);
8175 DPA_Destroy(infoPtr->hdpaColumns);
8176 ranges_destroy(infoPtr->selectionRanges);
8178 /* destroy image lists */
8179 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8181 if (infoPtr->himlNormal)
8182 ImageList_Destroy(infoPtr->himlNormal);
8183 if (infoPtr->himlSmall)
8184 ImageList_Destroy(infoPtr->himlSmall);
8185 if (infoPtr->himlState)
8186 ImageList_Destroy(infoPtr->himlState);
8189 /* destroy font, bkgnd brush */
8191 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8192 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8194 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
8196 /* free listview info pointer*/
8204 * Handles notifications from header.
8207 * [I] infoPtr : valid pointer to the listview structure
8208 * [I] nCtrlId : control identifier
8209 * [I] lpnmh : notification information
8214 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8216 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8218 TRACE("(lpnmh=%p)\n", lpnmh);
8220 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8222 switch (lpnmh->hdr.code)
8226 case HDN_ITEMCHANGEDW:
8227 case HDN_ITEMCHANGEDA:
8229 COLUMN_INFO *lpColumnInfo;
8232 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8236 hdi.mask = HDI_WIDTH;
8237 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, (LPARAM)&hdi)) return 0;
8241 cxy = lpnmh->pitem->cxy;
8243 /* determine how much we change since the last know position */
8244 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8245 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8248 RECT rcCol = lpColumnInfo->rcHeader;
8250 lpColumnInfo->rcHeader.right += dx;
8251 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
8252 LISTVIEW_UpdateItemSize(infoPtr);
8253 if (uView == LVS_REPORT && is_redrawing(infoPtr))
8255 /* this trick works for left aligned columns only */
8256 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8258 rcCol.right = min (rcCol.right, lpColumnInfo->rcHeader.right);
8259 rcCol.left = max (rcCol.left, rcCol.right - 3 * infoPtr->ntmAveCharWidth);
8261 rcCol.top = infoPtr->rcList.top;
8262 rcCol.bottom = infoPtr->rcList.bottom;
8263 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
8269 case HDN_ITEMCLICKW:
8270 case HDN_ITEMCLICKA:
8272 /* Handle sorting by Header Column */
8275 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
8277 nmlv.iSubItem = lpnmh->iItem;
8278 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
8288 * Determines the type of structure to use.
8291 * [I] infoPtr : valid pointer to the listview structureof the sender
8292 * [I] hwndFrom : listview window handle
8293 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
8298 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
8300 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
8302 if (nCommand != NF_REQUERY) return 0;
8304 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
8311 * Paints/Repaints the listview control.
8314 * [I] infoPtr : valid pointer to the listview structure
8315 * [I] hdc : device context handle
8320 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
8322 TRACE("(hdc=%p)\n", hdc);
8324 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
8326 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8328 infoPtr->bNoItemMetrics = FALSE;
8329 LISTVIEW_UpdateItemSize(infoPtr);
8330 if (uView == LVS_ICON || uView == LVS_SMALLICON)
8331 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8332 LISTVIEW_UpdateScroll(infoPtr);
8335 LISTVIEW_Refresh(infoPtr, hdc);
8340 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
8342 if (ps.fErase) LISTVIEW_FillBkgnd(infoPtr, hdc, &ps.rcPaint);
8343 LISTVIEW_Refresh(infoPtr, hdc);
8344 EndPaint(infoPtr->hwndSelf, &ps);
8353 * Paints/Repaints the listview control.
8356 * [I] infoPtr : valid pointer to the listview structure
8357 * [I] hdc : device context handle
8358 * [I] options : drawing options
8363 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
8365 FIXME("Partial Stub: (hdc=%p options=0x%08lx)\n", hdc, options);
8367 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
8370 if (options & PRF_ERASEBKGND)
8371 LISTVIEW_EraseBkgnd(infoPtr, hdc);
8373 if (options & PRF_CLIENT)
8374 LISTVIEW_Paint(infoPtr, hdc);
8382 * Processes double click messages (right mouse button).
8385 * [I] infoPtr : valid pointer to the listview structure
8386 * [I] wKey : key flag
8387 * [I] x,y : mouse coordinate
8392 static LRESULT LISTVIEW_RButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8394 LVHITTESTINFO lvHitTestInfo;
8396 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
8398 /* send NM_RELEASEDCAPTURE notification */
8399 notify(infoPtr, NM_RELEASEDCAPTURE);
8401 /* send NM_RDBLCLK notification */
8402 lvHitTestInfo.pt.x = x;
8403 lvHitTestInfo.pt.y = y;
8404 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8405 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
8412 * Processes mouse down messages (right mouse button).
8415 * [I] infoPtr : valid pointer to the listview structure
8416 * [I] wKey : key flag
8417 * [I] x,y : mouse coordinate
8422 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8424 LVHITTESTINFO lvHitTestInfo;
8427 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
8429 /* send NM_RELEASEDCAPTURE notification */
8430 notify(infoPtr, NM_RELEASEDCAPTURE);
8432 /* make sure the listview control window has the focus */
8433 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
8435 /* set right button down flag */
8436 infoPtr->bRButtonDown = TRUE;
8438 /* determine the index of the selected item */
8439 lvHitTestInfo.pt.x = x;
8440 lvHitTestInfo.pt.y = y;
8441 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8443 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8445 LISTVIEW_SetItemFocus(infoPtr, nItem);
8446 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
8447 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8448 LISTVIEW_SetSelection(infoPtr, nItem);
8452 LISTVIEW_DeselectAll(infoPtr);
8460 * Processes mouse up messages (right mouse button).
8463 * [I] infoPtr : valid pointer to the listview structure
8464 * [I] wKey : key flag
8465 * [I] x,y : mouse coordinate
8470 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8472 LVHITTESTINFO lvHitTestInfo;
8475 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
8477 if (!infoPtr->bRButtonDown) return 0;
8479 /* set button flag */
8480 infoPtr->bRButtonDown = FALSE;
8482 /* Send NM_RClICK notification */
8483 lvHitTestInfo.pt.x = x;
8484 lvHitTestInfo.pt.y = y;
8485 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8486 notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo);
8488 /* Change to screen coordinate for WM_CONTEXTMENU */
8489 pt = lvHitTestInfo.pt;
8490 ClientToScreen(infoPtr->hwndSelf, &pt);
8492 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
8493 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
8494 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
8505 * [I] infoPtr : valid pointer to the listview structure
8506 * [I] hwnd : window handle of window containing the cursor
8507 * [I] nHittest : hit-test code
8508 * [I] wMouseMsg : ideintifier of the mouse message
8511 * TRUE if cursor is set
8514 static BOOL LISTVIEW_SetCursor(LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
8516 LVHITTESTINFO lvHitTestInfo;
8518 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
8520 if(!infoPtr->hHotCursor) return FALSE;
8522 GetCursorPos(&lvHitTestInfo.pt);
8523 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
8525 SetCursor(infoPtr->hHotCursor);
8535 * [I] infoPtr : valid pointer to the listview structure
8536 * [I] hwndLoseFocus : handle of previously focused window
8541 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
8543 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
8545 /* if we have the focus already, there's nothing to do */
8546 if (infoPtr->bFocus) return 0;
8548 /* send NM_SETFOCUS notification */
8549 notify(infoPtr, NM_SETFOCUS);
8551 /* set window focus flag */
8552 infoPtr->bFocus = TRUE;
8554 /* put the focus rect back on */
8555 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
8557 /* redraw all visible selected items */
8558 LISTVIEW_InvalidateSelectedItems(infoPtr);
8568 * [I] infoPtr : valid pointer to the listview structure
8569 * [I] fRedraw : font handle
8570 * [I] fRedraw : redraw flag
8575 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
8577 HFONT oldFont = infoPtr->hFont;
8579 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
8581 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
8582 if (infoPtr->hFont == oldFont) return 0;
8584 LISTVIEW_SaveTextMetrics(infoPtr);
8586 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
8587 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
8589 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
8596 * Message handling for WM_SETREDRAW.
8597 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
8600 * [I] infoPtr : valid pointer to the listview structure
8601 * [I] bRedraw: state of redraw flag
8604 * DefWinProc return value
8606 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
8608 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
8610 /* we can not use straight equality here because _any_ non-zero value is TRUE */
8611 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
8613 infoPtr->bRedraw = bRedraw;
8615 if(!bRedraw) return 0;
8617 if (is_autoarrange(infoPtr))
8618 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8619 LISTVIEW_UpdateScroll(infoPtr);
8621 /* despite what the WM_SETREDRAW docs says, apps expect us
8622 * to invalidate the listview here... stupid! */
8623 LISTVIEW_InvalidateList(infoPtr);
8630 * Resizes the listview control. This function processes WM_SIZE
8631 * messages. At this time, the width and height are not used.
8634 * [I] infoPtr : valid pointer to the listview structure
8635 * [I] Width : new width
8636 * [I] Height : new height
8641 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
8643 RECT rcOld = infoPtr->rcList;
8645 TRACE("(width=%d, height=%d)\n", Width, Height);
8647 LISTVIEW_UpdateSize(infoPtr);
8648 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
8650 /* do not bother with display related stuff if we're not redrawing */
8651 if (!is_redrawing(infoPtr)) return 0;
8653 if (is_autoarrange(infoPtr))
8654 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8656 LISTVIEW_UpdateScroll(infoPtr);
8658 /* refresh all only for lists whose height changed significantly */
8659 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
8660 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
8661 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
8662 LISTVIEW_InvalidateList(infoPtr);
8669 * Sets the size information.
8672 * [I] infoPtr : valid pointer to the listview structure
8677 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
8679 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8681 TRACE("uView=%d, rcList(old)=%s\n", uView, debugrect(&infoPtr->rcList));
8683 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
8685 if (uView == LVS_LIST)
8687 /* Apparently the "LIST" style is supposed to have the same
8688 * number of items in a column even if there is no scroll bar.
8689 * Since if a scroll bar already exists then the bottom is already
8690 * reduced, only reduce if the scroll bar does not currently exist.
8691 * The "2" is there to mimic the native control. I think it may be
8692 * related to either padding or edges. (GLA 7/2002)
8694 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
8695 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
8696 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
8698 else if (uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER))
8703 hl.prc = &infoPtr->rcList;
8705 Header_Layout(infoPtr->hwndHeader, &hl);
8707 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8709 infoPtr->rcList.top = max(wp.cy, 0);
8712 TRACE(" rcList=%s\n", debugrect(&infoPtr->rcList));
8717 * Processes WM_STYLECHANGED messages.
8720 * [I] infoPtr : valid pointer to the listview structure
8721 * [I] wStyleType : window style type (normal or extended)
8722 * [I] lpss : window style information
8727 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
8728 const STYLESTRUCT *lpss)
8730 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
8731 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
8733 TRACE("(styletype=%x, styleOld=0x%08lx, styleNew=0x%08lx)\n",
8734 wStyleType, lpss->styleOld, lpss->styleNew);
8736 if (wStyleType != GWL_STYLE) return 0;
8738 /* FIXME: if LVS_NOSORTHEADER changed, update header */
8739 /* what if LVS_OWNERDATA changed? */
8740 /* or LVS_SINGLESEL */
8741 /* or LVS_SORT{AS,DES}CENDING */
8743 infoPtr->dwStyle = lpss->styleNew;
8745 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
8746 ((lpss->styleNew & WS_HSCROLL) == 0))
8747 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
8749 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
8750 ((lpss->styleNew & WS_VSCROLL) == 0))
8751 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
8753 if (uNewView != uOldView)
8755 SIZE oldIconSize = infoPtr->iconSize;
8758 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8759 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8761 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
8762 SetRectEmpty(&infoPtr->rcFocus);
8764 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
8765 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
8767 if (uNewView == LVS_ICON)
8769 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
8771 TRACE("icon old size=(%ld,%ld), new size=(%ld,%ld)\n",
8772 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
8773 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
8776 else if (uNewView == LVS_REPORT)
8781 hl.prc = &infoPtr->rcList;
8783 Header_Layout(infoPtr->hwndHeader, &hl);
8784 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8787 LISTVIEW_UpdateItemSize(infoPtr);
8790 if (uNewView == LVS_REPORT)
8791 ShowWindow(infoPtr->hwndHeader, (lpss->styleNew & LVS_NOCOLUMNHEADER) ? SW_HIDE : SW_SHOWNORMAL);
8793 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
8794 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
8795 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8797 /* update the size of the client area */
8798 LISTVIEW_UpdateSize(infoPtr);
8800 /* add scrollbars if needed */
8801 LISTVIEW_UpdateScroll(infoPtr);
8803 /* invalidate client area + erase background */
8804 LISTVIEW_InvalidateList(infoPtr);
8811 * Window procedure of the listview control.
8814 static LRESULT WINAPI
8815 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8817 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8819 TRACE("(uMsg=%x wParam=%x lParam=%lx)\n", uMsg, wParam, lParam);
8821 if (!infoPtr && (uMsg != WM_CREATE))
8822 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8826 case LVM_APPROXIMATEVIEWRECT:
8827 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
8828 LOWORD(lParam), HIWORD(lParam));
8830 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
8832 /* case LVM_CANCELEDITLABEL: */
8834 case LVM_CREATEDRAGIMAGE:
8835 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
8837 case LVM_DELETEALLITEMS:
8838 return LISTVIEW_DeleteAllItems(infoPtr);
8840 case LVM_DELETECOLUMN:
8841 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
8843 case LVM_DELETEITEM:
8844 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
8846 case LVM_EDITLABELW:
8847 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
8849 case LVM_EDITLABELA:
8850 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
8852 /* case LVM_ENABLEGROUPVIEW: */
8854 case LVM_ENSUREVISIBLE:
8855 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
8858 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
8861 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
8863 case LVM_GETBKCOLOR:
8864 return infoPtr->clrBk;
8866 /* case LVM_GETBKIMAGE: */
8868 case LVM_GETCALLBACKMASK:
8869 return infoPtr->uCallbackMask;
8871 case LVM_GETCOLUMNA:
8872 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8874 case LVM_GETCOLUMNW:
8875 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8877 case LVM_GETCOLUMNORDERARRAY:
8878 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
8880 case LVM_GETCOLUMNWIDTH:
8881 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
8883 case LVM_GETCOUNTPERPAGE:
8884 return LISTVIEW_GetCountPerPage(infoPtr);
8886 case LVM_GETEDITCONTROL:
8887 return (LRESULT)infoPtr->hwndEdit;
8889 case LVM_GETEXTENDEDLISTVIEWSTYLE:
8890 return infoPtr->dwLvExStyle;
8892 /* case LVM_GETGROUPINFO: */
8894 /* case LVM_GETGROUPMETRICS: */
8897 return (LRESULT)infoPtr->hwndHeader;
8899 case LVM_GETHOTCURSOR:
8900 return (LRESULT)infoPtr->hHotCursor;
8902 case LVM_GETHOTITEM:
8903 return infoPtr->nHotItem;
8905 case LVM_GETHOVERTIME:
8906 return infoPtr->dwHoverTime;
8908 case LVM_GETIMAGELIST:
8909 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
8911 /* case LVM_GETINSERTMARK: */
8913 /* case LVM_GETINSERTMARKCOLOR: */
8915 /* case LVM_GETINSERTMARKRECT: */
8917 case LVM_GETISEARCHSTRINGA:
8918 case LVM_GETISEARCHSTRINGW:
8919 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
8923 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
8926 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
8928 case LVM_GETITEMCOUNT:
8929 return infoPtr->nItemCount;
8931 case LVM_GETITEMPOSITION:
8932 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
8934 case LVM_GETITEMRECT:
8935 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
8937 case LVM_GETITEMSPACING:
8938 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
8940 case LVM_GETITEMSTATE:
8941 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
8943 case LVM_GETITEMTEXTA:
8944 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
8946 case LVM_GETITEMTEXTW:
8947 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
8949 case LVM_GETNEXTITEM:
8950 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
8952 case LVM_GETNUMBEROFWORKAREAS:
8953 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
8957 if (!lParam) return FALSE;
8958 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
8961 /* case LVM_GETOUTLINECOLOR: */
8963 /* case LVM_GETSELECTEDCOLUMN: */
8965 case LVM_GETSELECTEDCOUNT:
8966 return LISTVIEW_GetSelectedCount(infoPtr);
8968 case LVM_GETSELECTIONMARK:
8969 return infoPtr->nSelectionMark;
8971 case LVM_GETSTRINGWIDTHA:
8972 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
8974 case LVM_GETSTRINGWIDTHW:
8975 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
8977 case LVM_GETSUBITEMRECT:
8978 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
8980 case LVM_GETTEXTBKCOLOR:
8981 return infoPtr->clrTextBk;
8983 case LVM_GETTEXTCOLOR:
8984 return infoPtr->clrText;
8986 /* case LVM_GETTILEINFO: */
8988 /* case LVM_GETTILEVIEWINFO: */
8990 case LVM_GETTOOLTIPS:
8991 if( !infoPtr->hwndToolTip )
8992 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
8993 return (LRESULT)infoPtr->hwndToolTip;
8995 case LVM_GETTOPINDEX:
8996 return LISTVIEW_GetTopIndex(infoPtr);
8998 /*case LVM_GETUNICODEFORMAT:
8999 FIXME("LVM_GETUNICODEFORMAT: unimplemented\n");
9002 /* case LVM_GETVIEW: */
9004 case LVM_GETVIEWRECT:
9005 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
9007 case LVM_GETWORKAREAS:
9008 FIXME("LVM_GETWORKAREAS: unimplemented\n");
9011 /* case LVM_HASGROUP: */
9014 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
9016 case LVM_INSERTCOLUMNA:
9017 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9019 case LVM_INSERTCOLUMNW:
9020 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9022 /* case LVM_INSERTGROUP: */
9024 /* case LVM_INSERTGROUPSORTED: */
9026 case LVM_INSERTITEMA:
9027 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9029 case LVM_INSERTITEMW:
9030 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9032 /* case LVM_INSERTMARKHITTEST: */
9034 /* case LVM_ISGROUPVIEWENABLED: */
9036 /* case LVM_MAPIDTOINDEX: */
9038 /* case LVM_MAPINDEXTOID: */
9040 /* case LVM_MOVEGROUP: */
9042 /* case LVM_MOVEITEMTOGROUP: */
9044 case LVM_REDRAWITEMS:
9045 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
9047 /* case LVM_REMOVEALLGROUPS: */
9049 /* case LVM_REMOVEGROUP: */
9052 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
9054 case LVM_SETBKCOLOR:
9055 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
9057 /* case LVM_SETBKIMAGE: */
9059 case LVM_SETCALLBACKMASK:
9060 infoPtr->uCallbackMask = (UINT)wParam;
9063 case LVM_SETCOLUMNA:
9064 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9066 case LVM_SETCOLUMNW:
9067 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9069 case LVM_SETCOLUMNORDERARRAY:
9070 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9072 case LVM_SETCOLUMNWIDTH:
9073 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
9075 case LVM_SETEXTENDEDLISTVIEWSTYLE:
9076 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
9078 /* case LVM_SETGROUPINFO: */
9080 /* case LVM_SETGROUPMETRICS: */
9082 case LVM_SETHOTCURSOR:
9083 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
9085 case LVM_SETHOTITEM:
9086 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
9088 case LVM_SETHOVERTIME:
9089 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
9091 case LVM_SETICONSPACING:
9092 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9094 case LVM_SETIMAGELIST:
9095 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
9097 /* case LVM_SETINFOTIP: */
9099 /* case LVM_SETINSERTMARK: */
9101 /* case LVM_SETINSERTMARKCOLOR: */
9104 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9107 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9109 case LVM_SETITEMCOUNT:
9110 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
9112 case LVM_SETITEMPOSITION:
9115 pt.x = (short)LOWORD(lParam);
9116 pt.y = (short)HIWORD(lParam);
9117 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
9120 case LVM_SETITEMPOSITION32:
9121 if (lParam == 0) return FALSE;
9122 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
9124 case LVM_SETITEMSTATE:
9125 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
9127 case LVM_SETITEMTEXTA:
9128 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9130 case LVM_SETITEMTEXTW:
9131 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9133 /* case LVM_SETOUTLINECOLOR: */
9135 /* case LVM_SETSELECTEDCOLUMN: */
9137 case LVM_SETSELECTIONMARK:
9138 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
9140 case LVM_SETTEXTBKCOLOR:
9141 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
9143 case LVM_SETTEXTCOLOR:
9144 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
9146 /* case LVM_SETTILEINFO: */
9148 /* case LVM_SETTILEVIEWINFO: */
9150 /* case LVM_SETTILEWIDTH: */
9152 case LVM_SETTOOLTIPS:
9153 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
9155 /* case LVM_SETUNICODEFORMAT: */
9157 /* case LVM_SETVIEW: */
9159 /* case LVM_SETWORKAREAS: */
9161 /* case LVM_SORTGROUPS: */
9164 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
9166 /* LVM_SORTITEMSEX: */
9168 case LVM_SUBITEMHITTEST:
9169 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
9172 return LISTVIEW_Update(infoPtr, (INT)wParam);
9175 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
9178 return LISTVIEW_Command(infoPtr, wParam, lParam);
9181 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
9184 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
9187 return DLGC_WANTCHARS | DLGC_WANTARROWS;
9190 return (LRESULT)infoPtr->hFont;
9193 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9196 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
9199 return LISTVIEW_KillFocus(infoPtr);
9201 case WM_LBUTTONDBLCLK:
9202 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9204 case WM_LBUTTONDOWN:
9205 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9208 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9211 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9214 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9217 return LISTVIEW_NCDestroy(infoPtr);
9220 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
9221 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
9224 case WM_NOTIFYFORMAT:
9225 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
9227 case WM_PRINTCLIENT:
9228 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
9231 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
9233 case WM_RBUTTONDBLCLK:
9234 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9236 case WM_RBUTTONDOWN:
9237 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9240 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9243 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
9248 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
9251 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
9254 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
9257 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9259 case WM_STYLECHANGED:
9260 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
9262 case WM_SYSCOLORCHANGE:
9263 COMCTL32_RefreshSysColors();
9266 /* case WM_TIMER: */
9269 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9272 if (wParam & (MK_SHIFT | MK_CONTROL))
9273 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9274 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
9276 case WM_WINDOWPOSCHANGED:
9277 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
9279 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
9280 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
9281 LISTVIEW_UpdateSize(infoPtr);
9282 LISTVIEW_UpdateScroll(infoPtr);
9284 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9286 /* case WM_WININICHANGE: */
9289 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
9290 ERR("unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam);
9293 /* call default window procedure */
9294 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9301 * Registers the window class.
9309 void LISTVIEW_Register(void)
9313 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
9314 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
9315 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
9316 wndClass.cbClsExtra = 0;
9317 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
9318 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
9319 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
9320 wndClass.lpszClassName = WC_LISTVIEWW;
9321 RegisterClassW(&wndClass);
9326 * Unregisters the window class.
9334 void LISTVIEW_Unregister(void)
9336 UnregisterClassW(WC_LISTVIEWW, NULL);
9341 * Handle any WM_COMMAND messages
9344 * [I] infoPtr : valid pointer to the listview structure
9345 * [I] wParam : the first message parameter
9346 * [I] lParam : the second message parameter
9351 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
9353 switch (HIWORD(wParam))
9358 * Adjust the edit window size
9361 HDC hdc = GetDC(infoPtr->hwndEdit);
9362 HFONT hFont, hOldFont = 0;
9367 if (!infoPtr->hwndEdit || !hdc) return 0;
9368 len = GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
9369 GetWindowRect(infoPtr->hwndEdit, &rect);
9371 /* Select font to get the right dimension of the string */
9372 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
9375 hOldFont = SelectObject(hdc, hFont);
9378 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
9380 TEXTMETRICW textMetric;
9382 /* Add Extra spacing for the next character */
9383 GetTextMetricsW(hdc, &textMetric);
9384 sz.cx += (textMetric.tmMaxCharWidth * 2);
9392 rect.bottom - rect.top,
9393 SWP_DRAWFRAME|SWP_NOMOVE);
9396 SelectObject(hdc, hOldFont);
9398 ReleaseDC(infoPtr->hwndSelf, hdc);
9404 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
9413 * Subclassed edit control windproc function
9416 * [I] hwnd : the edit window handle
9417 * [I] uMsg : the message that is to be processed
9418 * [I] wParam : first message parameter
9419 * [I] lParam : second message parameter
9420 * [I] isW : TRUE if input is Unicode
9425 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
9427 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
9428 BOOL cancel = FALSE;
9430 TRACE("(hwnd=%p, uMsg=%x, wParam=%x, lParam=%lx, isW=%d)\n",
9431 hwnd, uMsg, wParam, lParam, isW);
9436 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
9443 WNDPROC editProc = infoPtr->EditWndProc;
9444 infoPtr->EditWndProc = 0;
9445 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
9446 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
9450 if (VK_ESCAPE == (INT)wParam)
9455 else if (VK_RETURN == (INT)wParam)
9459 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
9463 if (infoPtr->hwndEdit)
9465 LPWSTR buffer = NULL;
9467 infoPtr->hwndEdit = 0;
9470 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
9474 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
9476 if (isW) GetWindowTextW(hwnd, buffer, len+1);
9477 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
9481 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
9483 if (buffer) Free(buffer);
9487 SendMessageW(hwnd, WM_CLOSE, 0, 0);
9493 * Subclassed edit control Unicode windproc function
9496 * [I] hwnd : the edit window handle
9497 * [I] uMsg : the message that is to be processed
9498 * [I] wParam : first message parameter
9499 * [I] lParam : second message parameter
9503 LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9505 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
9510 * Subclassed edit control ANSI windproc function
9513 * [I] hwnd : the edit window handle
9514 * [I] uMsg : the message that is to be processed
9515 * [I] wParam : first message parameter
9516 * [I] lParam : second message parameter
9520 LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9522 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
9527 * Creates a subclassed edit cotrol
9530 * [I] infoPtr : valid pointer to the listview structure
9531 * [I] text : initial text for the edit
9532 * [I] style : the window style
9533 * [I] isW : TRUE if input is Unicode
9537 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
9538 INT x, INT y, INT width, INT height, BOOL isW)
9540 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
9545 TEXTMETRICW textMetric;
9546 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
9548 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
9550 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|WS_BORDER;
9551 hdc = GetDC(infoPtr->hwndSelf);
9553 /* Select the font to get appropriate metric dimensions */
9554 if(infoPtr->hFont != 0)
9555 hOldFont = SelectObject(hdc, infoPtr->hFont);
9557 /*Get String Length in pixels */
9558 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
9560 /*Add Extra spacing for the next character */
9561 GetTextMetricsW(hdc, &textMetric);
9562 sz.cx += (textMetric.tmMaxCharWidth * 2);
9564 if(infoPtr->hFont != 0)
9565 SelectObject(hdc, hOldFont);
9567 ReleaseDC(infoPtr->hwndSelf, hdc);
9569 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
9571 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
9573 if (!hedit) return 0;
9575 infoPtr->EditWndProc = (WNDPROC)
9576 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
9577 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
9579 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);