4 * Copyright 1998, 1999 Eric Kohl
5 * Copyright 1999 Luc Tourangeau
6 * Copyright 2000 Jason Mawdsley
7 * Copyright 2001 Codeweavers Inc.
8 * Copyright 2002 Dimitrie O. Paun
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 * This code was audited for completeness against the documented features
27 * of Comctl32.dll version 6.0 on Oct. 21, 2002, by Dimitrie O. Paun.
29 * Unless otherwise noted, we belive this code to be complete, as per
30 * the specification mentioned above.
31 * If you discover missing features, or bugs, please note them below.
36 * -- Hot item handling, mouse hovering
37 * -- Workareas support
42 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
43 * -- Support CustonDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs.
44 * -- in LISTVIEW_AddGroupSelection, se whould send LVN_ODSTATECHANGED
45 * -- LVA_SNAPTOGRID not implemented
46 * -- LISTVIEW_ApproximateViewRect partially implemented
47 * -- LISTVIEW_[GS]etColumnOrderArray stubs
48 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
49 * -- LISTVIEW_SetIconSpacing is incomplete
50 * -- LISTVIEW_SortItems is broken
51 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
54 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
55 * linear in the number of items in the list, and this is
56 * unacceptable for large lists.
57 * -- in sorted mode, LISTVIEW_InsertItemT sorts the array,
58 * instead of inserting in the right spot
59 * -- we should keep an ordered array of coordinates in iconic mode
60 * this would allow to frame items (iterator_frameditems),
61 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
69 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
76 * -- LVS_NOSCROLL (see Q137520)
77 * -- LVS_SORTASCENDING, LVS_SORTDESCENDING
80 * -- LVS_EX_BORDERSELECT
81 * -- LVS_EX_CHECKBOXES
84 * -- LVS_EX_HEADERDRAGDROP
87 * -- LVS_EX_MULTIWORKAREAS
88 * -- LVS_EX_ONECLICKACTIVATE
90 * -- LVS_EX_SIMPLESELECT
91 * -- LVS_EX_SUBITEMIMAGES
92 * -- LVS_EX_TRACKSELECT
93 * -- LVS_EX_TWOCLICKACTIVATE
94 * -- LVS_EX_UNDERLINECOLD
95 * -- LVS_EX_UNDERLINEHOT
98 * -- LVN_BEGINDRAG, LVN_BEGINRDRAG
99 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
102 * -- LVN_MARQUEEBEGIN
104 * -- LVN_ODSTATECHANGED
109 * -- LVM_CANCELEDITLABEL
110 * -- LVM_CREATEDRAGIMAGE
111 * -- LVM_ENABLEGROUPVIEW
112 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
113 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
114 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
115 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
116 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
117 * -- LVM_GETINSERTMARKRECT
118 * -- LVM_GETNUMBEROFWORKAREAS
119 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
120 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
121 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
122 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
123 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
124 * -- LVM_GETTOOLTIPS, LVM_SETTOOLTIPS
125 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
126 * -- LVM_GETVIEW, LVM_SETVIEW
127 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
128 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
129 * -- LVM_INSERTGROUPSORTED
130 * -- LVM_INSERTMARKHITTEST
131 * -- LVM_ISGROUPVIEWENABLED
132 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
134 * -- LVM_MOVEITEMTOGROUP
136 * -- LVM_SETTILEWIDTH
140 * Known differences in message stream from native control (not known if
141 * these differences cause problems):
142 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
143 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
144 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
145 * processing for "USEDOUBLECLICKTIME".
149 #include "wine/port.h"
160 #include "commctrl.h"
161 #include "comctl32.h"
163 #include "wine/debug.h"
164 #include "wine/unicode.h"
166 WINE_DEFAULT_DEBUG_CHANNEL(listview);
168 /* make sure you set this to 0 for production use! */
169 #define DEBUG_RANGES 1
171 typedef struct tagCOLUMN_INFO
173 RECT rcHeader; /* tracks the header's rectangle */
174 int fmt; /* same as LVCOLUMN.fmt */
177 typedef struct tagITEMHDR
181 } ITEMHDR, *LPITEMHDR;
183 typedef struct tagSUBITEM_INFO
189 typedef struct tagITEM_INFO
197 typedef struct tagRANGE
203 typedef struct tagRANGES
208 typedef struct tagITERATOR
217 typedef struct tagLISTVIEW_INFO
224 COLORREF clrTextBkDefault;
225 HIMAGELIST himlNormal;
226 HIMAGELIST himlSmall;
227 HIMAGELIST himlState;
232 RANGES selectionRanges;
236 RECT rcList; /* This rectangle is really the window
237 * client rectangle possibly reduced by the
238 * horizontal scroll bar and/or header - see
239 * LISTVIEW_UpdateSize. This rectangle offset
240 * by the LISTVIEW_GetOrigin value is in
241 * client coordinates */
250 INT ntmHeight; /* Some cached metrics of the font used */
251 INT ntmAveCharWidth; /* by the listview to draw items */
252 BOOL bRedraw; /* Turns on/off repaints & invalidations */
253 BOOL bFirstPaint; /* Flags if the control has never painted before */
254 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
258 DWORD dwStyle; /* the cached window GWL_STYLE */
259 DWORD dwLvExStyle; /* extended listview style */
260 INT nItemCount; /* the number of items in the list */
261 HDPA hdpaItems; /* array ITEM_INFO pointers */
262 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
263 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
264 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
265 POINT currIconPos; /* this is the position next icon will be placed */
266 PFNLVCOMPARE pfnCompare;
273 DWORD lastKeyPressTimestamp;
275 INT nSearchParamLength;
276 WCHAR szSearchParam[ MAX_PATH ];
283 /* How many we debug buffer to allocate */
284 #define DEBUG_BUFFERS 20
285 /* The size of a single debug bbuffer */
286 #define DEBUG_BUFFER_SIZE 256
288 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
289 #define SB_INTERNAL -1
291 /* maximum size of a label */
292 #define DISP_TEXT_SIZE 512
294 /* padding for items in list and small icon display modes */
295 #define WIDTH_PADDING 12
297 /* padding for items in list, report and small icon display modes */
298 #define HEIGHT_PADDING 1
300 /* offset of items in report display mode */
301 #define REPORT_MARGINX 2
303 /* padding for icon in large icon display mode
304 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
305 * that HITTEST will see.
306 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
307 * ICON_TOP_PADDING - sum of the two above.
308 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
309 * LABEL_HOR_PADDING - between text and sides of box
310 * LABEL_VERT_PADDING - between bottom of text and end of box
312 * ICON_LR_PADDING - additional width above icon size.
313 * ICON_LR_HALF - half of the above value
315 #define ICON_TOP_PADDING_NOTHITABLE 2
316 #define ICON_TOP_PADDING_HITABLE 2
317 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
318 #define ICON_BOTTOM_PADDING 4
319 #define LABEL_HOR_PADDING 5
320 #define LABEL_VERT_PADDING 7
321 #define ICON_LR_PADDING 16
322 #define ICON_LR_HALF (ICON_LR_PADDING/2)
324 /* default label width for items in list and small icon display modes */
325 #define DEFAULT_LABEL_WIDTH 40
327 /* default column width for items in list display mode */
328 #define DEFAULT_COLUMN_WIDTH 128
330 /* Size of "line" scroll for V & H scrolls */
331 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
333 /* Padding betwen image and label */
334 #define IMAGE_PADDING 2
336 /* Padding behind the label */
337 #define TRAILING_LABEL_PADDING 12
338 #define TRAILING_HEADER_PADDING 11
340 /* Border for the icon caption */
341 #define CAPTION_BORDER 2
343 /* Standard DrawText flags */
344 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
345 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
346 #define LV_SL_DT_FLAGS (DT_TOP | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
348 /* The time in milliseconds to reset the search in the list */
349 #define KEY_DELAY 450
351 /* Dump the LISTVIEW_INFO structure to the debug channel */
352 #define LISTVIEW_DUMP(iP) do { \
353 TRACE("hwndSelf=%p, clrBk=0x%06lx, clrText=0x%06lx, clrTextBk=0x%06lx, ItemHeight=%d, ItemWidth=%d, Style=0x%08lx\n", \
354 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
355 iP->nItemHeight, iP->nItemWidth, infoPtr->dwStyle); \
356 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08lx, Focus=%d\n", \
357 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
358 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
359 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%ld, icSz.cy=%ld, icSp.cx=%ld, icSp.cy=%ld, notifyFmt=%d\n", \
360 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
361 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
362 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, debugrect(&iP->rcList)); \
366 * forward declarations
368 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *, LPLVITEMW, BOOL);
369 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *, INT, LPRECT);
370 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *, INT, LPPOINT);
371 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *, INT, LPPOINT);
372 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *, INT, LPRECT);
373 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *, INT);
374 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *, LPPOINT);
375 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *, LPRECT);
376 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
377 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, const LVITEMW *, BOOL);
378 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *);
379 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
380 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
381 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
382 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
383 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *, PFNLVCOMPARE, LPARAM);
384 static INT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *, LPCWSTR, BOOL);
385 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT);
386 static UINT LISTVIEW_GetItemState(LISTVIEW_INFO *, INT, UINT);
387 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
388 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
389 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
390 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *);
391 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
392 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
394 /******** Text handling functions *************************************/
396 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
397 * text string. The string may be ANSI or Unicode, in which case
398 * the boolean isW tells us the type of the string.
400 * The name of the function tell what type of strings it expects:
401 * W: Unicode, T: ANSI/Unicode - function of isW
404 static inline BOOL is_textW(LPCWSTR text)
406 return text != NULL && text != LPSTR_TEXTCALLBACKW;
409 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
411 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
412 return is_textW(text);
415 static inline int textlenT(LPCWSTR text, BOOL isW)
417 return !is_textT(text, isW) ? 0 :
418 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
421 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
424 if (isSrcW) lstrcpynW(dest, src, max);
425 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
427 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
428 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
431 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
433 LPWSTR wstr = (LPWSTR)text;
435 if (!isW && is_textT(text, isW))
437 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
438 wstr = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
439 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
441 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
445 static inline void textfreeT(LPWSTR wstr, BOOL isW)
447 if (!isW && is_textT(wstr, isW)) HeapFree(GetProcessHeap(), 0, wstr);
451 * dest is a pointer to a Unicode string
452 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
454 static BOOL textsetptrT(LPWSTR *dest, LPWSTR src, BOOL isW)
458 if (src == LPSTR_TEXTCALLBACKW)
460 if (is_textW(*dest)) COMCTL32_Free(*dest);
461 *dest = LPSTR_TEXTCALLBACKW;
465 LPWSTR pszText = textdupTtoW(src, isW);
466 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
467 bResult = Str_SetPtrW(dest, pszText);
468 textfreeT(pszText, isW);
474 * compares a Unicode to a Unicode/ANSI text string
476 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
478 if (!aw) return bt ? -1 : 0;
479 if (!bt) return aw ? 1 : 0;
480 if (aw == LPSTR_TEXTCALLBACKW)
481 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
482 if (bt != LPSTR_TEXTCALLBACKW)
484 LPWSTR bw = textdupTtoW(bt, isW);
485 int r = bw ? lstrcmpW(aw, bw) : 1;
493 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
497 n = min(min(n, strlenW(s1)), strlenW(s2));
498 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
499 return res ? res - sizeof(WCHAR) : res;
502 /******** Debugging functions *****************************************/
504 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
506 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
507 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
510 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
512 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
513 n = min(textlenT(text, isW), n);
514 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
517 static char* debug_getbuf()
519 static int index = 0;
520 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
521 return buffers[index++ % DEBUG_BUFFERS];
524 static inline char* debugrange(const RANGE *lprng)
528 char* buf = debug_getbuf();
529 snprintf(buf, DEBUG_BUFFER_SIZE, "[%d, %d)", lprng->lower, lprng->upper);
531 } else return "(null)";
534 static inline char* debugpoint(const POINT *lppt)
538 char* buf = debug_getbuf();
539 snprintf(buf, DEBUG_BUFFER_SIZE, "(%ld, %ld)", lppt->x, lppt->y);
541 } else return "(null)";
544 static inline char* debugrect(const RECT *rect)
548 char* buf = debug_getbuf();
549 snprintf(buf, DEBUG_BUFFER_SIZE, "[(%ld, %ld);(%ld, %ld)]",
550 rect->left, rect->top, rect->right, rect->bottom);
552 } else return "(null)";
555 static char * debugscrollinfo(const SCROLLINFO *pScrollInfo)
557 char* buf = debug_getbuf(), *text = buf;
558 int len, size = DEBUG_BUFFER_SIZE;
560 if (pScrollInfo == NULL) return "(null)";
561 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
562 if (len == -1) goto end; buf += len; size -= len;
563 if (pScrollInfo->fMask & SIF_RANGE)
564 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
566 if (len == -1) goto end; buf += len; size -= len;
567 if (pScrollInfo->fMask & SIF_PAGE)
568 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
570 if (len == -1) goto end; buf += len; size -= len;
571 if (pScrollInfo->fMask & SIF_POS)
572 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
574 if (len == -1) goto end; buf += len; size -= len;
575 if (pScrollInfo->fMask & SIF_TRACKPOS)
576 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
578 if (len == -1) goto end; buf += len; size -= len;
581 buf = text + strlen(text);
583 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
587 static char* debugnmlistview(const NMLISTVIEW *plvnm)
591 char* buf = debug_getbuf();
592 snprintf(buf, DEBUG_BUFFER_SIZE, "iItem=%d, iSubItem=%d, uNewState=0x%x,"
593 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld\n",
594 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
595 plvnm->uChanged, debugpoint(&plvnm->ptAction), plvnm->lParam);
597 } else return "(null)";
600 static char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
602 char* buf = debug_getbuf(), *text = buf;
603 int len, size = DEBUG_BUFFER_SIZE;
605 if (lpLVItem == NULL) return "(null)";
606 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
607 if (len == -1) goto end; buf += len; size -= len;
608 if (lpLVItem->mask & LVIF_STATE)
609 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
611 if (len == -1) goto end; buf += len; size -= len;
612 if (lpLVItem->mask & LVIF_TEXT)
613 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
615 if (len == -1) goto end; buf += len; size -= len;
616 if (lpLVItem->mask & LVIF_IMAGE)
617 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
619 if (len == -1) goto end; buf += len; size -= len;
620 if (lpLVItem->mask & LVIF_PARAM)
621 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
623 if (len == -1) goto end; buf += len; size -= len;
624 if (lpLVItem->mask & LVIF_INDENT)
625 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
627 if (len == -1) goto end; buf += len; size -= len;
630 buf = text + strlen(text);
632 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
636 static char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
638 char* buf = debug_getbuf(), *text = buf;
639 int len, size = DEBUG_BUFFER_SIZE;
641 if (lpColumn == NULL) return "(null)";
642 len = snprintf(buf, size, "{");
643 if (len == -1) goto end; buf += len; size -= len;
644 if (lpColumn->mask & LVCF_SUBITEM)
645 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
647 if (len == -1) goto end; buf += len; size -= len;
648 if (lpColumn->mask & LVCF_FMT)
649 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
651 if (len == -1) goto end; buf += len; size -= len;
652 if (lpColumn->mask & LVCF_WIDTH)
653 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
655 if (len == -1) goto end; buf += len; size -= len;
656 if (lpColumn->mask & LVCF_TEXT)
657 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
659 if (len == -1) goto end; buf += len; size -= len;
660 if (lpColumn->mask & LVCF_IMAGE)
661 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
663 if (len == -1) goto end; buf += len; size -= len;
664 if (lpColumn->mask & LVCF_ORDER)
665 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
667 if (len == -1) goto end; buf += len; size -= len;
670 buf = text + strlen(text);
672 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
676 static char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
680 char* buf = debug_getbuf();
681 snprintf(buf, DEBUG_BUFFER_SIZE, "{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
682 debugpoint(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
684 } else return "(null)";
687 /* Return the corresponding text for a given scroll value */
688 static inline LPCSTR debugscrollcode(int nScrollCode)
692 case SB_LINELEFT: return "SB_LINELEFT";
693 case SB_LINERIGHT: return "SB_LINERIGHT";
694 case SB_PAGELEFT: return "SB_PAGELEFT";
695 case SB_PAGERIGHT: return "SB_PAGERIGHT";
696 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
697 case SB_THUMBTRACK: return "SB_THUMBTRACK";
698 case SB_ENDSCROLL: return "SB_ENDSCROLL";
699 case SB_INTERNAL: return "SB_INTERNAL";
700 default: return "unknown";
705 /******** Notification functions i************************************/
707 static LRESULT notify_hdr(LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
711 TRACE("(code=%d)\n", code);
713 pnmh->hwndFrom = infoPtr->hwndSelf;
714 pnmh->idFrom = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
716 result = SendMessageW(GetParent(infoPtr->hwndSelf), WM_NOTIFY,
717 (WPARAM)pnmh->idFrom, (LPARAM)pnmh);
719 TRACE(" <= %ld\n", result);
724 static inline LRESULT notify(LISTVIEW_INFO *infoPtr, INT code)
727 return notify_hdr(infoPtr, code, &nmh);
730 static inline void notify_itemactivate(LISTVIEW_INFO *infoPtr)
732 notify(infoPtr, LVN_ITEMACTIVATE);
735 static inline LRESULT notify_listview(LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
737 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
738 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
741 static LRESULT notify_click(LISTVIEW_INFO *infoPtr, INT code, LVHITTESTINFO *lvht)
746 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
747 ZeroMemory(&nmlv, sizeof(nmlv));
748 nmlv.iItem = lvht->iItem;
749 nmlv.iSubItem = lvht->iSubItem;
750 nmlv.ptAction = lvht->pt;
751 item.mask = LVIF_PARAM;
752 item.iItem = lvht->iItem;
754 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
755 return notify_listview(infoPtr, code, &nmlv);
758 static void notify_deleteitem(LISTVIEW_INFO *infoPtr, INT nItem)
763 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
765 item.mask = LVIF_PARAM;
768 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
769 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
772 static int get_ansi_notification(INT unicodeNotificationCode)
774 switch (unicodeNotificationCode)
776 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
777 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
778 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
779 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
780 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
781 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
783 ERR("unknown notification %x\n", unicodeNotificationCode);
788 With testing on Windows 2000 it looks like the notify format
789 has nothing to do with this message. It ALWAYS seems to be
792 infoPtr : listview struct
793 notificationCode : *Unicode* notification code
794 pdi : dispinfo structure (can be unicode or ansi)
795 isW : TRUE if dispinfo is Unicode
797 static BOOL notify_dispinfoT(LISTVIEW_INFO *infoPtr, INT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
799 BOOL bResult = FALSE;
800 BOOL convertToAnsi = FALSE;
801 INT cchTempBufMax = 0, savCchTextMax = 0;
802 LPWSTR pszTempBuf = NULL, savPszText = NULL;
804 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
809 if (notificationCode != LVN_GETDISPINFOW)
811 cchTempBufMax = WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText,
812 -1, NULL, 0, NULL, NULL);
816 cchTempBufMax = pdi->item.cchTextMax;
817 *pdi->item.pszText = 0; /* make sure we don't process garbage */
820 pszTempBuf = HeapAlloc(GetProcessHeap(), 0, sizeof(CHAR) *
822 if (!pszTempBuf) return FALSE;
824 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR)
825 pszTempBuf, cchTempBufMax, NULL, NULL);
827 savCchTextMax = pdi->item.cchTextMax;
828 savPszText = pdi->item.pszText;
829 pdi->item.pszText = pszTempBuf;
830 pdi->item.cchTextMax = cchTempBufMax;
833 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat !=
836 bResult = notify_hdr(infoPtr, get_ansi_notification(notificationCode),
841 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
842 savPszText, savCchTextMax);
843 pdi->item.pszText = savPszText; /* restores our buffer */
844 pdi->item.cchTextMax = savCchTextMax;
845 HeapFree(GetProcessHeap(), 0, pszTempBuf);
850 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *rcBounds)
852 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
853 lpnmlvcd->nmcd.hdc = hdc;
854 lpnmlvcd->nmcd.rc = *rcBounds;
855 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
856 lpnmlvcd->clrText = infoPtr->clrText;
859 static inline DWORD notify_customdraw (LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
861 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
862 return notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
865 /******** Item iterator functions **********************************/
867 static RANGES ranges_create(int count);
868 static void ranges_destroy(RANGES ranges);
869 static BOOL ranges_add(RANGES ranges, RANGE range);
870 static BOOL ranges_del(RANGES ranges, RANGE range);
871 static void ranges_dump(RANGES ranges);
873 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
875 RANGE range = { nItem, nItem + 1 };
877 return ranges_add(ranges, range);
880 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
882 RANGE range = { nItem, nItem + 1 };
884 return ranges_del(ranges, range);
888 * ITERATOR DOCUMENTATION
890 * The iterator functions allow for easy, and convenient iteration
891 * over items of iterest in the list. Typically, you create a
892 * iterator, use it, and destroy it, as such:
895 * iterator_xxxitems(&i, ...);
896 * while (iterator_{prev,next}(&i)
898 * //code which uses i.nItem
900 * iterator_destroy(&i);
902 * where xxx is either: framed, or visible.
903 * Note that it is important that the code destroys the iterator
904 * after it's done with it, as the creation of the iterator may
905 * allocate memory, which thus needs to be freed.
907 * You can iterate both forwards, and backwards through the list,
908 * by using iterator_next or iterator_prev respectively.
910 * Lower numbered items are draw on top of higher number items in
911 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
912 * items may overlap). So, to test items, you should use
914 * which lists the items top to bottom (in Z-order).
915 * For drawing items, you should use
917 * which lists the items bottom to top (in Z-order).
918 * If you keep iterating over the items after the end-of-items
919 * marker (-1) is returned, the iterator will start from the
920 * beginning. Typically, you don't need to test for -1,
921 * because iterator_{next,prev} will return TRUE if more items
922 * are to be iterated over, or FALSE otherwise.
924 * Note: the iterator is defined to be bidirectional. That is,
925 * any number of prev followed by any number of next, or
926 * five versa, should leave the iterator at the same item:
927 * prev * n, next * n = next * n, prev * n
929 * The iterator has a notion of a out-of-order, special item,
930 * which sits at the start of the list. This is used in
931 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
932 * which needs to be first, as it may overlap other items.
934 * The code is a bit messy because we have:
935 * - a special item to deal with
936 * - simple range, or composite range
938 * If find bugs, or want to add features, please make sure you
939 * always check/modify *both* iterator_prev, and iterator_next.
943 * This function iterates through the items in increasing order,
944 * but prefixed by the special item, then -1. That is:
945 * special, 1, 2, 3, ..., n, -1.
946 * Each item is listed only once.
948 static inline BOOL iterator_next(ITERATOR* i)
952 i->nItem = i->nSpecial;
953 if (i->nItem != -1) return TRUE;
955 if (i->nItem == i->nSpecial)
957 if (i->ranges) i->index = 0;
963 if (i->nItem == i->nSpecial) i->nItem++;
964 if (i->nItem < i->range.upper) return TRUE;
969 if (i->index < i->ranges->hdpa->nItemCount)
970 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
973 else if (i->nItem >= i->range.upper) goto end;
975 i->nItem = i->range.lower;
976 if (i->nItem >= 0) goto testitem;
983 * This function iterates through the items in decreasing order,
984 * followed by the special item, then -1. That is:
985 * n, n-1, ..., 3, 2, 1, special, -1.
986 * Each item is listed only once.
988 static inline BOOL iterator_prev(ITERATOR* i)
995 if (i->ranges) i->index = i->ranges->hdpa->nItemCount;
998 if (i->nItem == i->nSpecial)
1006 if (i->nItem == i->nSpecial) i->nItem--;
1007 if (i->nItem >= i->range.lower) return TRUE;
1013 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1016 else if (!start && i->nItem < i->range.lower) goto end;
1018 i->nItem = i->range.upper;
1019 if (i->nItem > 0) goto testitem;
1021 return (i->nItem = i->nSpecial) != -1;
1024 static RANGE iterator_range(ITERATOR* i)
1028 if (!i->ranges) return i->range;
1030 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1031 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->ranges->hdpa->nItemCount - 1)).upper;
1036 * Releases resources associated with this ierator.
1038 static inline void iterator_destroy(ITERATOR* i)
1040 ranges_destroy(i->ranges);
1044 * Create an empty iterator.
1046 static inline BOOL iterator_empty(ITERATOR* i)
1048 ZeroMemory(i, sizeof(*i));
1049 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1054 * Create an iterator over a range.
1056 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1064 * Create an iterator over a bunch of ranges.
1065 * Please note that the iterator will take ownership of the ranges,
1066 * and will free them upon destruction.
1068 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1076 * Creates an iterator over the items which intersect lprc.
1078 static BOOL iterator_frameditems(ITERATOR* i, LISTVIEW_INFO* infoPtr, const RECT *lprc)
1080 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1081 RECT frame = *lprc, rcItem, rcTemp;
1084 /* in case we fail, we want to return an empty iterator */
1085 if (!iterator_empty(i)) return FALSE;
1087 LISTVIEW_GetOrigin(infoPtr, &Origin);
1089 TRACE("(lprc=%s)\n", debugrect(lprc));
1090 OffsetRect(&frame, -Origin.x, -Origin.y);
1092 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1096 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1098 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1099 if (IntersectRect(&rcTemp, &rcItem, lprc))
1100 i->nSpecial = infoPtr->nFocusedItem;
1102 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1103 /* to do better here, we need to have PosX, and PosY sorted */
1104 TRACE("building icon ranges:\n");
1105 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1107 rcItem.left = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1108 rcItem.top = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1109 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1110 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1111 if (IntersectRect(&rcTemp, &rcItem, &frame))
1112 ranges_additem(i->ranges, nItem);
1116 else if (uView == LVS_REPORT)
1120 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1121 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1123 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1124 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1125 if (range.upper <= range.lower) return TRUE;
1126 if (!iterator_rangeitems(i, range)) return FALSE;
1127 TRACE(" report=%s\n", debugrange(&i->range));
1131 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1132 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1133 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1134 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1135 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1136 INT lower = nFirstCol * nPerCol + nFirstRow;
1140 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1141 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1143 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1145 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1146 TRACE("building list ranges:\n");
1147 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1149 item_range.lower = nCol * nPerCol + nFirstRow;
1150 if(item_range.lower >= infoPtr->nItemCount) break;
1151 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1152 TRACE(" list=%s\n", debugrange(&item_range));
1153 ranges_add(i->ranges, item_range);
1161 * Creates an iterator over the items which intersect the visible region of hdc.
1163 static BOOL iterator_visibleitems(ITERATOR *i, LISTVIEW_INFO *infoPtr, HDC hdc)
1165 POINT Origin, Position;
1166 RECT rcItem, rcClip;
1169 rgntype = GetClipBox(hdc, &rcClip);
1170 if (rgntype == NULLREGION) return iterator_empty(i);
1171 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1172 if (rgntype == SIMPLEREGION) return TRUE;
1174 /* first deal with the special item */
1175 if (i->nSpecial != -1)
1177 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1178 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1181 /* if we can't deal with the region, we'll just go with the simple range */
1182 LISTVIEW_GetOrigin(infoPtr, &Origin);
1183 TRACE("building visible range:\n");
1184 if (!i->ranges && i->range.lower < i->range.upper)
1186 if (!(i->ranges = ranges_create(50))) return TRUE;
1187 if (!ranges_add(i->ranges, i->range))
1189 ranges_destroy(i->ranges);
1195 /* now delete the invisible items from the list */
1196 while(iterator_next(i))
1198 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1199 rcItem.left = Position.x + Origin.x;
1200 rcItem.top = Position.y + Origin.y;
1201 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1202 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1203 if (!RectVisible(hdc, &rcItem))
1204 ranges_delitem(i->ranges, i->nItem);
1206 /* the iterator should restart on the next iterator_next */
1212 /******** Misc helper functions ************************************/
1214 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1215 WPARAM wParam, LPARAM lParam, BOOL isW)
1217 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1218 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1221 static inline BOOL is_autoarrange(LISTVIEW_INFO *infoPtr)
1223 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1225 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1226 (uView == LVS_ICON || uView == LVS_SMALLICON);
1229 /******** Internal API functions ************************************/
1231 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(LISTVIEW_INFO *infoPtr, INT nSubItem)
1233 static COLUMN_INFO mainItem;
1235 if (nSubItem == 0 && infoPtr->hdpaColumns->nItemCount == 0) return &mainItem;
1236 assert (nSubItem >= 0 && nSubItem < infoPtr->hdpaColumns->nItemCount);
1237 return (COLUMN_INFO *)DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1240 static inline void LISTVIEW_GetHeaderRect(LISTVIEW_INFO *infoPtr, INT nSubItem, RECT *lprc)
1242 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1245 static inline BOOL LISTVIEW_GetItemW(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1247 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1250 /* Listview invlaidation functions: use _only_ these function to invalidate */
1252 static inline BOOL is_redrawing(LISTVIEW_INFO *infoPtr)
1254 return infoPtr->bRedraw && !infoPtr->bFirstPaint;
1257 static inline void LISTVIEW_InvalidateRect(LISTVIEW_INFO *infoPtr, const RECT* rect)
1259 if(!is_redrawing(infoPtr)) return;
1260 TRACE(" invalidating rect=%s\n", debugrect(rect));
1261 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1264 static inline void LISTVIEW_InvalidateItem(LISTVIEW_INFO *infoPtr, INT nItem)
1268 if(!is_redrawing(infoPtr)) return;
1269 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1270 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1273 static inline void LISTVIEW_InvalidateSubItem(LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1275 POINT Origin, Position;
1278 if(!is_redrawing(infoPtr)) return;
1279 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1280 LISTVIEW_GetOrigin(infoPtr, &Origin);
1281 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1282 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1284 rcBox.bottom = infoPtr->nItemHeight;
1285 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1286 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1289 static inline void LISTVIEW_InvalidateList(LISTVIEW_INFO *infoPtr)
1291 LISTVIEW_InvalidateRect(infoPtr, NULL);
1294 static inline void LISTVIEW_InvalidateColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
1298 if(!is_redrawing(infoPtr)) return;
1299 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1300 rcCol.top = infoPtr->rcList.top;
1301 rcCol.bottom = infoPtr->rcList.bottom;
1302 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1307 * Retrieves the number of items that can fit vertically in the client area.
1310 * [I] infoPtr : valid pointer to the listview structure
1313 * Number of items per row.
1315 static inline INT LISTVIEW_GetCountPerRow(LISTVIEW_INFO *infoPtr)
1317 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1319 return max(nListWidth/infoPtr->nItemWidth, 1);
1324 * Retrieves the number of items that can fit horizontally in the client
1328 * [I] infoPtr : valid pointer to the listview structure
1331 * Number of items per column.
1333 static inline INT LISTVIEW_GetCountPerColumn(LISTVIEW_INFO *infoPtr)
1335 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1337 return max(nListHeight / infoPtr->nItemHeight, 1);
1341 /*************************************************************************
1342 * LISTVIEW_ProcessLetterKeys
1344 * Processes keyboard messages generated by pressing the letter keys
1346 * What this does is perform a case insensitive search from the
1347 * current position with the following quirks:
1348 * - If two chars or more are pressed in quick succession we search
1349 * for the corresponding string (e.g. 'abc').
1350 * - If there is a delay we wipe away the current search string and
1351 * restart with just that char.
1352 * - If the user keeps pressing the same character, whether slowly or
1353 * fast, so that the search string is entirely composed of this
1354 * character ('aaaaa' for instance), then we search for first item
1355 * that starting with that character.
1356 * - If the user types the above character in quick succession, then
1357 * we must also search for the corresponding string ('aaaaa'), and
1358 * go to that string if there is a match.
1361 * [I] hwnd : handle to the window
1362 * [I] charCode : the character code, the actual character
1363 * [I] keyData : key data
1371 * - The current implementation has a list of characters it will
1372 * accept and it ignores averything else. In particular it will
1373 * ignore accentuated characters which seems to match what
1374 * Windows does. But I'm not sure it makes sense to follow
1376 * - We don't sound a beep when the search fails.
1380 * TREEVIEW_ProcessLetterKeys
1382 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1387 WCHAR buffer[MAX_PATH];
1388 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1390 /* simple parameter checking */
1391 if (!charCode || !keyData) return 0;
1393 /* only allow the valid WM_CHARs through */
1394 if (!isalnum(charCode) &&
1395 charCode != '.' && charCode != '`' && charCode != '!' &&
1396 charCode != '@' && charCode != '#' && charCode != '$' &&
1397 charCode != '%' && charCode != '^' && charCode != '&' &&
1398 charCode != '*' && charCode != '(' && charCode != ')' &&
1399 charCode != '-' && charCode != '_' && charCode != '+' &&
1400 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1401 charCode != '}' && charCode != '[' && charCode != '{' &&
1402 charCode != '/' && charCode != '?' && charCode != '>' &&
1403 charCode != '<' && charCode != ',' && charCode != '~')
1406 /* if there's one item or less, there is no where to go */
1407 if (infoPtr->nItemCount <= 1) return 0;
1409 /* update the search parameters */
1410 infoPtr->lastKeyPressTimestamp = GetTickCount();
1411 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1412 if (infoPtr->nSearchParamLength < MAX_PATH)
1413 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1414 if (infoPtr->charCode != charCode)
1415 infoPtr->charCode = charCode = 0;
1417 infoPtr->charCode=charCode;
1418 infoPtr->szSearchParam[0]=charCode;
1419 infoPtr->nSearchParamLength=1;
1420 /* Redundant with the 1 char string */
1424 /* and search from the current position */
1426 if (infoPtr->nFocusedItem >= 0) {
1427 endidx=infoPtr->nFocusedItem;
1429 /* if looking for single character match,
1430 * then we must always move forward
1432 if (infoPtr->nSearchParamLength == 1)
1435 endidx=infoPtr->nItemCount;
1439 if (idx == infoPtr->nItemCount) {
1440 if (endidx == infoPtr->nItemCount || endidx == 0)
1446 item.mask = LVIF_TEXT;
1449 item.pszText = buffer;
1450 item.cchTextMax = MAX_PATH;
1451 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1453 /* check for a match */
1454 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1457 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1458 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1459 /* This would work but we must keep looking for a longer match */
1463 } while (idx != endidx);
1466 LISTVIEW_KeySelection(infoPtr, nItem);
1471 /*************************************************************************
1472 * LISTVIEW_UpdateHeaderSize [Internal]
1474 * Function to resize the header control
1477 * [I] hwnd : handle to a window
1478 * [I] nNewScrollPos : scroll pos to set
1483 static void LISTVIEW_UpdateHeaderSize(LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1488 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1490 GetWindowRect(infoPtr->hwndHeader, &winRect);
1491 point[0].x = winRect.left;
1492 point[0].y = winRect.top;
1493 point[1].x = winRect.right;
1494 point[1].y = winRect.bottom;
1496 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1497 point[0].x = -nNewScrollPos;
1498 point[1].x += nNewScrollPos;
1500 SetWindowPos(infoPtr->hwndHeader,0,
1501 point[0].x,point[0].y,point[1].x,point[1].y,
1502 SWP_NOZORDER | SWP_NOACTIVATE);
1507 * Update the scrollbars. This functions should be called whenever
1508 * the content, size or view changes.
1511 * [I] infoPtr : valid pointer to the listview structure
1516 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *infoPtr)
1518 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1519 SCROLLINFO horzInfo, vertInfo;
1521 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1523 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1524 horzInfo.cbSize = sizeof(SCROLLINFO);
1525 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1527 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1528 if (uView == LVS_LIST)
1530 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1531 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1533 /* scroll by at least one column per page */
1534 if(horzInfo.nPage < infoPtr->nItemWidth)
1535 horzInfo.nPage = infoPtr->nItemWidth;
1537 horzInfo.nPage /= infoPtr->nItemWidth;
1539 else if (uView == LVS_REPORT)
1541 horzInfo.nMax = infoPtr->nItemWidth;
1543 else /* LVS_ICON, or LVS_SMALLICON */
1547 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1550 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1551 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1552 SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1553 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1555 /* Setting the horizontal scroll can change the listview size
1556 * (and potentially everything else) so we need to recompute
1557 * everything again for the vertical scroll
1560 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1561 vertInfo.cbSize = sizeof(SCROLLINFO);
1562 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1564 if (uView == LVS_REPORT)
1566 vertInfo.nMax = infoPtr->nItemCount;
1568 /* scroll by at least one page */
1569 if(vertInfo.nPage < infoPtr->nItemHeight)
1570 vertInfo.nPage = infoPtr->nItemHeight;
1572 vertInfo.nPage /= infoPtr->nItemHeight;
1574 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1578 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1581 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1582 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1583 SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1584 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1586 /* Update the Header Control */
1587 if (uView == LVS_REPORT)
1589 horzInfo.fMask = SIF_POS;
1590 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1591 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1598 * Shows/hides the focus rectangle.
1601 * [I] infoPtr : valid pointer to the listview structure
1602 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1607 static void LISTVIEW_ShowFocusRect(LISTVIEW_INFO *infoPtr, BOOL fShow)
1609 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1612 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1614 if (infoPtr->nFocusedItem < 0) return;
1616 /* we need some gymnastics in ICON mode to handle large items */
1617 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1621 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1622 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1624 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1629 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1631 /* for some reason, owner draw should work only in report mode */
1632 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1637 item.iItem = infoPtr->nFocusedItem;
1639 item.mask = LVIF_PARAM;
1640 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1642 ZeroMemory(&dis, sizeof(dis));
1643 dis.CtlType = ODT_LISTVIEW;
1644 dis.CtlID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
1645 dis.itemID = item.iItem;
1646 dis.itemAction = ODA_FOCUS;
1647 if (fShow) dis.itemState |= ODS_FOCUS;
1648 dis.hwndItem = infoPtr->hwndSelf;
1650 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1651 dis.itemData = item.lParam;
1653 SendMessageW(GetParent(infoPtr->hwndSelf), WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1657 DrawFocusRect(hdc, &infoPtr->rcFocus);
1660 ReleaseDC(infoPtr->hwndSelf, hdc);
1664 * Invalidates all visible selected items.
1666 static void LISTVIEW_InvalidateSelectedItems(LISTVIEW_INFO *infoPtr)
1670 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1671 while(iterator_next(&i))
1673 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1674 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1676 iterator_destroy(&i);
1681 * DESCRIPTION: [INTERNAL]
1682 * Computes an item's (left,top) corner, relative to rcView.
1683 * That is, the position has NOT been made relative to the Origin.
1684 * This is deliberate, to avoid computing the Origin over, and
1685 * over again, when this function is call in a loop. Instead,
1686 * one ca factor the computation of the Origin before the loop,
1687 * and offset the value retured by this function, on every iteration.
1690 * [I] infoPtr : valid pointer to the listview structure
1691 * [I] nItem : item number
1692 * [O] lpptOrig : item top, left corner
1697 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1699 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1701 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1703 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1705 lpptPosition->x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1706 lpptPosition->y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1708 else if (uView == LVS_LIST)
1710 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1711 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1712 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1714 else /* LVS_REPORT */
1716 lpptPosition->x = 0;
1717 lpptPosition->y = nItem * infoPtr->nItemHeight;
1722 * DESCRIPTION: [INTERNAL]
1723 * Compute the rectangles of an item. This is to localize all
1724 * the computations in one place. If you are not interested in some
1725 * of these values, simply pass in a NULL -- the fucntion is smart
1726 * enough to compute only what's necessary. The function computes
1727 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1728 * one, the BOX rectangle. This rectangle is very cheap to compute,
1729 * and is guaranteed to contain all the other rectangles. Computing
1730 * the ICON rect is also cheap, but all the others are potentaily
1731 * expensive. This gives an easy and effective optimization when
1732 * searching (like point inclusion, or rectangle intersection):
1733 * first test against the BOX, and if TRUE, test agains the desired
1735 * If the function does not have all the necessary information
1736 * to computed the requested rectangles, will crash with a
1737 * failed assertion. This is done so we catch all programming
1738 * errors, given that the function is called only from our code.
1740 * We have the following 'special' meanings for a few fields:
1741 * * If LVIS_FOCUSED is set, we assume the item has the focus
1742 * This is important in ICON mode, where it might get a larger
1743 * then usual rectange
1745 * Please note that subitem support works only in REPORT mode.
1748 * [I] infoPtr : valid pointer to the listview structure
1749 * [I] lpLVItem : item to compute the measures for
1750 * [O] lprcBox : ptr to Box rectangle
1751 * The internal LVIR_BOX rectangle
1752 * [0] lprcState : ptr to State icon rectangle
1753 * The internal LVIR_STATE rectangle
1754 * [O] lprcIcon : ptr to Icon rectangle
1755 * Same as LVM_GETITEMRECT with LVIR_ICON
1756 * [O] lprcLabel : ptr to Label rectangle
1757 * Same as LVM_GETITEMRECT with LVIR_LABEL
1762 static void LISTVIEW_GetItemMetrics(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1763 LPRECT lprcBox, LPRECT lprcState,
1764 LPRECT lprcIcon, LPRECT lprcLabel)
1766 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1767 BOOL doState = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1768 RECT Box, State, Icon, Label;
1769 COLUMN_INFO *lpColumnInfo = NULL;
1771 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1773 /* Be smart and try to figure out the minimum we have to do */
1774 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1775 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1777 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1778 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1780 if (lprcLabel) doLabel = TRUE;
1781 if (doLabel || lprcIcon) doIcon = TRUE;
1782 if (doIcon || lprcState) doState = TRUE;
1784 /************************************************************/
1785 /* compute the box rectangle (it should be cheap to do) */
1786 /************************************************************/
1787 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1788 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1790 if (lpLVItem->iSubItem)
1792 Box = lpColumnInfo->rcHeader;
1797 Box.right = infoPtr->nItemWidth;
1800 Box.bottom = infoPtr->nItemHeight;
1802 /************************************************************/
1803 /* compute STATEICON bounding box */
1804 /************************************************************/
1807 if (uView == LVS_ICON)
1809 State.left = Box.left - infoPtr->iconStateSize.cx - 2;
1810 if (infoPtr->himlNormal)
1811 State.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1812 State.top = Box.top + infoPtr->iconSize.cy - infoPtr->iconStateSize.cy + 4;
1816 /* we need the ident in report mode, if we don't have it, we fail */
1817 State.left = Box.left;
1818 if (uView == LVS_REPORT)
1820 if (lpLVItem->iSubItem == 0)
1822 State.left += REPORT_MARGINX;
1823 assert(lpLVItem->mask & LVIF_INDENT);
1824 State.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
1827 State.top = Box.top;
1829 State.right = State.left;
1830 State.bottom = State.top;
1831 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1833 State.right += infoPtr->iconStateSize.cx;
1834 State.bottom += infoPtr->iconStateSize.cy;
1836 if (lprcState) *lprcState = State;
1837 TRACE(" - state=%s\n", debugrect(&State));
1840 /************************************************************/
1841 /* compute ICON bounding box (ala LVM_GETITEMRECT) */
1842 /************************************************************/
1845 if (uView == LVS_ICON)
1847 Icon.left = Box.left;
1848 if (infoPtr->himlNormal)
1849 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1850 Icon.top = Box.top + ICON_TOP_PADDING;
1851 Icon.right = Icon.left;
1852 Icon.bottom = Icon.top;
1853 if (infoPtr->himlNormal)
1855 Icon.right += infoPtr->iconSize.cx;
1856 Icon.bottom += infoPtr->iconSize.cy;
1859 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1861 Icon.left = State.right;
1863 Icon.right = Icon.left;
1864 if (infoPtr->himlSmall && (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE)))
1865 Icon.right += infoPtr->iconSize.cx;
1866 Icon.bottom = Icon.top + infoPtr->nItemHeight;
1868 if(lprcIcon) *lprcIcon = Icon;
1869 TRACE(" - icon=%s\n", debugrect(&Icon));
1872 /************************************************************/
1873 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
1874 /************************************************************/
1877 SIZE labelSize = { 0, 0 };
1879 /* calculate how far to the right can the label strech */
1880 Label.right = Box.right;
1881 if (uView == LVS_REPORT)
1883 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
1886 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
1888 labelSize.cx = infoPtr->nItemWidth;
1889 labelSize.cy = infoPtr->nItemHeight;
1893 /* we need the text in non owner draw mode */
1894 assert(lpLVItem->mask & LVIF_TEXT);
1895 if (is_textT(lpLVItem->pszText, TRUE))
1897 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1898 HDC hdc = GetDC(infoPtr->hwndSelf);
1899 HFONT hOldFont = SelectObject(hdc, hFont);
1903 /* compute rough rectangle where the label will go */
1904 SetRectEmpty(&rcText);
1905 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
1906 rcText.bottom = infoPtr->nItemHeight;
1907 if (uView == LVS_ICON)
1908 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
1910 /* now figure out the flags */
1911 if (uView == LVS_ICON)
1912 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
1914 uFormat = LV_SL_DT_FLAGS;
1916 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
1918 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
1919 labelSize.cy = rcText.bottom - rcText.top;
1921 SelectObject(hdc, hOldFont);
1922 ReleaseDC(infoPtr->hwndSelf, hdc);
1926 if (uView == LVS_ICON)
1928 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
1929 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
1930 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
1931 Label.right = Label.left + labelSize.cx;
1932 Label.bottom = Label.top + infoPtr->nItemHeight;
1933 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
1935 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
1936 labelSize.cy /= infoPtr->ntmHeight;
1937 labelSize.cy = max(labelSize.cy, 1);
1938 labelSize.cy *= infoPtr->ntmHeight;
1940 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
1942 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1944 Label.left = Icon.right;
1945 Label.top = Box.top;
1946 Label.right = min(Label.left + labelSize.cx, Label.right);
1947 Label.bottom = Label.top + infoPtr->nItemHeight;
1950 if (lprcLabel) *lprcLabel = Label;
1951 TRACE(" - label=%s\n", debugrect(&Label));
1954 /* Fix the Box if necessary */
1957 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
1958 else *lprcBox = Box;
1960 TRACE(" - box=%s\n", debugrect(&Box));
1964 * DESCRIPTION: [INTERNAL]
1967 * [I] infoPtr : valid pointer to the listview structure
1968 * [I] nItem : item number
1969 * [O] lprcBox : ptr to Box rectangle
1974 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
1976 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1977 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
1978 POINT Position, Origin;
1981 LISTVIEW_GetOrigin(infoPtr, &Origin);
1982 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1984 /* Be smart and try to figure out the minimum we have to do */
1986 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
1987 lvItem.mask |= LVIF_TEXT;
1988 lvItem.iItem = nItem;
1989 lvItem.iSubItem = 0;
1990 lvItem.pszText = szDispText;
1991 lvItem.cchTextMax = DISP_TEXT_SIZE;
1992 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
1993 if (uView == LVS_ICON)
1995 lvItem.mask |= LVIF_STATE;
1996 lvItem.stateMask = LVIS_FOCUSED;
1997 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
1999 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0);
2001 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2007 * Returns the current icon position, and advances it along the top.
2008 * The returned position is not offset by Origin.
2011 * [I] infoPtr : valid pointer to the listview structure
2012 * [O] lpPos : will get the current icon position
2017 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2019 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2021 *lpPos = infoPtr->currIconPos;
2023 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2024 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2026 infoPtr->currIconPos.x = 0;
2027 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2033 * Returns the current icon position, and advances it down the left edge.
2034 * The returned position is not offset by Origin.
2037 * [I] infoPtr : valid pointer to the listview structure
2038 * [O] lpPos : will get the current icon position
2043 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2045 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2047 *lpPos = infoPtr->currIconPos;
2049 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2050 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2052 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2053 infoPtr->currIconPos.y = 0;
2059 * Moves an icon to the specified position.
2060 * It takes care of invalidating the item, etc.
2063 * [I] infoPtr : valid pointer to the listview structure
2064 * [I] nItem : the item to move
2065 * [I] lpPos : the new icon position
2066 * [I] isNew : flags the item as being new
2072 static BOOL LISTVIEW_MoveIconTo(LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2078 old.x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2079 old.y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2081 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2082 LISTVIEW_InvalidateItem(infoPtr, nItem);
2085 /* Allocating a POINTER for every item is too resource intensive,
2086 * so we'll keep the (x,y) in different arrays */
2087 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)lppt->x)) return FALSE;
2088 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)lppt->y)) return FALSE;
2090 LISTVIEW_InvalidateItem(infoPtr, nItem);
2097 * Arranges listview items in icon display mode.
2100 * [I] infoPtr : valid pointer to the listview structure
2101 * [I] nAlignCode : alignment code
2107 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2109 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2110 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2114 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2116 TRACE("nAlignCode=%d\n", nAlignCode);
2118 if (nAlignCode == LVA_DEFAULT)
2120 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2121 else nAlignCode = LVA_ALIGNTOP;
2126 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2127 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2128 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2129 default: return FALSE;
2132 infoPtr->bAutoarrange = TRUE;
2133 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2134 for (i = 0; i < infoPtr->nItemCount; i++)
2136 next_pos(infoPtr, &pos);
2137 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2145 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2148 * [I] infoPtr : valid pointer to the listview structure
2149 * [O] lprcView : bounding rectangle
2155 static void LISTVIEW_GetAreaRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2159 SetRectEmpty(lprcView);
2161 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2165 for (i = 0; i < infoPtr->nItemCount; i++)
2167 x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, i);
2168 y = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, i);
2169 lprcView->right = max(lprcView->right, x);
2170 lprcView->bottom = max(lprcView->bottom, y);
2172 if (infoPtr->nItemCount > 0)
2174 lprcView->right += infoPtr->nItemWidth;
2175 lprcView->bottom += infoPtr->nItemHeight;
2180 y = LISTVIEW_GetCountPerColumn(infoPtr);
2181 x = infoPtr->nItemCount / y;
2182 if (infoPtr->nItemCount % y) x++;
2183 lprcView->right = x * infoPtr->nItemWidth;
2184 lprcView->bottom = y * infoPtr->nItemHeight;
2188 lprcView->right = infoPtr->nItemWidth;
2189 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2196 * Retrieves the bounding rectangle of all the items.
2199 * [I] infoPtr : valid pointer to the listview structure
2200 * [O] lprcView : bounding rectangle
2206 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2210 TRACE("(lprcView=%p)\n", lprcView);
2212 if (!lprcView) return FALSE;
2214 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2215 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2216 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2218 TRACE("lprcView=%s\n", debugrect(lprcView));
2225 * Retrieves the subitem pointer associated with the subitem index.
2228 * [I] hdpaSubItems : DPA handle for a specific item
2229 * [I] nSubItem : index of subitem
2232 * SUCCESS : subitem pointer
2235 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2237 SUBITEM_INFO *lpSubItem;
2240 /* we should binary search here if need be */
2241 for (i = 1; i < hdpaSubItems->nItemCount; i++)
2243 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
2244 if (lpSubItem->iSubItem == nSubItem)
2254 * Caclulates the desired item width.
2257 * [I] infoPtr : valid pointer to the listview structure
2260 * The desired item width.
2262 static INT LISTVIEW_CalculateItemWidth(LISTVIEW_INFO *infoPtr)
2264 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2267 TRACE("uView=%d\n", uView);
2269 if (uView == LVS_ICON)
2270 nItemWidth = infoPtr->iconSpacing.cx;
2271 else if (uView == LVS_REPORT)
2275 if (infoPtr->hdpaColumns->nItemCount > 0)
2277 LISTVIEW_GetHeaderRect(infoPtr, infoPtr->hdpaColumns->nItemCount - 1, &rcHeader);
2278 nItemWidth = rcHeader.right;
2281 else /* LVS_SMALLICON, or LVS_LIST */
2285 for (i = 0; i < infoPtr->nItemCount; i++)
2286 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2288 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2289 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2291 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2294 return max(nItemWidth, 1);
2299 * Caclulates the desired item height.
2302 * [I] infoPtr : valid pointer to the listview structure
2305 * The desired item height.
2307 static INT LISTVIEW_CalculateItemHeight(LISTVIEW_INFO *infoPtr)
2309 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2312 TRACE("uView=%d\n", uView);
2314 if (uView == LVS_ICON)
2315 nItemHeight = infoPtr->iconSpacing.cy;
2318 nItemHeight = infoPtr->ntmHeight;
2319 if (infoPtr->himlState)
2320 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2321 if (infoPtr->himlSmall)
2322 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2323 if (infoPtr->himlState || infoPtr->himlSmall)
2324 nItemHeight += HEIGHT_PADDING;
2327 return max(nItemHeight, 1);
2332 * Updates the width, and height of an item.
2335 * [I] infoPtr : valid pointer to the listview structure
2340 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2342 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2343 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2349 * Retrieves and saves important text metrics info for the current
2353 * [I] infoPtr : valid pointer to the listview structure
2356 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2358 HDC hdc = GetDC(infoPtr->hwndSelf);
2359 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2360 HFONT hOldFont = SelectObject(hdc, hFont);
2363 if (GetTextMetricsW(hdc, &tm))
2365 infoPtr->ntmHeight = tm.tmHeight;
2366 infoPtr->ntmAveCharWidth = tm.tmAveCharWidth;
2368 SelectObject(hdc, hOldFont);
2369 ReleaseDC(infoPtr->hwndSelf, hdc);
2371 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2376 * A compare function for ranges
2379 * [I] range1 : pointer to range 1;
2380 * [I] range2 : pointer to range 2;
2384 * > 0 : if range 1 > range 2
2385 * < 0 : if range 2 > range 1
2386 * = 0 : if range intersects range 2
2388 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2392 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2394 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2399 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2405 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2407 #define ranges_check(ranges, desc) do { } while(0)
2410 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2415 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2417 assert (ranges->hdpa->nItemCount >= 0);
2418 ranges_dump(ranges);
2419 prev = (RANGE *)DPA_GetPtr(ranges->hdpa, 0);
2420 if (ranges->hdpa->nItemCount > 0)
2421 assert (prev->lower >= 0 && prev->lower < prev->upper);
2422 for (i = 1; i < ranges->hdpa->nItemCount; i++)
2424 curr = (RANGE *)DPA_GetPtr(ranges->hdpa, i);
2425 assert (prev->upper <= curr->lower);
2426 assert (curr->lower < curr->upper);
2429 TRACE("--- Done checking---\n");
2432 static RANGES ranges_create(int count)
2434 RANGES ranges = (RANGES)COMCTL32_Alloc(sizeof(struct tagRANGES));
2435 if (!ranges) return NULL;
2436 ranges->hdpa = DPA_Create(count);
2437 if (ranges->hdpa) return ranges;
2438 COMCTL32_Free(ranges);
2442 static void ranges_clear(RANGES ranges)
2446 for(i = 0; i < ranges->hdpa->nItemCount; i++)
2447 COMCTL32_Free(DPA_GetPtr(ranges->hdpa, i));
2448 DPA_DeleteAllPtrs(ranges->hdpa);
2452 static void ranges_destroy(RANGES ranges)
2454 if (!ranges) return;
2455 ranges_clear(ranges);
2456 DPA_Destroy(ranges->hdpa);
2457 COMCTL32_Free(ranges);
2460 static RANGES ranges_clone(RANGES ranges)
2465 if (!(clone = ranges_create(ranges->hdpa->nItemCount))) goto fail;
2467 for (i = 0; i < ranges->hdpa->nItemCount; i++)
2469 RANGE *newrng = (RANGE *)COMCTL32_Alloc(sizeof(RANGE));
2470 if (!newrng) goto fail;
2471 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2472 DPA_SetPtr(clone->hdpa, i, newrng);
2477 TRACE ("clone failed\n");
2478 ranges_destroy(clone);
2482 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2486 for (i = 0; i < sub->hdpa->nItemCount; i++)
2487 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2492 static void ranges_dump(RANGES ranges)
2496 for (i = 0; i < ranges->hdpa->nItemCount; i++)
2497 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2500 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2502 RANGE srchrng = { nItem, nItem + 1 };
2504 TRACE("(nItem=%d)\n", nItem);
2505 ranges_check(ranges, "before contain");
2506 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2509 static INT ranges_itemcount(RANGES ranges)
2513 for (i = 0; i < ranges->hdpa->nItemCount; i++)
2515 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2516 count += sel->upper - sel->lower;
2522 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2524 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2527 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2528 if (index == -1) return TRUE;
2530 for (; index < ranges->hdpa->nItemCount; index++)
2532 chkrng = DPA_GetPtr(ranges->hdpa, index);
2533 if (chkrng->lower >= nItem)
2534 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2535 if (chkrng->upper > nItem)
2536 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2541 static BOOL ranges_add(RANGES ranges, RANGE range)
2546 TRACE("(%s)\n", debugrange(&range));
2547 ranges_check(ranges, "before add");
2549 /* try find overlapping regions first */
2550 srchrgn.lower = range.lower - 1;
2551 srchrgn.upper = range.upper + 1;
2552 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2558 TRACE("Adding new range\n");
2560 /* create the brand new range to insert */
2561 newrgn = (RANGE *)COMCTL32_Alloc(sizeof(RANGE));
2562 if(!newrgn) goto fail;
2565 /* figure out where to insert it */
2566 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2567 TRACE("index=%d\n", index);
2568 if (index == -1) index = 0;
2570 /* and get it over with */
2571 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2573 COMCTL32_Free(newrgn);
2579 RANGE *chkrgn, *mrgrgn;
2580 INT fromindex, mergeindex;
2582 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2583 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2585 chkrgn->lower = min(range.lower, chkrgn->lower);
2586 chkrgn->upper = max(range.upper, chkrgn->upper);
2588 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2590 /* merge now common anges */
2592 srchrgn.lower = chkrgn->lower - 1;
2593 srchrgn.upper = chkrgn->upper + 1;
2597 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2598 if (mergeindex == -1) break;
2599 if (mergeindex == index)
2601 fromindex = index + 1;
2605 TRACE("Merge with index %i\n", mergeindex);
2607 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2608 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2609 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2610 COMCTL32_Free(mrgrgn);
2611 DPA_DeletePtr(ranges->hdpa, mergeindex);
2612 if (mergeindex < index) index --;
2616 ranges_check(ranges, "after add");
2620 ranges_check(ranges, "failed add");
2624 static BOOL ranges_del(RANGES ranges, RANGE range)
2629 TRACE("(%s)\n", debugrange(&range));
2630 ranges_check(ranges, "before del");
2632 /* we don't use DPAS_SORTED here, since we need *
2633 * to find the first overlapping range */
2634 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2637 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2639 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2641 /* case 1: Same range */
2642 if ( (chkrgn->upper == range.upper) &&
2643 (chkrgn->lower == range.lower) )
2645 DPA_DeletePtr(ranges->hdpa, index);
2648 /* case 2: engulf */
2649 else if ( (chkrgn->upper <= range.upper) &&
2650 (chkrgn->lower >= range.lower) )
2652 DPA_DeletePtr(ranges->hdpa, index);
2654 /* case 3: overlap upper */
2655 else if ( (chkrgn->upper <= range.upper) &&
2656 (chkrgn->lower < range.lower) )
2658 chkrgn->upper = range.lower;
2660 /* case 4: overlap lower */
2661 else if ( (chkrgn->upper > range.upper) &&
2662 (chkrgn->lower >= range.lower) )
2664 chkrgn->lower = range.upper;
2667 /* case 5: fully internal */
2670 RANGE tmprgn = *chkrgn, *newrgn;
2672 if (!(newrgn = (RANGE *)COMCTL32_Alloc(sizeof(RANGE)))) goto fail;
2673 newrgn->lower = chkrgn->lower;
2674 newrgn->upper = range.lower;
2675 chkrgn->lower = range.upper;
2676 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2678 COMCTL32_Free(newrgn);
2685 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2688 ranges_check(ranges, "after del");
2692 ranges_check(ranges, "failed del");
2698 * Removes all selection ranges
2701 * [I] infoPtr : valid pointer to the listview structure
2702 * [I] toSkip : item range to skip removing the selection
2708 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2717 lvItem.stateMask = LVIS_SELECTED;
2719 /* need to clone the DPA because callbacks can change it */
2720 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2721 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2722 while(iterator_next(&i))
2723 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2724 /* note that the iterator destructor will free the cloned range */
2725 iterator_destroy(&i);
2730 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2734 if (!(toSkip = ranges_create(1))) return FALSE;
2735 if (nItem != -1) ranges_additem(toSkip, nItem);
2736 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2737 ranges_destroy(toSkip);
2741 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2743 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2748 * Retrieves the number of items that are marked as selected.
2751 * [I] infoPtr : valid pointer to the listview structure
2754 * Number of items selected.
2756 static INT LISTVIEW_GetSelectedCount(LISTVIEW_INFO *infoPtr)
2758 INT nSelectedCount = 0;
2760 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2763 for (i = 0; i < infoPtr->nItemCount; i++)
2765 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2770 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2772 TRACE("nSelectedCount=%d\n", nSelectedCount);
2773 return nSelectedCount;
2778 * Manages the item focus.
2781 * [I] infoPtr : valid pointer to the listview structure
2782 * [I] nItem : item index
2785 * TRUE : focused item changed
2786 * FALSE : focused item has NOT changed
2788 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2790 INT oldFocus = infoPtr->nFocusedItem;
2793 if (nItem == infoPtr->nFocusedItem) return FALSE;
2795 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2796 lvItem.stateMask = LVIS_FOCUSED;
2797 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
2799 return oldFocus != infoPtr->nFocusedItem;
2802 /* Helper function for LISTVIEW_ShiftIndices *only* */
2803 static INT shift_item(LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2805 if (nShiftItem < nItem) return nShiftItem;
2807 if (nShiftItem > nItem) return nShiftItem + direction;
2809 if (direction > 0) return nShiftItem + direction;
2811 return min(nShiftItem, infoPtr->nItemCount - 1);
2816 * Updates the various indices after an item has been inserted or deleted.
2819 * [I] infoPtr : valid pointer to the listview structure
2820 * [I] nItem : item index
2821 * [I] direction : Direction of shift, +1 or -1.
2826 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
2830 TRACE("Shifting %iu, %i steps\n", nItem, direction);
2832 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
2834 assert(abs(direction) == 1);
2836 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
2838 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
2839 if (nNewFocus != infoPtr->nFocusedItem)
2840 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
2842 /* But we are not supposed to modify nHotItem! */
2848 * Adds a block of selections.
2851 * [I] infoPtr : valid pointer to the listview structure
2852 * [I] nItem : item index
2857 static void LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2859 INT nFirst = min(infoPtr->nSelectionMark, nItem);
2860 INT nLast = max(infoPtr->nSelectionMark, nItem);
2864 if (nFirst == -1) nFirst = nItem;
2866 item.state = LVIS_SELECTED;
2867 item.stateMask = LVIS_SELECTED;
2869 /* FIXME: this is not correct LVS_OWNERDATA
2870 * setting the item states individually will generate
2871 * a LVN_ITEMCHANGED notification for each one. Instead,
2872 * we have to send a LVN_ODSTATECHANGED notification.
2873 * See MSDN documentation for LVN_ITEMCHANGED.
2875 for (i = nFirst; i <= nLast; i++)
2876 LISTVIEW_SetItemState(infoPtr,i,&item);
2882 * Sets a single group selection.
2885 * [I] infoPtr : valid pointer to the listview structure
2886 * [I] nItem : item index
2891 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2893 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2898 if (!(selection = ranges_create(100))) return;
2900 item.state = LVIS_SELECTED;
2901 item.stateMask = LVIS_SELECTED;
2903 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
2905 if (infoPtr->nSelectionMark == -1)
2907 infoPtr->nSelectionMark = nItem;
2908 ranges_additem(selection, nItem);
2914 sel.lower = min(infoPtr->nSelectionMark, nItem);
2915 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
2916 ranges_add(selection, sel);
2921 RECT rcItem, rcSel, rcSelMark;
2924 rcItem.left = LVIR_BOUNDS;
2925 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
2926 rcSelMark.left = LVIR_BOUNDS;
2927 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
2928 UnionRect(&rcSel, &rcItem, &rcSelMark);
2929 iterator_frameditems(&i, infoPtr, &rcSel);
2930 while(iterator_next(&i))
2932 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
2933 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
2935 iterator_destroy(&i);
2938 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
2939 iterator_rangesitems(&i, selection);
2940 while(iterator_next(&i))
2941 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
2942 /* this will also destroy the selection */
2943 iterator_destroy(&i);
2945 LISTVIEW_SetItemFocus(infoPtr, nItem);
2950 * Sets a single selection.
2953 * [I] infoPtr : valid pointer to the listview structure
2954 * [I] nItem : item index
2959 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2963 TRACE("nItem=%d\n", nItem);
2965 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
2967 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
2968 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
2969 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
2971 infoPtr->nSelectionMark = nItem;
2976 * Set selection(s) with keyboard.
2979 * [I] infoPtr : valid pointer to the listview structure
2980 * [I] nItem : item index
2983 * SUCCESS : TRUE (needs to be repainted)
2984 * FAILURE : FALSE (nothing has changed)
2986 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
2988 /* FIXME: pass in the state */
2989 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
2990 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
2991 BOOL bResult = FALSE;
2993 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
2995 if (infoPtr->dwStyle & LVS_SINGLESEL)
2998 LISTVIEW_SetSelection(infoPtr, nItem);
3005 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3009 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3014 LISTVIEW_SetSelection(infoPtr, nItem);
3017 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3020 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3027 * Called when the mouse is being actively tracked and has hovered for a specified
3031 * [I] infoPtr : valid pointer to the listview structure
3032 * [I] fwKeys : key indicator
3033 * [I] pts : mouse position
3036 * 0 if the message was processed, non-zero if there was an error
3039 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3040 * over the item for a certain period of time.
3043 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKyes, POINTS pts)
3045 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
3046 /* FIXME: select the item!!! */
3047 /*LISTVIEW_GetItemAtPt(infoPtr, pt)*/;
3054 * Called whenever WM_MOUSEMOVE is received.
3057 * [I] infoPtr : valid pointer to the listview structure
3058 * [I] fwKeys : key indicator
3059 * [I] pts : mouse position
3062 * 0 if the message is processed, non-zero if there was an error
3064 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, POINTS pts)
3066 TRACKMOUSEEVENT trackinfo;
3068 /* see if we are supposed to be tracking mouse hovering */
3069 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
3070 /* fill in the trackinfo struct */
3071 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3072 trackinfo.dwFlags = TME_QUERY;
3073 trackinfo.hwndTrack = infoPtr->hwndSelf;
3074 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3076 /* see if we are already tracking this hwnd */
3077 _TrackMouseEvent(&trackinfo);
3079 if(!(trackinfo.dwFlags & TME_HOVER)) {
3080 trackinfo.dwFlags = TME_HOVER;
3082 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3083 _TrackMouseEvent(&trackinfo);
3092 * Tests wheather the item is assignable to a list with style lStyle
3094 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3096 if ( (lpLVItem->mask & LVIF_TEXT) &&
3097 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3098 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3106 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3109 * [I] infoPtr : valid pointer to the listview structure
3110 * [I] lpLVItem : valid pointer to new item atttributes
3111 * [I] isNew : the item being set is being inserted
3112 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3113 * [O] bChanged : will be set to TRUE if the item really changed
3119 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3121 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3129 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3131 if (lpLVItem->mask == 0) return TRUE;
3133 if (infoPtr->dwStyle & LVS_OWNERDATA)
3135 /* a virtual listview we stores only selection and focus */
3136 if (lpLVItem->mask & ~LVIF_STATE)
3142 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3143 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
3147 /* we need to get the lParam and state of the item */
3148 item.iItem = lpLVItem->iItem;
3149 item.iSubItem = lpLVItem->iSubItem;
3150 item.mask = LVIF_STATE | LVIF_PARAM;
3151 item.stateMask = ~0;
3154 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3156 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3157 /* determine what fields will change */
3158 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
3159 uChanged |= LVIF_STATE;
3161 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3162 uChanged |= LVIF_IMAGE;
3164 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3165 uChanged |= LVIF_PARAM;
3167 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3168 uChanged |= LVIF_INDENT;
3170 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3171 uChanged |= LVIF_TEXT;
3173 TRACE("uChanged=0x%x\n", uChanged);
3174 if (!uChanged) return TRUE;
3177 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3178 nmlv.iItem = lpLVItem->iItem;
3179 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3180 nmlv.uOldState = item.state;
3181 nmlv.uChanged = uChanged;
3182 nmlv.lParam = item.lParam;
3184 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3185 /* and we are _NOT_ virtual (LVS_OWERNDATA) */
3186 if(lpItem && !isNew && notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3189 /* copy information */
3190 if (lpLVItem->mask & LVIF_TEXT)
3191 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3193 if (lpLVItem->mask & LVIF_IMAGE)
3194 lpItem->hdr.iImage = lpLVItem->iImage;
3196 if (lpLVItem->mask & LVIF_PARAM)
3197 lpItem->lParam = lpLVItem->lParam;
3199 if (lpLVItem->mask & LVIF_INDENT)
3200 lpItem->iIndent = lpLVItem->iIndent;
3202 if (uChanged & LVIF_STATE)
3204 if (lpItem && (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3206 lpItem->state &= ~lpLVItem->stateMask;
3207 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3209 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3211 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3212 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3214 else if (lpLVItem->stateMask & LVIS_SELECTED)
3215 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3217 /* if we are asked to change focus, and we manage it, do it */
3218 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3220 if (lpLVItem->state & LVIS_FOCUSED)
3222 LISTVIEW_SetItemFocus(infoPtr, -1);
3223 infoPtr->nFocusedItem = lpLVItem->iItem;
3224 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3226 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3227 infoPtr->nFocusedItem = -1;
3231 /* if we're inserting the item, we're done */
3232 if (isNew) return TRUE;
3234 /* send LVN_ITEMCHANGED notification */
3235 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3236 notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3243 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3246 * [I] infoPtr : valid pointer to the listview structure
3247 * [I] lpLVItem : valid pointer to new subitem atttributes
3248 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3249 * [O] bChanged : will be set to TRUE if the item really changed
3255 static BOOL set_sub_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3258 SUBITEM_INFO *lpSubItem;
3260 /* we do not support subitems for virtual listviews */
3261 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3263 /* set subitem only if column is present */
3264 if (lpLVItem->iSubItem >= infoPtr->hdpaColumns->nItemCount) return FALSE;
3266 /* First do some sanity checks */
3267 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE)) return FALSE;
3268 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE))) return TRUE;
3270 /* get the subitem structure, and create it if not there */
3271 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3272 assert (hdpaSubItems);
3274 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3277 SUBITEM_INFO *tmpSubItem;
3280 lpSubItem = (SUBITEM_INFO *)COMCTL32_Alloc(sizeof(SUBITEM_INFO));
3281 if (!lpSubItem) return FALSE;
3282 /* we could binary search here, if need be...*/
3283 for (i = 1; i < hdpaSubItems->nItemCount; i++)
3285 tmpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
3286 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3288 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3290 COMCTL32_Free(lpSubItem);
3293 lpSubItem->iSubItem = lpLVItem->iSubItem;
3297 if (lpLVItem->mask & LVIF_IMAGE)
3298 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3300 lpSubItem->hdr.iImage = lpLVItem->iImage;
3304 if (lpLVItem->mask & LVIF_TEXT)
3305 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3307 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3316 * Sets item attributes.
3319 * [I] infoPtr : valid pointer to the listview structure
3320 * [I] lpLVItem : new item atttributes
3321 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3327 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
3329 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3330 LPWSTR pszText = NULL;
3331 BOOL bResult, bChanged = FALSE;
3333 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3335 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3338 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3339 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3341 pszText = lpLVItem->pszText;
3342 ((LVITEMW *)lpLVItem)->pszText = textdupTtoW(lpLVItem->pszText, isW);
3345 /* actually set the fields */
3346 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3348 if (lpLVItem->iSubItem)
3349 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3351 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3353 /* redraw item, if necessary */
3354 if (bChanged && !infoPtr->bIsDrawing)
3356 /* this little optimization eliminates some nasty flicker */
3357 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3358 (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) || lpLVItem->iSubItem) )
3359 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3361 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3366 textfreeT(lpLVItem->pszText, isW);
3367 ((LVITEMW *)lpLVItem)->pszText = pszText;
3375 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3378 * [I] infoPtr : valid pointer to the listview structure
3383 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *infoPtr)
3385 LONG lStyle = infoPtr->dwStyle;
3386 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3388 SCROLLINFO scrollInfo;
3390 scrollInfo.cbSize = sizeof(SCROLLINFO);
3391 scrollInfo.fMask = SIF_POS;
3393 if (uView == LVS_LIST)
3395 if ((lStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3396 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3398 else if (uView == LVS_REPORT)
3400 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3401 nItem = scrollInfo.nPos;
3405 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3406 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3409 TRACE("nItem=%d\n", nItem);
3417 * Erases the background of the given rectangle
3420 * [I] infoPtr : valid pointer to the listview structure
3421 * [I] hdc : device context handle
3422 * [I] lprcBox : clipping rectangle
3428 static inline BOOL LISTVIEW_FillBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3430 if (!infoPtr->hBkBrush) return FALSE;
3432 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, debugrect(lprcBox), infoPtr->hBkBrush);
3434 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3442 * [I] infoPtr : valid pointer to the listview structure
3443 * [I] hdc : device context handle
3444 * [I] nItem : item index
3445 * [I] nSubItem : subitem index
3446 * [I] pos : item position in client coordinates
3447 * [I] cdmode : custom draw mode
3453 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3455 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3456 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3457 WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3458 DWORD cditemmode = CDRF_DODEFAULT;
3459 RECT* lprcFocus, rcSelect, rcBox, rcState, rcIcon, rcLabel;
3460 NMLVCUSTOMDRAW nmlvcd;
3464 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, debugpoint(&pos));
3466 /* get information needed for drawing the item */
3467 lvItem.mask = LVIF_TEXT | LVIF_IMAGE;
3468 if (nSubItem == 0) lvItem.mask |= LVIF_STATE | LVIF_PARAM;
3469 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3470 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3471 lvItem.iItem = nItem;
3472 lvItem.iSubItem = nSubItem;
3475 lvItem.cchTextMax = DISP_TEXT_SIZE;
3476 lvItem.pszText = szDispText;
3477 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3478 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3479 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3480 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3481 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3483 /* now check if we need to update the focus rectangle */
3484 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3486 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3487 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
3488 OffsetRect(&rcBox, pos.x, pos.y);
3489 OffsetRect(&rcState, pos.x, pos.y);
3490 OffsetRect(&rcIcon, pos.x, pos.y);
3491 OffsetRect(&rcLabel, pos.x, pos.y);
3492 TRACE(" rcBox=%s, rcState=%s, rcIcon=%s. rcLabel=%s\n",
3493 debugrect(&rcBox), debugrect(&rcState), debugrect(&rcIcon), debugrect(&rcLabel));
3495 /* fill in the custom draw structure */
3496 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox);
3497 nmlvcd.nmcd.dwItemSpec = lvItem.iItem;
3498 nmlvcd.iSubItem = lvItem.iSubItem;
3499 if (lvItem.state & LVIS_SELECTED) nmlvcd.nmcd.uItemState |= CDIS_SELECTED;
3500 if (lvItem.state & LVIS_FOCUSED) nmlvcd.nmcd.uItemState |= CDIS_FOCUS;
3501 if (lvItem.iItem == infoPtr->nHotItem) nmlvcd.nmcd.uItemState |= CDIS_HOT;
3502 nmlvcd.nmcd.lItemlParam = lvItem.lParam;
3504 if (cdmode & CDRF_NOTIFYITEMDRAW)
3505 cditemmode = notify_customdraw (infoPtr, CDDS_ITEMPREPAINT, &nmlvcd);
3506 if (cditemmode & CDRF_SKIPDEFAULT) goto postpaint;
3508 /* apprently, for selected items, we have to override the returned values */
3509 if (lvItem.state & LVIS_SELECTED)
3511 if (infoPtr->bFocus)
3513 nmlvcd.clrTextBk = comctl32_color.clrHighlight;
3514 nmlvcd.clrText = comctl32_color.clrHighlightText;
3516 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
3518 nmlvcd.clrTextBk = comctl32_color.clr3dFace;
3519 nmlvcd.clrText = comctl32_color.clrBtnText;
3523 /* in full row select, subitems, will just use main item's colors */
3524 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3525 nmlvcd.clrTextBk = CLR_NONE;
3528 if (infoPtr->himlState && !IsRectEmpty(&rcState))
3530 UINT uStateImage = (lvItem.state & LVIS_STATEIMAGEMASK) >> 12;
3533 TRACE("uStateImage=%d\n", uStateImage);
3534 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc, rcState.left, rcState.top, ILD_NORMAL);
3539 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3540 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3542 TRACE("iImage=%d\n", lvItem.iImage);
3543 ImageList_Draw(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3544 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3547 /* Don't bother painting item being edited */
3548 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3550 /* Set the text attributes */
3551 if (nmlvcd.clrTextBk != CLR_NONE)
3553 SetBkMode(hdc, OPAQUE);
3554 SetBkColor(hdc, nmlvcd.clrTextBk == CLR_DEFAULT ? infoPtr->clrTextBkDefault : nmlvcd.clrTextBk);
3557 SetBkMode(hdc, TRANSPARENT);
3558 SetTextColor(hdc, nmlvcd.clrText);
3560 /* draw the selection background, if we're drawing the main item */
3564 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3565 rcSelect.right = rcBox.right;
3567 if (nmlvcd.clrTextBk != CLR_NONE)
3568 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3569 if(lprcFocus) *lprcFocus = rcSelect;
3572 /* figure out the text drawing flags */
3573 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3574 if (uView == LVS_ICON)
3575 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3578 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3580 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3581 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3582 default: uFormat |= DT_LEFT;
3585 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3587 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3588 else rcLabel.left += LABEL_HOR_PADDING;
3590 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3591 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3594 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3595 notify_customdraw(infoPtr, CDDS_ITEMPOSTPAINT, &nmlvcd);
3601 * Draws listview items when in owner draw mode.
3604 * [I] infoPtr : valid pointer to the listview structure
3605 * [I] hdc : device context handle
3610 static void LISTVIEW_RefreshOwnerDraw(LISTVIEW_INFO *infoPtr, HDC hdc)
3612 UINT uID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
3613 HWND hwndParent = GetParent(infoPtr->hwndSelf);
3614 POINT Origin, Position;
3621 ZeroMemory(&dis, sizeof(dis));
3623 /* Get scroll info once before loop */
3624 LISTVIEW_GetOrigin(infoPtr, &Origin);
3626 /* figure out what we need to draw */
3627 iterator_visibleitems(&i, infoPtr, hdc);
3629 /* send cache hint notification */
3630 if (infoPtr->dwStyle & LVS_OWNERDATA)
3632 RANGE range = iterator_range(&i);
3635 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
3636 nmlv.iFrom = range.lower;
3637 nmlv.iTo = range.upper - 1;
3638 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
3641 /* iterate through the invalidated rows */
3642 while(iterator_next(&i))
3644 item.iItem = i.nItem;
3646 item.mask = LVIF_PARAM | LVIF_STATE;
3647 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3648 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3650 dis.CtlType = ODT_LISTVIEW;
3652 dis.itemID = item.iItem;
3653 dis.itemAction = ODA_DRAWENTIRE;
3655 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3656 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3657 dis.hwndItem = infoPtr->hwndSelf;
3659 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3660 dis.rcItem.left = Position.x + Origin.x;
3661 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3662 dis.rcItem.top = Position.y + Origin.y;
3663 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3664 dis.itemData = item.lParam;
3666 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), debugrect(&dis.rcItem));
3667 SendMessageW(hwndParent, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3669 iterator_destroy(&i);
3674 * Draws listview items when in report display mode.
3677 * [I] infoPtr : valid pointer to the listview structure
3678 * [I] hdc : device context handle
3679 * [I] cdmode : custom draw mode
3684 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD cdmode)
3687 RECT rcClip, rcItem;
3688 POINT Origin, Position;
3694 /* figure out what to draw */
3695 rgntype = GetClipBox(hdc, &rcClip);
3696 if (rgntype == NULLREGION) return;
3698 /* Get scroll info once before loop */
3699 LISTVIEW_GetOrigin(infoPtr, &Origin);
3701 /* narrow down the columns we need to paint */
3702 for(colRange.lower = 0; colRange.lower < infoPtr->hdpaColumns->nItemCount; colRange.lower++)
3704 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
3705 if (rcItem.right + Origin.x >= rcClip.left) break;
3707 for(colRange.upper = infoPtr->hdpaColumns->nItemCount; colRange.upper > 0; colRange.upper--)
3709 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
3710 if (rcItem.left + Origin.x < rcClip.right) break;
3712 iterator_rangeitems(&j, colRange);
3714 /* in full row select, we _have_ to draw the main item */
3715 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
3718 /* figure out what we need to draw */
3719 iterator_visibleitems(&i, infoPtr, hdc);
3721 /* iterate through the invalidated rows */
3722 while(iterator_next(&i))
3724 /* iterate through the invalidated columns */
3725 while(iterator_next(&j))
3727 LISTVIEW_GetItemOrigin(infoPtr, i.nItem, &Position);
3728 Position.x += Origin.x;
3729 Position.y += Origin.y;
3731 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
3733 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
3735 rcItem.bottom = infoPtr->nItemHeight;
3736 OffsetRect(&rcItem, Position.x, Position.y);
3737 if (!RectVisible(hdc, &rcItem)) continue;
3740 LISTVIEW_DrawItem(infoPtr, hdc, i.nItem, j.nItem, Position, cdmode);
3743 iterator_destroy(&i);
3748 * Draws listview items when in list display mode.
3751 * [I] infoPtr : valid pointer to the listview structure
3752 * [I] hdc : device context handle
3753 * [I] cdmode : custom draw mode
3758 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD cdmode)
3760 POINT Origin, Position;
3763 /* Get scroll info once before loop */
3764 LISTVIEW_GetOrigin(infoPtr, &Origin);
3766 /* figure out what we need to draw */
3767 iterator_visibleitems(&i, infoPtr, hdc);
3769 while(iterator_prev(&i))
3771 LISTVIEW_GetItemOrigin(infoPtr, i.nItem, &Position);
3772 Position.x += Origin.x;
3773 Position.y += Origin.y;
3775 LISTVIEW_DrawItem(infoPtr, hdc, i.nItem, 0, Position, cdmode);
3777 iterator_destroy(&i);
3783 * Draws listview items.
3786 * [I] infoPtr : valid pointer to the listview structure
3787 * [I] hdc : device context handle
3792 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc)
3794 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3795 COLORREF oldTextColor, oldClrTextBk, oldClrText;
3796 NMLVCUSTOMDRAW nmlvcd;
3802 LISTVIEW_DUMP(infoPtr);
3804 infoPtr->bIsDrawing = TRUE;
3806 /* save dc values we're gonna trash while drawing */
3807 hOldFont = SelectObject(hdc, infoPtr->hFont);
3808 oldBkMode = GetBkMode(hdc);
3809 infoPtr->clrTextBkDefault = GetBkColor(hdc);
3810 oldTextColor = GetTextColor(hdc);
3812 oldClrTextBk = infoPtr->clrTextBk;
3813 oldClrText = infoPtr->clrText;
3815 GetClientRect(infoPtr->hwndSelf, &rcClient);
3816 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient);
3817 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3818 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
3820 /* Use these colors to draw the items */
3821 infoPtr->clrTextBk = nmlvcd.clrTextBk;
3822 infoPtr->clrText = nmlvcd.clrText;
3824 /* nothing to draw */
3825 if(infoPtr->nItemCount == 0) goto enddraw;
3827 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
3828 LISTVIEW_RefreshOwnerDraw(infoPtr, hdc);
3831 if (uView == LVS_REPORT)
3832 LISTVIEW_RefreshReport(infoPtr, hdc, cdmode);
3833 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
3834 LISTVIEW_RefreshList(infoPtr, hdc, cdmode);
3836 /* if we have a focus rect, draw it */
3837 if (infoPtr->bFocus)
3838 DrawFocusRect(hdc, &infoPtr->rcFocus);
3842 if (cdmode & CDRF_NOTIFYPOSTPAINT)
3843 notify_customdraw(infoPtr, CDDS_POSTPAINT, &nmlvcd);
3845 infoPtr->clrTextBk = oldClrTextBk;
3846 infoPtr->clrText = oldClrText;
3848 SelectObject(hdc, hOldFont);
3849 SetBkMode(hdc, oldBkMode);
3850 SetBkColor(hdc, infoPtr->clrTextBkDefault);
3851 SetTextColor(hdc, oldTextColor);
3852 infoPtr->bIsDrawing = FALSE;
3858 * Calculates the approximate width and height of a given number of items.
3861 * [I] infoPtr : valid pointer to the listview structure
3862 * [I] nItemCount : number of items
3863 * [I] wWidth : width
3864 * [I] wHeight : height
3867 * Returns a DWORD. The width in the low word and the height in high word.
3869 static DWORD LISTVIEW_ApproximateViewRect(LISTVIEW_INFO *infoPtr, INT nItemCount,
3870 WORD wWidth, WORD wHeight)
3872 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3873 INT nItemCountPerColumn = 1;
3874 INT nColumnCount = 0;
3875 DWORD dwViewRect = 0;
3877 if (nItemCount == -1)
3878 nItemCount = infoPtr->nItemCount;
3880 if (uView == LVS_LIST)
3882 if (wHeight == 0xFFFF)
3884 /* use current height */
3885 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
3888 if (wHeight < infoPtr->nItemHeight)
3889 wHeight = infoPtr->nItemHeight;
3893 if (infoPtr->nItemHeight > 0)
3895 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
3896 if (nItemCountPerColumn == 0)
3897 nItemCountPerColumn = 1;
3899 if (nItemCount % nItemCountPerColumn != 0)
3900 nColumnCount = nItemCount / nItemCountPerColumn;
3902 nColumnCount = nItemCount / nItemCountPerColumn + 1;
3906 /* Microsoft padding magic */
3907 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
3908 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
3910 dwViewRect = MAKELONG(wWidth, wHeight);
3912 else if (uView == LVS_REPORT)
3913 FIXME("uView == LVS_REPORT: not implemented\n");
3914 else if (uView == LVS_SMALLICON)
3915 FIXME("uView == LVS_SMALLICON: not implemented\n");
3916 else if (uView == LVS_ICON)
3917 FIXME("uView == LVS_ICON: not implemented\n");
3922 /* << LISTVIEW_CreateDragImage >> */
3927 * Removes all listview items and subitems.
3930 * [I] infoPtr : valid pointer to the listview structure
3936 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr)
3939 HDPA hdpaSubItems = NULL;
3946 /* we do it directly, to avoid notifications */
3947 ranges_clear(infoPtr->selectionRanges);
3948 infoPtr->nSelectionMark = -1;
3949 infoPtr->nFocusedItem = -1;
3950 SetRectEmpty(&infoPtr->rcFocus);
3951 /* But we are supposed to leave nHotItem as is! */
3954 /* send LVN_DELETEALLITEMS notification */
3955 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3957 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
3959 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
3961 /* send LVN_DELETEITEM notification, if not supressed */
3962 if (!bSuppress) notify_deleteitem(infoPtr, i);
3963 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
3965 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
3966 for (j = 0; j < hdpaSubItems->nItemCount; j++)
3968 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, j);
3969 if (is_textW(hdrItem->pszText)) COMCTL32_Free(hdrItem->pszText);
3970 COMCTL32_Free(hdrItem);
3972 DPA_Destroy(hdpaSubItems);
3973 DPA_DeletePtr(infoPtr->hdpaItems, i);
3975 DPA_DeletePtr(infoPtr->hdpaPosX, i);
3976 DPA_DeletePtr(infoPtr->hdpaPosY, i);
3977 infoPtr->nItemCount --;
3980 LISTVIEW_UpdateScroll(infoPtr);
3982 LISTVIEW_InvalidateList(infoPtr);
3989 * Scrolls, and updates the columns, when a column is changing width.
3992 * [I] infoPtr : valid pointer to the listview structure
3993 * [I] nColumn : column to scroll
3994 * [I] dx : amount of scroll, in pixels
3999 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4001 COLUMN_INFO *lpColumnInfo;
4005 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, infoPtr->hdpaColumns->nItemCount - 1));
4006 rcCol = lpColumnInfo->rcHeader;
4007 if (nColumn >= infoPtr->hdpaColumns->nItemCount)
4008 rcCol.left = rcCol.right;
4010 /* ajust the other columns */
4011 for (nCol = nColumn; nCol < infoPtr->hdpaColumns->nItemCount; nCol++)
4013 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4014 lpColumnInfo->rcHeader.left += dx;
4015 lpColumnInfo->rcHeader.right += dx;
4018 /* do not update screen if not in report mode */
4019 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4021 /* if we have a focus, must first erase the focus rect */
4022 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4024 /* Need to reset the item width when inserting a new column */
4025 infoPtr->nItemWidth += dx;
4027 LISTVIEW_UpdateScroll(infoPtr);
4029 /* scroll to cover the deleted column, and invalidate for redraw */
4030 rcOld = infoPtr->rcList;
4031 rcOld.left = rcCol.left;
4032 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4034 /* we can restore focus now */
4035 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4040 * Removes a column from the listview control.
4043 * [I] infoPtr : valid pointer to the listview structure
4044 * [I] nColumn : column index
4050 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4054 TRACE("nColumn=%d\n", nColumn);
4056 if (nColumn <= 0 || nColumn >= infoPtr->hdpaColumns->nItemCount) return FALSE;
4058 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4060 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4063 COMCTL32_Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4064 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4066 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4068 SUBITEM_INFO *lpSubItem, *lpDelItem;
4070 INT nItem, nSubItem, i;
4072 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4074 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
4077 for (i = 1; i < hdpaSubItems->nItemCount; i++)
4079 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
4080 if (lpSubItem->iSubItem == nColumn)
4083 lpDelItem = lpSubItem;
4085 else if (lpSubItem->iSubItem > nColumn)
4087 lpSubItem->iSubItem--;
4091 /* if we found our subitem, zapp it */
4095 if (is_textW(lpDelItem->hdr.pszText))
4096 COMCTL32_Free(lpDelItem->hdr.pszText);
4099 COMCTL32_Free(lpDelItem);
4101 /* free dpa memory */
4102 DPA_DeletePtr(hdpaSubItems, nSubItem);
4107 /* update the other column info */
4108 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4115 * Invalidates the listview after an item's insertion or deletion.
4118 * [I] infoPtr : valid pointer to the listview structure
4119 * [I] nItem : item index
4120 * [I] dir : -1 if deleting, 1 if inserting
4125 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4127 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4128 INT nPerCol, nItemCol, nItemRow;
4132 /* if we don't refresh, what's the point of scrolling? */
4133 if (!is_redrawing(infoPtr)) return;
4135 assert (abs(dir) == 1);
4137 /* arrange icons if autoarrange is on */
4138 if (is_autoarrange(infoPtr))
4140 BOOL arrange = TRUE;
4141 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4142 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4143 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4146 /* scrollbars need updating */
4147 LISTVIEW_UpdateScroll(infoPtr);
4149 /* figure out the item's position */
4150 if (uView == LVS_REPORT)
4151 nPerCol = infoPtr->nItemCount + 1;
4152 else if (uView == LVS_LIST)
4153 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4154 else /* LVS_ICON, or LVS_SMALLICON */
4157 nItemCol = nItem / nPerCol;
4158 nItemRow = nItem % nPerCol;
4159 LISTVIEW_GetOrigin(infoPtr, &Origin);
4161 /* move the items below up a slot */
4162 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4163 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4164 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4165 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4166 OffsetRect(&rcScroll, Origin.x, Origin.y);
4167 TRACE("rcScroll=%s, dx=%d\n", debugrect(&rcScroll), dir * infoPtr->nItemHeight);
4168 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4170 TRACE("Scrolling rcScroll=%s, rcList=%s\n", debugrect(&rcScroll), debugrect(&infoPtr->rcList));
4171 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4172 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4175 /* report has only that column, so we're done */
4176 if (uView == LVS_REPORT) return;
4178 /* now for LISTs, we have to deal with the columns to the right */
4179 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4181 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4182 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4183 OffsetRect(&rcScroll, Origin.x, Origin.y);
4184 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4185 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4186 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4191 * Removes an item from the listview control.
4194 * [I] infoPtr : valid pointer to the listview structure
4195 * [I] nItem : item index
4201 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4203 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4206 TRACE("(nItem=%d)\n", nItem);
4208 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4210 /* remove selection, and focus */
4212 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4213 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4215 /* send LVN_DELETEITEM notification. */
4216 notify_deleteitem(infoPtr, nItem);
4218 /* we need to do this here, because we'll be deleting stuff */
4219 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4220 LISTVIEW_InvalidateItem(infoPtr, nItem);
4222 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4228 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4229 for (i = 0; i < hdpaSubItems->nItemCount; i++)
4231 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, i);
4232 if (is_textW(hdrItem->pszText)) COMCTL32_Free(hdrItem->pszText);
4233 COMCTL32_Free(hdrItem);
4235 DPA_Destroy(hdpaSubItems);
4238 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4240 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4241 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4244 infoPtr->nItemCount--;
4245 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4247 /* now is the invalidation fun */
4248 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4255 * Callback implementation for editlabel control
4258 * [I] infoPtr : valid pointer to the listview structure
4259 * [I] pszText : modified text
4260 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4266 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4268 NMLVDISPINFOW dispInfo;
4270 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4272 ZeroMemory(&dispInfo, sizeof(dispInfo));
4273 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
4274 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4275 dispInfo.item.iSubItem = 0;
4276 dispInfo.item.stateMask = ~0;
4277 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4278 /* add the text from the edit in */
4279 dispInfo.item.mask |= LVIF_TEXT;
4280 dispInfo.item.pszText = pszText;
4281 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4283 /* Do we need to update the Item Text */
4284 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4285 if (!pszText) return TRUE;
4287 ZeroMemory(&dispInfo, sizeof(dispInfo));
4288 dispInfo.item.mask = LVIF_TEXT;
4289 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4290 dispInfo.item.iSubItem = 0;
4291 dispInfo.item.pszText = pszText;
4292 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4293 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4298 * Begin in place editing of specified list view item
4301 * [I] infoPtr : valid pointer to the listview structure
4302 * [I] nItem : item index
4303 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4309 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4311 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4312 NMLVDISPINFOW dispInfo;
4315 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4317 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4318 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4320 infoPtr->nEditLabelItem = nItem;
4322 /* Is the EditBox still there, if so remove it */
4323 if(infoPtr->hwndEdit != 0)
4325 SetFocus(infoPtr->hwndSelf);
4326 infoPtr->hwndEdit = 0;
4329 LISTVIEW_SetSelection(infoPtr, nItem);
4330 LISTVIEW_SetItemFocus(infoPtr, nItem);
4331 LISTVIEW_InvalidateItem(infoPtr, nItem);
4333 rect.left = LVIR_LABEL;
4334 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4336 ZeroMemory(&dispInfo, sizeof(dispInfo));
4337 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4338 dispInfo.item.iItem = nItem;
4339 dispInfo.item.iSubItem = 0;
4340 dispInfo.item.stateMask = ~0;
4341 dispInfo.item.pszText = szDispText;
4342 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4343 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4345 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4346 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4347 if (!infoPtr->hwndEdit) return 0;
4349 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4351 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4352 infoPtr->hwndEdit = 0;
4356 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4357 SetFocus(infoPtr->hwndEdit);
4358 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4359 return infoPtr->hwndEdit;
4365 * Ensures the specified item is visible, scrolling into view if necessary.
4368 * [I] infoPtr : valid pointer to the listview structure
4369 * [I] nItem : item index
4370 * [I] bPartial : partially or entirely visible
4376 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4378 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4379 INT nScrollPosHeight = 0;
4380 INT nScrollPosWidth = 0;
4381 INT nHorzAdjust = 0;
4382 INT nVertAdjust = 0;
4385 RECT rcItem, rcTemp;
4387 rcItem.left = LVIR_BOUNDS;
4388 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4390 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4392 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4394 /* scroll left/right, but in LVS_REPORT mode */
4395 if (uView == LVS_LIST)
4396 nScrollPosWidth = infoPtr->nItemWidth;
4397 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4398 nScrollPosWidth = 1;
4400 if (rcItem.left < infoPtr->rcList.left)
4403 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
4408 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
4412 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
4414 /* scroll up/down, but not in LVS_LIST mode */
4415 if (uView == LVS_REPORT)
4416 nScrollPosHeight = infoPtr->nItemHeight;
4417 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
4418 nScrollPosHeight = 1;
4420 if (rcItem.top < infoPtr->rcList.top)
4423 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
4428 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
4432 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
4434 if (nScrollPosWidth)
4436 INT diff = nHorzDiff / nScrollPosWidth;
4437 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
4438 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
4441 if (nScrollPosHeight)
4443 INT diff = nVertDiff / nScrollPosHeight;
4444 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
4445 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
4453 * Searches for an item with specific characteristics.
4456 * [I] hwnd : window handle
4457 * [I] nStart : base item index
4458 * [I] lpFindInfo : item information to look for
4461 * SUCCESS : index of item
4464 static INT LISTVIEW_FindItemW(LISTVIEW_INFO *infoPtr, INT nStart,
4465 const LVFINDINFOW *lpFindInfo)
4467 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4468 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4469 BOOL bWrap = FALSE, bNearest = FALSE;
4470 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
4471 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
4472 POINT Position, Destination;
4475 if (!lpFindInfo || nItem < 0) return -1;
4478 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
4480 lvItem.mask |= LVIF_TEXT;
4481 lvItem.pszText = szDispText;
4482 lvItem.cchTextMax = DISP_TEXT_SIZE;
4485 if (lpFindInfo->flags & LVFI_WRAP)
4488 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
4489 (uView == LVS_ICON || uView ==LVS_SMALLICON))
4494 LISTVIEW_GetOrigin(infoPtr, &Origin);
4495 Destination.x = lpFindInfo->pt.x - Origin.x;
4496 Destination.y = lpFindInfo->pt.y - Origin.y;
4497 switch(lpFindInfo->vkDirection)
4499 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
4500 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
4501 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
4502 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
4503 case VK_HOME: Destination.x = Destination.y = 0; break;
4504 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4505 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4507 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
4508 Destination.x = rcArea.right;
4509 Destination.y = rcArea.bottom;
4511 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
4516 /* if LVFI_PARAM is specified, all other flags are ignored */
4517 if (lpFindInfo->flags & LVFI_PARAM)
4519 lvItem.mask |= LVIF_PARAM;
4521 lvItem.mask &= ~LVIF_TEXT;
4525 for (; nItem < nLast; nItem++)
4527 lvItem.iItem = nItem;
4528 lvItem.iSubItem = 0;
4529 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
4531 if (lvItem.mask & LVIF_PARAM)
4533 if (lpFindInfo->lParam == lvItem.lParam)
4539 if (lvItem.mask & LVIF_TEXT)
4541 if (lpFindInfo->flags & LVFI_PARTIAL)
4543 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
4547 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
4551 if (!bNearest) return nItem;
4553 /* This is very inefficient. To do a good job here,
4554 * we need a sorted array of (x,y) item positions */
4555 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
4557 /* compute the distance^2 to the destination */
4558 xdist = Destination.x - Position.x;
4559 ydist = Destination.y - Position.y;
4560 dist = xdist * xdist + ydist * ydist;
4562 /* remember the distance, and item if it's closer */
4566 nNearestItem = nItem;
4573 nLast = min(nStart + 1, infoPtr->nItemCount);
4578 return nNearestItem;
4583 * Searches for an item with specific characteristics.
4586 * [I] hwnd : window handle
4587 * [I] nStart : base item index
4588 * [I] lpFindInfo : item information to look for
4591 * SUCCESS : index of item
4594 static INT LISTVIEW_FindItemA(LISTVIEW_INFO *infoPtr, INT nStart,
4595 const LVFINDINFOA *lpFindInfo)
4597 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
4601 memcpy(&fiw, lpFindInfo, sizeof(fiw));
4602 if (hasText) fiw.psz = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
4603 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
4604 if (hasText) textfreeT((LPWSTR)fiw.psz, FALSE);
4610 * Retrieves the background image of the listview control.
4613 * [I] infoPtr : valid pointer to the listview structure
4614 * [O] lpBkImage : background image attributes
4620 /* static BOOL LISTVIEW_GetBkImage(LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
4622 /* FIXME (listview, "empty stub!\n"); */
4628 * Retrieves column attributes.
4631 * [I] infoPtr : valid pointer to the listview structure
4632 * [I] nColumn : column index
4633 * [IO] lpColumn : column information
4634 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
4635 * otherwise it is in fact a LPLVCOLUMNA
4641 static BOOL LISTVIEW_GetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
4643 COLUMN_INFO *lpColumnInfo;
4646 if (!lpColumn || nColumn < 0 || nColumn >= infoPtr->hdpaColumns->nItemCount) return FALSE;
4647 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
4649 /* initialize memory */
4650 ZeroMemory(&hdi, sizeof(hdi));
4652 if (lpColumn->mask & LVCF_TEXT)
4654 hdi.mask |= HDI_TEXT;
4655 hdi.pszText = lpColumn->pszText;
4656 hdi.cchTextMax = lpColumn->cchTextMax;
4659 if (lpColumn->mask & LVCF_IMAGE)
4660 hdi.mask |= HDI_IMAGE;
4662 if (lpColumn->mask & LVCF_ORDER)
4663 hdi.mask |= HDI_ORDER;
4665 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
4667 if (lpColumn->mask & LVCF_FMT)
4668 lpColumn->fmt = lpColumnInfo->fmt;
4670 if (lpColumn->mask & LVCF_WIDTH)
4671 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
4673 if (lpColumn->mask & LVCF_IMAGE)
4674 lpColumn->iImage = hdi.iImage;
4676 if (lpColumn->mask & LVCF_ORDER)
4677 lpColumn->iOrder = hdi.iOrder;
4683 static BOOL LISTVIEW_GetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
4690 /* FIXME: little hack */
4691 for (i = 0; i < iCount; i++)
4699 * Retrieves the column width.
4702 * [I] infoPtr : valid pointer to the listview structure
4703 * [I] int : column index
4706 * SUCCESS : column width
4709 static INT LISTVIEW_GetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn)
4711 INT nColumnWidth = 0;
4714 TRACE("nColumn=%d\n", nColumn);
4716 /* we have a 'column' in LIST and REPORT mode only */
4717 switch(infoPtr->dwStyle & LVS_TYPEMASK)
4720 nColumnWidth = infoPtr->nItemWidth;
4723 if (nColumn < 0 || nColumn >= infoPtr->hdpaColumns->nItemCount) return 0;
4724 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
4725 nColumnWidth = rcHeader.right - rcHeader.left;
4729 TRACE("nColumnWidth=%d\n", nColumnWidth);
4730 return nColumnWidth;
4735 * In list or report display mode, retrieves the number of items that can fit
4736 * vertically in the visible area. In icon or small icon display mode,
4737 * retrieves the total number of visible items.
4740 * [I] infoPtr : valid pointer to the listview structure
4743 * Number of fully visible items.
4745 static INT LISTVIEW_GetCountPerPage(LISTVIEW_INFO *infoPtr)
4747 switch (infoPtr->dwStyle & LVS_TYPEMASK)
4751 return infoPtr->nItemCount;
4753 return LISTVIEW_GetCountPerColumn(infoPtr);
4755 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
4762 * Retrieves an image list handle.
4765 * [I] infoPtr : valid pointer to the listview structure
4766 * [I] nImageList : image list identifier
4769 * SUCCESS : image list handle
4772 static HIMAGELIST LISTVIEW_GetImageList(LISTVIEW_INFO *infoPtr, INT nImageList)
4776 case LVSIL_NORMAL: return infoPtr->himlNormal;
4777 case LVSIL_SMALL: return infoPtr->himlSmall;
4778 case LVSIL_STATE: return infoPtr->himlState;
4783 /* LISTVIEW_GetISearchString */
4787 * Retrieves item attributes.
4790 * [I] hwnd : window handle
4791 * [IO] lpLVItem : item info
4792 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
4793 * if FALSE, the lpLVItem is a LPLVITEMA.
4796 * This is the internal 'GetItem' interface -- it tries to
4797 * be smart, and avoids text copies, if possible, by modifing
4798 * lpLVItem->pszText to point to the text string. Please note
4799 * that this is not always possible (e.g. OWNERDATA), so on
4800 * entry you *must* supply valid values for pszText, and cchTextMax.
4801 * The only difference to the documented interface is that upon
4802 * return, you should use *only* the lpLVItem->pszText, rather than
4803 * the buffer pointer you provided on input. Most code already does
4804 * that, so it's not a problem.
4805 * For the two cases when the text must be copied (that is,
4806 * for LVM_GETITEM, and LVMGETITEMTEXT), use LISTVIEW_GetItemExtT.
4812 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
4814 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
4815 NMLVDISPINFOW dispInfo;
4820 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
4822 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
4825 if (lpLVItem->mask == 0) return TRUE;
4827 /* a quick optimization if all we're asked is the focus state
4828 * these queries are worth optimising since they are common,
4829 * and can be answered in constant time, without the heavy accesses */
4830 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
4831 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
4833 lpLVItem->state = 0;
4834 if (infoPtr->nFocusedItem == lpLVItem->iItem)
4835 lpLVItem->state |= LVIS_FOCUSED;
4839 ZeroMemory(&dispInfo, sizeof(dispInfo));
4841 /* if the app stores all the data, handle it separately */
4842 if (infoPtr->dwStyle & LVS_OWNERDATA)
4844 dispInfo.item.state = 0;
4846 /* apprently, we should not callback for lParam in LVS_OWNERDATA */
4847 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
4849 /* NOTE: copy only fields which we _know_ are initialized, some apps
4850 * depend on the uninitialized fields being 0 */
4851 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
4852 dispInfo.item.iItem = lpLVItem->iItem;
4853 dispInfo.item.iSubItem = lpLVItem->iSubItem;
4854 if (lpLVItem->mask & LVIF_TEXT)
4856 dispInfo.item.pszText = lpLVItem->pszText;
4857 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
4859 if (lpLVItem->mask & LVIF_STATE)
4860 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
4861 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
4862 dispInfo.item.stateMask = lpLVItem->stateMask;
4863 *lpLVItem = dispInfo.item;
4864 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
4867 /* make sure lParam is zeroed out */
4868 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
4870 /* we store only a little state, so if we're not asked, we're done */
4871 if (!(lpLVItem->mask & LVIF_STATE) || lpLVItem->iSubItem) return TRUE;
4873 /* if focus is handled by us, report it */
4874 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
4876 lpLVItem->state &= ~LVIS_FOCUSED;
4877 if (infoPtr->nFocusedItem == lpLVItem->iItem)
4878 lpLVItem->state |= LVIS_FOCUSED;
4881 /* and do the same for selection, if we handle it */
4882 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
4884 lpLVItem->state &= ~LVIS_SELECTED;
4885 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
4886 lpLVItem->state |= LVIS_SELECTED;
4892 /* find the item and subitem structures before we proceed */
4893 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4894 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
4897 if (lpLVItem->iSubItem)
4899 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
4900 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
4903 pItemHdr = &lpItem->hdr;
4905 /* Do we need to query the state from the app? */
4906 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && lpLVItem->iSubItem == 0)
4908 dispInfo.item.mask |= LVIF_STATE;
4909 dispInfo.item.stateMask = infoPtr->uCallbackMask;
4912 /* Do we need to enquire about the image? */
4913 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK)
4914 dispInfo.item.mask |= LVIF_IMAGE;
4916 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
4917 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
4919 dispInfo.item.mask |= LVIF_TEXT;
4920 dispInfo.item.pszText = lpLVItem->pszText;
4921 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
4922 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
4923 *dispInfo.item.pszText = '\0';
4926 /* If we don't have all the requested info, query the application */
4927 if (dispInfo.item.mask != 0)
4929 dispInfo.item.iItem = lpLVItem->iItem;
4930 dispInfo.item.iSubItem = lpLVItem->iSubItem;
4931 dispInfo.item.lParam = lpItem->lParam;
4932 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
4933 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
4936 /* we should not store values for subitems */
4937 if (lpLVItem->iSubItem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
4939 /* Now, handle the iImage field */
4940 if (dispInfo.item.mask & LVIF_IMAGE)
4942 lpLVItem->iImage = dispInfo.item.iImage;
4943 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
4944 pItemHdr->iImage = dispInfo.item.iImage;
4946 else if (lpLVItem->mask & LVIF_IMAGE)
4947 lpLVItem->iImage = pItemHdr->iImage;
4949 /* The pszText field */
4950 if (dispInfo.item.mask & LVIF_TEXT)
4952 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
4953 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
4955 lpLVItem->pszText = dispInfo.item.pszText;
4957 else if (lpLVItem->mask & LVIF_TEXT)
4959 if (isW) lpLVItem->pszText = pItemHdr->pszText;
4960 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
4963 /* if this is a subitem, we're done */
4964 if (lpLVItem->iSubItem) return TRUE;
4966 /* Next is the lParam field */
4967 if (dispInfo.item.mask & LVIF_PARAM)
4969 lpLVItem->lParam = dispInfo.item.lParam;
4970 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
4971 lpItem->lParam = dispInfo.item.lParam;
4973 else if (lpLVItem->mask & LVIF_PARAM)
4974 lpLVItem->lParam = lpItem->lParam;
4976 /* ... the state field (this one is different due to uCallbackmask) */
4977 if (lpLVItem->mask & LVIF_STATE)
4979 lpLVItem->state = lpItem->state;
4980 if (dispInfo.item.mask & LVIF_STATE)
4982 lpLVItem->state &= ~dispInfo.item.stateMask;
4983 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
4985 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
4987 lpLVItem->state &= ~LVIS_FOCUSED;
4988 if (infoPtr->nFocusedItem == lpLVItem->iItem)
4989 lpLVItem->state |= LVIS_FOCUSED;
4991 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
4993 lpLVItem->state &= ~LVIS_SELECTED;
4994 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
4995 lpLVItem->state |= LVIS_SELECTED;
4999 /* and last, but not least, the indent field */
5000 if (lpLVItem->mask & LVIF_INDENT)
5001 lpLVItem->iIndent = lpItem->iIndent;
5008 * Retrieves item attributes.
5011 * [I] hwnd : window handle
5012 * [IO] lpLVItem : item info
5013 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5014 * if FALSE, the lpLVItem is a LPLVITEMA.
5017 * This is the external 'GetItem' interface -- it properly copies
5018 * the text in the provided buffer.
5024 static BOOL LISTVIEW_GetItemExtT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5029 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5032 pszText = lpLVItem->pszText;
5033 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5034 if (bResult && lpLVItem->pszText != pszText)
5035 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5036 lpLVItem->pszText = pszText;
5044 * Retrieves the position (upper-left) of the listview control item.
5045 * Note that for LVS_ICON style, the upper-left is that of the icon
5046 * and not the bounding box.
5049 * [I] infoPtr : valid pointer to the listview structure
5050 * [I] nItem : item index
5051 * [O] lpptPosition : coordinate information
5057 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5059 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5062 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5064 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5066 LISTVIEW_GetOrigin(infoPtr, &Origin);
5067 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5069 if (uView == LVS_ICON)
5071 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5072 lpptPosition->y += ICON_TOP_PADDING;
5074 lpptPosition->x += Origin.x;
5075 lpptPosition->y += Origin.y;
5077 TRACE (" lpptPosition=%s\n", debugpoint(lpptPosition));
5084 * Retrieves the bounding rectangle for a listview control item.
5087 * [I] infoPtr : valid pointer to the listview structure
5088 * [I] nItem : item index
5089 * [IO] lprc : bounding rectangle coordinates
5090 * lprc->left specifies the portion of the item for which the bounding
5091 * rectangle will be retrieved.
5093 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5094 * including the icon and label.
5097 * * Experiment shows that native control returns:
5098 * * width = min (48, length of text line)
5099 * * .left = position.x - (width - iconsize.cx)/2
5100 * * .right = .left + width
5101 * * height = #lines of text * ntmHeight + icon height + 8
5102 * * .top = position.y - 2
5103 * * .bottom = .top + height
5104 * * separation between items .y = itemSpacing.cy - height
5105 * * .x = itemSpacing.cx - width
5106 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5109 * * Experiment shows that native control returns:
5110 * * width = iconSize.cx + 16
5111 * * .left = position.x - (width - iconsize.cx)/2
5112 * * .right = .left + width
5113 * * height = iconSize.cy + 4
5114 * * .top = position.y - 2
5115 * * .bottom = .top + height
5116 * * separation between items .y = itemSpacing.cy - height
5117 * * .x = itemSpacing.cx - width
5118 * LVIR_LABEL Returns the bounding rectangle of the item text.
5121 * * Experiment shows that native control returns:
5122 * * width = text length
5123 * * .left = position.x - width/2
5124 * * .right = .left + width
5125 * * height = ntmH * linecount + 2
5126 * * .top = position.y + iconSize.cy + 6
5127 * * .bottom = .top + height
5128 * * separation between items .y = itemSpacing.cy - height
5129 * * .x = itemSpacing.cx - width
5130 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5131 * rectangles, but excludes columns in report view.
5138 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5139 * upon whether the window has the focus currently and on whether the item
5140 * is the one with the focus. Ensure that the control's record of which
5141 * item has the focus agrees with the items' records.
5143 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5145 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5146 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5147 BOOL doLabel = TRUE, oversizedBox = FALSE;
5148 POINT Position, Origin;
5152 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5154 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5156 LISTVIEW_GetOrigin(infoPtr, &Origin);
5157 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5159 /* Be smart and try to figure out the minimum we have to do */
5160 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5161 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5162 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5163 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5164 oversizedBox = TRUE;
5166 /* get what we need from the item before hand, so we make
5167 * only one request. This can speed up things, if data
5168 * is stored on the app side */
5170 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5171 if (doLabel) lvItem.mask |= LVIF_TEXT;
5172 lvItem.iItem = nItem;
5173 lvItem.iSubItem = 0;
5174 lvItem.pszText = szDispText;
5175 lvItem.cchTextMax = DISP_TEXT_SIZE;
5176 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5177 /* we got the state already up, simulate it here, to avoid a reget */
5178 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5180 lvItem.mask |= LVIF_STATE;
5181 lvItem.stateMask = LVIS_FOCUSED;
5182 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5185 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5186 lprc->left = LVIR_BOUNDS;
5190 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5194 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, lprc);
5198 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5201 case LVIR_SELECTBOUNDS:
5202 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, &label_rect);
5203 UnionRect(lprc, lprc, &label_rect);
5207 WARN("Unknown value: %ld\n", lprc->left);
5211 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5213 TRACE(" rect=%s\n", debugrect(lprc));
5220 * Retrieves the spacing between listview control items.
5223 * [I] infoPtr : valid pointer to the listview structure
5224 * [IO] lprc : rectangle to receive the output
5225 * on input, lprc->top = nSubItem
5226 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5228 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5229 * not only those of the first column.
5230 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5236 static BOOL LISTVIEW_GetSubItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5241 if (!lprc || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5243 TRACE("(nItem=%d, nSubItem=%ld)\n", nItem, lprc->top);
5244 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5246 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5248 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5250 lvItem.mask = lprc->top == 0 ? LVIF_INDENT : 0;
5251 lvItem.iItem = nItem;
5252 lvItem.iSubItem = lprc->top;
5254 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5258 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5263 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5267 ERR("Unknown bounds=%ld\n", lprc->left);
5271 OffsetRect(lprc, Position.x, Position.y);
5278 * Retrieves the width of a label.
5281 * [I] infoPtr : valid pointer to the listview structure
5284 * SUCCESS : string width (in pixels)
5287 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *infoPtr, INT nItem)
5289 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5292 TRACE("(nItem=%d)\n", nItem);
5294 lvItem.mask = LVIF_TEXT;
5295 lvItem.iItem = nItem;
5296 lvItem.iSubItem = 0;
5297 lvItem.pszText = szDispText;
5298 lvItem.cchTextMax = DISP_TEXT_SIZE;
5299 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5301 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5306 * Retrieves the spacing between listview control items.
5309 * [I] infoPtr : valid pointer to the listview structure
5310 * [I] bSmall : flag for small or large icon
5313 * Horizontal + vertical spacing
5315 static LONG LISTVIEW_GetItemSpacing(LISTVIEW_INFO *infoPtr, BOOL bSmall)
5321 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5325 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5326 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5328 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5335 * Retrieves the state of a listview control item.
5338 * [I] infoPtr : valid pointer to the listview structure
5339 * [I] nItem : item index
5340 * [I] uMask : state mask
5343 * State specified by the mask.
5345 static UINT LISTVIEW_GetItemState(LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5349 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5351 lvItem.iItem = nItem;
5352 lvItem.iSubItem = 0;
5353 lvItem.mask = LVIF_STATE;
5354 lvItem.stateMask = uMask;
5355 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5357 return lvItem.state & uMask;
5362 * Retrieves the text of a listview control item or subitem.
5365 * [I] hwnd : window handle
5366 * [I] nItem : item index
5367 * [IO] lpLVItem : item information
5368 * [I] isW : TRUE if lpLVItem is Unicode
5371 * SUCCESS : string length
5374 static INT LISTVIEW_GetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
5376 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5378 lpLVItem->mask = LVIF_TEXT;
5379 lpLVItem->iItem = nItem;
5380 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
5382 return textlenT(lpLVItem->pszText, isW);
5387 * Searches for an item based on properties + relationships.
5390 * [I] infoPtr : valid pointer to the listview structure
5391 * [I] nItem : item index
5392 * [I] uFlags : relationship flag
5395 * SUCCESS : item index
5398 static INT LISTVIEW_GetNextItem(LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
5400 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5402 LVFINDINFOW lvFindInfo;
5403 INT nCountPerColumn;
5406 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
5407 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
5409 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
5411 if (uFlags & LVNI_CUT)
5414 if (uFlags & LVNI_DROPHILITED)
5415 uMask |= LVIS_DROPHILITED;
5417 if (uFlags & LVNI_FOCUSED)
5418 uMask |= LVIS_FOCUSED;
5420 if (uFlags & LVNI_SELECTED)
5421 uMask |= LVIS_SELECTED;
5423 /* if we're asked for the focused item, that's only one,
5424 * so it's worth optimizing */
5425 if (uFlags & LVNI_FOCUSED)
5427 if (!(LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) == uMask) return -1;
5428 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
5431 if (uFlags & LVNI_ABOVE)
5433 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5438 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5444 lvFindInfo.flags = LVFI_NEARESTXY;
5445 lvFindInfo.vkDirection = VK_UP;
5446 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5447 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5449 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5454 else if (uFlags & LVNI_BELOW)
5456 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5458 while (nItem < infoPtr->nItemCount)
5461 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5467 lvFindInfo.flags = LVFI_NEARESTXY;
5468 lvFindInfo.vkDirection = VK_DOWN;
5469 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5470 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5472 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5477 else if (uFlags & LVNI_TOLEFT)
5479 if (uView == LVS_LIST)
5481 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5482 while (nItem - nCountPerColumn >= 0)
5484 nItem -= nCountPerColumn;
5485 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5489 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5491 lvFindInfo.flags = LVFI_NEARESTXY;
5492 lvFindInfo.vkDirection = VK_LEFT;
5493 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5494 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5496 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5501 else if (uFlags & LVNI_TORIGHT)
5503 if (uView == LVS_LIST)
5505 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5506 while (nItem + nCountPerColumn < infoPtr->nItemCount)
5508 nItem += nCountPerColumn;
5509 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5513 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5515 lvFindInfo.flags = LVFI_NEARESTXY;
5516 lvFindInfo.vkDirection = VK_RIGHT;
5517 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5518 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5520 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5529 /* search by index */
5530 for (i = nItem; i < infoPtr->nItemCount; i++)
5532 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
5540 /* LISTVIEW_GetNumberOfWorkAreas */
5544 * Retrieves the origin coordinates when in icon or small icon display mode.
5547 * [I] infoPtr : valid pointer to the listview structure
5548 * [O] lpptOrigin : coordinate information
5553 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
5555 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5556 INT nHorzPos = 0, nVertPos = 0;
5557 SCROLLINFO scrollInfo;
5559 scrollInfo.cbSize = sizeof(SCROLLINFO);
5560 scrollInfo.fMask = SIF_POS;
5562 if ((infoPtr->dwStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
5563 nHorzPos = scrollInfo.nPos;
5564 if ((infoPtr->dwStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
5565 nVertPos = scrollInfo.nPos;
5567 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
5569 lpptOrigin->x = infoPtr->rcList.left;
5570 lpptOrigin->y = infoPtr->rcList.top;
5571 if (uView == LVS_LIST)
5572 nHorzPos *= infoPtr->nItemWidth;
5573 else if (uView == LVS_REPORT)
5574 nVertPos *= infoPtr->nItemHeight;
5576 lpptOrigin->x -= nHorzPos;
5577 lpptOrigin->y -= nVertPos;
5579 TRACE(" origin=%s\n", debugpoint(lpptOrigin));
5584 * Retrieves the width of a string.
5587 * [I] hwnd : window handle
5588 * [I] lpszText : text string to process
5589 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
5592 * SUCCESS : string width (in pixels)
5595 static INT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
5600 if (is_textT(lpszText, isW))
5602 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
5603 HDC hdc = GetDC(infoPtr->hwndSelf);
5604 HFONT hOldFont = SelectObject(hdc, hFont);
5607 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
5609 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
5610 SelectObject(hdc, hOldFont);
5611 ReleaseDC(infoPtr->hwndSelf, hdc);
5613 return stringSize.cx;
5618 * Determines which listview item is located at the specified position.
5621 * [I] infoPtr : valid pointer to the listview structure
5622 * [IO] lpht : hit test information
5623 * [I] subitem : fill out iSubItem.
5624 * [I] select : return the index only if the hit selects the item
5627 * (mm 20001022): We must not allow iSubItem to be touched, for
5628 * an app might pass only a structure with space up to iItem!
5629 * (MS Office 97 does that for instance in the file open dialog)
5632 * SUCCESS : item index
5635 static INT LISTVIEW_HitTest(LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
5637 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5638 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5639 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
5640 POINT Origin, Position, opt;
5644 TRACE("(pt=%s, subitem=%d, select=%d)\n", debugpoint(&lpht->pt), subitem, select);
5648 if (subitem) lpht->iSubItem = 0;
5650 if (infoPtr->rcList.left > lpht->pt.x)
5651 lpht->flags |= LVHT_TOLEFT;
5652 else if (infoPtr->rcList.right < lpht->pt.x)
5653 lpht->flags |= LVHT_TORIGHT;
5655 if (infoPtr->rcList.top > lpht->pt.y)
5656 lpht->flags |= LVHT_ABOVE;
5657 else if (infoPtr->rcList.bottom < lpht->pt.y)
5658 lpht->flags |= LVHT_BELOW;
5660 TRACE("lpht->flags=0x%x\n", lpht->flags);
5661 if (lpht->flags) return -1;
5663 lpht->flags |= LVHT_NOWHERE;
5665 LISTVIEW_GetOrigin(infoPtr, &Origin);
5667 /* first deal with the large items */
5668 rcSearch.left = lpht->pt.x;
5669 rcSearch.top = lpht->pt.y;
5670 rcSearch.right = rcSearch.left + 1;
5671 rcSearch.bottom = rcSearch.top + 1;
5673 iterator_frameditems(&i, infoPtr, &rcSearch);
5674 iterator_next(&i); /* go to first item in the sequence */
5675 lpht->iItem = i.nItem;
5676 iterator_destroy(&i);
5678 TRACE("lpht->iItem=%d\n", lpht->iItem);
5679 if (lpht->iItem == -1) return -1;
5681 lvItem.mask = LVIF_STATE | LVIF_TEXT;
5682 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5683 lvItem.stateMask = LVIS_STATEIMAGEMASK;
5684 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
5685 lvItem.iItem = lpht->iItem;
5686 lvItem.iSubItem = 0;
5687 lvItem.pszText = szDispText;
5688 lvItem.cchTextMax = DISP_TEXT_SIZE;
5689 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
5690 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
5692 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
5693 LISTVIEW_GetItemOrigin(infoPtr, lpht->iItem, &Position);
5694 opt.x = lpht->pt.x - Position.x - Origin.x;
5695 opt.y = lpht->pt.y - Position.y - Origin.y;
5697 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
5700 UnionRect(&rcBounds, &rcIcon, &rcLabel);
5701 TRACE("rcBounds=%s\n", debugrect(&rcBounds));
5702 if (!PtInRect(&rcBounds, opt)) return -1;
5704 if (PtInRect(&rcIcon, opt))
5705 lpht->flags |= LVHT_ONITEMICON;
5706 else if (PtInRect(&rcLabel, opt))
5707 lpht->flags |= LVHT_ONITEMLABEL;
5708 else if (infoPtr->himlState && ((lvItem.state & LVIS_STATEIMAGEMASK) >> 12) && PtInRect(&rcState, opt))
5709 lpht->flags |= LVHT_ONITEMSTATEICON;
5710 if (lpht->flags & LVHT_ONITEM)
5711 lpht->flags &= ~LVHT_NOWHERE;
5713 TRACE("lpht->flags=0x%x\n", lpht->flags);
5714 if (uView == LVS_REPORT && lpht->iItem != -1 && subitem)
5718 rcBounds.right = rcBounds.left;
5719 for (j = 0; j < infoPtr->hdpaColumns->nItemCount; j++)
5721 rcBounds.left = rcBounds.right;
5722 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
5723 if (PtInRect(&rcBounds, opt))
5731 if (!select || lpht->iItem == -1) return lpht->iItem;
5733 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)) return lpht->iItem;
5735 if (uView == LVS_REPORT) UnionRect(&rcBounds, &rcIcon, &rcLabel);
5736 return PtInRect(&rcBounds, opt) ? lpht->iItem : -1;
5740 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
5741 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
5742 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
5743 and not during the processing of a LVM_SORTITEMS message. Applications should provide
5744 their own sort proc. when sending LVM_SORTITEMS.
5747 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
5749 LVS_SORTXXX must be specified,
5750 LVS_OWNERDRAW is not set,
5751 <item>.pszText is not LPSTR_TEXTCALLBACK.
5753 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
5754 are sorted based on item text..."
5756 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
5758 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
5759 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
5760 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
5762 /* if we're sorting descending, negate the return value */
5763 return (((LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
5768 * Inserts a new item in the listview control.
5771 * [I] infoPtr : valid pointer to the listview structure
5772 * [I] lpLVItem : item information
5773 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
5776 * SUCCESS : new item index
5779 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
5781 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5786 BOOL is_sorted, has_changed;
5789 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5791 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
5793 /* make sure it's an item, and not a subitem; cannot insert a subitem */
5794 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iSubItem) return -1;
5796 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
5798 if ( !(lpItem = (ITEM_INFO *)COMCTL32_Alloc(sizeof(ITEM_INFO))) )
5801 /* insert item in listview control data structure */
5802 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
5803 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
5805 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
5806 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
5808 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
5809 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
5810 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
5811 if (nItem == -1) goto fail;
5812 infoPtr->nItemCount++;
5814 /* set the item attributes */
5817 item.state &= ~LVIS_STATEIMAGEMASK;
5818 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
5820 /* if we're sorted, sort the list, and update the index */
5823 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
5824 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
5825 assert(nItem != -1);
5828 /* make room for the position, if we are in the right mode */
5829 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5831 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
5833 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
5835 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
5840 /* Add the subitem list to the items array. Do this last in case we go to
5841 * fail during the above.
5843 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
5845 /* send LVN_INSERTITEM notification */
5846 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
5848 nmlv.lParam = lpItem->lParam;
5849 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
5851 /* align items (set position of each item) */
5852 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
5856 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
5857 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
5859 LISTVIEW_NextIconPosTop(infoPtr, &pt);
5861 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
5864 /* now is the invalidation fun */
5865 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
5869 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
5870 infoPtr->nItemCount--;
5872 DPA_DeletePtr(hdpaSubItems, 0);
5873 DPA_Destroy (hdpaSubItems);
5874 COMCTL32_Free (lpItem);
5880 * Redraws a range of items.
5883 * [I] infoPtr : valid pointer to the listview structure
5884 * [I] nFirst : first item
5885 * [I] nLast : last item
5891 static BOOL LISTVIEW_RedrawItems(LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
5895 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
5896 max(nFirst, nLast) >= infoPtr->nItemCount)
5899 for (i = nFirst; i <= nLast; i++)
5900 LISTVIEW_InvalidateItem(infoPtr, i);
5907 * Scroll the content of a listview.
5910 * [I] infoPtr : valid pointer to the listview structure
5911 * [I] dx : horizontal scroll amount in pixels
5912 * [I] dy : vertical scroll amount in pixels
5919 * If the control is in report mode (LVS_REPORT) the control can
5920 * be scrolled only in line increments. "dy" will be rounded to the
5921 * nearest number of pixels that are a whole line. Ex: if line height
5922 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
5923 * is passed the the scroll will be 0. (per MSDN 7/2002)
5925 * For: (per experimentaion with native control and CSpy ListView)
5926 * LVS_ICON dy=1 = 1 pixel (vertical only)
5928 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
5930 * LVS_LIST dx=1 = 1 column (horizontal only)
5931 * but will only scroll 1 column per message
5932 * no matter what the value.
5933 * dy must be 0 or FALSE returned.
5934 * LVS_REPORT dx=1 = 1 pixel
5938 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
5940 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
5942 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
5943 dy /= infoPtr->nItemHeight;
5946 if (dy != 0) return FALSE;
5953 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
5954 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
5961 * Sets the background color.
5964 * [I] infoPtr : valid pointer to the listview structure
5965 * [I] clrBk : background color
5971 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
5973 TRACE("(clrBk=%lx)\n", clrBk);
5975 if(infoPtr->clrBk != clrBk) {
5976 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
5977 infoPtr->clrBk = clrBk;
5978 if (clrBk == CLR_NONE)
5979 infoPtr->hBkBrush = (HBRUSH)GetClassLongW(infoPtr->hwndSelf, GCL_HBRBACKGROUND);
5981 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
5982 LISTVIEW_InvalidateList(infoPtr);
5988 /* LISTVIEW_SetBkImage */
5990 /*** Helper for {Insert,Set}ColumnT *only* */
5991 static void column_fill_hditem(LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn, const LVCOLUMNW *lpColumn, BOOL isW)
5993 if (lpColumn->mask & LVCF_FMT)
5995 /* format member is valid */
5996 lphdi->mask |= HDI_FORMAT;
5998 /* set text alignment (leftmost column must be left-aligned) */
5999 if (nColumn == 0 || lpColumn->fmt & LVCFMT_LEFT)
6000 lphdi->fmt |= HDF_LEFT;
6001 else if (lpColumn->fmt & LVCFMT_RIGHT)
6002 lphdi->fmt |= HDF_RIGHT;
6003 else if (lpColumn->fmt & LVCFMT_CENTER)
6004 lphdi->fmt |= HDF_CENTER;
6006 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6007 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6009 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6011 lphdi->fmt |= HDF_IMAGE;
6012 lphdi->iImage = I_IMAGECALLBACK;
6016 if (lpColumn->mask & LVCF_WIDTH)
6018 lphdi->mask |= HDI_WIDTH;
6019 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6021 /* make it fill the remainder of the controls width */
6025 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6027 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6028 lphdi->cxy += rcHeader.right - rcHeader.left;
6031 /* retrieve the layout of the header */
6032 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6033 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, debugrect(&rcHeader));
6035 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6038 lphdi->cxy = lpColumn->cx;
6041 if (lpColumn->mask & LVCF_TEXT)
6043 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6044 lphdi->fmt |= HDF_STRING;
6045 lphdi->pszText = lpColumn->pszText;
6046 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6049 if (lpColumn->mask & LVCF_IMAGE)
6051 lphdi->mask |= HDI_IMAGE;
6052 lphdi->iImage = lpColumn->iImage;
6055 if (lpColumn->mask & LVCF_ORDER)
6057 lphdi->mask |= HDI_ORDER;
6058 lphdi->iOrder = lpColumn->iOrder;
6065 * Inserts a new column.
6068 * [I] infoPtr : valid pointer to the listview structure
6069 * [I] nColumn : column index
6070 * [I] lpColumn : column information
6071 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6074 * SUCCESS : new column index
6077 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6078 const LVCOLUMNW *lpColumn, BOOL isW)
6080 COLUMN_INFO *lpColumnInfo;
6084 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6086 if (!lpColumn || nColumn < 0) return -1;
6087 nColumn = min(nColumn, infoPtr->hdpaColumns->nItemCount);
6089 ZeroMemory(&hdi, sizeof(HDITEMW));
6090 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6092 /* insert item in header control */
6093 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6094 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6095 (WPARAM)nColumn, (LPARAM)&hdi);
6096 if (nNewColumn == -1) return -1;
6097 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6099 /* create our own column info */
6100 if (!(lpColumnInfo = COMCTL32_Alloc(sizeof(COLUMN_INFO)))) goto fail;
6101 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6103 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6104 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6106 /* now we have to actually adjust the data */
6107 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6109 SUBITEM_INFO *lpSubItem, *lpMainItem, **lpNewItems = 0;
6113 /* preallocate memory, so we can fail gracefully */
6114 if (nNewColumn == 0)
6116 lpNewItems = COMCTL32_Alloc(sizeof(SUBITEM_INFO *) * infoPtr->nItemCount);
6117 if (!lpNewItems) goto fail;
6118 for (i = 0; i < infoPtr->nItemCount; i++)
6119 if (!(lpNewItems[i] = COMCTL32_Alloc(sizeof(SUBITEM_INFO)))) break;
6120 if (i != infoPtr->nItemCount)
6122 for(; i >=0; i--) COMCTL32_Free(lpNewItems[i]);
6123 COMCTL32_Free(lpNewItems);
6128 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6130 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
6131 for (i = 1; i < hdpaSubItems->nItemCount; i++)
6133 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
6134 if (lpSubItem->iSubItem >= nNewColumn)
6135 lpSubItem->iSubItem++;
6138 /* for inserting column 0, we have to special-case the main item */
6139 if (nNewColumn == 0)
6141 lpMainItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
6142 lpSubItem = lpNewItems[nItem];
6143 lpSubItem->hdr = lpMainItem->hdr;
6144 lpSubItem->iSubItem = 1;
6145 ZeroMemory(&lpMainItem->hdr, sizeof(lpMainItem->hdr));
6146 lpMainItem->iSubItem = 0;
6147 DPA_InsertPtr(hdpaSubItems, 1, lpSubItem);
6151 COMCTL32_Free(lpNewItems);
6154 /* make space for the new column */
6155 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6160 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6163 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6164 COMCTL32_Free(lpColumnInfo);
6171 * Sets the attributes of a header item.
6174 * [I] infoPtr : valid pointer to the listview structure
6175 * [I] nColumn : column index
6176 * [I] lpColumn : column attributes
6177 * [I] isW: if TRUE, the lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6183 static BOOL LISTVIEW_SetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6184 const LVCOLUMNW *lpColumn, BOOL isW)
6186 HDITEMW hdi, hdiget;
6189 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6191 if (!lpColumn || nColumn < 0 || nColumn >= infoPtr->hdpaColumns->nItemCount) return FALSE;
6193 ZeroMemory(&hdi, sizeof(HDITEMW));
6194 if (lpColumn->mask & LVCF_FMT)
6196 hdi.mask |= HDI_FORMAT;
6197 hdiget.mask = HDI_FORMAT;
6198 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6199 hdi.fmt = hdiget.fmt & HDF_STRING;
6201 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6203 /* set header item attributes */
6204 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6205 if (!bResult) return FALSE;
6207 if (lpColumn->mask & LVCF_FMT)
6209 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6210 int oldFmt = lpColumnInfo->fmt;
6212 lpColumnInfo->fmt = lpColumn->fmt;
6213 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
6215 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6216 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
6225 * Sets the column order array
6228 * [I] infoPtr : valid pointer to the listview structure
6229 * [I] iCount : number of elements in column order array
6230 * [I] lpiArray : pointer to column order array
6236 static BOOL LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
6238 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6249 * Sets the width of a column
6252 * [I] infoPtr : valid pointer to the listview structure
6253 * [I] nColumn : column index
6254 * [I] cx : column width
6260 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
6262 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6263 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6267 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
6269 /* set column width only if in report or list mode */
6270 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6272 /* take care of invalid cx values */
6273 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
6274 else if (uView == LVS_LIST && cx < 1) return FALSE;
6276 /* resize all columns if in LVS_LIST mode */
6277 if(uView == LVS_LIST)
6279 infoPtr->nItemWidth = cx;
6280 LISTVIEW_InvalidateList(infoPtr);
6284 if (nColumn < 0 || nColumn >= infoPtr->hdpaColumns->nItemCount) return FALSE;
6286 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < infoPtr->hdpaColumns->nItemCount -1))
6291 lvItem.mask = LVIF_TEXT;
6293 lvItem.iSubItem = nColumn;
6294 lvItem.pszText = szDispText;
6295 lvItem.cchTextMax = DISP_TEXT_SIZE;
6296 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6298 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6299 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6300 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
6302 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
6303 max_cx += infoPtr->iconSize.cx;
6304 max_cx += TRAILING_LABEL_PADDING;
6307 /* autosize based on listview items width */
6308 if(cx == LVSCW_AUTOSIZE)
6310 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
6312 /* if iCol is the last column make it fill the remainder of the controls width */
6313 if(nColumn == infoPtr->hdpaColumns->nItemCount - 1)
6318 LISTVIEW_GetOrigin(infoPtr, &Origin);
6319 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
6321 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
6325 /* Despite what the MS docs say, if this is not the last
6326 column, then MS resizes the column to the width of the
6327 largest text string in the column, including headers
6328 and items. This is different from LVSCW_AUTOSIZE in that
6329 LVSCW_AUTOSIZE ignores the header string length. */
6332 /* retrieve header text */
6333 hdi.mask = HDI_TEXT;
6334 hdi.cchTextMax = DISP_TEXT_SIZE;
6335 hdi.pszText = szDispText;
6336 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, (LPARAM)&hdi))
6338 HDC hdc = GetDC(infoPtr->hwndSelf);
6339 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
6342 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
6343 cx = size.cx + TRAILING_HEADER_PADDING;
6344 /* FIXME: Take into account the header image, if one is present */
6345 SelectObject(hdc, old_font);
6346 ReleaseDC(infoPtr->hwndSelf, hdc);
6348 cx = max (cx, max_cx);
6352 if (cx < 0) return FALSE;
6354 /* call header to update the column change */
6355 hdi.mask = HDI_WIDTH;
6357 TRACE("hdi.cxy=%d\n", hdi.cxy);
6358 return Header_SetItemW(infoPtr->hwndHeader, nColumn, (LPARAM)&hdi);
6363 * Sets the extended listview style.
6366 * [I] infoPtr : valid pointer to the listview structure
6368 * [I] dwStyle : style
6371 * SUCCESS : previous style
6374 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwStyle)
6376 DWORD dwOldStyle = infoPtr->dwLvExStyle;
6380 infoPtr->dwLvExStyle = (dwOldStyle & ~dwMask) | (dwStyle & dwMask);
6382 infoPtr->dwLvExStyle = dwStyle;
6389 * Sets the new hot cursor used during hot tracking and hover selection.
6392 * [I] infoPtr : valid pointer to the listview structure
6393 * [I} hCurosr : the new hot cursor handle
6396 * Returns the previous hot cursor
6398 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
6400 HCURSOR oldCursor = infoPtr->hHotCursor;
6402 infoPtr->hHotCursor = hCursor;
6410 * Sets the hot item index.
6413 * [I] infoPtr : valid pointer to the listview structure
6414 * [I] iIndex : index
6417 * SUCCESS : previous hot item index
6418 * FAILURE : -1 (no hot item)
6420 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
6422 INT iOldIndex = infoPtr->nHotItem;
6424 infoPtr->nHotItem = iIndex;
6432 * Sets the amount of time the cursor must hover over an item before it is selected.
6435 * [I] infoPtr : valid pointer to the listview structure
6436 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
6439 * Returns the previous hover time
6441 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
6443 DWORD oldHoverTime = infoPtr->dwHoverTime;
6445 infoPtr->dwHoverTime = dwHoverTime;
6447 return oldHoverTime;
6452 * Sets spacing for icons of LVS_ICON style.
6455 * [I] infoPtr : valid pointer to the listview structure
6456 * [I] spacing : MAKELONG(cx, cy)
6459 * MAKELONG(oldcx, oldcy)
6461 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, DWORD spacing)
6463 INT cy = HIWORD(spacing), cx = LOWORD(spacing);
6464 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6465 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6467 TRACE("requested=(%d,%d)\n", cx, cy);
6469 /* this is supported only for LVS_ICON style */
6470 if (uView != LVS_ICON) return oldspacing;
6472 /* set to defaults, if instructed to */
6473 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
6474 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
6476 /* if 0 then compute width
6477 * FIXME: Should scan each item and determine max width of
6478 * icon or label, then make that the width */
6480 cx = infoPtr->iconSpacing.cx;
6482 /* if 0 then compute height */
6484 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
6485 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
6488 infoPtr->iconSpacing.cx = cx;
6489 infoPtr->iconSpacing.cy = cy;
6491 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%ld,%ld), ntmH=%d\n",
6492 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
6493 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
6494 infoPtr->ntmHeight);
6496 /* these depend on the iconSpacing */
6497 LISTVIEW_UpdateItemSize(infoPtr);
6502 inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
6506 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
6513 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
6514 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
6523 * [I] infoPtr : valid pointer to the listview structure
6524 * [I] nType : image list type
6525 * [I] himl : image list handle
6528 * SUCCESS : old image list
6531 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
6533 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6534 INT oldHeight = infoPtr->nItemHeight;
6535 HIMAGELIST himlOld = 0;
6537 TRACE("(nType=%d, himl=%p\n", nType, himl);
6542 himlOld = infoPtr->himlNormal;
6543 infoPtr->himlNormal = himl;
6544 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
6545 LISTVIEW_SetIconSpacing(infoPtr, 0);
6549 himlOld = infoPtr->himlSmall;
6550 infoPtr->himlSmall = himl;
6551 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
6555 himlOld = infoPtr->himlState;
6556 infoPtr->himlState = himl;
6557 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
6558 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
6562 ERR("Unknown icon type=%d\n", nType);
6566 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
6567 if (infoPtr->nItemHeight != oldHeight)
6568 LISTVIEW_UpdateScroll(infoPtr);
6575 * Preallocates memory (does *not* set the actual count of items !)
6578 * [I] infoPtr : valid pointer to the listview structure
6579 * [I] nItems : item count (projected number of items to allocate)
6580 * [I] dwFlags : update flags
6586 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
6588 TRACE("(nItems=%d, dwFlags=%lx)\n", nItems, dwFlags);
6590 if (infoPtr->dwStyle & LVS_OWNERDATA)
6592 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6593 INT nOldCount = infoPtr->nItemCount;
6595 if (nItems < nOldCount)
6597 RANGE range = { nItems, nOldCount };
6598 ranges_del(infoPtr->selectionRanges, range);
6599 if (infoPtr->nFocusedItem >= nItems)
6601 infoPtr->nFocusedItem = -1;
6602 SetRectEmpty(&infoPtr->rcFocus);
6606 infoPtr->nItemCount = nItems;
6607 LISTVIEW_UpdateScroll(infoPtr);
6609 /* the flags are valid only in ownerdata report and list modes */
6610 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
6612 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
6613 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
6615 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
6616 LISTVIEW_InvalidateList(infoPtr);
6623 LISTVIEW_GetOrigin(infoPtr, &Origin);
6624 nFrom = min(nOldCount, nItems);
6625 nTo = max(nOldCount, nItems);
6627 if (uView == LVS_REPORT)
6630 rcErase.top = nFrom * infoPtr->nItemHeight;
6631 rcErase.right = infoPtr->nItemWidth;
6632 rcErase.bottom = nTo * infoPtr->nItemHeight;
6633 OffsetRect(&rcErase, Origin.x, Origin.y);
6634 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6635 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6639 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
6641 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
6642 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
6643 rcErase.right = rcErase.left + infoPtr->nItemWidth;
6644 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
6645 OffsetRect(&rcErase, Origin.x, Origin.y);
6646 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6647 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6649 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
6651 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
6652 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
6653 OffsetRect(&rcErase, Origin.x, Origin.y);
6654 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6655 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6661 /* According to MSDN for non-LVS_OWNERDATA this is just
6662 * a performance issue. The control allocates its internal
6663 * data structures for the number of items specified. It
6664 * cuts down on the number of memory allocations. Therefore
6665 * we will just issue a WARN here
6667 WARN("for non-ownerdata performance option not implemented.\n");
6675 * Sets the position of an item.
6678 * [I] infoPtr : valid pointer to the listview structure
6679 * [I] nItem : item index
6680 * [I] pt : coordinate
6686 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
6688 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6691 TRACE("(nItem=%d, &pt=%s\n", nItem, debugpoint(&pt));
6693 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
6694 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
6696 LISTVIEW_GetOrigin(infoPtr, &Origin);
6698 /* This point value seems to be an undocumented feature.
6699 * The best guess is that it means either at the origin,
6700 * or at true beginning of the list. I will assume the origin. */
6701 if ((pt.x == -1) && (pt.y == -1))
6704 if (uView == LVS_ICON)
6706 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
6707 pt.y -= ICON_TOP_PADDING;
6712 infoPtr->bAutoarrange = FALSE;
6714 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
6719 * Sets the state of one or many items.
6722 * [I] infoPtr : valid pointer to the listview structure
6723 * [I] nItem : item index
6724 * [I] lpLVItem : item or subitem info
6730 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
6732 BOOL bResult = TRUE;
6735 lvItem.iItem = nItem;
6736 lvItem.iSubItem = 0;
6737 lvItem.mask = LVIF_STATE;
6738 lvItem.state = lpLVItem->state;
6739 lvItem.stateMask = lpLVItem->stateMask;
6740 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
6744 /* apply to all items */
6745 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6746 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
6749 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
6756 * Sets the text of an item or subitem.
6759 * [I] hwnd : window handle
6760 * [I] nItem : item index
6761 * [I] lpLVItem : item or subitem info
6762 * [I] isW : TRUE if input is Unicode
6768 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
6772 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
6774 lvItem.iItem = nItem;
6775 lvItem.iSubItem = lpLVItem->iSubItem;
6776 lvItem.mask = LVIF_TEXT;
6777 lvItem.pszText = lpLVItem->pszText;
6778 lvItem.cchTextMax = lpLVItem->cchTextMax;
6780 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
6782 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
6787 * Set item index that marks the start of a multiple selection.
6790 * [I] infoPtr : valid pointer to the listview structure
6791 * [I] nIndex : index
6794 * Index number or -1 if there is no selection mark.
6796 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
6798 INT nOldIndex = infoPtr->nSelectionMark;
6800 TRACE("(nIndex=%d)\n", nIndex);
6802 infoPtr->nSelectionMark = nIndex;
6809 * Sets the text background color.
6812 * [I] infoPtr : valid pointer to the listview structure
6813 * [I] clrTextBk : text background color
6819 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
6821 TRACE("(clrTextBk=%lx)\n", clrTextBk);
6823 if (infoPtr->clrTextBk != clrTextBk)
6825 infoPtr->clrTextBk = clrTextBk;
6826 LISTVIEW_InvalidateList(infoPtr);
6834 * Sets the text foreground color.
6837 * [I] infoPtr : valid pointer to the listview structure
6838 * [I] clrText : text color
6844 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
6846 TRACE("(clrText=%lx)\n", clrText);
6848 if (infoPtr->clrText != clrText)
6850 infoPtr->clrText = clrText;
6851 LISTVIEW_InvalidateList(infoPtr);
6857 /* LISTVIEW_SetToolTips */
6858 /* LISTVIEW_SetUnicodeFormat */
6859 /* LISTVIEW_SetWorkAreas */
6863 * Callback internally used by LISTVIEW_SortItems()
6866 * [I] first : pointer to first ITEM_INFO to compare
6867 * [I] second : pointer to second ITEM_INFO to compare
6868 * [I] lParam : HWND of control
6871 * if first comes before second : negative
6872 * if first comes after second : positive
6873 * if first and second are equivalent : zero
6875 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
6877 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW((HWND)lParam, 0);
6878 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
6879 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
6881 /* Forward the call to the client defined callback */
6882 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
6887 * Sorts the listview items.
6890 * [I] infoPtr : valid pointer to the listview structure
6891 * [I] pfnCompare : application-defined value
6892 * [I] lParamSort : pointer to comparision callback
6898 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
6900 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6903 LPVOID selectionMarkItem;
6907 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
6909 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
6911 if (!infoPtr->hdpaItems) return FALSE;
6913 /* if there are 0 or 1 items, there is no need to sort */
6914 if (infoPtr->nItemCount < 2) return TRUE;
6916 if (infoPtr->nFocusedItem >= 0)
6918 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
6919 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
6920 if (lpItem) lpItem->state |= LVIS_FOCUSED;
6922 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
6923 /* clear the lpItem->state for non-selected ones */
6924 /* remove the selection ranges */
6926 infoPtr->pfnCompare = pfnCompare;
6927 infoPtr->lParamSort = lParamSort;
6928 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr->hwndSelf);
6930 /* Adjust selections and indices so that they are the way they should
6931 * be after the sort (otherwise, the list items move around, but
6932 * whatever is at the item's previous original position will be
6935 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
6936 for (i=0; i < infoPtr->nItemCount; i++)
6938 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
6939 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
6941 if (lpItem->state & LVIS_SELECTED)
6943 item.state = LVIS_SELECTED;
6944 item.stateMask = LVIS_SELECTED;
6945 LISTVIEW_SetItemState(infoPtr, i, &item);
6947 if (lpItem->state & LVIS_FOCUSED)
6949 infoPtr->nFocusedItem = i;
6950 lpItem->state &= ~LVIS_FOCUSED;
6953 if (selectionMarkItem != NULL)
6954 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
6955 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
6957 /* refresh the display */
6958 if (uView != LVS_ICON && uView != LVS_SMALLICON)
6959 LISTVIEW_InvalidateList(infoPtr);
6966 * Updates an items or rearranges the listview control.
6969 * [I] infoPtr : valid pointer to the listview structure
6970 * [I] nItem : item index
6976 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
6978 TRACE("(nItem=%d)\n", nItem);
6980 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
6982 /* rearrange with default alignment style */
6983 if (is_autoarrange(infoPtr))
6984 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
6986 LISTVIEW_InvalidateItem(infoPtr, nItem);
6994 * Creates the listview control.
6997 * [I] hwnd : window handle
6998 * [I] lpcs : the create parameters
7004 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
7006 LISTVIEW_INFO *infoPtr;
7007 UINT uView = lpcs->style & LVS_TYPEMASK;
7010 TRACE("(lpcs=%p)\n", lpcs);
7012 /* initialize info pointer */
7013 infoPtr = (LISTVIEW_INFO *)COMCTL32_Alloc(sizeof(LISTVIEW_INFO));
7014 if (!infoPtr) return -1;
7016 SetWindowLongW(hwnd, 0, (LONG)infoPtr);
7018 infoPtr->hwndSelf = hwnd;
7019 infoPtr->dwStyle = lpcs->style;
7020 /* determine the type of structures to use */
7021 infoPtr->notifyFormat = SendMessageW(GetParent(infoPtr->hwndSelf), WM_NOTIFYFORMAT,
7022 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
7024 /* initialize color information */
7025 infoPtr->clrBk = CLR_NONE;
7026 infoPtr->clrText = comctl32_color.clrWindowText;
7027 infoPtr->clrTextBk = CLR_DEFAULT;
7028 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
7030 /* set default values */
7031 infoPtr->nFocusedItem = -1;
7032 infoPtr->nSelectionMark = -1;
7033 infoPtr->nHotItem = -1;
7034 infoPtr->bRedraw = TRUE;
7035 infoPtr->bFirstPaint = TRUE;
7036 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
7037 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
7038 infoPtr->nEditLabelItem = -1;
7039 infoPtr->dwHoverTime = -1; /* default system hover time */
7041 /* get default font (icon title) */
7042 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
7043 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
7044 infoPtr->hFont = infoPtr->hDefaultFont;
7045 LISTVIEW_SaveTextMetrics(infoPtr);
7048 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, (LPCWSTR)NULL,
7049 WS_CHILD | HDS_HORZ | (DWORD)((LVS_NOSORTHEADER & lpcs->style)?0:HDS_BUTTONS),
7050 0, 0, 0, 0, hwnd, NULL,
7051 lpcs->hInstance, NULL);
7052 if (!infoPtr->hwndHeader) goto fail;
7054 /* set header unicode format */
7055 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)NULL);
7057 /* set header font */
7058 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
7060 /* allocate memory for the data structure */
7061 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
7062 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
7063 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
7064 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
7065 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
7067 /* initialize the icon sizes */
7068 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
7069 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
7071 /* init item size to avoid division by 0 */
7072 LISTVIEW_UpdateItemSize (infoPtr);
7074 if (uView == LVS_REPORT)
7076 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
7078 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
7082 /* set HDS_HIDDEN flag to hide the header bar */
7083 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE,
7084 GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE) | HDS_HIDDEN);
7091 DestroyWindow(infoPtr->hwndHeader);
7092 ranges_destroy(infoPtr->selectionRanges);
7093 DPA_Destroy(infoPtr->hdpaItems);
7094 DPA_Destroy(infoPtr->hdpaPosX);
7095 DPA_Destroy(infoPtr->hdpaPosY);
7096 DPA_Destroy(infoPtr->hdpaColumns);
7097 COMCTL32_Free(infoPtr);
7103 * Erases the background of the listview control.
7106 * [I] infoPtr : valid pointer to the listview structure
7107 * [I] hdc : device context handle
7113 static inline BOOL LISTVIEW_EraseBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc)
7117 TRACE("(hdc=%p)\n", hdc);
7119 if (!GetClipBox(hdc, &rc)) return FALSE;
7121 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
7127 * Helper function for LISTVIEW_[HV]Scroll *only*.
7128 * Performs vertical/horizontal scrolling by a give amount.
7131 * [I] infoPtr : valid pointer to the listview structure
7132 * [I] dx : amount of horizontal scroll
7133 * [I] dy : amount of vertical scroll
7135 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7137 /* now we can scroll the list */
7138 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
7139 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
7140 /* if we have focus, adjust rect */
7141 OffsetRect(&infoPtr->rcFocus, dx, dy);
7142 UpdateWindow(infoPtr->hwndSelf);
7147 * Performs vertical scrolling.
7150 * [I] infoPtr : valid pointer to the listview structure
7151 * [I] nScrollCode : scroll code
7152 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7153 * [I] hScrollWnd : scrollbar control window handle
7159 * SB_LINEUP/SB_LINEDOWN:
7160 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
7161 * for LVS_REPORT is 1 line
7162 * for LVS_LIST cannot occur
7165 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7166 INT nScrollDiff, HWND hScrollWnd)
7168 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7169 INT nOldScrollPos, nNewScrollPos;
7170 SCROLLINFO scrollInfo;
7173 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
7174 debugscrollcode(nScrollCode), nScrollDiff);
7176 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7178 scrollInfo.cbSize = sizeof(SCROLLINFO);
7179 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7181 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
7183 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
7185 nOldScrollPos = scrollInfo.nPos;
7186 switch (nScrollCode)
7192 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
7196 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
7200 nScrollDiff = -scrollInfo.nPage;
7204 nScrollDiff = scrollInfo.nPage;
7207 case SB_THUMBPOSITION:
7209 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7216 /* quit right away if pos isn't changing */
7217 if (nScrollDiff == 0) return 0;
7219 /* calculate new position, and handle overflows */
7220 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7221 if (nScrollDiff > 0) {
7222 if (nNewScrollPos < nOldScrollPos ||
7223 nNewScrollPos > scrollInfo.nMax)
7224 nNewScrollPos = scrollInfo.nMax;
7226 if (nNewScrollPos > nOldScrollPos ||
7227 nNewScrollPos < scrollInfo.nMin)
7228 nNewScrollPos = scrollInfo.nMin;
7231 /* set the new position, and reread in case it changed */
7232 scrollInfo.fMask = SIF_POS;
7233 scrollInfo.nPos = nNewScrollPos;
7234 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
7236 /* carry on only if it really changed */
7237 if (nNewScrollPos == nOldScrollPos) return 0;
7239 /* now adjust to client coordinates */
7240 nScrollDiff = nOldScrollPos - nNewScrollPos;
7241 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
7243 /* and scroll the window */
7244 scroll_list(infoPtr, 0, nScrollDiff);
7251 * Performs horizontal scrolling.
7254 * [I] infoPtr : valid pointer to the listview structure
7255 * [I] nScrollCode : scroll code
7256 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7257 * [I] hScrollWnd : scrollbar control window handle
7263 * SB_LINELEFT/SB_LINERIGHT:
7264 * for LVS_ICON, LVS_SMALLICON 1 pixel
7265 * for LVS_REPORT is 1 pixel
7266 * for LVS_LIST is 1 column --> which is a 1 because the
7267 * scroll is based on columns not pixels
7270 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7271 INT nScrollDiff, HWND hScrollWnd)
7273 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7274 INT nOldScrollPos, nNewScrollPos;
7275 SCROLLINFO scrollInfo;
7277 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
7278 debugscrollcode(nScrollCode), nScrollDiff);
7280 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7282 scrollInfo.cbSize = sizeof(SCROLLINFO);
7283 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7285 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
7287 nOldScrollPos = scrollInfo.nPos;
7289 switch (nScrollCode)
7303 nScrollDiff = -scrollInfo.nPage;
7307 nScrollDiff = scrollInfo.nPage;
7310 case SB_THUMBPOSITION:
7312 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7319 /* quit right away if pos isn't changing */
7320 if (nScrollDiff == 0) return 0;
7322 /* calculate new position, and handle overflows */
7323 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7324 if (nScrollDiff > 0) {
7325 if (nNewScrollPos < nOldScrollPos ||
7326 nNewScrollPos > scrollInfo.nMax)
7327 nNewScrollPos = scrollInfo.nMax;
7329 if (nNewScrollPos > nOldScrollPos ||
7330 nNewScrollPos < scrollInfo.nMin)
7331 nNewScrollPos = scrollInfo.nMin;
7334 /* set the new position, and reread in case it changed */
7335 scrollInfo.fMask = SIF_POS;
7336 scrollInfo.nPos = nNewScrollPos;
7337 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
7339 /* carry on only if it really changed */
7340 if (nNewScrollPos == nOldScrollPos) return 0;
7342 if(uView == LVS_REPORT)
7343 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
7345 /* now adjust to client coordinates */
7346 nScrollDiff = nOldScrollPos - nNewScrollPos;
7347 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
7349 /* and scroll the window */
7350 scroll_list(infoPtr, nScrollDiff, 0);
7355 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
7357 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7358 INT gcWheelDelta = 0;
7359 UINT pulScrollLines = 3;
7360 SCROLLINFO scrollInfo;
7362 TRACE("(wheelDelta=%d)\n", wheelDelta);
7364 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
7365 gcWheelDelta -= wheelDelta;
7367 scrollInfo.cbSize = sizeof(SCROLLINFO);
7368 scrollInfo.fMask = SIF_POS;
7375 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
7376 * should be fixed in the future.
7378 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
7379 LISTVIEW_SCROLL_ICON_LINE_SIZE : -LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
7383 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
7385 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
7386 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
7387 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
7392 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
7403 * [I] infoPtr : valid pointer to the listview structure
7404 * [I] nVirtualKey : virtual key
7405 * [I] lKeyData : key data
7410 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
7412 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7414 NMLVKEYDOWN nmKeyDown;
7416 TRACE("(nVirtualKey=%d, lKeyData=%ld)\n", nVirtualKey, lKeyData);
7418 /* send LVN_KEYDOWN notification */
7419 nmKeyDown.wVKey = nVirtualKey;
7420 nmKeyDown.flags = 0;
7421 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
7423 switch (nVirtualKey)
7426 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
7428 notify(infoPtr, NM_RETURN);
7429 notify(infoPtr, LVN_ITEMACTIVATE);
7434 if (infoPtr->nItemCount > 0)
7439 if (infoPtr->nItemCount > 0)
7440 nItem = infoPtr->nItemCount - 1;
7444 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
7448 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
7452 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
7456 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
7460 if (uView == LVS_REPORT)
7461 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr);
7463 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
7464 * LISTVIEW_GetCountPerRow(infoPtr);
7465 if(nItem < 0) nItem = 0;
7469 if (uView == LVS_REPORT)
7470 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr);
7472 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
7473 * LISTVIEW_GetCountPerRow(infoPtr);
7474 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
7478 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem))
7479 LISTVIEW_KeySelection(infoPtr, nItem);
7489 * [I] infoPtr : valid pointer to the listview structure
7494 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
7498 /* if we did not have the focus, there's nothing to do */
7499 if (!infoPtr->bFocus) return 0;
7501 /* send NM_KILLFOCUS notification */
7502 notify(infoPtr, NM_KILLFOCUS);
7504 /* if we have a focus rectagle, get rid of it */
7505 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
7507 /* set window focus flag */
7508 infoPtr->bFocus = FALSE;
7510 /* invalidate the selected items before reseting focus flag */
7511 LISTVIEW_InvalidateSelectedItems(infoPtr);
7518 * Processes double click messages (left mouse button).
7521 * [I] infoPtr : valid pointer to the listview structure
7522 * [I] wKey : key flag
7523 * [I] pts : mouse coordinate
7528 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7530 LVHITTESTINFO htInfo;
7532 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7534 /* send NM_RELEASEDCAPTURE notification */
7535 notify(infoPtr, NM_RELEASEDCAPTURE);
7537 htInfo.pt.x = pts.x;
7538 htInfo.pt.y = pts.y;
7540 /* send NM_DBLCLK notification */
7541 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
7542 notify_click(infoPtr, NM_DBLCLK, &htInfo);
7544 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
7545 if(htInfo.iItem != -1) notify(infoPtr, LVN_ITEMACTIVATE);
7552 * Processes mouse down messages (left mouse button).
7555 * [I] infoPtr : valid pointer to the listview structure
7556 * [I] wKey : key flag
7557 * [I] pts : mouse coordinate
7562 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7564 LVHITTESTINFO lvHitTestInfo;
7565 static BOOL bGroupSelect = TRUE;
7566 POINT pt = { pts.x, pts.y };
7569 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7571 /* send NM_RELEASEDCAPTURE notification */
7572 notify(infoPtr, NM_RELEASEDCAPTURE);
7574 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
7576 /* set left button down flag */
7577 infoPtr->bLButtonDown = TRUE;
7579 lvHitTestInfo.pt.x = pts.x;
7580 lvHitTestInfo.pt.y = pts.y;
7582 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
7583 TRACE("at %s, nItem=%d\n", debugpoint(&pt), nItem);
7584 infoPtr->nEditLabelItem = -1;
7585 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
7587 if (infoPtr->dwStyle & LVS_SINGLESEL)
7589 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
7590 infoPtr->nEditLabelItem = nItem;
7592 LISTVIEW_SetSelection(infoPtr, nItem);
7596 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
7600 LISTVIEW_AddGroupSelection(infoPtr, nItem);
7601 LISTVIEW_SetItemFocus(infoPtr, nItem);
7602 infoPtr->nSelectionMark = nItem;
7608 item.state = LVIS_SELECTED | LVIS_FOCUSED;
7609 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
7611 LISTVIEW_SetItemState(infoPtr,nItem,&item);
7612 infoPtr->nSelectionMark = nItem;
7615 else if (wKey & MK_CONTROL)
7619 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
7621 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
7622 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
7623 LISTVIEW_SetItemState(infoPtr, nItem, &item);
7624 infoPtr->nSelectionMark = nItem;
7626 else if (wKey & MK_SHIFT)
7628 LISTVIEW_SetGroupSelection(infoPtr, nItem);
7632 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
7633 infoPtr->nEditLabelItem = nItem;
7635 /* set selection (clears other pre-existing selections) */
7636 LISTVIEW_SetSelection(infoPtr, nItem);
7642 /* remove all selections */
7643 LISTVIEW_DeselectAll(infoPtr);
7651 * Processes mouse up messages (left mouse button).
7654 * [I] infoPtr : valid pointer to the listview structure
7655 * [I] wKey : key flag
7656 * [I] pts : mouse coordinate
7661 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7663 LVHITTESTINFO lvHitTestInfo;
7665 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7667 if (!infoPtr->bLButtonDown) return 0;
7669 lvHitTestInfo.pt.x = pts.x;
7670 lvHitTestInfo.pt.y = pts.y;
7672 /* send NM_CLICK notification */
7673 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
7674 notify_click(infoPtr, NM_CLICK, &lvHitTestInfo);
7676 /* set left button flag */
7677 infoPtr->bLButtonDown = FALSE;
7679 /* if we clicked on a selected item, edit the label */
7680 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
7681 LISTVIEW_EditLabelT(infoPtr, lvHitTestInfo.iItem, TRUE);
7688 * Destroys the listview control (called after WM_DESTROY).
7691 * [I] infoPtr : valid pointer to the listview structure
7696 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
7700 /* delete all items */
7701 LISTVIEW_DeleteAllItems(infoPtr);
7703 /* destroy data structure */
7704 DPA_Destroy(infoPtr->hdpaItems);
7705 DPA_Destroy(infoPtr->hdpaPosX);
7706 DPA_Destroy(infoPtr->hdpaPosY);
7707 DPA_Destroy(infoPtr->hdpaColumns);
7708 ranges_destroy(infoPtr->selectionRanges);
7710 /* destroy image lists */
7711 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
7713 if (infoPtr->himlNormal)
7714 ImageList_Destroy(infoPtr->himlNormal);
7715 if (infoPtr->himlSmall)
7716 ImageList_Destroy(infoPtr->himlSmall);
7717 if (infoPtr->himlState)
7718 ImageList_Destroy(infoPtr->himlState);
7721 /* destroy font, bkgnd brush */
7723 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
7724 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
7726 /* free listview info pointer*/
7727 COMCTL32_Free(infoPtr);
7729 SetWindowLongW(infoPtr->hwndSelf, 0, 0);
7735 * Handles notifications from header.
7738 * [I] infoPtr : valid pointer to the listview structure
7739 * [I] nCtrlId : control identifier
7740 * [I] lpnmh : notification information
7745 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
7747 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7749 TRACE("(lpnmh=%p)\n", lpnmh);
7751 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= infoPtr->hdpaColumns->nItemCount) return 0;
7753 switch (lpnmh->hdr.code)
7757 case HDN_ITEMCHANGEDW:
7758 case HDN_ITEMCHANGEDA:
7760 COLUMN_INFO *lpColumnInfo;
7763 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
7767 hdi.mask = HDI_WIDTH;
7768 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, (LPARAM)&hdi)) return 0;
7772 cxy = lpnmh->pitem->cxy;
7774 /* determine how much we change since the last know position */
7775 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
7776 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
7779 RECT rcCol = lpColumnInfo->rcHeader;
7781 lpColumnInfo->rcHeader.right += dx;
7782 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
7783 if (uView == LVS_REPORT && is_redrawing(infoPtr))
7785 /* this trick works for left aligned columns only */
7786 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
7788 rcCol.right = min (rcCol.right, lpColumnInfo->rcHeader.right);
7789 rcCol.left = max (rcCol.left, rcCol.right - 3 * infoPtr->ntmAveCharWidth);
7791 rcCol.top = infoPtr->rcList.top;
7792 rcCol.bottom = infoPtr->rcList.bottom;
7793 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
7799 case HDN_ITEMCLICKW:
7800 case HDN_ITEMCLICKA:
7802 /* Handle sorting by Header Column */
7805 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
7807 nmlv.iSubItem = lpnmh->iItem;
7808 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
7818 * Determines the type of structure to use.
7821 * [I] infoPtr : valid pointer to the listview structureof the sender
7822 * [I] hwndFrom : listview window handle
7823 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
7828 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
7830 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
7832 if (nCommand != NF_REQUERY) return 0;
7834 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
7841 * Paints/Repaints the listview control.
7844 * [I] infoPtr : valid pointer to the listview structure
7845 * [I] hdc : device context handle
7850 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
7852 TRACE("(hdc=%p)\n", hdc);
7854 if (infoPtr->bFirstPaint)
7856 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7858 infoPtr->bFirstPaint = FALSE;
7859 LISTVIEW_UpdateItemSize(infoPtr);
7860 if (uView == LVS_ICON || uView == LVS_SMALLICON)
7861 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7862 LISTVIEW_UpdateScroll(infoPtr);
7865 LISTVIEW_Refresh(infoPtr, hdc);
7870 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
7872 if (ps.fErase) LISTVIEW_FillBkgnd(infoPtr, hdc, &ps.rcPaint);
7873 LISTVIEW_Refresh(infoPtr, hdc);
7874 EndPaint(infoPtr->hwndSelf, &ps);
7882 * Processes double click messages (right mouse button).
7885 * [I] infoPtr : valid pointer to the listview structure
7886 * [I] wKey : key flag
7887 * [I] pts : mouse coordinate
7892 static LRESULT LISTVIEW_RButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7894 LVHITTESTINFO lvHitTestInfo;
7896 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
7898 /* send NM_RELEASEDCAPTURE notification */
7899 notify(infoPtr, NM_RELEASEDCAPTURE);
7901 /* send NM_RDBLCLK notification */
7902 lvHitTestInfo.pt.x = pts.x;
7903 lvHitTestInfo.pt.y = pts.y;
7904 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
7905 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
7912 * Processes mouse down messages (right mouse button).
7915 * [I] infoPtr : valid pointer to the listview structure
7916 * [I] wKey : key flag
7917 * [I] pts : mouse coordinate
7922 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7924 LVHITTESTINFO lvHitTestInfo;
7927 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
7929 /* send NM_RELEASEDCAPTURE notification */
7930 notify(infoPtr, NM_RELEASEDCAPTURE);
7932 /* make sure the listview control window has the focus */
7933 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
7935 /* set right button down flag */
7936 infoPtr->bRButtonDown = TRUE;
7938 /* determine the index of the selected item */
7939 lvHitTestInfo.pt.x = pts.x;
7940 lvHitTestInfo.pt.y = pts.y;
7941 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
7943 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
7945 LISTVIEW_SetItemFocus(infoPtr, nItem);
7946 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
7947 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
7948 LISTVIEW_SetSelection(infoPtr, nItem);
7952 LISTVIEW_DeselectAll(infoPtr);
7960 * Processes mouse up messages (right mouse button).
7963 * [I] infoPtr : valid pointer to the listview structure
7964 * [I] wKey : key flag
7965 * [I] pts : mouse coordinate
7970 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7972 LVHITTESTINFO lvHitTestInfo;
7975 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
7977 if (!infoPtr->bRButtonDown) return 0;
7979 /* set button flag */
7980 infoPtr->bRButtonDown = FALSE;
7982 /* Send NM_RClICK notification */
7983 lvHitTestInfo.pt.x = pts.x;
7984 lvHitTestInfo.pt.y = pts.y;
7985 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
7986 notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo);
7988 /* Change to screen coordinate for WM_CONTEXTMENU */
7989 pt = lvHitTestInfo.pt;
7990 ClientToScreen(infoPtr->hwndSelf, &pt);
7992 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
7993 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
7994 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
8005 * [I] infoPtr : valid pointer to the listview structure
8006 * [I] hwnd : window handle of window containing the cursor
8007 * [I] nHittest : hit-test code
8008 * [I] wMouseMsg : ideintifier of the mouse message
8011 * TRUE if cursor is set
8014 static BOOL LISTVIEW_SetCursor(LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
8016 LVHITTESTINFO lvHitTestInfo;
8018 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
8020 if(!infoPtr->hHotCursor) return FALSE;
8022 GetCursorPos(&lvHitTestInfo.pt);
8023 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
8025 SetCursor(infoPtr->hHotCursor);
8035 * [I] infoPtr : valid pointer to the listview structure
8036 * [I] hwndLoseFocus : handle of previously focused window
8041 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
8043 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
8045 /* if we have the focus already, there's nothing to do */
8046 if (infoPtr->bFocus) return 0;
8048 /* send NM_SETFOCUS notification */
8049 notify(infoPtr, NM_SETFOCUS);
8051 /* set window focus flag */
8052 infoPtr->bFocus = TRUE;
8054 /* put the focus rect back on */
8055 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
8057 /* redraw all visible selected items */
8058 LISTVIEW_InvalidateSelectedItems(infoPtr);
8068 * [I] infoPtr : valid pointer to the listview structure
8069 * [I] fRedraw : font handle
8070 * [I] fRedraw : redraw flag
8075 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
8077 HFONT oldFont = infoPtr->hFont;
8079 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
8081 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
8082 if (infoPtr->hFont == oldFont) return 0;
8084 LISTVIEW_SaveTextMetrics(infoPtr);
8086 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
8087 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
8089 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
8096 * Message handling for WM_SETREDRAW.
8097 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
8100 * [I] infoPtr : valid pointer to the listview structure
8101 * [I] bRedraw: state of redraw flag
8104 * DefWinProc return value
8106 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
8108 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
8110 /* we can not use straight equality here because _any_ non-zero value is TRUE */
8111 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
8113 infoPtr->bRedraw = bRedraw;
8115 if(!bRedraw) return 0;
8117 if (is_autoarrange(infoPtr))
8118 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8119 LISTVIEW_UpdateScroll(infoPtr);
8121 /* despite what the WM_SETREDRAW docs says, apps expect us
8122 * to invalidate the listview here... stupid! */
8123 LISTVIEW_InvalidateList(infoPtr);
8130 * Resizes the listview control. This function processes WM_SIZE
8131 * messages. At this time, the width and height are not used.
8134 * [I] infoPtr : valid pointer to the listview structure
8135 * [I] Width : new width
8136 * [I] Height : new height
8141 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
8143 RECT rcOld = infoPtr->rcList;
8145 TRACE("(width=%d, height=%d)\n", Width, Height);
8147 LISTVIEW_UpdateSize(infoPtr);
8148 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
8150 /* do not bother with display related stuff if we're not redrawing */
8151 if (!is_redrawing(infoPtr)) return 0;
8153 if (is_autoarrange(infoPtr))
8154 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8156 LISTVIEW_UpdateScroll(infoPtr);
8158 /* refresh all only for lists whose height changed significantly */
8159 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
8160 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
8161 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
8162 LISTVIEW_InvalidateList(infoPtr);
8169 * Sets the size information.
8172 * [I] infoPtr : valid pointer to the listview structure
8177 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
8179 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8181 TRACE("uView=%d, rcList(old)=%s\n", uView, debugrect(&infoPtr->rcList));
8183 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
8185 if (uView == LVS_LIST)
8187 /* Apparently the "LIST" style is supposed to have the same
8188 * number of items in a column even if there is no scroll bar.
8189 * Since if a scroll bar already exists then the bottom is already
8190 * reduced, only reduce if the scroll bar does not currently exist.
8191 * The "2" is there to mimic the native control. I think it may be
8192 * related to either padding or edges. (GLA 7/2002)
8194 if (!(infoPtr->dwStyle & WS_HSCROLL))
8195 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
8196 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
8198 else if (uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER))
8203 hl.prc = &infoPtr->rcList;
8205 Header_Layout(infoPtr->hwndHeader, &hl);
8207 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8209 infoPtr->rcList.top = max(wp.cy, 0);
8212 TRACE(" rcList=%s\n", debugrect(&infoPtr->rcList));
8217 * Processes WM_STYLECHANGED messages.
8220 * [I] infoPtr : valid pointer to the listview structure
8221 * [I] wStyleType : window style type (normal or extended)
8222 * [I] lpss : window style information
8227 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
8228 const STYLESTRUCT *lpss)
8230 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
8231 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
8233 TRACE("(styletype=%x, styleOld=0x%08lx, styleNew=0x%08lx)\n",
8234 wStyleType, lpss->styleOld, lpss->styleNew);
8236 if (wStyleType != GWL_STYLE) return 0;
8238 /* FIXME: if LVS_NOSORTHEADER changed, update header */
8239 /* what if LVS_OWNERDATA changed? */
8240 /* or LVS_SINGLESEL */
8241 /* or LVS_SORT{AS,DES}CENDING */
8243 infoPtr->dwStyle = lpss->styleNew;
8245 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
8246 ((lpss->styleNew & WS_HSCROLL) == 0))
8247 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
8249 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
8250 ((lpss->styleNew & WS_VSCROLL) == 0))
8251 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
8253 if (uNewView != uOldView)
8255 SIZE oldIconSize = infoPtr->iconSize;
8258 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8259 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8261 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
8262 SetRectEmpty(&infoPtr->rcFocus);
8264 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
8265 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
8267 if (uNewView == LVS_ICON)
8269 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
8271 TRACE("icon old size=(%ld,%ld), new size=(%ld,%ld)\n",
8272 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
8273 LISTVIEW_SetIconSpacing(infoPtr, 0);
8276 else if (uNewView == LVS_REPORT)
8281 hl.prc = &infoPtr->rcList;
8283 Header_Layout(infoPtr->hwndHeader, &hl);
8284 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8287 LISTVIEW_UpdateItemSize(infoPtr);
8290 if (uNewView == LVS_REPORT)
8291 ShowWindow(infoPtr->hwndHeader, (lpss->styleNew & LVS_NOCOLUMNHEADER) ? SW_HIDE : SW_SHOWNORMAL);
8293 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
8294 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
8295 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8297 /* update the size of the client area */
8298 LISTVIEW_UpdateSize(infoPtr);
8300 /* add scrollbars if needed */
8301 LISTVIEW_UpdateScroll(infoPtr);
8303 /* invalidate client area + erase background */
8304 LISTVIEW_InvalidateList(infoPtr);
8311 * Window procedure of the listview control.
8314 static LRESULT WINAPI
8315 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8317 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(hwnd, 0);
8319 TRACE("(uMsg=%x wParam=%x lParam=%lx)\n", uMsg, wParam, lParam);
8321 if (!infoPtr && (uMsg != WM_CREATE))
8322 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8326 infoPtr->dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
8331 case LVM_APPROXIMATEVIEWRECT:
8332 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
8333 LOWORD(lParam), HIWORD(lParam));
8335 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
8337 /* case LVM_CANCELEDITLABEL: */
8339 /* case LVM_CREATEDRAGIMAGE: */
8341 case LVM_DELETEALLITEMS:
8342 return LISTVIEW_DeleteAllItems(infoPtr);
8344 case LVM_DELETECOLUMN:
8345 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
8347 case LVM_DELETEITEM:
8348 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
8350 case LVM_EDITLABELW:
8351 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
8353 case LVM_EDITLABELA:
8354 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
8356 /* case LVM_ENABLEGROUPVIEW: */
8358 case LVM_ENSUREVISIBLE:
8359 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
8362 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
8365 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
8367 case LVM_GETBKCOLOR:
8368 return infoPtr->clrBk;
8370 /* case LVM_GETBKIMAGE: */
8372 case LVM_GETCALLBACKMASK:
8373 return infoPtr->uCallbackMask;
8375 case LVM_GETCOLUMNA:
8376 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8378 case LVM_GETCOLUMNW:
8379 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8381 case LVM_GETCOLUMNORDERARRAY:
8382 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
8384 case LVM_GETCOLUMNWIDTH:
8385 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
8387 case LVM_GETCOUNTPERPAGE:
8388 return LISTVIEW_GetCountPerPage(infoPtr);
8390 case LVM_GETEDITCONTROL:
8391 return (LRESULT)infoPtr->hwndEdit;
8393 case LVM_GETEXTENDEDLISTVIEWSTYLE:
8394 return infoPtr->dwLvExStyle;
8396 /* case LVM_GETGROUPINFO: */
8398 /* case LVM_GETGROUPMETRICS: */
8401 return (LRESULT)infoPtr->hwndHeader;
8403 case LVM_GETHOTCURSOR:
8404 return (LRESULT)infoPtr->hHotCursor;
8406 case LVM_GETHOTITEM:
8407 return infoPtr->nHotItem;
8409 case LVM_GETHOVERTIME:
8410 return infoPtr->dwHoverTime;
8412 case LVM_GETIMAGELIST:
8413 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
8415 /* case LVM_GETINSERTMARK: */
8417 /* case LVM_GETINSERTMARKCOLOR: */
8419 /* case LVM_GETINSERTMARKRECT: */
8421 case LVM_GETISEARCHSTRINGA:
8422 case LVM_GETISEARCHSTRINGW:
8423 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
8427 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
8430 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
8432 case LVM_GETITEMCOUNT:
8433 return infoPtr->nItemCount;
8435 case LVM_GETITEMPOSITION:
8436 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
8438 case LVM_GETITEMRECT:
8439 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
8441 case LVM_GETITEMSPACING:
8442 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
8444 case LVM_GETITEMSTATE:
8445 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
8447 case LVM_GETITEMTEXTA:
8448 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
8450 case LVM_GETITEMTEXTW:
8451 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
8453 case LVM_GETNEXTITEM:
8454 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
8456 case LVM_GETNUMBEROFWORKAREAS:
8457 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
8461 if (!lParam) return FALSE;
8462 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
8465 /* case LVM_GETOUTLINECOLOR: */
8467 /* case LVM_GETSELECTEDCOLUMN: */
8469 case LVM_GETSELECTEDCOUNT:
8470 return LISTVIEW_GetSelectedCount(infoPtr);
8472 case LVM_GETSELECTIONMARK:
8473 return infoPtr->nSelectionMark;
8475 case LVM_GETSTRINGWIDTHA:
8476 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
8478 case LVM_GETSTRINGWIDTHW:
8479 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
8481 case LVM_GETSUBITEMRECT:
8482 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
8484 case LVM_GETTEXTBKCOLOR:
8485 return infoPtr->clrTextBk;
8487 case LVM_GETTEXTCOLOR:
8488 return infoPtr->clrText;
8490 /* case LVM_GETTILEINFO: */
8492 /* case LVM_GETTILEVIEWINFO: */
8494 case LVM_GETTOOLTIPS:
8495 FIXME("LVM_GETTOOLTIPS: unimplemented\n");
8498 case LVM_GETTOPINDEX:
8499 return LISTVIEW_GetTopIndex(infoPtr);
8501 /*case LVM_GETUNICODEFORMAT:
8502 FIXME("LVM_GETUNICODEFORMAT: unimplemented\n");
8505 /* case LVM_GETVIEW: */
8507 case LVM_GETVIEWRECT:
8508 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
8510 case LVM_GETWORKAREAS:
8511 FIXME("LVM_GETWORKAREAS: unimplemented\n");
8514 /* case LVM_HASGROUP: */
8517 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
8519 case LVM_INSERTCOLUMNA:
8520 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8522 case LVM_INSERTCOLUMNW:
8523 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8525 /* case LVM_INSERTGROUP: */
8527 /* case LVM_INSERTGROUPSORTED: */
8529 case LVM_INSERTITEMA:
8530 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
8532 case LVM_INSERTITEMW:
8533 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
8535 /* case LVM_INSERTMARKHITTEST: */
8537 /* case LVM_ISGROUPVIEWENABLED: */
8539 /* case LVM_MAPIDTOINDEX: */
8541 /* case LVM_MAPINDEXTOID: */
8543 /* case LVM_MOVEGROUP: */
8545 /* case LVM_MOVEITEMTOGROUP: */
8547 case LVM_REDRAWITEMS:
8548 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
8550 /* case LVM_REMOVEALLGROUPS: */
8552 /* case LVM_REMOVEGROUP: */
8555 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
8557 case LVM_SETBKCOLOR:
8558 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
8560 /* case LVM_SETBKIMAGE: */
8562 case LVM_SETCALLBACKMASK:
8563 infoPtr->uCallbackMask = (UINT)wParam;
8566 case LVM_SETCOLUMNA:
8567 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8569 case LVM_SETCOLUMNW:
8570 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8572 case LVM_SETCOLUMNORDERARRAY:
8573 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
8575 case LVM_SETCOLUMNWIDTH:
8576 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, SLOWORD(lParam));
8578 case LVM_SETEXTENDEDLISTVIEWSTYLE:
8579 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
8581 /* case LVM_SETGROUPINFO: */
8583 /* case LVM_SETGROUPMETRICS: */
8585 case LVM_SETHOTCURSOR:
8586 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
8588 case LVM_SETHOTITEM:
8589 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
8591 case LVM_SETHOVERTIME:
8592 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
8594 case LVM_SETICONSPACING:
8595 return LISTVIEW_SetIconSpacing(infoPtr, (DWORD)lParam);
8597 case LVM_SETIMAGELIST:
8598 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
8600 /* case LVM_SETINFOTIP: */
8602 /* case LVM_SETINSERTMARK: */
8604 /* case LVM_SETINSERTMARKCOLOR: */
8607 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
8610 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
8612 case LVM_SETITEMCOUNT:
8613 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
8615 case LVM_SETITEMPOSITION:
8617 POINT pt = { SLOWORD(lParam), SHIWORD(lParam) };
8618 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
8621 case LVM_SETITEMPOSITION32:
8622 if (lParam == 0) return FALSE;
8623 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
8625 case LVM_SETITEMSTATE:
8626 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
8628 case LVM_SETITEMTEXTA:
8629 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
8631 case LVM_SETITEMTEXTW:
8632 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
8634 /* case LVM_SETOUTLINECOLOR: */
8636 /* case LVM_SETSELECTEDCOLUMN: */
8638 case LVM_SETSELECTIONMARK:
8639 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
8641 case LVM_SETTEXTBKCOLOR:
8642 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
8644 case LVM_SETTEXTCOLOR:
8645 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
8647 /* case LVM_SETTILEINFO: */
8649 /* case LVM_SETTILEVIEWINFO: */
8651 /* case LVM_SETTILEWIDTH: */
8653 /* case LVM_SETTOOLTIPS: */
8655 /* case LVM_SETUNICODEFORMAT: */
8657 /* case LVM_SETVIEW: */
8659 /* case LVM_SETWORKAREAS: */
8661 /* case LVM_SORTGROUPS: */
8664 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
8666 /* LVM_SORTITEMSEX: */
8668 case LVM_SUBITEMHITTEST:
8669 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
8672 return LISTVIEW_Update(infoPtr, (INT)wParam);
8675 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
8678 return LISTVIEW_Command(infoPtr, wParam, lParam);
8681 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
8684 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
8687 return DLGC_WANTCHARS | DLGC_WANTARROWS;
8690 return (LRESULT)infoPtr->hFont;
8693 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
8696 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
8699 return LISTVIEW_KillFocus(infoPtr);
8701 case WM_LBUTTONDBLCLK:
8702 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8704 case WM_LBUTTONDOWN:
8705 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8708 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8711 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8714 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8717 return LISTVIEW_NCDestroy(infoPtr);
8720 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
8721 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
8724 case WM_NOTIFYFORMAT:
8725 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
8728 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
8730 case WM_RBUTTONDBLCLK:
8731 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8733 case WM_RBUTTONDOWN:
8734 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8737 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8740 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
8745 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
8748 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
8751 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
8754 return LISTVIEW_Size(infoPtr, (int)SLOWORD(lParam), (int)SHIWORD(lParam));
8756 case WM_STYLECHANGED:
8757 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
8759 case WM_SYSCOLORCHANGE:
8760 COMCTL32_RefreshSysColors();
8763 /* case WM_TIMER: */
8766 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
8769 if (wParam & (MK_SHIFT | MK_CONTROL))
8770 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8771 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
8773 case WM_WINDOWPOSCHANGED:
8774 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
8776 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
8777 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
8778 LISTVIEW_UpdateSize(infoPtr);
8779 LISTVIEW_UpdateScroll(infoPtr);
8781 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8783 /* case WM_WININICHANGE: */
8786 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
8787 ERR("unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam);
8790 /* call default window procedure */
8791 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8799 * Registers the window class.
8807 void LISTVIEW_Register(void)
8811 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
8812 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
8813 wndClass.lpfnWndProc = (WNDPROC)LISTVIEW_WindowProc;
8814 wndClass.cbClsExtra = 0;
8815 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
8816 wndClass.hCursor = LoadCursorW(0, IDC_ARROWW);
8817 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
8818 wndClass.lpszClassName = WC_LISTVIEWW;
8819 RegisterClassW(&wndClass);
8824 * Unregisters the window class.
8832 void LISTVIEW_Unregister(void)
8834 UnregisterClassW(WC_LISTVIEWW, NULL);
8839 * Handle any WM_COMMAND messages
8842 * [I] infoPtr : valid pointer to the listview structure
8843 * [I] wParam : the first message parameter
8844 * [I] lParam : the second message parameter
8849 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
8851 switch (HIWORD(wParam))
8856 * Adjust the edit window size
8859 HDC hdc = GetDC(infoPtr->hwndEdit);
8860 HFONT hFont, hOldFont = 0;
8865 if (!infoPtr->hwndEdit || !hdc) return 0;
8866 len = GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
8867 GetWindowRect(infoPtr->hwndEdit, &rect);
8869 /* Select font to get the right dimension of the string */
8870 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
8873 hOldFont = SelectObject(hdc, hFont);
8876 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
8878 TEXTMETRICW textMetric;
8880 /* Add Extra spacing for the next character */
8881 GetTextMetricsW(hdc, &textMetric);
8882 sz.cx += (textMetric.tmMaxCharWidth * 2);
8890 rect.bottom - rect.top,
8891 SWP_DRAWFRAME|SWP_NOMOVE);
8894 SelectObject(hdc, hOldFont);
8896 ReleaseDC(infoPtr->hwndSelf, hdc);
8902 return SendMessageW (GetParent (infoPtr->hwndSelf), WM_COMMAND, wParam, lParam);
8911 * Subclassed edit control windproc function
8914 * [I] hwnd : the edit window handle
8915 * [I] uMsg : the message that is to be processed
8916 * [I] wParam : first message parameter
8917 * [I] lParam : second message parameter
8918 * [I] isW : TRUE if input is Unicode
8923 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
8925 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(GetParent(hwnd), 0);
8926 BOOL cancel = FALSE;
8928 TRACE("(hwnd=%p, uMsg=%x, wParam=%x, lParam=%lx, isW=%d)\n",
8929 hwnd, uMsg, wParam, lParam, isW);
8934 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
8941 WNDPROC editProc = infoPtr->EditWndProc;
8942 infoPtr->EditWndProc = 0;
8943 SetWindowLongW(hwnd, GWL_WNDPROC, (LONG)editProc);
8944 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
8948 if (VK_ESCAPE == (INT)wParam)
8953 else if (VK_RETURN == (INT)wParam)
8957 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
8961 if (infoPtr->hwndEdit)
8963 LPWSTR buffer = NULL;
8965 infoPtr->hwndEdit = 0;
8968 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
8972 if ( (buffer = COMCTL32_Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
8974 if (isW) GetWindowTextW(hwnd, buffer, len+1);
8975 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
8979 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
8981 if (buffer) COMCTL32_Free(buffer);
8985 SendMessageW(hwnd, WM_CLOSE, 0, 0);
8991 * Subclassed edit control Unicode windproc function
8994 * [I] hwnd : the edit window handle
8995 * [I] uMsg : the message that is to be processed
8996 * [I] wParam : first message parameter
8997 * [I] lParam : second message parameter
9001 LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9003 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
9008 * Subclassed edit control ANSI windproc function
9011 * [I] hwnd : the edit window handle
9012 * [I] uMsg : the message that is to be processed
9013 * [I] wParam : first message parameter
9014 * [I] lParam : second message parameter
9018 LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9020 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
9025 * Creates a subclassed edit cotrol
9028 * [I] infoPtr : valid pointer to the listview structure
9029 * [I] text : initial text for the edit
9030 * [I] style : the window style
9031 * [I] isW : TRUE if input is Unicode
9035 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
9036 INT x, INT y, INT width, INT height, BOOL isW)
9038 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
9043 TEXTMETRICW textMetric;
9044 HINSTANCE hinst = (HINSTANCE)GetWindowLongW(infoPtr->hwndSelf, GWL_HINSTANCE);
9046 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
9048 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|WS_BORDER;
9049 hdc = GetDC(infoPtr->hwndSelf);
9051 /* Select the font to get appropriate metric dimensions */
9052 if(infoPtr->hFont != 0)
9053 hOldFont = SelectObject(hdc, infoPtr->hFont);
9055 /*Get String Lenght in pixels */
9056 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
9058 /*Add Extra spacing for the next character */
9059 GetTextMetricsW(hdc, &textMetric);
9060 sz.cx += (textMetric.tmMaxCharWidth * 2);
9062 if(infoPtr->hFont != 0)
9063 SelectObject(hdc, hOldFont);
9065 ReleaseDC(infoPtr->hwndSelf, hdc);
9067 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
9069 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
9071 if (!hedit) return 0;
9073 infoPtr->EditWndProc = (WNDPROC)
9074 (isW ? SetWindowLongW(hedit, GWL_WNDPROC, (LONG)EditLblWndProcW) :
9075 SetWindowLongA(hedit, GWL_WNDPROC, (LONG)EditLblWndProcA) );
9077 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);