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_TRACKSELECT
92 * -- LVS_EX_TWOCLICKACTIVATE
93 * -- LVS_EX_UNDERLINECOLD
94 * -- LVS_EX_UNDERLINEHOT
98 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
101 * -- LVN_MARQUEEBEGIN
103 * -- LVN_ODSTATECHANGED
108 * -- LVM_CANCELEDITLABEL
109 * -- LVM_ENABLEGROUPVIEW
110 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
111 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
112 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
113 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
114 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
115 * -- LVM_GETINSERTMARKRECT
116 * -- LVM_GETNUMBEROFWORKAREAS
117 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
118 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
119 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
120 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
121 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
122 * -- LVM_GETTOOLTIPS, LVM_SETTOOLTIPS
123 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
124 * -- LVM_GETVIEW, LVM_SETVIEW
125 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
126 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
127 * -- LVM_INSERTGROUPSORTED
128 * -- LVM_INSERTMARKHITTEST
129 * -- LVM_ISGROUPVIEWENABLED
130 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
132 * -- LVM_MOVEITEMTOGROUP
134 * -- LVM_SETTILEWIDTH
138 * Known differences in message stream from native control (not known if
139 * these differences cause problems):
140 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
141 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
142 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
143 * processing for "USEDOUBLECLICKTIME".
147 #include "wine/port.h"
162 #include "commctrl.h"
163 #include "comctl32.h"
165 #include "wine/debug.h"
166 #include "wine/unicode.h"
168 WINE_DEFAULT_DEBUG_CHANNEL(listview);
170 /* make sure you set this to 0 for production use! */
171 #define DEBUG_RANGES 1
173 typedef struct tagCOLUMN_INFO
175 RECT rcHeader; /* tracks the header's rectangle */
176 int fmt; /* same as LVCOLUMN.fmt */
179 typedef struct tagITEMHDR
183 } ITEMHDR, *LPITEMHDR;
185 typedef struct tagSUBITEM_INFO
191 typedef struct tagITEM_INFO
199 typedef struct tagRANGE
205 typedef struct tagRANGES
210 typedef struct tagITERATOR
219 typedef struct tagLISTVIEW_INFO
226 COLORREF clrTextBkDefault;
227 HIMAGELIST himlNormal;
228 HIMAGELIST himlSmall;
229 HIMAGELIST himlState;
232 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
235 RANGES selectionRanges;
240 RECT rcList; /* This rectangle is really the window
241 * client rectangle possibly reduced by the
242 * horizontal scroll bar and/or header - see
243 * LISTVIEW_UpdateSize. This rectangle offset
244 * by the LISTVIEW_GetOrigin value is in
245 * client coordinates */
254 INT ntmHeight; /* Some cached metrics of the font used */
255 INT ntmAveCharWidth; /* by the listview to draw items */
256 BOOL bRedraw; /* Turns on/off repaints & invalidations */
257 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
259 BOOL bDoChangeNotify; /* send change notification messages? */
262 DWORD dwStyle; /* the cached window GWL_STYLE */
263 DWORD dwLvExStyle; /* extended listview style */
264 INT nItemCount; /* the number of items in the list */
265 HDPA hdpaItems; /* array ITEM_INFO pointers */
266 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
267 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
268 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
269 POINT currIconPos; /* this is the position next icon will be placed */
270 PFNLVCOMPARE pfnCompare;
278 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
280 DWORD lastKeyPressTimestamp;
282 INT nSearchParamLength;
283 WCHAR szSearchParam[ MAX_PATH ];
290 /* How many we debug buffer to allocate */
291 #define DEBUG_BUFFERS 20
292 /* The size of a single debug bbuffer */
293 #define DEBUG_BUFFER_SIZE 256
295 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
296 #define SB_INTERNAL -1
298 /* maximum size of a label */
299 #define DISP_TEXT_SIZE 512
301 /* padding for items in list and small icon display modes */
302 #define WIDTH_PADDING 12
304 /* padding for items in list, report and small icon display modes */
305 #define HEIGHT_PADDING 1
307 /* offset of items in report display mode */
308 #define REPORT_MARGINX 2
310 /* padding for icon in large icon display mode
311 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
312 * that HITTEST will see.
313 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
314 * ICON_TOP_PADDING - sum of the two above.
315 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
316 * LABEL_HOR_PADDING - between text and sides of box
317 * LABEL_VERT_PADDING - between bottom of text and end of box
319 * ICON_LR_PADDING - additional width above icon size.
320 * ICON_LR_HALF - half of the above value
322 #define ICON_TOP_PADDING_NOTHITABLE 2
323 #define ICON_TOP_PADDING_HITABLE 2
324 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
325 #define ICON_BOTTOM_PADDING 4
326 #define LABEL_HOR_PADDING 5
327 #define LABEL_VERT_PADDING 7
328 #define ICON_LR_PADDING 16
329 #define ICON_LR_HALF (ICON_LR_PADDING/2)
331 /* default label width for items in list and small icon display modes */
332 #define DEFAULT_LABEL_WIDTH 40
334 /* default column width for items in list display mode */
335 #define DEFAULT_COLUMN_WIDTH 128
337 /* Size of "line" scroll for V & H scrolls */
338 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
340 /* Padding betwen image and label */
341 #define IMAGE_PADDING 2
343 /* Padding behind the label */
344 #define TRAILING_LABEL_PADDING 12
345 #define TRAILING_HEADER_PADDING 11
347 /* Border for the icon caption */
348 #define CAPTION_BORDER 2
350 /* Standard DrawText flags */
351 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
352 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
353 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
355 /* The time in milliseconds to reset the search in the list */
356 #define KEY_DELAY 450
358 /* Dump the LISTVIEW_INFO structure to the debug channel */
359 #define LISTVIEW_DUMP(iP) do { \
360 TRACE("hwndSelf=%p, clrBk=0x%06lx, clrText=0x%06lx, clrTextBk=0x%06lx, ItemHeight=%d, ItemWidth=%d, Style=0x%08lx\n", \
361 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
362 iP->nItemHeight, iP->nItemWidth, infoPtr->dwStyle); \
363 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08lx, Focus=%d\n", \
364 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
365 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
366 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%ld, icSz.cy=%ld, icSp.cx=%ld, icSp.cy=%ld, notifyFmt=%d\n", \
367 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
368 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
369 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, debugrect(&iP->rcList)); \
373 * forward declarations
375 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *, LPLVITEMW, BOOL);
376 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *, INT, LPRECT);
377 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *, INT, LPPOINT);
378 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *, INT, LPPOINT);
379 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *, INT, LPRECT);
380 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *, INT);
381 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *, LPPOINT);
382 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *, LPRECT);
383 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
384 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, const LVITEMW *, BOOL);
385 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *);
386 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
387 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
388 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
389 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
390 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *, PFNLVCOMPARE, LPARAM);
391 static INT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *, LPCWSTR, BOOL);
392 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT);
393 static UINT LISTVIEW_GetItemState(LISTVIEW_INFO *, INT, UINT);
394 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
395 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
396 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
397 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *);
398 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
399 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
401 /******** Text handling functions *************************************/
403 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
404 * text string. The string may be ANSI or Unicode, in which case
405 * the boolean isW tells us the type of the string.
407 * The name of the function tell what type of strings it expects:
408 * W: Unicode, T: ANSI/Unicode - function of isW
411 static inline BOOL is_textW(LPCWSTR text)
413 return text != NULL && text != LPSTR_TEXTCALLBACKW;
416 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
418 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
419 return is_textW(text);
422 static inline int textlenT(LPCWSTR text, BOOL isW)
424 return !is_textT(text, isW) ? 0 :
425 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
428 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
431 if (isSrcW) lstrcpynW(dest, src, max);
432 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
434 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
435 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
438 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
440 LPWSTR wstr = (LPWSTR)text;
442 if (!isW && is_textT(text, isW))
444 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
445 wstr = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
446 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
448 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
452 static inline void textfreeT(LPWSTR wstr, BOOL isW)
454 if (!isW && is_textT(wstr, isW)) HeapFree(GetProcessHeap(), 0, wstr);
458 * dest is a pointer to a Unicode string
459 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
461 static BOOL textsetptrT(LPWSTR *dest, LPWSTR src, BOOL isW)
465 if (src == LPSTR_TEXTCALLBACKW)
467 if (is_textW(*dest)) Free(*dest);
468 *dest = LPSTR_TEXTCALLBACKW;
472 LPWSTR pszText = textdupTtoW(src, isW);
473 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
474 bResult = Str_SetPtrW(dest, pszText);
475 textfreeT(pszText, isW);
481 * compares a Unicode to a Unicode/ANSI text string
483 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
485 if (!aw) return bt ? -1 : 0;
486 if (!bt) return aw ? 1 : 0;
487 if (aw == LPSTR_TEXTCALLBACKW)
488 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
489 if (bt != LPSTR_TEXTCALLBACKW)
491 LPWSTR bw = textdupTtoW(bt, isW);
492 int r = bw ? lstrcmpW(aw, bw) : 1;
500 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
504 n = min(min(n, strlenW(s1)), strlenW(s2));
505 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
506 return res ? res - sizeof(WCHAR) : res;
509 /******** Debugging functions *****************************************/
511 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
513 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
514 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
517 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
519 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
520 n = min(textlenT(text, isW), n);
521 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
524 static char* debug_getbuf()
526 static int index = 0;
527 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
528 return buffers[index++ % DEBUG_BUFFERS];
531 static inline const char* debugrange(const RANGE *lprng)
535 char* buf = debug_getbuf();
536 snprintf(buf, DEBUG_BUFFER_SIZE, "[%d, %d)", lprng->lower, lprng->upper);
538 } else return "(null)";
541 static inline const char* debugpoint(const POINT *lppt)
545 char* buf = debug_getbuf();
546 snprintf(buf, DEBUG_BUFFER_SIZE, "(%ld, %ld)", lppt->x, lppt->y);
548 } else return "(null)";
551 static inline const char* debugrect(const RECT *rect)
555 char* buf = debug_getbuf();
556 snprintf(buf, DEBUG_BUFFER_SIZE, "[(%ld, %ld);(%ld, %ld)]",
557 rect->left, rect->top, rect->right, rect->bottom);
559 } else return "(null)";
562 static const char * debugscrollinfo(const SCROLLINFO *pScrollInfo)
564 char* buf = debug_getbuf(), *text = buf;
565 int len, size = DEBUG_BUFFER_SIZE;
567 if (pScrollInfo == NULL) return "(null)";
568 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
569 if (len == -1) goto end; buf += len; size -= len;
570 if (pScrollInfo->fMask & SIF_RANGE)
571 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
573 if (len == -1) goto end; buf += len; size -= len;
574 if (pScrollInfo->fMask & SIF_PAGE)
575 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
577 if (len == -1) goto end; buf += len; size -= len;
578 if (pScrollInfo->fMask & SIF_POS)
579 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
581 if (len == -1) goto end; buf += len; size -= len;
582 if (pScrollInfo->fMask & SIF_TRACKPOS)
583 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
585 if (len == -1) goto end; buf += len; size -= len;
588 buf = text + strlen(text);
590 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
594 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
598 char* buf = debug_getbuf();
599 snprintf(buf, DEBUG_BUFFER_SIZE, "iItem=%d, iSubItem=%d, uNewState=0x%x,"
600 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld\n",
601 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
602 plvnm->uChanged, debugpoint(&plvnm->ptAction), plvnm->lParam);
604 } else return "(null)";
607 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
609 char* buf = debug_getbuf(), *text = buf;
610 int len, size = DEBUG_BUFFER_SIZE;
612 if (lpLVItem == NULL) return "(null)";
613 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
614 if (len == -1) goto end; buf += len; size -= len;
615 if (lpLVItem->mask & LVIF_STATE)
616 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
618 if (len == -1) goto end; buf += len; size -= len;
619 if (lpLVItem->mask & LVIF_TEXT)
620 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
622 if (len == -1) goto end; buf += len; size -= len;
623 if (lpLVItem->mask & LVIF_IMAGE)
624 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
626 if (len == -1) goto end; buf += len; size -= len;
627 if (lpLVItem->mask & LVIF_PARAM)
628 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
630 if (len == -1) goto end; buf += len; size -= len;
631 if (lpLVItem->mask & LVIF_INDENT)
632 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
634 if (len == -1) goto end; buf += len; size -= len;
637 buf = text + strlen(text);
639 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
643 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
645 char* buf = debug_getbuf(), *text = buf;
646 int len, size = DEBUG_BUFFER_SIZE;
648 if (lpColumn == NULL) return "(null)";
649 len = snprintf(buf, size, "{");
650 if (len == -1) goto end; buf += len; size -= len;
651 if (lpColumn->mask & LVCF_SUBITEM)
652 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
654 if (len == -1) goto end; buf += len; size -= len;
655 if (lpColumn->mask & LVCF_FMT)
656 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
658 if (len == -1) goto end; buf += len; size -= len;
659 if (lpColumn->mask & LVCF_WIDTH)
660 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
662 if (len == -1) goto end; buf += len; size -= len;
663 if (lpColumn->mask & LVCF_TEXT)
664 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
666 if (len == -1) goto end; buf += len; size -= len;
667 if (lpColumn->mask & LVCF_IMAGE)
668 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
670 if (len == -1) goto end; buf += len; size -= len;
671 if (lpColumn->mask & LVCF_ORDER)
672 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
674 if (len == -1) goto end; buf += len; size -= len;
677 buf = text + strlen(text);
679 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
683 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
687 char* buf = debug_getbuf();
688 snprintf(buf, DEBUG_BUFFER_SIZE, "{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
689 debugpoint(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
691 } else return "(null)";
694 /* Return the corresponding text for a given scroll value */
695 static inline LPCSTR debugscrollcode(int nScrollCode)
699 case SB_LINELEFT: return "SB_LINELEFT";
700 case SB_LINERIGHT: return "SB_LINERIGHT";
701 case SB_PAGELEFT: return "SB_PAGELEFT";
702 case SB_PAGERIGHT: return "SB_PAGERIGHT";
703 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
704 case SB_THUMBTRACK: return "SB_THUMBTRACK";
705 case SB_ENDSCROLL: return "SB_ENDSCROLL";
706 case SB_INTERNAL: return "SB_INTERNAL";
707 default: return "unknown";
712 /******** Notification functions i************************************/
714 static LRESULT notify_hdr(LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
718 TRACE("(code=%d)\n", code);
720 pnmh->hwndFrom = infoPtr->hwndSelf;
721 pnmh->idFrom = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
723 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
724 (WPARAM)pnmh->idFrom, (LPARAM)pnmh);
726 TRACE(" <= %ld\n", result);
731 static inline LRESULT notify(LISTVIEW_INFO *infoPtr, INT code)
734 return notify_hdr(infoPtr, code, &nmh);
737 static inline void notify_itemactivate(LISTVIEW_INFO *infoPtr, LVHITTESTINFO *htInfo)
748 item.mask = LVIF_PARAM|LVIF_STATE;
749 item.iItem = htInfo->iItem;
751 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
752 nmia.lParam = item.lParam;
753 nmia.uOldState = item.state;
754 nmia.uNewState = item.state | LVIS_ACTIVATING;
755 nmia.uChanged = LVIF_STATE;
758 nmia.iItem = htInfo->iItem;
759 nmia.iSubItem = htInfo->iSubItem;
760 nmia.ptAction = htInfo->pt;
762 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
763 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
764 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
766 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
769 static inline LRESULT notify_listview(LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
771 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
772 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
775 static LRESULT notify_click(LISTVIEW_INFO *infoPtr, INT code, LVHITTESTINFO *lvht)
780 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
781 ZeroMemory(&nmlv, sizeof(nmlv));
782 nmlv.iItem = lvht->iItem;
783 nmlv.iSubItem = lvht->iSubItem;
784 nmlv.ptAction = lvht->pt;
785 item.mask = LVIF_PARAM;
786 item.iItem = lvht->iItem;
788 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
789 return notify_listview(infoPtr, code, &nmlv);
792 static void notify_deleteitem(LISTVIEW_INFO *infoPtr, INT nItem)
797 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
799 item.mask = LVIF_PARAM;
802 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
803 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
806 static int get_ansi_notification(INT unicodeNotificationCode)
808 switch (unicodeNotificationCode)
810 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
811 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
812 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
813 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
814 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
815 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
817 ERR("unknown notification %x\n", unicodeNotificationCode);
823 With testing on Windows 2000 it looks like the notify format
824 has nothing to do with this message. It ALWAYS seems to be
827 infoPtr : listview struct
828 notificationCode : *Unicode* notification code
829 pdi : dispinfo structure (can be unicode or ansi)
830 isW : TRUE if dispinfo is Unicode
832 static BOOL notify_dispinfoT(LISTVIEW_INFO *infoPtr, INT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
834 BOOL bResult = FALSE;
835 BOOL convertToAnsi = FALSE;
836 INT cchTempBufMax = 0, savCchTextMax = 0;
837 LPWSTR pszTempBuf = NULL, savPszText = NULL;
839 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
844 if (notificationCode != LVN_GETDISPINFOW)
846 cchTempBufMax = WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText,
847 -1, NULL, 0, NULL, NULL);
851 cchTempBufMax = pdi->item.cchTextMax;
852 *pdi->item.pszText = 0; /* make sure we don't process garbage */
855 pszTempBuf = HeapAlloc(GetProcessHeap(), 0, sizeof(CHAR) *
857 if (!pszTempBuf) return FALSE;
859 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR)
860 pszTempBuf, cchTempBufMax, NULL, NULL);
862 savCchTextMax = pdi->item.cchTextMax;
863 savPszText = pdi->item.pszText;
864 pdi->item.pszText = pszTempBuf;
865 pdi->item.cchTextMax = cchTempBufMax;
868 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat !=
871 bResult = notify_hdr(infoPtr, get_ansi_notification(notificationCode),
876 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
877 savPszText, savCchTextMax);
878 pdi->item.pszText = savPszText; /* restores our buffer */
879 pdi->item.cchTextMax = savCchTextMax;
880 HeapFree(GetProcessHeap(), 0, pszTempBuf);
885 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, LISTVIEW_INFO *infoPtr, HDC hdc,
886 const RECT *rcBounds, const LVITEMW *lplvItem)
888 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
889 lpnmlvcd->nmcd.hdc = hdc;
890 lpnmlvcd->nmcd.rc = *rcBounds;
891 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
892 lpnmlvcd->clrText = infoPtr->clrText;
893 if (!lplvItem) return;
894 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
895 lpnmlvcd->iSubItem = lplvItem->iSubItem;
896 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
897 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
898 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
899 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
902 static inline DWORD notify_customdraw (LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
904 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
907 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
908 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
909 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
910 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
911 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
912 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
916 static void prepaint_setup (LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd)
918 /* apprently, for selected items, we have to override the returned values */
919 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
923 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
924 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
926 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
928 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
929 lpnmlvcd->clrText = comctl32_color.clrBtnText;
933 /* Set the text attributes */
934 if (lpnmlvcd->clrTextBk != CLR_NONE)
936 SetBkMode(hdc, OPAQUE);
937 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
938 SetBkColor(hdc, infoPtr->clrTextBkDefault);
940 SetBkColor(hdc,lpnmlvcd->clrTextBk);
943 SetBkMode(hdc, TRANSPARENT);
944 SetTextColor(hdc, lpnmlvcd->clrText);
947 static inline DWORD notify_postpaint (LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
949 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
952 /******** Item iterator functions **********************************/
954 static RANGES ranges_create(int count);
955 static void ranges_destroy(RANGES ranges);
956 static BOOL ranges_add(RANGES ranges, RANGE range);
957 static BOOL ranges_del(RANGES ranges, RANGE range);
958 static void ranges_dump(RANGES ranges);
960 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
962 RANGE range = { nItem, nItem + 1 };
964 return ranges_add(ranges, range);
967 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
969 RANGE range = { nItem, nItem + 1 };
971 return ranges_del(ranges, range);
975 * ITERATOR DOCUMENTATION
977 * The iterator functions allow for easy, and convenient iteration
978 * over items of iterest in the list. Typically, you create a
979 * iterator, use it, and destroy it, as such:
982 * iterator_xxxitems(&i, ...);
983 * while (iterator_{prev,next}(&i)
985 * //code which uses i.nItem
987 * iterator_destroy(&i);
989 * where xxx is either: framed, or visible.
990 * Note that it is important that the code destroys the iterator
991 * after it's done with it, as the creation of the iterator may
992 * allocate memory, which thus needs to be freed.
994 * You can iterate both forwards, and backwards through the list,
995 * by using iterator_next or iterator_prev respectively.
997 * Lower numbered items are draw on top of higher number items in
998 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
999 * items may overlap). So, to test items, you should use
1001 * which lists the items top to bottom (in Z-order).
1002 * For drawing items, you should use
1004 * which lists the items bottom to top (in Z-order).
1005 * If you keep iterating over the items after the end-of-items
1006 * marker (-1) is returned, the iterator will start from the
1007 * beginning. Typically, you don't need to test for -1,
1008 * because iterator_{next,prev} will return TRUE if more items
1009 * are to be iterated over, or FALSE otherwise.
1011 * Note: the iterator is defined to be bidirectional. That is,
1012 * any number of prev followed by any number of next, or
1013 * five versa, should leave the iterator at the same item:
1014 * prev * n, next * n = next * n, prev * n
1016 * The iterator has a notion of a out-of-order, special item,
1017 * which sits at the start of the list. This is used in
1018 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1019 * which needs to be first, as it may overlap other items.
1021 * The code is a bit messy because we have:
1022 * - a special item to deal with
1023 * - simple range, or composite range
1025 * If you find bugs, or want to add features, please make sure you
1026 * always check/modify *both* iterator_prev, and iterator_next.
1030 * This function iterates through the items in increasing order,
1031 * but prefixed by the special item, then -1. That is:
1032 * special, 1, 2, 3, ..., n, -1.
1033 * Each item is listed only once.
1035 static inline BOOL iterator_next(ITERATOR* i)
1039 i->nItem = i->nSpecial;
1040 if (i->nItem != -1) return TRUE;
1042 if (i->nItem == i->nSpecial)
1044 if (i->ranges) i->index = 0;
1050 if (i->nItem == i->nSpecial) i->nItem++;
1051 if (i->nItem < i->range.upper) return TRUE;
1056 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1057 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1060 else if (i->nItem >= i->range.upper) goto end;
1062 i->nItem = i->range.lower;
1063 if (i->nItem >= 0) goto testitem;
1070 * This function iterates through the items in decreasing order,
1071 * followed by the special item, then -1. That is:
1072 * n, n-1, ..., 3, 2, 1, special, -1.
1073 * Each item is listed only once.
1075 static inline BOOL iterator_prev(ITERATOR* i)
1082 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1085 if (i->nItem == i->nSpecial)
1093 if (i->nItem == i->nSpecial) i->nItem--;
1094 if (i->nItem >= i->range.lower) return TRUE;
1100 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1103 else if (!start && i->nItem < i->range.lower) goto end;
1105 i->nItem = i->range.upper;
1106 if (i->nItem > 0) goto testitem;
1108 return (i->nItem = i->nSpecial) != -1;
1111 static RANGE iterator_range(ITERATOR* i)
1115 if (!i->ranges) return i->range;
1117 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1118 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1123 * Releases resources associated with this ierator.
1125 static inline void iterator_destroy(ITERATOR* i)
1127 ranges_destroy(i->ranges);
1131 * Create an empty iterator.
1133 static inline BOOL iterator_empty(ITERATOR* i)
1135 ZeroMemory(i, sizeof(*i));
1136 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1141 * Create an iterator over a range.
1143 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1151 * Create an iterator over a bunch of ranges.
1152 * Please note that the iterator will take ownership of the ranges,
1153 * and will free them upon destruction.
1155 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1163 * Creates an iterator over the items which intersect lprc.
1165 static BOOL iterator_frameditems(ITERATOR* i, LISTVIEW_INFO* infoPtr, const RECT *lprc)
1167 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1168 RECT frame = *lprc, rcItem, rcTemp;
1171 /* in case we fail, we want to return an empty iterator */
1172 if (!iterator_empty(i)) return FALSE;
1174 LISTVIEW_GetOrigin(infoPtr, &Origin);
1176 TRACE("(lprc=%s)\n", debugrect(lprc));
1177 OffsetRect(&frame, -Origin.x, -Origin.y);
1179 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1183 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1185 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1186 if (IntersectRect(&rcTemp, &rcItem, lprc))
1187 i->nSpecial = infoPtr->nFocusedItem;
1189 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1190 /* to do better here, we need to have PosX, and PosY sorted */
1191 TRACE("building icon ranges:\n");
1192 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1194 rcItem.left = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1195 rcItem.top = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1196 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1197 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1198 if (IntersectRect(&rcTemp, &rcItem, &frame))
1199 ranges_additem(i->ranges, nItem);
1203 else if (uView == LVS_REPORT)
1207 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1208 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1210 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1211 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1212 if (range.upper <= range.lower) return TRUE;
1213 if (!iterator_rangeitems(i, range)) return FALSE;
1214 TRACE(" report=%s\n", debugrange(&i->range));
1218 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1219 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1220 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1221 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1222 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1223 INT lower = nFirstCol * nPerCol + nFirstRow;
1227 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1228 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1230 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1232 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1233 TRACE("building list ranges:\n");
1234 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1236 item_range.lower = nCol * nPerCol + nFirstRow;
1237 if(item_range.lower >= infoPtr->nItemCount) break;
1238 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1239 TRACE(" list=%s\n", debugrange(&item_range));
1240 ranges_add(i->ranges, item_range);
1248 * Creates an iterator over the items which intersect the visible region of hdc.
1250 static BOOL iterator_visibleitems(ITERATOR *i, LISTVIEW_INFO *infoPtr, HDC hdc)
1252 POINT Origin, Position;
1253 RECT rcItem, rcClip;
1256 rgntype = GetClipBox(hdc, &rcClip);
1257 if (rgntype == NULLREGION) return iterator_empty(i);
1258 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1259 if (rgntype == SIMPLEREGION) return TRUE;
1261 /* first deal with the special item */
1262 if (i->nSpecial != -1)
1264 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1265 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1268 /* if we can't deal with the region, we'll just go with the simple range */
1269 LISTVIEW_GetOrigin(infoPtr, &Origin);
1270 TRACE("building visible range:\n");
1271 if (!i->ranges && i->range.lower < i->range.upper)
1273 if (!(i->ranges = ranges_create(50))) return TRUE;
1274 if (!ranges_add(i->ranges, i->range))
1276 ranges_destroy(i->ranges);
1282 /* now delete the invisible items from the list */
1283 while(iterator_next(i))
1285 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1286 rcItem.left = Position.x + Origin.x;
1287 rcItem.top = Position.y + Origin.y;
1288 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1289 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1290 if (!RectVisible(hdc, &rcItem))
1291 ranges_delitem(i->ranges, i->nItem);
1293 /* the iterator should restart on the next iterator_next */
1299 /******** Misc helper functions ************************************/
1301 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1302 WPARAM wParam, LPARAM lParam, BOOL isW)
1304 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1305 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1308 static inline BOOL is_autoarrange(LISTVIEW_INFO *infoPtr)
1310 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1312 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1313 (uView == LVS_ICON || uView == LVS_SMALLICON);
1316 /******** Internal API functions ************************************/
1318 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(LISTVIEW_INFO *infoPtr, INT nSubItem)
1320 static COLUMN_INFO mainItem;
1322 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1323 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1324 return (COLUMN_INFO *)DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1327 static inline void LISTVIEW_GetHeaderRect(LISTVIEW_INFO *infoPtr, INT nSubItem, RECT *lprc)
1329 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1332 static inline BOOL LISTVIEW_GetItemW(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1334 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1337 /* Listview invalidation functions: use _only_ these functions to invalidate */
1339 static inline BOOL is_redrawing(LISTVIEW_INFO *infoPtr)
1341 return infoPtr->bRedraw;
1344 static inline void LISTVIEW_InvalidateRect(LISTVIEW_INFO *infoPtr, const RECT* rect)
1346 if(!is_redrawing(infoPtr)) return;
1347 TRACE(" invalidating rect=%s\n", debugrect(rect));
1348 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1351 static inline void LISTVIEW_InvalidateItem(LISTVIEW_INFO *infoPtr, INT nItem)
1355 if(!is_redrawing(infoPtr)) return;
1356 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1357 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1360 static inline void LISTVIEW_InvalidateSubItem(LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1362 POINT Origin, Position;
1365 if(!is_redrawing(infoPtr)) return;
1366 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1367 LISTVIEW_GetOrigin(infoPtr, &Origin);
1368 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1369 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1371 rcBox.bottom = infoPtr->nItemHeight;
1372 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1373 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1376 static inline void LISTVIEW_InvalidateList(LISTVIEW_INFO *infoPtr)
1378 LISTVIEW_InvalidateRect(infoPtr, NULL);
1381 static inline void LISTVIEW_InvalidateColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
1385 if(!is_redrawing(infoPtr)) return;
1386 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1387 rcCol.top = infoPtr->rcList.top;
1388 rcCol.bottom = infoPtr->rcList.bottom;
1389 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1394 * Retrieves the number of items that can fit vertically in the client area.
1397 * [I] infoPtr : valid pointer to the listview structure
1400 * Number of items per row.
1402 static inline INT LISTVIEW_GetCountPerRow(LISTVIEW_INFO *infoPtr)
1404 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1406 return max(nListWidth/infoPtr->nItemWidth, 1);
1411 * Retrieves the number of items that can fit horizontally in the client
1415 * [I] infoPtr : valid pointer to the listview structure
1418 * Number of items per column.
1420 static inline INT LISTVIEW_GetCountPerColumn(LISTVIEW_INFO *infoPtr)
1422 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1424 return max(nListHeight / infoPtr->nItemHeight, 1);
1428 /*************************************************************************
1429 * LISTVIEW_ProcessLetterKeys
1431 * Processes keyboard messages generated by pressing the letter keys
1433 * What this does is perform a case insensitive search from the
1434 * current position with the following quirks:
1435 * - If two chars or more are pressed in quick succession we search
1436 * for the corresponding string (e.g. 'abc').
1437 * - If there is a delay we wipe away the current search string and
1438 * restart with just that char.
1439 * - If the user keeps pressing the same character, whether slowly or
1440 * fast, so that the search string is entirely composed of this
1441 * character ('aaaaa' for instance), then we search for first item
1442 * that starting with that character.
1443 * - If the user types the above character in quick succession, then
1444 * we must also search for the corresponding string ('aaaaa'), and
1445 * go to that string if there is a match.
1448 * [I] hwnd : handle to the window
1449 * [I] charCode : the character code, the actual character
1450 * [I] keyData : key data
1458 * - The current implementation has a list of characters it will
1459 * accept and it ignores averything else. In particular it will
1460 * ignore accentuated characters which seems to match what
1461 * Windows does. But I'm not sure it makes sense to follow
1463 * - We don't sound a beep when the search fails.
1467 * TREEVIEW_ProcessLetterKeys
1469 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1474 WCHAR buffer[MAX_PATH];
1475 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1477 /* simple parameter checking */
1478 if (!charCode || !keyData) return 0;
1480 /* only allow the valid WM_CHARs through */
1481 if (!isalnum(charCode) &&
1482 charCode != '.' && charCode != '`' && charCode != '!' &&
1483 charCode != '@' && charCode != '#' && charCode != '$' &&
1484 charCode != '%' && charCode != '^' && charCode != '&' &&
1485 charCode != '*' && charCode != '(' && charCode != ')' &&
1486 charCode != '-' && charCode != '_' && charCode != '+' &&
1487 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1488 charCode != '}' && charCode != '[' && charCode != '{' &&
1489 charCode != '/' && charCode != '?' && charCode != '>' &&
1490 charCode != '<' && charCode != ',' && charCode != '~')
1493 /* if there's one item or less, there is no where to go */
1494 if (infoPtr->nItemCount <= 1) return 0;
1496 /* update the search parameters */
1497 infoPtr->lastKeyPressTimestamp = GetTickCount();
1498 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1499 if (infoPtr->nSearchParamLength < MAX_PATH)
1500 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1501 if (infoPtr->charCode != charCode)
1502 infoPtr->charCode = charCode = 0;
1504 infoPtr->charCode=charCode;
1505 infoPtr->szSearchParam[0]=charCode;
1506 infoPtr->nSearchParamLength=1;
1507 /* Redundant with the 1 char string */
1511 /* and search from the current position */
1513 if (infoPtr->nFocusedItem >= 0) {
1514 endidx=infoPtr->nFocusedItem;
1516 /* if looking for single character match,
1517 * then we must always move forward
1519 if (infoPtr->nSearchParamLength == 1)
1522 endidx=infoPtr->nItemCount;
1526 if (idx == infoPtr->nItemCount) {
1527 if (endidx == infoPtr->nItemCount || endidx == 0)
1533 item.mask = LVIF_TEXT;
1536 item.pszText = buffer;
1537 item.cchTextMax = MAX_PATH;
1538 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1540 /* check for a match */
1541 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1544 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1545 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1546 /* This would work but we must keep looking for a longer match */
1550 } while (idx != endidx);
1553 LISTVIEW_KeySelection(infoPtr, nItem);
1558 /*************************************************************************
1559 * LISTVIEW_UpdateHeaderSize [Internal]
1561 * Function to resize the header control
1564 * [I] hwnd : handle to a window
1565 * [I] nNewScrollPos : scroll pos to set
1570 static void LISTVIEW_UpdateHeaderSize(LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1575 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1577 GetWindowRect(infoPtr->hwndHeader, &winRect);
1578 point[0].x = winRect.left;
1579 point[0].y = winRect.top;
1580 point[1].x = winRect.right;
1581 point[1].y = winRect.bottom;
1583 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1584 point[0].x = -nNewScrollPos;
1585 point[1].x += nNewScrollPos;
1587 SetWindowPos(infoPtr->hwndHeader,0,
1588 point[0].x,point[0].y,point[1].x,point[1].y,
1589 SWP_NOZORDER | SWP_NOACTIVATE);
1594 * Update the scrollbars. This functions should be called whenever
1595 * the content, size or view changes.
1598 * [I] infoPtr : valid pointer to the listview structure
1603 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *infoPtr)
1605 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1606 SCROLLINFO horzInfo, vertInfo;
1608 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1610 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1611 horzInfo.cbSize = sizeof(SCROLLINFO);
1612 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1614 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1615 if (uView == LVS_LIST)
1617 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1618 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1620 /* scroll by at least one column per page */
1621 if(horzInfo.nPage < infoPtr->nItemWidth)
1622 horzInfo.nPage = infoPtr->nItemWidth;
1624 horzInfo.nPage /= infoPtr->nItemWidth;
1626 else if (uView == LVS_REPORT)
1628 horzInfo.nMax = infoPtr->nItemWidth;
1630 else /* LVS_ICON, or LVS_SMALLICON */
1634 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1637 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1638 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1639 SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1640 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1642 /* Setting the horizontal scroll can change the listview size
1643 * (and potentially everything else) so we need to recompute
1644 * everything again for the vertical scroll
1647 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1648 vertInfo.cbSize = sizeof(SCROLLINFO);
1649 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1651 if (uView == LVS_REPORT)
1653 vertInfo.nMax = infoPtr->nItemCount;
1655 /* scroll by at least one page */
1656 if(vertInfo.nPage < infoPtr->nItemHeight)
1657 vertInfo.nPage = infoPtr->nItemHeight;
1659 vertInfo.nPage /= infoPtr->nItemHeight;
1661 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1665 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1668 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1669 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1670 SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1671 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1673 /* Update the Header Control */
1674 if (uView == LVS_REPORT)
1676 horzInfo.fMask = SIF_POS;
1677 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1678 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1685 * Shows/hides the focus rectangle.
1688 * [I] infoPtr : valid pointer to the listview structure
1689 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1694 static void LISTVIEW_ShowFocusRect(LISTVIEW_INFO *infoPtr, BOOL fShow)
1696 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1699 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1701 if (infoPtr->nFocusedItem < 0) return;
1703 /* we need some gymnastics in ICON mode to handle large items */
1704 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1708 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1709 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1711 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1716 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1718 /* for some reason, owner draw should work only in report mode */
1719 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1724 item.iItem = infoPtr->nFocusedItem;
1726 item.mask = LVIF_PARAM;
1727 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1729 ZeroMemory(&dis, sizeof(dis));
1730 dis.CtlType = ODT_LISTVIEW;
1731 dis.CtlID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
1732 dis.itemID = item.iItem;
1733 dis.itemAction = ODA_FOCUS;
1734 if (fShow) dis.itemState |= ODS_FOCUS;
1735 dis.hwndItem = infoPtr->hwndSelf;
1737 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1738 dis.itemData = item.lParam;
1740 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1744 DrawFocusRect(hdc, &infoPtr->rcFocus);
1747 ReleaseDC(infoPtr->hwndSelf, hdc);
1751 * Invalidates all visible selected items.
1753 static void LISTVIEW_InvalidateSelectedItems(LISTVIEW_INFO *infoPtr)
1757 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1758 while(iterator_next(&i))
1760 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1761 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1763 iterator_destroy(&i);
1768 * DESCRIPTION: [INTERNAL]
1769 * Computes an item's (left,top) corner, relative to rcView.
1770 * That is, the position has NOT been made relative to the Origin.
1771 * This is deliberate, to avoid computing the Origin over, and
1772 * over again, when this function is call in a loop. Instead,
1773 * one ca factor the computation of the Origin before the loop,
1774 * and offset the value retured by this function, on every iteration.
1777 * [I] infoPtr : valid pointer to the listview structure
1778 * [I] nItem : item number
1779 * [O] lpptOrig : item top, left corner
1784 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1786 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1788 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1790 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1792 lpptPosition->x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1793 lpptPosition->y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1795 else if (uView == LVS_LIST)
1797 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1798 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1799 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1801 else /* LVS_REPORT */
1803 lpptPosition->x = 0;
1804 lpptPosition->y = nItem * infoPtr->nItemHeight;
1809 * DESCRIPTION: [INTERNAL]
1810 * Compute the rectangles of an item. This is to localize all
1811 * the computations in one place. If you are not interested in some
1812 * of these values, simply pass in a NULL -- the fucntion is smart
1813 * enough to compute only what's necessary. The function computes
1814 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1815 * one, the BOX rectangle. This rectangle is very cheap to compute,
1816 * and is guaranteed to contain all the other rectangles. Computing
1817 * the ICON rect is also cheap, but all the others are potentaily
1818 * expensive. This gives an easy and effective optimization when
1819 * searching (like point inclusion, or rectangle intersection):
1820 * first test against the BOX, and if TRUE, test agains the desired
1822 * If the function does not have all the necessary information
1823 * to computed the requested rectangles, will crash with a
1824 * failed assertion. This is done so we catch all programming
1825 * errors, given that the function is called only from our code.
1827 * We have the following 'special' meanings for a few fields:
1828 * * If LVIS_FOCUSED is set, we assume the item has the focus
1829 * This is important in ICON mode, where it might get a larger
1830 * then usual rectange
1832 * Please note that subitem support works only in REPORT mode.
1835 * [I] infoPtr : valid pointer to the listview structure
1836 * [I] lpLVItem : item to compute the measures for
1837 * [O] lprcBox : ptr to Box rectangle
1838 * The internal LVIR_BOX rectangle
1839 * [0] lprcState : ptr to State icon rectangle
1840 * The internal LVIR_STATE rectangle
1841 * [O] lprcIcon : ptr to Icon rectangle
1842 * Same as LVM_GETITEMRECT with LVIR_ICON
1843 * [O] lprcLabel : ptr to Label rectangle
1844 * Same as LVM_GETITEMRECT with LVIR_LABEL
1849 static void LISTVIEW_GetItemMetrics(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1850 LPRECT lprcBox, LPRECT lprcState,
1851 LPRECT lprcIcon, LPRECT lprcLabel)
1853 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1854 BOOL doState = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1855 RECT Box, State, Icon, Label;
1856 COLUMN_INFO *lpColumnInfo = NULL;
1858 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1860 /* Be smart and try to figure out the minimum we have to do */
1861 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1862 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1864 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1865 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1867 if (lprcLabel) doLabel = TRUE;
1868 if (doLabel || lprcIcon) doIcon = TRUE;
1869 if (doIcon || lprcState) doState = TRUE;
1871 /************************************************************/
1872 /* compute the box rectangle (it should be cheap to do) */
1873 /************************************************************/
1874 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1875 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1877 if (lpLVItem->iSubItem)
1879 Box = lpColumnInfo->rcHeader;
1884 Box.right = infoPtr->nItemWidth;
1887 Box.bottom = infoPtr->nItemHeight;
1889 /************************************************************/
1890 /* compute STATEICON bounding box */
1891 /************************************************************/
1894 if (uView == LVS_ICON)
1896 State.left = Box.left - infoPtr->iconStateSize.cx - 2;
1897 if (infoPtr->himlNormal)
1898 State.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1899 State.top = Box.top + infoPtr->iconSize.cy - infoPtr->iconStateSize.cy + 4;
1903 /* we need the ident in report mode, if we don't have it, we fail */
1904 State.left = Box.left;
1905 if (uView == LVS_REPORT)
1907 if (lpLVItem->iSubItem == 0)
1909 State.left += REPORT_MARGINX;
1910 assert(lpLVItem->mask & LVIF_INDENT);
1911 State.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
1914 State.top = Box.top;
1916 State.right = State.left;
1917 State.bottom = State.top;
1918 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1920 State.right += infoPtr->iconStateSize.cx;
1921 State.bottom += infoPtr->iconStateSize.cy;
1923 if (lprcState) *lprcState = State;
1924 TRACE(" - state=%s\n", debugrect(&State));
1927 /************************************************************/
1928 /* compute ICON bounding box (ala LVM_GETITEMRECT) */
1929 /************************************************************/
1932 if (uView == LVS_ICON)
1934 Icon.left = Box.left;
1935 if (infoPtr->himlNormal)
1936 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1937 Icon.top = Box.top + ICON_TOP_PADDING;
1938 Icon.right = Icon.left;
1939 Icon.bottom = Icon.top;
1940 if (infoPtr->himlNormal)
1942 Icon.right += infoPtr->iconSize.cx;
1943 Icon.bottom += infoPtr->iconSize.cy;
1946 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1948 Icon.left = State.right;
1950 Icon.right = Icon.left;
1951 if (infoPtr->himlSmall &&
1952 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
1953 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
1954 Icon.right += infoPtr->iconSize.cx;
1955 Icon.bottom = Icon.top + infoPtr->nItemHeight;
1957 if(lprcIcon) *lprcIcon = Icon;
1958 TRACE(" - icon=%s\n", debugrect(&Icon));
1961 /************************************************************/
1962 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
1963 /************************************************************/
1966 SIZE labelSize = { 0, 0 };
1968 /* calculate how far to the right can the label strech */
1969 Label.right = Box.right;
1970 if (uView == LVS_REPORT)
1972 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
1975 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
1977 labelSize.cx = infoPtr->nItemWidth;
1978 labelSize.cy = infoPtr->nItemHeight;
1982 /* we need the text in non owner draw mode */
1983 assert(lpLVItem->mask & LVIF_TEXT);
1984 if (is_textT(lpLVItem->pszText, TRUE))
1986 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1987 HDC hdc = GetDC(infoPtr->hwndSelf);
1988 HFONT hOldFont = SelectObject(hdc, hFont);
1992 /* compute rough rectangle where the label will go */
1993 SetRectEmpty(&rcText);
1994 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
1995 rcText.bottom = infoPtr->nItemHeight;
1996 if (uView == LVS_ICON)
1997 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
1999 /* now figure out the flags */
2000 if (uView == LVS_ICON)
2001 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2003 uFormat = LV_SL_DT_FLAGS;
2005 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2007 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2008 labelSize.cy = rcText.bottom - rcText.top;
2010 SelectObject(hdc, hOldFont);
2011 ReleaseDC(infoPtr->hwndSelf, hdc);
2015 if (uView == LVS_ICON)
2017 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2018 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2019 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2020 Label.right = Label.left + labelSize.cx;
2021 Label.bottom = Label.top + infoPtr->nItemHeight;
2022 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2024 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2025 labelSize.cy /= infoPtr->ntmHeight;
2026 labelSize.cy = max(labelSize.cy, 1);
2027 labelSize.cy *= infoPtr->ntmHeight;
2029 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2031 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2033 Label.left = Icon.right;
2034 Label.top = Box.top;
2035 Label.right = min(Label.left + labelSize.cx, Label.right);
2036 Label.bottom = Label.top + infoPtr->nItemHeight;
2039 if (lprcLabel) *lprcLabel = Label;
2040 TRACE(" - label=%s\n", debugrect(&Label));
2043 /* Fix the Box if necessary */
2046 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2047 else *lprcBox = Box;
2049 TRACE(" - box=%s\n", debugrect(&Box));
2053 * DESCRIPTION: [INTERNAL]
2056 * [I] infoPtr : valid pointer to the listview structure
2057 * [I] nItem : item number
2058 * [O] lprcBox : ptr to Box rectangle
2063 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2065 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2066 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2067 POINT Position, Origin;
2070 LISTVIEW_GetOrigin(infoPtr, &Origin);
2071 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2073 /* Be smart and try to figure out the minimum we have to do */
2075 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2076 lvItem.mask |= LVIF_TEXT;
2077 lvItem.iItem = nItem;
2078 lvItem.iSubItem = 0;
2079 lvItem.pszText = szDispText;
2080 lvItem.cchTextMax = DISP_TEXT_SIZE;
2081 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2082 if (uView == LVS_ICON)
2084 lvItem.mask |= LVIF_STATE;
2085 lvItem.stateMask = LVIS_FOCUSED;
2086 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2088 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0);
2090 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2096 * Returns the current icon position, and advances it along the top.
2097 * The returned position is not offset by Origin.
2100 * [I] infoPtr : valid pointer to the listview structure
2101 * [O] lpPos : will get the current icon position
2106 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2108 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2110 *lpPos = infoPtr->currIconPos;
2112 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2113 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2115 infoPtr->currIconPos.x = 0;
2116 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2122 * Returns the current icon position, and advances it down the left edge.
2123 * The returned position is not offset by Origin.
2126 * [I] infoPtr : valid pointer to the listview structure
2127 * [O] lpPos : will get the current icon position
2132 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2134 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2136 *lpPos = infoPtr->currIconPos;
2138 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2139 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2141 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2142 infoPtr->currIconPos.y = 0;
2148 * Moves an icon to the specified position.
2149 * It takes care of invalidating the item, etc.
2152 * [I] infoPtr : valid pointer to the listview structure
2153 * [I] nItem : the item to move
2154 * [I] lpPos : the new icon position
2155 * [I] isNew : flags the item as being new
2161 static BOOL LISTVIEW_MoveIconTo(LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2167 old.x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2168 old.y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2170 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2171 LISTVIEW_InvalidateItem(infoPtr, nItem);
2174 /* Allocating a POINTER for every item is too resource intensive,
2175 * so we'll keep the (x,y) in different arrays */
2176 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)lppt->x)) return FALSE;
2177 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)lppt->y)) return FALSE;
2179 LISTVIEW_InvalidateItem(infoPtr, nItem);
2186 * Arranges listview items in icon display mode.
2189 * [I] infoPtr : valid pointer to the listview structure
2190 * [I] nAlignCode : alignment code
2196 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2198 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2199 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2203 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2205 TRACE("nAlignCode=%d\n", nAlignCode);
2207 if (nAlignCode == LVA_DEFAULT)
2209 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2210 else nAlignCode = LVA_ALIGNTOP;
2215 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2216 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2217 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2218 default: return FALSE;
2221 infoPtr->bAutoarrange = TRUE;
2222 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2223 for (i = 0; i < infoPtr->nItemCount; i++)
2225 next_pos(infoPtr, &pos);
2226 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2234 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2237 * [I] infoPtr : valid pointer to the listview structure
2238 * [O] lprcView : bounding rectangle
2244 static void LISTVIEW_GetAreaRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2248 SetRectEmpty(lprcView);
2250 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2254 for (i = 0; i < infoPtr->nItemCount; i++)
2256 x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, i);
2257 y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, i);
2258 lprcView->right = max(lprcView->right, x);
2259 lprcView->bottom = max(lprcView->bottom, y);
2261 if (infoPtr->nItemCount > 0)
2263 lprcView->right += infoPtr->nItemWidth;
2264 lprcView->bottom += infoPtr->nItemHeight;
2269 y = LISTVIEW_GetCountPerColumn(infoPtr);
2270 x = infoPtr->nItemCount / y;
2271 if (infoPtr->nItemCount % y) x++;
2272 lprcView->right = x * infoPtr->nItemWidth;
2273 lprcView->bottom = y * infoPtr->nItemHeight;
2277 lprcView->right = infoPtr->nItemWidth;
2278 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2285 * Retrieves the bounding rectangle of all the items.
2288 * [I] infoPtr : valid pointer to the listview structure
2289 * [O] lprcView : bounding rectangle
2295 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2299 TRACE("(lprcView=%p)\n", lprcView);
2301 if (!lprcView) return FALSE;
2303 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2304 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2305 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2307 TRACE("lprcView=%s\n", debugrect(lprcView));
2314 * Retrieves the subitem pointer associated with the subitem index.
2317 * [I] hdpaSubItems : DPA handle for a specific item
2318 * [I] nSubItem : index of subitem
2321 * SUCCESS : subitem pointer
2324 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2326 SUBITEM_INFO *lpSubItem;
2329 /* we should binary search here if need be */
2330 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2332 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
2333 if (lpSubItem->iSubItem == nSubItem)
2343 * Caclulates the desired item width.
2346 * [I] infoPtr : valid pointer to the listview structure
2349 * The desired item width.
2351 static INT LISTVIEW_CalculateItemWidth(LISTVIEW_INFO *infoPtr)
2353 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2356 TRACE("uView=%d\n", uView);
2358 if (uView == LVS_ICON)
2359 nItemWidth = infoPtr->iconSpacing.cx;
2360 else if (uView == LVS_REPORT)
2364 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2366 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2367 nItemWidth = rcHeader.right;
2370 else /* LVS_SMALLICON, or LVS_LIST */
2374 for (i = 0; i < infoPtr->nItemCount; i++)
2375 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2377 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2378 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2380 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2383 return max(nItemWidth, 1);
2388 * Caclulates the desired item height.
2391 * [I] infoPtr : valid pointer to the listview structure
2394 * The desired item height.
2396 static INT LISTVIEW_CalculateItemHeight(LISTVIEW_INFO *infoPtr)
2398 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2401 TRACE("uView=%d\n", uView);
2403 if (uView == LVS_ICON)
2404 nItemHeight = infoPtr->iconSpacing.cy;
2407 nItemHeight = infoPtr->ntmHeight;
2408 if (infoPtr->himlState)
2409 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2410 if (infoPtr->himlSmall)
2411 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2412 if (infoPtr->himlState || infoPtr->himlSmall)
2413 nItemHeight += HEIGHT_PADDING;
2416 return max(nItemHeight, 1);
2421 * Updates the width, and height of an item.
2424 * [I] infoPtr : valid pointer to the listview structure
2429 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2431 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2432 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2438 * Retrieves and saves important text metrics info for the current
2442 * [I] infoPtr : valid pointer to the listview structure
2445 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2447 HDC hdc = GetDC(infoPtr->hwndSelf);
2448 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2449 HFONT hOldFont = SelectObject(hdc, hFont);
2452 if (GetTextMetricsW(hdc, &tm))
2454 infoPtr->ntmHeight = tm.tmHeight;
2455 infoPtr->ntmAveCharWidth = tm.tmAveCharWidth;
2457 SelectObject(hdc, hOldFont);
2458 ReleaseDC(infoPtr->hwndSelf, hdc);
2460 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2465 * A compare function for ranges
2468 * [I] range1 : pointer to range 1;
2469 * [I] range2 : pointer to range 2;
2473 * > 0 : if range 1 > range 2
2474 * < 0 : if range 2 > range 1
2475 * = 0 : if range intersects range 2
2477 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2481 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2483 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2488 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2494 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2496 #define ranges_check(ranges, desc) do { } while(0)
2499 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2504 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2506 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2507 ranges_dump(ranges);
2508 prev = (RANGE *)DPA_GetPtr(ranges->hdpa, 0);
2509 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2510 assert (prev->lower >= 0 && prev->lower < prev->upper);
2511 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2513 curr = (RANGE *)DPA_GetPtr(ranges->hdpa, i);
2514 assert (prev->upper <= curr->lower);
2515 assert (curr->lower < curr->upper);
2518 TRACE("--- Done checking---\n");
2521 static RANGES ranges_create(int count)
2523 RANGES ranges = (RANGES)Alloc(sizeof(struct tagRANGES));
2524 if (!ranges) return NULL;
2525 ranges->hdpa = DPA_Create(count);
2526 if (ranges->hdpa) return ranges;
2531 static void ranges_clear(RANGES ranges)
2535 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2536 Free(DPA_GetPtr(ranges->hdpa, i));
2537 DPA_DeleteAllPtrs(ranges->hdpa);
2541 static void ranges_destroy(RANGES ranges)
2543 if (!ranges) return;
2544 ranges_clear(ranges);
2545 DPA_Destroy(ranges->hdpa);
2549 static RANGES ranges_clone(RANGES ranges)
2554 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2556 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2558 RANGE *newrng = (RANGE *)Alloc(sizeof(RANGE));
2559 if (!newrng) goto fail;
2560 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2561 DPA_SetPtr(clone->hdpa, i, newrng);
2566 TRACE ("clone failed\n");
2567 ranges_destroy(clone);
2571 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2575 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2576 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2581 static void ranges_dump(RANGES ranges)
2585 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2586 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2589 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2591 RANGE srchrng = { nItem, nItem + 1 };
2593 TRACE("(nItem=%d)\n", nItem);
2594 ranges_check(ranges, "before contain");
2595 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2598 static INT ranges_itemcount(RANGES ranges)
2602 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2604 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2605 count += sel->upper - sel->lower;
2611 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2613 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2616 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2617 if (index == -1) return TRUE;
2619 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2621 chkrng = DPA_GetPtr(ranges->hdpa, index);
2622 if (chkrng->lower >= nItem)
2623 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2624 if (chkrng->upper > nItem)
2625 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2630 static BOOL ranges_add(RANGES ranges, RANGE range)
2635 TRACE("(%s)\n", debugrange(&range));
2636 ranges_check(ranges, "before add");
2638 /* try find overlapping regions first */
2639 srchrgn.lower = range.lower - 1;
2640 srchrgn.upper = range.upper + 1;
2641 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2647 TRACE("Adding new range\n");
2649 /* create the brand new range to insert */
2650 newrgn = (RANGE *)Alloc(sizeof(RANGE));
2651 if(!newrgn) goto fail;
2654 /* figure out where to insert it */
2655 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2656 TRACE("index=%d\n", index);
2657 if (index == -1) index = 0;
2659 /* and get it over with */
2660 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2668 RANGE *chkrgn, *mrgrgn;
2669 INT fromindex, mergeindex;
2671 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2672 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2674 chkrgn->lower = min(range.lower, chkrgn->lower);
2675 chkrgn->upper = max(range.upper, chkrgn->upper);
2677 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2679 /* merge now common anges */
2681 srchrgn.lower = chkrgn->lower - 1;
2682 srchrgn.upper = chkrgn->upper + 1;
2686 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2687 if (mergeindex == -1) break;
2688 if (mergeindex == index)
2690 fromindex = index + 1;
2694 TRACE("Merge with index %i\n", mergeindex);
2696 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2697 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2698 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2700 DPA_DeletePtr(ranges->hdpa, mergeindex);
2701 if (mergeindex < index) index --;
2705 ranges_check(ranges, "after add");
2709 ranges_check(ranges, "failed add");
2713 static BOOL ranges_del(RANGES ranges, RANGE range)
2718 TRACE("(%s)\n", debugrange(&range));
2719 ranges_check(ranges, "before del");
2721 /* we don't use DPAS_SORTED here, since we need *
2722 * to find the first overlapping range */
2723 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2726 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2728 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2730 /* case 1: Same range */
2731 if ( (chkrgn->upper == range.upper) &&
2732 (chkrgn->lower == range.lower) )
2734 DPA_DeletePtr(ranges->hdpa, index);
2737 /* case 2: engulf */
2738 else if ( (chkrgn->upper <= range.upper) &&
2739 (chkrgn->lower >= range.lower) )
2741 DPA_DeletePtr(ranges->hdpa, index);
2743 /* case 3: overlap upper */
2744 else if ( (chkrgn->upper <= range.upper) &&
2745 (chkrgn->lower < range.lower) )
2747 chkrgn->upper = range.lower;
2749 /* case 4: overlap lower */
2750 else if ( (chkrgn->upper > range.upper) &&
2751 (chkrgn->lower >= range.lower) )
2753 chkrgn->lower = range.upper;
2756 /* case 5: fully internal */
2759 RANGE tmprgn = *chkrgn, *newrgn;
2761 if (!(newrgn = (RANGE *)Alloc(sizeof(RANGE)))) goto fail;
2762 newrgn->lower = chkrgn->lower;
2763 newrgn->upper = range.lower;
2764 chkrgn->lower = range.upper;
2765 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2774 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2777 ranges_check(ranges, "after del");
2781 ranges_check(ranges, "failed del");
2787 * Removes all selection ranges
2790 * [I] infoPtr : valid pointer to the listview structure
2791 * [I] toSkip : item range to skip removing the selection
2797 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2806 lvItem.stateMask = LVIS_SELECTED;
2808 /* need to clone the DPA because callbacks can change it */
2809 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2810 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2811 while(iterator_next(&i))
2812 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2813 /* note that the iterator destructor will free the cloned range */
2814 iterator_destroy(&i);
2819 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2823 if (!(toSkip = ranges_create(1))) return FALSE;
2824 if (nItem != -1) ranges_additem(toSkip, nItem);
2825 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2826 ranges_destroy(toSkip);
2830 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2832 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2837 * Retrieves the number of items that are marked as selected.
2840 * [I] infoPtr : valid pointer to the listview structure
2843 * Number of items selected.
2845 static INT LISTVIEW_GetSelectedCount(LISTVIEW_INFO *infoPtr)
2847 INT nSelectedCount = 0;
2849 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2852 for (i = 0; i < infoPtr->nItemCount; i++)
2854 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2859 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2861 TRACE("nSelectedCount=%d\n", nSelectedCount);
2862 return nSelectedCount;
2867 * Manages the item focus.
2870 * [I] infoPtr : valid pointer to the listview structure
2871 * [I] nItem : item index
2874 * TRUE : focused item changed
2875 * FALSE : focused item has NOT changed
2877 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2879 INT oldFocus = infoPtr->nFocusedItem;
2882 if (nItem == infoPtr->nFocusedItem) return FALSE;
2884 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2885 lvItem.stateMask = LVIS_FOCUSED;
2886 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
2888 return oldFocus != infoPtr->nFocusedItem;
2891 /* Helper function for LISTVIEW_ShiftIndices *only* */
2892 static INT shift_item(LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2894 if (nShiftItem < nItem) return nShiftItem;
2896 if (nShiftItem > nItem) return nShiftItem + direction;
2898 if (direction > 0) return nShiftItem + direction;
2900 return min(nShiftItem, infoPtr->nItemCount - 1);
2905 * Updates the various indices after an item has been inserted or deleted.
2908 * [I] infoPtr : valid pointer to the listview structure
2909 * [I] nItem : item index
2910 * [I] direction : Direction of shift, +1 or -1.
2915 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
2920 /* temporarily disable change notification while shifting items */
2921 bOldChange = infoPtr->bDoChangeNotify;
2922 infoPtr->bDoChangeNotify = FALSE;
2924 TRACE("Shifting %iu, %i steps\n", nItem, direction);
2926 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
2928 assert(abs(direction) == 1);
2930 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
2932 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
2933 if (nNewFocus != infoPtr->nFocusedItem)
2934 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
2936 /* But we are not supposed to modify nHotItem! */
2938 infoPtr->bDoChangeNotify = bOldChange;
2944 * Adds a block of selections.
2947 * [I] infoPtr : valid pointer to the listview structure
2948 * [I] nItem : item index
2953 static void LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2955 INT nFirst = min(infoPtr->nSelectionMark, nItem);
2956 INT nLast = max(infoPtr->nSelectionMark, nItem);
2960 if (nFirst == -1) nFirst = nItem;
2962 item.state = LVIS_SELECTED;
2963 item.stateMask = LVIS_SELECTED;
2965 /* FIXME: this is not correct LVS_OWNERDATA
2966 * setting the item states individually will generate
2967 * a LVN_ITEMCHANGED notification for each one. Instead,
2968 * we have to send a LVN_ODSTATECHANGED notification.
2969 * See MSDN documentation for LVN_ITEMCHANGED.
2971 for (i = nFirst; i <= nLast; i++)
2972 LISTVIEW_SetItemState(infoPtr,i,&item);
2978 * Sets a single group selection.
2981 * [I] infoPtr : valid pointer to the listview structure
2982 * [I] nItem : item index
2987 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2989 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2994 if (!(selection = ranges_create(100))) return;
2996 item.state = LVIS_SELECTED;
2997 item.stateMask = LVIS_SELECTED;
2999 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3001 if (infoPtr->nSelectionMark == -1)
3003 infoPtr->nSelectionMark = nItem;
3004 ranges_additem(selection, nItem);
3010 sel.lower = min(infoPtr->nSelectionMark, nItem);
3011 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3012 ranges_add(selection, sel);
3017 RECT rcItem, rcSel, rcSelMark;
3020 rcItem.left = LVIR_BOUNDS;
3021 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3022 rcSelMark.left = LVIR_BOUNDS;
3023 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3024 UnionRect(&rcSel, &rcItem, &rcSelMark);
3025 iterator_frameditems(&i, infoPtr, &rcSel);
3026 while(iterator_next(&i))
3028 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3029 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3031 iterator_destroy(&i);
3034 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3035 iterator_rangesitems(&i, selection);
3036 while(iterator_next(&i))
3037 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3038 /* this will also destroy the selection */
3039 iterator_destroy(&i);
3041 LISTVIEW_SetItemFocus(infoPtr, nItem);
3046 * Sets a single selection.
3049 * [I] infoPtr : valid pointer to the listview structure
3050 * [I] nItem : item index
3055 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3059 TRACE("nItem=%d\n", nItem);
3061 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3063 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3064 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3065 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3067 infoPtr->nSelectionMark = nItem;
3072 * Set selection(s) with keyboard.
3075 * [I] infoPtr : valid pointer to the listview structure
3076 * [I] nItem : item index
3079 * SUCCESS : TRUE (needs to be repainted)
3080 * FAILURE : FALSE (nothing has changed)
3082 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
3084 /* FIXME: pass in the state */
3085 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3086 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3087 BOOL bResult = FALSE;
3089 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3091 if (infoPtr->dwStyle & LVS_SINGLESEL)
3094 LISTVIEW_SetSelection(infoPtr, nItem);
3101 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3105 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3110 LISTVIEW_SetSelection(infoPtr, nItem);
3113 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3116 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3123 * Called when the mouse is being actively tracked and has hovered for a specified
3127 * [I] infoPtr : valid pointer to the listview structure
3128 * [I] fwKeys : key indicator
3129 * [I] pts : mouse position
3132 * 0 if the message was processed, non-zero if there was an error
3135 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3136 * over the item for a certain period of time.
3139 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKyes, POINTS pts)
3141 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
3142 /* FIXME: select the item!!! */
3143 /*LISTVIEW_GetItemAtPt(infoPtr, pt)*/;
3150 * Called whenever WM_MOUSEMOVE is received.
3153 * [I] infoPtr : valid pointer to the listview structure
3154 * [I] fwKeys : key indicator
3155 * [I] pts : mouse position
3158 * 0 if the message is processed, non-zero if there was an error
3160 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, POINTS pts)
3162 TRACKMOUSEEVENT trackinfo;
3164 /* see if we are supposed to be tracking mouse hovering */
3165 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
3166 /* fill in the trackinfo struct */
3167 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3168 trackinfo.dwFlags = TME_QUERY;
3169 trackinfo.hwndTrack = infoPtr->hwndSelf;
3170 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3172 /* see if we are already tracking this hwnd */
3173 _TrackMouseEvent(&trackinfo);
3175 if(!(trackinfo.dwFlags & TME_HOVER)) {
3176 trackinfo.dwFlags = TME_HOVER;
3178 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3179 _TrackMouseEvent(&trackinfo);
3188 * Tests wheather the item is assignable to a list with style lStyle
3190 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3192 if ( (lpLVItem->mask & LVIF_TEXT) &&
3193 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3194 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3202 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3205 * [I] infoPtr : valid pointer to the listview structure
3206 * [I] lpLVItem : valid pointer to new item atttributes
3207 * [I] isNew : the item being set is being inserted
3208 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3209 * [O] bChanged : will be set to TRUE if the item really changed
3215 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3217 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3225 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3227 if (lpLVItem->mask == 0) return TRUE;
3229 if (infoPtr->dwStyle & LVS_OWNERDATA)
3231 /* a virtual listview we stores only selection and focus */
3232 if (lpLVItem->mask & ~LVIF_STATE)
3238 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3239 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
3243 /* we need to get the lParam and state of the item */
3244 item.iItem = lpLVItem->iItem;
3245 item.iSubItem = lpLVItem->iSubItem;
3246 item.mask = LVIF_STATE | LVIF_PARAM;
3247 item.stateMask = ~0;
3250 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3252 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3253 /* determine what fields will change */
3254 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
3255 uChanged |= LVIF_STATE;
3257 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3258 uChanged |= LVIF_IMAGE;
3260 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3261 uChanged |= LVIF_PARAM;
3263 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3264 uChanged |= LVIF_INDENT;
3266 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3267 uChanged |= LVIF_TEXT;
3269 TRACE("uChanged=0x%x\n", uChanged);
3270 if (!uChanged) return TRUE;
3273 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3274 nmlv.iItem = lpLVItem->iItem;
3275 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3276 nmlv.uOldState = item.state;
3277 nmlv.uChanged = uChanged;
3278 nmlv.lParam = item.lParam;
3280 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3281 /* and we are _NOT_ virtual (LVS_OWERNDATA), and change notifications */
3283 if(lpItem && !isNew && infoPtr->bDoChangeNotify &&
3284 notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3287 /* copy information */
3288 if (lpLVItem->mask & LVIF_TEXT)
3289 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3291 if (lpLVItem->mask & LVIF_IMAGE)
3292 lpItem->hdr.iImage = lpLVItem->iImage;
3294 if (lpLVItem->mask & LVIF_PARAM)
3295 lpItem->lParam = lpLVItem->lParam;
3297 if (lpLVItem->mask & LVIF_INDENT)
3298 lpItem->iIndent = lpLVItem->iIndent;
3300 if (uChanged & LVIF_STATE)
3302 if (lpItem && (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3304 lpItem->state &= ~lpLVItem->stateMask;
3305 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3307 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3309 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3310 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3312 else if (lpLVItem->stateMask & LVIS_SELECTED)
3313 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3315 /* if we are asked to change focus, and we manage it, do it */
3316 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3318 if (lpLVItem->state & LVIS_FOCUSED)
3320 LISTVIEW_SetItemFocus(infoPtr, -1);
3321 infoPtr->nFocusedItem = lpLVItem->iItem;
3322 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3324 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3325 infoPtr->nFocusedItem = -1;
3329 /* if we're inserting the item, we're done */
3330 if (isNew) return TRUE;
3332 /* send LVN_ITEMCHANGED notification */
3333 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3334 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3341 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3344 * [I] infoPtr : valid pointer to the listview structure
3345 * [I] lpLVItem : valid pointer to new subitem atttributes
3346 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3347 * [O] bChanged : will be set to TRUE if the item really changed
3353 static BOOL set_sub_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3356 SUBITEM_INFO *lpSubItem;
3358 /* we do not support subitems for virtual listviews */
3359 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3361 /* set subitem only if column is present */
3362 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3364 /* First do some sanity checks */
3365 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE)) return FALSE;
3366 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE))) return TRUE;
3368 /* get the subitem structure, and create it if not there */
3369 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3370 assert (hdpaSubItems);
3372 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3375 SUBITEM_INFO *tmpSubItem;
3378 lpSubItem = (SUBITEM_INFO *)Alloc(sizeof(SUBITEM_INFO));
3379 if (!lpSubItem) return FALSE;
3380 /* we could binary search here, if need be...*/
3381 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3383 tmpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
3384 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3386 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3391 lpSubItem->iSubItem = lpLVItem->iSubItem;
3392 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3396 if (lpLVItem->mask & LVIF_IMAGE)
3397 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3399 lpSubItem->hdr.iImage = lpLVItem->iImage;
3403 if (lpLVItem->mask & LVIF_TEXT)
3404 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3406 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3415 * Sets item attributes.
3418 * [I] infoPtr : valid pointer to the listview structure
3419 * [I] lpLVItem : new item atttributes
3420 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3426 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
3428 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3429 LPWSTR pszText = NULL;
3430 BOOL bResult, bChanged = FALSE;
3432 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3434 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3437 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3438 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3440 pszText = lpLVItem->pszText;
3441 ((LVITEMW *)lpLVItem)->pszText = textdupTtoW(lpLVItem->pszText, isW);
3444 /* actually set the fields */
3445 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3447 if (lpLVItem->iSubItem)
3448 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3450 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3452 /* redraw item, if necessary */
3453 if (bChanged && !infoPtr->bIsDrawing)
3455 /* this little optimization eliminates some nasty flicker */
3456 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3457 (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) || lpLVItem->iSubItem) )
3458 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3460 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3465 textfreeT(lpLVItem->pszText, isW);
3466 ((LVITEMW *)lpLVItem)->pszText = pszText;
3474 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3477 * [I] infoPtr : valid pointer to the listview structure
3482 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *infoPtr)
3484 LONG lStyle = infoPtr->dwStyle;
3485 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3487 SCROLLINFO scrollInfo;
3489 scrollInfo.cbSize = sizeof(SCROLLINFO);
3490 scrollInfo.fMask = SIF_POS;
3492 if (uView == LVS_LIST)
3494 if ((lStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3495 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3497 else if (uView == LVS_REPORT)
3499 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3500 nItem = scrollInfo.nPos;
3504 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3505 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3508 TRACE("nItem=%d\n", nItem);
3516 * Erases the background of the given rectangle
3519 * [I] infoPtr : valid pointer to the listview structure
3520 * [I] hdc : device context handle
3521 * [I] lprcBox : clipping rectangle
3527 static inline BOOL LISTVIEW_FillBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3529 if (!infoPtr->hBkBrush) return FALSE;
3531 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, debugrect(lprcBox), infoPtr->hBkBrush);
3533 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3541 * [I] infoPtr : valid pointer to the listview structure
3542 * [I] hdc : device context handle
3543 * [I] nItem : item index
3544 * [I] nSubItem : subitem index
3545 * [I] pos : item position in client coordinates
3546 * [I] cdmode : custom draw mode
3552 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3554 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3555 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3556 WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3557 DWORD cdsubitemmode = CDRF_DODEFAULT;
3558 RECT* lprcFocus, rcSelect, rcBox, rcState, rcIcon, rcLabel;
3559 NMLVCUSTOMDRAW nmlvcd;
3563 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, debugpoint(&pos));
3565 /* get information needed for drawing the item */
3566 lvItem.mask = LVIF_TEXT | LVIF_IMAGE;
3567 if (nSubItem == 0) lvItem.mask |= LVIF_STATE | LVIF_PARAM;
3568 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3569 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3570 lvItem.iItem = nItem;
3571 lvItem.iSubItem = nSubItem;
3574 lvItem.cchTextMax = DISP_TEXT_SIZE;
3575 lvItem.pszText = szDispText;
3576 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3577 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3578 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3579 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3580 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3582 /* now check if we need to update the focus rectangle */
3583 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3585 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3586 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
3587 OffsetRect(&rcBox, pos.x, pos.y);
3588 OffsetRect(&rcState, pos.x, pos.y);
3589 OffsetRect(&rcIcon, pos.x, pos.y);
3590 OffsetRect(&rcLabel, pos.x, pos.y);
3591 TRACE(" rcBox=%s, rcState=%s, rcIcon=%s. rcLabel=%s\n",
3592 debugrect(&rcBox), debugrect(&rcState), debugrect(&rcIcon), debugrect(&rcLabel));
3594 /* fill in the custom draw structure */
3595 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3597 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3598 if (cdmode & CDRF_NOTIFYITEMDRAW)
3599 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3600 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3601 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3602 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3603 prepaint_setup(infoPtr, hdc, &nmlvcd);
3605 /* in full row select, subitems, will just use main item's colors */
3606 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3607 nmlvcd.clrTextBk = CLR_NONE;
3610 if (infoPtr->himlState && !IsRectEmpty(&rcState))
3612 UINT uStateImage = (lvItem.state & LVIS_STATEIMAGEMASK) >> 12;
3615 TRACE("uStateImage=%d\n", uStateImage);
3616 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc, rcState.left, rcState.top, ILD_NORMAL);
3621 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3622 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3624 TRACE("iImage=%d\n", lvItem.iImage);
3625 ImageList_Draw(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3626 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3629 /* Don't bother painting item being edited */
3630 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3632 /* draw the selection background, if we're drawing the main item */
3636 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3637 rcSelect.right = rcBox.right;
3639 if (nmlvcd.clrTextBk != CLR_NONE)
3640 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3641 if(lprcFocus) *lprcFocus = rcSelect;
3644 /* figure out the text drawing flags */
3645 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3646 if (uView == LVS_ICON)
3647 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3650 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3652 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3653 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3654 default: uFormat |= DT_LEFT;
3657 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3659 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3660 else rcLabel.left += LABEL_HOR_PADDING;
3662 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3663 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3666 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3667 notify_postpaint(infoPtr, &nmlvcd);
3673 * Draws listview items when in owner draw mode.
3676 * [I] infoPtr : valid pointer to the listview structure
3677 * [I] hdc : device context handle
3682 static void LISTVIEW_RefreshOwnerDraw(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3684 UINT uID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
3685 DWORD cditemmode = CDRF_DODEFAULT;
3686 NMLVCUSTOMDRAW nmlvcd;
3687 POINT Origin, Position;
3693 ZeroMemory(&dis, sizeof(dis));
3695 /* Get scroll info once before loop */
3696 LISTVIEW_GetOrigin(infoPtr, &Origin);
3698 /* iterate through the invalidated rows */
3699 while(iterator_next(i))
3701 item.iItem = i->nItem;
3703 item.mask = LVIF_PARAM | LVIF_STATE;
3704 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3705 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3707 dis.CtlType = ODT_LISTVIEW;
3709 dis.itemID = item.iItem;
3710 dis.itemAction = ODA_DRAWENTIRE;
3712 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3713 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3714 dis.hwndItem = infoPtr->hwndSelf;
3716 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3717 dis.rcItem.left = Position.x + Origin.x;
3718 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3719 dis.rcItem.top = Position.y + Origin.y;
3720 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3721 dis.itemData = item.lParam;
3723 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), debugrect(&dis.rcItem));
3725 if (cdmode & CDRF_NOTIFYITEMDRAW)
3727 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
3728 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3731 if (!(cditemmode & CDRF_SKIPDEFAULT))
3733 prepaint_setup (infoPtr, hdc, &nmlvcd);
3734 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3737 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3738 notify_postpaint(infoPtr, &nmlvcd);
3744 * Draws listview items when in report display mode.
3747 * [I] infoPtr : valid pointer to the listview structure
3748 * [I] hdc : device context handle
3749 * [I] cdmode : custom draw mode
3754 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3757 RECT rcClip, rcItem;
3758 POINT Origin, Position;
3764 /* figure out what to draw */
3765 rgntype = GetClipBox(hdc, &rcClip);
3766 if (rgntype == NULLREGION) return;
3768 /* Get scroll info once before loop */
3769 LISTVIEW_GetOrigin(infoPtr, &Origin);
3771 /* narrow down the columns we need to paint */
3772 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
3774 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
3775 if (rcItem.right + Origin.x >= rcClip.left) break;
3777 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
3779 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
3780 if (rcItem.left + Origin.x < rcClip.right) break;
3782 iterator_rangeitems(&j, colRange);
3784 /* in full row select, we _have_ to draw the main item */
3785 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
3788 /* iterate through the invalidated rows */
3789 while(iterator_next(i))
3791 /* iterate through the invalidated columns */
3792 while(iterator_next(&j))
3794 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
3795 Position.x += Origin.x;
3796 Position.y += Origin.y;
3798 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
3800 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
3802 rcItem.bottom = infoPtr->nItemHeight;
3803 OffsetRect(&rcItem, Position.x, Position.y);
3804 if (!RectVisible(hdc, &rcItem)) continue;
3807 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
3810 iterator_destroy(&j);
3815 * Draws listview items when in list display mode.
3818 * [I] infoPtr : valid pointer to the listview structure
3819 * [I] hdc : device context handle
3820 * [I] cdmode : custom draw mode
3825 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3827 POINT Origin, Position;
3829 /* Get scroll info once before loop */
3830 LISTVIEW_GetOrigin(infoPtr, &Origin);
3832 while(iterator_prev(i))
3834 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
3835 Position.x += Origin.x;
3836 Position.y += Origin.y;
3838 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
3845 * Draws listview items.
3848 * [I] infoPtr : valid pointer to the listview structure
3849 * [I] hdc : device context handle
3854 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc)
3856 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3857 COLORREF oldTextColor, oldClrTextBk, oldClrText;
3858 NMLVCUSTOMDRAW nmlvcd;
3865 LISTVIEW_DUMP(infoPtr);
3867 infoPtr->bIsDrawing = TRUE;
3869 /* save dc values we're gonna trash while drawing */
3870 hOldFont = SelectObject(hdc, infoPtr->hFont);
3871 oldBkMode = GetBkMode(hdc);
3872 infoPtr->clrTextBkDefault = GetBkColor(hdc);
3873 oldTextColor = GetTextColor(hdc);
3875 oldClrTextBk = infoPtr->clrTextBk;
3876 oldClrText = infoPtr->clrText;
3878 infoPtr->cditemmode = CDRF_DODEFAULT;
3880 GetClientRect(infoPtr->hwndSelf, &rcClient);
3881 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
3882 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3883 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
3884 prepaint_setup(infoPtr, hdc, &nmlvcd);
3886 /* Use these colors to draw the items */
3887 infoPtr->clrTextBk = nmlvcd.clrTextBk;
3888 infoPtr->clrText = nmlvcd.clrText;
3890 /* nothing to draw */
3891 if(infoPtr->nItemCount == 0) goto enddraw;
3893 /* figure out what we need to draw */
3894 iterator_visibleitems(&i, infoPtr, hdc);
3896 /* send cache hint notification */
3897 if (infoPtr->dwStyle & LVS_OWNERDATA)
3899 RANGE range = iterator_range(&i);
3902 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
3903 nmlv.iFrom = range.lower;
3904 nmlv.iTo = range.upper - 1;
3905 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
3908 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
3909 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
3912 if (uView == LVS_REPORT)
3913 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
3914 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
3915 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
3917 /* if we have a focus rect, draw it */
3918 if (infoPtr->bFocus)
3919 DrawFocusRect(hdc, &infoPtr->rcFocus);
3921 iterator_destroy(&i);
3924 if (cdmode & CDRF_NOTIFYPOSTPAINT)
3925 notify_postpaint(infoPtr, &nmlvcd);
3927 infoPtr->clrTextBk = oldClrTextBk;
3928 infoPtr->clrText = oldClrText;
3930 SelectObject(hdc, hOldFont);
3931 SetBkMode(hdc, oldBkMode);
3932 SetBkColor(hdc, infoPtr->clrTextBkDefault);
3933 SetTextColor(hdc, oldTextColor);
3934 infoPtr->bIsDrawing = FALSE;
3940 * Calculates the approximate width and height of a given number of items.
3943 * [I] infoPtr : valid pointer to the listview structure
3944 * [I] nItemCount : number of items
3945 * [I] wWidth : width
3946 * [I] wHeight : height
3949 * Returns a DWORD. The width in the low word and the height in high word.
3951 static DWORD LISTVIEW_ApproximateViewRect(LISTVIEW_INFO *infoPtr, INT nItemCount,
3952 WORD wWidth, WORD wHeight)
3954 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3955 INT nItemCountPerColumn = 1;
3956 INT nColumnCount = 0;
3957 DWORD dwViewRect = 0;
3959 if (nItemCount == -1)
3960 nItemCount = infoPtr->nItemCount;
3962 if (uView == LVS_LIST)
3964 if (wHeight == 0xFFFF)
3966 /* use current height */
3967 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
3970 if (wHeight < infoPtr->nItemHeight)
3971 wHeight = infoPtr->nItemHeight;
3975 if (infoPtr->nItemHeight > 0)
3977 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
3978 if (nItemCountPerColumn == 0)
3979 nItemCountPerColumn = 1;
3981 if (nItemCount % nItemCountPerColumn != 0)
3982 nColumnCount = nItemCount / nItemCountPerColumn;
3984 nColumnCount = nItemCount / nItemCountPerColumn + 1;
3988 /* Microsoft padding magic */
3989 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
3990 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
3992 dwViewRect = MAKELONG(wWidth, wHeight);
3994 else if (uView == LVS_REPORT)
3995 FIXME("uView == LVS_REPORT: not implemented\n");
3996 else if (uView == LVS_SMALLICON)
3997 FIXME("uView == LVS_SMALLICON: not implemented\n");
3998 else if (uView == LVS_ICON)
3999 FIXME("uView == LVS_ICON: not implemented\n");
4007 * Create a drag image list for the specified item.
4010 * [I] infoPtr : valid pointer to the listview structure
4011 * [I] iItem : index of item
4012 * [O] lppt : Upperr-left corner of the image
4015 * Returns a handle to the image list if successful, NULL otherwise.
4017 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4023 HBITMAP hbmp, hOldbmp;
4024 HIMAGELIST dragList = 0;
4025 TRACE("iItem=%d Count=%d \n", iItem, infoPtr->nItemCount);
4027 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4030 rcItem.left = LVIR_BOUNDS;
4031 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4034 lppt->x = rcItem.left;
4035 lppt->y = rcItem.top;
4037 size.cx = rcItem.right - rcItem.left;
4038 size.cy = rcItem.bottom - rcItem.top;
4040 hdcOrig = GetDC(infoPtr->hwndSelf);
4041 hdc = CreateCompatibleDC(hdcOrig);
4042 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4043 hOldbmp = SelectObject(hdc, hbmp);
4045 rcItem.left = rcItem.top = 0;
4046 rcItem.right = size.cx;
4047 rcItem.bottom = size.cy;
4048 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4051 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4053 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4054 SelectObject(hdc, hOldbmp);
4055 ImageList_Add(dragList, hbmp, 0);
4058 SelectObject(hdc, hOldbmp);
4062 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4064 TRACE("ret=%p\n", dragList);
4072 * Removes all listview items and subitems.
4075 * [I] infoPtr : valid pointer to the listview structure
4081 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr)
4084 HDPA hdpaSubItems = NULL;
4091 /* we do it directly, to avoid notifications */
4092 ranges_clear(infoPtr->selectionRanges);
4093 infoPtr->nSelectionMark = -1;
4094 infoPtr->nFocusedItem = -1;
4095 SetRectEmpty(&infoPtr->rcFocus);
4096 /* But we are supposed to leave nHotItem as is! */
4099 /* send LVN_DELETEALLITEMS notification */
4100 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4102 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4104 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4106 /* send LVN_DELETEITEM notification, if not supressed */
4107 if (!bSuppress) notify_deleteitem(infoPtr, i);
4108 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4110 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
4111 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4113 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, j);
4114 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4117 DPA_Destroy(hdpaSubItems);
4118 DPA_DeletePtr(infoPtr->hdpaItems, i);
4120 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4121 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4122 infoPtr->nItemCount --;
4125 LISTVIEW_UpdateScroll(infoPtr);
4127 LISTVIEW_InvalidateList(infoPtr);
4134 * Scrolls, and updates the columns, when a column is changing width.
4137 * [I] infoPtr : valid pointer to the listview structure
4138 * [I] nColumn : column to scroll
4139 * [I] dx : amount of scroll, in pixels
4144 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4146 COLUMN_INFO *lpColumnInfo;
4150 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4151 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4152 rcCol = lpColumnInfo->rcHeader;
4153 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4154 rcCol.left = rcCol.right;
4156 /* ajust the other columns */
4157 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4159 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4160 lpColumnInfo->rcHeader.left += dx;
4161 lpColumnInfo->rcHeader.right += dx;
4164 /* do not update screen if not in report mode */
4165 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4167 /* if we have a focus, must first erase the focus rect */
4168 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4170 /* Need to reset the item width when inserting a new column */
4171 infoPtr->nItemWidth += dx;
4173 LISTVIEW_UpdateScroll(infoPtr);
4175 /* scroll to cover the deleted column, and invalidate for redraw */
4176 rcOld = infoPtr->rcList;
4177 rcOld.left = rcCol.left;
4178 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4180 /* we can restore focus now */
4181 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4186 * Removes a column from the listview control.
4189 * [I] infoPtr : valid pointer to the listview structure
4190 * [I] nColumn : column index
4196 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4200 TRACE("nColumn=%d\n", nColumn);
4202 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4203 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4205 /* While the MSDN specifically says that column zero should not be deleted,
4206 it does in fact work on WinNT, and at least one app depends on it. On
4207 WinNT, deleting column zero deletes the last column of items but the
4208 first header. Since no app will ever depend on that bizarre behavior,
4209 we just delete the last column including the header.
4212 nColumn = DPA_GetPtrCount(infoPtr->hdpaColumns) - 1;
4214 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4216 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4219 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4220 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4222 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4224 SUBITEM_INFO *lpSubItem, *lpDelItem;
4226 INT nItem, nSubItem, i;
4229 return LISTVIEW_DeleteAllItems(infoPtr);
4231 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4233 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
4236 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4238 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
4239 if (lpSubItem->iSubItem == nColumn)
4242 lpDelItem = lpSubItem;
4244 else if (lpSubItem->iSubItem > nColumn)
4246 lpSubItem->iSubItem--;
4250 /* if we found our subitem, zapp it */
4254 if (is_textW(lpDelItem->hdr.pszText))
4255 Free(lpDelItem->hdr.pszText);
4260 /* free dpa memory */
4261 DPA_DeletePtr(hdpaSubItems, nSubItem);
4266 /* update the other column info */
4267 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4274 * Invalidates the listview after an item's insertion or deletion.
4277 * [I] infoPtr : valid pointer to the listview structure
4278 * [I] nItem : item index
4279 * [I] dir : -1 if deleting, 1 if inserting
4284 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4286 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4287 INT nPerCol, nItemCol, nItemRow;
4291 /* if we don't refresh, what's the point of scrolling? */
4292 if (!is_redrawing(infoPtr)) return;
4294 assert (abs(dir) == 1);
4296 /* arrange icons if autoarrange is on */
4297 if (is_autoarrange(infoPtr))
4299 BOOL arrange = TRUE;
4300 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4301 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4302 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4305 /* scrollbars need updating */
4306 LISTVIEW_UpdateScroll(infoPtr);
4308 /* figure out the item's position */
4309 if (uView == LVS_REPORT)
4310 nPerCol = infoPtr->nItemCount + 1;
4311 else if (uView == LVS_LIST)
4312 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4313 else /* LVS_ICON, or LVS_SMALLICON */
4316 nItemCol = nItem / nPerCol;
4317 nItemRow = nItem % nPerCol;
4318 LISTVIEW_GetOrigin(infoPtr, &Origin);
4320 /* move the items below up a slot */
4321 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4322 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4323 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4324 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4325 OffsetRect(&rcScroll, Origin.x, Origin.y);
4326 TRACE("rcScroll=%s, dx=%d\n", debugrect(&rcScroll), dir * infoPtr->nItemHeight);
4327 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4329 TRACE("Scrolling rcScroll=%s, rcList=%s\n", debugrect(&rcScroll), debugrect(&infoPtr->rcList));
4330 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4331 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4334 /* report has only that column, so we're done */
4335 if (uView == LVS_REPORT) return;
4337 /* now for LISTs, we have to deal with the columns to the right */
4338 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4340 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4341 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4342 OffsetRect(&rcScroll, Origin.x, Origin.y);
4343 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4344 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4345 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4350 * Removes an item from the listview control.
4353 * [I] infoPtr : valid pointer to the listview structure
4354 * [I] nItem : item index
4360 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4362 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4365 TRACE("(nItem=%d)\n", nItem);
4367 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4369 /* remove selection, and focus */
4371 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4372 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4374 /* send LVN_DELETEITEM notification. */
4375 notify_deleteitem(infoPtr, nItem);
4377 /* we need to do this here, because we'll be deleting stuff */
4378 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4379 LISTVIEW_InvalidateItem(infoPtr, nItem);
4381 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4387 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4388 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4390 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, i);
4391 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4394 DPA_Destroy(hdpaSubItems);
4397 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4399 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4400 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4403 infoPtr->nItemCount--;
4404 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4406 /* now is the invalidation fun */
4407 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4414 * Callback implementation for editlabel control
4417 * [I] infoPtr : valid pointer to the listview structure
4418 * [I] pszText : modified text
4419 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4425 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4427 NMLVDISPINFOW dispInfo;
4429 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4431 ZeroMemory(&dispInfo, sizeof(dispInfo));
4432 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
4433 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4434 dispInfo.item.iSubItem = 0;
4435 dispInfo.item.stateMask = ~0;
4436 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4437 /* add the text from the edit in */
4438 dispInfo.item.mask |= LVIF_TEXT;
4439 dispInfo.item.pszText = pszText;
4440 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4442 /* Do we need to update the Item Text */
4443 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4444 if (!pszText) return TRUE;
4446 ZeroMemory(&dispInfo, sizeof(dispInfo));
4447 dispInfo.item.mask = LVIF_TEXT;
4448 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4449 dispInfo.item.iSubItem = 0;
4450 dispInfo.item.pszText = pszText;
4451 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4452 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4457 * Begin in place editing of specified list view item
4460 * [I] infoPtr : valid pointer to the listview structure
4461 * [I] nItem : item index
4462 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4468 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4470 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4471 NMLVDISPINFOW dispInfo;
4474 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4476 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4477 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4479 infoPtr->nEditLabelItem = nItem;
4481 /* Is the EditBox still there, if so remove it */
4482 if(infoPtr->hwndEdit != 0)
4484 SetFocus(infoPtr->hwndSelf);
4485 infoPtr->hwndEdit = 0;
4488 LISTVIEW_SetSelection(infoPtr, nItem);
4489 LISTVIEW_SetItemFocus(infoPtr, nItem);
4490 LISTVIEW_InvalidateItem(infoPtr, nItem);
4492 rect.left = LVIR_LABEL;
4493 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4495 ZeroMemory(&dispInfo, sizeof(dispInfo));
4496 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4497 dispInfo.item.iItem = nItem;
4498 dispInfo.item.iSubItem = 0;
4499 dispInfo.item.stateMask = ~0;
4500 dispInfo.item.pszText = szDispText;
4501 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4502 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4504 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4505 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4506 if (!infoPtr->hwndEdit) return 0;
4508 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4510 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4511 infoPtr->hwndEdit = 0;
4515 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4516 SetFocus(infoPtr->hwndEdit);
4517 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4518 return infoPtr->hwndEdit;
4524 * Ensures the specified item is visible, scrolling into view if necessary.
4527 * [I] infoPtr : valid pointer to the listview structure
4528 * [I] nItem : item index
4529 * [I] bPartial : partially or entirely visible
4535 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4537 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4538 INT nScrollPosHeight = 0;
4539 INT nScrollPosWidth = 0;
4540 INT nHorzAdjust = 0;
4541 INT nVertAdjust = 0;
4544 RECT rcItem, rcTemp;
4546 rcItem.left = LVIR_BOUNDS;
4547 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4549 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4551 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4553 /* scroll left/right, but in LVS_REPORT mode */
4554 if (uView == LVS_LIST)
4555 nScrollPosWidth = infoPtr->nItemWidth;
4556 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4557 nScrollPosWidth = 1;
4559 if (rcItem.left < infoPtr->rcList.left)
4562 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
4567 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
4571 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
4573 /* scroll up/down, but not in LVS_LIST mode */
4574 if (uView == LVS_REPORT)
4575 nScrollPosHeight = infoPtr->nItemHeight;
4576 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
4577 nScrollPosHeight = 1;
4579 if (rcItem.top < infoPtr->rcList.top)
4582 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
4587 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
4591 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
4593 if (nScrollPosWidth)
4595 INT diff = nHorzDiff / nScrollPosWidth;
4596 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
4597 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
4600 if (nScrollPosHeight)
4602 INT diff = nVertDiff / nScrollPosHeight;
4603 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
4604 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
4612 * Searches for an item with specific characteristics.
4615 * [I] hwnd : window handle
4616 * [I] nStart : base item index
4617 * [I] lpFindInfo : item information to look for
4620 * SUCCESS : index of item
4623 static INT LISTVIEW_FindItemW(LISTVIEW_INFO *infoPtr, INT nStart,
4624 const LVFINDINFOW *lpFindInfo)
4626 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4627 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4628 BOOL bWrap = FALSE, bNearest = FALSE;
4629 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
4630 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
4631 POINT Position, Destination;
4634 if (!lpFindInfo || nItem < 0) return -1;
4637 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
4639 lvItem.mask |= LVIF_TEXT;
4640 lvItem.pszText = szDispText;
4641 lvItem.cchTextMax = DISP_TEXT_SIZE;
4644 if (lpFindInfo->flags & LVFI_WRAP)
4647 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
4648 (uView == LVS_ICON || uView ==LVS_SMALLICON))
4653 LISTVIEW_GetOrigin(infoPtr, &Origin);
4654 Destination.x = lpFindInfo->pt.x - Origin.x;
4655 Destination.y = lpFindInfo->pt.y - Origin.y;
4656 switch(lpFindInfo->vkDirection)
4658 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
4659 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
4660 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
4661 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
4662 case VK_HOME: Destination.x = Destination.y = 0; break;
4663 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4664 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4666 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
4667 Destination.x = rcArea.right;
4668 Destination.y = rcArea.bottom;
4670 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
4675 /* if LVFI_PARAM is specified, all other flags are ignored */
4676 if (lpFindInfo->flags & LVFI_PARAM)
4678 lvItem.mask |= LVIF_PARAM;
4680 lvItem.mask &= ~LVIF_TEXT;
4684 for (; nItem < nLast; nItem++)
4686 lvItem.iItem = nItem;
4687 lvItem.iSubItem = 0;
4688 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
4690 if (lvItem.mask & LVIF_PARAM)
4692 if (lpFindInfo->lParam == lvItem.lParam)
4698 if (lvItem.mask & LVIF_TEXT)
4700 if (lpFindInfo->flags & LVFI_PARTIAL)
4702 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
4706 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
4710 if (!bNearest) return nItem;
4712 /* This is very inefficient. To do a good job here,
4713 * we need a sorted array of (x,y) item positions */
4714 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
4716 /* compute the distance^2 to the destination */
4717 xdist = Destination.x - Position.x;
4718 ydist = Destination.y - Position.y;
4719 dist = xdist * xdist + ydist * ydist;
4721 /* remember the distance, and item if it's closer */
4725 nNearestItem = nItem;
4732 nLast = min(nStart + 1, infoPtr->nItemCount);
4737 return nNearestItem;
4742 * Searches for an item with specific characteristics.
4745 * [I] hwnd : window handle
4746 * [I] nStart : base item index
4747 * [I] lpFindInfo : item information to look for
4750 * SUCCESS : index of item
4753 static INT LISTVIEW_FindItemA(LISTVIEW_INFO *infoPtr, INT nStart,
4754 const LVFINDINFOA *lpFindInfo)
4756 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
4760 memcpy(&fiw, lpFindInfo, sizeof(fiw));
4761 if (hasText) fiw.psz = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
4762 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
4763 if (hasText) textfreeT((LPWSTR)fiw.psz, FALSE);
4769 * Retrieves the background image of the listview control.
4772 * [I] infoPtr : valid pointer to the listview structure
4773 * [O] lpBkImage : background image attributes
4779 /* static BOOL LISTVIEW_GetBkImage(LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
4781 /* FIXME (listview, "empty stub!\n"); */
4787 * Retrieves column attributes.
4790 * [I] infoPtr : valid pointer to the listview structure
4791 * [I] nColumn : column index
4792 * [IO] lpColumn : column information
4793 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
4794 * otherwise it is in fact a LPLVCOLUMNA
4800 static BOOL LISTVIEW_GetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
4802 COLUMN_INFO *lpColumnInfo;
4805 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4806 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
4808 /* initialize memory */
4809 ZeroMemory(&hdi, sizeof(hdi));
4811 if (lpColumn->mask & LVCF_TEXT)
4813 hdi.mask |= HDI_TEXT;
4814 hdi.pszText = lpColumn->pszText;
4815 hdi.cchTextMax = lpColumn->cchTextMax;
4818 if (lpColumn->mask & LVCF_IMAGE)
4819 hdi.mask |= HDI_IMAGE;
4821 if (lpColumn->mask & LVCF_ORDER)
4822 hdi.mask |= HDI_ORDER;
4824 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
4826 if (lpColumn->mask & LVCF_FMT)
4827 lpColumn->fmt = lpColumnInfo->fmt;
4829 if (lpColumn->mask & LVCF_WIDTH)
4830 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
4832 if (lpColumn->mask & LVCF_IMAGE)
4833 lpColumn->iImage = hdi.iImage;
4835 if (lpColumn->mask & LVCF_ORDER)
4836 lpColumn->iOrder = hdi.iOrder;
4842 static BOOL LISTVIEW_GetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
4849 /* FIXME: little hack */
4850 for (i = 0; i < iCount; i++)
4858 * Retrieves the column width.
4861 * [I] infoPtr : valid pointer to the listview structure
4862 * [I] int : column index
4865 * SUCCESS : column width
4868 static INT LISTVIEW_GetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn)
4870 INT nColumnWidth = 0;
4873 TRACE("nColumn=%d\n", nColumn);
4875 /* we have a 'column' in LIST and REPORT mode only */
4876 switch(infoPtr->dwStyle & LVS_TYPEMASK)
4879 nColumnWidth = infoPtr->nItemWidth;
4882 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
4883 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
4884 nColumnWidth = rcHeader.right - rcHeader.left;
4888 TRACE("nColumnWidth=%d\n", nColumnWidth);
4889 return nColumnWidth;
4894 * In list or report display mode, retrieves the number of items that can fit
4895 * vertically in the visible area. In icon or small icon display mode,
4896 * retrieves the total number of visible items.
4899 * [I] infoPtr : valid pointer to the listview structure
4902 * Number of fully visible items.
4904 static INT LISTVIEW_GetCountPerPage(LISTVIEW_INFO *infoPtr)
4906 switch (infoPtr->dwStyle & LVS_TYPEMASK)
4910 return infoPtr->nItemCount;
4912 return LISTVIEW_GetCountPerColumn(infoPtr);
4914 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
4922 * Retrieves an image list handle.
4925 * [I] infoPtr : valid pointer to the listview structure
4926 * [I] nImageList : image list identifier
4929 * SUCCESS : image list handle
4932 static HIMAGELIST LISTVIEW_GetImageList(LISTVIEW_INFO *infoPtr, INT nImageList)
4936 case LVSIL_NORMAL: return infoPtr->himlNormal;
4937 case LVSIL_SMALL: return infoPtr->himlSmall;
4938 case LVSIL_STATE: return infoPtr->himlState;
4943 /* LISTVIEW_GetISearchString */
4947 * Retrieves item attributes.
4950 * [I] hwnd : window handle
4951 * [IO] lpLVItem : item info
4952 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
4953 * if FALSE, the lpLVItem is a LPLVITEMA.
4956 * This is the internal 'GetItem' interface -- it tries to
4957 * be smart, and avoids text copies, if possible, by modifing
4958 * lpLVItem->pszText to point to the text string. Please note
4959 * that this is not always possible (e.g. OWNERDATA), so on
4960 * entry you *must* supply valid values for pszText, and cchTextMax.
4961 * The only difference to the documented interface is that upon
4962 * return, you should use *only* the lpLVItem->pszText, rather than
4963 * the buffer pointer you provided on input. Most code already does
4964 * that, so it's not a problem.
4965 * For the two cases when the text must be copied (that is,
4966 * for LVM_GETITEM, and LVMGETITEMTEXT), use LISTVIEW_GetItemExtT.
4972 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
4974 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
4975 NMLVDISPINFOW dispInfo;
4980 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
4982 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
4985 if (lpLVItem->mask == 0) return TRUE;
4987 /* a quick optimization if all we're asked is the focus state
4988 * these queries are worth optimising since they are common,
4989 * and can be answered in constant time, without the heavy accesses */
4990 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
4991 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
4993 lpLVItem->state = 0;
4994 if (infoPtr->nFocusedItem == lpLVItem->iItem)
4995 lpLVItem->state |= LVIS_FOCUSED;
4999 ZeroMemory(&dispInfo, sizeof(dispInfo));
5001 /* if the app stores all the data, handle it separately */
5002 if (infoPtr->dwStyle & LVS_OWNERDATA)
5004 dispInfo.item.state = 0;
5006 /* apprently, we should not callback for lParam in LVS_OWNERDATA */
5007 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5009 /* NOTE: copy only fields which we _know_ are initialized, some apps
5010 * depend on the uninitialized fields being 0 */
5011 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5012 dispInfo.item.iItem = lpLVItem->iItem;
5013 dispInfo.item.iSubItem = lpLVItem->iSubItem;
5014 if (lpLVItem->mask & LVIF_TEXT)
5016 dispInfo.item.pszText = lpLVItem->pszText;
5017 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5019 if (lpLVItem->mask & LVIF_STATE)
5020 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5021 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5022 dispInfo.item.stateMask = lpLVItem->stateMask;
5023 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5025 /* full size structure expected - _WIN32IE >= 0x560 */
5026 *lpLVItem = dispInfo.item;
5028 else if (lpLVItem->mask & LVIF_INDENT)
5030 /* indent member expected - _WIN32IE >= 0x300 */
5031 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5035 /* minimal structure expected */
5036 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5038 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5041 /* make sure lParam is zeroed out */
5042 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5044 /* we store only a little state, so if we're not asked, we're done */
5045 if (!(lpLVItem->mask & LVIF_STATE) || lpLVItem->iSubItem) return TRUE;
5047 /* if focus is handled by us, report it */
5048 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5050 lpLVItem->state &= ~LVIS_FOCUSED;
5051 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5052 lpLVItem->state |= LVIS_FOCUSED;
5055 /* and do the same for selection, if we handle it */
5056 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5058 lpLVItem->state &= ~LVIS_SELECTED;
5059 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5060 lpLVItem->state |= LVIS_SELECTED;
5066 /* find the item and subitem structures before we proceed */
5067 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5068 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
5071 if (lpLVItem->iSubItem)
5073 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
5074 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5077 pItemHdr = &lpItem->hdr;
5079 /* Do we need to query the state from the app? */
5080 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && lpLVItem->iSubItem == 0)
5082 dispInfo.item.mask |= LVIF_STATE;
5083 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5086 /* Do we need to enquire about the image? */
5087 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5088 (lpLVItem->iSubItem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5090 dispInfo.item.mask |= LVIF_IMAGE;
5091 dispInfo.item.iImage = I_IMAGECALLBACK;
5094 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5095 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
5097 dispInfo.item.mask |= LVIF_TEXT;
5098 dispInfo.item.pszText = lpLVItem->pszText;
5099 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5100 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5101 *dispInfo.item.pszText = '\0';
5104 /* If we don't have all the requested info, query the application */
5105 if (dispInfo.item.mask != 0)
5107 dispInfo.item.iItem = lpLVItem->iItem;
5108 dispInfo.item.iSubItem = lpLVItem->iSubItem;
5109 dispInfo.item.lParam = lpItem->lParam;
5110 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5111 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5114 /* we should not store values for subitems */
5115 if (lpLVItem->iSubItem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5117 /* Now, handle the iImage field */
5118 if (dispInfo.item.mask & LVIF_IMAGE)
5120 lpLVItem->iImage = dispInfo.item.iImage;
5121 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5122 pItemHdr->iImage = dispInfo.item.iImage;
5124 else if (lpLVItem->mask & LVIF_IMAGE)
5126 if(lpLVItem->iSubItem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5127 lpLVItem->iImage = pItemHdr->iImage;
5129 lpLVItem->iImage = 0;
5132 /* The pszText field */
5133 if (dispInfo.item.mask & LVIF_TEXT)
5135 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5136 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5138 lpLVItem->pszText = dispInfo.item.pszText;
5140 else if (lpLVItem->mask & LVIF_TEXT)
5142 if (isW) lpLVItem->pszText = pItemHdr->pszText;
5143 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5146 /* if this is a subitem, we're done */
5147 if (lpLVItem->iSubItem) return TRUE;
5149 /* Next is the lParam field */
5150 if (dispInfo.item.mask & LVIF_PARAM)
5152 lpLVItem->lParam = dispInfo.item.lParam;
5153 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5154 lpItem->lParam = dispInfo.item.lParam;
5156 else if (lpLVItem->mask & LVIF_PARAM)
5157 lpLVItem->lParam = lpItem->lParam;
5159 /* ... the state field (this one is different due to uCallbackmask) */
5160 if (lpLVItem->mask & LVIF_STATE)
5162 lpLVItem->state = lpItem->state;
5163 if (dispInfo.item.mask & LVIF_STATE)
5165 lpLVItem->state &= ~dispInfo.item.stateMask;
5166 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5168 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5170 lpLVItem->state &= ~LVIS_FOCUSED;
5171 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5172 lpLVItem->state |= LVIS_FOCUSED;
5174 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5176 lpLVItem->state &= ~LVIS_SELECTED;
5177 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5178 lpLVItem->state |= LVIS_SELECTED;
5182 /* and last, but not least, the indent field */
5183 if (lpLVItem->mask & LVIF_INDENT)
5184 lpLVItem->iIndent = lpItem->iIndent;
5191 * Retrieves item attributes.
5194 * [I] hwnd : window handle
5195 * [IO] lpLVItem : item info
5196 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5197 * if FALSE, the lpLVItem is a LPLVITEMA.
5200 * This is the external 'GetItem' interface -- it properly copies
5201 * the text in the provided buffer.
5207 static BOOL LISTVIEW_GetItemExtT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5212 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5215 pszText = lpLVItem->pszText;
5216 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5217 if (bResult && lpLVItem->pszText != pszText)
5218 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5219 lpLVItem->pszText = pszText;
5227 * Retrieves the position (upper-left) of the listview control item.
5228 * Note that for LVS_ICON style, the upper-left is that of the icon
5229 * and not the bounding box.
5232 * [I] infoPtr : valid pointer to the listview structure
5233 * [I] nItem : item index
5234 * [O] lpptPosition : coordinate information
5240 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5242 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5245 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5247 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5249 LISTVIEW_GetOrigin(infoPtr, &Origin);
5250 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5252 if (uView == LVS_ICON)
5254 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5255 lpptPosition->y += ICON_TOP_PADDING;
5257 lpptPosition->x += Origin.x;
5258 lpptPosition->y += Origin.y;
5260 TRACE (" lpptPosition=%s\n", debugpoint(lpptPosition));
5267 * Retrieves the bounding rectangle for a listview control item.
5270 * [I] infoPtr : valid pointer to the listview structure
5271 * [I] nItem : item index
5272 * [IO] lprc : bounding rectangle coordinates
5273 * lprc->left specifies the portion of the item for which the bounding
5274 * rectangle will be retrieved.
5276 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5277 * including the icon and label.
5280 * * Experiment shows that native control returns:
5281 * * width = min (48, length of text line)
5282 * * .left = position.x - (width - iconsize.cx)/2
5283 * * .right = .left + width
5284 * * height = #lines of text * ntmHeight + icon height + 8
5285 * * .top = position.y - 2
5286 * * .bottom = .top + height
5287 * * separation between items .y = itemSpacing.cy - height
5288 * * .x = itemSpacing.cx - width
5289 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5292 * * Experiment shows that native control returns:
5293 * * width = iconSize.cx + 16
5294 * * .left = position.x - (width - iconsize.cx)/2
5295 * * .right = .left + width
5296 * * height = iconSize.cy + 4
5297 * * .top = position.y - 2
5298 * * .bottom = .top + height
5299 * * separation between items .y = itemSpacing.cy - height
5300 * * .x = itemSpacing.cx - width
5301 * LVIR_LABEL Returns the bounding rectangle of the item text.
5304 * * Experiment shows that native control returns:
5305 * * width = text length
5306 * * .left = position.x - width/2
5307 * * .right = .left + width
5308 * * height = ntmH * linecount + 2
5309 * * .top = position.y + iconSize.cy + 6
5310 * * .bottom = .top + height
5311 * * separation between items .y = itemSpacing.cy - height
5312 * * .x = itemSpacing.cx - width
5313 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5314 * rectangles, but excludes columns in report view.
5321 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5322 * upon whether the window has the focus currently and on whether the item
5323 * is the one with the focus. Ensure that the control's record of which
5324 * item has the focus agrees with the items' records.
5326 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5328 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5329 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5330 BOOL doLabel = TRUE, oversizedBox = FALSE;
5331 POINT Position, Origin;
5335 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5337 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5339 LISTVIEW_GetOrigin(infoPtr, &Origin);
5340 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5342 /* Be smart and try to figure out the minimum we have to do */
5343 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5344 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5345 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5346 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5347 oversizedBox = TRUE;
5349 /* get what we need from the item before hand, so we make
5350 * only one request. This can speed up things, if data
5351 * is stored on the app side */
5353 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5354 if (doLabel) lvItem.mask |= LVIF_TEXT;
5355 lvItem.iItem = nItem;
5356 lvItem.iSubItem = 0;
5357 lvItem.pszText = szDispText;
5358 lvItem.cchTextMax = DISP_TEXT_SIZE;
5359 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5360 /* we got the state already up, simulate it here, to avoid a reget */
5361 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5363 lvItem.mask |= LVIF_STATE;
5364 lvItem.stateMask = LVIS_FOCUSED;
5365 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5368 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5369 lprc->left = LVIR_BOUNDS;
5373 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5377 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, lprc);
5381 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5384 case LVIR_SELECTBOUNDS:
5385 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, &label_rect);
5386 UnionRect(lprc, lprc, &label_rect);
5390 WARN("Unknown value: %ld\n", lprc->left);
5394 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5396 TRACE(" rect=%s\n", debugrect(lprc));
5403 * Retrieves the spacing between listview control items.
5406 * [I] infoPtr : valid pointer to the listview structure
5407 * [IO] lprc : rectangle to receive the output
5408 * on input, lprc->top = nSubItem
5409 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5411 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5412 * not only those of the first column.
5413 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5419 static BOOL LISTVIEW_GetSubItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5424 if (!lprc) return FALSE;
5426 TRACE("(nItem=%d, nSubItem=%ld)\n", nItem, lprc->top);
5427 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5429 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5431 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5433 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5436 lvItem.iItem = nItem;
5437 lvItem.iSubItem = lprc->top;
5439 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5443 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5448 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5452 ERR("Unknown bounds=%ld\n", lprc->left);
5456 OffsetRect(lprc, Position.x, Position.y);
5463 * Retrieves the width of a label.
5466 * [I] infoPtr : valid pointer to the listview structure
5469 * SUCCESS : string width (in pixels)
5472 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *infoPtr, INT nItem)
5474 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5477 TRACE("(nItem=%d)\n", nItem);
5479 lvItem.mask = LVIF_TEXT;
5480 lvItem.iItem = nItem;
5481 lvItem.iSubItem = 0;
5482 lvItem.pszText = szDispText;
5483 lvItem.cchTextMax = DISP_TEXT_SIZE;
5484 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5486 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5491 * Retrieves the spacing between listview control items.
5494 * [I] infoPtr : valid pointer to the listview structure
5495 * [I] bSmall : flag for small or large icon
5498 * Horizontal + vertical spacing
5500 static LONG LISTVIEW_GetItemSpacing(LISTVIEW_INFO *infoPtr, BOOL bSmall)
5506 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5510 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5511 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5513 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5520 * Retrieves the state of a listview control item.
5523 * [I] infoPtr : valid pointer to the listview structure
5524 * [I] nItem : item index
5525 * [I] uMask : state mask
5528 * State specified by the mask.
5530 static UINT LISTVIEW_GetItemState(LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5534 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5536 lvItem.iItem = nItem;
5537 lvItem.iSubItem = 0;
5538 lvItem.mask = LVIF_STATE;
5539 lvItem.stateMask = uMask;
5540 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5542 return lvItem.state & uMask;
5547 * Retrieves the text of a listview control item or subitem.
5550 * [I] hwnd : window handle
5551 * [I] nItem : item index
5552 * [IO] lpLVItem : item information
5553 * [I] isW : TRUE if lpLVItem is Unicode
5556 * SUCCESS : string length
5559 static INT LISTVIEW_GetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
5561 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5563 lpLVItem->mask = LVIF_TEXT;
5564 lpLVItem->iItem = nItem;
5565 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
5567 return textlenT(lpLVItem->pszText, isW);
5572 * Searches for an item based on properties + relationships.
5575 * [I] infoPtr : valid pointer to the listview structure
5576 * [I] nItem : item index
5577 * [I] uFlags : relationship flag
5580 * SUCCESS : item index
5583 static INT LISTVIEW_GetNextItem(LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
5585 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5587 LVFINDINFOW lvFindInfo;
5588 INT nCountPerColumn;
5592 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
5593 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
5595 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
5597 if (uFlags & LVNI_CUT)
5600 if (uFlags & LVNI_DROPHILITED)
5601 uMask |= LVIS_DROPHILITED;
5603 if (uFlags & LVNI_FOCUSED)
5604 uMask |= LVIS_FOCUSED;
5606 if (uFlags & LVNI_SELECTED)
5607 uMask |= LVIS_SELECTED;
5609 /* if we're asked for the focused item, that's only one,
5610 * so it's worth optimizing */
5611 if (uFlags & LVNI_FOCUSED)
5613 if (!(LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) == uMask) return -1;
5614 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
5617 if (uFlags & LVNI_ABOVE)
5619 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5624 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5630 /* Special case for autoarrange - move 'til the top of a list */
5631 if (is_autoarrange(infoPtr))
5633 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5634 while (nItem - nCountPerRow >= 0)
5636 nItem -= nCountPerRow;
5637 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5642 lvFindInfo.flags = LVFI_NEARESTXY;
5643 lvFindInfo.vkDirection = VK_UP;
5644 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5645 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5647 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5652 else if (uFlags & LVNI_BELOW)
5654 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5656 while (nItem < infoPtr->nItemCount)
5659 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5665 /* Special case for autoarrange - move 'til the bottom of a list */
5666 if (is_autoarrange(infoPtr))
5668 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5669 while (nItem + nCountPerRow < infoPtr->nItemCount )
5671 nItem += nCountPerRow;
5672 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5677 lvFindInfo.flags = LVFI_NEARESTXY;
5678 lvFindInfo.vkDirection = VK_DOWN;
5679 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5680 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5682 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5687 else if (uFlags & LVNI_TOLEFT)
5689 if (uView == LVS_LIST)
5691 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5692 while (nItem - nCountPerColumn >= 0)
5694 nItem -= nCountPerColumn;
5695 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5699 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5701 /* Special case for autoarrange - move 'ti the beginning of a row */
5702 if (is_autoarrange(infoPtr))
5704 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5705 while (nItem % nCountPerRow > 0)
5708 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5713 lvFindInfo.flags = LVFI_NEARESTXY;
5714 lvFindInfo.vkDirection = VK_LEFT;
5715 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5716 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5718 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5723 else if (uFlags & LVNI_TORIGHT)
5725 if (uView == LVS_LIST)
5727 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5728 while (nItem + nCountPerColumn < infoPtr->nItemCount)
5730 nItem += nCountPerColumn;
5731 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5735 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5737 /* Special case for autoarrange - move 'til the end of a row */
5738 if (is_autoarrange(infoPtr))
5740 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5741 while (nItem % nCountPerRow < nCountPerRow - 1 )
5744 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5749 lvFindInfo.flags = LVFI_NEARESTXY;
5750 lvFindInfo.vkDirection = VK_RIGHT;
5751 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5752 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5754 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5763 /* search by index */
5764 for (i = nItem; i < infoPtr->nItemCount; i++)
5766 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
5774 /* LISTVIEW_GetNumberOfWorkAreas */
5778 * Retrieves the origin coordinates when in icon or small icon display mode.
5781 * [I] infoPtr : valid pointer to the listview structure
5782 * [O] lpptOrigin : coordinate information
5787 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
5789 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5790 INT nHorzPos = 0, nVertPos = 0;
5791 SCROLLINFO scrollInfo;
5793 scrollInfo.cbSize = sizeof(SCROLLINFO);
5794 scrollInfo.fMask = SIF_POS;
5796 if ((infoPtr->dwStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
5797 nHorzPos = scrollInfo.nPos;
5798 if ((infoPtr->dwStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
5799 nVertPos = scrollInfo.nPos;
5801 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
5803 lpptOrigin->x = infoPtr->rcList.left;
5804 lpptOrigin->y = infoPtr->rcList.top;
5805 if (uView == LVS_LIST)
5806 nHorzPos *= infoPtr->nItemWidth;
5807 else if (uView == LVS_REPORT)
5808 nVertPos *= infoPtr->nItemHeight;
5810 lpptOrigin->x -= nHorzPos;
5811 lpptOrigin->y -= nVertPos;
5813 TRACE(" origin=%s\n", debugpoint(lpptOrigin));
5818 * Retrieves the width of a string.
5821 * [I] hwnd : window handle
5822 * [I] lpszText : text string to process
5823 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
5826 * SUCCESS : string width (in pixels)
5829 static INT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
5834 if (is_textT(lpszText, isW))
5836 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
5837 HDC hdc = GetDC(infoPtr->hwndSelf);
5838 HFONT hOldFont = SelectObject(hdc, hFont);
5841 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
5843 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
5844 SelectObject(hdc, hOldFont);
5845 ReleaseDC(infoPtr->hwndSelf, hdc);
5847 return stringSize.cx;
5852 * Determines which listview item is located at the specified position.
5855 * [I] infoPtr : valid pointer to the listview structure
5856 * [IO] lpht : hit test information
5857 * [I] subitem : fill out iSubItem.
5858 * [I] select : return the index only if the hit selects the item
5861 * (mm 20001022): We must not allow iSubItem to be touched, for
5862 * an app might pass only a structure with space up to iItem!
5863 * (MS Office 97 does that for instance in the file open dialog)
5866 * SUCCESS : item index
5869 static INT LISTVIEW_HitTest(LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
5871 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5872 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5873 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
5874 POINT Origin, Position, opt;
5879 TRACE("(pt=%s, subitem=%d, select=%d)\n", debugpoint(&lpht->pt), subitem, select);
5883 if (subitem) lpht->iSubItem = 0;
5885 if (infoPtr->rcList.left > lpht->pt.x)
5886 lpht->flags |= LVHT_TOLEFT;
5887 else if (infoPtr->rcList.right < lpht->pt.x)
5888 lpht->flags |= LVHT_TORIGHT;
5890 if (infoPtr->rcList.top > lpht->pt.y)
5891 lpht->flags |= LVHT_ABOVE;
5892 else if (infoPtr->rcList.bottom < lpht->pt.y)
5893 lpht->flags |= LVHT_BELOW;
5895 TRACE("lpht->flags=0x%x\n", lpht->flags);
5896 if (lpht->flags) return -1;
5898 lpht->flags |= LVHT_NOWHERE;
5900 LISTVIEW_GetOrigin(infoPtr, &Origin);
5902 /* first deal with the large items */
5903 rcSearch.left = lpht->pt.x;
5904 rcSearch.top = lpht->pt.y;
5905 rcSearch.right = rcSearch.left + 1;
5906 rcSearch.bottom = rcSearch.top + 1;
5908 iterator_frameditems(&i, infoPtr, &rcSearch);
5909 iterator_next(&i); /* go to first item in the sequence */
5911 iterator_destroy(&i);
5913 TRACE("lpht->iItem=%d\n", iItem);
5914 if (iItem == -1) return -1;
5916 lvItem.mask = LVIF_STATE | LVIF_TEXT;
5917 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5918 lvItem.stateMask = LVIS_STATEIMAGEMASK;
5919 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
5920 lvItem.iItem = iItem;
5921 lvItem.iSubItem = 0;
5922 lvItem.pszText = szDispText;
5923 lvItem.cchTextMax = DISP_TEXT_SIZE;
5924 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
5925 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
5927 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
5928 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
5929 opt.x = lpht->pt.x - Position.x - Origin.x;
5930 opt.y = lpht->pt.y - Position.y - Origin.y;
5932 if (uView == LVS_REPORT)
5935 UnionRect(&rcBounds, &rcIcon, &rcLabel);
5936 TRACE("rcBounds=%s\n", debugrect(&rcBounds));
5937 if (!PtInRect(&rcBounds, opt)) return -1;
5939 if (PtInRect(&rcIcon, opt))
5940 lpht->flags |= LVHT_ONITEMICON;
5941 else if (PtInRect(&rcLabel, opt))
5942 lpht->flags |= LVHT_ONITEMLABEL;
5943 else if (infoPtr->himlState && ((lvItem.state & LVIS_STATEIMAGEMASK) >> 12) && PtInRect(&rcState, opt))
5944 lpht->flags |= LVHT_ONITEMSTATEICON;
5945 if (lpht->flags & LVHT_ONITEM)
5946 lpht->flags &= ~LVHT_NOWHERE;
5948 TRACE("lpht->flags=0x%x\n", lpht->flags);
5949 if (uView == LVS_REPORT && subitem)
5953 rcBounds.right = rcBounds.left;
5954 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
5956 rcBounds.left = rcBounds.right;
5957 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
5958 if (PtInRect(&rcBounds, opt))
5966 if (select && !(uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)))
5968 if (uView == LVS_REPORT) UnionRect(&rcBounds, &rcIcon, &rcLabel);
5969 if (!PtInRect(&rcBounds, opt)) iItem = -1;
5971 return lpht->iItem = iItem;
5975 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
5976 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
5977 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
5978 and not during the processing of a LVM_SORTITEMS message. Applications should provide
5979 their own sort proc. when sending LVM_SORTITEMS.
5982 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
5984 LVS_SORTXXX must be specified,
5985 LVS_OWNERDRAW is not set,
5986 <item>.pszText is not LPSTR_TEXTCALLBACK.
5988 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
5989 are sorted based on item text..."
5991 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
5993 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
5994 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
5995 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
5997 /* if we're sorting descending, negate the return value */
5998 return (((LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
6003 * Inserts a new item in the listview control.
6006 * [I] infoPtr : valid pointer to the listview structure
6007 * [I] lpLVItem : item information
6008 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6011 * SUCCESS : new item index
6014 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6016 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6021 BOOL is_sorted, has_changed;
6024 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6026 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6028 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6029 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iSubItem) return -1;
6031 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6033 if ( !(lpItem = (ITEM_INFO *)Alloc(sizeof(ITEM_INFO))) )
6036 /* insert item in listview control data structure */
6037 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6038 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6040 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6041 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6043 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
6044 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6045 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6046 if (nItem == -1) goto fail;
6047 infoPtr->nItemCount++;
6049 /* shift indices first so they don't get tangled */
6050 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6052 /* set the item attributes */
6053 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6055 /* full size structure expected - _WIN32IE >= 0x560 */
6058 else if (lpLVItem->mask & LVIF_INDENT)
6060 /* indent member expected - _WIN32IE >= 0x300 */
6061 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6065 /* minimal structure expected */
6066 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6069 item.state &= ~LVIS_STATEIMAGEMASK;
6070 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6072 /* if we're sorted, sort the list, and update the index */
6075 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
6076 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
6077 assert(nItem != -1);
6080 /* make room for the position, if we are in the right mode */
6081 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6083 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6085 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6087 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6092 /* send LVN_INSERTITEM notification */
6093 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6095 nmlv.lParam = lpItem->lParam;
6096 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6098 /* align items (set position of each item) */
6099 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6103 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6104 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6106 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6108 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6111 /* now is the invalidation fun */
6112 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6116 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6117 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6118 infoPtr->nItemCount--;
6120 DPA_DeletePtr(hdpaSubItems, 0);
6121 DPA_Destroy (hdpaSubItems);
6128 * Redraws a range of items.
6131 * [I] infoPtr : valid pointer to the listview structure
6132 * [I] nFirst : first item
6133 * [I] nLast : last item
6139 static BOOL LISTVIEW_RedrawItems(LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6143 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6144 max(nFirst, nLast) >= infoPtr->nItemCount)
6147 for (i = nFirst; i <= nLast; i++)
6148 LISTVIEW_InvalidateItem(infoPtr, i);
6155 * Scroll the content of a listview.
6158 * [I] infoPtr : valid pointer to the listview structure
6159 * [I] dx : horizontal scroll amount in pixels
6160 * [I] dy : vertical scroll amount in pixels
6167 * If the control is in report mode (LVS_REPORT) the control can
6168 * be scrolled only in line increments. "dy" will be rounded to the
6169 * nearest number of pixels that are a whole line. Ex: if line height
6170 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6171 * is passed the the scroll will be 0. (per MSDN 7/2002)
6173 * For: (per experimentaion with native control and CSpy ListView)
6174 * LVS_ICON dy=1 = 1 pixel (vertical only)
6176 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6178 * LVS_LIST dx=1 = 1 column (horizontal only)
6179 * but will only scroll 1 column per message
6180 * no matter what the value.
6181 * dy must be 0 or FALSE returned.
6182 * LVS_REPORT dx=1 = 1 pixel
6186 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6188 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6190 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6191 dy /= infoPtr->nItemHeight;
6194 if (dy != 0) return FALSE;
6201 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6202 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6209 * Sets the background color.
6212 * [I] infoPtr : valid pointer to the listview structure
6213 * [I] clrBk : background color
6219 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6221 TRACE("(clrBk=%lx)\n", clrBk);
6223 if(infoPtr->clrBk != clrBk) {
6224 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6225 infoPtr->clrBk = clrBk;
6226 if (clrBk == CLR_NONE)
6227 infoPtr->hBkBrush = (HBRUSH)GetClassLongW(infoPtr->hwndSelf, GCL_HBRBACKGROUND);
6229 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6230 LISTVIEW_InvalidateList(infoPtr);
6236 /* LISTVIEW_SetBkImage */
6238 /*** Helper for {Insert,Set}ColumnT *only* */
6239 static void column_fill_hditem(LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn, const LVCOLUMNW *lpColumn, BOOL isW)
6241 if (lpColumn->mask & LVCF_FMT)
6243 /* format member is valid */
6244 lphdi->mask |= HDI_FORMAT;
6246 /* set text alignment (leftmost column must be left-aligned) */
6247 if (nColumn == 0 || lpColumn->fmt & LVCFMT_LEFT)
6248 lphdi->fmt |= HDF_LEFT;
6249 else if (lpColumn->fmt & LVCFMT_RIGHT)
6250 lphdi->fmt |= HDF_RIGHT;
6251 else if (lpColumn->fmt & LVCFMT_CENTER)
6252 lphdi->fmt |= HDF_CENTER;
6254 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6255 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6257 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6259 lphdi->fmt |= HDF_IMAGE;
6260 lphdi->iImage = I_IMAGECALLBACK;
6264 if (lpColumn->mask & LVCF_WIDTH)
6266 lphdi->mask |= HDI_WIDTH;
6267 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6269 /* make it fill the remainder of the controls width */
6273 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6275 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6276 lphdi->cxy += rcHeader.right - rcHeader.left;
6279 /* retrieve the layout of the header */
6280 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6281 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, debugrect(&rcHeader));
6283 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6286 lphdi->cxy = lpColumn->cx;
6289 if (lpColumn->mask & LVCF_TEXT)
6291 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6292 lphdi->fmt |= HDF_STRING;
6293 lphdi->pszText = lpColumn->pszText;
6294 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6297 if (lpColumn->mask & LVCF_IMAGE)
6299 lphdi->mask |= HDI_IMAGE;
6300 lphdi->iImage = lpColumn->iImage;
6303 if (lpColumn->mask & LVCF_ORDER)
6305 lphdi->mask |= HDI_ORDER;
6306 lphdi->iOrder = lpColumn->iOrder;
6313 * Inserts a new column.
6316 * [I] infoPtr : valid pointer to the listview structure
6317 * [I] nColumn : column index
6318 * [I] lpColumn : column information
6319 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6322 * SUCCESS : new column index
6325 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6326 const LVCOLUMNW *lpColumn, BOOL isW)
6328 COLUMN_INFO *lpColumnInfo;
6332 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6334 if (!lpColumn || nColumn < 0) return -1;
6335 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6337 ZeroMemory(&hdi, sizeof(HDITEMW));
6338 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6340 /* insert item in header control */
6341 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6342 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6343 (WPARAM)nColumn, (LPARAM)&hdi);
6344 if (nNewColumn == -1) return -1;
6345 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6347 /* create our own column info */
6348 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6349 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6351 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6352 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6354 /* now we have to actually adjust the data */
6355 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6357 SUBITEM_INFO *lpSubItem;
6361 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6363 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
6364 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
6366 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
6367 if (lpSubItem->iSubItem >= nNewColumn)
6368 lpSubItem->iSubItem++;
6373 /* make space for the new column */
6374 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6379 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6382 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6390 * Sets the attributes of a header item.
6393 * [I] infoPtr : valid pointer to the listview structure
6394 * [I] nColumn : column index
6395 * [I] lpColumn : column attributes
6396 * [I] isW: if TRUE, the lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6402 static BOOL LISTVIEW_SetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6403 const LVCOLUMNW *lpColumn, BOOL isW)
6405 HDITEMW hdi, hdiget;
6408 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6410 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6412 ZeroMemory(&hdi, sizeof(HDITEMW));
6413 if (lpColumn->mask & LVCF_FMT)
6415 hdi.mask |= HDI_FORMAT;
6416 hdiget.mask = HDI_FORMAT;
6417 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6418 hdi.fmt = hdiget.fmt & HDF_STRING;
6420 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6422 /* set header item attributes */
6423 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6424 if (!bResult) return FALSE;
6426 if (lpColumn->mask & LVCF_FMT)
6428 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6429 int oldFmt = lpColumnInfo->fmt;
6431 lpColumnInfo->fmt = lpColumn->fmt;
6432 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
6434 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6435 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
6444 * Sets the column order array
6447 * [I] infoPtr : valid pointer to the listview structure
6448 * [I] iCount : number of elements in column order array
6449 * [I] lpiArray : pointer to column order array
6455 static BOOL LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
6457 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6468 * Sets the width of a column
6471 * [I] infoPtr : valid pointer to the listview structure
6472 * [I] nColumn : column index
6473 * [I] cx : column width
6479 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
6481 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6482 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6486 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
6488 /* set column width only if in report or list mode */
6489 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6491 /* take care of invalid cx values */
6492 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
6493 else if (uView == LVS_LIST && cx < 1) return FALSE;
6495 /* resize all columns if in LVS_LIST mode */
6496 if(uView == LVS_LIST)
6498 infoPtr->nItemWidth = cx;
6499 LISTVIEW_InvalidateList(infoPtr);
6503 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6505 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
6510 lvItem.mask = LVIF_TEXT;
6512 lvItem.iSubItem = nColumn;
6513 lvItem.pszText = szDispText;
6514 lvItem.cchTextMax = DISP_TEXT_SIZE;
6515 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6517 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6518 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6519 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
6521 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
6522 max_cx += infoPtr->iconSize.cx;
6523 max_cx += TRAILING_LABEL_PADDING;
6526 /* autosize based on listview items width */
6527 if(cx == LVSCW_AUTOSIZE)
6529 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
6531 /* if iCol is the last column make it fill the remainder of the controls width */
6532 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
6537 LISTVIEW_GetOrigin(infoPtr, &Origin);
6538 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
6540 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
6544 /* Despite what the MS docs say, if this is not the last
6545 column, then MS resizes the column to the width of the
6546 largest text string in the column, including headers
6547 and items. This is different from LVSCW_AUTOSIZE in that
6548 LVSCW_AUTOSIZE ignores the header string length. */
6551 /* retrieve header text */
6552 hdi.mask = HDI_TEXT;
6553 hdi.cchTextMax = DISP_TEXT_SIZE;
6554 hdi.pszText = szDispText;
6555 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, (LPARAM)&hdi))
6557 HDC hdc = GetDC(infoPtr->hwndSelf);
6558 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
6561 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
6562 cx = size.cx + TRAILING_HEADER_PADDING;
6563 /* FIXME: Take into account the header image, if one is present */
6564 SelectObject(hdc, old_font);
6565 ReleaseDC(infoPtr->hwndSelf, hdc);
6567 cx = max (cx, max_cx);
6571 if (cx < 0) return FALSE;
6573 /* call header to update the column change */
6574 hdi.mask = HDI_WIDTH;
6576 TRACE("hdi.cxy=%d\n", hdi.cxy);
6577 return Header_SetItemW(infoPtr->hwndHeader, nColumn, (LPARAM)&hdi);
6582 * Sets the extended listview style.
6585 * [I] infoPtr : valid pointer to the listview structure
6587 * [I] dwStyle : style
6590 * SUCCESS : previous style
6593 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwStyle)
6595 DWORD dwOldStyle = infoPtr->dwLvExStyle;
6599 infoPtr->dwLvExStyle = (dwOldStyle & ~dwMask) | (dwStyle & dwMask);
6601 infoPtr->dwLvExStyle = dwStyle;
6608 * Sets the new hot cursor used during hot tracking and hover selection.
6611 * [I] infoPtr : valid pointer to the listview structure
6612 * [I} hCurosr : the new hot cursor handle
6615 * Returns the previous hot cursor
6617 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
6619 HCURSOR oldCursor = infoPtr->hHotCursor;
6621 infoPtr->hHotCursor = hCursor;
6629 * Sets the hot item index.
6632 * [I] infoPtr : valid pointer to the listview structure
6633 * [I] iIndex : index
6636 * SUCCESS : previous hot item index
6637 * FAILURE : -1 (no hot item)
6639 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
6641 INT iOldIndex = infoPtr->nHotItem;
6643 infoPtr->nHotItem = iIndex;
6651 * Sets the amount of time the cursor must hover over an item before it is selected.
6654 * [I] infoPtr : valid pointer to the listview structure
6655 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
6658 * Returns the previous hover time
6660 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
6662 DWORD oldHoverTime = infoPtr->dwHoverTime;
6664 infoPtr->dwHoverTime = dwHoverTime;
6666 return oldHoverTime;
6671 * Sets spacing for icons of LVS_ICON style.
6674 * [I] infoPtr : valid pointer to the listview structure
6675 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
6676 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
6679 * MAKELONG(oldcx, oldcy)
6681 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
6683 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6684 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6686 TRACE("requested=(%d,%d)\n", cx, cy);
6688 /* this is supported only for LVS_ICON style */
6689 if (uView != LVS_ICON) return oldspacing;
6691 /* set to defaults, if instructed to */
6692 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
6693 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
6695 /* if 0 then compute width
6696 * FIXME: Should scan each item and determine max width of
6697 * icon or label, then make that the width */
6699 cx = infoPtr->iconSpacing.cx;
6701 /* if 0 then compute height */
6703 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
6704 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
6707 infoPtr->iconSpacing.cx = cx;
6708 infoPtr->iconSpacing.cy = cy;
6710 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%ld,%ld), ntmH=%d\n",
6711 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
6712 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
6713 infoPtr->ntmHeight);
6715 /* these depend on the iconSpacing */
6716 LISTVIEW_UpdateItemSize(infoPtr);
6721 inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
6725 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
6732 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
6733 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
6742 * [I] infoPtr : valid pointer to the listview structure
6743 * [I] nType : image list type
6744 * [I] himl : image list handle
6747 * SUCCESS : old image list
6750 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
6752 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6753 INT oldHeight = infoPtr->nItemHeight;
6754 HIMAGELIST himlOld = 0;
6756 TRACE("(nType=%d, himl=%p\n", nType, himl);
6761 himlOld = infoPtr->himlNormal;
6762 infoPtr->himlNormal = himl;
6763 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
6764 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
6768 himlOld = infoPtr->himlSmall;
6769 infoPtr->himlSmall = himl;
6770 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
6774 himlOld = infoPtr->himlState;
6775 infoPtr->himlState = himl;
6776 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
6777 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
6781 ERR("Unknown icon type=%d\n", nType);
6785 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
6786 if (infoPtr->nItemHeight != oldHeight)
6787 LISTVIEW_UpdateScroll(infoPtr);
6794 * Preallocates memory (does *not* set the actual count of items !)
6797 * [I] infoPtr : valid pointer to the listview structure
6798 * [I] nItems : item count (projected number of items to allocate)
6799 * [I] dwFlags : update flags
6805 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
6807 TRACE("(nItems=%d, dwFlags=%lx)\n", nItems, dwFlags);
6809 if (infoPtr->dwStyle & LVS_OWNERDATA)
6811 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6812 INT nOldCount = infoPtr->nItemCount;
6814 if (nItems < nOldCount)
6816 RANGE range = { nItems, nOldCount };
6817 ranges_del(infoPtr->selectionRanges, range);
6818 if (infoPtr->nFocusedItem >= nItems)
6820 infoPtr->nFocusedItem = -1;
6821 SetRectEmpty(&infoPtr->rcFocus);
6825 infoPtr->nItemCount = nItems;
6826 LISTVIEW_UpdateScroll(infoPtr);
6828 /* the flags are valid only in ownerdata report and list modes */
6829 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
6831 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
6832 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
6834 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
6835 LISTVIEW_InvalidateList(infoPtr);
6842 LISTVIEW_GetOrigin(infoPtr, &Origin);
6843 nFrom = min(nOldCount, nItems);
6844 nTo = max(nOldCount, nItems);
6846 if (uView == LVS_REPORT)
6849 rcErase.top = nFrom * infoPtr->nItemHeight;
6850 rcErase.right = infoPtr->nItemWidth;
6851 rcErase.bottom = nTo * infoPtr->nItemHeight;
6852 OffsetRect(&rcErase, Origin.x, Origin.y);
6853 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6854 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6858 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
6860 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
6861 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
6862 rcErase.right = rcErase.left + infoPtr->nItemWidth;
6863 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
6864 OffsetRect(&rcErase, Origin.x, Origin.y);
6865 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6866 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6868 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
6870 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
6871 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
6872 OffsetRect(&rcErase, Origin.x, Origin.y);
6873 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6874 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6880 /* According to MSDN for non-LVS_OWNERDATA this is just
6881 * a performance issue. The control allocates its internal
6882 * data structures for the number of items specified. It
6883 * cuts down on the number of memory allocations. Therefore
6884 * we will just issue a WARN here
6886 WARN("for non-ownerdata performance option not implemented.\n");
6894 * Sets the position of an item.
6897 * [I] infoPtr : valid pointer to the listview structure
6898 * [I] nItem : item index
6899 * [I] pt : coordinate
6905 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
6907 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6910 TRACE("(nItem=%d, &pt=%s\n", nItem, debugpoint(&pt));
6912 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
6913 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
6915 LISTVIEW_GetOrigin(infoPtr, &Origin);
6917 /* This point value seems to be an undocumented feature.
6918 * The best guess is that it means either at the origin,
6919 * or at true beginning of the list. I will assume the origin. */
6920 if ((pt.x == -1) && (pt.y == -1))
6923 if (uView == LVS_ICON)
6925 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
6926 pt.y -= ICON_TOP_PADDING;
6931 infoPtr->bAutoarrange = FALSE;
6933 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
6938 * Sets the state of one or many items.
6941 * [I] infoPtr : valid pointer to the listview structure
6942 * [I] nItem : item index
6943 * [I] lpLVItem : item or subitem info
6949 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
6951 BOOL bResult = TRUE;
6954 lvItem.iItem = nItem;
6955 lvItem.iSubItem = 0;
6956 lvItem.mask = LVIF_STATE;
6957 lvItem.state = lpLVItem->state;
6958 lvItem.stateMask = lpLVItem->stateMask;
6959 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
6963 /* apply to all items */
6964 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6965 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
6968 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
6975 * Sets the text of an item or subitem.
6978 * [I] hwnd : window handle
6979 * [I] nItem : item index
6980 * [I] lpLVItem : item or subitem info
6981 * [I] isW : TRUE if input is Unicode
6987 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
6991 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
6993 lvItem.iItem = nItem;
6994 lvItem.iSubItem = lpLVItem->iSubItem;
6995 lvItem.mask = LVIF_TEXT;
6996 lvItem.pszText = lpLVItem->pszText;
6997 lvItem.cchTextMax = lpLVItem->cchTextMax;
6999 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7001 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7006 * Set item index that marks the start of a multiple selection.
7009 * [I] infoPtr : valid pointer to the listview structure
7010 * [I] nIndex : index
7013 * Index number or -1 if there is no selection mark.
7015 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7017 INT nOldIndex = infoPtr->nSelectionMark;
7019 TRACE("(nIndex=%d)\n", nIndex);
7021 infoPtr->nSelectionMark = nIndex;
7028 * Sets the text background color.
7031 * [I] infoPtr : valid pointer to the listview structure
7032 * [I] clrTextBk : text background color
7038 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7040 TRACE("(clrTextBk=%lx)\n", clrTextBk);
7042 if (infoPtr->clrTextBk != clrTextBk)
7044 infoPtr->clrTextBk = clrTextBk;
7045 LISTVIEW_InvalidateList(infoPtr);
7053 * Sets the text foreground color.
7056 * [I] infoPtr : valid pointer to the listview structure
7057 * [I] clrText : text color
7063 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7065 TRACE("(clrText=%lx)\n", clrText);
7067 if (infoPtr->clrText != clrText)
7069 infoPtr->clrText = clrText;
7070 LISTVIEW_InvalidateList(infoPtr);
7078 * Determines which listview item is located at the specified position.
7081 * [I] infoPtr : valid pointer to the listview structure
7082 * [I] hwndNewToolTip : handle to new ToolTip
7087 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7089 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7090 infoPtr->hwndToolTip = hwndNewToolTip;
7091 return hwndOldToolTip;
7094 /* LISTVIEW_SetUnicodeFormat */
7095 /* LISTVIEW_SetWorkAreas */
7099 * Callback internally used by LISTVIEW_SortItems()
7102 * [I] first : pointer to first ITEM_INFO to compare
7103 * [I] second : pointer to second ITEM_INFO to compare
7104 * [I] lParam : HWND of control
7107 * if first comes before second : negative
7108 * if first comes after second : positive
7109 * if first and second are equivalent : zero
7111 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7113 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW((HWND)lParam, 0);
7114 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
7115 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
7117 /* Forward the call to the client defined callback */
7118 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7123 * Sorts the listview items.
7126 * [I] infoPtr : valid pointer to the listview structure
7127 * [I] pfnCompare : application-defined value
7128 * [I] lParamSort : pointer to comparision callback
7134 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
7136 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7139 LPVOID selectionMarkItem;
7143 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7145 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7147 if (!infoPtr->hdpaItems) return FALSE;
7149 /* if there are 0 or 1 items, there is no need to sort */
7150 if (infoPtr->nItemCount < 2) return TRUE;
7152 if (infoPtr->nFocusedItem >= 0)
7154 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7155 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7156 if (lpItem) lpItem->state |= LVIS_FOCUSED;
7158 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
7159 /* clear the lpItem->state for non-selected ones */
7160 /* remove the selection ranges */
7162 infoPtr->pfnCompare = pfnCompare;
7163 infoPtr->lParamSort = lParamSort;
7164 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr->hwndSelf);
7166 /* Adjust selections and indices so that they are the way they should
7167 * be after the sort (otherwise, the list items move around, but
7168 * whatever is at the item's previous original position will be
7171 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
7172 for (i=0; i < infoPtr->nItemCount; i++)
7174 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
7175 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7177 if (lpItem->state & LVIS_SELECTED)
7179 item.state = LVIS_SELECTED;
7180 item.stateMask = LVIS_SELECTED;
7181 LISTVIEW_SetItemState(infoPtr, i, &item);
7183 if (lpItem->state & LVIS_FOCUSED)
7185 infoPtr->nFocusedItem = i;
7186 lpItem->state &= ~LVIS_FOCUSED;
7189 if (selectionMarkItem != NULL)
7190 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7191 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7193 /* refresh the display */
7194 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7195 LISTVIEW_InvalidateList(infoPtr);
7202 * Updates an items or rearranges the listview control.
7205 * [I] infoPtr : valid pointer to the listview structure
7206 * [I] nItem : item index
7212 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
7214 TRACE("(nItem=%d)\n", nItem);
7216 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7218 /* rearrange with default alignment style */
7219 if (is_autoarrange(infoPtr))
7220 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7222 LISTVIEW_InvalidateItem(infoPtr, nItem);
7230 * Creates the listview control.
7233 * [I] hwnd : window handle
7234 * [I] lpcs : the create parameters
7240 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
7242 LISTVIEW_INFO *infoPtr;
7243 UINT uView = lpcs->style & LVS_TYPEMASK;
7246 TRACE("(lpcs=%p)\n", lpcs);
7248 /* initialize info pointer */
7249 infoPtr = (LISTVIEW_INFO *)Alloc(sizeof(LISTVIEW_INFO));
7250 if (!infoPtr) return -1;
7252 SetWindowLongW(hwnd, 0, (LONG)infoPtr);
7254 infoPtr->hwndSelf = hwnd;
7255 infoPtr->dwStyle = lpcs->style;
7256 /* determine the type of structures to use */
7257 infoPtr->hwndNotify = lpcs->hwndParent;
7258 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
7259 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
7261 /* initialize color information */
7262 infoPtr->clrBk = CLR_NONE;
7263 infoPtr->clrText = comctl32_color.clrWindowText;
7264 infoPtr->clrTextBk = CLR_DEFAULT;
7265 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
7267 /* set default values */
7268 infoPtr->nFocusedItem = -1;
7269 infoPtr->nSelectionMark = -1;
7270 infoPtr->nHotItem = -1;
7271 infoPtr->bRedraw = TRUE;
7272 infoPtr->bNoItemMetrics = TRUE;
7273 infoPtr->bDoChangeNotify = TRUE;
7274 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
7275 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
7276 infoPtr->nEditLabelItem = -1;
7277 infoPtr->dwHoverTime = -1; /* default system hover time */
7279 /* get default font (icon title) */
7280 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
7281 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
7282 infoPtr->hFont = infoPtr->hDefaultFont;
7283 LISTVIEW_SaveTextMetrics(infoPtr);
7286 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL,
7287 WS_CHILD | HDS_HORZ | (DWORD)((LVS_NOSORTHEADER & lpcs->style)?0:HDS_BUTTONS),
7288 0, 0, 0, 0, hwnd, NULL,
7289 lpcs->hInstance, NULL);
7290 if (!infoPtr->hwndHeader) goto fail;
7292 /* set header unicode format */
7293 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)NULL);
7295 /* set header font */
7296 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
7298 /* allocate memory for the data structure */
7299 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
7300 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
7301 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
7302 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
7303 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
7305 /* initialize the icon sizes */
7306 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
7307 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
7309 /* init item size to avoid division by 0 */
7310 LISTVIEW_UpdateItemSize (infoPtr);
7312 if (uView == LVS_REPORT)
7314 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
7316 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
7320 /* set HDS_HIDDEN flag to hide the header bar */
7321 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE,
7322 GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE) | HDS_HIDDEN);
7329 DestroyWindow(infoPtr->hwndHeader);
7330 ranges_destroy(infoPtr->selectionRanges);
7331 DPA_Destroy(infoPtr->hdpaItems);
7332 DPA_Destroy(infoPtr->hdpaPosX);
7333 DPA_Destroy(infoPtr->hdpaPosY);
7334 DPA_Destroy(infoPtr->hdpaColumns);
7341 * Erases the background of the listview control.
7344 * [I] infoPtr : valid pointer to the listview structure
7345 * [I] hdc : device context handle
7351 static inline BOOL LISTVIEW_EraseBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc)
7355 TRACE("(hdc=%p)\n", hdc);
7357 if (!GetClipBox(hdc, &rc)) return FALSE;
7359 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
7365 * Helper function for LISTVIEW_[HV]Scroll *only*.
7366 * Performs vertical/horizontal scrolling by a give amount.
7369 * [I] infoPtr : valid pointer to the listview structure
7370 * [I] dx : amount of horizontal scroll
7371 * [I] dy : amount of vertical scroll
7373 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7375 /* now we can scroll the list */
7376 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
7377 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
7378 /* if we have focus, adjust rect */
7379 OffsetRect(&infoPtr->rcFocus, dx, dy);
7380 UpdateWindow(infoPtr->hwndSelf);
7385 * Performs vertical scrolling.
7388 * [I] infoPtr : valid pointer to the listview structure
7389 * [I] nScrollCode : scroll code
7390 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7391 * [I] hScrollWnd : scrollbar control window handle
7397 * SB_LINEUP/SB_LINEDOWN:
7398 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
7399 * for LVS_REPORT is 1 line
7400 * for LVS_LIST cannot occur
7403 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7404 INT nScrollDiff, HWND hScrollWnd)
7406 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7407 INT nOldScrollPos, nNewScrollPos;
7408 SCROLLINFO scrollInfo;
7411 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
7412 debugscrollcode(nScrollCode), nScrollDiff);
7414 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7416 scrollInfo.cbSize = sizeof(SCROLLINFO);
7417 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7419 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
7421 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
7423 nOldScrollPos = scrollInfo.nPos;
7424 switch (nScrollCode)
7430 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
7434 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
7438 nScrollDiff = -scrollInfo.nPage;
7442 nScrollDiff = scrollInfo.nPage;
7445 case SB_THUMBPOSITION:
7447 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7454 /* quit right away if pos isn't changing */
7455 if (nScrollDiff == 0) return 0;
7457 /* calculate new position, and handle overflows */
7458 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7459 if (nScrollDiff > 0) {
7460 if (nNewScrollPos < nOldScrollPos ||
7461 nNewScrollPos > scrollInfo.nMax)
7462 nNewScrollPos = scrollInfo.nMax;
7464 if (nNewScrollPos > nOldScrollPos ||
7465 nNewScrollPos < scrollInfo.nMin)
7466 nNewScrollPos = scrollInfo.nMin;
7469 /* set the new position, and reread in case it changed */
7470 scrollInfo.fMask = SIF_POS;
7471 scrollInfo.nPos = nNewScrollPos;
7472 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
7474 /* carry on only if it really changed */
7475 if (nNewScrollPos == nOldScrollPos) return 0;
7477 /* now adjust to client coordinates */
7478 nScrollDiff = nOldScrollPos - nNewScrollPos;
7479 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
7481 /* and scroll the window */
7482 scroll_list(infoPtr, 0, nScrollDiff);
7489 * Performs horizontal scrolling.
7492 * [I] infoPtr : valid pointer to the listview structure
7493 * [I] nScrollCode : scroll code
7494 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7495 * [I] hScrollWnd : scrollbar control window handle
7501 * SB_LINELEFT/SB_LINERIGHT:
7502 * for LVS_ICON, LVS_SMALLICON 1 pixel
7503 * for LVS_REPORT is 1 pixel
7504 * for LVS_LIST is 1 column --> which is a 1 because the
7505 * scroll is based on columns not pixels
7508 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7509 INT nScrollDiff, HWND hScrollWnd)
7511 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7512 INT nOldScrollPos, nNewScrollPos;
7513 SCROLLINFO scrollInfo;
7515 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
7516 debugscrollcode(nScrollCode), nScrollDiff);
7518 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7520 scrollInfo.cbSize = sizeof(SCROLLINFO);
7521 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7523 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
7525 nOldScrollPos = scrollInfo.nPos;
7527 switch (nScrollCode)
7541 nScrollDiff = -scrollInfo.nPage;
7545 nScrollDiff = scrollInfo.nPage;
7548 case SB_THUMBPOSITION:
7550 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7557 /* quit right away if pos isn't changing */
7558 if (nScrollDiff == 0) return 0;
7560 /* calculate new position, and handle overflows */
7561 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7562 if (nScrollDiff > 0) {
7563 if (nNewScrollPos < nOldScrollPos ||
7564 nNewScrollPos > scrollInfo.nMax)
7565 nNewScrollPos = scrollInfo.nMax;
7567 if (nNewScrollPos > nOldScrollPos ||
7568 nNewScrollPos < scrollInfo.nMin)
7569 nNewScrollPos = scrollInfo.nMin;
7572 /* set the new position, and reread in case it changed */
7573 scrollInfo.fMask = SIF_POS;
7574 scrollInfo.nPos = nNewScrollPos;
7575 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
7577 /* carry on only if it really changed */
7578 if (nNewScrollPos == nOldScrollPos) return 0;
7580 if(uView == LVS_REPORT)
7581 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
7583 /* now adjust to client coordinates */
7584 nScrollDiff = nOldScrollPos - nNewScrollPos;
7585 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
7587 /* and scroll the window */
7588 scroll_list(infoPtr, nScrollDiff, 0);
7593 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
7595 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7596 INT gcWheelDelta = 0;
7597 UINT pulScrollLines = 3;
7598 SCROLLINFO scrollInfo;
7600 TRACE("(wheelDelta=%d)\n", wheelDelta);
7602 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
7603 gcWheelDelta -= wheelDelta;
7605 scrollInfo.cbSize = sizeof(SCROLLINFO);
7606 scrollInfo.fMask = SIF_POS;
7613 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
7614 * should be fixed in the future.
7616 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
7617 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
7621 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
7623 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
7624 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
7625 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
7630 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
7641 * [I] infoPtr : valid pointer to the listview structure
7642 * [I] nVirtualKey : virtual key
7643 * [I] lKeyData : key data
7648 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
7650 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7652 NMLVKEYDOWN nmKeyDown;
7654 TRACE("(nVirtualKey=%d, lKeyData=%ld)\n", nVirtualKey, lKeyData);
7656 /* send LVN_KEYDOWN notification */
7657 nmKeyDown.wVKey = nVirtualKey;
7658 nmKeyDown.flags = 0;
7659 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
7661 switch (nVirtualKey)
7664 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
7666 notify(infoPtr, NM_RETURN);
7667 notify(infoPtr, LVN_ITEMACTIVATE);
7672 if (infoPtr->nItemCount > 0)
7677 if (infoPtr->nItemCount > 0)
7678 nItem = infoPtr->nItemCount - 1;
7682 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
7686 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
7690 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
7694 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
7698 if (uView == LVS_REPORT)
7699 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr);
7701 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
7702 * LISTVIEW_GetCountPerRow(infoPtr);
7703 if(nItem < 0) nItem = 0;
7707 if (uView == LVS_REPORT)
7708 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr);
7710 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
7711 * LISTVIEW_GetCountPerRow(infoPtr);
7712 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
7716 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem))
7717 LISTVIEW_KeySelection(infoPtr, nItem);
7727 * [I] infoPtr : valid pointer to the listview structure
7732 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
7736 /* if we did not have the focus, there's nothing to do */
7737 if (!infoPtr->bFocus) return 0;
7739 /* send NM_KILLFOCUS notification */
7740 notify(infoPtr, NM_KILLFOCUS);
7742 /* if we have a focus rectagle, get rid of it */
7743 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
7745 /* set window focus flag */
7746 infoPtr->bFocus = FALSE;
7748 /* invalidate the selected items before reseting focus flag */
7749 LISTVIEW_InvalidateSelectedItems(infoPtr);
7757 * Track mouse/dragging
7760 * [I] infoPtr : valid pointer to the listview structure
7761 * [I] pt : mouse coordinate
7766 static LRESULT LISTVIEW_TrackMouse(LISTVIEW_INFO *infoPtr, POINT pt)
7768 INT cxDrag = GetSystemMetrics(SM_CXDRAG);
7769 INT cyDrag = GetSystemMetrics(SM_CYDRAG);
7775 r.top = pt.y - cyDrag;
7776 r.left = pt.x - cxDrag;
7777 r.bottom = pt.y + cyDrag;
7778 r.right = pt.x + cxDrag;
7780 SetCapture(infoPtr->hwndSelf);
7784 if (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD))
7786 if (msg.message == WM_MOUSEMOVE)
7788 pt.x = (short)LOWORD(msg.lParam);
7789 pt.y = (short)HIWORD(msg.lParam);
7790 if (PtInRect(&r, pt))
7798 else if (msg.message >= WM_LBUTTONDOWN &&
7799 msg.message <= WM_RBUTTONDBLCLK)
7804 DispatchMessageW(&msg);
7807 if (GetCapture() != infoPtr->hwndSelf)
7818 * Processes double click messages (left mouse button).
7821 * [I] infoPtr : valid pointer to the listview structure
7822 * [I] wKey : key flag
7823 * [I] pts : mouse coordinate
7828 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7830 LVHITTESTINFO htInfo;
7832 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7834 /* send NM_RELEASEDCAPTURE notification */
7835 notify(infoPtr, NM_RELEASEDCAPTURE);
7837 htInfo.pt.x = pts.x;
7838 htInfo.pt.y = pts.y;
7840 /* send NM_DBLCLK notification */
7841 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
7842 notify_click(infoPtr, NM_DBLCLK, &htInfo);
7844 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
7845 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
7852 * Processes mouse down messages (left mouse button).
7855 * [I] infoPtr : valid pointer to the listview structure
7856 * [I] wKey : key flag
7857 * [I] pts : mouse coordinate
7862 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7864 LVHITTESTINFO lvHitTestInfo;
7865 static BOOL bGroupSelect = TRUE;
7866 POINT pt = { pts.x, pts.y };
7869 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7871 /* send NM_RELEASEDCAPTURE notification */
7872 notify(infoPtr, NM_RELEASEDCAPTURE);
7874 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
7876 /* set left button down flag */
7877 infoPtr->bLButtonDown = TRUE;
7879 lvHitTestInfo.pt.x = pts.x;
7880 lvHitTestInfo.pt.y = pts.y;
7882 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
7883 TRACE("at %s, nItem=%d\n", debugpoint(&pt), nItem);
7884 infoPtr->nEditLabelItem = -1;
7885 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
7887 if (LISTVIEW_TrackMouse(infoPtr, lvHitTestInfo.pt))
7891 ZeroMemory(&nmlv, sizeof(nmlv));
7893 nmlv.ptAction.x = lvHitTestInfo.pt.x;
7894 nmlv.ptAction.y = lvHitTestInfo.pt.y;
7896 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
7901 if (infoPtr->dwStyle & LVS_SINGLESEL)
7903 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
7904 infoPtr->nEditLabelItem = nItem;
7906 LISTVIEW_SetSelection(infoPtr, nItem);
7910 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
7914 LISTVIEW_AddGroupSelection(infoPtr, nItem);
7915 LISTVIEW_SetItemFocus(infoPtr, nItem);
7916 infoPtr->nSelectionMark = nItem;
7922 item.state = LVIS_SELECTED | LVIS_FOCUSED;
7923 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
7925 LISTVIEW_SetItemState(infoPtr,nItem,&item);
7926 infoPtr->nSelectionMark = nItem;
7929 else if (wKey & MK_CONTROL)
7933 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
7935 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
7936 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
7937 LISTVIEW_SetItemState(infoPtr, nItem, &item);
7938 infoPtr->nSelectionMark = nItem;
7940 else if (wKey & MK_SHIFT)
7942 LISTVIEW_SetGroupSelection(infoPtr, nItem);
7946 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
7947 infoPtr->nEditLabelItem = nItem;
7949 /* set selection (clears other pre-existing selections) */
7950 LISTVIEW_SetSelection(infoPtr, nItem);
7956 /* remove all selections */
7957 LISTVIEW_DeselectAll(infoPtr);
7966 * Processes mouse up messages (left mouse button).
7969 * [I] infoPtr : valid pointer to the listview structure
7970 * [I] wKey : key flag
7971 * [I] pts : mouse coordinate
7976 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7978 LVHITTESTINFO lvHitTestInfo;
7980 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7982 if (!infoPtr->bLButtonDown) return 0;
7984 lvHitTestInfo.pt.x = pts.x;
7985 lvHitTestInfo.pt.y = pts.y;
7987 /* send NM_CLICK notification */
7988 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
7989 notify_click(infoPtr, NM_CLICK, &lvHitTestInfo);
7991 /* set left button flag */
7992 infoPtr->bLButtonDown = FALSE;
7994 /* if we clicked on a selected item, edit the label */
7995 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
7996 LISTVIEW_EditLabelT(infoPtr, lvHitTestInfo.iItem, TRUE);
8003 * Destroys the listview control (called after WM_DESTROY).
8006 * [I] infoPtr : valid pointer to the listview structure
8011 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8015 /* delete all items */
8016 LISTVIEW_DeleteAllItems(infoPtr);
8018 /* destroy data structure */
8019 DPA_Destroy(infoPtr->hdpaItems);
8020 DPA_Destroy(infoPtr->hdpaPosX);
8021 DPA_Destroy(infoPtr->hdpaPosY);
8022 DPA_Destroy(infoPtr->hdpaColumns);
8023 ranges_destroy(infoPtr->selectionRanges);
8025 /* destroy image lists */
8026 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8028 if (infoPtr->himlNormal)
8029 ImageList_Destroy(infoPtr->himlNormal);
8030 if (infoPtr->himlSmall)
8031 ImageList_Destroy(infoPtr->himlSmall);
8032 if (infoPtr->himlState)
8033 ImageList_Destroy(infoPtr->himlState);
8036 /* destroy font, bkgnd brush */
8038 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8039 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8041 SetWindowLongW(infoPtr->hwndSelf, 0, 0);
8043 /* free listview info pointer*/
8051 * Handles notifications from header.
8054 * [I] infoPtr : valid pointer to the listview structure
8055 * [I] nCtrlId : control identifier
8056 * [I] lpnmh : notification information
8061 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8063 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8065 TRACE("(lpnmh=%p)\n", lpnmh);
8067 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8069 switch (lpnmh->hdr.code)
8073 case HDN_ITEMCHANGEDW:
8074 case HDN_ITEMCHANGEDA:
8076 COLUMN_INFO *lpColumnInfo;
8079 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8083 hdi.mask = HDI_WIDTH;
8084 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, (LPARAM)&hdi)) return 0;
8088 cxy = lpnmh->pitem->cxy;
8090 /* determine how much we change since the last know position */
8091 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8092 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8095 RECT rcCol = lpColumnInfo->rcHeader;
8097 lpColumnInfo->rcHeader.right += dx;
8098 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
8099 if (uView == LVS_REPORT && is_redrawing(infoPtr))
8101 /* this trick works for left aligned columns only */
8102 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8104 rcCol.right = min (rcCol.right, lpColumnInfo->rcHeader.right);
8105 rcCol.left = max (rcCol.left, rcCol.right - 3 * infoPtr->ntmAveCharWidth);
8107 rcCol.top = infoPtr->rcList.top;
8108 rcCol.bottom = infoPtr->rcList.bottom;
8109 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
8115 case HDN_ITEMCLICKW:
8116 case HDN_ITEMCLICKA:
8118 /* Handle sorting by Header Column */
8121 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
8123 nmlv.iSubItem = lpnmh->iItem;
8124 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
8134 * Determines the type of structure to use.
8137 * [I] infoPtr : valid pointer to the listview structureof the sender
8138 * [I] hwndFrom : listview window handle
8139 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
8144 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
8146 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
8148 if (nCommand != NF_REQUERY) return 0;
8150 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
8157 * Paints/Repaints the listview control.
8160 * [I] infoPtr : valid pointer to the listview structure
8161 * [I] hdc : device context handle
8166 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
8168 TRACE("(hdc=%p)\n", hdc);
8170 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
8172 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8174 infoPtr->bNoItemMetrics = FALSE;
8175 LISTVIEW_UpdateItemSize(infoPtr);
8176 if (uView == LVS_ICON || uView == LVS_SMALLICON)
8177 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8178 LISTVIEW_UpdateScroll(infoPtr);
8181 LISTVIEW_Refresh(infoPtr, hdc);
8186 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
8188 if (ps.fErase) LISTVIEW_FillBkgnd(infoPtr, hdc, &ps.rcPaint);
8189 LISTVIEW_Refresh(infoPtr, hdc);
8190 EndPaint(infoPtr->hwndSelf, &ps);
8198 * Processes double click messages (right mouse button).
8201 * [I] infoPtr : valid pointer to the listview structure
8202 * [I] wKey : key flag
8203 * [I] pts : mouse coordinate
8208 static LRESULT LISTVIEW_RButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
8210 LVHITTESTINFO lvHitTestInfo;
8212 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
8214 /* send NM_RELEASEDCAPTURE notification */
8215 notify(infoPtr, NM_RELEASEDCAPTURE);
8217 /* send NM_RDBLCLK notification */
8218 lvHitTestInfo.pt.x = pts.x;
8219 lvHitTestInfo.pt.y = pts.y;
8220 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8221 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
8228 * Processes mouse down messages (right mouse button).
8231 * [I] infoPtr : valid pointer to the listview structure
8232 * [I] wKey : key flag
8233 * [I] pts : mouse coordinate
8238 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
8240 LVHITTESTINFO lvHitTestInfo;
8243 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
8245 /* send NM_RELEASEDCAPTURE notification */
8246 notify(infoPtr, NM_RELEASEDCAPTURE);
8248 /* make sure the listview control window has the focus */
8249 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
8251 /* set right button down flag */
8252 infoPtr->bRButtonDown = TRUE;
8254 /* determine the index of the selected item */
8255 lvHitTestInfo.pt.x = pts.x;
8256 lvHitTestInfo.pt.y = pts.y;
8257 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8259 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8261 LISTVIEW_SetItemFocus(infoPtr, nItem);
8262 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
8263 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8264 LISTVIEW_SetSelection(infoPtr, nItem);
8268 LISTVIEW_DeselectAll(infoPtr);
8276 * Processes mouse up messages (right mouse button).
8279 * [I] infoPtr : valid pointer to the listview structure
8280 * [I] wKey : key flag
8281 * [I] pts : mouse coordinate
8286 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
8288 LVHITTESTINFO lvHitTestInfo;
8291 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
8293 if (!infoPtr->bRButtonDown) return 0;
8295 /* set button flag */
8296 infoPtr->bRButtonDown = FALSE;
8298 /* Send NM_RClICK notification */
8299 lvHitTestInfo.pt.x = pts.x;
8300 lvHitTestInfo.pt.y = pts.y;
8301 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8302 notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo);
8304 /* Change to screen coordinate for WM_CONTEXTMENU */
8305 pt = lvHitTestInfo.pt;
8306 ClientToScreen(infoPtr->hwndSelf, &pt);
8308 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
8309 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
8310 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
8321 * [I] infoPtr : valid pointer to the listview structure
8322 * [I] hwnd : window handle of window containing the cursor
8323 * [I] nHittest : hit-test code
8324 * [I] wMouseMsg : ideintifier of the mouse message
8327 * TRUE if cursor is set
8330 static BOOL LISTVIEW_SetCursor(LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
8332 LVHITTESTINFO lvHitTestInfo;
8334 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
8336 if(!infoPtr->hHotCursor) return FALSE;
8338 GetCursorPos(&lvHitTestInfo.pt);
8339 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
8341 SetCursor(infoPtr->hHotCursor);
8351 * [I] infoPtr : valid pointer to the listview structure
8352 * [I] hwndLoseFocus : handle of previously focused window
8357 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
8359 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
8361 /* if we have the focus already, there's nothing to do */
8362 if (infoPtr->bFocus) return 0;
8364 /* send NM_SETFOCUS notification */
8365 notify(infoPtr, NM_SETFOCUS);
8367 /* set window focus flag */
8368 infoPtr->bFocus = TRUE;
8370 /* put the focus rect back on */
8371 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
8373 /* redraw all visible selected items */
8374 LISTVIEW_InvalidateSelectedItems(infoPtr);
8384 * [I] infoPtr : valid pointer to the listview structure
8385 * [I] fRedraw : font handle
8386 * [I] fRedraw : redraw flag
8391 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
8393 HFONT oldFont = infoPtr->hFont;
8395 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
8397 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
8398 if (infoPtr->hFont == oldFont) return 0;
8400 LISTVIEW_SaveTextMetrics(infoPtr);
8402 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
8403 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
8405 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
8412 * Message handling for WM_SETREDRAW.
8413 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
8416 * [I] infoPtr : valid pointer to the listview structure
8417 * [I] bRedraw: state of redraw flag
8420 * DefWinProc return value
8422 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
8424 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
8426 /* we can not use straight equality here because _any_ non-zero value is TRUE */
8427 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
8429 infoPtr->bRedraw = bRedraw;
8431 if(!bRedraw) return 0;
8433 if (is_autoarrange(infoPtr))
8434 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8435 LISTVIEW_UpdateScroll(infoPtr);
8437 /* despite what the WM_SETREDRAW docs says, apps expect us
8438 * to invalidate the listview here... stupid! */
8439 LISTVIEW_InvalidateList(infoPtr);
8446 * Resizes the listview control. This function processes WM_SIZE
8447 * messages. At this time, the width and height are not used.
8450 * [I] infoPtr : valid pointer to the listview structure
8451 * [I] Width : new width
8452 * [I] Height : new height
8457 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
8459 RECT rcOld = infoPtr->rcList;
8461 TRACE("(width=%d, height=%d)\n", Width, Height);
8463 LISTVIEW_UpdateSize(infoPtr);
8464 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
8466 /* do not bother with display related stuff if we're not redrawing */
8467 if (!is_redrawing(infoPtr)) return 0;
8469 if (is_autoarrange(infoPtr))
8470 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8472 LISTVIEW_UpdateScroll(infoPtr);
8474 /* refresh all only for lists whose height changed significantly */
8475 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
8476 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
8477 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
8478 LISTVIEW_InvalidateList(infoPtr);
8485 * Sets the size information.
8488 * [I] infoPtr : valid pointer to the listview structure
8493 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
8495 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8497 TRACE("uView=%d, rcList(old)=%s\n", uView, debugrect(&infoPtr->rcList));
8499 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
8501 if (uView == LVS_LIST)
8503 /* Apparently the "LIST" style is supposed to have the same
8504 * number of items in a column even if there is no scroll bar.
8505 * Since if a scroll bar already exists then the bottom is already
8506 * reduced, only reduce if the scroll bar does not currently exist.
8507 * The "2" is there to mimic the native control. I think it may be
8508 * related to either padding or edges. (GLA 7/2002)
8510 if (!(infoPtr->dwStyle & WS_HSCROLL))
8511 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
8512 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
8514 else if (uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER))
8519 hl.prc = &infoPtr->rcList;
8521 Header_Layout(infoPtr->hwndHeader, &hl);
8523 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8525 infoPtr->rcList.top = max(wp.cy, 0);
8528 TRACE(" rcList=%s\n", debugrect(&infoPtr->rcList));
8533 * Processes WM_STYLECHANGED messages.
8536 * [I] infoPtr : valid pointer to the listview structure
8537 * [I] wStyleType : window style type (normal or extended)
8538 * [I] lpss : window style information
8543 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
8544 const STYLESTRUCT *lpss)
8546 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
8547 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
8549 TRACE("(styletype=%x, styleOld=0x%08lx, styleNew=0x%08lx)\n",
8550 wStyleType, lpss->styleOld, lpss->styleNew);
8552 if (wStyleType != GWL_STYLE) return 0;
8554 /* FIXME: if LVS_NOSORTHEADER changed, update header */
8555 /* what if LVS_OWNERDATA changed? */
8556 /* or LVS_SINGLESEL */
8557 /* or LVS_SORT{AS,DES}CENDING */
8559 infoPtr->dwStyle = lpss->styleNew;
8561 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
8562 ((lpss->styleNew & WS_HSCROLL) == 0))
8563 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
8565 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
8566 ((lpss->styleNew & WS_VSCROLL) == 0))
8567 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
8569 if (uNewView != uOldView)
8571 SIZE oldIconSize = infoPtr->iconSize;
8574 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8575 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8577 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
8578 SetRectEmpty(&infoPtr->rcFocus);
8580 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
8581 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
8583 if (uNewView == LVS_ICON)
8585 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
8587 TRACE("icon old size=(%ld,%ld), new size=(%ld,%ld)\n",
8588 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
8589 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
8592 else if (uNewView == LVS_REPORT)
8597 hl.prc = &infoPtr->rcList;
8599 Header_Layout(infoPtr->hwndHeader, &hl);
8600 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8603 LISTVIEW_UpdateItemSize(infoPtr);
8606 if (uNewView == LVS_REPORT)
8607 ShowWindow(infoPtr->hwndHeader, (lpss->styleNew & LVS_NOCOLUMNHEADER) ? SW_HIDE : SW_SHOWNORMAL);
8609 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
8610 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
8611 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8613 /* update the size of the client area */
8614 LISTVIEW_UpdateSize(infoPtr);
8616 /* add scrollbars if needed */
8617 LISTVIEW_UpdateScroll(infoPtr);
8619 /* invalidate client area + erase background */
8620 LISTVIEW_InvalidateList(infoPtr);
8627 * Window procedure of the listview control.
8630 static LRESULT WINAPI
8631 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8633 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(hwnd, 0);
8635 TRACE("(uMsg=%x wParam=%x lParam=%lx)\n", uMsg, wParam, lParam);
8637 if (!infoPtr && (uMsg != WM_CREATE))
8638 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8642 infoPtr->dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
8647 case LVM_APPROXIMATEVIEWRECT:
8648 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
8649 LOWORD(lParam), HIWORD(lParam));
8651 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
8653 /* case LVM_CANCELEDITLABEL: */
8655 case LVM_CREATEDRAGIMAGE:
8656 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
8658 case LVM_DELETEALLITEMS:
8659 return LISTVIEW_DeleteAllItems(infoPtr);
8661 case LVM_DELETECOLUMN:
8662 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
8664 case LVM_DELETEITEM:
8665 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
8667 case LVM_EDITLABELW:
8668 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
8670 case LVM_EDITLABELA:
8671 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
8673 /* case LVM_ENABLEGROUPVIEW: */
8675 case LVM_ENSUREVISIBLE:
8676 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
8679 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
8682 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
8684 case LVM_GETBKCOLOR:
8685 return infoPtr->clrBk;
8687 /* case LVM_GETBKIMAGE: */
8689 case LVM_GETCALLBACKMASK:
8690 return infoPtr->uCallbackMask;
8692 case LVM_GETCOLUMNA:
8693 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8695 case LVM_GETCOLUMNW:
8696 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8698 case LVM_GETCOLUMNORDERARRAY:
8699 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
8701 case LVM_GETCOLUMNWIDTH:
8702 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
8704 case LVM_GETCOUNTPERPAGE:
8705 return LISTVIEW_GetCountPerPage(infoPtr);
8707 case LVM_GETEDITCONTROL:
8708 return (LRESULT)infoPtr->hwndEdit;
8710 case LVM_GETEXTENDEDLISTVIEWSTYLE:
8711 return infoPtr->dwLvExStyle;
8713 /* case LVM_GETGROUPINFO: */
8715 /* case LVM_GETGROUPMETRICS: */
8718 return (LRESULT)infoPtr->hwndHeader;
8720 case LVM_GETHOTCURSOR:
8721 return (LRESULT)infoPtr->hHotCursor;
8723 case LVM_GETHOTITEM:
8724 return infoPtr->nHotItem;
8726 case LVM_GETHOVERTIME:
8727 return infoPtr->dwHoverTime;
8729 case LVM_GETIMAGELIST:
8730 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
8732 /* case LVM_GETINSERTMARK: */
8734 /* case LVM_GETINSERTMARKCOLOR: */
8736 /* case LVM_GETINSERTMARKRECT: */
8738 case LVM_GETISEARCHSTRINGA:
8739 case LVM_GETISEARCHSTRINGW:
8740 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
8744 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
8747 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
8749 case LVM_GETITEMCOUNT:
8750 return infoPtr->nItemCount;
8752 case LVM_GETITEMPOSITION:
8753 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
8755 case LVM_GETITEMRECT:
8756 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
8758 case LVM_GETITEMSPACING:
8759 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
8761 case LVM_GETITEMSTATE:
8762 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
8764 case LVM_GETITEMTEXTA:
8765 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
8767 case LVM_GETITEMTEXTW:
8768 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
8770 case LVM_GETNEXTITEM:
8771 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
8773 case LVM_GETNUMBEROFWORKAREAS:
8774 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
8778 if (!lParam) return FALSE;
8779 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
8782 /* case LVM_GETOUTLINECOLOR: */
8784 /* case LVM_GETSELECTEDCOLUMN: */
8786 case LVM_GETSELECTEDCOUNT:
8787 return LISTVIEW_GetSelectedCount(infoPtr);
8789 case LVM_GETSELECTIONMARK:
8790 return infoPtr->nSelectionMark;
8792 case LVM_GETSTRINGWIDTHA:
8793 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
8795 case LVM_GETSTRINGWIDTHW:
8796 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
8798 case LVM_GETSUBITEMRECT:
8799 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
8801 case LVM_GETTEXTBKCOLOR:
8802 return infoPtr->clrTextBk;
8804 case LVM_GETTEXTCOLOR:
8805 return infoPtr->clrText;
8807 /* case LVM_GETTILEINFO: */
8809 /* case LVM_GETTILEVIEWINFO: */
8811 case LVM_GETTOOLTIPS:
8812 return (LRESULT)infoPtr->hwndToolTip;
8814 case LVM_GETTOPINDEX:
8815 return LISTVIEW_GetTopIndex(infoPtr);
8817 /*case LVM_GETUNICODEFORMAT:
8818 FIXME("LVM_GETUNICODEFORMAT: unimplemented\n");
8821 /* case LVM_GETVIEW: */
8823 case LVM_GETVIEWRECT:
8824 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
8826 case LVM_GETWORKAREAS:
8827 FIXME("LVM_GETWORKAREAS: unimplemented\n");
8830 /* case LVM_HASGROUP: */
8833 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
8835 case LVM_INSERTCOLUMNA:
8836 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8838 case LVM_INSERTCOLUMNW:
8839 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8841 /* case LVM_INSERTGROUP: */
8843 /* case LVM_INSERTGROUPSORTED: */
8845 case LVM_INSERTITEMA:
8846 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
8848 case LVM_INSERTITEMW:
8849 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
8851 /* case LVM_INSERTMARKHITTEST: */
8853 /* case LVM_ISGROUPVIEWENABLED: */
8855 /* case LVM_MAPIDTOINDEX: */
8857 /* case LVM_MAPINDEXTOID: */
8859 /* case LVM_MOVEGROUP: */
8861 /* case LVM_MOVEITEMTOGROUP: */
8863 case LVM_REDRAWITEMS:
8864 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
8866 /* case LVM_REMOVEALLGROUPS: */
8868 /* case LVM_REMOVEGROUP: */
8871 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
8873 case LVM_SETBKCOLOR:
8874 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
8876 /* case LVM_SETBKIMAGE: */
8878 case LVM_SETCALLBACKMASK:
8879 infoPtr->uCallbackMask = (UINT)wParam;
8882 case LVM_SETCOLUMNA:
8883 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8885 case LVM_SETCOLUMNW:
8886 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8888 case LVM_SETCOLUMNORDERARRAY:
8889 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
8891 case LVM_SETCOLUMNWIDTH:
8892 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
8894 case LVM_SETEXTENDEDLISTVIEWSTYLE:
8895 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
8897 /* case LVM_SETGROUPINFO: */
8899 /* case LVM_SETGROUPMETRICS: */
8901 case LVM_SETHOTCURSOR:
8902 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
8904 case LVM_SETHOTITEM:
8905 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
8907 case LVM_SETHOVERTIME:
8908 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
8910 case LVM_SETICONSPACING:
8911 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
8913 case LVM_SETIMAGELIST:
8914 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
8916 /* case LVM_SETINFOTIP: */
8918 /* case LVM_SETINSERTMARK: */
8920 /* case LVM_SETINSERTMARKCOLOR: */
8923 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
8926 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
8928 case LVM_SETITEMCOUNT:
8929 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
8931 case LVM_SETITEMPOSITION:
8934 pt.x = (short)LOWORD(lParam);
8935 pt.y = (short)HIWORD(lParam);
8936 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
8939 case LVM_SETITEMPOSITION32:
8940 if (lParam == 0) return FALSE;
8941 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
8943 case LVM_SETITEMSTATE:
8944 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
8946 case LVM_SETITEMTEXTA:
8947 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
8949 case LVM_SETITEMTEXTW:
8950 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
8952 /* case LVM_SETOUTLINECOLOR: */
8954 /* case LVM_SETSELECTEDCOLUMN: */
8956 case LVM_SETSELECTIONMARK:
8957 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
8959 case LVM_SETTEXTBKCOLOR:
8960 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
8962 case LVM_SETTEXTCOLOR:
8963 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
8965 /* case LVM_SETTILEINFO: */
8967 /* case LVM_SETTILEVIEWINFO: */
8969 /* case LVM_SETTILEWIDTH: */
8971 case LVM_SETTOOLTIPS:
8972 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
8974 /* case LVM_SETUNICODEFORMAT: */
8976 /* case LVM_SETVIEW: */
8978 /* case LVM_SETWORKAREAS: */
8980 /* case LVM_SORTGROUPS: */
8983 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
8985 /* LVM_SORTITEMSEX: */
8987 case LVM_SUBITEMHITTEST:
8988 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
8991 return LISTVIEW_Update(infoPtr, (INT)wParam);
8994 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
8997 return LISTVIEW_Command(infoPtr, wParam, lParam);
9000 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
9003 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
9006 return DLGC_WANTCHARS | DLGC_WANTARROWS;
9009 return (LRESULT)infoPtr->hFont;
9012 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9015 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
9018 return LISTVIEW_KillFocus(infoPtr);
9020 case WM_LBUTTONDBLCLK:
9021 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9023 case WM_LBUTTONDOWN:
9024 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9027 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9030 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9033 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9036 return LISTVIEW_NCDestroy(infoPtr);
9039 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
9040 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
9043 case WM_NOTIFYFORMAT:
9044 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
9047 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
9049 case WM_RBUTTONDBLCLK:
9050 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9052 case WM_RBUTTONDOWN:
9053 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9056 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9059 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
9064 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
9067 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
9070 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
9073 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9075 case WM_STYLECHANGED:
9076 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
9078 case WM_SYSCOLORCHANGE:
9079 COMCTL32_RefreshSysColors();
9082 /* case WM_TIMER: */
9085 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9088 if (wParam & (MK_SHIFT | MK_CONTROL))
9089 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9090 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
9092 case WM_WINDOWPOSCHANGED:
9093 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
9095 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
9096 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
9097 LISTVIEW_UpdateSize(infoPtr);
9098 LISTVIEW_UpdateScroll(infoPtr);
9100 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9102 /* case WM_WININICHANGE: */
9105 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
9106 ERR("unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam);
9109 /* call default window procedure */
9110 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9118 * Registers the window class.
9126 void LISTVIEW_Register(void)
9130 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
9131 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
9132 wndClass.lpfnWndProc = (WNDPROC)LISTVIEW_WindowProc;
9133 wndClass.cbClsExtra = 0;
9134 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
9135 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
9136 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
9137 wndClass.lpszClassName = WC_LISTVIEWW;
9138 RegisterClassW(&wndClass);
9143 * Unregisters the window class.
9151 void LISTVIEW_Unregister(void)
9153 UnregisterClassW(WC_LISTVIEWW, NULL);
9158 * Handle any WM_COMMAND messages
9161 * [I] infoPtr : valid pointer to the listview structure
9162 * [I] wParam : the first message parameter
9163 * [I] lParam : the second message parameter
9168 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
9170 switch (HIWORD(wParam))
9175 * Adjust the edit window size
9178 HDC hdc = GetDC(infoPtr->hwndEdit);
9179 HFONT hFont, hOldFont = 0;
9184 if (!infoPtr->hwndEdit || !hdc) return 0;
9185 len = GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
9186 GetWindowRect(infoPtr->hwndEdit, &rect);
9188 /* Select font to get the right dimension of the string */
9189 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
9192 hOldFont = SelectObject(hdc, hFont);
9195 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
9197 TEXTMETRICW textMetric;
9199 /* Add Extra spacing for the next character */
9200 GetTextMetricsW(hdc, &textMetric);
9201 sz.cx += (textMetric.tmMaxCharWidth * 2);
9209 rect.bottom - rect.top,
9210 SWP_DRAWFRAME|SWP_NOMOVE);
9213 SelectObject(hdc, hOldFont);
9215 ReleaseDC(infoPtr->hwndSelf, hdc);
9221 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
9230 * Subclassed edit control windproc function
9233 * [I] hwnd : the edit window handle
9234 * [I] uMsg : the message that is to be processed
9235 * [I] wParam : first message parameter
9236 * [I] lParam : second message parameter
9237 * [I] isW : TRUE if input is Unicode
9242 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
9244 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(GetParent(hwnd), 0);
9245 BOOL cancel = FALSE;
9247 TRACE("(hwnd=%p, uMsg=%x, wParam=%x, lParam=%lx, isW=%d)\n",
9248 hwnd, uMsg, wParam, lParam, isW);
9253 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
9260 WNDPROC editProc = infoPtr->EditWndProc;
9261 infoPtr->EditWndProc = 0;
9262 SetWindowLongW(hwnd, GWL_WNDPROC, (LONG)editProc);
9263 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
9267 if (VK_ESCAPE == (INT)wParam)
9272 else if (VK_RETURN == (INT)wParam)
9276 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
9280 if (infoPtr->hwndEdit)
9282 LPWSTR buffer = NULL;
9284 infoPtr->hwndEdit = 0;
9287 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
9291 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
9293 if (isW) GetWindowTextW(hwnd, buffer, len+1);
9294 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
9298 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
9300 if (buffer) Free(buffer);
9304 SendMessageW(hwnd, WM_CLOSE, 0, 0);
9310 * Subclassed edit control Unicode windproc function
9313 * [I] hwnd : the edit window handle
9314 * [I] uMsg : the message that is to be processed
9315 * [I] wParam : first message parameter
9316 * [I] lParam : second message parameter
9320 LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9322 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
9327 * Subclassed edit control ANSI windproc function
9330 * [I] hwnd : the edit window handle
9331 * [I] uMsg : the message that is to be processed
9332 * [I] wParam : first message parameter
9333 * [I] lParam : second message parameter
9337 LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9339 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
9344 * Creates a subclassed edit cotrol
9347 * [I] infoPtr : valid pointer to the listview structure
9348 * [I] text : initial text for the edit
9349 * [I] style : the window style
9350 * [I] isW : TRUE if input is Unicode
9354 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
9355 INT x, INT y, INT width, INT height, BOOL isW)
9357 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
9362 TEXTMETRICW textMetric;
9363 HINSTANCE hinst = (HINSTANCE)GetWindowLongW(infoPtr->hwndSelf, GWL_HINSTANCE);
9365 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
9367 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|WS_BORDER;
9368 hdc = GetDC(infoPtr->hwndSelf);
9370 /* Select the font to get appropriate metric dimensions */
9371 if(infoPtr->hFont != 0)
9372 hOldFont = SelectObject(hdc, infoPtr->hFont);
9374 /*Get String Length in pixels */
9375 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
9377 /*Add Extra spacing for the next character */
9378 GetTextMetricsW(hdc, &textMetric);
9379 sz.cx += (textMetric.tmMaxCharWidth * 2);
9381 if(infoPtr->hFont != 0)
9382 SelectObject(hdc, hOldFont);
9384 ReleaseDC(infoPtr->hwndSelf, hdc);
9386 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
9388 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
9390 if (!hedit) return 0;
9392 infoPtr->EditWndProc = (WNDPROC)
9393 (isW ? SetWindowLongW(hedit, GWL_WNDPROC, (LONG)EditLblWndProcW) :
9394 SetWindowLongA(hedit, GWL_WNDPROC, (LONG)EditLblWndProcA) );
9396 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);