4 * Copyright 1998, 1999 Eric Kohl
5 * Copyright 1999 Luc Tourangeau
6 * Copyright 2000 Jason Mawdsley
7 * Copyright 2001 Codeweavers Inc.
8 * Copyright 2002 Dimitrie O. Paun
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 * This code was audited for completeness against the documented features
27 * of Comctl32.dll version 6.0 on Oct. 21, 2002, by Dimitrie O. Paun.
29 * Unless otherwise noted, we belive this code to be complete, as per
30 * the specification mentioned above.
31 * If you discover missing features, or bugs, please note them below.
36 * -- Hot item handling, mouse hovering
37 * -- Workareas support
42 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
43 * -- Support CustonDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs.
44 * -- in LISTVIEW_AddGroupSelection, se whould send LVN_ODSTATECHANGED
45 * -- LVA_SNAPTOGRID not implemented
46 * -- LISTVIEW_ApproximateViewRect partially implemented
47 * -- LISTVIEW_[GS]etColumnOrderArray stubs
48 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
49 * -- LISTVIEW_SetIconSpacing is incomplete
50 * -- LISTVIEW_SortItems is broken
51 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
54 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
55 * linear in the number of items in the list, and this is
56 * unacceptable for large lists.
57 * -- in sorted mode, LISTVIEW_InsertItemT sorts the array,
58 * instead of inserting in the right spot
59 * -- we should keep an ordered array of coordinates in iconic mode
60 * this would allow to frame items (iterator_frameditems),
61 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
69 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
76 * -- LVS_NOSCROLL (see Q137520)
77 * -- LVS_SORTASCENDING, LVS_SORTDESCENDING
80 * -- LVS_EX_BORDERSELECT
81 * -- LVS_EX_CHECKBOXES
84 * -- LVS_EX_HEADERDRAGDROP
87 * -- LVS_EX_MULTIWORKAREAS
88 * -- LVS_EX_ONECLICKACTIVATE
90 * -- LVS_EX_SIMPLESELECT
91 * -- LVS_EX_SUBITEMIMAGES
92 * -- LVS_EX_TRACKSELECT
93 * -- LVS_EX_TWOCLICKACTIVATE
94 * -- LVS_EX_UNDERLINECOLD
95 * -- LVS_EX_UNDERLINEHOT
98 * -- LVN_BEGINDRAG, LVN_BEGINRDRAG
99 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
102 * -- LVN_MARQUEEBEGIN
104 * -- LVN_ODSTATECHANGED
109 * -- LVM_CANCELEDITLABEL
110 * -- LVM_CREATEDRAGIMAGE
111 * -- LVM_ENABLEGROUPVIEW
112 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
113 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
114 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
115 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
116 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
117 * -- LVM_GETINSERTMARKRECT
118 * -- LVM_GETNUMBEROFWORKAREAS
119 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
120 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
121 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
122 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
123 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
124 * -- LVM_GETTOOLTIPS, LVM_SETTOOLTIPS
125 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
126 * -- LVM_GETVIEW, LVM_SETVIEW
127 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
128 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
129 * -- LVM_INSERTGROUPSORTED
130 * -- LVM_INSERTMARKHITTEST
131 * -- LVM_ISGROUPVIEWENABLED
132 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
134 * -- LVM_MOVEITEMTOGROUP
136 * -- LVM_SETTILEWIDTH
140 * Known differences in message stream from native control (not known if
141 * these differences cause problems):
142 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
143 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
144 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
145 * processing for "USEDOUBLECLICKTIME".
149 #include "wine/port.h"
160 #include "commctrl.h"
161 #include "comctl32.h"
163 #include "wine/debug.h"
164 #include "wine/unicode.h"
166 WINE_DEFAULT_DEBUG_CHANNEL(listview);
168 /* make sure you set this to 0 for production use! */
169 #define DEBUG_RANGES 1
171 typedef struct tagCOLUMN_INFO
173 RECT rcHeader; /* tracks the header's rectangle */
174 int fmt; /* same as LVCOLUMN.fmt */
177 typedef struct tagITEMHDR
181 } ITEMHDR, *LPITEMHDR;
183 typedef struct tagSUBITEM_INFO
189 typedef struct tagITEM_INFO
197 typedef struct tagRANGE
203 typedef struct tagRANGES
208 typedef struct tagITERATOR
217 typedef struct tagLISTVIEW_INFO
224 COLORREF clrTextBkDefault;
225 HIMAGELIST himlNormal;
226 HIMAGELIST himlSmall;
227 HIMAGELIST himlState;
232 RANGES selectionRanges;
236 RECT rcList; /* This rectangle is really the window
237 * client rectangle possibly reduced by the
238 * horizontal scroll bar and/or header - see
239 * LISTVIEW_UpdateSize. This rectangle offset
240 * by the LISTVIEW_GetOrigin value is in
241 * client coordinates */
250 INT ntmHeight; /* Some cached metrics of the font used */
251 INT ntmAveCharWidth; /* by the listview to draw items */
252 BOOL bRedraw; /* Turns on/off repaints & invalidations */
253 BOOL bFirstPaint; /* Flags if the control has never painted before */
254 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
258 DWORD dwStyle; /* the cached window GWL_STYLE */
259 DWORD dwLvExStyle; /* extended listview style */
260 INT nItemCount; /* the number of items in the list */
261 HDPA hdpaItems; /* array ITEM_INFO pointers */
262 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
263 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
264 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
265 POINT currIconPos; /* this is the position next icon will be placed */
266 PFNLVCOMPARE pfnCompare;
273 DWORD lastKeyPressTimestamp;
275 INT nSearchParamLength;
276 WCHAR szSearchParam[ MAX_PATH ];
283 /* How many we debug buffer to allocate */
284 #define DEBUG_BUFFERS 20
285 /* The size of a single debug bbuffer */
286 #define DEBUG_BUFFER_SIZE 256
288 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
289 #define SB_INTERNAL -1
291 /* maximum size of a label */
292 #define DISP_TEXT_SIZE 512
294 /* padding for items in list and small icon display modes */
295 #define WIDTH_PADDING 12
297 /* padding for items in list, report and small icon display modes */
298 #define HEIGHT_PADDING 1
300 /* offset of items in report display mode */
301 #define REPORT_MARGINX 2
303 /* padding for icon in large icon display mode
304 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
305 * that HITTEST will see.
306 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
307 * ICON_TOP_PADDING - sum of the two above.
308 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
309 * LABEL_VERT_PADDING - between bottom of text and end of box
311 * ICON_LR_PADDING - additional width above icon size.
312 * ICON_LR_HALF - half of the above value
314 #define ICON_TOP_PADDING_NOTHITABLE 2
315 #define ICON_TOP_PADDING_HITABLE 2
316 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
317 #define ICON_BOTTOM_PADDING 4
318 #define LABEL_VERT_PADDING 7
319 #define ICON_LR_PADDING 16
320 #define ICON_LR_HALF (ICON_LR_PADDING/2)
322 /* default label width for items in list and small icon display modes */
323 #define DEFAULT_LABEL_WIDTH 40
325 /* default column width for items in list display mode */
326 #define DEFAULT_COLUMN_WIDTH 128
328 /* Size of "line" scroll for V & H scrolls */
329 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
331 /* Padding betwen image and label */
332 #define IMAGE_PADDING 2
334 /* Padding behind the label */
335 #define TRAILING_PADDING 5
337 /* Border for the icon caption */
338 #define CAPTION_BORDER 2
340 /* Standard DrawText flags */
341 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
342 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
343 #define LV_SL_DT_FLAGS (DT_TOP | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
345 /* The time in milliseconds to reset the search in the list */
346 #define KEY_DELAY 450
348 /* Dump the LISTVIEW_INFO structure to the debug channel */
349 #define LISTVIEW_DUMP(iP) do { \
350 TRACE("hwndSelf=%p, clrBk=0x%06lx, clrText=0x%06lx, clrTextBk=0x%06lx, ItemHeight=%d, ItemWidth=%d, Style=0x%08lx\n", \
351 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
352 iP->nItemHeight, iP->nItemWidth, infoPtr->dwStyle); \
353 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08lx, Focus=%d\n", \
354 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
355 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
356 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%ld, icSz.cy=%ld, icSp.cx=%ld, icSp.cy=%ld, notifyFmt=%d\n", \
357 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
358 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
359 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, debugrect(&iP->rcList)); \
364 * forward declarations
366 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *, LPLVITEMW, BOOL);
367 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *, INT, LPRECT);
368 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *, INT, LPPOINT);
369 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *, INT, LPPOINT);
370 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *, INT, LPRECT);
371 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *, INT);
372 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *, LPPOINT);
373 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *, LPRECT);
374 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
375 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, const LVITEMW *, BOOL);
376 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *);
377 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
378 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
379 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
380 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
381 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *, PFNLVCOMPARE, LPARAM);
382 static INT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *, LPCWSTR, BOOL);
383 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT);
384 static UINT LISTVIEW_GetItemState(LISTVIEW_INFO *, INT, UINT);
385 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
386 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
387 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
388 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *);
389 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
390 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
392 /******** Text handling functions *************************************/
394 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
395 * text string. The string may be ANSI or Unicode, in which case
396 * the boolean isW tells us the type of the string.
398 * The name of the function tell what type of strings it expects:
399 * W: Unicode, T: ANSI/Unicode - function of isW
402 static inline BOOL is_textW(LPCWSTR text)
404 return text != NULL && text != LPSTR_TEXTCALLBACKW;
407 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
409 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
410 return is_textW(text);
413 static inline int textlenT(LPCWSTR text, BOOL isW)
415 return !is_textT(text, isW) ? 0 :
416 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
419 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
422 if (isSrcW) lstrcpynW(dest, src, max);
423 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
425 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
426 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
429 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
431 LPWSTR wstr = (LPWSTR)text;
433 if (!isW && is_textT(text, isW))
435 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
436 wstr = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
437 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
439 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
443 static inline void textfreeT(LPWSTR wstr, BOOL isW)
445 if (!isW && is_textT(wstr, isW)) HeapFree(GetProcessHeap(), 0, wstr);
449 * dest is a pointer to a Unicode string
450 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
452 static BOOL textsetptrT(LPWSTR *dest, LPWSTR src, BOOL isW)
456 if (src == LPSTR_TEXTCALLBACKW)
458 if (is_textW(*dest)) COMCTL32_Free(*dest);
459 *dest = LPSTR_TEXTCALLBACKW;
463 LPWSTR pszText = textdupTtoW(src, isW);
464 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
465 bResult = Str_SetPtrW(dest, pszText);
466 textfreeT(pszText, isW);
472 * compares a Unicode to a Unicode/ANSI text string
474 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
476 if (!aw) return bt ? -1 : 0;
477 if (!bt) return aw ? 1 : 0;
478 if (aw == LPSTR_TEXTCALLBACKW)
479 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
480 if (bt != LPSTR_TEXTCALLBACKW)
482 LPWSTR bw = textdupTtoW(bt, isW);
483 int r = bw ? lstrcmpW(aw, bw) : 1;
491 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
495 n = min(min(n, strlenW(s1)), strlenW(s2));
496 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
497 return res ? res - sizeof(WCHAR) : res;
500 /******** Debugging functions *****************************************/
502 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
504 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
505 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
508 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
510 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
511 n = min(textlenT(text, isW), n);
512 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
515 static char* debug_getbuf()
517 static int index = 0;
518 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
519 return buffers[index++ % DEBUG_BUFFERS];
522 static inline char* debugrange(const RANGE *lprng)
526 char* buf = debug_getbuf();
527 snprintf(buf, DEBUG_BUFFER_SIZE, "[%d, %d)", lprng->lower, lprng->upper);
529 } else return "(null)";
532 static inline char* debugpoint(const POINT *lppt)
536 char* buf = debug_getbuf();
537 snprintf(buf, DEBUG_BUFFER_SIZE, "(%ld, %ld)", lppt->x, lppt->y);
539 } else return "(null)";
542 static inline char* debugrect(const RECT *rect)
546 char* buf = debug_getbuf();
547 snprintf(buf, DEBUG_BUFFER_SIZE, "[(%d, %d);(%d, %d)]",
548 rect->left, rect->top, rect->right, rect->bottom);
550 } else return "(null)";
553 static char * debugscrollinfo(const SCROLLINFO *pScrollInfo)
555 char* buf = debug_getbuf(), *text = buf;
556 int len, size = DEBUG_BUFFER_SIZE;
558 if (pScrollInfo == NULL) return "(null)";
559 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
560 if (len == -1) goto end; buf += len; size -= len;
561 if (pScrollInfo->fMask & SIF_RANGE)
562 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
564 if (len == -1) goto end; buf += len; size -= len;
565 if (pScrollInfo->fMask & SIF_PAGE)
566 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
568 if (len == -1) goto end; buf += len; size -= len;
569 if (pScrollInfo->fMask & SIF_POS)
570 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
572 if (len == -1) goto end; buf += len; size -= len;
573 if (pScrollInfo->fMask & SIF_TRACKPOS)
574 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
576 if (len == -1) goto end; buf += len; size -= len;
579 buf = text + strlen(text);
581 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
585 static char* debugnmlistview(const NMLISTVIEW *plvnm)
589 char* buf = debug_getbuf();
590 snprintf(buf, DEBUG_BUFFER_SIZE, "iItem=%d, iSubItem=%d, uNewState=0x%x,"
591 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld\n",
592 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
593 plvnm->uChanged, debugpoint(&plvnm->ptAction), plvnm->lParam);
595 } else return "(null)";
598 static char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
600 char* buf = debug_getbuf(), *text = buf;
601 int len, size = DEBUG_BUFFER_SIZE;
603 if (lpLVItem == NULL) return "(null)";
604 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
605 if (len == -1) goto end; buf += len; size -= len;
606 if (lpLVItem->mask & LVIF_STATE)
607 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
609 if (len == -1) goto end; buf += len; size -= len;
610 if (lpLVItem->mask & LVIF_TEXT)
611 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
613 if (len == -1) goto end; buf += len; size -= len;
614 if (lpLVItem->mask & LVIF_IMAGE)
615 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
617 if (len == -1) goto end; buf += len; size -= len;
618 if (lpLVItem->mask & LVIF_PARAM)
619 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
621 if (len == -1) goto end; buf += len; size -= len;
622 if (lpLVItem->mask & LVIF_INDENT)
623 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
625 if (len == -1) goto end; buf += len; size -= len;
628 buf = text + strlen(text);
630 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
634 static char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
636 char* buf = debug_getbuf(), *text = buf;
637 int len, size = DEBUG_BUFFER_SIZE;
639 if (lpColumn == NULL) return "(null)";
640 len = snprintf(buf, size, "{");
641 if (len == -1) goto end; buf += len; size -= len;
642 if (lpColumn->mask & LVCF_SUBITEM)
643 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
645 if (len == -1) goto end; buf += len; size -= len;
646 if (lpColumn->mask & LVCF_FMT)
647 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
649 if (len == -1) goto end; buf += len; size -= len;
650 if (lpColumn->mask & LVCF_WIDTH)
651 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
653 if (len == -1) goto end; buf += len; size -= len;
654 if (lpColumn->mask & LVCF_TEXT)
655 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
657 if (len == -1) goto end; buf += len; size -= len;
658 if (lpColumn->mask & LVCF_IMAGE)
659 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
661 if (len == -1) goto end; buf += len; size -= len;
662 if (lpColumn->mask & LVCF_ORDER)
663 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
665 if (len == -1) goto end; buf += len; size -= len;
668 buf = text + strlen(text);
670 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
675 static char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
679 char* buf = debug_getbuf();
680 snprintf(buf, DEBUG_BUFFER_SIZE, "{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
681 debugpoint(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
683 } else return "(null)";
686 /******** Notification functions i************************************/
688 static LRESULT notify_hdr(LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
692 TRACE("(code=%d)\n", code);
694 pnmh->hwndFrom = infoPtr->hwndSelf;
695 pnmh->idFrom = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
697 result = SendMessageW(GetParent(infoPtr->hwndSelf), WM_NOTIFY,
698 (WPARAM)pnmh->idFrom, (LPARAM)pnmh);
700 TRACE(" <= %ld\n", result);
705 static inline LRESULT notify(LISTVIEW_INFO *infoPtr, INT code)
708 return notify_hdr(infoPtr, code, &nmh);
711 static inline void notify_itemactivate(LISTVIEW_INFO *infoPtr)
713 notify(infoPtr, LVN_ITEMACTIVATE);
716 static inline LRESULT notify_listview(LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
718 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
719 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
722 static LRESULT notify_click(LISTVIEW_INFO *infoPtr, INT code, LVHITTESTINFO *lvht)
727 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
728 ZeroMemory(&nmlv, sizeof(nmlv));
729 nmlv.iItem = lvht->iItem;
730 nmlv.iSubItem = lvht->iSubItem;
731 nmlv.ptAction = lvht->pt;
732 item.mask = LVIF_PARAM;
733 item.iItem = lvht->iItem;
735 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
736 return notify_listview(infoPtr, code, &nmlv);
739 static void notify_deleteitem(LISTVIEW_INFO *infoPtr, INT nItem)
744 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
746 item.mask = LVIF_PARAM;
749 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
750 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
753 static int get_ansi_notification(INT unicodeNotificationCode)
755 switch (unicodeNotificationCode)
757 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
758 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
759 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
760 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
761 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
762 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
764 ERR("unknown notification %x\n", unicodeNotificationCode);
769 With testing on Windows 2000 it looks like the notify format
770 has nothing to do with this message. It ALWAYS seems to be
773 infoPtr : listview struct
774 notificationCode : *Unicode* notification code
775 pdi : dispinfo structure (can be unicode or ansi)
776 isW : TRUE if dispinfo is Unicode
778 static BOOL notify_dispinfoT(LISTVIEW_INFO *infoPtr, INT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
780 BOOL bResult = FALSE;
781 BOOL convertToAnsi = FALSE;
782 INT cchTempBufMax = 0, savCchTextMax = 0;
783 LPWSTR pszTempBuf = NULL, savPszText = NULL;
785 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
790 if (notificationCode != LVN_GETDISPINFOW)
792 cchTempBufMax = WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText,
793 -1, NULL, 0, NULL, NULL);
797 cchTempBufMax = pdi->item.cchTextMax;
798 *pdi->item.pszText = 0; /* make sure we don't process garbage */
801 pszTempBuf = HeapAlloc(GetProcessHeap(), 0, sizeof(CHAR) *
803 if (!pszTempBuf) return FALSE;
805 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR)
806 pszTempBuf, cchTempBufMax, NULL, NULL);
808 savCchTextMax = pdi->item.cchTextMax;
809 savPszText = pdi->item.pszText;
810 pdi->item.pszText = pszTempBuf;
811 pdi->item.cchTextMax = cchTempBufMax;
814 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat !=
817 bResult = notify_hdr(infoPtr, get_ansi_notification(notificationCode),
822 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
823 savPszText, savCchTextMax);
824 pdi->item.pszText = savPszText; /* restores our buffer */
825 pdi->item.cchTextMax = savCchTextMax;
826 HeapFree(GetProcessHeap(), 0, pszTempBuf);
831 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *rcBounds)
833 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
834 lpnmlvcd->nmcd.hdc = hdc;
835 lpnmlvcd->nmcd.rc = *rcBounds;
836 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
837 lpnmlvcd->clrText = infoPtr->clrText;
840 static inline DWORD notify_customdraw (LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
842 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
843 return notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
846 /******** Item iterator functions **********************************/
848 static RANGES ranges_create(int count);
849 static void ranges_destroy(RANGES ranges);
850 static BOOL ranges_add(RANGES ranges, RANGE range);
851 static BOOL ranges_del(RANGES ranges, RANGE range);
852 static void ranges_dump(RANGES ranges);
854 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
856 RANGE range = { nItem, nItem + 1 };
858 return ranges_add(ranges, range);
861 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
863 RANGE range = { nItem, nItem + 1 };
865 return ranges_del(ranges, range);
869 * ITERATOR DOCUMENTATION
871 * The iterator functions allow for easy, and convenient iteration
872 * over items of iterest in the list. Typically, you create a
873 * iterator, use it, and destroy it, as such:
876 * iterator_xxxitems(&i, ...);
877 * while (iterator_{prev,next}(&i)
879 * //code which uses i.nItem
881 * iterator_destroy(&i);
883 * where xxx is either: framed, or visible.
884 * Note that it is important that the code destroys the iterator
885 * after it's done with it, as the creation of the iterator may
886 * allocate memory, which thus needs to be freed.
888 * You can iterate both forwards, and backwards through the list,
889 * by using iterator_next or iterator_prev respectively.
891 * Lower numbered items are draw on top of higher number items in
892 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
893 * items may overlap). So, to test items, you should use
895 * which lists the items top to bottom (in Z-order).
896 * For drawing items, you should use
898 * which lists the items bottom to top (in Z-order).
899 * If you keep iterating over the items after the end-of-items
900 * marker (-1) is returned, the iterator will start from the
901 * beginning. Typically, you don't need to test for -1,
902 * because iterator_{next,prev} will return TRUE if more items
903 * are to be iterated over, or FALSE otherwise.
905 * Note: the iterator is defined to be bidirectional. That is,
906 * any number of prev followed by any number of next, or
907 * five versa, should leave the iterator at the same item:
908 * prev * n, next * n = next * n, prev * n
910 * The iterator has a notion of a out-of-order, special item,
911 * which sits at the start of the list. This is used in
912 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
913 * which needs to be first, as it may overlap other items.
915 * The code is a bit messy because we have:
916 * - a special item to deal with
917 * - simple range, or composite range
919 * If find bugs, or want to add features, please make sure you
920 * always check/modify *both* iterator_prev, and iterator_next.
924 * This function iterates through the items in increasing order,
925 * but prefixed by the special item, then -1. That is:
926 * special, 1, 2, 3, ..., n, -1.
927 * Each item is listed only once.
929 static inline BOOL iterator_next(ITERATOR* i)
933 i->nItem = i->nSpecial;
934 if (i->nItem != -1) return TRUE;
936 if (i->nItem == i->nSpecial)
938 if (i->ranges) i->index = 0;
944 if (i->nItem == i->nSpecial) i->nItem++;
945 if (i->nItem < i->range.upper) return TRUE;
950 if (i->index < i->ranges->hdpa->nItemCount)
951 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
954 else if (i->nItem >= i->range.upper) goto end;
956 i->nItem = i->range.lower;
957 if (i->nItem >= 0) goto testitem;
964 * This function iterates through the items in decreasing order,
965 * followed by the special item, then -1. That is:
966 * n, n-1, ..., 3, 2, 1, special, -1.
967 * Each item is listed only once.
969 static inline BOOL iterator_prev(ITERATOR* i)
976 if (i->ranges) i->index = i->ranges->hdpa->nItemCount;
979 if (i->nItem == i->nSpecial)
987 if (i->nItem == i->nSpecial) i->nItem--;
988 if (i->nItem >= i->range.lower) return TRUE;
994 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
997 else if (!start && i->nItem < i->range.lower) goto end;
999 i->nItem = i->range.upper;
1000 if (i->nItem > 0) goto testitem;
1002 return (i->nItem = i->nSpecial) != -1;
1005 static RANGE iterator_range(ITERATOR* i)
1009 if (!i->ranges) return i->range;
1011 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1012 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->ranges->hdpa->nItemCount - 1)).upper;
1017 * Releases resources associated with this ierator.
1019 static inline void iterator_destroy(ITERATOR* i)
1021 ranges_destroy(i->ranges);
1025 * Create an empty iterator.
1027 static inline BOOL iterator_empty(ITERATOR* i)
1029 ZeroMemory(i, sizeof(*i));
1030 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1035 * Create an iterator over a range.
1037 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1045 * Create an iterator over a bunch of ranges.
1046 * Please note that the iterator will take ownership of the ranges,
1047 * and will free them upon destruction.
1049 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1057 * Creates an iterator over the items which intersect lprc.
1059 static BOOL iterator_frameditems(ITERATOR* i, LISTVIEW_INFO* infoPtr, const RECT *lprc)
1061 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1062 RECT frame = *lprc, rcItem, rcTemp;
1065 /* in case we fail, we want to return an empty iterator */
1066 if (!iterator_empty(i)) return FALSE;
1068 LISTVIEW_GetOrigin(infoPtr, &Origin);
1070 TRACE("(lprc=%s)\n", debugrect(lprc));
1071 OffsetRect(&frame, -Origin.x, -Origin.y);
1073 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1077 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1079 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1080 if (IntersectRect(&rcTemp, &rcItem, lprc))
1081 i->nSpecial = infoPtr->nFocusedItem;
1083 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1084 /* to do better here, we need to have PosX, and PosY sorted */
1085 TRACE("building icon ranges:\n");
1086 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1088 rcItem.left = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1089 rcItem.top = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1090 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1091 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1092 if (IntersectRect(&rcTemp, &rcItem, &frame))
1093 ranges_additem(i->ranges, nItem);
1097 else if (uView == LVS_REPORT)
1101 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1102 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1104 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1105 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1106 if (range.upper <= range.lower) return TRUE;
1107 if (!iterator_rangeitems(i, range)) return FALSE;
1108 TRACE(" report=%s\n", debugrange(&i->range));
1112 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1113 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1114 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1115 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1116 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1117 INT lower = nFirstCol * nPerCol + nFirstRow;
1121 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1122 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1124 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1126 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1127 TRACE("building list ranges:\n");
1128 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1130 item_range.lower = nCol * nPerCol + nFirstRow;
1131 if(item_range.lower >= infoPtr->nItemCount) break;
1132 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1133 TRACE(" list=%s\n", debugrange(&item_range));
1134 ranges_add(i->ranges, item_range);
1142 * Creates an iterator over the items which intersect the visible region of hdc.
1144 static BOOL iterator_visibleitems(ITERATOR *i, LISTVIEW_INFO *infoPtr, HDC hdc)
1146 POINT Origin, Position;
1147 RECT rcItem, rcClip;
1150 rgntype = GetClipBox(hdc, &rcClip);
1151 if (rgntype == NULLREGION) return iterator_empty(i);
1152 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1153 if (rgntype == SIMPLEREGION) return TRUE;
1155 /* first deal with the special item */
1156 if (i->nSpecial != -1)
1158 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1159 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1162 /* if we can't deal with the region, we'll just go with the simple range */
1163 LISTVIEW_GetOrigin(infoPtr, &Origin);
1164 TRACE("building visible range:\n");
1165 if (!i->ranges && i->range.lower < i->range.upper)
1167 if (!(i->ranges = ranges_create(50))) return TRUE;
1168 if (!ranges_add(i->ranges, i->range))
1170 ranges_destroy(i->ranges);
1176 /* now delete the invisible items from the list */
1177 while(iterator_next(i))
1179 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1180 rcItem.left = Position.x + Origin.x;
1181 rcItem.top = Position.y + Origin.y;
1182 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1183 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1184 if (!RectVisible(hdc, &rcItem))
1185 ranges_delitem(i->ranges, i->nItem);
1187 /* the iterator should restart on the next iterator_next */
1193 /******** Misc helper functions ************************************/
1195 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1196 WPARAM wParam, LPARAM lParam, BOOL isW)
1198 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1199 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1202 static inline BOOL is_autoarrange(LISTVIEW_INFO *infoPtr)
1204 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1206 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1207 (uView == LVS_ICON || uView == LVS_SMALLICON);
1210 /******** Internal API functions ************************************/
1212 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(LISTVIEW_INFO *infoPtr, INT nSubItem)
1214 static COLUMN_INFO mainItem;
1216 if (nSubItem == 0 && infoPtr->hdpaColumns->nItemCount == 0) return &mainItem;
1217 assert (nSubItem >= 0 && nSubItem < infoPtr->hdpaColumns->nItemCount);
1218 return (COLUMN_INFO *)DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1221 static inline void LISTVIEW_GetHeaderRect(LISTVIEW_INFO *infoPtr, INT nSubItem, RECT *lprc)
1223 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1226 static inline BOOL LISTVIEW_GetItemW(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1228 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1231 /* Listview invlaidation functions: use _only_ these function to invalidate */
1233 static inline BOOL is_redrawing(LISTVIEW_INFO *infoPtr)
1235 return infoPtr->bRedraw && !infoPtr->bFirstPaint;
1238 static inline void LISTVIEW_InvalidateRect(LISTVIEW_INFO *infoPtr, const RECT* rect)
1240 if(!is_redrawing(infoPtr)) return;
1241 TRACE(" invalidating rect=%s\n", debugrect(rect));
1242 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1245 static inline void LISTVIEW_InvalidateItem(LISTVIEW_INFO *infoPtr, INT nItem)
1249 if(!is_redrawing(infoPtr)) return;
1250 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1251 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1254 static inline void LISTVIEW_InvalidateSubItem(LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1256 POINT Origin, Position;
1259 if(!is_redrawing(infoPtr)) return;
1260 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1261 LISTVIEW_GetOrigin(infoPtr, &Origin);
1262 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1263 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1265 rcBox.bottom = infoPtr->nItemHeight;
1266 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1267 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1270 static inline void LISTVIEW_InvalidateList(LISTVIEW_INFO *infoPtr)
1272 LISTVIEW_InvalidateRect(infoPtr, NULL);
1275 static inline void LISTVIEW_InvalidateColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
1279 if(!is_redrawing(infoPtr)) return;
1280 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1281 rcCol.top = infoPtr->rcList.top;
1282 rcCol.bottom = infoPtr->rcList.bottom;
1283 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1288 * Retrieves the number of items that can fit vertically in the client area.
1291 * [I] infoPtr : valid pointer to the listview structure
1294 * Number of items per row.
1296 static inline INT LISTVIEW_GetCountPerRow(LISTVIEW_INFO *infoPtr)
1298 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1300 return max(nListWidth/infoPtr->nItemWidth, 1);
1305 * Retrieves the number of items that can fit horizontally in the client
1309 * [I] infoPtr : valid pointer to the listview structure
1312 * Number of items per column.
1314 static inline INT LISTVIEW_GetCountPerColumn(LISTVIEW_INFO *infoPtr)
1316 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1318 return max(nListHeight / infoPtr->nItemHeight, 1);
1322 /*************************************************************************
1323 * LISTVIEW_ProcessLetterKeys
1325 * Processes keyboard messages generated by pressing the letter keys
1327 * What this does is perform a case insensitive search from the
1328 * current position with the following quirks:
1329 * - If two chars or more are pressed in quick succession we search
1330 * for the corresponding string (e.g. 'abc').
1331 * - If there is a delay we wipe away the current search string and
1332 * restart with just that char.
1333 * - If the user keeps pressing the same character, whether slowly or
1334 * fast, so that the search string is entirely composed of this
1335 * character ('aaaaa' for instance), then we search for first item
1336 * that starting with that character.
1337 * - If the user types the above character in quick succession, then
1338 * we must also search for the corresponding string ('aaaaa'), and
1339 * go to that string if there is a match.
1342 * [I] hwnd : handle to the window
1343 * [I] charCode : the character code, the actual character
1344 * [I] keyData : key data
1352 * - The current implementation has a list of characters it will
1353 * accept and it ignores averything else. In particular it will
1354 * ignore accentuated characters which seems to match what
1355 * Windows does. But I'm not sure it makes sense to follow
1357 * - We don't sound a beep when the search fails.
1361 * TREEVIEW_ProcessLetterKeys
1363 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1368 WCHAR buffer[MAX_PATH];
1369 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1371 /* simple parameter checking */
1372 if (!charCode || !keyData) return 0;
1374 /* only allow the valid WM_CHARs through */
1375 if (!isalnum(charCode) &&
1376 charCode != '.' && charCode != '`' && charCode != '!' &&
1377 charCode != '@' && charCode != '#' && charCode != '$' &&
1378 charCode != '%' && charCode != '^' && charCode != '&' &&
1379 charCode != '*' && charCode != '(' && charCode != ')' &&
1380 charCode != '-' && charCode != '_' && charCode != '+' &&
1381 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1382 charCode != '}' && charCode != '[' && charCode != '{' &&
1383 charCode != '/' && charCode != '?' && charCode != '>' &&
1384 charCode != '<' && charCode != ',' && charCode != '~')
1387 /* if there's one item or less, there is no where to go */
1388 if (infoPtr->nItemCount <= 1) return 0;
1390 /* update the search parameters */
1391 infoPtr->lastKeyPressTimestamp = GetTickCount();
1392 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1393 if (infoPtr->nSearchParamLength < MAX_PATH)
1394 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1395 if (infoPtr->charCode != charCode)
1396 infoPtr->charCode = charCode = 0;
1398 infoPtr->charCode=charCode;
1399 infoPtr->szSearchParam[0]=charCode;
1400 infoPtr->nSearchParamLength=1;
1401 /* Redundant with the 1 char string */
1405 /* and search from the current position */
1407 if (infoPtr->nFocusedItem >= 0) {
1408 endidx=infoPtr->nFocusedItem;
1410 /* if looking for single character match,
1411 * then we must always move forward
1413 if (infoPtr->nSearchParamLength == 1)
1416 endidx=infoPtr->nItemCount;
1420 if (idx == infoPtr->nItemCount) {
1421 if (endidx == infoPtr->nItemCount || endidx == 0)
1427 item.mask = LVIF_TEXT;
1430 item.pszText = buffer;
1431 item.cchTextMax = MAX_PATH;
1432 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1434 /* check for a match */
1435 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1438 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1439 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1440 /* This would work but we must keep looking for a longer match */
1444 } while (idx != endidx);
1447 LISTVIEW_KeySelection(infoPtr, nItem);
1452 /*************************************************************************
1453 * LISTVIEW_UpdateHeaderSize [Internal]
1455 * Function to resize the header control
1458 * [I] hwnd : handle to a window
1459 * [I] nNewScrollPos : scroll pos to set
1464 static void LISTVIEW_UpdateHeaderSize(LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1469 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1471 GetWindowRect(infoPtr->hwndHeader, &winRect);
1472 point[0].x = winRect.left;
1473 point[0].y = winRect.top;
1474 point[1].x = winRect.right;
1475 point[1].y = winRect.bottom;
1477 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1478 point[0].x = -nNewScrollPos;
1479 point[1].x += nNewScrollPos;
1481 SetWindowPos(infoPtr->hwndHeader,0,
1482 point[0].x,point[0].y,point[1].x,point[1].y,
1483 SWP_NOZORDER | SWP_NOACTIVATE);
1488 * Update the scrollbars. This functions should be called whenever
1489 * the content, size or view changes.
1492 * [I] infoPtr : valid pointer to the listview structure
1497 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *infoPtr)
1499 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1500 SCROLLINFO horzInfo, vertInfo;
1502 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1504 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1505 horzInfo.cbSize = sizeof(SCROLLINFO);
1506 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1508 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1509 if (uView == LVS_LIST)
1511 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1512 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1513 horzInfo.nPage /= infoPtr->nItemWidth;
1515 else if (uView == LVS_REPORT)
1517 horzInfo.nMax = infoPtr->nItemWidth;
1519 else /* LVS_ICON, or LVS_SMALLICON */
1523 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1526 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1527 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1528 SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1529 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1531 /* Setting the horizontal scroll can change the listview size
1532 * (and potentially everything else) so we need to recompute
1533 * everything again for the vertical scroll
1536 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1537 vertInfo.cbSize = sizeof(SCROLLINFO);
1538 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1540 if (uView == LVS_REPORT)
1542 vertInfo.nMax = infoPtr->nItemCount;
1543 vertInfo.nPage /= infoPtr->nItemHeight;
1545 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1549 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1552 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1553 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1554 SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1555 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1557 /* Update the Header Control */
1558 if (uView == LVS_REPORT)
1560 horzInfo.fMask = SIF_POS;
1561 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1562 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1569 * Shows/hides the focus rectangle.
1572 * [I] infoPtr : valid pointer to the listview structure
1573 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1578 static void LISTVIEW_ShowFocusRect(LISTVIEW_INFO *infoPtr, BOOL fShow)
1580 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1583 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1585 if (infoPtr->nFocusedItem < 0) return;
1587 /* we need some gymnastics in ICON mode to handle large items */
1588 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1592 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1593 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1595 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1600 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1602 /* for some reason, owner draw should work only in report mode */
1603 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1608 item.iItem = infoPtr->nFocusedItem;
1610 item.mask = LVIF_PARAM;
1611 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1613 ZeroMemory(&dis, sizeof(dis));
1614 dis.CtlType = ODT_LISTVIEW;
1615 dis.CtlID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
1616 dis.itemID = item.iItem;
1617 dis.itemAction = ODA_FOCUS;
1618 if (fShow) dis.itemState |= ODS_FOCUS;
1619 dis.hwndItem = infoPtr->hwndSelf;
1621 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1622 dis.itemData = item.lParam;
1624 SendMessageW(GetParent(infoPtr->hwndSelf), WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1628 DrawFocusRect(hdc, &infoPtr->rcFocus);
1631 ReleaseDC(infoPtr->hwndSelf, hdc);
1635 * Invalidates all visible selected items.
1637 static void LISTVIEW_InvalidateSelectedItems(LISTVIEW_INFO *infoPtr)
1641 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1642 while(iterator_next(&i))
1644 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1645 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1647 iterator_destroy(&i);
1652 * DESCRIPTION: [INTERNAL]
1653 * Computes an item's (left,top) corner, relative to rcView.
1654 * That is, the position has NOT been made relative to the Origin.
1655 * This is deliberate, to avoid computing the Origin over, and
1656 * over again, when this function is call in a loop. Instead,
1657 * one ca factor the computation of the Origin before the loop,
1658 * and offset the value retured by this function, on every iteration.
1661 * [I] infoPtr : valid pointer to the listview structure
1662 * [I] nItem : item number
1663 * [O] lpptOrig : item top, left corner
1668 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1670 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1672 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1674 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1676 lpptPosition->x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1677 lpptPosition->y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1679 else if (uView == LVS_LIST)
1681 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1682 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1683 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1685 else /* LVS_REPORT */
1687 lpptPosition->x = REPORT_MARGINX;
1688 lpptPosition->y = nItem * infoPtr->nItemHeight;
1693 * DESCRIPTION: [INTERNAL]
1694 * Compute the rectangles of an item. This is to localize all
1695 * the computations in one place. If you are not interested in some
1696 * of these values, simply pass in a NULL -- the fucntion is smart
1697 * enough to compute only what's necessary. The function computes
1698 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1699 * one, the BOX rectangle. This rectangle is very cheap to compute,
1700 * and is guaranteed to contain all the other rectangles. Computing
1701 * the ICON rect is also cheap, but all the others are potentaily
1702 * expensive. This gives an easy and effective optimization when
1703 * searching (like point inclusion, or rectangle intersection):
1704 * first test against the BOX, and if TRUE, test agains the desired
1706 * If the function does not have all the necessary information
1707 * to computed the requested rectangles, will crash with a
1708 * failed assertion. This is done so we catch all programming
1709 * errors, given that the function is called only from our code.
1711 * We have the following 'special' meanings for a few fields:
1712 * * If LVIS_FOCUSED is set, we assume the item has the focus
1713 * This is important in ICON mode, where it might get a larger
1714 * then usual rectange
1716 * Please note that subitem support works only in REPORT mode.
1719 * [I] infoPtr : valid pointer to the listview structure
1720 * [I] lpLVItem : item to compute the measures for
1721 * [O] lprcBox : ptr to Box rectangle
1722 * The internal LVIR_BOX rectangle
1723 * [0] lprcState : ptr to State icon rectangle
1724 * The internal LVIR_STATE rectangle
1725 * [O] lprcIcon : ptr to Icon rectangle
1726 * Same as LVM_GETITEMRECT with LVIR_ICON
1727 * [O] lprcLabel : ptr to Label rectangle
1728 * Same as LVM_GETITEMRECT with LVIR_LABEL
1733 static void LISTVIEW_GetItemMetrics(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1734 LPRECT lprcBox, LPRECT lprcState,
1735 LPRECT lprcIcon, LPRECT lprcLabel)
1737 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1738 BOOL doState = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1739 RECT Box, State, Icon, Label;
1740 COLUMN_INFO *lpColumnInfo = NULL;
1742 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1744 /* Be smart and try to figure out the minimum we have to do */
1745 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1746 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1748 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1749 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1751 if (lprcLabel) doLabel = TRUE;
1752 if (doLabel || lprcIcon) doIcon = TRUE;
1753 if (doIcon || lprcState) doState = TRUE;
1755 /************************************************************/
1756 /* compute the box rectangle (it should be cheap to do) */
1757 /************************************************************/
1758 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1759 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1761 if (lpLVItem->iSubItem)
1763 Box = lpColumnInfo->rcHeader;
1768 Box.right = infoPtr->nItemWidth;
1771 Box.bottom = infoPtr->nItemHeight;
1773 /************************************************************/
1774 /* compute STATEICON bounding box */
1775 /************************************************************/
1778 if (uView == LVS_ICON)
1780 State.left = Box.left - infoPtr->iconStateSize.cx - 2;
1781 if (infoPtr->himlNormal)
1782 State.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1783 State.top = Box.top + infoPtr->iconSize.cy - infoPtr->iconStateSize.cy + 4;
1787 /* we need the ident in report mode, if we don't have it, we fail */
1788 State.left = Box.left;
1789 if (uView == LVS_REPORT)
1791 State.left += REPORT_MARGINX;
1792 if (lpLVItem->iSubItem == 0)
1794 assert(lpLVItem->mask & LVIF_INDENT);
1795 State.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
1798 State.top = Box.top;
1800 State.right = State.left;
1801 State.bottom = State.top;
1802 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1804 State.right += infoPtr->iconStateSize.cx;
1805 State.bottom += infoPtr->iconStateSize.cy;
1807 if (lprcState) *lprcState = State;
1808 TRACE(" - state=%s\n", debugrect(&State));
1811 /************************************************************/
1812 /* compute ICON bounding box (ala LVM_GETITEMRECT) */
1813 /************************************************************/
1816 if (uView == LVS_ICON)
1818 Icon.left = Box.left;
1819 if (infoPtr->himlNormal)
1820 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1821 Icon.top = Box.top + ICON_TOP_PADDING;
1822 Icon.right = Icon.left;
1823 Icon.bottom = Icon.top;
1824 if (infoPtr->himlNormal)
1826 Icon.right += infoPtr->iconSize.cx;
1827 Icon.bottom += infoPtr->iconSize.cy;
1830 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1832 Icon.left = State.right;
1833 if (!IsRectEmpty(&State)) Icon.left += IMAGE_PADDING;
1835 Icon.right = Icon.left;
1836 if (infoPtr->himlSmall && (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE)))
1837 Icon.right += infoPtr->iconSize.cx;
1838 Icon.bottom = Icon.top + infoPtr->nItemHeight;
1840 if(lprcIcon) *lprcIcon = Icon;
1841 TRACE(" - icon=%s\n", debugrect(&Icon));
1844 /************************************************************/
1845 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
1846 /************************************************************/
1849 SIZE labelSize = { 0, 0 };
1851 /* calculate how far to the right can the label strech */
1852 Label.right = Box.right;
1853 if (uView == LVS_REPORT)
1855 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
1856 Label.right -= REPORT_MARGINX;
1859 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
1861 labelSize.cx = infoPtr->nItemWidth;
1862 labelSize.cy = infoPtr->nItemHeight;
1866 /* we need the text in non owner draw mode */
1867 assert(lpLVItem->mask & LVIF_TEXT);
1868 if (is_textT(lpLVItem->pszText, TRUE))
1870 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1871 HDC hdc = GetDC(infoPtr->hwndSelf);
1872 HFONT hOldFont = SelectObject(hdc, hFont);
1876 /* compute rough rectangle where the label will go */
1877 SetRectEmpty(&rcText);
1878 rcText.right = infoPtr->nItemWidth - TRAILING_PADDING;
1879 rcText.bottom = infoPtr->nItemHeight;
1880 if (uView == LVS_ICON)
1881 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
1883 /* now figure out the flags */
1884 if (uView == LVS_ICON)
1885 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
1887 uFormat = LV_SL_DT_FLAGS;
1889 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
1891 labelSize.cx = min(rcText.right - rcText.left + TRAILING_PADDING, infoPtr->nItemWidth);
1892 labelSize.cy = rcText.bottom - rcText.top;
1894 SelectObject(hdc, hOldFont);
1895 ReleaseDC(infoPtr->hwndSelf, hdc);
1899 if (uView == LVS_ICON)
1901 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
1902 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
1903 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
1904 Label.right = Label.left + labelSize.cx;
1905 Label.bottom = Label.top + infoPtr->nItemHeight;
1906 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
1908 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
1909 labelSize.cy /= infoPtr->ntmHeight;
1910 labelSize.cy = max(labelSize.cy, 1);
1911 labelSize.cy *= infoPtr->ntmHeight;
1913 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
1915 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1917 Label.left = Icon.right;
1918 if (!IsRectEmpty(&Icon) || !IsRectEmpty(&State)) Label.left += IMAGE_PADDING;
1919 Label.top = Box.top;
1920 Label.right = min(Label.left + labelSize.cx, Label.right);
1921 Label.bottom = Label.top + infoPtr->nItemHeight;
1924 if (lprcLabel) *lprcLabel = Label;
1925 TRACE(" - label=%s\n", debugrect(&Label));
1928 /* Fix the Box if necessary */
1931 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
1932 else *lprcBox = Box;
1934 TRACE(" - box=%s\n", debugrect(&Box));
1938 * DESCRIPTION: [INTERNAL]
1941 * [I] infoPtr : valid pointer to the listview structure
1942 * [I] nItem : item number
1943 * [O] lprcBox : ptr to Box rectangle
1948 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
1950 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1951 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
1952 POINT Position, Origin;
1955 LISTVIEW_GetOrigin(infoPtr, &Origin);
1956 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1958 /* Be smart and try to figure out the minimum we have to do */
1960 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
1961 lvItem.mask |= LVIF_TEXT;
1962 lvItem.iItem = nItem;
1963 lvItem.iSubItem = 0;
1964 lvItem.pszText = szDispText;
1965 lvItem.cchTextMax = DISP_TEXT_SIZE;
1966 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
1967 if (uView == LVS_ICON)
1969 lvItem.mask |= LVIF_STATE;
1970 lvItem.stateMask = LVIS_FOCUSED;
1971 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
1973 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0);
1975 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
1981 * Returns the current icon position, and advances it along the top.
1982 * The returned position is not offset by Origin.
1985 * [I] infoPtr : valid pointer to the listview structure
1986 * [O] lpPos : will get the current icon position
1991 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
1993 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1995 *lpPos = infoPtr->currIconPos;
1997 infoPtr->currIconPos.x += infoPtr->nItemWidth;
1998 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2000 infoPtr->currIconPos.x = 0;
2001 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2007 * Returns the current icon position, and advances it down the left edge.
2008 * The returned position is not offset by Origin.
2011 * [I] infoPtr : valid pointer to the listview structure
2012 * [O] lpPos : will get the current icon position
2017 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2019 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2021 *lpPos = infoPtr->currIconPos;
2023 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2024 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2026 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2027 infoPtr->currIconPos.y = 0;
2033 * Moves an icon to the specified position.
2034 * It takes care of invalidating the item, etc.
2037 * [I] infoPtr : valid pointer to the listview structure
2038 * [I] nItem : the item to move
2039 * [I] lpPos : the new icon position
2040 * [I] isNew : flags the item as being new
2046 static BOOL LISTVIEW_MoveIconTo(LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2052 old.x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2053 old.y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2055 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2056 LISTVIEW_InvalidateItem(infoPtr, nItem);
2059 /* Allocating a POINTER for every item is too resource intensive,
2060 * so we'll keep the (x,y) in different arrays */
2061 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)lppt->x)) return FALSE;
2062 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)lppt->y)) return FALSE;
2064 LISTVIEW_InvalidateItem(infoPtr, nItem);
2071 * Arranges listview items in icon display mode.
2074 * [I] infoPtr : valid pointer to the listview structure
2075 * [I] nAlignCode : alignment code
2081 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2083 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2084 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2088 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2090 TRACE("nAlignCode=%d\n", nAlignCode);
2092 if (nAlignCode == LVA_DEFAULT)
2094 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2095 else nAlignCode = LVA_ALIGNTOP;
2100 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2101 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2102 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2103 default: return FALSE;
2106 infoPtr->bAutoarrange = TRUE;
2107 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2108 for (i = 0; i < infoPtr->nItemCount; i++)
2110 next_pos(infoPtr, &pos);
2111 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2119 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2122 * [I] infoPtr : valid pointer to the listview structure
2123 * [O] lprcView : bounding rectangle
2129 static void LISTVIEW_GetAreaRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2133 SetRectEmpty(lprcView);
2135 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2139 for (i = 0; i < infoPtr->nItemCount; i++)
2141 x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, i);
2142 y = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, i);
2143 lprcView->right = max(lprcView->right, x);
2144 lprcView->bottom = max(lprcView->bottom, y);
2146 if (infoPtr->nItemCount > 0)
2148 lprcView->right += infoPtr->nItemWidth;
2149 lprcView->bottom += infoPtr->nItemHeight;
2154 y = LISTVIEW_GetCountPerColumn(infoPtr);
2155 x = infoPtr->nItemCount / y;
2156 if (infoPtr->nItemCount % y) x++;
2157 lprcView->right = x * infoPtr->nItemWidth;
2158 lprcView->bottom = y * infoPtr->nItemHeight;
2162 lprcView->right = infoPtr->nItemWidth;
2163 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2170 * Retrieves the bounding rectangle of all the items.
2173 * [I] infoPtr : valid pointer to the listview structure
2174 * [O] lprcView : bounding rectangle
2180 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2184 TRACE("(lprcView=%p)\n", lprcView);
2186 if (!lprcView) return FALSE;
2188 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2189 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2190 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2192 TRACE("lprcView=%s\n", debugrect(lprcView));
2199 * Retrieves the subitem pointer associated with the subitem index.
2202 * [I] hdpaSubItems : DPA handle for a specific item
2203 * [I] nSubItem : index of subitem
2206 * SUCCESS : subitem pointer
2209 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2211 SUBITEM_INFO *lpSubItem;
2214 /* we should binary search here if need be */
2215 for (i = 1; i < hdpaSubItems->nItemCount; i++)
2217 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
2218 if (lpSubItem->iSubItem == nSubItem)
2228 * Caclulates the desired item width.
2231 * [I] infoPtr : valid pointer to the listview structure
2234 * The desired item width.
2236 static INT LISTVIEW_CalculateItemWidth(LISTVIEW_INFO *infoPtr)
2238 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2241 TRACE("uView=%d\n", uView);
2243 if (uView == LVS_ICON)
2244 nItemWidth = infoPtr->iconSpacing.cx;
2245 else if (uView == LVS_REPORT)
2249 if (infoPtr->hdpaColumns->nItemCount > 0)
2251 LISTVIEW_GetHeaderRect(infoPtr, infoPtr->hdpaColumns->nItemCount - 1, &rcHeader);
2252 nItemWidth = rcHeader.right;
2255 else /* LVS_SMALLICON, or LVS_LIST */
2259 for (i = 0; i < infoPtr->nItemCount; i++)
2260 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2262 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx + IMAGE_PADDING;
2263 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx + IMAGE_PADDING;
2265 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2268 return max(nItemWidth, 1);
2273 * Caclulates the desired item height.
2276 * [I] infoPtr : valid pointer to the listview structure
2279 * The desired item height.
2281 static INT LISTVIEW_CalculateItemHeight(LISTVIEW_INFO *infoPtr)
2283 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2286 TRACE("uView=%d\n", uView);
2288 if (uView == LVS_ICON)
2289 nItemHeight = infoPtr->iconSpacing.cy;
2292 nItemHeight = infoPtr->ntmHeight;
2293 if (infoPtr->himlState)
2294 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2295 if (infoPtr->himlSmall)
2296 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2297 if (infoPtr->himlState || infoPtr->himlSmall)
2298 nItemHeight += HEIGHT_PADDING;
2301 return max(nItemHeight, 1);
2306 * Updates the width, and height of an item.
2309 * [I] infoPtr : valid pointer to the listview structure
2314 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2316 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2317 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2323 * Retrieves and saves important text metrics info for the current
2327 * [I] infoPtr : valid pointer to the listview structure
2330 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2332 HDC hdc = GetDC(infoPtr->hwndSelf);
2333 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2334 HFONT hOldFont = SelectObject(hdc, hFont);
2337 if (GetTextMetricsW(hdc, &tm))
2339 infoPtr->ntmHeight = tm.tmHeight;
2340 infoPtr->ntmAveCharWidth = tm.tmAveCharWidth;
2342 SelectObject(hdc, hOldFont);
2343 ReleaseDC(infoPtr->hwndSelf, hdc);
2345 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2350 * A compare function for ranges
2353 * [I] range1 : pointer to range 1;
2354 * [I] range2 : pointer to range 2;
2358 * > 0 : if range 1 > range 2
2359 * < 0 : if range 2 > range 1
2360 * = 0 : if range intersects range 2
2362 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2366 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2368 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2373 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2379 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2381 #define ranges_check(ranges, desc) do { } while(0)
2384 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2389 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2391 assert (ranges->hdpa->nItemCount >= 0);
2392 ranges_dump(ranges);
2393 prev = (RANGE *)DPA_GetPtr(ranges->hdpa, 0);
2394 if (ranges->hdpa->nItemCount > 0)
2395 assert (prev->lower >= 0 && prev->lower < prev->upper);
2396 for (i = 1; i < ranges->hdpa->nItemCount; i++)
2398 curr = (RANGE *)DPA_GetPtr(ranges->hdpa, i);
2399 assert (prev->upper <= curr->lower);
2400 assert (curr->lower < curr->upper);
2403 TRACE("--- Done checking---\n");
2406 static RANGES ranges_create(int count)
2408 RANGES ranges = (RANGES)COMCTL32_Alloc(sizeof(struct tagRANGES));
2409 if (!ranges) return NULL;
2410 ranges->hdpa = DPA_Create(count);
2411 if (ranges->hdpa) return ranges;
2412 COMCTL32_Free(ranges);
2416 static void ranges_clear(RANGES ranges)
2420 for(i = 0; i < ranges->hdpa->nItemCount; i++)
2421 COMCTL32_Free(DPA_GetPtr(ranges->hdpa, i));
2422 DPA_DeleteAllPtrs(ranges->hdpa);
2426 static void ranges_destroy(RANGES ranges)
2428 if (!ranges) return;
2429 ranges_clear(ranges);
2430 DPA_Destroy(ranges->hdpa);
2431 COMCTL32_Free(ranges);
2434 static RANGES ranges_clone(RANGES ranges)
2439 if (!(clone = ranges_create(ranges->hdpa->nItemCount))) goto fail;
2441 for (i = 0; i < ranges->hdpa->nItemCount; i++)
2443 RANGE *newrng = (RANGE *)COMCTL32_Alloc(sizeof(RANGE));
2444 if (!newrng) goto fail;
2445 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2446 DPA_SetPtr(clone->hdpa, i, newrng);
2451 TRACE ("clone failed\n");
2452 ranges_destroy(clone);
2456 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2460 for (i = 0; i < sub->hdpa->nItemCount; i++)
2461 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2466 static void ranges_dump(RANGES ranges)
2470 for (i = 0; i < ranges->hdpa->nItemCount; i++)
2471 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2474 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2476 RANGE srchrng = { nItem, nItem + 1 };
2478 TRACE("(nItem=%d)\n", nItem);
2479 ranges_check(ranges, "before contain");
2480 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2483 static INT ranges_itemcount(RANGES ranges)
2487 for (i = 0; i < ranges->hdpa->nItemCount; i++)
2489 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2490 count += sel->upper - sel->lower;
2496 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2498 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2501 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2502 if (index == -1) return TRUE;
2504 for (; index < ranges->hdpa->nItemCount; index++)
2506 chkrng = DPA_GetPtr(ranges->hdpa, index);
2507 if (chkrng->lower >= nItem)
2508 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2509 if (chkrng->upper > nItem)
2510 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2515 static BOOL ranges_add(RANGES ranges, RANGE range)
2520 TRACE("(%s)\n", debugrange(&range));
2521 ranges_check(ranges, "before add");
2523 /* try find overlapping regions first */
2524 srchrgn.lower = range.lower - 1;
2525 srchrgn.upper = range.upper + 1;
2526 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2532 TRACE("Adding new range\n");
2534 /* create the brand new range to insert */
2535 newrgn = (RANGE *)COMCTL32_Alloc(sizeof(RANGE));
2536 if(!newrgn) goto fail;
2539 /* figure out where to insert it */
2540 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2541 TRACE("index=%d\n", index);
2542 if (index == -1) index = 0;
2544 /* and get it over with */
2545 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2547 COMCTL32_Free(newrgn);
2553 RANGE *chkrgn, *mrgrgn;
2554 INT fromindex, mergeindex;
2556 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2557 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2559 chkrgn->lower = min(range.lower, chkrgn->lower);
2560 chkrgn->upper = max(range.upper, chkrgn->upper);
2562 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2564 /* merge now common anges */
2566 srchrgn.lower = chkrgn->lower - 1;
2567 srchrgn.upper = chkrgn->upper + 1;
2571 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2572 if (mergeindex == -1) break;
2573 if (mergeindex == index)
2575 fromindex = index + 1;
2579 TRACE("Merge with index %i\n", mergeindex);
2581 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2582 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2583 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2584 COMCTL32_Free(mrgrgn);
2585 DPA_DeletePtr(ranges->hdpa, mergeindex);
2586 if (mergeindex < index) index --;
2590 ranges_check(ranges, "after add");
2594 ranges_check(ranges, "failed add");
2598 static BOOL ranges_del(RANGES ranges, RANGE range)
2603 TRACE("(%s)\n", debugrange(&range));
2604 ranges_check(ranges, "before del");
2606 /* we don't use DPAS_SORTED here, since we need *
2607 * to find the first overlapping range */
2608 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2611 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2613 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2615 /* case 1: Same range */
2616 if ( (chkrgn->upper == range.upper) &&
2617 (chkrgn->lower == range.lower) )
2619 DPA_DeletePtr(ranges->hdpa, index);
2622 /* case 2: engulf */
2623 else if ( (chkrgn->upper <= range.upper) &&
2624 (chkrgn->lower >= range.lower) )
2626 DPA_DeletePtr(ranges->hdpa, index);
2628 /* case 3: overlap upper */
2629 else if ( (chkrgn->upper <= range.upper) &&
2630 (chkrgn->lower < range.lower) )
2632 chkrgn->upper = range.lower;
2634 /* case 4: overlap lower */
2635 else if ( (chkrgn->upper > range.upper) &&
2636 (chkrgn->lower >= range.lower) )
2638 chkrgn->lower = range.upper;
2641 /* case 5: fully internal */
2644 RANGE tmprgn = *chkrgn, *newrgn;
2646 if (!(newrgn = (RANGE *)COMCTL32_Alloc(sizeof(RANGE)))) goto fail;
2647 newrgn->lower = chkrgn->lower;
2648 newrgn->upper = range.lower;
2649 chkrgn->lower = range.upper;
2650 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2652 COMCTL32_Free(newrgn);
2659 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2662 ranges_check(ranges, "after del");
2666 ranges_check(ranges, "failed del");
2672 * Removes all selection ranges
2675 * [I] infoPtr : valid pointer to the listview structure
2676 * [I] toSkip : item range to skip removing the selection
2682 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2691 lvItem.stateMask = LVIS_SELECTED;
2693 /* need to clone the DPA because callbacks can change it */
2694 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2695 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2696 while(iterator_next(&i))
2697 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2698 /* note that the iterator destructor will free the cloned range */
2699 iterator_destroy(&i);
2704 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2708 if (!(toSkip = ranges_create(1))) return FALSE;
2709 if (nItem != -1) ranges_additem(toSkip, nItem);
2710 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2711 ranges_destroy(toSkip);
2715 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2717 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2722 * Retrieves the number of items that are marked as selected.
2725 * [I] infoPtr : valid pointer to the listview structure
2728 * Number of items selected.
2730 static INT LISTVIEW_GetSelectedCount(LISTVIEW_INFO *infoPtr)
2732 INT nSelectedCount = 0;
2734 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2737 for (i = 0; i < infoPtr->nItemCount; i++)
2739 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2744 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2746 TRACE("nSelectedCount=%d\n", nSelectedCount);
2747 return nSelectedCount;
2752 * Manages the item focus.
2755 * [I] infoPtr : valid pointer to the listview structure
2756 * [I] nItem : item index
2759 * TRUE : focused item changed
2760 * FALSE : focused item has NOT changed
2762 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2764 INT oldFocus = infoPtr->nFocusedItem;
2767 if (nItem == infoPtr->nFocusedItem) return FALSE;
2769 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2770 lvItem.stateMask = LVIS_FOCUSED;
2771 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
2773 return oldFocus != infoPtr->nFocusedItem;
2776 /* Helper function for LISTVIEW_ShiftIndices *only* */
2777 static INT shift_item(LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2779 if (nShiftItem < nItem) return nShiftItem;
2781 if (nShiftItem > nItem) return nShiftItem + direction;
2783 if (direction > 0) return nShiftItem + direction;
2785 return min(nShiftItem, infoPtr->nItemCount - 1);
2790 * Updates the various indices after an item has been inserted or deleted.
2793 * [I] infoPtr : valid pointer to the listview structure
2794 * [I] nItem : item index
2795 * [I] direction : Direction of shift, +1 or -1.
2800 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
2804 TRACE("Shifting %iu, %i steps\n", nItem, direction);
2806 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
2808 assert(abs(direction) == 1);
2810 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
2812 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
2813 if (nNewFocus != infoPtr->nFocusedItem)
2814 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
2816 /* But we are not supposed to modify nHotItem! */
2822 * Adds a block of selections.
2825 * [I] infoPtr : valid pointer to the listview structure
2826 * [I] nItem : item index
2831 static void LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2833 INT nFirst = min(infoPtr->nSelectionMark, nItem);
2834 INT nLast = max(infoPtr->nSelectionMark, nItem);
2838 if (nFirst == -1) nFirst = nItem;
2840 item.state = LVIS_SELECTED;
2841 item.stateMask = LVIS_SELECTED;
2843 /* FIXME: this is not correct LVS_OWNERDATA
2844 * setting the item states individually will generate
2845 * a LVN_ITEMCHANGED notification for each one. Instead,
2846 * we have to send a LVN_ODSTATECHANGED notification.
2847 * See MSDN documentation for LVN_ITEMCHANGED.
2849 for (i = nFirst; i <= nLast; i++)
2850 LISTVIEW_SetItemState(infoPtr,i,&item);
2856 * Sets a single group selection.
2859 * [I] infoPtr : valid pointer to the listview structure
2860 * [I] nItem : item index
2865 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2867 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2872 if (!(selection = ranges_create(100))) return;
2874 item.state = LVIS_SELECTED;
2875 item.stateMask = LVIS_SELECTED;
2877 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
2879 if (infoPtr->nSelectionMark == -1)
2881 infoPtr->nSelectionMark = nItem;
2882 ranges_additem(selection, nItem);
2888 sel.lower = min(infoPtr->nSelectionMark, nItem);
2889 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
2890 ranges_add(selection, sel);
2895 RECT rcItem, rcSel, rcSelMark;
2898 rcItem.left = LVIR_BOUNDS;
2899 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
2900 rcSelMark.left = LVIR_BOUNDS;
2901 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
2902 UnionRect(&rcSel, &rcItem, &rcSelMark);
2903 iterator_frameditems(&i, infoPtr, &rcSel);
2904 while(iterator_next(&i))
2906 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
2907 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
2909 iterator_destroy(&i);
2912 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
2913 iterator_rangesitems(&i, selection);
2914 while(iterator_next(&i))
2915 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
2916 /* this will also destroy the selection */
2917 iterator_destroy(&i);
2919 LISTVIEW_SetItemFocus(infoPtr, nItem);
2924 * Sets a single selection.
2927 * [I] infoPtr : valid pointer to the listview structure
2928 * [I] nItem : item index
2933 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2937 TRACE("nItem=%d\n", nItem);
2939 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
2941 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
2942 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
2943 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
2945 infoPtr->nSelectionMark = nItem;
2950 * Set selection(s) with keyboard.
2953 * [I] infoPtr : valid pointer to the listview structure
2954 * [I] nItem : item index
2957 * SUCCESS : TRUE (needs to be repainted)
2958 * FAILURE : FALSE (nothing has changed)
2960 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
2962 /* FIXME: pass in the state */
2963 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
2964 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
2965 BOOL bResult = FALSE;
2967 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
2969 if (infoPtr->dwStyle & LVS_SINGLESEL)
2972 LISTVIEW_SetSelection(infoPtr, nItem);
2979 LISTVIEW_SetGroupSelection(infoPtr, nItem);
2983 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
2988 LISTVIEW_SetSelection(infoPtr, nItem);
2991 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
2994 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3001 * Called when the mouse is being actively tracked and has hovered for a specified
3005 * [I] infoPtr : valid pointer to the listview structure
3006 * [I] fwKeys : key indicator
3007 * [I] pts : mouse position
3010 * 0 if the message was processed, non-zero if there was an error
3013 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3014 * over the item for a certain period of time.
3017 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKyes, POINTS pts)
3019 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
3020 /* FIXME: select the item!!! */
3021 /*LISTVIEW_GetItemAtPt(infoPtr, pt)*/;
3028 * Called whenever WM_MOUSEMOVE is received.
3031 * [I] infoPtr : valid pointer to the listview structure
3032 * [I] fwKeys : key indicator
3033 * [I] pts : mouse position
3036 * 0 if the message is processed, non-zero if there was an error
3038 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, POINTS pts)
3040 TRACKMOUSEEVENT trackinfo;
3042 /* see if we are supposed to be tracking mouse hovering */
3043 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
3044 /* fill in the trackinfo struct */
3045 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3046 trackinfo.dwFlags = TME_QUERY;
3047 trackinfo.hwndTrack = infoPtr->hwndSelf;
3048 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3050 /* see if we are already tracking this hwnd */
3051 _TrackMouseEvent(&trackinfo);
3053 if(!(trackinfo.dwFlags & TME_HOVER)) {
3054 trackinfo.dwFlags = TME_HOVER;
3056 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3057 _TrackMouseEvent(&trackinfo);
3066 * Tests wheather the item is assignable to a list with style lStyle
3068 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3070 if ( (lpLVItem->mask & LVIF_TEXT) &&
3071 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3072 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3080 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3083 * [I] infoPtr : valid pointer to the listview structure
3084 * [I] lpLVItem : valid pointer to new item atttributes
3085 * [I] isNew : the item being set is being inserted
3086 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3087 * [O] bChanged : will be set to TRUE if the item really changed
3093 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3095 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3103 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3105 if (lpLVItem->mask == 0) return TRUE;
3107 if (infoPtr->dwStyle & LVS_OWNERDATA)
3109 /* a virtual listview we stores only selection and focus */
3110 if (lpLVItem->mask & ~LVIF_STATE)
3116 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3117 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
3121 /* we need to get the lParam and state of the item */
3122 item.iItem = lpLVItem->iItem;
3123 item.iSubItem = lpLVItem->iSubItem;
3124 item.mask = LVIF_STATE | LVIF_PARAM;
3125 item.stateMask = ~0;
3128 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3130 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3131 /* determine what fields will change */
3132 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
3133 uChanged |= LVIF_STATE;
3135 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3136 uChanged |= LVIF_IMAGE;
3138 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3139 uChanged |= LVIF_PARAM;
3141 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3142 uChanged |= LVIF_INDENT;
3144 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3145 uChanged |= LVIF_TEXT;
3147 TRACE("uChanged=0x%x\n", uChanged);
3148 if (!uChanged) return TRUE;
3151 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3152 nmlv.iItem = lpLVItem->iItem;
3153 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3154 nmlv.uOldState = item.state;
3155 nmlv.uChanged = uChanged;
3156 nmlv.lParam = item.lParam;
3158 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3159 /* and we are _NOT_ virtual (LVS_OWERNDATA) */
3160 if(lpItem && !isNew && notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3163 /* copy information */
3164 if (lpLVItem->mask & LVIF_TEXT)
3165 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3167 if (lpLVItem->mask & LVIF_IMAGE)
3168 lpItem->hdr.iImage = lpLVItem->iImage;
3170 if (lpLVItem->mask & LVIF_PARAM)
3171 lpItem->lParam = lpLVItem->lParam;
3173 if (lpLVItem->mask & LVIF_INDENT)
3174 lpItem->iIndent = lpLVItem->iIndent;
3176 if (uChanged & LVIF_STATE)
3178 if (lpItem && (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3180 lpItem->state &= ~lpLVItem->stateMask;
3181 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3183 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3185 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3186 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3188 else if (lpLVItem->stateMask & LVIS_SELECTED)
3189 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3191 /* if we are asked to change focus, and we manage it, do it */
3192 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3194 if (lpLVItem->state & LVIS_FOCUSED)
3196 LISTVIEW_SetItemFocus(infoPtr, -1);
3197 infoPtr->nFocusedItem = lpLVItem->iItem;
3198 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3200 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3201 infoPtr->nFocusedItem = -1;
3205 /* if we're inserting the item, we're done */
3206 if (isNew) return TRUE;
3208 /* send LVN_ITEMCHANGED notification */
3209 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3210 notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3217 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3220 * [I] infoPtr : valid pointer to the listview structure
3221 * [I] lpLVItem : valid pointer to new subitem atttributes
3222 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3223 * [O] bChanged : will be set to TRUE if the item really changed
3229 static BOOL set_sub_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3232 SUBITEM_INFO *lpSubItem;
3234 /* we do not support subitems for virtual listviews */
3235 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3237 /* set subitem only if column is present */
3238 if (lpLVItem->iSubItem >= infoPtr->hdpaColumns->nItemCount) return FALSE;
3240 /* First do some sanity checks */
3241 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE)) return FALSE;
3242 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE))) return TRUE;
3244 /* get the subitem structure, and create it if not there */
3245 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3246 assert (hdpaSubItems);
3248 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3251 SUBITEM_INFO *tmpSubItem;
3254 lpSubItem = (SUBITEM_INFO *)COMCTL32_Alloc(sizeof(SUBITEM_INFO));
3255 if (!lpSubItem) return FALSE;
3256 /* we could binary search here, if need be...*/
3257 for (i = 1; i < hdpaSubItems->nItemCount; i++)
3259 tmpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
3260 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3262 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3264 COMCTL32_Free(lpSubItem);
3267 lpSubItem->iSubItem = lpLVItem->iSubItem;
3271 if (lpLVItem->mask & LVIF_IMAGE)
3272 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3274 lpSubItem->hdr.iImage = lpLVItem->iImage;
3278 if (lpLVItem->mask & LVIF_TEXT)
3279 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3281 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3290 * Sets item attributes.
3293 * [I] infoPtr : valid pointer to the listview structure
3294 * [I] lpLVItem : new item atttributes
3295 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3301 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
3303 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3304 LPWSTR pszText = NULL;
3305 BOOL bResult, bChanged = FALSE;
3307 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3309 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3312 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3313 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3315 pszText = lpLVItem->pszText;
3316 ((LVITEMW *)lpLVItem)->pszText = textdupTtoW(lpLVItem->pszText, isW);
3319 /* actually set the fields */
3320 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3322 if (lpLVItem->iSubItem)
3323 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3325 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3327 /* redraw item, if necessary */
3328 if (bChanged && !infoPtr->bIsDrawing)
3330 /* this little optimization eliminates some nasty flicker */
3331 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3332 (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) || lpLVItem->iSubItem) )
3333 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3335 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3340 textfreeT(lpLVItem->pszText, isW);
3341 ((LVITEMW *)lpLVItem)->pszText = pszText;
3349 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3352 * [I] infoPtr : valid pointer to the listview structure
3357 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *infoPtr)
3359 LONG lStyle = infoPtr->dwStyle;
3360 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3362 SCROLLINFO scrollInfo;
3364 scrollInfo.cbSize = sizeof(SCROLLINFO);
3365 scrollInfo.fMask = SIF_POS;
3367 if (uView == LVS_LIST)
3369 if ((lStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3370 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3372 else if (uView == LVS_REPORT)
3374 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3375 nItem = scrollInfo.nPos;
3379 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3380 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3383 TRACE("nItem=%d\n", nItem);
3391 * Erases the background of the given rectangle
3394 * [I] infoPtr : valid pointer to the listview structure
3395 * [I] hdc : device context handle
3396 * [I] lprcBox : clipping rectangle
3402 static inline BOOL LISTVIEW_FillBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3404 if (!infoPtr->hBkBrush) return FALSE;
3406 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, debugrect(lprcBox), infoPtr->hBkBrush);
3408 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3416 * [I] infoPtr : valid pointer to the listview structure
3417 * [I] hdc : device context handle
3418 * [I] nItem : item index
3419 * [I] nSubItem : subitem index
3420 * [I] pos : item position in client coordinates
3421 * [I] cdmode : custom draw mode
3427 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3429 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3430 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3431 WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3432 DWORD cditemmode = CDRF_DODEFAULT;
3433 RECT* lprcFocus, rcSelect, rcBox, rcState, rcIcon, rcLabel;
3434 NMLVCUSTOMDRAW nmlvcd;
3438 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, debugpoint(&pos));
3440 /* get information needed for drawing the item */
3441 lvItem.mask = LVIF_TEXT | LVIF_IMAGE;
3442 if (nSubItem == 0) lvItem.mask |= LVIF_STATE | LVIF_PARAM;
3443 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3444 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3445 lvItem.iItem = nItem;
3446 lvItem.iSubItem = nSubItem;
3449 lvItem.cchTextMax = DISP_TEXT_SIZE;
3450 lvItem.pszText = szDispText;
3451 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3452 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3453 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3454 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3455 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3457 /* now check if we need to update the focus rectangle */
3458 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3460 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3461 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
3462 OffsetRect(&rcBox, pos.x, pos.y);
3463 OffsetRect(&rcState, pos.x, pos.y);
3464 OffsetRect(&rcIcon, pos.x, pos.y);
3465 OffsetRect(&rcLabel, pos.x, pos.y);
3466 TRACE(" rcBox=%s, rcState=%s, rcIcon=%s. rcLabel=%s\n",
3467 debugrect(&rcBox), debugrect(&rcState), debugrect(&rcIcon), debugrect(&rcLabel));
3469 /* fill in the custom draw structure */
3470 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox);
3471 nmlvcd.nmcd.dwItemSpec = lvItem.iItem;
3472 nmlvcd.iSubItem = lvItem.iSubItem;
3473 if (lvItem.state & LVIS_SELECTED) nmlvcd.nmcd.uItemState |= CDIS_SELECTED;
3474 if (lvItem.state & LVIS_FOCUSED) nmlvcd.nmcd.uItemState |= CDIS_FOCUS;
3475 if (lvItem.iItem == infoPtr->nHotItem) nmlvcd.nmcd.uItemState |= CDIS_HOT;
3476 nmlvcd.nmcd.lItemlParam = lvItem.lParam;
3478 if (cdmode & CDRF_NOTIFYITEMDRAW)
3479 cditemmode = notify_customdraw (infoPtr, CDDS_ITEMPREPAINT, &nmlvcd);
3480 if (cditemmode & CDRF_SKIPDEFAULT) goto postpaint;
3482 /* apprently, for selected items, we have to override the returned values */
3483 if (lvItem.state & LVIS_SELECTED)
3485 if (infoPtr->bFocus)
3487 nmlvcd.clrTextBk = comctl32_color.clrHighlight;
3488 nmlvcd.clrText = comctl32_color.clrHighlightText;
3490 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
3492 nmlvcd.clrTextBk = comctl32_color.clr3dFace;
3493 nmlvcd.clrText = comctl32_color.clrBtnText;
3497 /* in full row select, subitems, will just use main item's colors */
3498 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3499 nmlvcd.clrTextBk = CLR_NONE;
3502 if (infoPtr->himlState && !IsRectEmpty(&rcState))
3504 UINT uStateImage = (lvItem.state & LVIS_STATEIMAGEMASK) >> 12;
3507 TRACE("uStateImage=%d\n", uStateImage);
3508 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc, rcState.left, rcState.top, ILD_NORMAL);
3513 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3514 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3516 TRACE("iImage=%d\n", lvItem.iImage);
3517 ImageList_Draw(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3518 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3521 /* Don't bother painting item being edited */
3522 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3524 /* Set the text attributes */
3525 if (nmlvcd.clrTextBk != CLR_NONE)
3527 SetBkMode(hdc, OPAQUE);
3528 SetBkColor(hdc, nmlvcd.clrTextBk == CLR_DEFAULT ? infoPtr->clrTextBkDefault : nmlvcd.clrTextBk);
3531 SetBkMode(hdc, TRANSPARENT);
3532 SetTextColor(hdc, nmlvcd.clrText);
3534 /* draw the selection background, if we're drawing the main item */
3538 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3539 rcSelect.right = rcBox.right;
3541 if (nmlvcd.clrTextBk != CLR_NONE)
3542 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3543 if(lprcFocus) *lprcFocus = rcSelect;
3546 /* figure out the text drawing flags */
3547 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3548 if (uView == LVS_ICON)
3549 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3552 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3554 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3555 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3556 default: uFormat |= DT_LEFT;
3559 if (!(uFormat & (DT_RIGHT | DT_CENTER))) rcLabel.left += 2;
3560 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3563 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3564 notify_customdraw(infoPtr, CDDS_ITEMPOSTPAINT, &nmlvcd);
3570 * Draws listview items when in owner draw mode.
3573 * [I] infoPtr : valid pointer to the listview structure
3574 * [I] hdc : device context handle
3579 static void LISTVIEW_RefreshOwnerDraw(LISTVIEW_INFO *infoPtr, HDC hdc)
3581 UINT uID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
3582 HWND hwndParent = GetParent(infoPtr->hwndSelf);
3583 POINT Origin, Position;
3590 ZeroMemory(&dis, sizeof(dis));
3592 /* Get scroll info once before loop */
3593 LISTVIEW_GetOrigin(infoPtr, &Origin);
3595 /* figure out what we need to draw */
3596 iterator_visibleitems(&i, infoPtr, hdc);
3598 /* send cache hint notification */
3599 if (infoPtr->dwStyle & LVS_OWNERDATA)
3601 RANGE range = iterator_range(&i);
3604 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
3605 nmlv.iFrom = range.lower;
3606 nmlv.iTo = range.upper - 1;
3607 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
3610 /* iterate through the invalidated rows */
3611 while(iterator_next(&i))
3613 item.iItem = i.nItem;
3615 item.mask = LVIF_PARAM | LVIF_STATE;
3616 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3617 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3619 dis.CtlType = ODT_LISTVIEW;
3621 dis.itemID = item.iItem;
3622 dis.itemAction = ODA_DRAWENTIRE;
3624 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3625 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3626 dis.hwndItem = infoPtr->hwndSelf;
3628 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3629 dis.rcItem.left = Position.x + Origin.x;
3630 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3631 dis.rcItem.top = Position.y + Origin.y;
3632 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3633 dis.itemData = item.lParam;
3635 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), debugrect(&dis.rcItem));
3636 SendMessageW(hwndParent, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3638 iterator_destroy(&i);
3643 * Draws listview items when in report display mode.
3646 * [I] infoPtr : valid pointer to the listview structure
3647 * [I] hdc : device context handle
3648 * [I] cdmode : custom draw mode
3653 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD cdmode)
3656 RECT rcClip, rcItem;
3657 POINT Origin, Position;
3663 /* figure out what to draw */
3664 rgntype = GetClipBox(hdc, &rcClip);
3665 if (rgntype == NULLREGION) return;
3667 /* Get scroll info once before loop */
3668 LISTVIEW_GetOrigin(infoPtr, &Origin);
3670 /* narrow down the columns we need to paint */
3671 for(colRange.lower = 0; colRange.lower < infoPtr->hdpaColumns->nItemCount; colRange.lower++)
3673 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
3674 if (rcItem.right + Origin.x >= rcClip.left) break;
3676 for(colRange.upper = infoPtr->hdpaColumns->nItemCount; colRange.upper > 0; colRange.upper--)
3678 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
3679 if (rcItem.left + Origin.x < rcClip.right) break;
3681 iterator_rangeitems(&j, colRange);
3683 /* in full row select, we _have_ to draw the main item */
3684 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
3687 /* figure out what we need to draw */
3688 iterator_visibleitems(&i, infoPtr, hdc);
3690 /* iterate through the invalidated rows */
3691 while(iterator_next(&i))
3693 /* iterate through the invalidated columns */
3694 while(iterator_next(&j))
3696 LISTVIEW_GetItemOrigin(infoPtr, i.nItem, &Position);
3697 Position.x += Origin.x;
3698 Position.y += Origin.y;
3700 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
3702 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
3704 rcItem.bottom = infoPtr->nItemHeight;
3705 OffsetRect(&rcItem, Position.x, Position.y);
3706 if (!RectVisible(hdc, &rcItem)) continue;
3709 LISTVIEW_DrawItem(infoPtr, hdc, i.nItem, j.nItem, Position, cdmode);
3712 iterator_destroy(&i);
3717 * Draws listview items when in list display mode.
3720 * [I] infoPtr : valid pointer to the listview structure
3721 * [I] hdc : device context handle
3722 * [I] cdmode : custom draw mode
3727 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD cdmode)
3729 POINT Origin, Position;
3732 /* Get scroll info once before loop */
3733 LISTVIEW_GetOrigin(infoPtr, &Origin);
3735 /* figure out what we need to draw */
3736 iterator_visibleitems(&i, infoPtr, hdc);
3738 while(iterator_prev(&i))
3740 LISTVIEW_GetItemOrigin(infoPtr, i.nItem, &Position);
3741 Position.x += Origin.x;
3742 Position.y += Origin.y;
3744 LISTVIEW_DrawItem(infoPtr, hdc, i.nItem, 0, Position, cdmode);
3746 iterator_destroy(&i);
3752 * Draws listview items.
3755 * [I] infoPtr : valid pointer to the listview structure
3756 * [I] hdc : device context handle
3761 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc)
3763 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3764 COLORREF oldTextColor, oldClrTextBk, oldClrText;
3765 NMLVCUSTOMDRAW nmlvcd;
3771 LISTVIEW_DUMP(infoPtr);
3773 infoPtr->bIsDrawing = TRUE;
3775 /* save dc values we're gonna trash while drawing */
3776 hOldFont = SelectObject(hdc, infoPtr->hFont);
3777 oldBkMode = GetBkMode(hdc);
3778 infoPtr->clrTextBkDefault = GetBkColor(hdc);
3779 oldTextColor = GetTextColor(hdc);
3781 oldClrTextBk = infoPtr->clrTextBk;
3782 oldClrText = infoPtr->clrText;
3784 GetClientRect(infoPtr->hwndSelf, &rcClient);
3785 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient);
3786 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3787 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
3789 /* Use these colors to draw the items */
3790 infoPtr->clrTextBk = nmlvcd.clrTextBk;
3791 infoPtr->clrText = nmlvcd.clrText;
3793 /* nothing to draw */
3794 if(infoPtr->nItemCount == 0) goto enddraw;
3796 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
3797 LISTVIEW_RefreshOwnerDraw(infoPtr, hdc);
3800 if (uView == LVS_REPORT)
3801 LISTVIEW_RefreshReport(infoPtr, hdc, cdmode);
3802 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
3803 LISTVIEW_RefreshList(infoPtr, hdc, cdmode);
3805 /* if we have a focus rect, draw it */
3806 if (infoPtr->bFocus)
3807 DrawFocusRect(hdc, &infoPtr->rcFocus);
3811 if (cdmode & CDRF_NOTIFYPOSTPAINT)
3812 notify_customdraw(infoPtr, CDDS_POSTPAINT, &nmlvcd);
3814 infoPtr->clrTextBk = oldClrTextBk;
3815 infoPtr->clrText = oldClrText;
3817 SelectObject(hdc, hOldFont);
3818 SetBkMode(hdc, oldBkMode);
3819 SetBkColor(hdc, infoPtr->clrTextBkDefault);
3820 SetTextColor(hdc, oldTextColor);
3821 infoPtr->bIsDrawing = FALSE;
3827 * Calculates the approximate width and height of a given number of items.
3830 * [I] infoPtr : valid pointer to the listview structure
3831 * [I] nItemCount : number of items
3832 * [I] wWidth : width
3833 * [I] wHeight : height
3836 * Returns a DWORD. The width in the low word and the height in high word.
3838 static DWORD LISTVIEW_ApproximateViewRect(LISTVIEW_INFO *infoPtr, INT nItemCount,
3839 WORD wWidth, WORD wHeight)
3841 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3842 INT nItemCountPerColumn = 1;
3843 INT nColumnCount = 0;
3844 DWORD dwViewRect = 0;
3846 if (nItemCount == -1)
3847 nItemCount = infoPtr->nItemCount;
3849 if (uView == LVS_LIST)
3851 if (wHeight == 0xFFFF)
3853 /* use current height */
3854 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
3857 if (wHeight < infoPtr->nItemHeight)
3858 wHeight = infoPtr->nItemHeight;
3862 if (infoPtr->nItemHeight > 0)
3864 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
3865 if (nItemCountPerColumn == 0)
3866 nItemCountPerColumn = 1;
3868 if (nItemCount % nItemCountPerColumn != 0)
3869 nColumnCount = nItemCount / nItemCountPerColumn;
3871 nColumnCount = nItemCount / nItemCountPerColumn + 1;
3875 /* Microsoft padding magic */
3876 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
3877 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
3879 dwViewRect = MAKELONG(wWidth, wHeight);
3881 else if (uView == LVS_REPORT)
3882 FIXME("uView == LVS_REPORT: not implemented\n");
3883 else if (uView == LVS_SMALLICON)
3884 FIXME("uView == LVS_SMALLICON: not implemented\n");
3885 else if (uView == LVS_ICON)
3886 FIXME("uView == LVS_ICON: not implemented\n");
3891 /* << LISTVIEW_CreateDragImage >> */
3896 * Removes all listview items and subitems.
3899 * [I] infoPtr : valid pointer to the listview structure
3905 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr)
3908 HDPA hdpaSubItems = NULL;
3915 /* we do it directly, to avoid notifications */
3916 ranges_clear(infoPtr->selectionRanges);
3917 infoPtr->nSelectionMark = -1;
3918 infoPtr->nFocusedItem = -1;
3919 SetRectEmpty(&infoPtr->rcFocus);
3920 /* But we are supposed to leave nHotItem as is! */
3923 /* send LVN_DELETEALLITEMS notification */
3924 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3926 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
3928 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
3930 /* send LVN_DELETEITEM notification, if not supressed */
3931 if (!bSuppress) notify_deleteitem(infoPtr, i);
3932 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
3934 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
3935 for (j = 0; j < hdpaSubItems->nItemCount; j++)
3937 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, j);
3938 if (is_textW(hdrItem->pszText)) COMCTL32_Free(hdrItem->pszText);
3939 COMCTL32_Free(hdrItem);
3941 DPA_Destroy(hdpaSubItems);
3942 DPA_DeletePtr(infoPtr->hdpaItems, i);
3944 DPA_DeletePtr(infoPtr->hdpaPosX, i);
3945 DPA_DeletePtr(infoPtr->hdpaPosY, i);
3946 infoPtr->nItemCount --;
3949 LISTVIEW_UpdateScroll(infoPtr);
3951 LISTVIEW_InvalidateList(infoPtr);
3958 * Scrolls, and updates the columns, when a column is changing width.
3961 * [I] infoPtr : valid pointer to the listview structure
3962 * [I] nColumn : column to scroll
3963 * [I] dx : amount of scroll, in pixels
3968 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
3970 COLUMN_INFO *lpColumnInfo;
3974 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, infoPtr->hdpaColumns->nItemCount - 1));
3975 rcCol = lpColumnInfo->rcHeader;
3976 if (nColumn >= infoPtr->hdpaColumns->nItemCount)
3977 rcCol.left = rcCol.right;
3979 /* ajust the other columns */
3980 for (nCol = nColumn; nCol < infoPtr->hdpaColumns->nItemCount; nCol++)
3982 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
3983 lpColumnInfo->rcHeader.left += dx;
3984 lpColumnInfo->rcHeader.right += dx;
3987 /* do not update screen if not in report mode */
3988 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
3990 /* if we have a focus, must first erase the focus rect */
3991 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
3993 /* Need to reset the item width when inserting a new column */
3994 infoPtr->nItemWidth += dx;
3996 LISTVIEW_UpdateScroll(infoPtr);
3998 /* scroll to cover the deleted column, and invalidate for redraw */
3999 rcOld = infoPtr->rcList;
4000 rcOld.left = rcCol.left;
4001 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4003 /* we can restore focus now */
4004 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4009 * Removes a column from the listview control.
4012 * [I] infoPtr : valid pointer to the listview structure
4013 * [I] nColumn : column index
4019 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4023 TRACE("nColumn=%d\n", nColumn);
4025 if (nColumn <= 0 || nColumn >= infoPtr->hdpaColumns->nItemCount) return FALSE;
4027 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4029 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4032 COMCTL32_Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4033 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4035 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4037 SUBITEM_INFO *lpSubItem, *lpDelItem;
4039 INT nItem, nSubItem, i;
4041 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4043 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
4046 for (i = 1; i < hdpaSubItems->nItemCount; i++)
4048 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
4049 if (lpSubItem->iSubItem == nColumn)
4052 lpDelItem = lpSubItem;
4054 else if (lpSubItem->iSubItem > nColumn)
4056 lpSubItem->iSubItem--;
4060 /* if we found our subitem, zapp it */
4064 if (is_textW(lpDelItem->hdr.pszText))
4065 COMCTL32_Free(lpDelItem->hdr.pszText);
4068 COMCTL32_Free(lpDelItem);
4070 /* free dpa memory */
4071 DPA_DeletePtr(hdpaSubItems, nSubItem);
4076 /* update the other column info */
4077 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4084 * Invalidates the listview after an item's insertion or deletion.
4087 * [I] infoPtr : valid pointer to the listview structure
4088 * [I] nItem : item index
4089 * [I] dir : -1 if deleting, 1 if inserting
4094 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4096 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4097 INT nPerCol, nItemCol, nItemRow;
4101 /* if we don't refresh, what's the point of scrolling? */
4102 if (!is_redrawing(infoPtr)) return;
4104 assert (abs(dir) == 1);
4106 /* arrange icons if autoarrange is on */
4107 if (is_autoarrange(infoPtr))
4109 BOOL arrange = TRUE;
4110 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4111 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4112 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4115 /* scrollbars need updating */
4116 LISTVIEW_UpdateScroll(infoPtr);
4118 /* figure out the item's position */
4119 if (uView == LVS_REPORT)
4120 nPerCol = infoPtr->nItemCount + 1;
4121 else if (uView == LVS_LIST)
4122 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4123 else /* LVS_ICON, or LVS_SMALLICON */
4126 nItemCol = nItem / nPerCol;
4127 nItemRow = nItem % nPerCol;
4128 LISTVIEW_GetOrigin(infoPtr, &Origin);
4130 /* move the items below up a slot */
4131 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4132 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4133 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4134 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4135 OffsetRect(&rcScroll, Origin.x, Origin.y);
4136 TRACE("rcScroll=%s, dx=%d\n", debugrect(&rcScroll), dir * infoPtr->nItemHeight);
4137 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4139 TRACE("Scrolling rcScroll=%s, rcList=%s\n", debugrect(&rcScroll), debugrect(&infoPtr->rcList));
4140 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4141 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4144 /* report has only that column, so we're done */
4145 if (uView == LVS_REPORT) return;
4147 /* now for LISTs, we have to deal with the columns to the right */
4148 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4150 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4151 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4152 OffsetRect(&rcScroll, Origin.x, Origin.y);
4153 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4154 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4155 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4160 * Removes an item from the listview control.
4163 * [I] infoPtr : valid pointer to the listview structure
4164 * [I] nItem : item index
4170 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4172 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4175 TRACE("(nItem=%d)\n", nItem);
4177 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4179 /* remove selection, and focus */
4181 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4182 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4184 /* send LVN_DELETEITEM notification. */
4185 notify_deleteitem(infoPtr, nItem);
4187 /* we need to do this here, because we'll be deleting stuff */
4188 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4189 LISTVIEW_InvalidateItem(infoPtr, nItem);
4191 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4197 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4198 for (i = 0; i < hdpaSubItems->nItemCount; i++)
4200 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, i);
4201 if (is_textW(hdrItem->pszText)) COMCTL32_Free(hdrItem->pszText);
4202 COMCTL32_Free(hdrItem);
4204 DPA_Destroy(hdpaSubItems);
4207 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4209 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4210 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4213 infoPtr->nItemCount--;
4214 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4216 /* now is the invalidation fun */
4217 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4224 * Callback implementation for editlabel control
4227 * [I] infoPtr : valid pointer to the listview structure
4228 * [I] pszText : modified text
4229 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4235 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4237 NMLVDISPINFOW dispInfo;
4239 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4241 ZeroMemory(&dispInfo, sizeof(dispInfo));
4242 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
4243 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4244 dispInfo.item.iSubItem = 0;
4245 dispInfo.item.stateMask = ~0;
4246 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4247 /* add the text from the edit in */
4248 dispInfo.item.mask |= LVIF_TEXT;
4249 dispInfo.item.pszText = pszText;
4250 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4252 /* Do we need to update the Item Text */
4253 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4254 if (!pszText) return TRUE;
4256 ZeroMemory(&dispInfo, sizeof(dispInfo));
4257 dispInfo.item.mask = LVIF_TEXT;
4258 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4259 dispInfo.item.iSubItem = 0;
4260 dispInfo.item.pszText = pszText;
4261 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4262 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4267 * Begin in place editing of specified list view item
4270 * [I] infoPtr : valid pointer to the listview structure
4271 * [I] nItem : item index
4272 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4278 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4280 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4281 NMLVDISPINFOW dispInfo;
4284 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4286 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4287 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4289 infoPtr->nEditLabelItem = nItem;
4291 /* Is the EditBox still there, if so remove it */
4292 if(infoPtr->hwndEdit != 0)
4294 SetFocus(infoPtr->hwndSelf);
4295 infoPtr->hwndEdit = 0;
4298 LISTVIEW_SetSelection(infoPtr, nItem);
4299 LISTVIEW_SetItemFocus(infoPtr, nItem);
4300 LISTVIEW_InvalidateItem(infoPtr, nItem);
4302 rect.left = LVIR_LABEL;
4303 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4305 ZeroMemory(&dispInfo, sizeof(dispInfo));
4306 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4307 dispInfo.item.iItem = nItem;
4308 dispInfo.item.iSubItem = 0;
4309 dispInfo.item.stateMask = ~0;
4310 dispInfo.item.pszText = szDispText;
4311 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4312 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4314 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4315 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4316 if (!infoPtr->hwndEdit) return 0;
4318 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4320 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4321 infoPtr->hwndEdit = 0;
4325 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4326 SetFocus(infoPtr->hwndEdit);
4327 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4328 return infoPtr->hwndEdit;
4334 * Ensures the specified item is visible, scrolling into view if necessary.
4337 * [I] infoPtr : valid pointer to the listview structure
4338 * [I] nItem : item index
4339 * [I] bPartial : partially or entirely visible
4345 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4347 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4348 INT nScrollPosHeight = 0;
4349 INT nScrollPosWidth = 0;
4350 INT nHorzAdjust = 0;
4351 INT nVertAdjust = 0;
4354 RECT rcItem, rcTemp;
4356 rcItem.left = LVIR_BOUNDS;
4357 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4359 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4361 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4363 /* scroll left/right, but in LVS_REPORT mode */
4364 if (uView == LVS_LIST)
4365 nScrollPosWidth = infoPtr->nItemWidth;
4366 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4367 nScrollPosWidth = 1;
4369 if (rcItem.left < infoPtr->rcList.left)
4372 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
4377 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
4381 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
4383 /* scroll up/down, but not in LVS_LIST mode */
4384 if (uView == LVS_REPORT)
4385 nScrollPosHeight = infoPtr->nItemHeight;
4386 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
4387 nScrollPosHeight = 1;
4389 if (rcItem.top < infoPtr->rcList.top)
4392 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
4397 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
4401 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
4403 if (nScrollPosWidth)
4405 INT diff = nHorzDiff / nScrollPosWidth;
4406 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
4407 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
4410 if (nScrollPosHeight)
4412 INT diff = nVertDiff / nScrollPosHeight;
4413 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
4414 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
4422 * Searches for an item with specific characteristics.
4425 * [I] hwnd : window handle
4426 * [I] nStart : base item index
4427 * [I] lpFindInfo : item information to look for
4430 * SUCCESS : index of item
4433 static INT LISTVIEW_FindItemW(LISTVIEW_INFO *infoPtr, INT nStart,
4434 const LVFINDINFOW *lpFindInfo)
4436 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4437 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4438 BOOL bWrap = FALSE, bNearest = FALSE;
4439 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
4440 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
4441 POINT Position, Destination;
4444 if (!lpFindInfo || nItem < 0) return -1;
4447 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
4449 lvItem.mask |= LVIF_TEXT;
4450 lvItem.pszText = szDispText;
4451 lvItem.cchTextMax = DISP_TEXT_SIZE;
4454 if (lpFindInfo->flags & LVFI_WRAP)
4457 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
4458 (uView == LVS_ICON || uView ==LVS_SMALLICON))
4463 LISTVIEW_GetOrigin(infoPtr, &Origin);
4464 Destination.x = lpFindInfo->pt.x - Origin.x;
4465 Destination.y = lpFindInfo->pt.y - Origin.y;
4466 switch(lpFindInfo->vkDirection)
4468 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
4469 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
4470 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
4471 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
4472 case VK_HOME: Destination.x = Destination.y = 0; break;
4473 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4474 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4476 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
4477 Destination.x = rcArea.right;
4478 Destination.y = rcArea.bottom;
4480 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
4485 /* if LVFI_PARAM is specified, all other flags are ignored */
4486 if (lpFindInfo->flags & LVFI_PARAM)
4488 lvItem.mask |= LVIF_PARAM;
4490 lvItem.mask &= ~LVIF_TEXT;
4494 for (; nItem < nLast; nItem++)
4496 lvItem.iItem = nItem;
4497 lvItem.iSubItem = 0;
4498 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
4500 if (lvItem.mask & LVIF_PARAM)
4502 if (lpFindInfo->lParam == lvItem.lParam)
4508 if (lvItem.mask & LVIF_TEXT)
4510 if (lpFindInfo->flags & LVFI_PARTIAL)
4512 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
4516 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
4520 if (!bNearest) return nItem;
4522 /* This is very inefficient. To do a good job here,
4523 * we need a sorted array of (x,y) item positions */
4524 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
4526 /* compute the distance^2 to the destination */
4527 xdist = Destination.x - Position.x;
4528 ydist = Destination.y - Position.y;
4529 dist = xdist * xdist + ydist * ydist;
4531 /* remember the distance, and item if it's closer */
4535 nNearestItem = nItem;
4542 nLast = min(nStart + 1, infoPtr->nItemCount);
4547 return nNearestItem;
4552 * Searches for an item with specific characteristics.
4555 * [I] hwnd : window handle
4556 * [I] nStart : base item index
4557 * [I] lpFindInfo : item information to look for
4560 * SUCCESS : index of item
4563 static INT LISTVIEW_FindItemA(LISTVIEW_INFO *infoPtr, INT nStart,
4564 const LVFINDINFOA *lpFindInfo)
4566 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
4570 memcpy(&fiw, lpFindInfo, sizeof(fiw));
4571 if (hasText) fiw.psz = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
4572 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
4573 if (hasText) textfreeT((LPWSTR)fiw.psz, FALSE);
4579 * Retrieves the background image of the listview control.
4582 * [I] infoPtr : valid pointer to the listview structure
4583 * [O] lpBkImage : background image attributes
4589 /* static BOOL LISTVIEW_GetBkImage(LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
4591 /* FIXME (listview, "empty stub!\n"); */
4597 * Retrieves column attributes.
4600 * [I] infoPtr : valid pointer to the listview structure
4601 * [I] nColumn : column index
4602 * [IO] lpColumn : column information
4603 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
4604 * otherwise it is in fact a LPLVCOLUMNA
4610 static BOOL LISTVIEW_GetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
4612 COLUMN_INFO *lpColumnInfo;
4615 if (!lpColumn || nColumn < 0 || nColumn >= infoPtr->hdpaColumns->nItemCount) return FALSE;
4616 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
4618 /* initialize memory */
4619 ZeroMemory(&hdi, sizeof(hdi));
4621 if (lpColumn->mask & LVCF_TEXT)
4623 hdi.mask |= HDI_TEXT;
4624 hdi.pszText = lpColumn->pszText;
4625 hdi.cchTextMax = lpColumn->cchTextMax;
4628 if (lpColumn->mask & LVCF_IMAGE)
4629 hdi.mask |= HDI_IMAGE;
4631 if (lpColumn->mask & LVCF_ORDER)
4632 hdi.mask |= HDI_ORDER;
4634 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
4636 if (lpColumn->mask & LVCF_FMT)
4637 lpColumn->fmt = lpColumnInfo->fmt;
4639 if (lpColumn->mask & LVCF_WIDTH)
4640 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
4642 if (lpColumn->mask & LVCF_IMAGE)
4643 lpColumn->iImage = hdi.iImage;
4645 if (lpColumn->mask & LVCF_ORDER)
4646 lpColumn->iOrder = hdi.iOrder;
4652 static BOOL LISTVIEW_GetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
4659 /* FIXME: little hack */
4660 for (i = 0; i < iCount; i++)
4668 * Retrieves the column width.
4671 * [I] infoPtr : valid pointer to the listview structure
4672 * [I] int : column index
4675 * SUCCESS : column width
4678 static INT LISTVIEW_GetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn)
4680 INT nColumnWidth = 0;
4683 TRACE("nColumn=%d\n", nColumn);
4685 /* we have a 'column' in LIST and REPORT mode only */
4686 switch(infoPtr->dwStyle & LVS_TYPEMASK)
4689 nColumnWidth = infoPtr->nItemWidth;
4692 if (nColumn < 0 || nColumn >= infoPtr->hdpaColumns->nItemCount) return 0;
4693 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
4694 nColumnWidth = rcHeader.right - rcHeader.left;
4698 TRACE("nColumnWidth=%d\n", nColumnWidth);
4699 return nColumnWidth;
4704 * In list or report display mode, retrieves the number of items that can fit
4705 * vertically in the visible area. In icon or small icon display mode,
4706 * retrieves the total number of visible items.
4709 * [I] infoPtr : valid pointer to the listview structure
4712 * Number of fully visible items.
4714 static INT LISTVIEW_GetCountPerPage(LISTVIEW_INFO *infoPtr)
4716 switch (infoPtr->dwStyle & LVS_TYPEMASK)
4720 return infoPtr->nItemCount;
4722 return LISTVIEW_GetCountPerColumn(infoPtr);
4724 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
4731 * Retrieves an image list handle.
4734 * [I] infoPtr : valid pointer to the listview structure
4735 * [I] nImageList : image list identifier
4738 * SUCCESS : image list handle
4741 static HIMAGELIST LISTVIEW_GetImageList(LISTVIEW_INFO *infoPtr, INT nImageList)
4745 case LVSIL_NORMAL: return infoPtr->himlNormal;
4746 case LVSIL_SMALL: return infoPtr->himlSmall;
4747 case LVSIL_STATE: return infoPtr->himlState;
4752 /* LISTVIEW_GetISearchString */
4756 * Retrieves item attributes.
4759 * [I] hwnd : window handle
4760 * [IO] lpLVItem : item info
4761 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
4762 * if FALSE, the lpLVItem is a LPLVITEMA.
4765 * This is the internal 'GetItem' interface -- it tries to
4766 * be smart, and avoids text copies, if possible, by modifing
4767 * lpLVItem->pszText to point to the text string. Please note
4768 * that this is not always possible (e.g. OWNERDATA), so on
4769 * entry you *must* supply valid values for pszText, and cchTextMax.
4770 * The only difference to the documented interface is that upon
4771 * return, you should use *only* the lpLVItem->pszText, rather than
4772 * the buffer pointer you provided on input. Most code already does
4773 * that, so it's not a problem.
4774 * For the two cases when the text must be copied (that is,
4775 * for LVM_GETITEM, and LVMGETITEMTEXT), use LISTVIEW_GetItemExtT.
4781 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
4783 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
4784 NMLVDISPINFOW dispInfo;
4789 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
4791 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
4794 if (lpLVItem->mask == 0) return TRUE;
4796 /* a quick optimization if all we're asked is the focus state
4797 * these queries are worth optimising since they are common,
4798 * and can be answered in constant time, without the heavy accesses */
4799 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
4800 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
4802 lpLVItem->state = 0;
4803 if (infoPtr->nFocusedItem == lpLVItem->iItem)
4804 lpLVItem->state |= LVIS_FOCUSED;
4808 ZeroMemory(&dispInfo, sizeof(dispInfo));
4810 /* if the app stores all the data, handle it separately */
4811 if (infoPtr->dwStyle & LVS_OWNERDATA)
4813 dispInfo.item.state = 0;
4815 /* apprently, we should not callback for lParam in LVS_OWNERDATA */
4816 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
4818 /* NOTE: copy only fields which we _know_ are initialized, some apps
4819 * depend on the uninitialized fields being 0 */
4820 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
4821 dispInfo.item.iItem = lpLVItem->iItem;
4822 dispInfo.item.iSubItem = lpLVItem->iSubItem;
4823 if (lpLVItem->mask & LVIF_TEXT)
4825 dispInfo.item.pszText = lpLVItem->pszText;
4826 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
4828 if (lpLVItem->mask & LVIF_STATE)
4829 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
4830 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
4831 dispInfo.item.stateMask = lpLVItem->stateMask;
4832 *lpLVItem = dispInfo.item;
4833 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
4836 /* make sure lParam is zeroed out */
4837 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
4839 /* we store only a little state, so if we're not asked, we're done */
4840 if (!(lpLVItem->mask & LVIF_STATE) || lpLVItem->iSubItem) return TRUE;
4842 /* if focus is handled by us, report it */
4843 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
4845 lpLVItem->state &= ~LVIS_FOCUSED;
4846 if (infoPtr->nFocusedItem == lpLVItem->iItem)
4847 lpLVItem->state |= LVIS_FOCUSED;
4850 /* and do the same for selection, if we handle it */
4851 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
4853 lpLVItem->state &= ~LVIS_SELECTED;
4854 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
4855 lpLVItem->state |= LVIS_SELECTED;
4861 /* find the item and subitem structures before we proceed */
4862 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4863 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
4866 if (lpLVItem->iSubItem)
4868 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
4869 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
4872 pItemHdr = &lpItem->hdr;
4874 /* Do we need to query the state from the app? */
4875 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && lpLVItem->iSubItem == 0)
4877 dispInfo.item.mask |= LVIF_STATE;
4878 dispInfo.item.stateMask = infoPtr->uCallbackMask;
4881 /* Do we need to enquire about the image? */
4882 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK)
4883 dispInfo.item.mask |= LVIF_IMAGE;
4885 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
4886 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
4888 dispInfo.item.mask |= LVIF_TEXT;
4889 dispInfo.item.pszText = lpLVItem->pszText;
4890 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
4891 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
4892 *dispInfo.item.pszText = '\0';
4895 /* If we don't have all the requested info, query the application */
4896 if (dispInfo.item.mask != 0)
4898 dispInfo.item.iItem = lpLVItem->iItem;
4899 dispInfo.item.iSubItem = lpLVItem->iSubItem;
4900 dispInfo.item.lParam = lpItem->lParam;
4901 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
4902 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
4905 /* we should not store values for subitems */
4906 if (lpLVItem->iSubItem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
4908 /* Now, handle the iImage field */
4909 if (dispInfo.item.mask & LVIF_IMAGE)
4911 lpLVItem->iImage = dispInfo.item.iImage;
4912 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
4913 pItemHdr->iImage = dispInfo.item.iImage;
4915 else if (lpLVItem->mask & LVIF_IMAGE)
4916 lpLVItem->iImage = pItemHdr->iImage;
4918 /* The pszText field */
4919 if (dispInfo.item.mask & LVIF_TEXT)
4921 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
4922 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
4924 lpLVItem->pszText = dispInfo.item.pszText;
4926 else if (lpLVItem->mask & LVIF_TEXT)
4928 if (isW) lpLVItem->pszText = pItemHdr->pszText;
4929 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
4932 /* if this is a subitem, we're done */
4933 if (lpLVItem->iSubItem) return TRUE;
4935 /* Next is the lParam field */
4936 if (dispInfo.item.mask & LVIF_PARAM)
4938 lpLVItem->lParam = dispInfo.item.lParam;
4939 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
4940 lpItem->lParam = dispInfo.item.lParam;
4942 else if (lpLVItem->mask & LVIF_PARAM)
4943 lpLVItem->lParam = lpItem->lParam;
4945 /* ... the state field (this one is different due to uCallbackmask) */
4946 if (lpLVItem->mask & LVIF_STATE)
4948 lpLVItem->state = lpItem->state;
4949 if (dispInfo.item.mask & LVIF_STATE)
4951 lpLVItem->state &= ~dispInfo.item.stateMask;
4952 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
4954 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
4956 lpLVItem->state &= ~LVIS_FOCUSED;
4957 if (infoPtr->nFocusedItem == lpLVItem->iItem)
4958 lpLVItem->state |= LVIS_FOCUSED;
4960 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
4962 lpLVItem->state &= ~LVIS_SELECTED;
4963 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
4964 lpLVItem->state |= LVIS_SELECTED;
4968 /* and last, but not least, the indent field */
4969 if (lpLVItem->mask & LVIF_INDENT)
4970 lpLVItem->iIndent = lpItem->iIndent;
4977 * Retrieves item attributes.
4980 * [I] hwnd : window handle
4981 * [IO] lpLVItem : item info
4982 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
4983 * if FALSE, the lpLVItem is a LPLVITEMA.
4986 * This is the external 'GetItem' interface -- it properly copies
4987 * the text in the provided buffer.
4993 static BOOL LISTVIEW_GetItemExtT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
4998 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5001 pszText = lpLVItem->pszText;
5002 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5003 if (bResult && lpLVItem->pszText != pszText)
5004 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5005 lpLVItem->pszText = pszText;
5013 * Retrieves the position (upper-left) of the listview control item.
5014 * Note that for LVS_ICON style, the upper-left is that of the icon
5015 * and not the bounding box.
5018 * [I] infoPtr : valid pointer to the listview structure
5019 * [I] nItem : item index
5020 * [O] lpptPosition : coordinate information
5026 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5028 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5031 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5033 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5035 LISTVIEW_GetOrigin(infoPtr, &Origin);
5036 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5038 if (uView == LVS_ICON)
5040 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5041 lpptPosition->y += ICON_TOP_PADDING;
5043 lpptPosition->x += Origin.x;
5044 lpptPosition->y += Origin.y;
5046 TRACE (" lpptPosition=%s\n", debugpoint(lpptPosition));
5053 * Retrieves the bounding rectangle for a listview control item.
5056 * [I] infoPtr : valid pointer to the listview structure
5057 * [I] nItem : item index
5058 * [IO] lprc : bounding rectangle coordinates
5059 * lprc->left specifies the portion of the item for which the bounding
5060 * rectangle will be retrieved.
5062 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5063 * including the icon and label.
5066 * * Experiment shows that native control returns:
5067 * * width = min (48, length of text line)
5068 * * .left = position.x - (width - iconsize.cx)/2
5069 * * .right = .left + width
5070 * * height = #lines of text * ntmHeight + icon height + 8
5071 * * .top = position.y - 2
5072 * * .bottom = .top + height
5073 * * separation between items .y = itemSpacing.cy - height
5074 * * .x = itemSpacing.cx - width
5075 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5078 * * Experiment shows that native control returns:
5079 * * width = iconSize.cx + 16
5080 * * .left = position.x - (width - iconsize.cx)/2
5081 * * .right = .left + width
5082 * * height = iconSize.cy + 4
5083 * * .top = position.y - 2
5084 * * .bottom = .top + height
5085 * * separation between items .y = itemSpacing.cy - height
5086 * * .x = itemSpacing.cx - width
5087 * LVIR_LABEL Returns the bounding rectangle of the item text.
5090 * * Experiment shows that native control returns:
5091 * * width = text length
5092 * * .left = position.x - width/2
5093 * * .right = .left + width
5094 * * height = ntmH * linecount + 2
5095 * * .top = position.y + iconSize.cy + 6
5096 * * .bottom = .top + height
5097 * * separation between items .y = itemSpacing.cy - height
5098 * * .x = itemSpacing.cx - width
5099 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5100 * rectangles, but excludes columns in report view.
5107 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5108 * upon whether the window has the focus currently and on whether the item
5109 * is the one with the focus. Ensure that the control's record of which
5110 * item has the focus agrees with the items' records.
5112 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5114 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5115 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5116 BOOL doLabel = TRUE, oversizedBox = FALSE;
5117 POINT Position, Origin;
5121 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5123 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5125 LISTVIEW_GetOrigin(infoPtr, &Origin);
5126 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5128 /* Be smart and try to figure out the minimum we have to do */
5129 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5130 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5131 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5132 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5133 oversizedBox = TRUE;
5135 /* get what we need from the item before hand, so we make
5136 * only one request. This can speed up things, if data
5137 * is stored on the app side */
5139 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5140 if (doLabel) lvItem.mask |= LVIF_TEXT;
5141 lvItem.iItem = nItem;
5142 lvItem.iSubItem = 0;
5143 lvItem.pszText = szDispText;
5144 lvItem.cchTextMax = DISP_TEXT_SIZE;
5145 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5146 /* we got the state already up, simulate it here, to avoid a reget */
5147 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5149 lvItem.mask |= LVIF_STATE;
5150 lvItem.stateMask = LVIS_FOCUSED;
5151 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5154 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5155 lprc->left = LVIR_BOUNDS;
5159 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5163 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, lprc);
5167 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5170 case LVIR_SELECTBOUNDS:
5171 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, &label_rect);
5172 UnionRect(lprc, lprc, &label_rect);
5176 WARN("Unknown value: %d\n", lprc->left);
5180 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5182 TRACE(" rect=%s\n", debugrect(lprc));
5189 * Retrieves the spacing between listview control items.
5192 * [I] infoPtr : valid pointer to the listview structure
5193 * [IO] lprc : rectangle to receive the output
5194 * on input, lprc->top = nSubItem
5195 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5197 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5198 * not only those of the first column.
5199 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5205 static BOOL LISTVIEW_GetSubItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5207 POINT Position, Origin;
5210 if (!lprc || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5212 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
5214 LISTVIEW_GetOrigin(infoPtr, &Origin);
5215 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5217 lvItem.mask = lprc->top == 0 ? LVIF_INDENT : 0;
5218 lvItem.iItem = nItem;
5219 lvItem.iSubItem = lprc->top;
5221 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5225 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5230 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5234 ERR("Unknown bounds=%d\n", lprc->left);
5238 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5245 * Retrieves the width of a label.
5248 * [I] infoPtr : valid pointer to the listview structure
5251 * SUCCESS : string width (in pixels)
5254 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *infoPtr, INT nItem)
5256 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5259 TRACE("(nItem=%d)\n", nItem);
5261 lvItem.mask = LVIF_TEXT;
5262 lvItem.iItem = nItem;
5263 lvItem.iSubItem = 0;
5264 lvItem.pszText = szDispText;
5265 lvItem.cchTextMax = DISP_TEXT_SIZE;
5266 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5268 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5273 * Retrieves the spacing between listview control items.
5276 * [I] infoPtr : valid pointer to the listview structure
5277 * [I] bSmall : flag for small or large icon
5280 * Horizontal + vertical spacing
5282 static LONG LISTVIEW_GetItemSpacing(LISTVIEW_INFO *infoPtr, BOOL bSmall)
5288 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5292 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5293 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5295 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5302 * Retrieves the state of a listview control item.
5305 * [I] infoPtr : valid pointer to the listview structure
5306 * [I] nItem : item index
5307 * [I] uMask : state mask
5310 * State specified by the mask.
5312 static UINT LISTVIEW_GetItemState(LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5316 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5318 lvItem.iItem = nItem;
5319 lvItem.iSubItem = 0;
5320 lvItem.mask = LVIF_STATE;
5321 lvItem.stateMask = uMask;
5322 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5324 return lvItem.state & uMask;
5329 * Retrieves the text of a listview control item or subitem.
5332 * [I] hwnd : window handle
5333 * [I] nItem : item index
5334 * [IO] lpLVItem : item information
5335 * [I] isW : TRUE if lpLVItem is Unicode
5338 * SUCCESS : string length
5341 static INT LISTVIEW_GetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
5343 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5345 lpLVItem->mask = LVIF_TEXT;
5346 lpLVItem->iItem = nItem;
5347 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
5349 return textlenT(lpLVItem->pszText, isW);
5354 * Searches for an item based on properties + relationships.
5357 * [I] infoPtr : valid pointer to the listview structure
5358 * [I] nItem : item index
5359 * [I] uFlags : relationship flag
5362 * SUCCESS : item index
5365 static INT LISTVIEW_GetNextItem(LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
5367 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5369 LVFINDINFOW lvFindInfo;
5370 INT nCountPerColumn;
5373 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
5374 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
5376 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
5378 if (uFlags & LVNI_CUT)
5381 if (uFlags & LVNI_DROPHILITED)
5382 uMask |= LVIS_DROPHILITED;
5384 if (uFlags & LVNI_FOCUSED)
5385 uMask |= LVIS_FOCUSED;
5387 if (uFlags & LVNI_SELECTED)
5388 uMask |= LVIS_SELECTED;
5390 /* if we're asked for the focused item, that's only one,
5391 * so it's worth optimizing */
5392 if (uFlags & LVNI_FOCUSED)
5394 if (!(LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) == uMask) return -1;
5395 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
5398 if (uFlags & LVNI_ABOVE)
5400 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5405 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5411 lvFindInfo.flags = LVFI_NEARESTXY;
5412 lvFindInfo.vkDirection = VK_UP;
5413 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5414 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5416 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5421 else if (uFlags & LVNI_BELOW)
5423 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5425 while (nItem < infoPtr->nItemCount)
5428 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5434 lvFindInfo.flags = LVFI_NEARESTXY;
5435 lvFindInfo.vkDirection = VK_DOWN;
5436 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5437 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5439 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5444 else if (uFlags & LVNI_TOLEFT)
5446 if (uView == LVS_LIST)
5448 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5449 while (nItem - nCountPerColumn >= 0)
5451 nItem -= nCountPerColumn;
5452 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5456 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5458 lvFindInfo.flags = LVFI_NEARESTXY;
5459 lvFindInfo.vkDirection = VK_LEFT;
5460 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5461 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5463 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5468 else if (uFlags & LVNI_TORIGHT)
5470 if (uView == LVS_LIST)
5472 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5473 while (nItem + nCountPerColumn < infoPtr->nItemCount)
5475 nItem += nCountPerColumn;
5476 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5480 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5482 lvFindInfo.flags = LVFI_NEARESTXY;
5483 lvFindInfo.vkDirection = VK_RIGHT;
5484 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5485 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5487 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5496 /* search by index */
5497 for (i = nItem; i < infoPtr->nItemCount; i++)
5499 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
5507 /* LISTVIEW_GetNumberOfWorkAreas */
5511 * Retrieves the origin coordinates when in icon or small icon display mode.
5514 * [I] infoPtr : valid pointer to the listview structure
5515 * [O] lpptOrigin : coordinate information
5520 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
5522 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5523 INT nHorzPos = 0, nVertPos = 0;
5524 SCROLLINFO scrollInfo;
5526 scrollInfo.cbSize = sizeof(SCROLLINFO);
5527 scrollInfo.fMask = SIF_POS;
5529 if ((infoPtr->dwStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
5530 nHorzPos = scrollInfo.nPos;
5531 if ((infoPtr->dwStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
5532 nVertPos = scrollInfo.nPos;
5534 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
5536 lpptOrigin->x = infoPtr->rcList.left;
5537 lpptOrigin->y = infoPtr->rcList.top;
5538 if (uView == LVS_LIST)
5539 nHorzPos *= infoPtr->nItemWidth;
5540 else if (uView == LVS_REPORT)
5541 nVertPos *= infoPtr->nItemHeight;
5543 lpptOrigin->x -= nHorzPos;
5544 lpptOrigin->y -= nVertPos;
5546 TRACE(" origin=%s\n", debugpoint(lpptOrigin));
5551 * Retrieves the width of a string.
5554 * [I] hwnd : window handle
5555 * [I] lpszText : text string to process
5556 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
5559 * SUCCESS : string width (in pixels)
5562 static INT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
5567 if (is_textT(lpszText, isW))
5569 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
5570 HDC hdc = GetDC(infoPtr->hwndSelf);
5571 HFONT hOldFont = SelectObject(hdc, hFont);
5574 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
5576 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
5577 SelectObject(hdc, hOldFont);
5578 ReleaseDC(infoPtr->hwndSelf, hdc);
5580 return stringSize.cx;
5585 * Determines which listview item is located at the specified position.
5588 * [I] infoPtr : valid pointer to the listview structure
5589 * [IO] lpht : hit test information
5590 * [I] subitem : fill out iSubItem.
5591 * [I] select : return the index only if the hit selects the item
5594 * (mm 20001022): We must not allow iSubItem to be touched, for
5595 * an app might pass only a structure with space up to iItem!
5596 * (MS Office 97 does that for instance in the file open dialog)
5599 * SUCCESS : item index
5602 static INT LISTVIEW_HitTest(LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
5604 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5605 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5606 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
5607 POINT Origin, Position, opt;
5611 TRACE("(pt=%s, subitem=%d, select=%d)\n", debugpoint(&lpht->pt), subitem, select);
5615 if (subitem) lpht->iSubItem = 0;
5617 if (infoPtr->rcList.left > lpht->pt.x)
5618 lpht->flags |= LVHT_TOLEFT;
5619 else if (infoPtr->rcList.right < lpht->pt.x)
5620 lpht->flags |= LVHT_TORIGHT;
5622 if (infoPtr->rcList.top > lpht->pt.y)
5623 lpht->flags |= LVHT_ABOVE;
5624 else if (infoPtr->rcList.bottom < lpht->pt.y)
5625 lpht->flags |= LVHT_BELOW;
5627 TRACE("lpht->flags=0x%x\n", lpht->flags);
5628 if (lpht->flags) return -1;
5630 lpht->flags |= LVHT_NOWHERE;
5632 LISTVIEW_GetOrigin(infoPtr, &Origin);
5634 /* first deal with the large items */
5635 rcSearch.left = lpht->pt.x;
5636 rcSearch.top = lpht->pt.y;
5637 rcSearch.right = rcSearch.left + 1;
5638 rcSearch.bottom = rcSearch.top + 1;
5640 iterator_frameditems(&i, infoPtr, &rcSearch);
5641 iterator_next(&i); /* go to first item in the sequence */
5642 lpht->iItem = i.nItem;
5643 iterator_destroy(&i);
5645 TRACE("lpht->iItem=%d\n", lpht->iItem);
5646 if (lpht->iItem == -1) return -1;
5648 lvItem.mask = LVIF_STATE | LVIF_TEXT;
5649 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5650 lvItem.stateMask = LVIS_STATEIMAGEMASK;
5651 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
5652 lvItem.iItem = lpht->iItem;
5653 lvItem.iSubItem = 0;
5654 lvItem.pszText = szDispText;
5655 lvItem.cchTextMax = DISP_TEXT_SIZE;
5656 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
5657 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
5659 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
5660 LISTVIEW_GetItemOrigin(infoPtr, lpht->iItem, &Position);
5661 opt.x = lpht->pt.x - Position.x - Origin.x;
5662 opt.y = lpht->pt.y - Position.y - Origin.y;
5664 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
5667 UnionRect(&rcBounds, &rcIcon, &rcLabel);
5668 TRACE("rcBounds=%s\n", debugrect(&rcBounds));
5669 if (!PtInRect(&rcBounds, opt)) return -1;
5671 if (PtInRect(&rcIcon, opt))
5672 lpht->flags |= LVHT_ONITEMICON;
5673 else if (PtInRect(&rcLabel, opt))
5674 lpht->flags |= LVHT_ONITEMLABEL;
5675 else if (infoPtr->himlState && ((lvItem.state & LVIS_STATEIMAGEMASK) >> 12) && PtInRect(&rcState, opt))
5676 lpht->flags |= LVHT_ONITEMSTATEICON;
5677 if (lpht->flags & LVHT_ONITEM)
5678 lpht->flags &= ~LVHT_NOWHERE;
5680 TRACE("lpht->flags=0x%x\n", lpht->flags);
5681 if (uView == LVS_REPORT && lpht->iItem != -1 && subitem)
5685 rcBounds.right = rcBounds.left;
5686 for (j = 0; j < infoPtr->hdpaColumns->nItemCount; j++)
5688 rcBounds.left = rcBounds.right;
5689 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
5690 if (PtInRect(&rcBounds, opt))
5698 if (!select || lpht->iItem == -1) return lpht->iItem;
5700 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)) return lpht->iItem;
5702 if (uView == LVS_REPORT) UnionRect(&rcBounds, &rcIcon, &rcLabel);
5703 return PtInRect(&rcBounds, opt) ? lpht->iItem : -1;
5707 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
5708 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
5709 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
5710 and not during the processing of a LVM_SORTITEMS message. Applications should provide
5711 their own sort proc. when sending LVM_SORTITEMS.
5714 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
5716 LVS_SORTXXX must be specified,
5717 LVS_OWNERDRAW is not set,
5718 <item>.pszText is not LPSTR_TEXTCALLBACK.
5720 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
5721 are sorted based on item text..."
5723 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
5725 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
5726 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
5727 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
5729 /* if we're sorting descending, negate the return value */
5730 return (((LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
5735 * Inserts a new item in the listview control.
5738 * [I] infoPtr : valid pointer to the listview structure
5739 * [I] lpLVItem : item information
5740 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
5743 * SUCCESS : new item index
5746 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
5748 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5753 BOOL is_sorted, has_changed;
5756 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5758 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
5760 /* make sure it's an item, and not a subitem; cannot insert a subitem */
5761 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iSubItem) return -1;
5763 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
5765 if ( !(lpItem = (ITEM_INFO *)COMCTL32_Alloc(sizeof(ITEM_INFO))) )
5768 /* insert item in listview control data structure */
5769 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
5770 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
5772 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
5773 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
5775 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
5776 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
5777 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
5778 if (nItem == -1) goto fail;
5779 infoPtr->nItemCount++;
5781 /* set the item attributes */
5784 item.state &= ~LVIS_STATEIMAGEMASK;
5785 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
5787 /* if we're sorted, sort the list, and update the index */
5790 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
5791 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
5792 assert(nItem != -1);
5795 /* make room for the position, if we are in the right mode */
5796 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5798 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
5800 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
5802 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
5807 /* Add the subitem list to the items array. Do this last in case we go to
5808 * fail during the above.
5810 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
5812 /* send LVN_INSERTITEM notification */
5813 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
5815 nmlv.lParam = lpItem->lParam;
5816 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
5818 /* align items (set position of each item) */
5819 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
5823 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
5824 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
5826 LISTVIEW_NextIconPosTop(infoPtr, &pt);
5828 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
5831 /* now is the invalidation fun */
5832 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
5836 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
5837 infoPtr->nItemCount--;
5839 DPA_DeletePtr(hdpaSubItems, 0);
5840 DPA_Destroy (hdpaSubItems);
5841 COMCTL32_Free (lpItem);
5847 * Redraws a range of items.
5850 * [I] infoPtr : valid pointer to the listview structure
5851 * [I] nFirst : first item
5852 * [I] nLast : last item
5858 static BOOL LISTVIEW_RedrawItems(LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
5862 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
5863 max(nFirst, nLast) >= infoPtr->nItemCount)
5866 for (i = nFirst; i <= nLast; i++)
5867 LISTVIEW_InvalidateItem(infoPtr, i);
5874 * Scroll the content of a listview.
5877 * [I] infoPtr : valid pointer to the listview structure
5878 * [I] dx : horizontal scroll amount in pixels
5879 * [I] dy : vertical scroll amount in pixels
5886 * If the control is in report mode (LVS_REPORT) the control can
5887 * be scrolled only in line increments. "dy" will be rounded to the
5888 * nearest number of pixels that are a whole line. Ex: if line height
5889 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
5890 * is passed the the scroll will be 0. (per MSDN 7/2002)
5892 * For: (per experimentaion with native control and CSpy ListView)
5893 * LVS_ICON dy=1 = 1 pixel (vertical only)
5895 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
5897 * LVS_LIST dx=1 = 1 column (horizontal only)
5898 * but will only scroll 1 column per message
5899 * no matter what the value.
5900 * dy must be 0 or FALSE returned.
5901 * LVS_REPORT dx=1 = 1 pixel
5905 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
5907 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
5909 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
5910 dy /= infoPtr->nItemHeight;
5913 if (dy != 0) return FALSE;
5920 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
5921 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
5928 * Sets the background color.
5931 * [I] infoPtr : valid pointer to the listview structure
5932 * [I] clrBk : background color
5938 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
5940 TRACE("(clrBk=%lx)\n", clrBk);
5942 if(infoPtr->clrBk != clrBk) {
5943 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
5944 infoPtr->clrBk = clrBk;
5945 if (clrBk == CLR_NONE)
5946 infoPtr->hBkBrush = (HBRUSH)GetClassLongW(infoPtr->hwndSelf, GCL_HBRBACKGROUND);
5948 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
5949 LISTVIEW_InvalidateList(infoPtr);
5955 /* LISTVIEW_SetBkImage */
5957 /*** Helper for {Insert,Set}ColumnT *only* */
5958 static void column_fill_hditem(LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn, const LVCOLUMNW *lpColumn, BOOL isW)
5960 if (lpColumn->mask & LVCF_FMT)
5962 /* format member is valid */
5963 lphdi->mask |= HDI_FORMAT;
5965 /* set text alignment (leftmost column must be left-aligned) */
5966 if (nColumn == 0 || lpColumn->fmt & LVCFMT_LEFT)
5967 lphdi->fmt |= HDF_LEFT;
5968 else if (lpColumn->fmt & LVCFMT_RIGHT)
5969 lphdi->fmt |= HDF_RIGHT;
5970 else if (lpColumn->fmt & LVCFMT_CENTER)
5971 lphdi->fmt |= HDF_CENTER;
5973 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
5974 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
5976 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
5978 lphdi->fmt |= HDF_IMAGE;
5979 lphdi->iImage = I_IMAGECALLBACK;
5983 if (lpColumn->mask & LVCF_WIDTH)
5985 lphdi->mask |= HDI_WIDTH;
5986 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
5988 /* make it fill the remainder of the controls width */
5992 for(item_index = 0; item_index < (nColumn - 1); item_index++)
5994 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
5995 lphdi->cxy += rcHeader.right - rcHeader.left;
5998 /* retrieve the layout of the header */
5999 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6000 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, debugrect(&rcHeader));
6002 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6005 lphdi->cxy = lpColumn->cx;
6008 if (lpColumn->mask & LVCF_TEXT)
6010 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6011 lphdi->fmt |= HDF_STRING;
6012 lphdi->pszText = lpColumn->pszText;
6013 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6016 if (lpColumn->mask & LVCF_IMAGE)
6018 lphdi->mask |= HDI_IMAGE;
6019 lphdi->iImage = lpColumn->iImage;
6022 if (lpColumn->mask & LVCF_ORDER)
6024 lphdi->mask |= HDI_ORDER;
6025 lphdi->iOrder = lpColumn->iOrder;
6032 * Inserts a new column.
6035 * [I] infoPtr : valid pointer to the listview structure
6036 * [I] nColumn : column index
6037 * [I] lpColumn : column information
6038 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6041 * SUCCESS : new column index
6044 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6045 const LVCOLUMNW *lpColumn, BOOL isW)
6047 COLUMN_INFO *lpColumnInfo;
6051 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6053 if (!lpColumn || nColumn < 0) return -1;
6054 nColumn = min(nColumn, infoPtr->hdpaColumns->nItemCount);
6056 ZeroMemory(&hdi, sizeof(HDITEMW));
6057 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6059 /* insert item in header control */
6060 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6061 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6062 (WPARAM)nColumn, (LPARAM)&hdi);
6063 if (nNewColumn == -1) return -1;
6064 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6066 /* create our own column info */
6067 if (!(lpColumnInfo = COMCTL32_Alloc(sizeof(COLUMN_INFO)))) goto fail;
6068 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6070 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6071 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6073 /* now we have to actually adjust the data */
6074 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6076 SUBITEM_INFO *lpSubItem, *lpMainItem, **lpNewItems = 0;
6080 /* preallocate memory, so we can fail gracefully */
6081 if (nNewColumn == 0)
6083 lpNewItems = COMCTL32_Alloc(sizeof(SUBITEM_INFO *) * infoPtr->nItemCount);
6084 if (!lpNewItems) goto fail;
6085 for (i = 0; i < infoPtr->nItemCount; i++)
6086 if (!(lpNewItems[i] = COMCTL32_Alloc(sizeof(SUBITEM_INFO)))) break;
6087 if (i != infoPtr->nItemCount)
6089 for(; i >=0; i--) COMCTL32_Free(lpNewItems[i]);
6090 COMCTL32_Free(lpNewItems);
6095 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6097 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
6098 for (i = 1; i < hdpaSubItems->nItemCount; i++)
6100 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
6101 if (lpSubItem->iSubItem >= nNewColumn)
6102 lpSubItem->iSubItem++;
6105 /* for inserting column 0, we have to special-case the main item */
6106 if (nNewColumn == 0)
6108 lpMainItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
6109 lpSubItem = lpNewItems[nItem];
6110 lpSubItem->hdr = lpMainItem->hdr;
6111 lpSubItem->iSubItem = 1;
6112 ZeroMemory(&lpMainItem->hdr, sizeof(lpMainItem->hdr));
6113 lpMainItem->iSubItem = 0;
6114 DPA_InsertPtr(hdpaSubItems, 1, lpSubItem);
6118 COMCTL32_Free(lpNewItems);
6121 /* make space for the new column */
6122 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6127 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6130 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6131 COMCTL32_Free(lpColumnInfo);
6138 * Sets the attributes of a header item.
6141 * [I] infoPtr : valid pointer to the listview structure
6142 * [I] nColumn : column index
6143 * [I] lpColumn : column attributes
6144 * [I] isW: if TRUE, the lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6150 static BOOL LISTVIEW_SetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6151 const LVCOLUMNW *lpColumn, BOOL isW)
6153 HDITEMW hdi, hdiget;
6156 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6158 if (!lpColumn || nColumn < 0 || nColumn >= infoPtr->hdpaColumns->nItemCount) return FALSE;
6160 ZeroMemory(&hdi, sizeof(HDITEMW));
6161 if (lpColumn->mask & LVCF_FMT)
6163 hdi.mask |= HDI_FORMAT;
6164 hdiget.mask = HDI_FORMAT;
6165 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6166 hdi.fmt = hdiget.fmt & HDF_STRING;
6168 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6170 /* set header item attributes */
6171 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6172 if (!bResult) return FALSE;
6174 if (lpColumn->mask & LVCF_FMT)
6176 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6177 int oldFmt = lpColumnInfo->fmt;
6179 lpColumnInfo->fmt = lpColumn->fmt;
6180 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
6182 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6183 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
6192 * Sets the column order array
6195 * [I] infoPtr : valid pointer to the listview structure
6196 * [I] iCount : number of elements in column order array
6197 * [I] lpiArray : pointer to column order array
6203 static BOOL LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
6205 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6216 * Sets the width of a column
6219 * [I] infoPtr : valid pointer to the listview structure
6220 * [I] nColumn : column index
6221 * [I] cx : column width
6227 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
6229 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6230 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6234 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
6236 /* set column width only if in report or list mode */
6237 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6239 /* take care of invalid cx values */
6240 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
6241 else if (uView == LVS_LIST && cx < 1) return FALSE;
6243 /* resize all columns if in LVS_LIST mode */
6244 if(uView == LVS_LIST)
6246 infoPtr->nItemWidth = cx;
6247 LISTVIEW_InvalidateList(infoPtr);
6251 if (nColumn < 0 || nColumn >= infoPtr->hdpaColumns->nItemCount) return FALSE;
6253 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < infoPtr->hdpaColumns->nItemCount -1))
6258 lvItem.mask = LVIF_TEXT;
6260 lvItem.iSubItem = nColumn;
6261 lvItem.pszText = szDispText;
6262 lvItem.cchTextMax = DISP_TEXT_SIZE;
6263 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6265 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6266 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6267 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
6269 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
6270 max_cx += infoPtr->iconSize.cx + IMAGE_PADDING;
6271 max_cx += REPORT_MARGINX + TRAILING_PADDING;
6274 /* autosize based on listview items width */
6275 if(cx == LVSCW_AUTOSIZE)
6277 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
6279 /* if iCol is the last column make it fill the remainder of the controls width */
6280 if(nColumn == infoPtr->hdpaColumns->nItemCount - 1)
6285 LISTVIEW_GetOrigin(infoPtr, &Origin);
6286 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
6288 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
6292 /* Despite what the MS docs say, if this is not the last
6293 column, then MS resizes the column to the width of the
6294 largest text string in the column, including headers
6295 and items. This is different from LVSCW_AUTOSIZE in that
6296 LVSCW_AUTOSIZE ignores the header string length. */
6299 /* retrieve header text */
6300 hdi.mask = HDI_TEXT;
6301 hdi.cchTextMax = DISP_TEXT_SIZE;
6302 hdi.pszText = szDispText;
6303 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, (LPARAM)&hdi))
6305 HDC hdc = GetDC(infoPtr->hwndSelf);
6306 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
6309 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
6311 /* FIXME: Take into account the header image, if one is present */
6312 SelectObject(hdc, old_font);
6313 ReleaseDC(infoPtr->hwndSelf, hdc);
6315 cx = max (cx, max_cx);
6319 if (cx < 0) return FALSE;
6321 /* call header to update the column change */
6322 hdi.mask = HDI_WIDTH;
6324 TRACE("hdi.cxy=%d\n", hdi.cxy);
6325 return Header_SetItemW(infoPtr->hwndHeader, nColumn, (LPARAM)&hdi);
6330 * Sets the extended listview style.
6333 * [I] infoPtr : valid pointer to the listview structure
6335 * [I] dwStyle : style
6338 * SUCCESS : previous style
6341 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwStyle)
6343 DWORD dwOldStyle = infoPtr->dwLvExStyle;
6347 infoPtr->dwLvExStyle = (dwOldStyle & ~dwMask) | (dwStyle & dwMask);
6349 infoPtr->dwLvExStyle = dwStyle;
6356 * Sets the new hot cursor used during hot tracking and hover selection.
6359 * [I] infoPtr : valid pointer to the listview structure
6360 * [I} hCurosr : the new hot cursor handle
6363 * Returns the previous hot cursor
6365 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
6367 HCURSOR oldCursor = infoPtr->hHotCursor;
6369 infoPtr->hHotCursor = hCursor;
6377 * Sets the hot item index.
6380 * [I] infoPtr : valid pointer to the listview structure
6381 * [I] iIndex : index
6384 * SUCCESS : previous hot item index
6385 * FAILURE : -1 (no hot item)
6387 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
6389 INT iOldIndex = infoPtr->nHotItem;
6391 infoPtr->nHotItem = iIndex;
6399 * Sets the amount of time the cursor must hover over an item before it is selected.
6402 * [I] infoPtr : valid pointer to the listview structure
6403 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
6406 * Returns the previous hover time
6408 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
6410 DWORD oldHoverTime = infoPtr->dwHoverTime;
6412 infoPtr->dwHoverTime = dwHoverTime;
6414 return oldHoverTime;
6419 * Sets spacing for icons of LVS_ICON style.
6422 * [I] infoPtr : valid pointer to the listview structure
6423 * [I] spacing : MAKELONG(cx, cy)
6426 * MAKELONG(oldcx, oldcy)
6428 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, DWORD spacing)
6430 INT cy = HIWORD(spacing), cx = LOWORD(spacing);
6431 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6432 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6434 TRACE("requested=(%d,%d)\n", cx, cy);
6436 /* this is supported only for LVS_ICON style */
6437 if (uView != LVS_ICON) return oldspacing;
6439 /* set to defaults, if instructed to */
6440 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
6441 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
6443 /* if 0 then compute width
6444 * FIXME: Should scan each item and determine max width of
6445 * icon or label, then make that the width */
6447 cx = infoPtr->iconSpacing.cx;
6449 /* if 0 then compute height */
6451 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
6452 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
6455 infoPtr->iconSpacing.cx = cx;
6456 infoPtr->iconSpacing.cy = cy;
6458 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%ld,%ld), ntmH=%d\n",
6459 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
6460 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
6461 infoPtr->ntmHeight);
6463 /* these depend on the iconSpacing */
6464 LISTVIEW_UpdateItemSize(infoPtr);
6469 inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
6473 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
6480 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
6481 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
6490 * [I] infoPtr : valid pointer to the listview structure
6491 * [I] nType : image list type
6492 * [I] himl : image list handle
6495 * SUCCESS : old image list
6498 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
6500 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6501 INT oldHeight = infoPtr->nItemHeight;
6502 HIMAGELIST himlOld = 0;
6504 TRACE("(nType=%d, himl=%p\n", nType, himl);
6509 himlOld = infoPtr->himlNormal;
6510 infoPtr->himlNormal = himl;
6511 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
6512 LISTVIEW_SetIconSpacing(infoPtr, 0);
6516 himlOld = infoPtr->himlSmall;
6517 infoPtr->himlSmall = himl;
6518 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
6522 himlOld = infoPtr->himlState;
6523 infoPtr->himlState = himl;
6524 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
6525 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
6529 ERR("Unknown icon type=%d\n", nType);
6533 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
6534 if (infoPtr->nItemHeight != oldHeight)
6535 LISTVIEW_UpdateScroll(infoPtr);
6542 * Preallocates memory (does *not* set the actual count of items !)
6545 * [I] infoPtr : valid pointer to the listview structure
6546 * [I] nItems : item count (projected number of items to allocate)
6547 * [I] dwFlags : update flags
6553 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
6555 TRACE("(nItems=%d, dwFlags=%lx)\n", nItems, dwFlags);
6557 if (infoPtr->dwStyle & LVS_OWNERDATA)
6559 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6560 INT nOldCount = infoPtr->nItemCount;
6562 if (nItems < nOldCount)
6564 RANGE range = { nItems, nOldCount };
6565 ranges_del(infoPtr->selectionRanges, range);
6566 if (infoPtr->nFocusedItem >= nItems)
6568 infoPtr->nFocusedItem = -1;
6569 SetRectEmpty(&infoPtr->rcFocus);
6573 infoPtr->nItemCount = nItems;
6574 LISTVIEW_UpdateScroll(infoPtr);
6576 /* the flags are valid only in ownerdata report and list modes */
6577 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
6579 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
6580 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
6582 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
6583 LISTVIEW_InvalidateList(infoPtr);
6590 LISTVIEW_GetOrigin(infoPtr, &Origin);
6591 nFrom = min(nOldCount, nItems);
6592 nTo = max(nOldCount, nItems);
6594 if (uView == LVS_REPORT)
6597 rcErase.top = nFrom * infoPtr->nItemHeight;
6598 rcErase.right = infoPtr->nItemWidth;
6599 rcErase.bottom = nTo * infoPtr->nItemHeight;
6600 OffsetRect(&rcErase, Origin.x, Origin.y);
6601 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6602 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6606 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
6608 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
6609 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
6610 rcErase.right = rcErase.left + infoPtr->nItemWidth;
6611 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
6612 OffsetRect(&rcErase, Origin.x, Origin.y);
6613 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6614 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6616 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
6618 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
6619 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
6620 OffsetRect(&rcErase, Origin.x, Origin.y);
6621 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6622 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6628 /* According to MSDN for non-LVS_OWNERDATA this is just
6629 * a performance issue. The control allocates its internal
6630 * data structures for the number of items specified. It
6631 * cuts down on the number of memory allocations. Therefore
6632 * we will just issue a WARN here
6634 WARN("for non-ownerdata performance option not implemented.\n");
6642 * Sets the position of an item.
6645 * [I] infoPtr : valid pointer to the listview structure
6646 * [I] nItem : item index
6647 * [I] pt : coordinate
6653 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
6655 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6658 TRACE("(nItem=%d, &pt=%s\n", nItem, debugpoint(&pt));
6660 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
6661 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
6663 LISTVIEW_GetOrigin(infoPtr, &Origin);
6665 /* This point value seems to be an undocumented feature.
6666 * The best guess is that it means either at the origin,
6667 * or at true beginning of the list. I will assume the origin. */
6668 if ((pt.x == -1) && (pt.y == -1))
6671 if (uView == LVS_ICON)
6673 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
6674 pt.y -= ICON_TOP_PADDING;
6679 infoPtr->bAutoarrange = FALSE;
6681 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
6686 * Sets the state of one or many items.
6689 * [I] infoPtr : valid pointer to the listview structure
6690 * [I] nItem : item index
6691 * [I] lpLVItem : item or subitem info
6697 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
6699 BOOL bResult = TRUE;
6702 lvItem.iItem = nItem;
6703 lvItem.iSubItem = 0;
6704 lvItem.mask = LVIF_STATE;
6705 lvItem.state = lpLVItem->state;
6706 lvItem.stateMask = lpLVItem->stateMask;
6707 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
6711 /* apply to all items */
6712 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6713 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
6716 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
6723 * Sets the text of an item or subitem.
6726 * [I] hwnd : window handle
6727 * [I] nItem : item index
6728 * [I] lpLVItem : item or subitem info
6729 * [I] isW : TRUE if input is Unicode
6735 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
6739 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
6741 lvItem.iItem = nItem;
6742 lvItem.iSubItem = lpLVItem->iSubItem;
6743 lvItem.mask = LVIF_TEXT;
6744 lvItem.pszText = lpLVItem->pszText;
6745 lvItem.cchTextMax = lpLVItem->cchTextMax;
6747 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
6749 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
6754 * Set item index that marks the start of a multiple selection.
6757 * [I] infoPtr : valid pointer to the listview structure
6758 * [I] nIndex : index
6761 * Index number or -1 if there is no selection mark.
6763 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
6765 INT nOldIndex = infoPtr->nSelectionMark;
6767 TRACE("(nIndex=%d)\n", nIndex);
6769 infoPtr->nSelectionMark = nIndex;
6776 * Sets the text background color.
6779 * [I] infoPtr : valid pointer to the listview structure
6780 * [I] clrTextBk : text background color
6786 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
6788 TRACE("(clrTextBk=%lx)\n", clrTextBk);
6790 if (infoPtr->clrTextBk != clrTextBk)
6792 infoPtr->clrTextBk = clrTextBk;
6793 LISTVIEW_InvalidateList(infoPtr);
6801 * Sets the text foreground color.
6804 * [I] infoPtr : valid pointer to the listview structure
6805 * [I] clrText : text color
6811 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
6813 TRACE("(clrText=%lx)\n", clrText);
6815 if (infoPtr->clrText != clrText)
6817 infoPtr->clrText = clrText;
6818 LISTVIEW_InvalidateList(infoPtr);
6824 /* LISTVIEW_SetToolTips */
6825 /* LISTVIEW_SetUnicodeFormat */
6826 /* LISTVIEW_SetWorkAreas */
6830 * Callback internally used by LISTVIEW_SortItems()
6833 * [I] first : pointer to first ITEM_INFO to compare
6834 * [I] second : pointer to second ITEM_INFO to compare
6835 * [I] lParam : HWND of control
6838 * if first comes before second : negative
6839 * if first comes after second : positive
6840 * if first and second are equivalent : zero
6842 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
6844 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW((HWND)lParam, 0);
6845 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
6846 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
6848 /* Forward the call to the client defined callback */
6849 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
6854 * Sorts the listview items.
6857 * [I] infoPtr : valid pointer to the listview structure
6858 * [I] pfnCompare : application-defined value
6859 * [I] lParamSort : pointer to comparision callback
6865 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
6867 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6870 LPVOID selectionMarkItem;
6874 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
6876 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
6878 if (!infoPtr->hdpaItems) return FALSE;
6880 /* if there are 0 or 1 items, there is no need to sort */
6881 if (infoPtr->nItemCount < 2) return TRUE;
6883 if (infoPtr->nFocusedItem >= 0)
6885 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
6886 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
6887 if (lpItem) lpItem->state |= LVIS_FOCUSED;
6889 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
6890 /* clear the lpItem->state for non-selected ones */
6891 /* remove the selection ranges */
6893 infoPtr->pfnCompare = pfnCompare;
6894 infoPtr->lParamSort = lParamSort;
6895 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr->hwndSelf);
6897 /* Adjust selections and indices so that they are the way they should
6898 * be after the sort (otherwise, the list items move around, but
6899 * whatever is at the item's previous original position will be
6902 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
6903 for (i=0; i < infoPtr->nItemCount; i++)
6905 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
6906 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
6908 if (lpItem->state & LVIS_SELECTED)
6910 item.state = LVIS_SELECTED;
6911 item.stateMask = LVIS_SELECTED;
6912 LISTVIEW_SetItemState(infoPtr, i, &item);
6914 if (lpItem->state & LVIS_FOCUSED)
6916 infoPtr->nFocusedItem = i;
6917 lpItem->state &= ~LVIS_FOCUSED;
6920 if (selectionMarkItem != NULL)
6921 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
6922 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
6924 /* refresh the display */
6925 if (uView != LVS_ICON && uView != LVS_SMALLICON)
6926 LISTVIEW_InvalidateList(infoPtr);
6933 * Updates an items or rearranges the listview control.
6936 * [I] infoPtr : valid pointer to the listview structure
6937 * [I] nItem : item index
6943 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
6945 TRACE("(nItem=%d)\n", nItem);
6947 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
6949 /* rearrange with default alignment style */
6950 if (is_autoarrange(infoPtr))
6951 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
6953 LISTVIEW_InvalidateItem(infoPtr, nItem);
6961 * Creates the listview control.
6964 * [I] hwnd : window handle
6965 * [I] lpcs : the create parameters
6971 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
6973 LISTVIEW_INFO *infoPtr;
6974 UINT uView = lpcs->style & LVS_TYPEMASK;
6977 TRACE("(lpcs=%p)\n", lpcs);
6979 /* initialize info pointer */
6980 infoPtr = (LISTVIEW_INFO *)COMCTL32_Alloc(sizeof(LISTVIEW_INFO));
6981 if (!infoPtr) return -1;
6983 SetWindowLongW(hwnd, 0, (LONG)infoPtr);
6985 infoPtr->hwndSelf = hwnd;
6986 infoPtr->dwStyle = lpcs->style;
6987 /* determine the type of structures to use */
6988 infoPtr->notifyFormat = SendMessageW(GetParent(infoPtr->hwndSelf), WM_NOTIFYFORMAT,
6989 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
6991 /* initialize color information */
6992 infoPtr->clrBk = CLR_NONE;
6993 infoPtr->clrText = comctl32_color.clrWindowText;
6994 infoPtr->clrTextBk = CLR_DEFAULT;
6995 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
6997 /* set default values */
6998 infoPtr->nFocusedItem = -1;
6999 infoPtr->nSelectionMark = -1;
7000 infoPtr->nHotItem = -1;
7001 infoPtr->bRedraw = TRUE;
7002 infoPtr->bFirstPaint = TRUE;
7003 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
7004 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
7005 infoPtr->nEditLabelItem = -1;
7006 infoPtr->dwHoverTime = -1; /* default system hover time */
7008 /* get default font (icon title) */
7009 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
7010 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
7011 infoPtr->hFont = infoPtr->hDefaultFont;
7012 LISTVIEW_SaveTextMetrics(infoPtr);
7015 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, (LPCWSTR)NULL,
7016 WS_CHILD | HDS_HORZ | (DWORD)((LVS_NOSORTHEADER & lpcs->style)?0:HDS_BUTTONS),
7017 0, 0, 0, 0, hwnd, NULL,
7018 lpcs->hInstance, NULL);
7019 if (!infoPtr->hwndHeader) goto fail;
7021 /* set header unicode format */
7022 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)NULL);
7024 /* set header font */
7025 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
7027 /* allocate memory for the data structure */
7028 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
7029 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
7030 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
7031 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
7032 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
7034 /* initialize the icon sizes */
7035 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
7036 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
7038 /* init item size to avoid division by 0 */
7039 LISTVIEW_UpdateItemSize (infoPtr);
7041 if (uView == LVS_REPORT)
7043 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
7045 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
7049 /* set HDS_HIDDEN flag to hide the header bar */
7050 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE,
7051 GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE) | HDS_HIDDEN);
7058 DestroyWindow(infoPtr->hwndHeader);
7059 ranges_destroy(infoPtr->selectionRanges);
7060 DPA_Destroy(infoPtr->hdpaItems);
7061 DPA_Destroy(infoPtr->hdpaPosX);
7062 DPA_Destroy(infoPtr->hdpaPosY);
7063 DPA_Destroy(infoPtr->hdpaColumns);
7064 COMCTL32_Free(infoPtr);
7070 * Erases the background of the listview control.
7073 * [I] infoPtr : valid pointer to the listview structure
7074 * [I] hdc : device context handle
7080 static inline BOOL LISTVIEW_EraseBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc)
7084 TRACE("(hdc=%p)\n", hdc);
7086 if (!GetClipBox(hdc, &rc)) return FALSE;
7088 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
7094 * Helper function for LISTVIEW_[HV]Scroll *only*.
7095 * Performs vertical/horizontal scrolling by a give amount.
7098 * [I] infoPtr : valid pointer to the listview structure
7099 * [I] dx : amount of horizontal scroll
7100 * [I] dy : amount of vertical scroll
7102 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7104 /* now we can scroll the list */
7105 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
7106 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
7107 /* if we have focus, adjust rect */
7108 OffsetRect(&infoPtr->rcFocus, dx, dy);
7109 UpdateWindow(infoPtr->hwndSelf);
7114 * Performs vertical scrolling.
7117 * [I] infoPtr : valid pointer to the listview structure
7118 * [I] nScrollCode : scroll code
7119 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7120 * [I] hScrollWnd : scrollbar control window handle
7126 * SB_LINEUP/SB_LINEDOWN:
7127 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
7128 * for LVS_REPORT is 1 line
7129 * for LVS_LIST cannot occur
7132 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7133 INT nScrollDiff, HWND hScrollWnd)
7135 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7136 INT nOldScrollPos, nNewScrollPos;
7137 SCROLLINFO scrollInfo;
7140 TRACE("(nScrollCode=%d, nScrollDiff=%d)\n", nScrollCode, nScrollDiff);
7142 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7144 scrollInfo.cbSize = sizeof(SCROLLINFO);
7145 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7147 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
7149 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
7151 nOldScrollPos = scrollInfo.nPos;
7152 switch (nScrollCode)
7158 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
7162 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
7166 nScrollDiff = -scrollInfo.nPage;
7170 nScrollDiff = scrollInfo.nPage;
7173 case SB_THUMBPOSITION:
7175 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7182 /* quit right away if pos isn't changing */
7183 if (nScrollDiff == 0) return 0;
7185 /* calculate new position, and handle overflows */
7186 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7187 if (nScrollDiff > 0) {
7188 if (nNewScrollPos < nOldScrollPos ||
7189 nNewScrollPos > scrollInfo.nMax)
7190 nNewScrollPos = scrollInfo.nMax;
7192 if (nNewScrollPos > nOldScrollPos ||
7193 nNewScrollPos < scrollInfo.nMin)
7194 nNewScrollPos = scrollInfo.nMin;
7197 /* set the new position, and reread in case it changed */
7198 scrollInfo.fMask = SIF_POS;
7199 scrollInfo.nPos = nNewScrollPos;
7200 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
7202 /* carry on only if it really changed */
7203 if (nNewScrollPos == nOldScrollPos) return 0;
7205 /* now adjust to client coordinates */
7206 nScrollDiff = nOldScrollPos - nNewScrollPos;
7207 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
7209 /* and scroll the window */
7210 scroll_list(infoPtr, 0, nScrollDiff);
7217 * Performs horizontal scrolling.
7220 * [I] infoPtr : valid pointer to the listview structure
7221 * [I] nScrollCode : scroll code
7222 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7223 * [I] hScrollWnd : scrollbar control window handle
7229 * SB_LINELEFT/SB_LINERIGHT:
7230 * for LVS_ICON, LVS_SMALLICON 1 pixel
7231 * for LVS_REPORT is 1 pixel
7232 * for LVS_LIST is 1 column --> which is a 1 because the
7233 * scroll is based on columns not pixels
7236 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7237 INT nScrollDiff, HWND hScrollWnd)
7239 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7240 INT nOldScrollPos, nNewScrollPos;
7241 SCROLLINFO scrollInfo;
7243 TRACE("(nScrollCode=%d, nScrollDiff=%d)\n", nScrollCode, nScrollDiff);
7245 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7247 scrollInfo.cbSize = sizeof(SCROLLINFO);
7248 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7250 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
7252 nOldScrollPos = scrollInfo.nPos;
7254 switch (nScrollCode)
7268 nScrollDiff = -scrollInfo.nPage;
7272 nScrollDiff = scrollInfo.nPage;
7275 case SB_THUMBPOSITION:
7277 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7284 /* quit right away if pos isn't changing */
7285 if (nScrollDiff == 0) return 0;
7287 /* calculate new position, and handle overflows */
7288 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7289 if (nScrollDiff > 0) {
7290 if (nNewScrollPos < nOldScrollPos ||
7291 nNewScrollPos > scrollInfo.nMax)
7292 nNewScrollPos = scrollInfo.nMax;
7294 if (nNewScrollPos > nOldScrollPos ||
7295 nNewScrollPos < scrollInfo.nMin)
7296 nNewScrollPos = scrollInfo.nMin;
7299 /* set the new position, and reread in case it changed */
7300 scrollInfo.fMask = SIF_POS;
7301 scrollInfo.nPos = nNewScrollPos;
7302 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
7304 /* carry on only if it really changed */
7305 if (nNewScrollPos == nOldScrollPos) return 0;
7307 if(uView == LVS_REPORT)
7308 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
7310 /* now adjust to client coordinates */
7311 nScrollDiff = nOldScrollPos - nNewScrollPos;
7312 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
7314 /* and scroll the window */
7315 scroll_list(infoPtr, nScrollDiff, 0);
7320 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
7322 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7323 INT gcWheelDelta = 0;
7324 UINT pulScrollLines = 3;
7325 SCROLLINFO scrollInfo;
7327 TRACE("(wheelDelta=%d)\n", wheelDelta);
7329 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
7330 gcWheelDelta -= wheelDelta;
7332 scrollInfo.cbSize = sizeof(SCROLLINFO);
7333 scrollInfo.fMask = SIF_POS;
7340 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
7341 * should be fixed in the future.
7343 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
7344 LISTVIEW_SCROLL_ICON_LINE_SIZE : -LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
7348 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
7350 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
7351 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
7352 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
7357 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
7368 * [I] infoPtr : valid pointer to the listview structure
7369 * [I] nVirtualKey : virtual key
7370 * [I] lKeyData : key data
7375 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
7377 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7379 NMLVKEYDOWN nmKeyDown;
7381 TRACE("(nVirtualKey=%d, lKeyData=%ld)\n", nVirtualKey, lKeyData);
7383 /* send LVN_KEYDOWN notification */
7384 nmKeyDown.wVKey = nVirtualKey;
7385 nmKeyDown.flags = 0;
7386 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
7388 switch (nVirtualKey)
7391 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
7393 notify(infoPtr, NM_RETURN);
7394 notify(infoPtr, LVN_ITEMACTIVATE);
7399 if (infoPtr->nItemCount > 0)
7404 if (infoPtr->nItemCount > 0)
7405 nItem = infoPtr->nItemCount - 1;
7409 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
7413 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
7417 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
7421 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
7425 if (uView == LVS_REPORT)
7426 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr);
7428 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
7429 * LISTVIEW_GetCountPerRow(infoPtr);
7430 if(nItem < 0) nItem = 0;
7434 if (uView == LVS_REPORT)
7435 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr);
7437 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
7438 * LISTVIEW_GetCountPerRow(infoPtr);
7439 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
7443 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem))
7444 LISTVIEW_KeySelection(infoPtr, nItem);
7454 * [I] infoPtr : valid pointer to the listview structure
7459 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
7463 /* if we did not have the focus, there's nothing to do */
7464 if (!infoPtr->bFocus) return 0;
7466 /* send NM_KILLFOCUS notification */
7467 notify(infoPtr, NM_KILLFOCUS);
7469 /* if we have a focus rectagle, get rid of it */
7470 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
7472 /* set window focus flag */
7473 infoPtr->bFocus = FALSE;
7475 /* invalidate the selected items before reseting focus flag */
7476 LISTVIEW_InvalidateSelectedItems(infoPtr);
7483 * Processes double click messages (left mouse button).
7486 * [I] infoPtr : valid pointer to the listview structure
7487 * [I] wKey : key flag
7488 * [I] pts : mouse coordinate
7493 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7495 LVHITTESTINFO htInfo;
7497 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7499 /* send NM_RELEASEDCAPTURE notification */
7500 notify(infoPtr, NM_RELEASEDCAPTURE);
7502 htInfo.pt.x = pts.x;
7503 htInfo.pt.y = pts.y;
7505 /* send NM_DBLCLK notification */
7506 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
7507 notify_click(infoPtr, NM_DBLCLK, &htInfo);
7509 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
7510 if(htInfo.iItem != -1) notify(infoPtr, LVN_ITEMACTIVATE);
7517 * Processes mouse down messages (left mouse button).
7520 * [I] infoPtr : valid pointer to the listview structure
7521 * [I] wKey : key flag
7522 * [I] pts : mouse coordinate
7527 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7529 LVHITTESTINFO lvHitTestInfo;
7530 static BOOL bGroupSelect = TRUE;
7531 POINT pt = { pts.x, pts.y };
7534 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7536 /* send NM_RELEASEDCAPTURE notification */
7537 notify(infoPtr, NM_RELEASEDCAPTURE);
7539 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
7541 /* set left button down flag */
7542 infoPtr->bLButtonDown = TRUE;
7544 lvHitTestInfo.pt.x = pts.x;
7545 lvHitTestInfo.pt.y = pts.y;
7547 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
7548 TRACE("at %s, nItem=%d\n", debugpoint(&pt), nItem);
7549 infoPtr->nEditLabelItem = -1;
7550 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
7552 if (infoPtr->dwStyle & LVS_SINGLESEL)
7554 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
7555 infoPtr->nEditLabelItem = nItem;
7557 LISTVIEW_SetSelection(infoPtr, nItem);
7561 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
7565 LISTVIEW_AddGroupSelection(infoPtr, nItem);
7566 LISTVIEW_SetItemFocus(infoPtr, nItem);
7567 infoPtr->nSelectionMark = nItem;
7573 item.state = LVIS_SELECTED | LVIS_FOCUSED;
7574 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
7576 LISTVIEW_SetItemState(infoPtr,nItem,&item);
7577 infoPtr->nSelectionMark = nItem;
7580 else if (wKey & MK_CONTROL)
7584 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
7586 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
7587 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
7588 LISTVIEW_SetItemState(infoPtr, nItem, &item);
7589 infoPtr->nSelectionMark = nItem;
7591 else if (wKey & MK_SHIFT)
7593 LISTVIEW_SetGroupSelection(infoPtr, nItem);
7597 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
7598 infoPtr->nEditLabelItem = nItem;
7600 /* set selection (clears other pre-existing selections) */
7601 LISTVIEW_SetSelection(infoPtr, nItem);
7607 /* remove all selections */
7608 LISTVIEW_DeselectAll(infoPtr);
7616 * Processes mouse up messages (left mouse button).
7619 * [I] infoPtr : valid pointer to the listview structure
7620 * [I] wKey : key flag
7621 * [I] pts : mouse coordinate
7626 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7628 LVHITTESTINFO lvHitTestInfo;
7630 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7632 if (!infoPtr->bLButtonDown) return 0;
7634 lvHitTestInfo.pt.x = pts.x;
7635 lvHitTestInfo.pt.y = pts.y;
7637 /* send NM_CLICK notification */
7638 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
7639 notify_click(infoPtr, NM_CLICK, &lvHitTestInfo);
7641 /* set left button flag */
7642 infoPtr->bLButtonDown = FALSE;
7644 /* if we clicked on a selected item, edit the label */
7645 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
7646 LISTVIEW_EditLabelT(infoPtr, lvHitTestInfo.iItem, TRUE);
7653 * Destroys the listview control (called after WM_DESTROY).
7656 * [I] infoPtr : valid pointer to the listview structure
7661 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
7665 /* delete all items */
7666 LISTVIEW_DeleteAllItems(infoPtr);
7668 /* destroy data structure */
7669 DPA_Destroy(infoPtr->hdpaItems);
7670 DPA_Destroy(infoPtr->hdpaPosX);
7671 DPA_Destroy(infoPtr->hdpaPosY);
7672 DPA_Destroy(infoPtr->hdpaColumns);
7673 ranges_destroy(infoPtr->selectionRanges);
7675 /* destroy image lists */
7676 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
7678 if (infoPtr->himlNormal)
7679 ImageList_Destroy(infoPtr->himlNormal);
7680 if (infoPtr->himlSmall)
7681 ImageList_Destroy(infoPtr->himlSmall);
7682 if (infoPtr->himlState)
7683 ImageList_Destroy(infoPtr->himlState);
7686 /* destroy font, bkgnd brush */
7688 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
7689 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
7691 /* free listview info pointer*/
7692 COMCTL32_Free(infoPtr);
7694 SetWindowLongW(infoPtr->hwndSelf, 0, 0);
7700 * Handles notifications from header.
7703 * [I] infoPtr : valid pointer to the listview structure
7704 * [I] nCtrlId : control identifier
7705 * [I] lpnmh : notification information
7710 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
7712 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7714 TRACE("(lpnmh=%p)\n", lpnmh);
7716 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= infoPtr->hdpaColumns->nItemCount) return 0;
7718 switch (lpnmh->hdr.code)
7722 case HDN_ITEMCHANGEDW:
7723 case HDN_ITEMCHANGEDA:
7725 COLUMN_INFO *lpColumnInfo;
7728 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
7732 hdi.mask = HDI_WIDTH;
7733 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, (LPARAM)&hdi)) return 0;
7737 cxy = lpnmh->pitem->cxy;
7739 /* determine how much we change since the last know position */
7740 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
7741 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
7744 RECT rcCol = lpColumnInfo->rcHeader;
7746 lpColumnInfo->rcHeader.right += dx;
7747 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
7748 if (uView == LVS_REPORT && is_redrawing(infoPtr))
7750 /* this trick works for left aligned columns only */
7751 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
7753 rcCol.right = min (rcCol.right, lpColumnInfo->rcHeader.right);
7754 rcCol.left = max (rcCol.left, rcCol.right - 3 * infoPtr->ntmAveCharWidth);
7756 rcCol.top = infoPtr->rcList.top;
7757 rcCol.bottom = infoPtr->rcList.bottom;
7758 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
7764 case HDN_ITEMCLICKW:
7765 case HDN_ITEMCLICKA:
7767 /* Handle sorting by Header Column */
7770 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
7772 nmlv.iSubItem = lpnmh->iItem;
7773 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
7783 * Determines the type of structure to use.
7786 * [I] infoPtr : valid pointer to the listview structureof the sender
7787 * [I] hwndFrom : listview window handle
7788 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
7793 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
7795 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
7797 if (nCommand != NF_REQUERY) return 0;
7799 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
7806 * Paints/Repaints the listview control.
7809 * [I] infoPtr : valid pointer to the listview structure
7810 * [I] hdc : device context handle
7815 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
7817 TRACE("(hdc=%p)\n", hdc);
7819 if (infoPtr->bFirstPaint)
7821 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7823 infoPtr->bFirstPaint = FALSE;
7824 LISTVIEW_UpdateItemSize(infoPtr);
7825 if (uView == LVS_ICON || uView == LVS_SMALLICON)
7826 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7827 LISTVIEW_UpdateScroll(infoPtr);
7830 LISTVIEW_Refresh(infoPtr, hdc);
7835 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
7837 if (ps.fErase) LISTVIEW_FillBkgnd(infoPtr, hdc, &ps.rcPaint);
7838 LISTVIEW_Refresh(infoPtr, hdc);
7839 EndPaint(infoPtr->hwndSelf, &ps);
7847 * Processes double click messages (right mouse button).
7850 * [I] infoPtr : valid pointer to the listview structure
7851 * [I] wKey : key flag
7852 * [I] pts : mouse coordinate
7857 static LRESULT LISTVIEW_RButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7859 LVHITTESTINFO lvHitTestInfo;
7861 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
7863 /* send NM_RELEASEDCAPTURE notification */
7864 notify(infoPtr, NM_RELEASEDCAPTURE);
7866 /* send NM_RDBLCLK notification */
7867 lvHitTestInfo.pt.x = pts.x;
7868 lvHitTestInfo.pt.y = pts.y;
7869 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
7870 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
7877 * Processes mouse down messages (right mouse button).
7880 * [I] infoPtr : valid pointer to the listview structure
7881 * [I] wKey : key flag
7882 * [I] pts : mouse coordinate
7887 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7889 LVHITTESTINFO lvHitTestInfo;
7892 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
7894 /* send NM_RELEASEDCAPTURE notification */
7895 notify(infoPtr, NM_RELEASEDCAPTURE);
7897 /* make sure the listview control window has the focus */
7898 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
7900 /* set right button down flag */
7901 infoPtr->bRButtonDown = TRUE;
7903 /* determine the index of the selected item */
7904 lvHitTestInfo.pt.x = pts.x;
7905 lvHitTestInfo.pt.y = pts.y;
7906 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
7908 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
7910 LISTVIEW_SetItemFocus(infoPtr, nItem);
7911 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
7912 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
7913 LISTVIEW_SetSelection(infoPtr, nItem);
7917 LISTVIEW_DeselectAll(infoPtr);
7925 * Processes mouse up messages (right mouse button).
7928 * [I] infoPtr : valid pointer to the listview structure
7929 * [I] wKey : key flag
7930 * [I] pts : mouse coordinate
7935 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7937 LVHITTESTINFO lvHitTestInfo;
7940 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
7942 if (!infoPtr->bRButtonDown) return 0;
7944 /* set button flag */
7945 infoPtr->bRButtonDown = FALSE;
7947 /* Send NM_RClICK notification */
7948 lvHitTestInfo.pt.x = pts.x;
7949 lvHitTestInfo.pt.y = pts.y;
7950 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
7951 notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo);
7953 /* Change to screen coordinate for WM_CONTEXTMENU */
7954 pt = lvHitTestInfo.pt;
7955 ClientToScreen(infoPtr->hwndSelf, &pt);
7957 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
7958 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
7959 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
7970 * [I] infoPtr : valid pointer to the listview structure
7971 * [I] hwnd : window handle of window containing the cursor
7972 * [I] nHittest : hit-test code
7973 * [I] wMouseMsg : ideintifier of the mouse message
7976 * TRUE if cursor is set
7979 static BOOL LISTVIEW_SetCursor(LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
7981 LVHITTESTINFO lvHitTestInfo;
7983 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
7985 if(!infoPtr->hHotCursor) return FALSE;
7987 GetCursorPos(&lvHitTestInfo.pt);
7988 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
7990 SetCursor(infoPtr->hHotCursor);
8000 * [I] infoPtr : valid pointer to the listview structure
8001 * [I] hwndLoseFocus : handle of previously focused window
8006 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
8008 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
8010 /* if we have the focus already, there's nothing to do */
8011 if (infoPtr->bFocus) return 0;
8013 /* send NM_SETFOCUS notification */
8014 notify(infoPtr, NM_SETFOCUS);
8016 /* set window focus flag */
8017 infoPtr->bFocus = TRUE;
8019 /* put the focus rect back on */
8020 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
8022 /* redraw all visible selected items */
8023 LISTVIEW_InvalidateSelectedItems(infoPtr);
8033 * [I] infoPtr : valid pointer to the listview structure
8034 * [I] fRedraw : font handle
8035 * [I] fRedraw : redraw flag
8040 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
8042 HFONT oldFont = infoPtr->hFont;
8044 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
8046 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
8047 if (infoPtr->hFont == oldFont) return 0;
8049 LISTVIEW_SaveTextMetrics(infoPtr);
8051 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
8052 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
8054 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
8061 * Message handling for WM_SETREDRAW.
8062 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
8065 * [I] infoPtr : valid pointer to the listview structure
8066 * [I] bRedraw: state of redraw flag
8069 * DefWinProc return value
8071 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
8073 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
8075 /* we can not use straight equality here because _any_ non-zero value is TRUE */
8076 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
8078 infoPtr->bRedraw = bRedraw;
8080 if(!bRedraw) return 0;
8082 if (is_autoarrange(infoPtr))
8083 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8084 LISTVIEW_UpdateScroll(infoPtr);
8086 /* despite what the WM_SETREDRAW docs says, apps expect us
8087 * to invalidate the listview here... stupid! */
8088 LISTVIEW_InvalidateList(infoPtr);
8095 * Resizes the listview control. This function processes WM_SIZE
8096 * messages. At this time, the width and height are not used.
8099 * [I] infoPtr : valid pointer to the listview structure
8100 * [I] Width : new width
8101 * [I] Height : new height
8106 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
8108 RECT rcOld = infoPtr->rcList;
8110 TRACE("(width=%d, height=%d)\n", Width, Height);
8112 LISTVIEW_UpdateSize(infoPtr);
8113 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
8115 /* do not bother with display related stuff if we're not redrawing */
8116 if (!is_redrawing(infoPtr)) return 0;
8118 if (is_autoarrange(infoPtr))
8119 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8121 LISTVIEW_UpdateScroll(infoPtr);
8123 /* refresh all only for lists whose height changed significantly */
8124 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
8125 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
8126 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
8127 LISTVIEW_InvalidateList(infoPtr);
8134 * Sets the size information.
8137 * [I] infoPtr : valid pointer to the listview structure
8142 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
8144 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8146 TRACE("uView=%d, rcList(old)=%s\n", uView, debugrect(&infoPtr->rcList));
8148 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
8150 if (uView == LVS_LIST)
8152 /* Apparently the "LIST" style is supposed to have the same
8153 * number of items in a column even if there is no scroll bar.
8154 * Since if a scroll bar already exists then the bottom is already
8155 * reduced, only reduce if the scroll bar does not currently exist.
8156 * The "2" is there to mimic the native control. I think it may be
8157 * related to either padding or edges. (GLA 7/2002)
8159 if (!(infoPtr->dwStyle & WS_HSCROLL))
8160 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
8161 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
8163 else if (uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER))
8168 hl.prc = &infoPtr->rcList;
8170 Header_Layout(infoPtr->hwndHeader, &hl);
8172 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8174 infoPtr->rcList.top = max(wp.cy, 0);
8177 TRACE(" rcList=%s\n", debugrect(&infoPtr->rcList));
8182 * Processes WM_STYLECHANGED messages.
8185 * [I] infoPtr : valid pointer to the listview structure
8186 * [I] wStyleType : window style type (normal or extended)
8187 * [I] lpss : window style information
8192 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
8193 const STYLESTRUCT *lpss)
8195 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
8196 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
8198 TRACE("(styletype=%x, styleOld=0x%08lx, styleNew=0x%08lx)\n",
8199 wStyleType, lpss->styleOld, lpss->styleNew);
8201 if (wStyleType != GWL_STYLE) return 0;
8203 /* FIXME: if LVS_NOSORTHEADER changed, update header */
8204 /* what if LVS_OWNERDATA changed? */
8205 /* or LVS_SINGLESEL */
8206 /* or LVS_SORT{AS,DES}CENDING */
8208 infoPtr->dwStyle = lpss->styleNew;
8210 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
8211 ((lpss->styleNew & WS_HSCROLL) == 0))
8212 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
8214 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
8215 ((lpss->styleNew & WS_VSCROLL) == 0))
8216 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
8218 if (uNewView != uOldView)
8220 SIZE oldIconSize = infoPtr->iconSize;
8223 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8224 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8226 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
8227 SetRectEmpty(&infoPtr->rcFocus);
8229 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
8230 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
8232 if (uNewView == LVS_ICON)
8234 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
8236 TRACE("icon old size=(%ld,%ld), new size=(%ld,%ld)\n",
8237 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
8238 LISTVIEW_SetIconSpacing(infoPtr, 0);
8241 else if (uNewView == LVS_REPORT)
8246 hl.prc = &infoPtr->rcList;
8248 Header_Layout(infoPtr->hwndHeader, &hl);
8249 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8252 LISTVIEW_UpdateItemSize(infoPtr);
8255 if (uNewView == LVS_REPORT)
8256 ShowWindow(infoPtr->hwndHeader, (lpss->styleNew & LVS_NOCOLUMNHEADER) ? SW_HIDE : SW_SHOWNORMAL);
8258 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
8259 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
8260 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8262 /* update the size of the client area */
8263 LISTVIEW_UpdateSize(infoPtr);
8265 /* add scrollbars if needed */
8266 LISTVIEW_UpdateScroll(infoPtr);
8268 /* invalidate client area + erase background */
8269 LISTVIEW_InvalidateList(infoPtr);
8276 * Window procedure of the listview control.
8279 static LRESULT WINAPI
8280 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8282 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(hwnd, 0);
8284 TRACE("(uMsg=%x wParam=%x lParam=%lx)\n", uMsg, wParam, lParam);
8286 if (!infoPtr && (uMsg != WM_CREATE))
8287 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8291 infoPtr->dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
8296 case LVM_APPROXIMATEVIEWRECT:
8297 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
8298 LOWORD(lParam), HIWORD(lParam));
8300 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
8302 /* case LVM_CANCELEDITLABEL: */
8304 /* case LVM_CREATEDRAGIMAGE: */
8306 case LVM_DELETEALLITEMS:
8307 return LISTVIEW_DeleteAllItems(infoPtr);
8309 case LVM_DELETECOLUMN:
8310 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
8312 case LVM_DELETEITEM:
8313 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
8315 case LVM_EDITLABELW:
8316 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
8318 case LVM_EDITLABELA:
8319 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
8321 /* case LVM_ENABLEGROUPVIEW: */
8323 case LVM_ENSUREVISIBLE:
8324 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
8327 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
8330 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
8332 case LVM_GETBKCOLOR:
8333 return infoPtr->clrBk;
8335 /* case LVM_GETBKIMAGE: */
8337 case LVM_GETCALLBACKMASK:
8338 return infoPtr->uCallbackMask;
8340 case LVM_GETCOLUMNA:
8341 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8343 case LVM_GETCOLUMNW:
8344 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8346 case LVM_GETCOLUMNORDERARRAY:
8347 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
8349 case LVM_GETCOLUMNWIDTH:
8350 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
8352 case LVM_GETCOUNTPERPAGE:
8353 return LISTVIEW_GetCountPerPage(infoPtr);
8355 case LVM_GETEDITCONTROL:
8356 return (LRESULT)infoPtr->hwndEdit;
8358 case LVM_GETEXTENDEDLISTVIEWSTYLE:
8359 return infoPtr->dwLvExStyle;
8361 /* case LVM_GETGROUPINFO: */
8363 /* case LVM_GETGROUPMETRICS: */
8366 return (LRESULT)infoPtr->hwndHeader;
8368 case LVM_GETHOTCURSOR:
8369 return (LRESULT)infoPtr->hHotCursor;
8371 case LVM_GETHOTITEM:
8372 return infoPtr->nHotItem;
8374 case LVM_GETHOVERTIME:
8375 return infoPtr->dwHoverTime;
8377 case LVM_GETIMAGELIST:
8378 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
8380 /* case LVM_GETINSERTMARK: */
8382 /* case LVM_GETINSERTMARKCOLOR: */
8384 /* case LVM_GETINSERTMARKRECT: */
8386 case LVM_GETISEARCHSTRINGA:
8387 case LVM_GETISEARCHSTRINGW:
8388 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
8392 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
8395 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
8397 case LVM_GETITEMCOUNT:
8398 return infoPtr->nItemCount;
8400 case LVM_GETITEMPOSITION:
8401 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
8403 case LVM_GETITEMRECT:
8404 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
8406 case LVM_GETITEMSPACING:
8407 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
8409 case LVM_GETITEMSTATE:
8410 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
8412 case LVM_GETITEMTEXTA:
8413 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
8415 case LVM_GETITEMTEXTW:
8416 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
8418 case LVM_GETNEXTITEM:
8419 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
8421 case LVM_GETNUMBEROFWORKAREAS:
8422 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
8426 if (!lParam) return FALSE;
8427 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
8430 /* case LVM_GETOUTLINECOLOR: */
8432 /* case LVM_GETSELECTEDCOLUMN: */
8434 case LVM_GETSELECTEDCOUNT:
8435 return LISTVIEW_GetSelectedCount(infoPtr);
8437 case LVM_GETSELECTIONMARK:
8438 return infoPtr->nSelectionMark;
8440 case LVM_GETSTRINGWIDTHA:
8441 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
8443 case LVM_GETSTRINGWIDTHW:
8444 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
8446 case LVM_GETSUBITEMRECT:
8447 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
8449 case LVM_GETTEXTBKCOLOR:
8450 return infoPtr->clrTextBk;
8452 case LVM_GETTEXTCOLOR:
8453 return infoPtr->clrText;
8455 /* case LVM_GETTILEINFO: */
8457 /* case LVM_GETTILEVIEWINFO: */
8459 case LVM_GETTOOLTIPS:
8460 FIXME("LVM_GETTOOLTIPS: unimplemented\n");
8463 case LVM_GETTOPINDEX:
8464 return LISTVIEW_GetTopIndex(infoPtr);
8466 /*case LVM_GETUNICODEFORMAT:
8467 FIXME("LVM_GETUNICODEFORMAT: unimplemented\n");
8470 /* case LVM_GETVIEW: */
8472 case LVM_GETVIEWRECT:
8473 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
8475 case LVM_GETWORKAREAS:
8476 FIXME("LVM_GETWORKAREAS: unimplemented\n");
8479 /* case LVM_HASGROUP: */
8482 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
8484 case LVM_INSERTCOLUMNA:
8485 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8487 case LVM_INSERTCOLUMNW:
8488 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8490 /* case LVM_INSERTGROUP: */
8492 /* case LVM_INSERTGROUPSORTED: */
8494 case LVM_INSERTITEMA:
8495 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
8497 case LVM_INSERTITEMW:
8498 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
8500 /* case LVM_INSERTMARKHITTEST: */
8502 /* case LVM_ISGROUPVIEWENABLED: */
8504 /* case LVM_MAPIDTOINDEX: */
8506 /* case LVM_MAPINDEXTOID: */
8508 /* case LVM_MOVEGROUP: */
8510 /* case LVM_MOVEITEMTOGROUP: */
8512 case LVM_REDRAWITEMS:
8513 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
8515 /* case LVM_REMOVEALLGROUPS: */
8517 /* case LVM_REMOVEGROUP: */
8520 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
8522 case LVM_SETBKCOLOR:
8523 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
8525 /* case LVM_SETBKIMAGE: */
8527 case LVM_SETCALLBACKMASK:
8528 infoPtr->uCallbackMask = (UINT)wParam;
8531 case LVM_SETCOLUMNA:
8532 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8534 case LVM_SETCOLUMNW:
8535 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8537 case LVM_SETCOLUMNORDERARRAY:
8538 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
8540 case LVM_SETCOLUMNWIDTH:
8541 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, SLOWORD(lParam));
8543 case LVM_SETEXTENDEDLISTVIEWSTYLE:
8544 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
8546 /* case LVM_SETGROUPINFO: */
8548 /* case LVM_SETGROUPMETRICS: */
8550 case LVM_SETHOTCURSOR:
8551 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
8553 case LVM_SETHOTITEM:
8554 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
8556 case LVM_SETHOVERTIME:
8557 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
8559 case LVM_SETICONSPACING:
8560 return LISTVIEW_SetIconSpacing(infoPtr, (DWORD)lParam);
8562 case LVM_SETIMAGELIST:
8563 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
8565 /* case LVM_SETINFOTIP: */
8567 /* case LVM_SETINSERTMARK: */
8569 /* case LVM_SETINSERTMARKCOLOR: */
8572 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
8575 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
8577 case LVM_SETITEMCOUNT:
8578 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
8580 case LVM_SETITEMPOSITION:
8582 POINT pt = { SLOWORD(lParam), SHIWORD(lParam) };
8583 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
8586 case LVM_SETITEMPOSITION32:
8587 if (lParam == 0) return FALSE;
8588 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
8590 case LVM_SETITEMSTATE:
8591 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
8593 case LVM_SETITEMTEXTA:
8594 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
8596 case LVM_SETITEMTEXTW:
8597 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
8599 /* case LVM_SETOUTLINECOLOR: */
8601 /* case LVM_SETSELECTEDCOLUMN: */
8603 case LVM_SETSELECTIONMARK:
8604 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
8606 case LVM_SETTEXTBKCOLOR:
8607 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
8609 case LVM_SETTEXTCOLOR:
8610 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
8612 /* case LVM_SETTILEINFO: */
8614 /* case LVM_SETTILEVIEWINFO: */
8616 /* case LVM_SETTILEWIDTH: */
8618 /* case LVM_SETTOOLTIPS: */
8620 /* case LVM_SETUNICODEFORMAT: */
8622 /* case LVM_SETVIEW: */
8624 /* case LVM_SETWORKAREAS: */
8626 /* case LVM_SORTGROUPS: */
8629 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
8631 /* LVM_SORTITEMSEX: */
8633 case LVM_SUBITEMHITTEST:
8634 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
8637 return LISTVIEW_Update(infoPtr, (INT)wParam);
8640 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
8643 return LISTVIEW_Command(infoPtr, wParam, lParam);
8646 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
8649 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
8652 return DLGC_WANTCHARS | DLGC_WANTARROWS;
8655 return (LRESULT)infoPtr->hFont;
8658 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
8661 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
8664 return LISTVIEW_KillFocus(infoPtr);
8666 case WM_LBUTTONDBLCLK:
8667 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8669 case WM_LBUTTONDOWN:
8670 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8673 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8676 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8679 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8682 return LISTVIEW_NCDestroy(infoPtr);
8685 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
8686 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
8689 case WM_NOTIFYFORMAT:
8690 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
8693 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
8695 case WM_RBUTTONDBLCLK:
8696 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8698 case WM_RBUTTONDOWN:
8699 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8702 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8705 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
8710 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
8713 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
8716 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
8719 return LISTVIEW_Size(infoPtr, (int)SLOWORD(lParam), (int)SHIWORD(lParam));
8721 case WM_STYLECHANGED:
8722 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
8724 case WM_SYSCOLORCHANGE:
8725 COMCTL32_RefreshSysColors();
8728 /* case WM_TIMER: */
8731 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
8734 if (wParam & (MK_SHIFT | MK_CONTROL))
8735 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8736 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
8738 case WM_WINDOWPOSCHANGED:
8739 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
8741 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
8742 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
8743 LISTVIEW_UpdateSize(infoPtr);
8744 LISTVIEW_UpdateScroll(infoPtr);
8746 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8748 /* case WM_WININICHANGE: */
8751 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
8752 ERR("unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam);
8755 /* call default window procedure */
8756 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8764 * Registers the window class.
8772 void LISTVIEW_Register(void)
8776 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
8777 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
8778 wndClass.lpfnWndProc = (WNDPROC)LISTVIEW_WindowProc;
8779 wndClass.cbClsExtra = 0;
8780 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
8781 wndClass.hCursor = LoadCursorW(0, IDC_ARROWW);
8782 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
8783 wndClass.lpszClassName = WC_LISTVIEWW;
8784 RegisterClassW(&wndClass);
8789 * Unregisters the window class.
8797 void LISTVIEW_Unregister(void)
8799 UnregisterClassW(WC_LISTVIEWW, NULL);
8804 * Handle any WM_COMMAND messages
8807 * [I] infoPtr : valid pointer to the listview structure
8808 * [I] wParam : the first message parameter
8809 * [I] lParam : the second message parameter
8814 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
8816 switch (HIWORD(wParam))
8821 * Adjust the edit window size
8824 HDC hdc = GetDC(infoPtr->hwndEdit);
8825 HFONT hFont, hOldFont = 0;
8830 if (!infoPtr->hwndEdit || !hdc) return 0;
8831 len = GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
8832 GetWindowRect(infoPtr->hwndEdit, &rect);
8834 /* Select font to get the right dimension of the string */
8835 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
8838 hOldFont = SelectObject(hdc, hFont);
8841 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
8843 TEXTMETRICW textMetric;
8845 /* Add Extra spacing for the next character */
8846 GetTextMetricsW(hdc, &textMetric);
8847 sz.cx += (textMetric.tmMaxCharWidth * 2);
8855 rect.bottom - rect.top,
8856 SWP_DRAWFRAME|SWP_NOMOVE);
8859 SelectObject(hdc, hOldFont);
8861 ReleaseDC(infoPtr->hwndSelf, hdc);
8867 return SendMessageW (GetParent (infoPtr->hwndSelf), WM_COMMAND, wParam, lParam);
8876 * Subclassed edit control windproc function
8879 * [I] hwnd : the edit window handle
8880 * [I] uMsg : the message that is to be processed
8881 * [I] wParam : first message parameter
8882 * [I] lParam : second message parameter
8883 * [I] isW : TRUE if input is Unicode
8888 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
8890 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(GetParent(hwnd), 0);
8891 BOOL cancel = FALSE;
8893 TRACE("(hwnd=%p, uMsg=%x, wParam=%x, lParam=%lx, isW=%d)\n",
8894 hwnd, uMsg, wParam, lParam, isW);
8899 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
8906 WNDPROC editProc = infoPtr->EditWndProc;
8907 infoPtr->EditWndProc = 0;
8908 SetWindowLongW(hwnd, GWL_WNDPROC, (LONG)editProc);
8909 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
8913 if (VK_ESCAPE == (INT)wParam)
8918 else if (VK_RETURN == (INT)wParam)
8922 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
8926 if (infoPtr->hwndEdit)
8928 LPWSTR buffer = NULL;
8930 infoPtr->hwndEdit = 0;
8933 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
8937 if ( (buffer = COMCTL32_Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
8939 if (isW) GetWindowTextW(hwnd, buffer, len+1);
8940 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
8944 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
8946 if (buffer) COMCTL32_Free(buffer);
8950 SendMessageW(hwnd, WM_CLOSE, 0, 0);
8956 * Subclassed edit control Unicode windproc function
8959 * [I] hwnd : the edit window handle
8960 * [I] uMsg : the message that is to be processed
8961 * [I] wParam : first message parameter
8962 * [I] lParam : second message parameter
8966 LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8968 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
8973 * Subclassed edit control ANSI windproc function
8976 * [I] hwnd : the edit window handle
8977 * [I] uMsg : the message that is to be processed
8978 * [I] wParam : first message parameter
8979 * [I] lParam : second message parameter
8983 LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8985 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
8990 * Creates a subclassed edit cotrol
8993 * [I] infoPtr : valid pointer to the listview structure
8994 * [I] text : initial text for the edit
8995 * [I] style : the window style
8996 * [I] isW : TRUE if input is Unicode
9000 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
9001 INT x, INT y, INT width, INT height, BOOL isW)
9003 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
9008 TEXTMETRICW textMetric;
9009 HINSTANCE hinst = (HINSTANCE)GetWindowLongW(infoPtr->hwndSelf, GWL_HINSTANCE);
9011 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
9013 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|WS_BORDER;
9014 hdc = GetDC(infoPtr->hwndSelf);
9016 /* Select the font to get appropriate metric dimensions */
9017 if(infoPtr->hFont != 0)
9018 hOldFont = SelectObject(hdc, infoPtr->hFont);
9020 /*Get String Lenght in pixels */
9021 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
9023 /*Add Extra spacing for the next character */
9024 GetTextMetricsW(hdc, &textMetric);
9025 sz.cx += (textMetric.tmMaxCharWidth * 2);
9027 if(infoPtr->hFont != 0)
9028 SelectObject(hdc, hOldFont);
9030 ReleaseDC(infoPtr->hwndSelf, hdc);
9032 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
9034 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
9036 if (!hedit) return 0;
9038 infoPtr->EditWndProc = (WNDPROC)
9039 (isW ? SetWindowLongW(hedit, GWL_WNDPROC, (LONG)EditLblWndProcW) :
9040 SetWindowLongA(hedit, GWL_WNDPROC, (LONG)EditLblWndProcA) );
9042 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);