4 * Copyright 1998, 1999 Eric Kohl
5 * Copyright 1999 Luc Tourangeau
6 * Copyright 2000 Jason Mawdsley
7 * Copyright 2001 CodeWeavers Inc.
8 * Copyright 2002 Dimitrie O. Paun
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 * This code was audited for completeness against the documented features
27 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
29 * Unless otherwise noted, we believe this code to be complete, as per
30 * the specification mentioned above.
31 * If you discover missing features, or bugs, please note them below.
35 * Default Message Processing
36 * -- EN_KILLFOCUS should be handled in WM_COMMAND
37 * -- WM_CREATE: create the icon and small icon image lists at this point only if
38 * the LVS_SHAREIMAGELISTS style is not specified.
39 * -- WM_ERASEBKGND: forward this message to the parent window if the bkgnd
41 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
42 * or small icon and the LVS_AUTOARRANGE style is specified.
47 * -- Hot item handling, mouse hovering
48 * -- Workareas support
53 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
54 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
55 * -- LVA_SNAPTOGRID not implemented
56 * -- LISTVIEW_ApproximateViewRect partially implemented
57 * -- LISTVIEW_[GS]etColumnOrderArray stubs
58 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
59 * -- LISTVIEW_SetIconSpacing is incomplete
60 * -- LISTVIEW_SortItems is broken
61 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
64 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
65 * linear in the number of items in the list, and this is
66 * unacceptable for large lists.
67 * -- in sorted mode, LISTVIEW_InsertItemT sorts the array,
68 * instead of inserting in the right spot
69 * -- we should keep an ordered array of coordinates in iconic mode
70 * this would allow to frame items (iterator_frameditems),
71 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
79 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
86 * -- LVS_NOSCROLL (see Q137520)
87 * -- LVS_SORTASCENDING, LVS_SORTDESCENDING
89 * -- LVS_TYPESTYLEMASK
92 * -- LVS_EX_BORDERSELECT
95 * -- LVS_EX_HEADERDRAGDROP
98 * -- LVS_EX_MULTIWORKAREAS
99 * -- LVS_EX_ONECLICKACTIVATE
101 * -- LVS_EX_SIMPLESELECT
102 * -- LVS_EX_TRACKSELECT
103 * -- LVS_EX_TWOCLICKACTIVATE
104 * -- LVS_EX_UNDERLINECOLD
105 * -- LVS_EX_UNDERLINEHOT
108 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
111 * -- LVN_MARQUEEBEGIN
118 * -- LVM_CANCELEDITLABEL
119 * -- LVM_ENABLEGROUPVIEW
120 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
121 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
122 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
123 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
124 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
125 * -- LVM_GETINSERTMARKRECT
126 * -- LVM_GETNUMBEROFWORKAREAS
127 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
128 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
129 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
130 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
131 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
132 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
133 * -- LVM_GETVIEW, LVM_SETVIEW
134 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
135 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
136 * -- LVM_INSERTGROUPSORTED
137 * -- LVM_INSERTMARKHITTEST
138 * -- LVM_ISGROUPVIEWENABLED
139 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
141 * -- LVM_MOVEITEMTOGROUP
143 * -- LVM_SETTILEWIDTH
148 * -- ListView_GetCheckSate, ListView_SetCheckState
149 * -- ListView_GetHoverTime, ListView_SetHoverTime
150 * -- ListView_GetISearchString
151 * -- ListView_GetNumberOfWorkAreas
152 * -- ListView_GetOrigin
153 * -- ListView_GetTextBkColor
154 * -- ListView_GetUnicodeFormat, ListView_SetUnicodeFormat
155 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
156 * -- ListView_SortItemsEx
161 * Known differences in message stream from native control (not known if
162 * these differences cause problems):
163 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
164 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
165 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
166 * processing for "USEDOUBLECLICKTIME".
170 #include "wine/port.h"
185 #include "commctrl.h"
186 #include "comctl32.h"
188 #include "wine/debug.h"
189 #include "wine/unicode.h"
191 WINE_DEFAULT_DEBUG_CHANNEL(listview);
193 /* make sure you set this to 0 for production use! */
194 #define DEBUG_RANGES 1
196 typedef struct tagCOLUMN_INFO
198 RECT rcHeader; /* tracks the header's rectangle */
199 int fmt; /* same as LVCOLUMN.fmt */
202 typedef struct tagITEMHDR
206 } ITEMHDR, *LPITEMHDR;
208 typedef struct tagSUBITEM_INFO
214 typedef struct tagITEM_INFO
222 typedef struct tagRANGE
228 typedef struct tagRANGES
233 typedef struct tagITERATOR
242 typedef struct tagLISTVIEW_INFO
249 COLORREF clrTextBkDefault;
250 HIMAGELIST himlNormal;
251 HIMAGELIST himlSmall;
252 HIMAGELIST himlState;
255 POINT ptClickPos; /* point where the user clicked */
256 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
259 RANGES selectionRanges;
264 RECT rcList; /* This rectangle is really the window
265 * client rectangle possibly reduced by the
266 * horizontal scroll bar and/or header - see
267 * LISTVIEW_UpdateSize. This rectangle offset
268 * by the LISTVIEW_GetOrigin value is in
269 * client coordinates */
278 INT ntmHeight; /* Some cached metrics of the font used */
279 INT ntmAveCharWidth; /* by the listview to draw items */
280 BOOL bRedraw; /* Turns on/off repaints & invalidations */
281 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
283 BOOL bDoChangeNotify; /* send change notification messages? */
286 DWORD dwStyle; /* the cached window GWL_STYLE */
287 DWORD dwLvExStyle; /* extended listview style */
288 INT nItemCount; /* the number of items in the list */
289 HDPA hdpaItems; /* array ITEM_INFO pointers */
290 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
291 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
292 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
293 POINT currIconPos; /* this is the position next icon will be placed */
294 PFNLVCOMPARE pfnCompare;
302 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
304 DWORD lastKeyPressTimestamp;
306 INT nSearchParamLength;
307 WCHAR szSearchParam[ MAX_PATH ];
309 INT nMeasureItemHeight;
315 /* How many we debug buffer to allocate */
316 #define DEBUG_BUFFERS 20
317 /* The size of a single debug bbuffer */
318 #define DEBUG_BUFFER_SIZE 256
320 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
321 #define SB_INTERNAL -1
323 /* maximum size of a label */
324 #define DISP_TEXT_SIZE 512
326 /* padding for items in list and small icon display modes */
327 #define WIDTH_PADDING 12
329 /* padding for items in list, report and small icon display modes */
330 #define HEIGHT_PADDING 1
332 /* offset of items in report display mode */
333 #define REPORT_MARGINX 2
335 /* padding for icon in large icon display mode
336 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
337 * that HITTEST will see.
338 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
339 * ICON_TOP_PADDING - sum of the two above.
340 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
341 * LABEL_HOR_PADDING - between text and sides of box
342 * LABEL_VERT_PADDING - between bottom of text and end of box
344 * ICON_LR_PADDING - additional width above icon size.
345 * ICON_LR_HALF - half of the above value
347 #define ICON_TOP_PADDING_NOTHITABLE 2
348 #define ICON_TOP_PADDING_HITABLE 2
349 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
350 #define ICON_BOTTOM_PADDING 4
351 #define LABEL_HOR_PADDING 5
352 #define LABEL_VERT_PADDING 7
353 #define ICON_LR_PADDING 16
354 #define ICON_LR_HALF (ICON_LR_PADDING/2)
356 /* default label width for items in list and small icon display modes */
357 #define DEFAULT_LABEL_WIDTH 40
359 /* default column width for items in list display mode */
360 #define DEFAULT_COLUMN_WIDTH 128
362 /* Size of "line" scroll for V & H scrolls */
363 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
365 /* Padding betwen image and label */
366 #define IMAGE_PADDING 2
368 /* Padding behind the label */
369 #define TRAILING_LABEL_PADDING 12
370 #define TRAILING_HEADER_PADDING 11
372 /* Border for the icon caption */
373 #define CAPTION_BORDER 2
375 /* Standard DrawText flags */
376 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
377 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
378 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
380 /* The time in milliseconds to reset the search in the list */
381 #define KEY_DELAY 450
383 /* Dump the LISTVIEW_INFO structure to the debug channel */
384 #define LISTVIEW_DUMP(iP) do { \
385 TRACE("hwndSelf=%p, clrBk=0x%06lx, clrText=0x%06lx, clrTextBk=0x%06lx, ItemHeight=%d, ItemWidth=%d, Style=0x%08lx\n", \
386 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
387 iP->nItemHeight, iP->nItemWidth, infoPtr->dwStyle); \
388 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08lx, Focus=%d\n", \
389 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
390 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
391 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%ld, icSz.cy=%ld, icSp.cx=%ld, icSp.cy=%ld, notifyFmt=%d\n", \
392 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
393 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
394 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, debugrect(&iP->rcList)); \
398 * forward declarations
400 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *, LPLVITEMW, BOOL);
401 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *, INT, LPRECT);
402 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *, INT, LPPOINT);
403 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *, INT, LPPOINT);
404 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *, INT, LPRECT);
405 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *, INT);
406 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *, LPPOINT);
407 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *, LPRECT);
408 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
409 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, const LVITEMW *, BOOL);
410 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *);
411 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
412 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
413 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
414 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
415 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *, PFNLVCOMPARE, LPARAM);
416 static INT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *, LPCWSTR, BOOL);
417 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT);
418 static UINT LISTVIEW_GetItemState(LISTVIEW_INFO *, INT, UINT);
419 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
420 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
421 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
422 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *);
423 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
424 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
425 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
426 static INT LISTVIEW_HitTest(LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
428 /******** Text handling functions *************************************/
430 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
431 * text string. The string may be ANSI or Unicode, in which case
432 * the boolean isW tells us the type of the string.
434 * The name of the function tell what type of strings it expects:
435 * W: Unicode, T: ANSI/Unicode - function of isW
438 static inline BOOL is_textW(LPCWSTR text)
440 return text != NULL && text != LPSTR_TEXTCALLBACKW;
443 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
445 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
446 return is_textW(text);
449 static inline int textlenT(LPCWSTR text, BOOL isW)
451 return !is_textT(text, isW) ? 0 :
452 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
455 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
458 if (isSrcW) lstrcpynW(dest, src, max);
459 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
461 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
462 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
465 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
467 LPWSTR wstr = (LPWSTR)text;
469 if (!isW && is_textT(text, isW))
471 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
472 wstr = Alloc(len * sizeof(WCHAR));
473 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
475 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
479 static inline void textfreeT(LPWSTR wstr, BOOL isW)
481 if (!isW && is_textT(wstr, isW)) Free (wstr);
485 * dest is a pointer to a Unicode string
486 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
488 static BOOL textsetptrT(LPWSTR *dest, LPWSTR src, BOOL isW)
492 if (src == LPSTR_TEXTCALLBACKW)
494 if (is_textW(*dest)) Free(*dest);
495 *dest = LPSTR_TEXTCALLBACKW;
499 LPWSTR pszText = textdupTtoW(src, isW);
500 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
501 bResult = Str_SetPtrW(dest, pszText);
502 textfreeT(pszText, isW);
508 * compares a Unicode to a Unicode/ANSI text string
510 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
512 if (!aw) return bt ? -1 : 0;
513 if (!bt) return aw ? 1 : 0;
514 if (aw == LPSTR_TEXTCALLBACKW)
515 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
516 if (bt != LPSTR_TEXTCALLBACKW)
518 LPWSTR bw = textdupTtoW(bt, isW);
519 int r = bw ? lstrcmpW(aw, bw) : 1;
527 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
531 n = min(min(n, strlenW(s1)), strlenW(s2));
532 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
533 return res ? res - sizeof(WCHAR) : res;
536 /******** Debugging functions *****************************************/
538 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
540 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
541 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
544 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
546 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
547 n = min(textlenT(text, isW), n);
548 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
551 static char* debug_getbuf()
553 static int index = 0;
554 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
555 return buffers[index++ % DEBUG_BUFFERS];
558 static inline const char* debugrange(const RANGE *lprng)
562 char* buf = debug_getbuf();
563 snprintf(buf, DEBUG_BUFFER_SIZE, "[%d, %d)", lprng->lower, lprng->upper);
565 } else return "(null)";
568 static inline const char* debugpoint(const POINT *lppt)
572 char* buf = debug_getbuf();
573 snprintf(buf, DEBUG_BUFFER_SIZE, "(%ld, %ld)", lppt->x, lppt->y);
575 } else return "(null)";
578 static inline const char* debugrect(const RECT *rect)
582 char* buf = debug_getbuf();
583 snprintf(buf, DEBUG_BUFFER_SIZE, "[(%ld, %ld);(%ld, %ld)]",
584 rect->left, rect->top, rect->right, rect->bottom);
586 } else return "(null)";
589 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
591 char* buf = debug_getbuf(), *text = buf;
592 int len, size = DEBUG_BUFFER_SIZE;
594 if (pScrollInfo == NULL) return "(null)";
595 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
596 if (len == -1) goto end; buf += len; size -= len;
597 if (pScrollInfo->fMask & SIF_RANGE)
598 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
600 if (len == -1) goto end; buf += len; size -= len;
601 if (pScrollInfo->fMask & SIF_PAGE)
602 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
604 if (len == -1) goto end; buf += len; size -= len;
605 if (pScrollInfo->fMask & SIF_POS)
606 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
608 if (len == -1) goto end; buf += len; size -= len;
609 if (pScrollInfo->fMask & SIF_TRACKPOS)
610 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
612 if (len == -1) goto end; buf += len; size -= len;
615 buf = text + strlen(text);
617 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
621 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
625 char* buf = debug_getbuf();
626 snprintf(buf, DEBUG_BUFFER_SIZE, "iItem=%d, iSubItem=%d, uNewState=0x%x,"
627 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld\n",
628 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
629 plvnm->uChanged, debugpoint(&plvnm->ptAction), plvnm->lParam);
631 } else return "(null)";
634 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
636 char* buf = debug_getbuf(), *text = buf;
637 int len, size = DEBUG_BUFFER_SIZE;
639 if (lpLVItem == NULL) return "(null)";
640 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
641 if (len == -1) goto end; buf += len; size -= len;
642 if (lpLVItem->mask & LVIF_STATE)
643 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
645 if (len == -1) goto end; buf += len; size -= len;
646 if (lpLVItem->mask & LVIF_TEXT)
647 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
649 if (len == -1) goto end; buf += len; size -= len;
650 if (lpLVItem->mask & LVIF_IMAGE)
651 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
653 if (len == -1) goto end; buf += len; size -= len;
654 if (lpLVItem->mask & LVIF_PARAM)
655 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
657 if (len == -1) goto end; buf += len; size -= len;
658 if (lpLVItem->mask & LVIF_INDENT)
659 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
661 if (len == -1) goto end; buf += len; size -= len;
664 buf = text + strlen(text);
666 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
670 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
672 char* buf = debug_getbuf(), *text = buf;
673 int len, size = DEBUG_BUFFER_SIZE;
675 if (lpColumn == NULL) return "(null)";
676 len = snprintf(buf, size, "{");
677 if (len == -1) goto end; buf += len; size -= len;
678 if (lpColumn->mask & LVCF_SUBITEM)
679 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
681 if (len == -1) goto end; buf += len; size -= len;
682 if (lpColumn->mask & LVCF_FMT)
683 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
685 if (len == -1) goto end; buf += len; size -= len;
686 if (lpColumn->mask & LVCF_WIDTH)
687 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
689 if (len == -1) goto end; buf += len; size -= len;
690 if (lpColumn->mask & LVCF_TEXT)
691 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
693 if (len == -1) goto end; buf += len; size -= len;
694 if (lpColumn->mask & LVCF_IMAGE)
695 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
697 if (len == -1) goto end; buf += len; size -= len;
698 if (lpColumn->mask & LVCF_ORDER)
699 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
701 if (len == -1) goto end; buf += len; size -= len;
704 buf = text + strlen(text);
706 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
710 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
714 char* buf = debug_getbuf();
715 snprintf(buf, DEBUG_BUFFER_SIZE, "{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
716 debugpoint(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
718 } else return "(null)";
721 /* Return the corresponding text for a given scroll value */
722 static inline LPCSTR debugscrollcode(int nScrollCode)
726 case SB_LINELEFT: return "SB_LINELEFT";
727 case SB_LINERIGHT: return "SB_LINERIGHT";
728 case SB_PAGELEFT: return "SB_PAGELEFT";
729 case SB_PAGERIGHT: return "SB_PAGERIGHT";
730 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
731 case SB_THUMBTRACK: return "SB_THUMBTRACK";
732 case SB_ENDSCROLL: return "SB_ENDSCROLL";
733 case SB_INTERNAL: return "SB_INTERNAL";
734 default: return "unknown";
739 /******** Notification functions i************************************/
741 static LRESULT notify_hdr(LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
745 TRACE("(code=%d)\n", code);
747 pnmh->hwndFrom = infoPtr->hwndSelf;
748 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
750 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
751 (WPARAM)pnmh->idFrom, (LPARAM)pnmh);
753 TRACE(" <= %ld\n", result);
758 static inline LRESULT notify(LISTVIEW_INFO *infoPtr, INT code)
761 return notify_hdr(infoPtr, code, &nmh);
764 static inline void notify_itemactivate(LISTVIEW_INFO *infoPtr, LVHITTESTINFO *htInfo)
775 item.mask = LVIF_PARAM|LVIF_STATE;
776 item.iItem = htInfo->iItem;
778 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
779 nmia.lParam = item.lParam;
780 nmia.uOldState = item.state;
781 nmia.uNewState = item.state | LVIS_ACTIVATING;
782 nmia.uChanged = LVIF_STATE;
785 nmia.iItem = htInfo->iItem;
786 nmia.iSubItem = htInfo->iSubItem;
787 nmia.ptAction = htInfo->pt;
789 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
790 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
791 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
793 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
796 static inline LRESULT notify_listview(LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
798 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
799 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
802 static LRESULT notify_click(LISTVIEW_INFO *infoPtr, INT code, LVHITTESTINFO *lvht)
807 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
808 ZeroMemory(&nmlv, sizeof(nmlv));
809 nmlv.iItem = lvht->iItem;
810 nmlv.iSubItem = lvht->iSubItem;
811 nmlv.ptAction = lvht->pt;
812 item.mask = LVIF_PARAM;
813 item.iItem = lvht->iItem;
815 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
816 return notify_listview(infoPtr, code, &nmlv);
819 static void notify_deleteitem(LISTVIEW_INFO *infoPtr, INT nItem)
824 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
826 item.mask = LVIF_PARAM;
829 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
830 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
833 static int get_ansi_notification(INT unicodeNotificationCode)
835 switch (unicodeNotificationCode)
837 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
838 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
839 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
840 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
841 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
842 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
844 ERR("unknown notification %x\n", unicodeNotificationCode);
850 Send notification. depends on dispinfoW having same
851 structure as dispinfoA.
852 infoPtr : listview struct
853 notificationCode : *Unicode* notification code
854 pdi : dispinfo structure (can be unicode or ansi)
855 isW : TRUE if dispinfo is Unicode
857 static BOOL notify_dispinfoT(LISTVIEW_INFO *infoPtr, INT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
859 BOOL bResult = FALSE;
860 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
861 INT cchTempBufMax = 0, savCchTextMax = 0, realNotifCode;
862 LPWSTR pszTempBuf = NULL, savPszText = NULL;
864 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
866 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
867 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
870 if (convertToAnsi || convertToUnicode)
872 if (notificationCode != LVN_GETDISPINFOW)
874 cchTempBufMax = convertToUnicode ?
875 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
876 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
880 cchTempBufMax = pdi->item.cchTextMax;
881 *pdi->item.pszText = 0; /* make sure we don't process garbage */
884 pszTempBuf = Alloc( (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
885 if (!pszTempBuf) return FALSE;
887 if (convertToUnicode)
888 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
889 pszTempBuf, cchTempBufMax);
891 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
892 cchTempBufMax, NULL, NULL);
894 savCchTextMax = pdi->item.cchTextMax;
895 savPszText = pdi->item.pszText;
896 pdi->item.pszText = pszTempBuf;
897 pdi->item.cchTextMax = cchTempBufMax;
900 if (infoPtr->notifyFormat == NFR_ANSI)
901 realNotifCode = get_ansi_notification(notificationCode);
903 realNotifCode = notificationCode;
904 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
905 bResult = notify_hdr(infoPtr, realNotifCode, &pdi->hdr);
907 if (convertToUnicode || convertToAnsi)
909 if (convertToUnicode) /* note : pointer can be changed by app ! */
910 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
911 savCchTextMax, NULL, NULL);
913 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
914 savPszText, savCchTextMax);
915 pdi->item.pszText = savPszText; /* restores our buffer */
916 pdi->item.cchTextMax = savCchTextMax;
922 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, LISTVIEW_INFO *infoPtr, HDC hdc,
923 const RECT *rcBounds, const LVITEMW *lplvItem)
925 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
926 lpnmlvcd->nmcd.hdc = hdc;
927 lpnmlvcd->nmcd.rc = *rcBounds;
928 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
929 lpnmlvcd->clrText = infoPtr->clrText;
930 if (!lplvItem) return;
931 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
932 lpnmlvcd->iSubItem = lplvItem->iSubItem;
933 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
934 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
935 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
936 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
939 static inline DWORD notify_customdraw (LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
941 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
944 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
945 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
946 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
947 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
948 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
949 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
953 static void prepaint_setup (LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd)
955 /* apprently, for selected items, we have to override the returned values */
956 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
960 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
961 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
963 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
965 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
966 lpnmlvcd->clrText = comctl32_color.clrBtnText;
970 /* Set the text attributes */
971 if (lpnmlvcd->clrTextBk != CLR_NONE)
973 SetBkMode(hdc, OPAQUE);
974 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
975 SetBkColor(hdc, infoPtr->clrTextBkDefault);
977 SetBkColor(hdc,lpnmlvcd->clrTextBk);
980 SetBkMode(hdc, TRANSPARENT);
981 SetTextColor(hdc, lpnmlvcd->clrText);
984 static inline DWORD notify_postpaint (LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
986 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
989 /******** Item iterator functions **********************************/
991 static RANGES ranges_create(int count);
992 static void ranges_destroy(RANGES ranges);
993 static BOOL ranges_add(RANGES ranges, RANGE range);
994 static BOOL ranges_del(RANGES ranges, RANGE range);
995 static void ranges_dump(RANGES ranges);
997 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
999 RANGE range = { nItem, nItem + 1 };
1001 return ranges_add(ranges, range);
1004 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1006 RANGE range = { nItem, nItem + 1 };
1008 return ranges_del(ranges, range);
1012 * ITERATOR DOCUMENTATION
1014 * The iterator functions allow for easy, and convenient iteration
1015 * over items of iterest in the list. Typically, you create a
1016 * iterator, use it, and destroy it, as such:
1019 * iterator_xxxitems(&i, ...);
1020 * while (iterator_{prev,next}(&i)
1022 * //code which uses i.nItem
1024 * iterator_destroy(&i);
1026 * where xxx is either: framed, or visible.
1027 * Note that it is important that the code destroys the iterator
1028 * after it's done with it, as the creation of the iterator may
1029 * allocate memory, which thus needs to be freed.
1031 * You can iterate both forwards, and backwards through the list,
1032 * by using iterator_next or iterator_prev respectively.
1034 * Lower numbered items are draw on top of higher number items in
1035 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1036 * items may overlap). So, to test items, you should use
1038 * which lists the items top to bottom (in Z-order).
1039 * For drawing items, you should use
1041 * which lists the items bottom to top (in Z-order).
1042 * If you keep iterating over the items after the end-of-items
1043 * marker (-1) is returned, the iterator will start from the
1044 * beginning. Typically, you don't need to test for -1,
1045 * because iterator_{next,prev} will return TRUE if more items
1046 * are to be iterated over, or FALSE otherwise.
1048 * Note: the iterator is defined to be bidirectional. That is,
1049 * any number of prev followed by any number of next, or
1050 * five versa, should leave the iterator at the same item:
1051 * prev * n, next * n = next * n, prev * n
1053 * The iterator has a notion of an out-of-order, special item,
1054 * which sits at the start of the list. This is used in
1055 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1056 * which needs to be first, as it may overlap other items.
1058 * The code is a bit messy because we have:
1059 * - a special item to deal with
1060 * - simple range, or composite range
1062 * If you find bugs, or want to add features, please make sure you
1063 * always check/modify *both* iterator_prev, and iterator_next.
1067 * This function iterates through the items in increasing order,
1068 * but prefixed by the special item, then -1. That is:
1069 * special, 1, 2, 3, ..., n, -1.
1070 * Each item is listed only once.
1072 static inline BOOL iterator_next(ITERATOR* i)
1076 i->nItem = i->nSpecial;
1077 if (i->nItem != -1) return TRUE;
1079 if (i->nItem == i->nSpecial)
1081 if (i->ranges) i->index = 0;
1087 if (i->nItem == i->nSpecial) i->nItem++;
1088 if (i->nItem < i->range.upper) return TRUE;
1093 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1094 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1097 else if (i->nItem >= i->range.upper) goto end;
1099 i->nItem = i->range.lower;
1100 if (i->nItem >= 0) goto testitem;
1107 * This function iterates through the items in decreasing order,
1108 * followed by the special item, then -1. That is:
1109 * n, n-1, ..., 3, 2, 1, special, -1.
1110 * Each item is listed only once.
1112 static inline BOOL iterator_prev(ITERATOR* i)
1119 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1122 if (i->nItem == i->nSpecial)
1130 if (i->nItem == i->nSpecial) i->nItem--;
1131 if (i->nItem >= i->range.lower) return TRUE;
1137 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1140 else if (!start && i->nItem < i->range.lower) goto end;
1142 i->nItem = i->range.upper;
1143 if (i->nItem > 0) goto testitem;
1145 return (i->nItem = i->nSpecial) != -1;
1148 static RANGE iterator_range(ITERATOR* i)
1152 if (!i->ranges) return i->range;
1154 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1156 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1157 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1159 else range.lower = range.upper = 0;
1165 * Releases resources associated with this ierator.
1167 static inline void iterator_destroy(ITERATOR* i)
1169 ranges_destroy(i->ranges);
1173 * Create an empty iterator.
1175 static inline BOOL iterator_empty(ITERATOR* i)
1177 ZeroMemory(i, sizeof(*i));
1178 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1183 * Create an iterator over a range.
1185 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1193 * Create an iterator over a bunch of ranges.
1194 * Please note that the iterator will take ownership of the ranges,
1195 * and will free them upon destruction.
1197 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1205 * Creates an iterator over the items which intersect lprc.
1207 static BOOL iterator_frameditems(ITERATOR* i, LISTVIEW_INFO* infoPtr, const RECT *lprc)
1209 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1210 RECT frame = *lprc, rcItem, rcTemp;
1213 /* in case we fail, we want to return an empty iterator */
1214 if (!iterator_empty(i)) return FALSE;
1216 LISTVIEW_GetOrigin(infoPtr, &Origin);
1218 TRACE("(lprc=%s)\n", debugrect(lprc));
1219 OffsetRect(&frame, -Origin.x, -Origin.y);
1221 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1225 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1227 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1228 if (IntersectRect(&rcTemp, &rcItem, lprc))
1229 i->nSpecial = infoPtr->nFocusedItem;
1231 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1232 /* to do better here, we need to have PosX, and PosY sorted */
1233 TRACE("building icon ranges:\n");
1234 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1236 rcItem.left = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1237 rcItem.top = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1238 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1239 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1240 if (IntersectRect(&rcTemp, &rcItem, &frame))
1241 ranges_additem(i->ranges, nItem);
1245 else if (uView == LVS_REPORT)
1249 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1250 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1252 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1253 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1254 if (range.upper <= range.lower) return TRUE;
1255 if (!iterator_rangeitems(i, range)) return FALSE;
1256 TRACE(" report=%s\n", debugrange(&i->range));
1260 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1261 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1262 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1263 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1264 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1265 INT lower = nFirstCol * nPerCol + nFirstRow;
1269 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1270 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1272 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1274 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1275 TRACE("building list ranges:\n");
1276 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1278 item_range.lower = nCol * nPerCol + nFirstRow;
1279 if(item_range.lower >= infoPtr->nItemCount) break;
1280 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1281 TRACE(" list=%s\n", debugrange(&item_range));
1282 ranges_add(i->ranges, item_range);
1290 * Creates an iterator over the items which intersect the visible region of hdc.
1292 static BOOL iterator_visibleitems(ITERATOR *i, LISTVIEW_INFO *infoPtr, HDC hdc)
1294 POINT Origin, Position;
1295 RECT rcItem, rcClip;
1298 rgntype = GetClipBox(hdc, &rcClip);
1299 if (rgntype == NULLREGION) return iterator_empty(i);
1300 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1301 if (rgntype == SIMPLEREGION) return TRUE;
1303 /* first deal with the special item */
1304 if (i->nSpecial != -1)
1306 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1307 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1310 /* if we can't deal with the region, we'll just go with the simple range */
1311 LISTVIEW_GetOrigin(infoPtr, &Origin);
1312 TRACE("building visible range:\n");
1313 if (!i->ranges && i->range.lower < i->range.upper)
1315 if (!(i->ranges = ranges_create(50))) return TRUE;
1316 if (!ranges_add(i->ranges, i->range))
1318 ranges_destroy(i->ranges);
1324 /* now delete the invisible items from the list */
1325 while(iterator_next(i))
1327 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1328 rcItem.left = Position.x + Origin.x;
1329 rcItem.top = Position.y + Origin.y;
1330 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1331 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1332 if (!RectVisible(hdc, &rcItem))
1333 ranges_delitem(i->ranges, i->nItem);
1335 /* the iterator should restart on the next iterator_next */
1341 /******** Misc helper functions ************************************/
1343 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1344 WPARAM wParam, LPARAM lParam, BOOL isW)
1346 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1347 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1350 static inline BOOL is_autoarrange(LISTVIEW_INFO *infoPtr)
1352 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1354 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1355 (uView == LVS_ICON || uView == LVS_SMALLICON);
1358 /******** Internal API functions ************************************/
1360 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(LISTVIEW_INFO *infoPtr, INT nSubItem)
1362 static COLUMN_INFO mainItem;
1364 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1365 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1366 return (COLUMN_INFO *)DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1369 static inline void LISTVIEW_GetHeaderRect(LISTVIEW_INFO *infoPtr, INT nSubItem, RECT *lprc)
1371 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1374 static inline BOOL LISTVIEW_GetItemW(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1376 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1379 /* Listview invalidation functions: use _only_ these functions to invalidate */
1381 static inline BOOL is_redrawing(LISTVIEW_INFO *infoPtr)
1383 return infoPtr->bRedraw;
1386 static inline void LISTVIEW_InvalidateRect(LISTVIEW_INFO *infoPtr, const RECT* rect)
1388 if(!is_redrawing(infoPtr)) return;
1389 TRACE(" invalidating rect=%s\n", debugrect(rect));
1390 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1393 static inline void LISTVIEW_InvalidateItem(LISTVIEW_INFO *infoPtr, INT nItem)
1397 if(!is_redrawing(infoPtr)) return;
1398 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1399 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1402 static inline void LISTVIEW_InvalidateSubItem(LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1404 POINT Origin, Position;
1407 if(!is_redrawing(infoPtr)) return;
1408 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1409 LISTVIEW_GetOrigin(infoPtr, &Origin);
1410 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1411 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1413 rcBox.bottom = infoPtr->nItemHeight;
1414 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1415 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1418 static inline void LISTVIEW_InvalidateList(LISTVIEW_INFO *infoPtr)
1420 LISTVIEW_InvalidateRect(infoPtr, NULL);
1423 static inline void LISTVIEW_InvalidateColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
1427 if(!is_redrawing(infoPtr)) return;
1428 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1429 rcCol.top = infoPtr->rcList.top;
1430 rcCol.bottom = infoPtr->rcList.bottom;
1431 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1436 * Retrieves the number of items that can fit vertically in the client area.
1439 * [I] infoPtr : valid pointer to the listview structure
1442 * Number of items per row.
1444 static inline INT LISTVIEW_GetCountPerRow(LISTVIEW_INFO *infoPtr)
1446 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1448 return max(nListWidth/infoPtr->nItemWidth, 1);
1453 * Retrieves the number of items that can fit horizontally in the client
1457 * [I] infoPtr : valid pointer to the listview structure
1460 * Number of items per column.
1462 static inline INT LISTVIEW_GetCountPerColumn(LISTVIEW_INFO *infoPtr)
1464 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1466 return max(nListHeight / infoPtr->nItemHeight, 1);
1470 /*************************************************************************
1471 * LISTVIEW_ProcessLetterKeys
1473 * Processes keyboard messages generated by pressing the letter keys
1475 * What this does is perform a case insensitive search from the
1476 * current position with the following quirks:
1477 * - If two chars or more are pressed in quick succession we search
1478 * for the corresponding string (e.g. 'abc').
1479 * - If there is a delay we wipe away the current search string and
1480 * restart with just that char.
1481 * - If the user keeps pressing the same character, whether slowly or
1482 * fast, so that the search string is entirely composed of this
1483 * character ('aaaaa' for instance), then we search for first item
1484 * that starting with that character.
1485 * - If the user types the above character in quick succession, then
1486 * we must also search for the corresponding string ('aaaaa'), and
1487 * go to that string if there is a match.
1490 * [I] hwnd : handle to the window
1491 * [I] charCode : the character code, the actual character
1492 * [I] keyData : key data
1500 * - The current implementation has a list of characters it will
1501 * accept and it ignores averything else. In particular it will
1502 * ignore accentuated characters which seems to match what
1503 * Windows does. But I'm not sure it makes sense to follow
1505 * - We don't sound a beep when the search fails.
1509 * TREEVIEW_ProcessLetterKeys
1511 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1516 WCHAR buffer[MAX_PATH];
1517 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1519 /* simple parameter checking */
1520 if (!charCode || !keyData) return 0;
1522 /* only allow the valid WM_CHARs through */
1523 if (!isalnum(charCode) &&
1524 charCode != '.' && charCode != '`' && charCode != '!' &&
1525 charCode != '@' && charCode != '#' && charCode != '$' &&
1526 charCode != '%' && charCode != '^' && charCode != '&' &&
1527 charCode != '*' && charCode != '(' && charCode != ')' &&
1528 charCode != '-' && charCode != '_' && charCode != '+' &&
1529 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1530 charCode != '}' && charCode != '[' && charCode != '{' &&
1531 charCode != '/' && charCode != '?' && charCode != '>' &&
1532 charCode != '<' && charCode != ',' && charCode != '~')
1535 /* if there's one item or less, there is no where to go */
1536 if (infoPtr->nItemCount <= 1) return 0;
1538 /* update the search parameters */
1539 infoPtr->lastKeyPressTimestamp = GetTickCount();
1540 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1541 if (infoPtr->nSearchParamLength < MAX_PATH)
1542 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1543 if (infoPtr->charCode != charCode)
1544 infoPtr->charCode = charCode = 0;
1546 infoPtr->charCode=charCode;
1547 infoPtr->szSearchParam[0]=charCode;
1548 infoPtr->nSearchParamLength=1;
1549 /* Redundant with the 1 char string */
1553 /* and search from the current position */
1555 if (infoPtr->nFocusedItem >= 0) {
1556 endidx=infoPtr->nFocusedItem;
1558 /* if looking for single character match,
1559 * then we must always move forward
1561 if (infoPtr->nSearchParamLength == 1)
1564 endidx=infoPtr->nItemCount;
1568 if (idx == infoPtr->nItemCount) {
1569 if (endidx == infoPtr->nItemCount || endidx == 0)
1575 item.mask = LVIF_TEXT;
1578 item.pszText = buffer;
1579 item.cchTextMax = MAX_PATH;
1580 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1582 /* check for a match */
1583 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1586 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1587 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1588 /* This would work but we must keep looking for a longer match */
1592 } while (idx != endidx);
1595 LISTVIEW_KeySelection(infoPtr, nItem);
1600 /*************************************************************************
1601 * LISTVIEW_UpdateHeaderSize [Internal]
1603 * Function to resize the header control
1606 * [I] hwnd : handle to a window
1607 * [I] nNewScrollPos : scroll pos to set
1612 static void LISTVIEW_UpdateHeaderSize(LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1617 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1619 GetWindowRect(infoPtr->hwndHeader, &winRect);
1620 point[0].x = winRect.left;
1621 point[0].y = winRect.top;
1622 point[1].x = winRect.right;
1623 point[1].y = winRect.bottom;
1625 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1626 point[0].x = -nNewScrollPos;
1627 point[1].x += nNewScrollPos;
1629 SetWindowPos(infoPtr->hwndHeader,0,
1630 point[0].x,point[0].y,point[1].x,point[1].y,
1631 SWP_NOZORDER | SWP_NOACTIVATE);
1636 * Update the scrollbars. This functions should be called whenever
1637 * the content, size or view changes.
1640 * [I] infoPtr : valid pointer to the listview structure
1645 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *infoPtr)
1647 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1648 SCROLLINFO horzInfo, vertInfo;
1650 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1652 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1653 horzInfo.cbSize = sizeof(SCROLLINFO);
1654 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1656 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1657 if (uView == LVS_LIST)
1659 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1660 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1662 /* scroll by at least one column per page */
1663 if(horzInfo.nPage < infoPtr->nItemWidth)
1664 horzInfo.nPage = infoPtr->nItemWidth;
1666 horzInfo.nPage /= infoPtr->nItemWidth;
1668 else if (uView == LVS_REPORT)
1670 horzInfo.nMax = infoPtr->nItemWidth;
1672 else /* LVS_ICON, or LVS_SMALLICON */
1676 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1679 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1680 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1681 SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1682 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1684 /* Setting the horizontal scroll can change the listview size
1685 * (and potentially everything else) so we need to recompute
1686 * everything again for the vertical scroll
1689 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1690 vertInfo.cbSize = sizeof(SCROLLINFO);
1691 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1693 if (uView == LVS_REPORT)
1695 vertInfo.nMax = infoPtr->nItemCount;
1697 /* scroll by at least one page */
1698 if(vertInfo.nPage < infoPtr->nItemHeight)
1699 vertInfo.nPage = infoPtr->nItemHeight;
1701 vertInfo.nPage /= infoPtr->nItemHeight;
1703 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1707 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1710 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1711 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1712 SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1713 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1715 /* Update the Header Control */
1716 if (uView == LVS_REPORT)
1718 horzInfo.fMask = SIF_POS;
1719 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1720 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1727 * Shows/hides the focus rectangle.
1730 * [I] infoPtr : valid pointer to the listview structure
1731 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1736 static void LISTVIEW_ShowFocusRect(LISTVIEW_INFO *infoPtr, BOOL fShow)
1738 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1741 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1743 if (infoPtr->nFocusedItem < 0) return;
1745 /* we need some gymnastics in ICON mode to handle large items */
1746 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1750 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1751 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1753 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1758 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1760 /* for some reason, owner draw should work only in report mode */
1761 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1766 item.iItem = infoPtr->nFocusedItem;
1768 item.mask = LVIF_PARAM;
1769 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1771 ZeroMemory(&dis, sizeof(dis));
1772 dis.CtlType = ODT_LISTVIEW;
1773 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1774 dis.itemID = item.iItem;
1775 dis.itemAction = ODA_FOCUS;
1776 if (fShow) dis.itemState |= ODS_FOCUS;
1777 dis.hwndItem = infoPtr->hwndSelf;
1779 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1780 dis.itemData = item.lParam;
1782 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1786 DrawFocusRect(hdc, &infoPtr->rcFocus);
1789 ReleaseDC(infoPtr->hwndSelf, hdc);
1793 * Invalidates all visible selected items.
1795 static void LISTVIEW_InvalidateSelectedItems(LISTVIEW_INFO *infoPtr)
1799 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1800 while(iterator_next(&i))
1802 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1803 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1805 iterator_destroy(&i);
1810 * DESCRIPTION: [INTERNAL]
1811 * Computes an item's (left,top) corner, relative to rcView.
1812 * That is, the position has NOT been made relative to the Origin.
1813 * This is deliberate, to avoid computing the Origin over, and
1814 * over again, when this function is call in a loop. Instead,
1815 * one ca factor the computation of the Origin before the loop,
1816 * and offset the value retured by this function, on every iteration.
1819 * [I] infoPtr : valid pointer to the listview structure
1820 * [I] nItem : item number
1821 * [O] lpptOrig : item top, left corner
1826 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1828 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1830 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1832 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1834 lpptPosition->x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1835 lpptPosition->y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1837 else if (uView == LVS_LIST)
1839 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1840 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1841 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1843 else /* LVS_REPORT */
1845 lpptPosition->x = 0;
1846 lpptPosition->y = nItem * infoPtr->nItemHeight;
1851 * DESCRIPTION: [INTERNAL]
1852 * Compute the rectangles of an item. This is to localize all
1853 * the computations in one place. If you are not interested in some
1854 * of these values, simply pass in a NULL -- the fucntion is smart
1855 * enough to compute only what's necessary. The function computes
1856 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1857 * one, the BOX rectangle. This rectangle is very cheap to compute,
1858 * and is guaranteed to contain all the other rectangles. Computing
1859 * the ICON rect is also cheap, but all the others are potentaily
1860 * expensive. This gives an easy and effective optimization when
1861 * searching (like point inclusion, or rectangle intersection):
1862 * first test against the BOX, and if TRUE, test agains the desired
1864 * If the function does not have all the necessary information
1865 * to computed the requested rectangles, will crash with a
1866 * failed assertion. This is done so we catch all programming
1867 * errors, given that the function is called only from our code.
1869 * We have the following 'special' meanings for a few fields:
1870 * * If LVIS_FOCUSED is set, we assume the item has the focus
1871 * This is important in ICON mode, where it might get a larger
1872 * then usual rectange
1874 * Please note that subitem support works only in REPORT mode.
1877 * [I] infoPtr : valid pointer to the listview structure
1878 * [I] lpLVItem : item to compute the measures for
1879 * [O] lprcBox : ptr to Box rectangle
1880 * The internal LVIR_BOX rectangle
1881 * [0] lprcState : ptr to State icon rectangle
1882 * The internal LVIR_STATE rectangle
1883 * [O] lprcIcon : ptr to Icon rectangle
1884 * Same as LVM_GETITEMRECT with LVIR_ICON
1885 * [O] lprcLabel : ptr to Label rectangle
1886 * Same as LVM_GETITEMRECT with LVIR_LABEL
1891 static void LISTVIEW_GetItemMetrics(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1892 LPRECT lprcBox, LPRECT lprcState,
1893 LPRECT lprcIcon, LPRECT lprcLabel)
1895 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1896 BOOL doState = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1897 RECT Box, State, Icon, Label;
1898 COLUMN_INFO *lpColumnInfo = NULL;
1900 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1902 /* Be smart and try to figure out the minimum we have to do */
1903 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1904 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1906 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1907 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1909 if (lprcLabel) doLabel = TRUE;
1910 if (doLabel || lprcIcon) doIcon = TRUE;
1911 if (doIcon || lprcState) doState = TRUE;
1913 /************************************************************/
1914 /* compute the box rectangle (it should be cheap to do) */
1915 /************************************************************/
1916 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1917 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1919 if (lpLVItem->iSubItem)
1921 Box = lpColumnInfo->rcHeader;
1926 Box.right = infoPtr->nItemWidth;
1929 Box.bottom = infoPtr->nItemHeight;
1931 /************************************************************/
1932 /* compute STATEICON bounding box */
1933 /************************************************************/
1936 if (uView == LVS_ICON)
1938 State.left = Box.left - infoPtr->iconStateSize.cx - 2;
1939 if (infoPtr->himlNormal)
1940 State.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1941 State.top = Box.top + infoPtr->iconSize.cy - infoPtr->iconStateSize.cy + 4;
1945 /* we need the ident in report mode, if we don't have it, we fail */
1946 State.left = Box.left;
1947 if (uView == LVS_REPORT)
1949 if (lpLVItem->iSubItem == 0)
1951 State.left += REPORT_MARGINX;
1952 assert(lpLVItem->mask & LVIF_INDENT);
1953 State.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
1956 State.top = Box.top;
1958 State.right = State.left;
1959 State.bottom = State.top;
1960 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1962 State.right += infoPtr->iconStateSize.cx;
1963 State.bottom += infoPtr->iconStateSize.cy;
1965 if (lprcState) *lprcState = State;
1966 TRACE(" - state=%s\n", debugrect(&State));
1969 /************************************************************/
1970 /* compute ICON bounding box (ala LVM_GETITEMRECT) */
1971 /************************************************************/
1974 if (uView == LVS_ICON)
1976 Icon.left = Box.left;
1977 if (infoPtr->himlNormal)
1978 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1979 Icon.top = Box.top + ICON_TOP_PADDING;
1980 Icon.right = Icon.left;
1981 Icon.bottom = Icon.top;
1982 if (infoPtr->himlNormal)
1984 Icon.right += infoPtr->iconSize.cx;
1985 Icon.bottom += infoPtr->iconSize.cy;
1988 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1990 Icon.left = State.right;
1992 Icon.right = Icon.left;
1993 if (infoPtr->himlSmall &&
1994 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
1995 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
1996 Icon.right += infoPtr->iconSize.cx;
1997 Icon.bottom = Icon.top + infoPtr->nItemHeight;
1999 if(lprcIcon) *lprcIcon = Icon;
2000 TRACE(" - icon=%s\n", debugrect(&Icon));
2003 /************************************************************/
2004 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2005 /************************************************************/
2008 SIZE labelSize = { 0, 0 };
2010 /* calculate how far to the right can the label strech */
2011 Label.right = Box.right;
2012 if (uView == LVS_REPORT)
2014 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
2017 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
2019 labelSize.cx = infoPtr->nItemWidth;
2020 labelSize.cy = infoPtr->nItemHeight;
2024 /* we need the text in non owner draw mode */
2025 assert(lpLVItem->mask & LVIF_TEXT);
2026 if (is_textT(lpLVItem->pszText, TRUE))
2028 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2029 HDC hdc = GetDC(infoPtr->hwndSelf);
2030 HFONT hOldFont = SelectObject(hdc, hFont);
2034 /* compute rough rectangle where the label will go */
2035 SetRectEmpty(&rcText);
2036 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2037 rcText.bottom = infoPtr->nItemHeight;
2038 if (uView == LVS_ICON)
2039 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2041 /* now figure out the flags */
2042 if (uView == LVS_ICON)
2043 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2045 uFormat = LV_SL_DT_FLAGS;
2047 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2049 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2050 labelSize.cy = rcText.bottom - rcText.top;
2052 SelectObject(hdc, hOldFont);
2053 ReleaseDC(infoPtr->hwndSelf, hdc);
2057 if (uView == LVS_ICON)
2059 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2060 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2061 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2062 Label.right = Label.left + labelSize.cx;
2063 Label.bottom = Label.top + infoPtr->nItemHeight;
2064 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2066 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2067 labelSize.cy /= infoPtr->ntmHeight;
2068 labelSize.cy = max(labelSize.cy, 1);
2069 labelSize.cy *= infoPtr->ntmHeight;
2071 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2073 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2075 Label.left = Icon.right;
2076 Label.top = Box.top;
2077 Label.right = min(Label.left + labelSize.cx, Label.right);
2078 Label.bottom = Label.top + infoPtr->nItemHeight;
2081 if (lprcLabel) *lprcLabel = Label;
2082 TRACE(" - label=%s\n", debugrect(&Label));
2085 /* Fix the Box if necessary */
2088 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2089 else *lprcBox = Box;
2091 TRACE(" - box=%s\n", debugrect(&Box));
2095 * DESCRIPTION: [INTERNAL]
2098 * [I] infoPtr : valid pointer to the listview structure
2099 * [I] nItem : item number
2100 * [O] lprcBox : ptr to Box rectangle
2105 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2107 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2108 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2109 POINT Position, Origin;
2112 LISTVIEW_GetOrigin(infoPtr, &Origin);
2113 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2115 /* Be smart and try to figure out the minimum we have to do */
2117 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2118 lvItem.mask |= LVIF_TEXT;
2119 lvItem.iItem = nItem;
2120 lvItem.iSubItem = 0;
2121 lvItem.pszText = szDispText;
2122 lvItem.cchTextMax = DISP_TEXT_SIZE;
2123 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2124 if (uView == LVS_ICON)
2126 lvItem.mask |= LVIF_STATE;
2127 lvItem.stateMask = LVIS_FOCUSED;
2128 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2130 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0);
2132 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2138 * Returns the current icon position, and advances it along the top.
2139 * The returned position is not offset by Origin.
2142 * [I] infoPtr : valid pointer to the listview structure
2143 * [O] lpPos : will get the current icon position
2148 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2150 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2152 *lpPos = infoPtr->currIconPos;
2154 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2155 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2157 infoPtr->currIconPos.x = 0;
2158 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2164 * Returns the current icon position, and advances it down the left edge.
2165 * The returned position is not offset by Origin.
2168 * [I] infoPtr : valid pointer to the listview structure
2169 * [O] lpPos : will get the current icon position
2174 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2176 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2178 *lpPos = infoPtr->currIconPos;
2180 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2181 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2183 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2184 infoPtr->currIconPos.y = 0;
2190 * Moves an icon to the specified position.
2191 * It takes care of invalidating the item, etc.
2194 * [I] infoPtr : valid pointer to the listview structure
2195 * [I] nItem : the item to move
2196 * [I] lpPos : the new icon position
2197 * [I] isNew : flags the item as being new
2203 static BOOL LISTVIEW_MoveIconTo(LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2209 old.x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2210 old.y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2212 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2213 LISTVIEW_InvalidateItem(infoPtr, nItem);
2216 /* Allocating a POINTER for every item is too resource intensive,
2217 * so we'll keep the (x,y) in different arrays */
2218 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)lppt->x)) return FALSE;
2219 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)lppt->y)) return FALSE;
2221 LISTVIEW_InvalidateItem(infoPtr, nItem);
2228 * Arranges listview items in icon display mode.
2231 * [I] infoPtr : valid pointer to the listview structure
2232 * [I] nAlignCode : alignment code
2238 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2240 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2241 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2245 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2247 TRACE("nAlignCode=%d\n", nAlignCode);
2249 if (nAlignCode == LVA_DEFAULT)
2251 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2252 else nAlignCode = LVA_ALIGNTOP;
2257 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2258 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2259 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2260 default: return FALSE;
2263 infoPtr->bAutoarrange = TRUE;
2264 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2265 for (i = 0; i < infoPtr->nItemCount; i++)
2267 next_pos(infoPtr, &pos);
2268 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2276 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2279 * [I] infoPtr : valid pointer to the listview structure
2280 * [O] lprcView : bounding rectangle
2286 static void LISTVIEW_GetAreaRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2290 SetRectEmpty(lprcView);
2292 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2296 for (i = 0; i < infoPtr->nItemCount; i++)
2298 x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, i);
2299 y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, i);
2300 lprcView->right = max(lprcView->right, x);
2301 lprcView->bottom = max(lprcView->bottom, y);
2303 if (infoPtr->nItemCount > 0)
2305 lprcView->right += infoPtr->nItemWidth;
2306 lprcView->bottom += infoPtr->nItemHeight;
2311 y = LISTVIEW_GetCountPerColumn(infoPtr);
2312 x = infoPtr->nItemCount / y;
2313 if (infoPtr->nItemCount % y) x++;
2314 lprcView->right = x * infoPtr->nItemWidth;
2315 lprcView->bottom = y * infoPtr->nItemHeight;
2319 lprcView->right = infoPtr->nItemWidth;
2320 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2327 * Retrieves the bounding rectangle of all the items.
2330 * [I] infoPtr : valid pointer to the listview structure
2331 * [O] lprcView : bounding rectangle
2337 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2341 TRACE("(lprcView=%p)\n", lprcView);
2343 if (!lprcView) return FALSE;
2345 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2346 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2347 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2349 TRACE("lprcView=%s\n", debugrect(lprcView));
2356 * Retrieves the subitem pointer associated with the subitem index.
2359 * [I] hdpaSubItems : DPA handle for a specific item
2360 * [I] nSubItem : index of subitem
2363 * SUCCESS : subitem pointer
2366 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2368 SUBITEM_INFO *lpSubItem;
2371 /* we should binary search here if need be */
2372 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2374 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
2375 if (lpSubItem->iSubItem == nSubItem)
2385 * Caclulates the desired item width.
2388 * [I] infoPtr : valid pointer to the listview structure
2391 * The desired item width.
2393 static INT LISTVIEW_CalculateItemWidth(LISTVIEW_INFO *infoPtr)
2395 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2398 TRACE("uView=%d\n", uView);
2400 if (uView == LVS_ICON)
2401 nItemWidth = infoPtr->iconSpacing.cx;
2402 else if (uView == LVS_REPORT)
2406 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2408 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2409 nItemWidth = rcHeader.right;
2412 else /* LVS_SMALLICON, or LVS_LIST */
2416 for (i = 0; i < infoPtr->nItemCount; i++)
2417 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2419 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2420 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2422 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2425 return max(nItemWidth, 1);
2430 * Caclulates the desired item height.
2433 * [I] infoPtr : valid pointer to the listview structure
2436 * The desired item height.
2438 static INT LISTVIEW_CalculateItemHeight(LISTVIEW_INFO *infoPtr)
2440 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2443 TRACE("uView=%d\n", uView);
2445 if (uView == LVS_ICON)
2446 nItemHeight = infoPtr->iconSpacing.cy;
2449 nItemHeight = infoPtr->ntmHeight;
2450 if (infoPtr->himlState)
2451 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2452 if (infoPtr->himlSmall)
2453 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2454 if (infoPtr->himlState || infoPtr->himlSmall)
2455 nItemHeight += HEIGHT_PADDING;
2456 if (infoPtr->nMeasureItemHeight > 0)
2457 nItemHeight = infoPtr->nMeasureItemHeight;
2460 return max(nItemHeight, 1);
2465 * Updates the width, and height of an item.
2468 * [I] infoPtr : valid pointer to the listview structure
2473 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2475 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2476 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2482 * Retrieves and saves important text metrics info for the current
2486 * [I] infoPtr : valid pointer to the listview structure
2489 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2491 HDC hdc = GetDC(infoPtr->hwndSelf);
2492 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2493 HFONT hOldFont = SelectObject(hdc, hFont);
2496 if (GetTextMetricsW(hdc, &tm))
2498 infoPtr->ntmHeight = tm.tmHeight;
2499 infoPtr->ntmAveCharWidth = tm.tmAveCharWidth;
2501 SelectObject(hdc, hOldFont);
2502 ReleaseDC(infoPtr->hwndSelf, hdc);
2504 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2509 * A compare function for ranges
2512 * [I] range1 : pointer to range 1;
2513 * [I] range2 : pointer to range 2;
2517 * > 0 : if range 1 > range 2
2518 * < 0 : if range 2 > range 1
2519 * = 0 : if range intersects range 2
2521 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2525 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2527 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2532 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2538 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2540 #define ranges_check(ranges, desc) do { } while(0)
2543 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2548 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2550 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2551 ranges_dump(ranges);
2552 prev = (RANGE *)DPA_GetPtr(ranges->hdpa, 0);
2553 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2554 assert (prev->lower >= 0 && prev->lower < prev->upper);
2555 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2557 curr = (RANGE *)DPA_GetPtr(ranges->hdpa, i);
2558 assert (prev->upper <= curr->lower);
2559 assert (curr->lower < curr->upper);
2562 TRACE("--- Done checking---\n");
2565 static RANGES ranges_create(int count)
2567 RANGES ranges = (RANGES)Alloc(sizeof(struct tagRANGES));
2568 if (!ranges) return NULL;
2569 ranges->hdpa = DPA_Create(count);
2570 if (ranges->hdpa) return ranges;
2575 static void ranges_clear(RANGES ranges)
2579 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2580 Free(DPA_GetPtr(ranges->hdpa, i));
2581 DPA_DeleteAllPtrs(ranges->hdpa);
2585 static void ranges_destroy(RANGES ranges)
2587 if (!ranges) return;
2588 ranges_clear(ranges);
2589 DPA_Destroy(ranges->hdpa);
2593 static RANGES ranges_clone(RANGES ranges)
2598 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2600 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2602 RANGE *newrng = (RANGE *)Alloc(sizeof(RANGE));
2603 if (!newrng) goto fail;
2604 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2605 DPA_SetPtr(clone->hdpa, i, newrng);
2610 TRACE ("clone failed\n");
2611 ranges_destroy(clone);
2615 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2619 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2620 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2625 static void ranges_dump(RANGES ranges)
2629 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2630 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2633 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2635 RANGE srchrng = { nItem, nItem + 1 };
2637 TRACE("(nItem=%d)\n", nItem);
2638 ranges_check(ranges, "before contain");
2639 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2642 static INT ranges_itemcount(RANGES ranges)
2646 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2648 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2649 count += sel->upper - sel->lower;
2655 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2657 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2660 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2661 if (index == -1) return TRUE;
2663 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2665 chkrng = DPA_GetPtr(ranges->hdpa, index);
2666 if (chkrng->lower >= nItem)
2667 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2668 if (chkrng->upper > nItem)
2669 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2674 static BOOL ranges_add(RANGES ranges, RANGE range)
2679 TRACE("(%s)\n", debugrange(&range));
2680 ranges_check(ranges, "before add");
2682 /* try find overlapping regions first */
2683 srchrgn.lower = range.lower - 1;
2684 srchrgn.upper = range.upper + 1;
2685 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2691 TRACE("Adding new range\n");
2693 /* create the brand new range to insert */
2694 newrgn = (RANGE *)Alloc(sizeof(RANGE));
2695 if(!newrgn) goto fail;
2698 /* figure out where to insert it */
2699 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2700 TRACE("index=%d\n", index);
2701 if (index == -1) index = 0;
2703 /* and get it over with */
2704 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2712 RANGE *chkrgn, *mrgrgn;
2713 INT fromindex, mergeindex;
2715 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2716 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2718 chkrgn->lower = min(range.lower, chkrgn->lower);
2719 chkrgn->upper = max(range.upper, chkrgn->upper);
2721 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2723 /* merge now common anges */
2725 srchrgn.lower = chkrgn->lower - 1;
2726 srchrgn.upper = chkrgn->upper + 1;
2730 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2731 if (mergeindex == -1) break;
2732 if (mergeindex == index)
2734 fromindex = index + 1;
2738 TRACE("Merge with index %i\n", mergeindex);
2740 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2741 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2742 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2744 DPA_DeletePtr(ranges->hdpa, mergeindex);
2745 if (mergeindex < index) index --;
2749 ranges_check(ranges, "after add");
2753 ranges_check(ranges, "failed add");
2757 static BOOL ranges_del(RANGES ranges, RANGE range)
2762 TRACE("(%s)\n", debugrange(&range));
2763 ranges_check(ranges, "before del");
2765 /* we don't use DPAS_SORTED here, since we need *
2766 * to find the first overlapping range */
2767 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2770 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2772 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2774 /* case 1: Same range */
2775 if ( (chkrgn->upper == range.upper) &&
2776 (chkrgn->lower == range.lower) )
2778 DPA_DeletePtr(ranges->hdpa, index);
2781 /* case 2: engulf */
2782 else if ( (chkrgn->upper <= range.upper) &&
2783 (chkrgn->lower >= range.lower) )
2785 DPA_DeletePtr(ranges->hdpa, index);
2787 /* case 3: overlap upper */
2788 else if ( (chkrgn->upper <= range.upper) &&
2789 (chkrgn->lower < range.lower) )
2791 chkrgn->upper = range.lower;
2793 /* case 4: overlap lower */
2794 else if ( (chkrgn->upper > range.upper) &&
2795 (chkrgn->lower >= range.lower) )
2797 chkrgn->lower = range.upper;
2800 /* case 5: fully internal */
2803 RANGE tmprgn = *chkrgn, *newrgn;
2805 if (!(newrgn = (RANGE *)Alloc(sizeof(RANGE)))) goto fail;
2806 newrgn->lower = chkrgn->lower;
2807 newrgn->upper = range.lower;
2808 chkrgn->lower = range.upper;
2809 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2818 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2821 ranges_check(ranges, "after del");
2825 ranges_check(ranges, "failed del");
2831 * Removes all selection ranges
2834 * [I] infoPtr : valid pointer to the listview structure
2835 * [I] toSkip : item range to skip removing the selection
2841 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2850 lvItem.stateMask = LVIS_SELECTED;
2852 /* need to clone the DPA because callbacks can change it */
2853 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2854 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2855 while(iterator_next(&i))
2856 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2857 /* note that the iterator destructor will free the cloned range */
2858 iterator_destroy(&i);
2863 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2867 if (!(toSkip = ranges_create(1))) return FALSE;
2868 if (nItem != -1) ranges_additem(toSkip, nItem);
2869 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2870 ranges_destroy(toSkip);
2874 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2876 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2881 * Retrieves the number of items that are marked as selected.
2884 * [I] infoPtr : valid pointer to the listview structure
2887 * Number of items selected.
2889 static INT LISTVIEW_GetSelectedCount(LISTVIEW_INFO *infoPtr)
2891 INT nSelectedCount = 0;
2893 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2896 for (i = 0; i < infoPtr->nItemCount; i++)
2898 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2903 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2905 TRACE("nSelectedCount=%d\n", nSelectedCount);
2906 return nSelectedCount;
2911 * Manages the item focus.
2914 * [I] infoPtr : valid pointer to the listview structure
2915 * [I] nItem : item index
2918 * TRUE : focused item changed
2919 * FALSE : focused item has NOT changed
2921 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2923 INT oldFocus = infoPtr->nFocusedItem;
2926 if (nItem == infoPtr->nFocusedItem) return FALSE;
2928 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2929 lvItem.stateMask = LVIS_FOCUSED;
2930 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
2932 return oldFocus != infoPtr->nFocusedItem;
2935 /* Helper function for LISTVIEW_ShiftIndices *only* */
2936 static INT shift_item(LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2938 if (nShiftItem < nItem) return nShiftItem;
2940 if (nShiftItem > nItem) return nShiftItem + direction;
2942 if (direction > 0) return nShiftItem + direction;
2944 return min(nShiftItem, infoPtr->nItemCount - 1);
2949 * Updates the various indices after an item has been inserted or deleted.
2952 * [I] infoPtr : valid pointer to the listview structure
2953 * [I] nItem : item index
2954 * [I] direction : Direction of shift, +1 or -1.
2959 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
2964 /* temporarily disable change notification while shifting items */
2965 bOldChange = infoPtr->bDoChangeNotify;
2966 infoPtr->bDoChangeNotify = FALSE;
2968 TRACE("Shifting %iu, %i steps\n", nItem, direction);
2970 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
2972 assert(abs(direction) == 1);
2974 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
2976 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
2977 if (nNewFocus != infoPtr->nFocusedItem)
2978 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
2980 /* But we are not supposed to modify nHotItem! */
2982 infoPtr->bDoChangeNotify = bOldChange;
2988 * Adds a block of selections.
2991 * [I] infoPtr : valid pointer to the listview structure
2992 * [I] nItem : item index
2997 static void LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2999 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3000 INT nLast = max(infoPtr->nSelectionMark, nItem);
3001 NMLVODSTATECHANGE nmlv;
3006 /* Temporarily disable change notification
3007 * If the control is LVS_OWNERDATA, we need to send
3008 * only one LVN_ODSTATECHANGED notification.
3009 * See MSDN documentation for LVN_ITEMCHANGED.
3011 bOldChange = infoPtr->bDoChangeNotify;
3012 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3014 if (nFirst == -1) nFirst = nItem;
3016 item.state = LVIS_SELECTED;
3017 item.stateMask = LVIS_SELECTED;
3019 for (i = nFirst; i <= nLast; i++)
3020 LISTVIEW_SetItemState(infoPtr,i,&item);
3022 ZeroMemory(&nmlv, sizeof(nmlv));
3023 nmlv.iFrom = nFirst;
3026 nmlv.uOldState = item.state;
3028 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3029 infoPtr->bDoChangeNotify = bOldChange;
3035 * Sets a single group selection.
3038 * [I] infoPtr : valid pointer to the listview structure
3039 * [I] nItem : item index
3044 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3046 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3051 if (!(selection = ranges_create(100))) return;
3053 item.state = LVIS_SELECTED;
3054 item.stateMask = LVIS_SELECTED;
3056 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3058 if (infoPtr->nSelectionMark == -1)
3060 infoPtr->nSelectionMark = nItem;
3061 ranges_additem(selection, nItem);
3067 sel.lower = min(infoPtr->nSelectionMark, nItem);
3068 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3069 ranges_add(selection, sel);
3074 RECT rcItem, rcSel, rcSelMark;
3077 rcItem.left = LVIR_BOUNDS;
3078 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3079 rcSelMark.left = LVIR_BOUNDS;
3080 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3081 UnionRect(&rcSel, &rcItem, &rcSelMark);
3082 iterator_frameditems(&i, infoPtr, &rcSel);
3083 while(iterator_next(&i))
3085 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3086 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3088 iterator_destroy(&i);
3091 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3092 iterator_rangesitems(&i, selection);
3093 while(iterator_next(&i))
3094 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3095 /* this will also destroy the selection */
3096 iterator_destroy(&i);
3098 LISTVIEW_SetItemFocus(infoPtr, nItem);
3103 * Sets a single selection.
3106 * [I] infoPtr : valid pointer to the listview structure
3107 * [I] nItem : item index
3112 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3116 TRACE("nItem=%d\n", nItem);
3118 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3120 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3121 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3122 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3124 infoPtr->nSelectionMark = nItem;
3129 * Set selection(s) with keyboard.
3132 * [I] infoPtr : valid pointer to the listview structure
3133 * [I] nItem : item index
3136 * SUCCESS : TRUE (needs to be repainted)
3137 * FAILURE : FALSE (nothing has changed)
3139 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
3141 /* FIXME: pass in the state */
3142 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3143 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3144 BOOL bResult = FALSE;
3146 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3148 if (infoPtr->dwStyle & LVS_SINGLESEL)
3151 LISTVIEW_SetSelection(infoPtr, nItem);
3158 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3162 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3167 LISTVIEW_SetSelection(infoPtr, nItem);
3170 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3173 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3177 static BOOL LISTVIEW_GetItemAtPt(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3179 LVHITTESTINFO lvHitTestInfo;
3181 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3182 lvHitTestInfo.pt.x = pt.x;
3183 lvHitTestInfo.pt.y = pt.y;
3185 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3187 lpLVItem->mask = LVIF_PARAM;
3188 lpLVItem->iItem = lvHitTestInfo.iItem;
3189 lpLVItem->iSubItem = 0;
3191 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3196 * Called when the mouse is being actively tracked and has hovered for a specified
3200 * [I] infoPtr : valid pointer to the listview structure
3201 * [I] fwKeys : key indicator
3202 * [I] x,y : mouse position
3205 * 0 if the message was processed, non-zero if there was an error
3208 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3209 * over the item for a certain period of time.
3212 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKyes, INT x, INT y)
3214 if (infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
3222 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3223 LISTVIEW_SetSelection(infoPtr, item.iItem);
3231 * Called whenever WM_MOUSEMOVE is received.
3234 * [I] infoPtr : valid pointer to the listview structure
3235 * [I] fwKeys : key indicator
3236 * [I] x,y : mouse position
3239 * 0 if the message is processed, non-zero if there was an error
3241 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3243 TRACKMOUSEEVENT trackinfo;
3245 if (infoPtr->bLButtonDown && DragDetect(infoPtr->hwndSelf, infoPtr->ptClickPos))
3247 LVHITTESTINFO lvHitTestInfo;
3250 lvHitTestInfo.pt = infoPtr->ptClickPos;
3251 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3253 ZeroMemory(&nmlv, sizeof(nmlv));
3254 nmlv.iItem = lvHitTestInfo.iItem;
3255 nmlv.ptAction = infoPtr->ptClickPos;
3257 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3262 /* see if we are supposed to be tracking mouse hovering */
3263 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
3264 /* fill in the trackinfo struct */
3265 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3266 trackinfo.dwFlags = TME_QUERY;
3267 trackinfo.hwndTrack = infoPtr->hwndSelf;
3268 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3270 /* see if we are already tracking this hwnd */
3271 _TrackMouseEvent(&trackinfo);
3273 if(!(trackinfo.dwFlags & TME_HOVER)) {
3274 trackinfo.dwFlags = TME_HOVER;
3276 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3277 _TrackMouseEvent(&trackinfo);
3286 * Tests wheather the item is assignable to a list with style lStyle
3288 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3290 if ( (lpLVItem->mask & LVIF_TEXT) &&
3291 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3292 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3300 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3303 * [I] infoPtr : valid pointer to the listview structure
3304 * [I] lpLVItem : valid pointer to new item atttributes
3305 * [I] isNew : the item being set is being inserted
3306 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3307 * [O] bChanged : will be set to TRUE if the item really changed
3313 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3315 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3323 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3325 if (lpLVItem->mask == 0) return TRUE;
3327 if (infoPtr->dwStyle & LVS_OWNERDATA)
3329 /* a virtual listview we stores only selection and focus */
3330 if (lpLVItem->mask & ~LVIF_STATE)
3336 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3337 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
3341 /* we need to get the lParam and state of the item */
3342 item.iItem = lpLVItem->iItem;
3343 item.iSubItem = lpLVItem->iSubItem;
3344 item.mask = LVIF_STATE | LVIF_PARAM;
3345 item.stateMask = ~0;
3348 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3350 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3351 /* determine what fields will change */
3352 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
3353 uChanged |= LVIF_STATE;
3355 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3356 uChanged |= LVIF_IMAGE;
3358 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3359 uChanged |= LVIF_PARAM;
3361 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3362 uChanged |= LVIF_INDENT;
3364 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3365 uChanged |= LVIF_TEXT;
3367 TRACE("uChanged=0x%x\n", uChanged);
3368 if (!uChanged) return TRUE;
3371 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3372 nmlv.iItem = lpLVItem->iItem;
3373 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3374 nmlv.uOldState = item.state;
3375 nmlv.uChanged = uChanged;
3376 nmlv.lParam = item.lParam;
3378 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3379 /* and we are _NOT_ virtual (LVS_OWERNDATA), and change notifications */
3381 if(lpItem && !isNew && infoPtr->bDoChangeNotify &&
3382 notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3385 /* copy information */
3386 if (lpLVItem->mask & LVIF_TEXT)
3387 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3389 if (lpLVItem->mask & LVIF_IMAGE)
3390 lpItem->hdr.iImage = lpLVItem->iImage;
3392 if (lpLVItem->mask & LVIF_PARAM)
3393 lpItem->lParam = lpLVItem->lParam;
3395 if (lpLVItem->mask & LVIF_INDENT)
3396 lpItem->iIndent = lpLVItem->iIndent;
3398 if (uChanged & LVIF_STATE)
3400 if (lpItem && (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3402 lpItem->state &= ~lpLVItem->stateMask;
3403 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3405 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3407 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3408 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3410 else if (lpLVItem->stateMask & LVIS_SELECTED)
3411 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3413 /* if we are asked to change focus, and we manage it, do it */
3414 if (lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3416 if (lpLVItem->state & LVIS_FOCUSED)
3418 LISTVIEW_SetItemFocus(infoPtr, -1);
3419 infoPtr->nFocusedItem = lpLVItem->iItem;
3420 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3422 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3423 infoPtr->nFocusedItem = -1;
3427 /* if we're inserting the item, we're done */
3428 if (isNew) return TRUE;
3430 /* send LVN_ITEMCHANGED notification */
3431 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3432 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3439 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3442 * [I] infoPtr : valid pointer to the listview structure
3443 * [I] lpLVItem : valid pointer to new subitem atttributes
3444 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3445 * [O] bChanged : will be set to TRUE if the item really changed
3451 static BOOL set_sub_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3454 SUBITEM_INFO *lpSubItem;
3456 /* we do not support subitems for virtual listviews */
3457 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3459 /* set subitem only if column is present */
3460 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3462 /* First do some sanity checks */
3463 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE)) return FALSE;
3464 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE))) return TRUE;
3466 /* get the subitem structure, and create it if not there */
3467 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3468 assert (hdpaSubItems);
3470 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3473 SUBITEM_INFO *tmpSubItem;
3476 lpSubItem = (SUBITEM_INFO *)Alloc(sizeof(SUBITEM_INFO));
3477 if (!lpSubItem) return FALSE;
3478 /* we could binary search here, if need be...*/
3479 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3481 tmpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
3482 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3484 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3489 lpSubItem->iSubItem = lpLVItem->iSubItem;
3490 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3494 if (lpLVItem->mask & LVIF_IMAGE)
3495 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3497 lpSubItem->hdr.iImage = lpLVItem->iImage;
3501 if (lpLVItem->mask & LVIF_TEXT)
3502 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3504 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3513 * Sets item attributes.
3516 * [I] infoPtr : valid pointer to the listview structure
3517 * [I] lpLVItem : new item atttributes
3518 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3524 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
3526 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3527 LPWSTR pszText = NULL;
3528 BOOL bResult, bChanged = FALSE;
3530 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3532 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3535 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3536 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3538 pszText = lpLVItem->pszText;
3539 ((LVITEMW *)lpLVItem)->pszText = textdupTtoW(lpLVItem->pszText, isW);
3542 /* actually set the fields */
3543 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3545 if (lpLVItem->iSubItem)
3546 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3548 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3550 /* redraw item, if necessary */
3551 if (bChanged && !infoPtr->bIsDrawing)
3553 /* this little optimization eliminates some nasty flicker */
3554 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3555 (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) || lpLVItem->iSubItem) )
3556 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3558 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3563 textfreeT(lpLVItem->pszText, isW);
3564 ((LVITEMW *)lpLVItem)->pszText = pszText;
3572 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3575 * [I] infoPtr : valid pointer to the listview structure
3580 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *infoPtr)
3582 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3584 SCROLLINFO scrollInfo;
3586 scrollInfo.cbSize = sizeof(SCROLLINFO);
3587 scrollInfo.fMask = SIF_POS;
3589 if (uView == LVS_LIST)
3591 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3592 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3594 else if (uView == LVS_REPORT)
3596 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3597 nItem = scrollInfo.nPos;
3601 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3602 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3605 TRACE("nItem=%d\n", nItem);
3613 * Erases the background of the given rectangle
3616 * [I] infoPtr : valid pointer to the listview structure
3617 * [I] hdc : device context handle
3618 * [I] lprcBox : clipping rectangle
3624 static inline BOOL LISTVIEW_FillBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3626 if (!infoPtr->hBkBrush) return FALSE;
3628 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, debugrect(lprcBox), infoPtr->hBkBrush);
3630 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3638 * [I] infoPtr : valid pointer to the listview structure
3639 * [I] hdc : device context handle
3640 * [I] nItem : item index
3641 * [I] nSubItem : subitem index
3642 * [I] pos : item position in client coordinates
3643 * [I] cdmode : custom draw mode
3649 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3651 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3652 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3653 static const WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3654 DWORD cdsubitemmode = CDRF_DODEFAULT;
3655 RECT* lprcFocus, rcSelect, rcBox, rcState, rcIcon, rcLabel;
3656 NMLVCUSTOMDRAW nmlvcd;
3660 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, debugpoint(&pos));
3662 /* get information needed for drawing the item */
3663 lvItem.mask = LVIF_TEXT | LVIF_IMAGE;
3664 if (nSubItem == 0) lvItem.mask |= LVIF_STATE | LVIF_PARAM;
3665 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3666 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3667 lvItem.iItem = nItem;
3668 lvItem.iSubItem = nSubItem;
3671 lvItem.cchTextMax = DISP_TEXT_SIZE;
3672 lvItem.pszText = szDispText;
3673 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3674 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3675 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3676 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = (LPWSTR)szCallback;
3677 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3679 /* now check if we need to update the focus rectangle */
3680 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3682 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3683 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
3684 OffsetRect(&rcBox, pos.x, pos.y);
3685 OffsetRect(&rcState, pos.x, pos.y);
3686 OffsetRect(&rcIcon, pos.x, pos.y);
3687 OffsetRect(&rcLabel, pos.x, pos.y);
3688 TRACE(" rcBox=%s, rcState=%s, rcIcon=%s. rcLabel=%s\n",
3689 debugrect(&rcBox), debugrect(&rcState), debugrect(&rcIcon), debugrect(&rcLabel));
3691 /* fill in the custom draw structure */
3692 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3694 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3695 if (cdmode & CDRF_NOTIFYITEMDRAW)
3696 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3697 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3698 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3699 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3700 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3702 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3703 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3705 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3706 prepaint_setup(infoPtr, hdc, &nmlvcd);
3708 /* in full row select, subitems, will just use main item's colors */
3709 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3710 nmlvcd.clrTextBk = CLR_NONE;
3713 if (infoPtr->himlState && !IsRectEmpty(&rcState))
3715 UINT uStateImage = (lvItem.state & LVIS_STATEIMAGEMASK) >> 12;
3718 TRACE("uStateImage=%d\n", uStateImage);
3719 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc, rcState.left, rcState.top, ILD_NORMAL);
3724 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3725 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3727 TRACE("iImage=%d\n", lvItem.iImage);
3728 ImageList_Draw(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3729 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3732 /* Don't bother painting item being edited */
3733 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3735 /* draw the selection background, if we're drawing the main item */
3739 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3740 rcSelect.right = rcBox.right;
3742 if (nmlvcd.clrTextBk != CLR_NONE)
3743 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3744 if(lprcFocus) *lprcFocus = rcSelect;
3747 /* figure out the text drawing flags */
3748 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3749 if (uView == LVS_ICON)
3750 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3753 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3755 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3756 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3757 default: uFormat |= DT_LEFT;
3760 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3762 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3763 else rcLabel.left += LABEL_HOR_PADDING;
3765 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3766 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3769 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3770 notify_postpaint(infoPtr, &nmlvcd);
3776 * Draws listview items when in owner draw mode.
3779 * [I] infoPtr : valid pointer to the listview structure
3780 * [I] hdc : device context handle
3785 static void LISTVIEW_RefreshOwnerDraw(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3787 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
3788 DWORD cditemmode = CDRF_DODEFAULT;
3789 NMLVCUSTOMDRAW nmlvcd;
3790 POINT Origin, Position;
3796 ZeroMemory(&dis, sizeof(dis));
3798 /* Get scroll info once before loop */
3799 LISTVIEW_GetOrigin(infoPtr, &Origin);
3801 /* iterate through the invalidated rows */
3802 while(iterator_next(i))
3804 item.iItem = i->nItem;
3806 item.mask = LVIF_PARAM | LVIF_STATE;
3807 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3808 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3810 dis.CtlType = ODT_LISTVIEW;
3812 dis.itemID = item.iItem;
3813 dis.itemAction = ODA_DRAWENTIRE;
3815 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3816 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3817 dis.hwndItem = infoPtr->hwndSelf;
3819 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3820 dis.rcItem.left = Position.x + Origin.x;
3821 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3822 dis.rcItem.top = Position.y + Origin.y;
3823 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3824 dis.itemData = item.lParam;
3826 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), debugrect(&dis.rcItem));
3829 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
3830 * structure for the rest. of the paint cycle
3832 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
3833 if (cdmode & CDRF_NOTIFYITEMDRAW)
3834 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3836 if (!(cditemmode & CDRF_SKIPDEFAULT))
3838 prepaint_setup (infoPtr, hdc, &nmlvcd);
3839 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3842 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3843 notify_postpaint(infoPtr, &nmlvcd);
3849 * Draws listview items when in report display mode.
3852 * [I] infoPtr : valid pointer to the listview structure
3853 * [I] hdc : device context handle
3854 * [I] cdmode : custom draw mode
3859 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3862 RECT rcClip, rcItem;
3863 POINT Origin, Position;
3869 /* figure out what to draw */
3870 rgntype = GetClipBox(hdc, &rcClip);
3871 if (rgntype == NULLREGION) return;
3873 /* Get scroll info once before loop */
3874 LISTVIEW_GetOrigin(infoPtr, &Origin);
3876 /* narrow down the columns we need to paint */
3877 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
3879 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
3880 if (rcItem.right + Origin.x >= rcClip.left) break;
3882 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
3884 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
3885 if (rcItem.left + Origin.x < rcClip.right) break;
3887 iterator_rangeitems(&j, colRange);
3889 /* in full row select, we _have_ to draw the main item */
3890 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
3893 /* iterate through the invalidated rows */
3894 while(iterator_next(i))
3896 /* iterate through the invalidated columns */
3897 while(iterator_next(&j))
3899 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
3900 Position.x += Origin.x;
3901 Position.y += Origin.y;
3903 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
3905 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
3907 rcItem.bottom = infoPtr->nItemHeight;
3908 OffsetRect(&rcItem, Position.x, Position.y);
3909 if (!RectVisible(hdc, &rcItem)) continue;
3912 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
3915 iterator_destroy(&j);
3920 * Draws listview items when in list display mode.
3923 * [I] infoPtr : valid pointer to the listview structure
3924 * [I] hdc : device context handle
3925 * [I] cdmode : custom draw mode
3930 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3932 POINT Origin, Position;
3934 /* Get scroll info once before loop */
3935 LISTVIEW_GetOrigin(infoPtr, &Origin);
3937 while(iterator_prev(i))
3939 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
3940 Position.x += Origin.x;
3941 Position.y += Origin.y;
3943 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
3950 * Draws listview items.
3953 * [I] infoPtr : valid pointer to the listview structure
3954 * [I] hdc : device context handle
3959 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc)
3961 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3962 COLORREF oldTextColor, oldClrTextBk, oldClrText;
3963 NMLVCUSTOMDRAW nmlvcd;
3970 LISTVIEW_DUMP(infoPtr);
3972 infoPtr->bIsDrawing = TRUE;
3974 /* save dc values we're gonna trash while drawing */
3975 hOldFont = SelectObject(hdc, infoPtr->hFont);
3976 oldBkMode = GetBkMode(hdc);
3977 infoPtr->clrTextBkDefault = GetBkColor(hdc);
3978 oldTextColor = GetTextColor(hdc);
3980 oldClrTextBk = infoPtr->clrTextBk;
3981 oldClrText = infoPtr->clrText;
3983 infoPtr->cditemmode = CDRF_DODEFAULT;
3985 GetClientRect(infoPtr->hwndSelf, &rcClient);
3986 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
3987 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3988 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
3989 prepaint_setup(infoPtr, hdc, &nmlvcd);
3991 /* Use these colors to draw the items */
3992 infoPtr->clrTextBk = nmlvcd.clrTextBk;
3993 infoPtr->clrText = nmlvcd.clrText;
3995 /* nothing to draw */
3996 if(infoPtr->nItemCount == 0) goto enddraw;
3998 /* figure out what we need to draw */
3999 iterator_visibleitems(&i, infoPtr, hdc);
4001 /* send cache hint notification */
4002 if (infoPtr->dwStyle & LVS_OWNERDATA)
4004 RANGE range = iterator_range(&i);
4007 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4008 nmlv.iFrom = range.lower;
4009 nmlv.iTo = range.upper - 1;
4010 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4013 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
4014 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4017 if (uView == LVS_REPORT)
4018 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4019 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
4020 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4022 /* if we have a focus rect, draw it */
4023 if (infoPtr->bFocus)
4024 DrawFocusRect(hdc, &infoPtr->rcFocus);
4026 iterator_destroy(&i);
4029 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4030 notify_postpaint(infoPtr, &nmlvcd);
4032 infoPtr->clrTextBk = oldClrTextBk;
4033 infoPtr->clrText = oldClrText;
4035 SelectObject(hdc, hOldFont);
4036 SetBkMode(hdc, oldBkMode);
4037 SetBkColor(hdc, infoPtr->clrTextBkDefault);
4038 SetTextColor(hdc, oldTextColor);
4039 infoPtr->bIsDrawing = FALSE;
4045 * Calculates the approximate width and height of a given number of items.
4048 * [I] infoPtr : valid pointer to the listview structure
4049 * [I] nItemCount : number of items
4050 * [I] wWidth : width
4051 * [I] wHeight : height
4054 * Returns a DWORD. The width in the low word and the height in high word.
4056 static DWORD LISTVIEW_ApproximateViewRect(LISTVIEW_INFO *infoPtr, INT nItemCount,
4057 WORD wWidth, WORD wHeight)
4059 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4060 INT nItemCountPerColumn = 1;
4061 INT nColumnCount = 0;
4062 DWORD dwViewRect = 0;
4064 if (nItemCount == -1)
4065 nItemCount = infoPtr->nItemCount;
4067 if (uView == LVS_LIST)
4069 if (wHeight == 0xFFFF)
4071 /* use current height */
4072 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4075 if (wHeight < infoPtr->nItemHeight)
4076 wHeight = infoPtr->nItemHeight;
4080 if (infoPtr->nItemHeight > 0)
4082 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4083 if (nItemCountPerColumn == 0)
4084 nItemCountPerColumn = 1;
4086 if (nItemCount % nItemCountPerColumn != 0)
4087 nColumnCount = nItemCount / nItemCountPerColumn;
4089 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4093 /* Microsoft padding magic */
4094 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4095 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4097 dwViewRect = MAKELONG(wWidth, wHeight);
4099 else if (uView == LVS_REPORT)
4103 if (infoPtr->nItemCount > 0)
4105 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4106 wWidth = rcBox.right - rcBox.left;
4107 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4111 /* use current height and width */
4112 if (wHeight == 0xffff)
4113 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4114 if (wWidth == 0xffff)
4115 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4118 dwViewRect = MAKELONG(wWidth, wHeight);
4120 else if (uView == LVS_SMALLICON)
4121 FIXME("uView == LVS_SMALLICON: not implemented\n");
4122 else if (uView == LVS_ICON)
4123 FIXME("uView == LVS_ICON: not implemented\n");
4131 * Create a drag image list for the specified item.
4134 * [I] infoPtr : valid pointer to the listview structure
4135 * [I] iItem : index of item
4136 * [O] lppt : Upperr-left corner of the image
4139 * Returns a handle to the image list if successful, NULL otherwise.
4141 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4147 HBITMAP hbmp, hOldbmp;
4148 HIMAGELIST dragList = 0;
4149 TRACE("iItem=%d Count=%d \n", iItem, infoPtr->nItemCount);
4151 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4154 rcItem.left = LVIR_BOUNDS;
4155 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4158 lppt->x = rcItem.left;
4159 lppt->y = rcItem.top;
4161 size.cx = rcItem.right - rcItem.left;
4162 size.cy = rcItem.bottom - rcItem.top;
4164 hdcOrig = GetDC(infoPtr->hwndSelf);
4165 hdc = CreateCompatibleDC(hdcOrig);
4166 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4167 hOldbmp = SelectObject(hdc, hbmp);
4169 rcItem.left = rcItem.top = 0;
4170 rcItem.right = size.cx;
4171 rcItem.bottom = size.cy;
4172 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4175 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4177 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4178 SelectObject(hdc, hOldbmp);
4179 ImageList_Add(dragList, hbmp, 0);
4182 SelectObject(hdc, hOldbmp);
4186 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4188 TRACE("ret=%p\n", dragList);
4196 * Removes all listview items and subitems.
4199 * [I] infoPtr : valid pointer to the listview structure
4205 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr)
4208 HDPA hdpaSubItems = NULL;
4215 /* we do it directly, to avoid notifications */
4216 ranges_clear(infoPtr->selectionRanges);
4217 infoPtr->nSelectionMark = -1;
4218 infoPtr->nFocusedItem = -1;
4219 SetRectEmpty(&infoPtr->rcFocus);
4220 /* But we are supposed to leave nHotItem as is! */
4223 /* send LVN_DELETEALLITEMS notification */
4224 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4226 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4228 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4230 /* send LVN_DELETEITEM notification, if not suppressed */
4231 if (!bSuppress) notify_deleteitem(infoPtr, i);
4232 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4234 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
4235 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4237 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, j);
4238 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4241 DPA_Destroy(hdpaSubItems);
4242 DPA_DeletePtr(infoPtr->hdpaItems, i);
4244 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4245 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4246 infoPtr->nItemCount --;
4249 LISTVIEW_UpdateScroll(infoPtr);
4251 LISTVIEW_InvalidateList(infoPtr);
4258 * Scrolls, and updates the columns, when a column is changing width.
4261 * [I] infoPtr : valid pointer to the listview structure
4262 * [I] nColumn : column to scroll
4263 * [I] dx : amount of scroll, in pixels
4268 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4270 COLUMN_INFO *lpColumnInfo;
4274 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4275 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4276 rcCol = lpColumnInfo->rcHeader;
4277 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4278 rcCol.left = rcCol.right;
4280 /* ajust the other columns */
4281 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4283 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4284 lpColumnInfo->rcHeader.left += dx;
4285 lpColumnInfo->rcHeader.right += dx;
4288 /* do not update screen if not in report mode */
4289 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4291 /* if we have a focus, must first erase the focus rect */
4292 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4294 /* Need to reset the item width when inserting a new column */
4295 infoPtr->nItemWidth += dx;
4297 LISTVIEW_UpdateScroll(infoPtr);
4299 /* scroll to cover the deleted column, and invalidate for redraw */
4300 rcOld = infoPtr->rcList;
4301 rcOld.left = rcCol.left;
4302 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4304 /* we can restore focus now */
4305 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4310 * Removes a column from the listview control.
4313 * [I] infoPtr : valid pointer to the listview structure
4314 * [I] nColumn : column index
4320 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4324 TRACE("nColumn=%d\n", nColumn);
4326 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4327 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4329 /* While the MSDN specifically says that column zero should not be deleted,
4330 what actually happens is that the column itself is deleted but no items or subitems
4334 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4336 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4339 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4340 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4342 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4344 SUBITEM_INFO *lpSubItem, *lpDelItem;
4346 INT nItem, nSubItem, i;
4348 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4350 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
4353 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4355 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
4356 if (lpSubItem->iSubItem == nColumn)
4359 lpDelItem = lpSubItem;
4361 else if (lpSubItem->iSubItem > nColumn)
4363 lpSubItem->iSubItem--;
4367 /* if we found our subitem, zapp it */
4371 if (is_textW(lpDelItem->hdr.pszText))
4372 Free(lpDelItem->hdr.pszText);
4377 /* free dpa memory */
4378 DPA_DeletePtr(hdpaSubItems, nSubItem);
4383 /* update the other column info */
4384 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4385 LISTVIEW_InvalidateList(infoPtr);
4387 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4394 * Invalidates the listview after an item's insertion or deletion.
4397 * [I] infoPtr : valid pointer to the listview structure
4398 * [I] nItem : item index
4399 * [I] dir : -1 if deleting, 1 if inserting
4404 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4406 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4407 INT nPerCol, nItemCol, nItemRow;
4411 /* if we don't refresh, what's the point of scrolling? */
4412 if (!is_redrawing(infoPtr)) return;
4414 assert (abs(dir) == 1);
4416 /* arrange icons if autoarrange is on */
4417 if (is_autoarrange(infoPtr))
4419 BOOL arrange = TRUE;
4420 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4421 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4422 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4425 /* scrollbars need updating */
4426 LISTVIEW_UpdateScroll(infoPtr);
4428 /* figure out the item's position */
4429 if (uView == LVS_REPORT)
4430 nPerCol = infoPtr->nItemCount + 1;
4431 else if (uView == LVS_LIST)
4432 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4433 else /* LVS_ICON, or LVS_SMALLICON */
4436 nItemCol = nItem / nPerCol;
4437 nItemRow = nItem % nPerCol;
4438 LISTVIEW_GetOrigin(infoPtr, &Origin);
4440 /* move the items below up a slot */
4441 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4442 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4443 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4444 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4445 OffsetRect(&rcScroll, Origin.x, Origin.y);
4446 TRACE("rcScroll=%s, dx=%d\n", debugrect(&rcScroll), dir * infoPtr->nItemHeight);
4447 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4449 TRACE("Scrolling rcScroll=%s, rcList=%s\n", debugrect(&rcScroll), debugrect(&infoPtr->rcList));
4450 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4451 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4454 /* report has only that column, so we're done */
4455 if (uView == LVS_REPORT) return;
4457 /* now for LISTs, we have to deal with the columns to the right */
4458 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4460 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4461 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4462 OffsetRect(&rcScroll, Origin.x, Origin.y);
4463 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4464 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4465 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4470 * Removes an item from the listview control.
4473 * [I] infoPtr : valid pointer to the listview structure
4474 * [I] nItem : item index
4480 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4482 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4485 TRACE("(nItem=%d)\n", nItem);
4487 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4489 /* remove selection, and focus */
4491 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4492 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4494 /* send LVN_DELETEITEM notification. */
4495 notify_deleteitem(infoPtr, nItem);
4497 /* we need to do this here, because we'll be deleting stuff */
4498 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4499 LISTVIEW_InvalidateItem(infoPtr, nItem);
4501 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4507 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4508 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4510 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, i);
4511 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4514 DPA_Destroy(hdpaSubItems);
4517 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4519 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4520 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4523 infoPtr->nItemCount--;
4524 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4526 /* now is the invalidation fun */
4527 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4534 * Callback implementation for editlabel control
4537 * [I] infoPtr : valid pointer to the listview structure
4538 * [I] pszText : modified text
4539 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4545 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4547 NMLVDISPINFOW dispInfo;
4549 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4551 ZeroMemory(&dispInfo, sizeof(dispInfo));
4552 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
4553 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4554 dispInfo.item.iSubItem = 0;
4555 dispInfo.item.stateMask = ~0;
4556 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4557 /* add the text from the edit in */
4558 dispInfo.item.mask |= LVIF_TEXT;
4559 dispInfo.item.pszText = pszText;
4560 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4562 /* Do we need to update the Item Text */
4563 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4564 if (!pszText) return TRUE;
4566 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4568 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nEditLabelItem);
4569 ITEM_INFO* lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
4570 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
4572 LISTVIEW_InvalidateItem(infoPtr, infoPtr->nEditLabelItem);
4577 ZeroMemory(&dispInfo, sizeof(dispInfo));
4578 dispInfo.item.mask = LVIF_TEXT;
4579 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4580 dispInfo.item.iSubItem = 0;
4581 dispInfo.item.pszText = pszText;
4582 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4583 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4588 * Begin in place editing of specified list view item
4591 * [I] infoPtr : valid pointer to the listview structure
4592 * [I] nItem : item index
4593 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4599 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4601 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4602 NMLVDISPINFOW dispInfo;
4605 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4607 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4608 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4610 infoPtr->nEditLabelItem = nItem;
4612 /* Is the EditBox still there, if so remove it */
4613 if(infoPtr->hwndEdit != 0)
4615 SetFocus(infoPtr->hwndSelf);
4616 infoPtr->hwndEdit = 0;
4619 LISTVIEW_SetSelection(infoPtr, nItem);
4620 LISTVIEW_SetItemFocus(infoPtr, nItem);
4621 LISTVIEW_InvalidateItem(infoPtr, nItem);
4623 rect.left = LVIR_LABEL;
4624 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4626 ZeroMemory(&dispInfo, sizeof(dispInfo));
4627 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4628 dispInfo.item.iItem = nItem;
4629 dispInfo.item.iSubItem = 0;
4630 dispInfo.item.stateMask = ~0;
4631 dispInfo.item.pszText = szDispText;
4632 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4633 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4635 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4636 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4637 if (!infoPtr->hwndEdit) return 0;
4639 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4641 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4642 infoPtr->hwndEdit = 0;
4646 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4647 SetFocus(infoPtr->hwndEdit);
4648 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4649 return infoPtr->hwndEdit;
4655 * Ensures the specified item is visible, scrolling into view if necessary.
4658 * [I] infoPtr : valid pointer to the listview structure
4659 * [I] nItem : item index
4660 * [I] bPartial : partially or entirely visible
4666 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4668 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4669 INT nScrollPosHeight = 0;
4670 INT nScrollPosWidth = 0;
4671 INT nHorzAdjust = 0;
4672 INT nVertAdjust = 0;
4675 RECT rcItem, rcTemp;
4677 rcItem.left = LVIR_BOUNDS;
4678 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4680 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4682 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4684 /* scroll left/right, but in LVS_REPORT mode */
4685 if (uView == LVS_LIST)
4686 nScrollPosWidth = infoPtr->nItemWidth;
4687 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4688 nScrollPosWidth = 1;
4690 if (rcItem.left < infoPtr->rcList.left)
4693 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
4698 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
4702 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
4704 /* scroll up/down, but not in LVS_LIST mode */
4705 if (uView == LVS_REPORT)
4706 nScrollPosHeight = infoPtr->nItemHeight;
4707 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
4708 nScrollPosHeight = 1;
4710 if (rcItem.top < infoPtr->rcList.top)
4713 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
4718 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
4722 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
4724 if (nScrollPosWidth)
4726 INT diff = nHorzDiff / nScrollPosWidth;
4727 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
4728 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
4731 if (nScrollPosHeight)
4733 INT diff = nVertDiff / nScrollPosHeight;
4734 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
4735 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
4743 * Searches for an item with specific characteristics.
4746 * [I] hwnd : window handle
4747 * [I] nStart : base item index
4748 * [I] lpFindInfo : item information to look for
4751 * SUCCESS : index of item
4754 static INT LISTVIEW_FindItemW(LISTVIEW_INFO *infoPtr, INT nStart,
4755 const LVFINDINFOW *lpFindInfo)
4757 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4758 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4759 BOOL bWrap = FALSE, bNearest = FALSE;
4760 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
4761 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
4762 POINT Position, Destination;
4765 if (!lpFindInfo || nItem < 0) return -1;
4768 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
4770 lvItem.mask |= LVIF_TEXT;
4771 lvItem.pszText = szDispText;
4772 lvItem.cchTextMax = DISP_TEXT_SIZE;
4775 if (lpFindInfo->flags & LVFI_WRAP)
4778 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
4779 (uView == LVS_ICON || uView ==LVS_SMALLICON))
4784 LISTVIEW_GetOrigin(infoPtr, &Origin);
4785 Destination.x = lpFindInfo->pt.x - Origin.x;
4786 Destination.y = lpFindInfo->pt.y - Origin.y;
4787 switch(lpFindInfo->vkDirection)
4789 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
4790 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
4791 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
4792 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
4793 case VK_HOME: Destination.x = Destination.y = 0; break;
4794 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4795 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4797 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
4798 Destination.x = rcArea.right;
4799 Destination.y = rcArea.bottom;
4801 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
4806 /* if LVFI_PARAM is specified, all other flags are ignored */
4807 if (lpFindInfo->flags & LVFI_PARAM)
4809 lvItem.mask |= LVIF_PARAM;
4811 lvItem.mask &= ~LVIF_TEXT;
4815 for (; nItem < nLast; nItem++)
4817 lvItem.iItem = nItem;
4818 lvItem.iSubItem = 0;
4819 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
4821 if (lvItem.mask & LVIF_PARAM)
4823 if (lpFindInfo->lParam == lvItem.lParam)
4829 if (lvItem.mask & LVIF_TEXT)
4831 if (lpFindInfo->flags & LVFI_PARTIAL)
4833 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
4837 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
4841 if (!bNearest) return nItem;
4843 /* This is very inefficient. To do a good job here,
4844 * we need a sorted array of (x,y) item positions */
4845 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
4847 /* compute the distance^2 to the destination */
4848 xdist = Destination.x - Position.x;
4849 ydist = Destination.y - Position.y;
4850 dist = xdist * xdist + ydist * ydist;
4852 /* remember the distance, and item if it's closer */
4856 nNearestItem = nItem;
4863 nLast = min(nStart + 1, infoPtr->nItemCount);
4868 return nNearestItem;
4873 * Searches for an item with specific characteristics.
4876 * [I] hwnd : window handle
4877 * [I] nStart : base item index
4878 * [I] lpFindInfo : item information to look for
4881 * SUCCESS : index of item
4884 static INT LISTVIEW_FindItemA(LISTVIEW_INFO *infoPtr, INT nStart,
4885 const LVFINDINFOA *lpFindInfo)
4887 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
4891 memcpy(&fiw, lpFindInfo, sizeof(fiw));
4892 if (hasText) fiw.psz = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
4893 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
4894 if (hasText) textfreeT((LPWSTR)fiw.psz, FALSE);
4900 * Retrieves the background image of the listview control.
4903 * [I] infoPtr : valid pointer to the listview structure
4904 * [O] lpBkImage : background image attributes
4910 /* static BOOL LISTVIEW_GetBkImage(LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
4912 /* FIXME (listview, "empty stub!\n"); */
4918 * Retrieves column attributes.
4921 * [I] infoPtr : valid pointer to the listview structure
4922 * [I] nColumn : column index
4923 * [IO] lpColumn : column information
4924 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
4925 * otherwise it is in fact a LPLVCOLUMNA
4931 static BOOL LISTVIEW_GetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
4933 COLUMN_INFO *lpColumnInfo;
4936 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4937 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
4939 /* initialize memory */
4940 ZeroMemory(&hdi, sizeof(hdi));
4942 if (lpColumn->mask & LVCF_TEXT)
4944 hdi.mask |= HDI_TEXT;
4945 hdi.pszText = lpColumn->pszText;
4946 hdi.cchTextMax = lpColumn->cchTextMax;
4949 if (lpColumn->mask & LVCF_IMAGE)
4950 hdi.mask |= HDI_IMAGE;
4952 if (lpColumn->mask & LVCF_ORDER)
4953 hdi.mask |= HDI_ORDER;
4955 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
4957 if (lpColumn->mask & LVCF_FMT)
4958 lpColumn->fmt = lpColumnInfo->fmt;
4960 if (lpColumn->mask & LVCF_WIDTH)
4961 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
4963 if (lpColumn->mask & LVCF_IMAGE)
4964 lpColumn->iImage = hdi.iImage;
4966 if (lpColumn->mask & LVCF_ORDER)
4967 lpColumn->iOrder = hdi.iOrder;
4973 static BOOL LISTVIEW_GetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
4980 /* FIXME: little hack */
4981 for (i = 0; i < iCount; i++)
4989 * Retrieves the column width.
4992 * [I] infoPtr : valid pointer to the listview structure
4993 * [I] int : column index
4996 * SUCCESS : column width
4999 static INT LISTVIEW_GetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn)
5001 INT nColumnWidth = 0;
5004 TRACE("nColumn=%d\n", nColumn);
5006 /* we have a 'column' in LIST and REPORT mode only */
5007 switch(infoPtr->dwStyle & LVS_TYPEMASK)
5010 nColumnWidth = infoPtr->nItemWidth;
5013 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
5014 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
5015 nColumnWidth = rcHeader.right - rcHeader.left;
5019 TRACE("nColumnWidth=%d\n", nColumnWidth);
5020 return nColumnWidth;
5025 * In list or report display mode, retrieves the number of items that can fit
5026 * vertically in the visible area. In icon or small icon display mode,
5027 * retrieves the total number of visible items.
5030 * [I] infoPtr : valid pointer to the listview structure
5033 * Number of fully visible items.
5035 static INT LISTVIEW_GetCountPerPage(LISTVIEW_INFO *infoPtr)
5037 switch (infoPtr->dwStyle & LVS_TYPEMASK)
5041 return infoPtr->nItemCount;
5043 return LISTVIEW_GetCountPerColumn(infoPtr);
5045 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5053 * Retrieves an image list handle.
5056 * [I] infoPtr : valid pointer to the listview structure
5057 * [I] nImageList : image list identifier
5060 * SUCCESS : image list handle
5063 static HIMAGELIST LISTVIEW_GetImageList(LISTVIEW_INFO *infoPtr, INT nImageList)
5067 case LVSIL_NORMAL: return infoPtr->himlNormal;
5068 case LVSIL_SMALL: return infoPtr->himlSmall;
5069 case LVSIL_STATE: return infoPtr->himlState;
5074 /* LISTVIEW_GetISearchString */
5078 * Retrieves item attributes.
5081 * [I] hwnd : window handle
5082 * [IO] lpLVItem : item info
5083 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5084 * if FALSE, the lpLVItem is a LPLVITEMA.
5087 * This is the internal 'GetItem' interface -- it tries to
5088 * be smart, and avoids text copies, if possible, by modifing
5089 * lpLVItem->pszText to point to the text string. Please note
5090 * that this is not always possible (e.g. OWNERDATA), so on
5091 * entry you *must* supply valid values for pszText, and cchTextMax.
5092 * The only difference to the documented interface is that upon
5093 * return, you should use *only* the lpLVItem->pszText, rather than
5094 * the buffer pointer you provided on input. Most code already does
5095 * that, so it's not a problem.
5096 * For the two cases when the text must be copied (that is,
5097 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5103 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5105 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5106 NMLVDISPINFOW dispInfo;
5112 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5114 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5117 if (lpLVItem->mask == 0) return TRUE;
5119 /* make a local copy */
5120 isubitem = lpLVItem->iSubItem;
5122 /* a quick optimization if all we're asked is the focus state
5123 * these queries are worth optimising since they are common,
5124 * and can be answered in constant time, without the heavy accesses */
5125 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5126 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5128 lpLVItem->state = 0;
5129 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5130 lpLVItem->state |= LVIS_FOCUSED;
5134 ZeroMemory(&dispInfo, sizeof(dispInfo));
5136 /* if the app stores all the data, handle it separately */
5137 if (infoPtr->dwStyle & LVS_OWNERDATA)
5139 dispInfo.item.state = 0;
5141 /* apprently, we should not callback for lParam in LVS_OWNERDATA */
5142 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5144 /* NOTE: copy only fields which we _know_ are initialized, some apps
5145 * depend on the uninitialized fields being 0 */
5146 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5147 dispInfo.item.iItem = lpLVItem->iItem;
5148 dispInfo.item.iSubItem = isubitem;
5149 if (lpLVItem->mask & LVIF_TEXT)
5151 dispInfo.item.pszText = lpLVItem->pszText;
5152 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5154 if (lpLVItem->mask & LVIF_STATE)
5155 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5156 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5157 dispInfo.item.stateMask = lpLVItem->stateMask;
5158 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5160 /* full size structure expected - _WIN32IE >= 0x560 */
5161 *lpLVItem = dispInfo.item;
5163 else if (lpLVItem->mask & LVIF_INDENT)
5165 /* indent member expected - _WIN32IE >= 0x300 */
5166 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5170 /* minimal structure expected */
5171 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5173 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5176 /* make sure lParam is zeroed out */
5177 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5179 /* we store only a little state, so if we're not asked, we're done */
5180 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5182 /* if focus is handled by us, report it */
5183 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5185 lpLVItem->state &= ~LVIS_FOCUSED;
5186 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5187 lpLVItem->state |= LVIS_FOCUSED;
5190 /* and do the same for selection, if we handle it */
5191 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5193 lpLVItem->state &= ~LVIS_SELECTED;
5194 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5195 lpLVItem->state |= LVIS_SELECTED;
5201 /* find the item and subitem structures before we proceed */
5202 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5203 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
5208 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5209 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5212 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5217 pItemHdr = &lpItem->hdr;
5219 /* Do we need to query the state from the app? */
5220 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5222 dispInfo.item.mask |= LVIF_STATE;
5223 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5226 /* Do we need to enquire about the image? */
5227 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5228 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5230 dispInfo.item.mask |= LVIF_IMAGE;
5231 dispInfo.item.iImage = I_IMAGECALLBACK;
5234 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5235 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
5237 dispInfo.item.mask |= LVIF_TEXT;
5238 dispInfo.item.pszText = lpLVItem->pszText;
5239 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5240 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5241 *dispInfo.item.pszText = '\0';
5244 /* If we don't have all the requested info, query the application */
5245 if (dispInfo.item.mask != 0)
5247 dispInfo.item.iItem = lpLVItem->iItem;
5248 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5249 dispInfo.item.lParam = lpItem->lParam;
5250 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5251 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5254 /* we should not store values for subitems */
5255 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5257 /* Now, handle the iImage field */
5258 if (dispInfo.item.mask & LVIF_IMAGE)
5260 lpLVItem->iImage = dispInfo.item.iImage;
5261 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5262 pItemHdr->iImage = dispInfo.item.iImage;
5264 else if (lpLVItem->mask & LVIF_IMAGE)
5266 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5267 lpLVItem->iImage = pItemHdr->iImage;
5269 lpLVItem->iImage = 0;
5272 /* The pszText field */
5273 if (dispInfo.item.mask & LVIF_TEXT)
5275 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5276 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5278 lpLVItem->pszText = dispInfo.item.pszText;
5280 else if (lpLVItem->mask & LVIF_TEXT)
5282 if (isW) lpLVItem->pszText = pItemHdr->pszText;
5283 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5286 /* if this is a subitem, we're done */
5287 if (isubitem) return TRUE;
5289 /* Next is the lParam field */
5290 if (dispInfo.item.mask & LVIF_PARAM)
5292 lpLVItem->lParam = dispInfo.item.lParam;
5293 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5294 lpItem->lParam = dispInfo.item.lParam;
5296 else if (lpLVItem->mask & LVIF_PARAM)
5297 lpLVItem->lParam = lpItem->lParam;
5299 /* ... the state field (this one is different due to uCallbackmask) */
5300 if (lpLVItem->mask & LVIF_STATE)
5302 lpLVItem->state = lpItem->state;
5303 if (dispInfo.item.mask & LVIF_STATE)
5305 lpLVItem->state &= ~dispInfo.item.stateMask;
5306 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5308 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5310 lpLVItem->state &= ~LVIS_FOCUSED;
5311 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5312 lpLVItem->state |= LVIS_FOCUSED;
5314 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5316 lpLVItem->state &= ~LVIS_SELECTED;
5317 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5318 lpLVItem->state |= LVIS_SELECTED;
5322 /* and last, but not least, the indent field */
5323 if (lpLVItem->mask & LVIF_INDENT)
5324 lpLVItem->iIndent = lpItem->iIndent;
5331 * Retrieves item attributes.
5334 * [I] hwnd : window handle
5335 * [IO] lpLVItem : item info
5336 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5337 * if FALSE, the lpLVItem is a LPLVITEMA.
5340 * This is the external 'GetItem' interface -- it properly copies
5341 * the text in the provided buffer.
5347 static BOOL LISTVIEW_GetItemExtT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5352 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5355 pszText = lpLVItem->pszText;
5356 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5357 if (bResult && lpLVItem->pszText != pszText)
5358 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5359 lpLVItem->pszText = pszText;
5367 * Retrieves the position (upper-left) of the listview control item.
5368 * Note that for LVS_ICON style, the upper-left is that of the icon
5369 * and not the bounding box.
5372 * [I] infoPtr : valid pointer to the listview structure
5373 * [I] nItem : item index
5374 * [O] lpptPosition : coordinate information
5380 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5382 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5385 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5387 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5389 LISTVIEW_GetOrigin(infoPtr, &Origin);
5390 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5392 if (uView == LVS_ICON)
5394 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5395 lpptPosition->y += ICON_TOP_PADDING;
5397 lpptPosition->x += Origin.x;
5398 lpptPosition->y += Origin.y;
5400 TRACE (" lpptPosition=%s\n", debugpoint(lpptPosition));
5407 * Retrieves the bounding rectangle for a listview control item.
5410 * [I] infoPtr : valid pointer to the listview structure
5411 * [I] nItem : item index
5412 * [IO] lprc : bounding rectangle coordinates
5413 * lprc->left specifies the portion of the item for which the bounding
5414 * rectangle will be retrieved.
5416 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5417 * including the icon and label.
5420 * * Experiment shows that native control returns:
5421 * * width = min (48, length of text line)
5422 * * .left = position.x - (width - iconsize.cx)/2
5423 * * .right = .left + width
5424 * * height = #lines of text * ntmHeight + icon height + 8
5425 * * .top = position.y - 2
5426 * * .bottom = .top + height
5427 * * separation between items .y = itemSpacing.cy - height
5428 * * .x = itemSpacing.cx - width
5429 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5432 * * Experiment shows that native control returns:
5433 * * width = iconSize.cx + 16
5434 * * .left = position.x - (width - iconsize.cx)/2
5435 * * .right = .left + width
5436 * * height = iconSize.cy + 4
5437 * * .top = position.y - 2
5438 * * .bottom = .top + height
5439 * * separation between items .y = itemSpacing.cy - height
5440 * * .x = itemSpacing.cx - width
5441 * LVIR_LABEL Returns the bounding rectangle of the item text.
5444 * * Experiment shows that native control returns:
5445 * * width = text length
5446 * * .left = position.x - width/2
5447 * * .right = .left + width
5448 * * height = ntmH * linecount + 2
5449 * * .top = position.y + iconSize.cy + 6
5450 * * .bottom = .top + height
5451 * * separation between items .y = itemSpacing.cy - height
5452 * * .x = itemSpacing.cx - width
5453 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5454 * rectangles, but excludes columns in report view.
5461 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5462 * upon whether the window has the focus currently and on whether the item
5463 * is the one with the focus. Ensure that the control's record of which
5464 * item has the focus agrees with the items' records.
5466 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5468 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5469 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5470 BOOL doLabel = TRUE, oversizedBox = FALSE;
5471 POINT Position, Origin;
5475 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5477 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5479 LISTVIEW_GetOrigin(infoPtr, &Origin);
5480 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5482 /* Be smart and try to figure out the minimum we have to do */
5483 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5484 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5485 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5486 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5487 oversizedBox = TRUE;
5489 /* get what we need from the item before hand, so we make
5490 * only one request. This can speed up things, if data
5491 * is stored on the app side */
5493 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5494 if (doLabel) lvItem.mask |= LVIF_TEXT;
5495 lvItem.iItem = nItem;
5496 lvItem.iSubItem = 0;
5497 lvItem.pszText = szDispText;
5498 lvItem.cchTextMax = DISP_TEXT_SIZE;
5499 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5500 /* we got the state already up, simulate it here, to avoid a reget */
5501 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5503 lvItem.mask |= LVIF_STATE;
5504 lvItem.stateMask = LVIS_FOCUSED;
5505 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5508 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5509 lprc->left = LVIR_BOUNDS;
5513 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5517 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, lprc);
5521 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5524 case LVIR_SELECTBOUNDS:
5525 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, &label_rect);
5526 UnionRect(lprc, lprc, &label_rect);
5530 WARN("Unknown value: %ld\n", lprc->left);
5534 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5536 TRACE(" rect=%s\n", debugrect(lprc));
5543 * Retrieves the spacing between listview control items.
5546 * [I] infoPtr : valid pointer to the listview structure
5547 * [IO] lprc : rectangle to receive the output
5548 * on input, lprc->top = nSubItem
5549 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5551 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5552 * not only those of the first column.
5553 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5559 static BOOL LISTVIEW_GetSubItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5564 if (!lprc) return FALSE;
5566 TRACE("(nItem=%d, nSubItem=%ld)\n", nItem, lprc->top);
5567 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5569 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5571 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5573 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5576 lvItem.iItem = nItem;
5577 lvItem.iSubItem = lprc->top;
5579 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5583 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5588 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5592 ERR("Unknown bounds=%ld\n", lprc->left);
5596 OffsetRect(lprc, Position.x, Position.y);
5603 * Retrieves the width of a label.
5606 * [I] infoPtr : valid pointer to the listview structure
5609 * SUCCESS : string width (in pixels)
5612 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *infoPtr, INT nItem)
5614 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5617 TRACE("(nItem=%d)\n", nItem);
5619 lvItem.mask = LVIF_TEXT;
5620 lvItem.iItem = nItem;
5621 lvItem.iSubItem = 0;
5622 lvItem.pszText = szDispText;
5623 lvItem.cchTextMax = DISP_TEXT_SIZE;
5624 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5626 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5631 * Retrieves the spacing between listview control items.
5634 * [I] infoPtr : valid pointer to the listview structure
5635 * [I] bSmall : flag for small or large icon
5638 * Horizontal + vertical spacing
5640 static LONG LISTVIEW_GetItemSpacing(LISTVIEW_INFO *infoPtr, BOOL bSmall)
5646 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5650 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5651 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5653 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5660 * Retrieves the state of a listview control item.
5663 * [I] infoPtr : valid pointer to the listview structure
5664 * [I] nItem : item index
5665 * [I] uMask : state mask
5668 * State specified by the mask.
5670 static UINT LISTVIEW_GetItemState(LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5674 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5676 lvItem.iItem = nItem;
5677 lvItem.iSubItem = 0;
5678 lvItem.mask = LVIF_STATE;
5679 lvItem.stateMask = uMask;
5680 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5682 return lvItem.state & uMask;
5687 * Retrieves the text of a listview control item or subitem.
5690 * [I] hwnd : window handle
5691 * [I] nItem : item index
5692 * [IO] lpLVItem : item information
5693 * [I] isW : TRUE if lpLVItem is Unicode
5696 * SUCCESS : string length
5699 static INT LISTVIEW_GetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
5701 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5703 lpLVItem->mask = LVIF_TEXT;
5704 lpLVItem->iItem = nItem;
5705 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
5707 return textlenT(lpLVItem->pszText, isW);
5712 * Searches for an item based on properties + relationships.
5715 * [I] infoPtr : valid pointer to the listview structure
5716 * [I] nItem : item index
5717 * [I] uFlags : relationship flag
5720 * SUCCESS : item index
5723 static INT LISTVIEW_GetNextItem(LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
5725 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5727 LVFINDINFOW lvFindInfo;
5728 INT nCountPerColumn;
5732 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
5733 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
5735 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
5737 if (uFlags & LVNI_CUT)
5740 if (uFlags & LVNI_DROPHILITED)
5741 uMask |= LVIS_DROPHILITED;
5743 if (uFlags & LVNI_FOCUSED)
5744 uMask |= LVIS_FOCUSED;
5746 if (uFlags & LVNI_SELECTED)
5747 uMask |= LVIS_SELECTED;
5749 /* if we're asked for the focused item, that's only one,
5750 * so it's worth optimizing */
5751 if (uFlags & LVNI_FOCUSED)
5753 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
5754 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
5757 if (uFlags & LVNI_ABOVE)
5759 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5764 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5770 /* Special case for autoarrange - move 'til the top of a list */
5771 if (is_autoarrange(infoPtr))
5773 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5774 while (nItem - nCountPerRow >= 0)
5776 nItem -= nCountPerRow;
5777 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5782 lvFindInfo.flags = LVFI_NEARESTXY;
5783 lvFindInfo.vkDirection = VK_UP;
5784 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5785 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5787 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5792 else if (uFlags & LVNI_BELOW)
5794 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5796 while (nItem < infoPtr->nItemCount)
5799 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5805 /* Special case for autoarrange - move 'til the bottom of a list */
5806 if (is_autoarrange(infoPtr))
5808 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5809 while (nItem + nCountPerRow < infoPtr->nItemCount )
5811 nItem += nCountPerRow;
5812 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5817 lvFindInfo.flags = LVFI_NEARESTXY;
5818 lvFindInfo.vkDirection = VK_DOWN;
5819 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5820 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5822 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5827 else if (uFlags & LVNI_TOLEFT)
5829 if (uView == LVS_LIST)
5831 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5832 while (nItem - nCountPerColumn >= 0)
5834 nItem -= nCountPerColumn;
5835 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5839 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5841 /* Special case for autoarrange - move 'ti the beginning of a row */
5842 if (is_autoarrange(infoPtr))
5844 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5845 while (nItem % nCountPerRow > 0)
5848 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5853 lvFindInfo.flags = LVFI_NEARESTXY;
5854 lvFindInfo.vkDirection = VK_LEFT;
5855 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5856 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5858 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5863 else if (uFlags & LVNI_TORIGHT)
5865 if (uView == LVS_LIST)
5867 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5868 while (nItem + nCountPerColumn < infoPtr->nItemCount)
5870 nItem += nCountPerColumn;
5871 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5875 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5877 /* Special case for autoarrange - move 'til the end of a row */
5878 if (is_autoarrange(infoPtr))
5880 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5881 while (nItem % nCountPerRow < nCountPerRow - 1 )
5884 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5889 lvFindInfo.flags = LVFI_NEARESTXY;
5890 lvFindInfo.vkDirection = VK_RIGHT;
5891 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5892 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5894 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5903 /* search by index */
5904 for (i = nItem; i < infoPtr->nItemCount; i++)
5906 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
5914 /* LISTVIEW_GetNumberOfWorkAreas */
5918 * Retrieves the origin coordinates when in icon or small icon display mode.
5921 * [I] infoPtr : valid pointer to the listview structure
5922 * [O] lpptOrigin : coordinate information
5927 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
5929 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5930 INT nHorzPos = 0, nVertPos = 0;
5931 SCROLLINFO scrollInfo;
5933 scrollInfo.cbSize = sizeof(SCROLLINFO);
5934 scrollInfo.fMask = SIF_POS;
5936 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
5937 nHorzPos = scrollInfo.nPos;
5938 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
5939 nVertPos = scrollInfo.nPos;
5941 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
5943 lpptOrigin->x = infoPtr->rcList.left;
5944 lpptOrigin->y = infoPtr->rcList.top;
5945 if (uView == LVS_LIST)
5946 nHorzPos *= infoPtr->nItemWidth;
5947 else if (uView == LVS_REPORT)
5948 nVertPos *= infoPtr->nItemHeight;
5950 lpptOrigin->x -= nHorzPos;
5951 lpptOrigin->y -= nVertPos;
5953 TRACE(" origin=%s\n", debugpoint(lpptOrigin));
5958 * Retrieves the width of a string.
5961 * [I] hwnd : window handle
5962 * [I] lpszText : text string to process
5963 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
5966 * SUCCESS : string width (in pixels)
5969 static INT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
5974 if (is_textT(lpszText, isW))
5976 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
5977 HDC hdc = GetDC(infoPtr->hwndSelf);
5978 HFONT hOldFont = SelectObject(hdc, hFont);
5981 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
5983 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
5984 SelectObject(hdc, hOldFont);
5985 ReleaseDC(infoPtr->hwndSelf, hdc);
5987 return stringSize.cx;
5992 * Determines which listview item is located at the specified position.
5995 * [I] infoPtr : valid pointer to the listview structure
5996 * [IO] lpht : hit test information
5997 * [I] subitem : fill out iSubItem.
5998 * [I] select : return the index only if the hit selects the item
6001 * (mm 20001022): We must not allow iSubItem to be touched, for
6002 * an app might pass only a structure with space up to iItem!
6003 * (MS Office 97 does that for instance in the file open dialog)
6006 * SUCCESS : item index
6009 static INT LISTVIEW_HitTest(LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6011 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6012 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6013 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6014 POINT Origin, Position, opt;
6019 TRACE("(pt=%s, subitem=%d, select=%d)\n", debugpoint(&lpht->pt), subitem, select);
6023 if (subitem) lpht->iSubItem = 0;
6025 if (infoPtr->rcList.left > lpht->pt.x)
6026 lpht->flags |= LVHT_TOLEFT;
6027 else if (infoPtr->rcList.right < lpht->pt.x)
6028 lpht->flags |= LVHT_TORIGHT;
6030 if (infoPtr->rcList.top > lpht->pt.y)
6031 lpht->flags |= LVHT_ABOVE;
6032 else if (infoPtr->rcList.bottom < lpht->pt.y)
6033 lpht->flags |= LVHT_BELOW;
6035 TRACE("lpht->flags=0x%x\n", lpht->flags);
6036 if (lpht->flags) return -1;
6038 lpht->flags |= LVHT_NOWHERE;
6040 LISTVIEW_GetOrigin(infoPtr, &Origin);
6042 /* first deal with the large items */
6043 rcSearch.left = lpht->pt.x;
6044 rcSearch.top = lpht->pt.y;
6045 rcSearch.right = rcSearch.left + 1;
6046 rcSearch.bottom = rcSearch.top + 1;
6048 iterator_frameditems(&i, infoPtr, &rcSearch);
6049 iterator_next(&i); /* go to first item in the sequence */
6051 iterator_destroy(&i);
6053 TRACE("lpht->iItem=%d\n", iItem);
6054 if (iItem == -1) return -1;
6056 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6057 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
6058 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6059 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6060 lvItem.iItem = iItem;
6061 lvItem.iSubItem = 0;
6062 lvItem.pszText = szDispText;
6063 lvItem.cchTextMax = DISP_TEXT_SIZE;
6064 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6065 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6067 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
6068 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6069 opt.x = lpht->pt.x - Position.x - Origin.x;
6070 opt.y = lpht->pt.y - Position.y - Origin.y;
6072 if (uView == LVS_REPORT)
6075 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6076 TRACE("rcBounds=%s\n", debugrect(&rcBounds));
6077 if (!PtInRect(&rcBounds, opt)) return -1;
6079 if (PtInRect(&rcIcon, opt))
6080 lpht->flags |= LVHT_ONITEMICON;
6081 else if (PtInRect(&rcLabel, opt))
6082 lpht->flags |= LVHT_ONITEMLABEL;
6083 else if (infoPtr->himlState && ((lvItem.state & LVIS_STATEIMAGEMASK) >> 12) && PtInRect(&rcState, opt))
6084 lpht->flags |= LVHT_ONITEMSTATEICON;
6085 if (lpht->flags & LVHT_ONITEM)
6086 lpht->flags &= ~LVHT_NOWHERE;
6088 TRACE("lpht->flags=0x%x\n", lpht->flags);
6089 if (uView == LVS_REPORT && subitem)
6093 rcBounds.right = rcBounds.left;
6094 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6096 rcBounds.left = rcBounds.right;
6097 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
6098 if (PtInRect(&rcBounds, opt))
6106 if (select && !(uView == LVS_REPORT &&
6107 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6108 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6110 if (uView == LVS_REPORT)
6112 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6113 UnionRect(&rcBounds, &rcBounds, &rcState);
6115 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6117 return lpht->iItem = iItem;
6121 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
6122 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
6123 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
6124 and not during the processing of a LVM_SORTITEMS message. Applications should provide
6125 their own sort proc. when sending LVM_SORTITEMS.
6128 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
6130 LVS_SORTXXX must be specified,
6131 LVS_OWNERDRAW is not set,
6132 <item>.pszText is not LPSTR_TEXTCALLBACK.
6134 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
6135 are sorted based on item text..."
6137 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
6139 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
6140 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
6141 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
6143 /* if we're sorting descending, negate the return value */
6144 return (((LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
6149 * Inserts a new item in the listview control.
6152 * [I] infoPtr : valid pointer to the listview structure
6153 * [I] lpLVItem : item information
6154 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6157 * SUCCESS : new item index
6160 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6162 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6167 BOOL is_sorted, has_changed;
6170 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6172 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6174 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6175 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iSubItem) return -1;
6177 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6179 if (!(lpItem = (ITEM_INFO *)Alloc(sizeof(ITEM_INFO)))) return -1;
6181 /* insert item in listview control data structure */
6182 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6183 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6185 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6186 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6188 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
6189 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6190 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6191 if (nItem == -1) goto fail;
6192 infoPtr->nItemCount++;
6194 /* shift indices first so they don't get tangled */
6195 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6197 /* set the item attributes */
6198 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6200 /* full size structure expected - _WIN32IE >= 0x560 */
6203 else if (lpLVItem->mask & LVIF_INDENT)
6205 /* indent member expected - _WIN32IE >= 0x300 */
6206 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6210 /* minimal structure expected */
6211 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6214 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) item.state &= ~LVIS_STATEIMAGEMASK;
6215 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6217 /* if we're sorted, sort the list, and update the index */
6220 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
6221 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
6222 assert(nItem != -1);
6225 /* make room for the position, if we are in the right mode */
6226 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6228 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6230 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6232 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6237 /* send LVN_INSERTITEM notification */
6238 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6240 nmlv.lParam = lpItem->lParam;
6241 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6243 /* align items (set position of each item) */
6244 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6248 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6249 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6251 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6253 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6256 /* now is the invalidation fun */
6257 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6261 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6262 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6263 infoPtr->nItemCount--;
6265 DPA_DeletePtr(hdpaSubItems, 0);
6266 DPA_Destroy (hdpaSubItems);
6273 * Redraws a range of items.
6276 * [I] infoPtr : valid pointer to the listview structure
6277 * [I] nFirst : first item
6278 * [I] nLast : last item
6284 static BOOL LISTVIEW_RedrawItems(LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6288 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6289 max(nFirst, nLast) >= infoPtr->nItemCount)
6292 for (i = nFirst; i <= nLast; i++)
6293 LISTVIEW_InvalidateItem(infoPtr, i);
6300 * Scroll the content of a listview.
6303 * [I] infoPtr : valid pointer to the listview structure
6304 * [I] dx : horizontal scroll amount in pixels
6305 * [I] dy : vertical scroll amount in pixels
6312 * If the control is in report mode (LVS_REPORT) the control can
6313 * be scrolled only in line increments. "dy" will be rounded to the
6314 * nearest number of pixels that are a whole line. Ex: if line height
6315 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6316 * is passed the the scroll will be 0. (per MSDN 7/2002)
6318 * For: (per experimentaion with native control and CSpy ListView)
6319 * LVS_ICON dy=1 = 1 pixel (vertical only)
6321 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6323 * LVS_LIST dx=1 = 1 column (horizontal only)
6324 * but will only scroll 1 column per message
6325 * no matter what the value.
6326 * dy must be 0 or FALSE returned.
6327 * LVS_REPORT dx=1 = 1 pixel
6331 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6333 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6335 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6336 dy /= infoPtr->nItemHeight;
6339 if (dy != 0) return FALSE;
6346 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6347 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6354 * Sets the background color.
6357 * [I] infoPtr : valid pointer to the listview structure
6358 * [I] clrBk : background color
6364 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6366 TRACE("(clrBk=%lx)\n", clrBk);
6368 if(infoPtr->clrBk != clrBk) {
6369 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6370 infoPtr->clrBk = clrBk;
6371 if (clrBk == CLR_NONE)
6372 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6374 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6375 LISTVIEW_InvalidateList(infoPtr);
6381 /* LISTVIEW_SetBkImage */
6383 /*** Helper for {Insert,Set}ColumnT *only* */
6384 static void column_fill_hditem(LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn, const LVCOLUMNW *lpColumn, BOOL isW)
6386 if (lpColumn->mask & LVCF_FMT)
6388 /* format member is valid */
6389 lphdi->mask |= HDI_FORMAT;
6391 /* set text alignment (leftmost column must be left-aligned) */
6392 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6393 lphdi->fmt |= HDF_LEFT;
6394 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6395 lphdi->fmt |= HDF_RIGHT;
6396 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6397 lphdi->fmt |= HDF_CENTER;
6399 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6400 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6402 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6404 lphdi->fmt |= HDF_IMAGE;
6405 lphdi->iImage = I_IMAGECALLBACK;
6409 if (lpColumn->mask & LVCF_WIDTH)
6411 lphdi->mask |= HDI_WIDTH;
6412 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6414 /* make it fill the remainder of the controls width */
6418 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6420 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6421 lphdi->cxy += rcHeader.right - rcHeader.left;
6424 /* retrieve the layout of the header */
6425 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6426 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, debugrect(&rcHeader));
6428 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6431 lphdi->cxy = lpColumn->cx;
6434 if (lpColumn->mask & LVCF_TEXT)
6436 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6437 lphdi->fmt |= HDF_STRING;
6438 lphdi->pszText = lpColumn->pszText;
6439 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6442 if (lpColumn->mask & LVCF_IMAGE)
6444 lphdi->mask |= HDI_IMAGE;
6445 lphdi->iImage = lpColumn->iImage;
6448 if (lpColumn->mask & LVCF_ORDER)
6450 lphdi->mask |= HDI_ORDER;
6451 lphdi->iOrder = lpColumn->iOrder;
6458 * Inserts a new column.
6461 * [I] infoPtr : valid pointer to the listview structure
6462 * [I] nColumn : column index
6463 * [I] lpColumn : column information
6464 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6467 * SUCCESS : new column index
6470 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6471 const LVCOLUMNW *lpColumn, BOOL isW)
6473 COLUMN_INFO *lpColumnInfo;
6477 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6479 if (!lpColumn || nColumn < 0) return -1;
6480 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6482 ZeroMemory(&hdi, sizeof(HDITEMW));
6483 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6485 /* insert item in header control */
6486 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6487 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6488 (WPARAM)nColumn, (LPARAM)&hdi);
6489 if (nNewColumn == -1) return -1;
6490 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6492 /* create our own column info */
6493 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6494 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6496 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6497 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6499 /* now we have to actually adjust the data */
6500 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6502 SUBITEM_INFO *lpSubItem;
6506 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6508 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
6509 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
6511 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
6512 if (lpSubItem->iSubItem >= nNewColumn)
6513 lpSubItem->iSubItem++;
6518 /* make space for the new column */
6519 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6524 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6527 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6535 * Sets the attributes of a header item.
6538 * [I] infoPtr : valid pointer to the listview structure
6539 * [I] nColumn : column index
6540 * [I] lpColumn : column attributes
6541 * [I] isW: if TRUE, the lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6547 static BOOL LISTVIEW_SetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6548 const LVCOLUMNW *lpColumn, BOOL isW)
6550 HDITEMW hdi, hdiget;
6553 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6555 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6557 ZeroMemory(&hdi, sizeof(HDITEMW));
6558 if (lpColumn->mask & LVCF_FMT)
6560 hdi.mask |= HDI_FORMAT;
6561 hdiget.mask = HDI_FORMAT;
6562 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6563 hdi.fmt = hdiget.fmt & HDF_STRING;
6565 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6567 /* set header item attributes */
6568 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6569 if (!bResult) return FALSE;
6571 if (lpColumn->mask & LVCF_FMT)
6573 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6574 int oldFmt = lpColumnInfo->fmt;
6576 lpColumnInfo->fmt = lpColumn->fmt;
6577 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
6579 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6580 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
6589 * Sets the column order array
6592 * [I] infoPtr : valid pointer to the listview structure
6593 * [I] iCount : number of elements in column order array
6594 * [I] lpiArray : pointer to column order array
6600 static BOOL LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
6602 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6613 * Sets the width of a column
6616 * [I] infoPtr : valid pointer to the listview structure
6617 * [I] nColumn : column index
6618 * [I] cx : column width
6624 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
6626 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6627 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6631 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
6633 /* set column width only if in report or list mode */
6634 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6636 /* take care of invalid cx values */
6637 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
6638 else if (uView == LVS_LIST && cx < 1) return FALSE;
6640 /* resize all columns if in LVS_LIST mode */
6641 if(uView == LVS_LIST)
6643 infoPtr->nItemWidth = cx;
6644 LISTVIEW_InvalidateList(infoPtr);
6648 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6650 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
6655 lvItem.mask = LVIF_TEXT;
6657 lvItem.iSubItem = nColumn;
6658 lvItem.pszText = szDispText;
6659 lvItem.cchTextMax = DISP_TEXT_SIZE;
6660 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6662 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6663 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6664 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
6666 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
6667 max_cx += infoPtr->iconSize.cx;
6668 max_cx += TRAILING_LABEL_PADDING;
6671 /* autosize based on listview items width */
6672 if(cx == LVSCW_AUTOSIZE)
6674 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
6676 /* if iCol is the last column make it fill the remainder of the controls width */
6677 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
6682 LISTVIEW_GetOrigin(infoPtr, &Origin);
6683 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
6685 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
6689 /* Despite what the MS docs say, if this is not the last
6690 column, then MS resizes the column to the width of the
6691 largest text string in the column, including headers
6692 and items. This is different from LVSCW_AUTOSIZE in that
6693 LVSCW_AUTOSIZE ignores the header string length. */
6696 /* retrieve header text */
6697 hdi.mask = HDI_TEXT;
6698 hdi.cchTextMax = DISP_TEXT_SIZE;
6699 hdi.pszText = szDispText;
6700 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, (LPARAM)&hdi))
6702 HDC hdc = GetDC(infoPtr->hwndSelf);
6703 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
6706 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
6707 cx = size.cx + TRAILING_HEADER_PADDING;
6708 /* FIXME: Take into account the header image, if one is present */
6709 SelectObject(hdc, old_font);
6710 ReleaseDC(infoPtr->hwndSelf, hdc);
6712 cx = max (cx, max_cx);
6716 if (cx < 0) return FALSE;
6718 /* call header to update the column change */
6719 hdi.mask = HDI_WIDTH;
6721 TRACE("hdi.cxy=%d\n", hdi.cxy);
6722 return Header_SetItemW(infoPtr->hwndHeader, nColumn, (LPARAM)&hdi);
6726 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
6729 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(LISTVIEW_INFO *infoPtr)
6732 HBITMAP hbm_im, hbm_mask, hbm_orig;
6734 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
6735 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
6738 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
6739 ILC_COLOR | ILC_MASK, 2, 2);
6740 hdc_wnd = GetDC(infoPtr->hwndSelf);
6741 hdc = CreateCompatibleDC(hdc_wnd);
6742 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
6743 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
6744 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
6746 rc.left = rc.top = 0;
6747 rc.right = GetSystemMetrics(SM_CXSMICON);
6748 rc.bottom = GetSystemMetrics(SM_CYSMICON);
6750 hbm_orig = SelectObject(hdc, hbm_mask);
6751 FillRect(hdc, &rc, hbr_white);
6752 InflateRect(&rc, -3, -3);
6753 FillRect(hdc, &rc, hbr_black);
6755 SelectObject(hdc, hbm_im);
6756 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
6757 SelectObject(hdc, hbm_orig);
6758 ImageList_Add(himl, hbm_im, hbm_mask);
6760 SelectObject(hdc, hbm_im);
6761 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
6762 SelectObject(hdc, hbm_orig);
6763 ImageList_Add(himl, hbm_im, hbm_mask);
6765 DeleteObject(hbm_mask);
6766 DeleteObject(hbm_im);
6774 * Sets the extended listview style.
6777 * [I] infoPtr : valid pointer to the listview structure
6779 * [I] dwStyle : style
6782 * SUCCESS : previous style
6785 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwStyle)
6787 DWORD dwOldStyle = infoPtr->dwLvExStyle;
6791 infoPtr->dwLvExStyle = (dwOldStyle & ~dwMask) | (dwStyle & dwMask);
6793 infoPtr->dwLvExStyle = dwStyle;
6795 if((infoPtr->dwLvExStyle ^ dwOldStyle) & LVS_EX_CHECKBOXES)
6797 HIMAGELIST himl = 0;
6798 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6799 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
6800 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
6808 * Sets the new hot cursor used during hot tracking and hover selection.
6811 * [I] infoPtr : valid pointer to the listview structure
6812 * [I} hCurosr : the new hot cursor handle
6815 * Returns the previous hot cursor
6817 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
6819 HCURSOR oldCursor = infoPtr->hHotCursor;
6821 infoPtr->hHotCursor = hCursor;
6829 * Sets the hot item index.
6832 * [I] infoPtr : valid pointer to the listview structure
6833 * [I] iIndex : index
6836 * SUCCESS : previous hot item index
6837 * FAILURE : -1 (no hot item)
6839 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
6841 INT iOldIndex = infoPtr->nHotItem;
6843 infoPtr->nHotItem = iIndex;
6851 * Sets the amount of time the cursor must hover over an item before it is selected.
6854 * [I] infoPtr : valid pointer to the listview structure
6855 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
6858 * Returns the previous hover time
6860 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
6862 DWORD oldHoverTime = infoPtr->dwHoverTime;
6864 infoPtr->dwHoverTime = dwHoverTime;
6866 return oldHoverTime;
6871 * Sets spacing for icons of LVS_ICON style.
6874 * [I] infoPtr : valid pointer to the listview structure
6875 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
6876 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
6879 * MAKELONG(oldcx, oldcy)
6881 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
6883 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6884 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6886 TRACE("requested=(%d,%d)\n", cx, cy);
6888 /* this is supported only for LVS_ICON style */
6889 if (uView != LVS_ICON) return oldspacing;
6891 /* set to defaults, if instructed to */
6892 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
6893 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
6895 /* if 0 then compute width
6896 * FIXME: Should scan each item and determine max width of
6897 * icon or label, then make that the width */
6899 cx = infoPtr->iconSpacing.cx;
6901 /* if 0 then compute height */
6903 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
6904 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
6907 infoPtr->iconSpacing.cx = cx;
6908 infoPtr->iconSpacing.cy = cy;
6910 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%ld,%ld), ntmH=%d\n",
6911 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
6912 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
6913 infoPtr->ntmHeight);
6915 /* these depend on the iconSpacing */
6916 LISTVIEW_UpdateItemSize(infoPtr);
6921 inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
6925 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
6932 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
6933 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
6942 * [I] infoPtr : valid pointer to the listview structure
6943 * [I] nType : image list type
6944 * [I] himl : image list handle
6947 * SUCCESS : old image list
6950 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
6952 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6953 INT oldHeight = infoPtr->nItemHeight;
6954 HIMAGELIST himlOld = 0;
6956 TRACE("(nType=%d, himl=%p\n", nType, himl);
6961 himlOld = infoPtr->himlNormal;
6962 infoPtr->himlNormal = himl;
6963 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
6964 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
6968 himlOld = infoPtr->himlSmall;
6969 infoPtr->himlSmall = himl;
6970 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
6974 himlOld = infoPtr->himlState;
6975 infoPtr->himlState = himl;
6976 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
6977 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
6981 ERR("Unknown icon type=%d\n", nType);
6985 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
6986 if (infoPtr->nItemHeight != oldHeight)
6987 LISTVIEW_UpdateScroll(infoPtr);
6994 * Preallocates memory (does *not* set the actual count of items !)
6997 * [I] infoPtr : valid pointer to the listview structure
6998 * [I] nItems : item count (projected number of items to allocate)
6999 * [I] dwFlags : update flags
7005 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7007 TRACE("(nItems=%d, dwFlags=%lx)\n", nItems, dwFlags);
7009 if (infoPtr->dwStyle & LVS_OWNERDATA)
7011 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7012 INT nOldCount = infoPtr->nItemCount;
7014 if (nItems < nOldCount)
7016 RANGE range = { nItems, nOldCount };
7017 ranges_del(infoPtr->selectionRanges, range);
7018 if (infoPtr->nFocusedItem >= nItems)
7020 infoPtr->nFocusedItem = -1;
7021 SetRectEmpty(&infoPtr->rcFocus);
7025 infoPtr->nItemCount = nItems;
7026 LISTVIEW_UpdateScroll(infoPtr);
7028 /* the flags are valid only in ownerdata report and list modes */
7029 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
7031 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7032 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7034 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7035 LISTVIEW_InvalidateList(infoPtr);
7042 LISTVIEW_GetOrigin(infoPtr, &Origin);
7043 nFrom = min(nOldCount, nItems);
7044 nTo = max(nOldCount, nItems);
7046 if (uView == LVS_REPORT)
7049 rcErase.top = nFrom * infoPtr->nItemHeight;
7050 rcErase.right = infoPtr->nItemWidth;
7051 rcErase.bottom = nTo * infoPtr->nItemHeight;
7052 OffsetRect(&rcErase, Origin.x, Origin.y);
7053 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7054 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7058 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
7060 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
7061 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
7062 rcErase.right = rcErase.left + infoPtr->nItemWidth;
7063 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7064 OffsetRect(&rcErase, Origin.x, Origin.y);
7065 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7066 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7068 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
7070 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
7071 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7072 OffsetRect(&rcErase, Origin.x, Origin.y);
7073 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7074 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7080 /* According to MSDN for non-LVS_OWNERDATA this is just
7081 * a performance issue. The control allocates its internal
7082 * data structures for the number of items specified. It
7083 * cuts down on the number of memory allocations. Therefore
7084 * we will just issue a WARN here
7086 WARN("for non-ownerdata performance option not implemented.\n");
7094 * Sets the position of an item.
7097 * [I] infoPtr : valid pointer to the listview structure
7098 * [I] nItem : item index
7099 * [I] pt : coordinate
7105 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7107 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7110 TRACE("(nItem=%d, &pt=%s\n", nItem, debugpoint(&pt));
7112 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7113 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7115 LISTVIEW_GetOrigin(infoPtr, &Origin);
7117 /* This point value seems to be an undocumented feature.
7118 * The best guess is that it means either at the origin,
7119 * or at true beginning of the list. I will assume the origin. */
7120 if ((pt.x == -1) && (pt.y == -1))
7123 if (uView == LVS_ICON)
7125 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7126 pt.y -= ICON_TOP_PADDING;
7131 infoPtr->bAutoarrange = FALSE;
7133 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7138 * Sets the state of one or many items.
7141 * [I] infoPtr : valid pointer to the listview structure
7142 * [I] nItem : item index
7143 * [I] lpLVItem : item or subitem info
7149 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7151 BOOL bResult = TRUE;
7154 lvItem.iItem = nItem;
7155 lvItem.iSubItem = 0;
7156 lvItem.mask = LVIF_STATE;
7157 lvItem.state = lpLVItem->state;
7158 lvItem.stateMask = lpLVItem->stateMask;
7159 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7163 /* apply to all items */
7164 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7165 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7168 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7171 * Update selection mark
7173 * Investigation on windows 2k showed that selection mark was updated
7174 * whenever a new selection was made, but if the selected item was
7175 * unselected it was not updated.
7177 * we are probably still not 100% accurate, but this at least sets the
7178 * proper selection mark when it is needed
7181 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7182 ((infoPtr->nSelectionMark == -1) || (lvItem.iItem <= infoPtr->nSelectionMark)))
7185 infoPtr->nSelectionMark = -1;
7186 for (i = 0; i < infoPtr->nItemCount; i++)
7188 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7190 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7192 infoPtr->nSelectionMark = i;
7196 else if (ranges_contain(infoPtr->selectionRanges, i))
7198 infoPtr->nSelectionMark = i;
7209 * Sets the text of an item or subitem.
7212 * [I] hwnd : window handle
7213 * [I] nItem : item index
7214 * [I] lpLVItem : item or subitem info
7215 * [I] isW : TRUE if input is Unicode
7221 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7225 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7227 lvItem.iItem = nItem;
7228 lvItem.iSubItem = lpLVItem->iSubItem;
7229 lvItem.mask = LVIF_TEXT;
7230 lvItem.pszText = lpLVItem->pszText;
7231 lvItem.cchTextMax = lpLVItem->cchTextMax;
7233 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7235 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7240 * Set item index that marks the start of a multiple selection.
7243 * [I] infoPtr : valid pointer to the listview structure
7244 * [I] nIndex : index
7247 * Index number or -1 if there is no selection mark.
7249 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7251 INT nOldIndex = infoPtr->nSelectionMark;
7253 TRACE("(nIndex=%d)\n", nIndex);
7255 infoPtr->nSelectionMark = nIndex;
7262 * Sets the text background color.
7265 * [I] infoPtr : valid pointer to the listview structure
7266 * [I] clrTextBk : text background color
7272 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7274 TRACE("(clrTextBk=%lx)\n", clrTextBk);
7276 if (infoPtr->clrTextBk != clrTextBk)
7278 infoPtr->clrTextBk = clrTextBk;
7279 LISTVIEW_InvalidateList(infoPtr);
7287 * Sets the text foreground color.
7290 * [I] infoPtr : valid pointer to the listview structure
7291 * [I] clrText : text color
7297 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7299 TRACE("(clrText=%lx)\n", clrText);
7301 if (infoPtr->clrText != clrText)
7303 infoPtr->clrText = clrText;
7304 LISTVIEW_InvalidateList(infoPtr);
7312 * Determines which listview item is located at the specified position.
7315 * [I] infoPtr : valid pointer to the listview structure
7316 * [I] hwndNewToolTip : handle to new ToolTip
7321 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7323 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7324 infoPtr->hwndToolTip = hwndNewToolTip;
7325 return hwndOldToolTip;
7328 /* LISTVIEW_SetUnicodeFormat */
7329 /* LISTVIEW_SetWorkAreas */
7333 * Callback internally used by LISTVIEW_SortItems()
7336 * [I] first : pointer to first ITEM_INFO to compare
7337 * [I] second : pointer to second ITEM_INFO to compare
7338 * [I] lParam : HWND of control
7341 * if first comes before second : negative
7342 * if first comes after second : positive
7343 * if first and second are equivalent : zero
7345 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7347 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7348 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
7349 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
7351 /* Forward the call to the client defined callback */
7352 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7357 * Sorts the listview items.
7360 * [I] infoPtr : valid pointer to the listview structure
7361 * [I] pfnCompare : application-defined value
7362 * [I] lParamSort : pointer to comparision callback
7368 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
7370 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7373 LPVOID selectionMarkItem;
7377 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7379 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7381 if (!pfnCompare) return FALSE;
7382 if (!infoPtr->hdpaItems) return FALSE;
7384 /* if there are 0 or 1 items, there is no need to sort */
7385 if (infoPtr->nItemCount < 2) return TRUE;
7387 if (infoPtr->nFocusedItem >= 0)
7389 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7390 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7391 if (lpItem) lpItem->state |= LVIS_FOCUSED;
7393 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
7394 /* clear the lpItem->state for non-selected ones */
7395 /* remove the selection ranges */
7397 infoPtr->pfnCompare = pfnCompare;
7398 infoPtr->lParamSort = lParamSort;
7399 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
7401 /* Adjust selections and indices so that they are the way they should
7402 * be after the sort (otherwise, the list items move around, but
7403 * whatever is at the item's previous original position will be
7406 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
7407 for (i=0; i < infoPtr->nItemCount; i++)
7409 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
7410 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7412 if (lpItem->state & LVIS_SELECTED)
7414 item.state = LVIS_SELECTED;
7415 item.stateMask = LVIS_SELECTED;
7416 LISTVIEW_SetItemState(infoPtr, i, &item);
7418 if (lpItem->state & LVIS_FOCUSED)
7420 infoPtr->nFocusedItem = i;
7421 lpItem->state &= ~LVIS_FOCUSED;
7424 if (selectionMarkItem != NULL)
7425 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7426 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7428 /* refresh the display */
7429 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7430 LISTVIEW_InvalidateList(infoPtr);
7437 * Updates an items or rearranges the listview control.
7440 * [I] infoPtr : valid pointer to the listview structure
7441 * [I] nItem : item index
7447 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
7449 TRACE("(nItem=%d)\n", nItem);
7451 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7453 /* rearrange with default alignment style */
7454 if (is_autoarrange(infoPtr))
7455 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7457 LISTVIEW_InvalidateItem(infoPtr, nItem);
7465 * Creates the listview control.
7468 * [I] hwnd : window handle
7469 * [I] lpcs : the create parameters
7475 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
7477 LISTVIEW_INFO *infoPtr;
7478 UINT uView = lpcs->style & LVS_TYPEMASK;
7481 TRACE("(lpcs=%p)\n", lpcs);
7483 /* initialize info pointer */
7484 infoPtr = (LISTVIEW_INFO *)Alloc(sizeof(LISTVIEW_INFO));
7485 if (!infoPtr) return -1;
7487 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
7489 infoPtr->hwndSelf = hwnd;
7490 infoPtr->dwStyle = lpcs->style;
7491 /* determine the type of structures to use */
7492 infoPtr->hwndNotify = lpcs->hwndParent;
7493 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
7494 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
7496 /* initialize color information */
7497 infoPtr->clrBk = CLR_NONE;
7498 infoPtr->clrText = comctl32_color.clrWindowText;
7499 infoPtr->clrTextBk = CLR_DEFAULT;
7500 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
7502 /* set default values */
7503 infoPtr->nFocusedItem = -1;
7504 infoPtr->nSelectionMark = -1;
7505 infoPtr->nHotItem = -1;
7506 infoPtr->bRedraw = TRUE;
7507 infoPtr->bNoItemMetrics = TRUE;
7508 infoPtr->bDoChangeNotify = TRUE;
7509 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
7510 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
7511 infoPtr->nEditLabelItem = -1;
7512 infoPtr->dwHoverTime = -1; /* default system hover time */
7513 infoPtr->nMeasureItemHeight = 0;
7515 /* get default font (icon title) */
7516 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
7517 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
7518 infoPtr->hFont = infoPtr->hDefaultFont;
7519 LISTVIEW_SaveTextMetrics(infoPtr);
7522 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL,
7523 WS_CHILD | HDS_HORZ | (DWORD)((LVS_NOSORTHEADER & lpcs->style)?0:HDS_BUTTONS),
7524 0, 0, 0, 0, hwnd, NULL,
7525 lpcs->hInstance, NULL);
7526 if (!infoPtr->hwndHeader) goto fail;
7528 /* set header unicode format */
7529 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)NULL);
7531 /* set header font */
7532 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
7534 /* allocate memory for the data structure */
7535 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
7536 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
7537 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
7538 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
7539 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
7541 /* initialize the icon sizes */
7542 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
7543 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
7545 /* init item size to avoid division by 0 */
7546 LISTVIEW_UpdateItemSize (infoPtr);
7548 if (uView == LVS_REPORT)
7550 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
7552 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
7556 /* set HDS_HIDDEN flag to hide the header bar */
7557 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE,
7558 GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE) | HDS_HIDDEN);
7565 DestroyWindow(infoPtr->hwndHeader);
7566 ranges_destroy(infoPtr->selectionRanges);
7567 DPA_Destroy(infoPtr->hdpaItems);
7568 DPA_Destroy(infoPtr->hdpaPosX);
7569 DPA_Destroy(infoPtr->hdpaPosY);
7570 DPA_Destroy(infoPtr->hdpaColumns);
7577 * Enables the listview control.
7580 * [I] infoPtr : valid pointer to the listview structure
7581 * [I] bEnable : specifies whether to enable or disable the window
7587 static BOOL LISTVIEW_Enable(LISTVIEW_INFO *infoPtr, BOOL bEnable)
7589 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
7590 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
7596 * Erases the background of the listview control.
7599 * [I] infoPtr : valid pointer to the listview structure
7600 * [I] hdc : device context handle
7606 static inline BOOL LISTVIEW_EraseBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc)
7610 TRACE("(hdc=%p)\n", hdc);
7612 if (!GetClipBox(hdc, &rc)) return FALSE;
7614 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
7620 * Helper function for LISTVIEW_[HV]Scroll *only*.
7621 * Performs vertical/horizontal scrolling by a give amount.
7624 * [I] infoPtr : valid pointer to the listview structure
7625 * [I] dx : amount of horizontal scroll
7626 * [I] dy : amount of vertical scroll
7628 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7630 /* now we can scroll the list */
7631 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
7632 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
7633 /* if we have focus, adjust rect */
7634 OffsetRect(&infoPtr->rcFocus, dx, dy);
7635 UpdateWindow(infoPtr->hwndSelf);
7640 * Performs vertical scrolling.
7643 * [I] infoPtr : valid pointer to the listview structure
7644 * [I] nScrollCode : scroll code
7645 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7646 * [I] hScrollWnd : scrollbar control window handle
7652 * SB_LINEUP/SB_LINEDOWN:
7653 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
7654 * for LVS_REPORT is 1 line
7655 * for LVS_LIST cannot occur
7658 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7659 INT nScrollDiff, HWND hScrollWnd)
7661 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7662 INT nOldScrollPos, nNewScrollPos;
7663 SCROLLINFO scrollInfo;
7666 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
7667 debugscrollcode(nScrollCode), nScrollDiff);
7669 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7671 scrollInfo.cbSize = sizeof(SCROLLINFO);
7672 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7674 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
7676 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
7678 nOldScrollPos = scrollInfo.nPos;
7679 switch (nScrollCode)
7685 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
7689 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
7693 nScrollDiff = -scrollInfo.nPage;
7697 nScrollDiff = scrollInfo.nPage;
7700 case SB_THUMBPOSITION:
7702 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7709 /* quit right away if pos isn't changing */
7710 if (nScrollDiff == 0) return 0;
7712 /* calculate new position, and handle overflows */
7713 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7714 if (nScrollDiff > 0) {
7715 if (nNewScrollPos < nOldScrollPos ||
7716 nNewScrollPos > scrollInfo.nMax)
7717 nNewScrollPos = scrollInfo.nMax;
7719 if (nNewScrollPos > nOldScrollPos ||
7720 nNewScrollPos < scrollInfo.nMin)
7721 nNewScrollPos = scrollInfo.nMin;
7724 /* set the new position, and reread in case it changed */
7725 scrollInfo.fMask = SIF_POS;
7726 scrollInfo.nPos = nNewScrollPos;
7727 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
7729 /* carry on only if it really changed */
7730 if (nNewScrollPos == nOldScrollPos) return 0;
7732 /* now adjust to client coordinates */
7733 nScrollDiff = nOldScrollPos - nNewScrollPos;
7734 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
7736 /* and scroll the window */
7737 scroll_list(infoPtr, 0, nScrollDiff);
7744 * Performs horizontal scrolling.
7747 * [I] infoPtr : valid pointer to the listview structure
7748 * [I] nScrollCode : scroll code
7749 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7750 * [I] hScrollWnd : scrollbar control window handle
7756 * SB_LINELEFT/SB_LINERIGHT:
7757 * for LVS_ICON, LVS_SMALLICON 1 pixel
7758 * for LVS_REPORT is 1 pixel
7759 * for LVS_LIST is 1 column --> which is a 1 because the
7760 * scroll is based on columns not pixels
7763 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7764 INT nScrollDiff, HWND hScrollWnd)
7766 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7767 INT nOldScrollPos, nNewScrollPos;
7768 SCROLLINFO scrollInfo;
7770 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
7771 debugscrollcode(nScrollCode), nScrollDiff);
7773 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7775 scrollInfo.cbSize = sizeof(SCROLLINFO);
7776 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7778 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
7780 nOldScrollPos = scrollInfo.nPos;
7782 switch (nScrollCode)
7796 nScrollDiff = -scrollInfo.nPage;
7800 nScrollDiff = scrollInfo.nPage;
7803 case SB_THUMBPOSITION:
7805 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7812 /* quit right away if pos isn't changing */
7813 if (nScrollDiff == 0) return 0;
7815 /* calculate new position, and handle overflows */
7816 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7817 if (nScrollDiff > 0) {
7818 if (nNewScrollPos < nOldScrollPos ||
7819 nNewScrollPos > scrollInfo.nMax)
7820 nNewScrollPos = scrollInfo.nMax;
7822 if (nNewScrollPos > nOldScrollPos ||
7823 nNewScrollPos < scrollInfo.nMin)
7824 nNewScrollPos = scrollInfo.nMin;
7827 /* set the new position, and reread in case it changed */
7828 scrollInfo.fMask = SIF_POS;
7829 scrollInfo.nPos = nNewScrollPos;
7830 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
7832 /* carry on only if it really changed */
7833 if (nNewScrollPos == nOldScrollPos) return 0;
7835 if(uView == LVS_REPORT)
7836 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
7838 /* now adjust to client coordinates */
7839 nScrollDiff = nOldScrollPos - nNewScrollPos;
7840 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
7842 /* and scroll the window */
7843 scroll_list(infoPtr, nScrollDiff, 0);
7848 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
7850 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7851 INT gcWheelDelta = 0;
7852 INT pulScrollLines = 3;
7853 SCROLLINFO scrollInfo;
7855 TRACE("(wheelDelta=%d)\n", wheelDelta);
7857 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
7858 gcWheelDelta -= wheelDelta;
7860 scrollInfo.cbSize = sizeof(SCROLLINFO);
7861 scrollInfo.fMask = SIF_POS;
7868 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
7869 * should be fixed in the future.
7871 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
7872 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
7876 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
7878 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
7879 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
7880 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
7885 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
7896 * [I] infoPtr : valid pointer to the listview structure
7897 * [I] nVirtualKey : virtual key
7898 * [I] lKeyData : key data
7903 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
7905 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7907 NMLVKEYDOWN nmKeyDown;
7909 TRACE("(nVirtualKey=%d, lKeyData=%ld)\n", nVirtualKey, lKeyData);
7911 /* send LVN_KEYDOWN notification */
7912 nmKeyDown.wVKey = nVirtualKey;
7913 nmKeyDown.flags = 0;
7914 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
7916 switch (nVirtualKey)
7919 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
7921 notify(infoPtr, NM_RETURN);
7922 notify(infoPtr, LVN_ITEMACTIVATE);
7927 if (infoPtr->nItemCount > 0)
7932 if (infoPtr->nItemCount > 0)
7933 nItem = infoPtr->nItemCount - 1;
7937 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
7941 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
7945 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
7949 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
7953 if (uView == LVS_REPORT)
7954 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr);
7956 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
7957 * LISTVIEW_GetCountPerRow(infoPtr);
7958 if(nItem < 0) nItem = 0;
7962 if (uView == LVS_REPORT)
7963 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr);
7965 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
7966 * LISTVIEW_GetCountPerRow(infoPtr);
7967 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
7971 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem))
7972 LISTVIEW_KeySelection(infoPtr, nItem);
7982 * [I] infoPtr : valid pointer to the listview structure
7987 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
7991 /* if we did not have the focus, there's nothing to do */
7992 if (!infoPtr->bFocus) return 0;
7994 /* send NM_KILLFOCUS notification */
7995 notify(infoPtr, NM_KILLFOCUS);
7997 /* if we have a focus rectagle, get rid of it */
7998 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
8000 /* set window focus flag */
8001 infoPtr->bFocus = FALSE;
8003 /* invalidate the selected items before reseting focus flag */
8004 LISTVIEW_InvalidateSelectedItems(infoPtr);
8011 * Processes double click messages (left mouse button).
8014 * [I] infoPtr : valid pointer to the listview structure
8015 * [I] wKey : key flag
8016 * [I] x,y : mouse coordinate
8021 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8023 LVHITTESTINFO htInfo;
8025 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8027 /* send NM_RELEASEDCAPTURE notification */
8028 notify(infoPtr, NM_RELEASEDCAPTURE);
8033 /* send NM_DBLCLK notification */
8034 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
8035 notify_click(infoPtr, NM_DBLCLK, &htInfo);
8037 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
8038 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
8045 * Processes mouse down messages (left mouse button).
8048 * [I] infoPtr : valid pointer to the listview structure
8049 * [I] wKey : key flag
8050 * [I] x,y : mouse coordinate
8055 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8057 LVHITTESTINFO lvHitTestInfo;
8058 static BOOL bGroupSelect = TRUE;
8059 POINT pt = { x, y };
8062 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8064 /* send NM_RELEASEDCAPTURE notification */
8065 notify(infoPtr, NM_RELEASEDCAPTURE);
8067 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
8069 /* set left button down flag and record the click position */
8070 infoPtr->bLButtonDown = TRUE;
8071 infoPtr->ptClickPos = pt;
8073 lvHitTestInfo.pt.x = x;
8074 lvHitTestInfo.pt.y = y;
8076 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8077 TRACE("at %s, nItem=%d\n", debugpoint(&pt), nItem);
8078 infoPtr->nEditLabelItem = -1;
8079 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8081 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8083 DWORD state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK) >> 12;
8084 if(state == 1 || state == 2)
8088 lvitem.state = state << 12;
8089 lvitem.stateMask = LVIS_STATEIMAGEMASK;
8090 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
8095 if (infoPtr->dwStyle & LVS_SINGLESEL)
8097 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8098 infoPtr->nEditLabelItem = nItem;
8100 LISTVIEW_SetSelection(infoPtr, nItem);
8104 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8108 LISTVIEW_AddGroupSelection(infoPtr, nItem);
8109 LISTVIEW_SetItemFocus(infoPtr, nItem);
8110 infoPtr->nSelectionMark = nItem;
8116 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8117 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8119 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8120 infoPtr->nSelectionMark = nItem;
8123 else if (wKey & MK_CONTROL)
8127 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8129 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8130 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8131 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8132 infoPtr->nSelectionMark = nItem;
8134 else if (wKey & MK_SHIFT)
8136 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8140 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8141 infoPtr->nEditLabelItem = nItem;
8143 /* set selection (clears other pre-existing selections) */
8144 LISTVIEW_SetSelection(infoPtr, nItem);
8150 /* remove all selections */
8151 LISTVIEW_DeselectAll(infoPtr);
8160 * Processes mouse up messages (left mouse button).
8163 * [I] infoPtr : valid pointer to the listview structure
8164 * [I] wKey : key flag
8165 * [I] x,y : mouse coordinate
8170 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8172 LVHITTESTINFO lvHitTestInfo;
8174 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8176 if (!infoPtr->bLButtonDown) return 0;
8178 lvHitTestInfo.pt.x = x;
8179 lvHitTestInfo.pt.y = y;
8181 /* send NM_CLICK notification */
8182 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8183 notify_click(infoPtr, NM_CLICK, &lvHitTestInfo);
8185 /* set left button flag */
8186 infoPtr->bLButtonDown = FALSE;
8188 /* if we clicked on a selected item, edit the label */
8189 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8190 LISTVIEW_EditLabelT(infoPtr, lvHitTestInfo.iItem, TRUE);
8197 * Destroys the listview control (called after WM_DESTROY).
8200 * [I] infoPtr : valid pointer to the listview structure
8205 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8209 /* delete all items */
8210 LISTVIEW_DeleteAllItems(infoPtr);
8212 /* destroy data structure */
8213 DPA_Destroy(infoPtr->hdpaItems);
8214 DPA_Destroy(infoPtr->hdpaPosX);
8215 DPA_Destroy(infoPtr->hdpaPosY);
8216 DPA_Destroy(infoPtr->hdpaColumns);
8217 ranges_destroy(infoPtr->selectionRanges);
8219 /* destroy image lists */
8220 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8222 if (infoPtr->himlNormal)
8223 ImageList_Destroy(infoPtr->himlNormal);
8224 if (infoPtr->himlSmall)
8225 ImageList_Destroy(infoPtr->himlSmall);
8226 if (infoPtr->himlState)
8227 ImageList_Destroy(infoPtr->himlState);
8230 /* destroy font, bkgnd brush */
8232 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8233 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8235 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
8237 /* free listview info pointer*/
8245 * Handles notifications from header.
8248 * [I] infoPtr : valid pointer to the listview structure
8249 * [I] nCtrlId : control identifier
8250 * [I] lpnmh : notification information
8255 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8257 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8259 TRACE("(lpnmh=%p)\n", lpnmh);
8261 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8263 switch (lpnmh->hdr.code)
8267 case HDN_ITEMCHANGEDW:
8268 case HDN_ITEMCHANGEDA:
8270 COLUMN_INFO *lpColumnInfo;
8273 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8277 hdi.mask = HDI_WIDTH;
8278 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, (LPARAM)&hdi)) return 0;
8282 cxy = lpnmh->pitem->cxy;
8284 /* determine how much we change since the last know position */
8285 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8286 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8289 RECT rcCol = lpColumnInfo->rcHeader;
8291 lpColumnInfo->rcHeader.right += dx;
8292 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
8293 LISTVIEW_UpdateItemSize(infoPtr);
8294 if (uView == LVS_REPORT && is_redrawing(infoPtr))
8296 /* this trick works for left aligned columns only */
8297 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8299 rcCol.right = min (rcCol.right, lpColumnInfo->rcHeader.right);
8300 rcCol.left = max (rcCol.left, rcCol.right - 3 * infoPtr->ntmAveCharWidth);
8302 rcCol.top = infoPtr->rcList.top;
8303 rcCol.bottom = infoPtr->rcList.bottom;
8304 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
8310 case HDN_ITEMCLICKW:
8311 case HDN_ITEMCLICKA:
8313 /* Handle sorting by Header Column */
8316 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
8318 nmlv.iSubItem = lpnmh->iItem;
8319 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
8323 case HDN_DIVIDERDBLCLICKW:
8324 case HDN_DIVIDERDBLCLICKA:
8325 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
8334 * Determines the type of structure to use.
8337 * [I] infoPtr : valid pointer to the listview structureof the sender
8338 * [I] hwndFrom : listview window handle
8339 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
8344 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
8346 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
8348 if (nCommand != NF_REQUERY) return 0;
8350 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
8357 * Paints/Repaints the listview control.
8360 * [I] infoPtr : valid pointer to the listview structure
8361 * [I] hdc : device context handle
8366 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
8368 TRACE("(hdc=%p)\n", hdc);
8370 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
8372 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8374 infoPtr->bNoItemMetrics = FALSE;
8375 LISTVIEW_UpdateItemSize(infoPtr);
8376 if (uView == LVS_ICON || uView == LVS_SMALLICON)
8377 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8378 LISTVIEW_UpdateScroll(infoPtr);
8381 LISTVIEW_Refresh(infoPtr, hdc);
8386 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
8388 if (ps.fErase) LISTVIEW_FillBkgnd(infoPtr, hdc, &ps.rcPaint);
8389 LISTVIEW_Refresh(infoPtr, hdc);
8390 EndPaint(infoPtr->hwndSelf, &ps);
8399 * Paints/Repaints the listview control.
8402 * [I] infoPtr : valid pointer to the listview structure
8403 * [I] hdc : device context handle
8404 * [I] options : drawing options
8409 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
8411 FIXME("Partial Stub: (hdc=%p options=0x%08lx)\n", hdc, options);
8413 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
8416 if (options & PRF_ERASEBKGND)
8417 LISTVIEW_EraseBkgnd(infoPtr, hdc);
8419 if (options & PRF_CLIENT)
8420 LISTVIEW_Paint(infoPtr, hdc);
8428 * Processes double click messages (right mouse button).
8431 * [I] infoPtr : valid pointer to the listview structure
8432 * [I] wKey : key flag
8433 * [I] x,y : mouse coordinate
8438 static LRESULT LISTVIEW_RButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8440 LVHITTESTINFO lvHitTestInfo;
8442 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
8444 /* send NM_RELEASEDCAPTURE notification */
8445 notify(infoPtr, NM_RELEASEDCAPTURE);
8447 /* send NM_RDBLCLK notification */
8448 lvHitTestInfo.pt.x = x;
8449 lvHitTestInfo.pt.y = y;
8450 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8451 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
8458 * Processes mouse down messages (right mouse button).
8461 * [I] infoPtr : valid pointer to the listview structure
8462 * [I] wKey : key flag
8463 * [I] x,y : mouse coordinate
8468 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8470 LVHITTESTINFO lvHitTestInfo;
8473 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
8475 /* send NM_RELEASEDCAPTURE notification */
8476 notify(infoPtr, NM_RELEASEDCAPTURE);
8478 /* make sure the listview control window has the focus */
8479 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
8481 /* set right button down flag */
8482 infoPtr->bRButtonDown = TRUE;
8484 /* determine the index of the selected item */
8485 lvHitTestInfo.pt.x = x;
8486 lvHitTestInfo.pt.y = y;
8487 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8489 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8491 LISTVIEW_SetItemFocus(infoPtr, nItem);
8492 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
8493 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8494 LISTVIEW_SetSelection(infoPtr, nItem);
8498 LISTVIEW_DeselectAll(infoPtr);
8506 * Processes mouse up messages (right mouse button).
8509 * [I] infoPtr : valid pointer to the listview structure
8510 * [I] wKey : key flag
8511 * [I] x,y : mouse coordinate
8516 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8518 LVHITTESTINFO lvHitTestInfo;
8521 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
8523 if (!infoPtr->bRButtonDown) return 0;
8525 /* set button flag */
8526 infoPtr->bRButtonDown = FALSE;
8528 /* Send NM_RClICK notification */
8529 lvHitTestInfo.pt.x = x;
8530 lvHitTestInfo.pt.y = y;
8531 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8532 notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo);
8534 /* Change to screen coordinate for WM_CONTEXTMENU */
8535 pt = lvHitTestInfo.pt;
8536 ClientToScreen(infoPtr->hwndSelf, &pt);
8538 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
8539 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
8540 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
8551 * [I] infoPtr : valid pointer to the listview structure
8552 * [I] hwnd : window handle of window containing the cursor
8553 * [I] nHittest : hit-test code
8554 * [I] wMouseMsg : ideintifier of the mouse message
8557 * TRUE if cursor is set
8560 static BOOL LISTVIEW_SetCursor(LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
8562 LVHITTESTINFO lvHitTestInfo;
8564 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
8566 if(!infoPtr->hHotCursor) return FALSE;
8568 GetCursorPos(&lvHitTestInfo.pt);
8569 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
8571 SetCursor(infoPtr->hHotCursor);
8581 * [I] infoPtr : valid pointer to the listview structure
8582 * [I] hwndLoseFocus : handle of previously focused window
8587 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
8589 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
8591 /* if we have the focus already, there's nothing to do */
8592 if (infoPtr->bFocus) return 0;
8594 /* send NM_SETFOCUS notification */
8595 notify(infoPtr, NM_SETFOCUS);
8597 /* set window focus flag */
8598 infoPtr->bFocus = TRUE;
8600 /* put the focus rect back on */
8601 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
8603 /* redraw all visible selected items */
8604 LISTVIEW_InvalidateSelectedItems(infoPtr);
8614 * [I] infoPtr : valid pointer to the listview structure
8615 * [I] fRedraw : font handle
8616 * [I] fRedraw : redraw flag
8621 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
8623 HFONT oldFont = infoPtr->hFont;
8625 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
8627 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
8628 if (infoPtr->hFont == oldFont) return 0;
8630 LISTVIEW_SaveTextMetrics(infoPtr);
8632 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
8633 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
8635 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
8642 * Message handling for WM_SETREDRAW.
8643 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
8646 * [I] infoPtr : valid pointer to the listview structure
8647 * [I] bRedraw: state of redraw flag
8650 * DefWinProc return value
8652 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
8654 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
8656 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
8657 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
8659 infoPtr->bRedraw = bRedraw;
8661 if(!bRedraw) return 0;
8663 if (is_autoarrange(infoPtr))
8664 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8665 LISTVIEW_UpdateScroll(infoPtr);
8667 /* despite what the WM_SETREDRAW docs says, apps expect us
8668 * to invalidate the listview here... stupid! */
8669 LISTVIEW_InvalidateList(infoPtr);
8676 * Resizes the listview control. This function processes WM_SIZE
8677 * messages. At this time, the width and height are not used.
8680 * [I] infoPtr : valid pointer to the listview structure
8681 * [I] Width : new width
8682 * [I] Height : new height
8687 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
8689 RECT rcOld = infoPtr->rcList;
8691 TRACE("(width=%d, height=%d)\n", Width, Height);
8693 LISTVIEW_UpdateSize(infoPtr);
8694 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
8696 /* do not bother with display related stuff if we're not redrawing */
8697 if (!is_redrawing(infoPtr)) return 0;
8699 if (is_autoarrange(infoPtr))
8700 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8702 LISTVIEW_UpdateScroll(infoPtr);
8704 /* refresh all only for lists whose height changed significantly */
8705 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
8706 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
8707 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
8708 LISTVIEW_InvalidateList(infoPtr);
8715 * Sets the size information.
8718 * [I] infoPtr : valid pointer to the listview structure
8723 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
8725 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8727 TRACE("uView=%d, rcList(old)=%s\n", uView, debugrect(&infoPtr->rcList));
8729 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
8731 if (uView == LVS_LIST)
8733 /* Apparently the "LIST" style is supposed to have the same
8734 * number of items in a column even if there is no scroll bar.
8735 * Since if a scroll bar already exists then the bottom is already
8736 * reduced, only reduce if the scroll bar does not currently exist.
8737 * The "2" is there to mimic the native control. I think it may be
8738 * related to either padding or edges. (GLA 7/2002)
8740 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
8741 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
8742 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
8744 else if (uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER))
8749 hl.prc = &infoPtr->rcList;
8751 Header_Layout(infoPtr->hwndHeader, &hl);
8753 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8755 infoPtr->rcList.top = max(wp.cy, 0);
8758 TRACE(" rcList=%s\n", debugrect(&infoPtr->rcList));
8763 * Processes WM_STYLECHANGED messages.
8766 * [I] infoPtr : valid pointer to the listview structure
8767 * [I] wStyleType : window style type (normal or extended)
8768 * [I] lpss : window style information
8773 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
8774 const STYLESTRUCT *lpss)
8776 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
8777 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
8779 TRACE("(styletype=%x, styleOld=0x%08lx, styleNew=0x%08lx)\n",
8780 wStyleType, lpss->styleOld, lpss->styleNew);
8782 if (wStyleType != GWL_STYLE) return 0;
8784 /* FIXME: if LVS_NOSORTHEADER changed, update header */
8785 /* what if LVS_OWNERDATA changed? */
8786 /* or LVS_SINGLESEL */
8787 /* or LVS_SORT{AS,DES}CENDING */
8789 infoPtr->dwStyle = lpss->styleNew;
8791 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
8792 ((lpss->styleNew & WS_HSCROLL) == 0))
8793 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
8795 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
8796 ((lpss->styleNew & WS_VSCROLL) == 0))
8797 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
8799 if (uNewView != uOldView)
8801 SIZE oldIconSize = infoPtr->iconSize;
8804 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8805 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8807 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
8808 SetRectEmpty(&infoPtr->rcFocus);
8810 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
8811 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
8813 if (uNewView == LVS_ICON)
8815 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
8817 TRACE("icon old size=(%ld,%ld), new size=(%ld,%ld)\n",
8818 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
8819 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
8822 else if (uNewView == LVS_REPORT)
8827 hl.prc = &infoPtr->rcList;
8829 Header_Layout(infoPtr->hwndHeader, &hl);
8830 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8833 LISTVIEW_UpdateItemSize(infoPtr);
8836 if (uNewView == LVS_REPORT)
8837 ShowWindow(infoPtr->hwndHeader, (lpss->styleNew & LVS_NOCOLUMNHEADER) ? SW_HIDE : SW_SHOWNORMAL);
8839 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
8840 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
8841 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8843 /* update the size of the client area */
8844 LISTVIEW_UpdateSize(infoPtr);
8846 /* add scrollbars if needed */
8847 LISTVIEW_UpdateScroll(infoPtr);
8849 /* invalidate client area + erase background */
8850 LISTVIEW_InvalidateList(infoPtr);
8857 * Window procedure of the listview control.
8860 static LRESULT WINAPI
8861 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8863 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8865 TRACE("(uMsg=%x wParam=%x lParam=%lx)\n", uMsg, wParam, lParam);
8867 if (!infoPtr && (uMsg != WM_CREATE))
8868 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8872 case LVM_APPROXIMATEVIEWRECT:
8873 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
8874 LOWORD(lParam), HIWORD(lParam));
8876 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
8878 /* case LVM_CANCELEDITLABEL: */
8880 case LVM_CREATEDRAGIMAGE:
8881 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
8883 case LVM_DELETEALLITEMS:
8884 return LISTVIEW_DeleteAllItems(infoPtr);
8886 case LVM_DELETECOLUMN:
8887 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
8889 case LVM_DELETEITEM:
8890 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
8892 case LVM_EDITLABELW:
8893 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
8895 case LVM_EDITLABELA:
8896 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
8898 /* case LVM_ENABLEGROUPVIEW: */
8900 case LVM_ENSUREVISIBLE:
8901 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
8904 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
8907 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
8909 case LVM_GETBKCOLOR:
8910 return infoPtr->clrBk;
8912 /* case LVM_GETBKIMAGE: */
8914 case LVM_GETCALLBACKMASK:
8915 return infoPtr->uCallbackMask;
8917 case LVM_GETCOLUMNA:
8918 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8920 case LVM_GETCOLUMNW:
8921 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8923 case LVM_GETCOLUMNORDERARRAY:
8924 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
8926 case LVM_GETCOLUMNWIDTH:
8927 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
8929 case LVM_GETCOUNTPERPAGE:
8930 return LISTVIEW_GetCountPerPage(infoPtr);
8932 case LVM_GETEDITCONTROL:
8933 return (LRESULT)infoPtr->hwndEdit;
8935 case LVM_GETEXTENDEDLISTVIEWSTYLE:
8936 return infoPtr->dwLvExStyle;
8938 /* case LVM_GETGROUPINFO: */
8940 /* case LVM_GETGROUPMETRICS: */
8943 return (LRESULT)infoPtr->hwndHeader;
8945 case LVM_GETHOTCURSOR:
8946 return (LRESULT)infoPtr->hHotCursor;
8948 case LVM_GETHOTITEM:
8949 return infoPtr->nHotItem;
8951 case LVM_GETHOVERTIME:
8952 return infoPtr->dwHoverTime;
8954 case LVM_GETIMAGELIST:
8955 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
8957 /* case LVM_GETINSERTMARK: */
8959 /* case LVM_GETINSERTMARKCOLOR: */
8961 /* case LVM_GETINSERTMARKRECT: */
8963 case LVM_GETISEARCHSTRINGA:
8964 case LVM_GETISEARCHSTRINGW:
8965 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
8969 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
8972 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
8974 case LVM_GETITEMCOUNT:
8975 return infoPtr->nItemCount;
8977 case LVM_GETITEMPOSITION:
8978 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
8980 case LVM_GETITEMRECT:
8981 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
8983 case LVM_GETITEMSPACING:
8984 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
8986 case LVM_GETITEMSTATE:
8987 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
8989 case LVM_GETITEMTEXTA:
8990 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
8992 case LVM_GETITEMTEXTW:
8993 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
8995 case LVM_GETNEXTITEM:
8996 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
8998 case LVM_GETNUMBEROFWORKAREAS:
8999 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
9003 if (!lParam) return FALSE;
9004 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
9007 /* case LVM_GETOUTLINECOLOR: */
9009 /* case LVM_GETSELECTEDCOLUMN: */
9011 case LVM_GETSELECTEDCOUNT:
9012 return LISTVIEW_GetSelectedCount(infoPtr);
9014 case LVM_GETSELECTIONMARK:
9015 return infoPtr->nSelectionMark;
9017 case LVM_GETSTRINGWIDTHA:
9018 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
9020 case LVM_GETSTRINGWIDTHW:
9021 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
9023 case LVM_GETSUBITEMRECT:
9024 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
9026 case LVM_GETTEXTBKCOLOR:
9027 return infoPtr->clrTextBk;
9029 case LVM_GETTEXTCOLOR:
9030 return infoPtr->clrText;
9032 /* case LVM_GETTILEINFO: */
9034 /* case LVM_GETTILEVIEWINFO: */
9036 case LVM_GETTOOLTIPS:
9037 if( !infoPtr->hwndToolTip )
9038 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
9039 return (LRESULT)infoPtr->hwndToolTip;
9041 case LVM_GETTOPINDEX:
9042 return LISTVIEW_GetTopIndex(infoPtr);
9044 /*case LVM_GETUNICODEFORMAT:
9045 FIXME("LVM_GETUNICODEFORMAT: unimplemented\n");
9048 /* case LVM_GETVIEW: */
9050 case LVM_GETVIEWRECT:
9051 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
9053 case LVM_GETWORKAREAS:
9054 FIXME("LVM_GETWORKAREAS: unimplemented\n");
9057 /* case LVM_HASGROUP: */
9060 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
9062 case LVM_INSERTCOLUMNA:
9063 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9065 case LVM_INSERTCOLUMNW:
9066 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9068 /* case LVM_INSERTGROUP: */
9070 /* case LVM_INSERTGROUPSORTED: */
9072 case LVM_INSERTITEMA:
9073 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9075 case LVM_INSERTITEMW:
9076 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9078 /* case LVM_INSERTMARKHITTEST: */
9080 /* case LVM_ISGROUPVIEWENABLED: */
9082 /* case LVM_MAPIDTOINDEX: */
9084 /* case LVM_MAPINDEXTOID: */
9086 /* case LVM_MOVEGROUP: */
9088 /* case LVM_MOVEITEMTOGROUP: */
9090 case LVM_REDRAWITEMS:
9091 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
9093 /* case LVM_REMOVEALLGROUPS: */
9095 /* case LVM_REMOVEGROUP: */
9098 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
9100 case LVM_SETBKCOLOR:
9101 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
9103 /* case LVM_SETBKIMAGE: */
9105 case LVM_SETCALLBACKMASK:
9106 infoPtr->uCallbackMask = (UINT)wParam;
9109 case LVM_SETCOLUMNA:
9110 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9112 case LVM_SETCOLUMNW:
9113 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9115 case LVM_SETCOLUMNORDERARRAY:
9116 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9118 case LVM_SETCOLUMNWIDTH:
9119 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
9121 case LVM_SETEXTENDEDLISTVIEWSTYLE:
9122 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
9124 /* case LVM_SETGROUPINFO: */
9126 /* case LVM_SETGROUPMETRICS: */
9128 case LVM_SETHOTCURSOR:
9129 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
9131 case LVM_SETHOTITEM:
9132 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
9134 case LVM_SETHOVERTIME:
9135 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
9137 case LVM_SETICONSPACING:
9138 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9140 case LVM_SETIMAGELIST:
9141 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
9143 /* case LVM_SETINFOTIP: */
9145 /* case LVM_SETINSERTMARK: */
9147 /* case LVM_SETINSERTMARKCOLOR: */
9150 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9153 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9155 case LVM_SETITEMCOUNT:
9156 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
9158 case LVM_SETITEMPOSITION:
9161 pt.x = (short)LOWORD(lParam);
9162 pt.y = (short)HIWORD(lParam);
9163 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
9166 case LVM_SETITEMPOSITION32:
9167 if (lParam == 0) return FALSE;
9168 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
9170 case LVM_SETITEMSTATE:
9171 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
9173 case LVM_SETITEMTEXTA:
9174 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9176 case LVM_SETITEMTEXTW:
9177 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9179 /* case LVM_SETOUTLINECOLOR: */
9181 /* case LVM_SETSELECTEDCOLUMN: */
9183 case LVM_SETSELECTIONMARK:
9184 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
9186 case LVM_SETTEXTBKCOLOR:
9187 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
9189 case LVM_SETTEXTCOLOR:
9190 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
9192 /* case LVM_SETTILEINFO: */
9194 /* case LVM_SETTILEVIEWINFO: */
9196 /* case LVM_SETTILEWIDTH: */
9198 case LVM_SETTOOLTIPS:
9199 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
9201 /* case LVM_SETUNICODEFORMAT: */
9203 /* case LVM_SETVIEW: */
9205 /* case LVM_SETWORKAREAS: */
9207 /* case LVM_SORTGROUPS: */
9210 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
9212 /* LVM_SORTITEMSEX: */
9214 case LVM_SUBITEMHITTEST:
9215 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
9218 return LISTVIEW_Update(infoPtr, (INT)wParam);
9221 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
9224 return LISTVIEW_Command(infoPtr, wParam, lParam);
9227 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
9230 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
9233 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
9236 return DLGC_WANTCHARS | DLGC_WANTARROWS;
9239 return (LRESULT)infoPtr->hFont;
9242 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9245 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
9248 return LISTVIEW_KillFocus(infoPtr);
9250 case WM_LBUTTONDBLCLK:
9251 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9253 case WM_LBUTTONDOWN:
9254 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9257 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9260 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9263 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9266 return LISTVIEW_NCDestroy(infoPtr);
9269 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
9270 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
9273 case WM_NOTIFYFORMAT:
9274 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
9276 case WM_PRINTCLIENT:
9277 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
9280 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
9282 case WM_RBUTTONDBLCLK:
9283 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9285 case WM_RBUTTONDOWN:
9286 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9289 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9292 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
9297 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
9300 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
9303 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
9306 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9308 case WM_STYLECHANGED:
9309 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
9311 case WM_SYSCOLORCHANGE:
9312 COMCTL32_RefreshSysColors();
9315 /* case WM_TIMER: */
9318 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9321 if (wParam & (MK_SHIFT | MK_CONTROL))
9322 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9323 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
9325 case WM_WINDOWPOSCHANGED:
9326 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
9328 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9329 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
9330 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
9332 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
9334 MEASUREITEMSTRUCT mis;
9335 mis.CtlType = ODT_LISTVIEW;
9336 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
9340 mis.itemHeight= infoPtr->nItemHeight;
9341 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
9342 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
9343 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
9346 LISTVIEW_UpdateSize(infoPtr);
9347 LISTVIEW_UpdateScroll(infoPtr);
9349 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9351 /* case WM_WININICHANGE: */
9354 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
9355 ERR("unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam);
9358 /* call default window procedure */
9359 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9366 * Registers the window class.
9374 void LISTVIEW_Register(void)
9378 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
9379 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
9380 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
9381 wndClass.cbClsExtra = 0;
9382 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
9383 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
9384 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
9385 wndClass.lpszClassName = WC_LISTVIEWW;
9386 RegisterClassW(&wndClass);
9391 * Unregisters the window class.
9399 void LISTVIEW_Unregister(void)
9401 UnregisterClassW(WC_LISTVIEWW, NULL);
9406 * Handle any WM_COMMAND messages
9409 * [I] infoPtr : valid pointer to the listview structure
9410 * [I] wParam : the first message parameter
9411 * [I] lParam : the second message parameter
9416 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
9418 switch (HIWORD(wParam))
9423 * Adjust the edit window size
9426 HDC hdc = GetDC(infoPtr->hwndEdit);
9427 HFONT hFont, hOldFont = 0;
9432 if (!infoPtr->hwndEdit || !hdc) return 0;
9433 len = GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
9434 GetWindowRect(infoPtr->hwndEdit, &rect);
9436 /* Select font to get the right dimension of the string */
9437 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
9440 hOldFont = SelectObject(hdc, hFont);
9443 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
9445 TEXTMETRICW textMetric;
9447 /* Add Extra spacing for the next character */
9448 GetTextMetricsW(hdc, &textMetric);
9449 sz.cx += (textMetric.tmMaxCharWidth * 2);
9457 rect.bottom - rect.top,
9458 SWP_DRAWFRAME|SWP_NOMOVE);
9461 SelectObject(hdc, hOldFont);
9463 ReleaseDC(infoPtr->hwndEdit, hdc);
9469 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
9478 * Subclassed edit control windproc function
9481 * [I] hwnd : the edit window handle
9482 * [I] uMsg : the message that is to be processed
9483 * [I] wParam : first message parameter
9484 * [I] lParam : second message parameter
9485 * [I] isW : TRUE if input is Unicode
9490 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
9492 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
9493 BOOL cancel = FALSE;
9495 TRACE("(hwnd=%p, uMsg=%x, wParam=%x, lParam=%lx, isW=%d)\n",
9496 hwnd, uMsg, wParam, lParam, isW);
9501 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
9508 WNDPROC editProc = infoPtr->EditWndProc;
9509 infoPtr->EditWndProc = 0;
9510 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
9511 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
9515 if (VK_ESCAPE == (INT)wParam)
9520 else if (VK_RETURN == (INT)wParam)
9524 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
9528 if (infoPtr->hwndEdit)
9530 LPWSTR buffer = NULL;
9532 infoPtr->hwndEdit = 0;
9535 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
9539 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
9541 if (isW) GetWindowTextW(hwnd, buffer, len+1);
9542 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
9546 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
9548 if (buffer) Free(buffer);
9552 SendMessageW(hwnd, WM_CLOSE, 0, 0);
9558 * Subclassed edit control Unicode windproc function
9561 * [I] hwnd : the edit window handle
9562 * [I] uMsg : the message that is to be processed
9563 * [I] wParam : first message parameter
9564 * [I] lParam : second message parameter
9568 LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9570 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
9575 * Subclassed edit control ANSI windproc function
9578 * [I] hwnd : the edit window handle
9579 * [I] uMsg : the message that is to be processed
9580 * [I] wParam : first message parameter
9581 * [I] lParam : second message parameter
9585 LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9587 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
9592 * Creates a subclassed edit cotrol
9595 * [I] infoPtr : valid pointer to the listview structure
9596 * [I] text : initial text for the edit
9597 * [I] style : the window style
9598 * [I] isW : TRUE if input is Unicode
9602 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
9603 INT x, INT y, INT width, INT height, BOOL isW)
9605 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
9610 TEXTMETRICW textMetric;
9611 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
9613 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
9615 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
9616 hdc = GetDC(infoPtr->hwndSelf);
9618 /* Select the font to get appropriate metric dimensions */
9619 if(infoPtr->hFont != 0)
9620 hOldFont = SelectObject(hdc, infoPtr->hFont);
9622 /*Get String Length in pixels */
9623 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
9625 /*Add Extra spacing for the next character */
9626 GetTextMetricsW(hdc, &textMetric);
9627 sz.cx += (textMetric.tmMaxCharWidth * 2);
9629 if(infoPtr->hFont != 0)
9630 SelectObject(hdc, hOldFont);
9632 ReleaseDC(infoPtr->hwndSelf, hdc);
9634 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
9636 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
9638 if (!hedit) return 0;
9640 infoPtr->EditWndProc = (WNDPROC)
9641 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
9642 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
9644 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);