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
25 * Listview control implementation.
28 * -- Hot item handling.
29 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
30 * -- Support CustonDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs)
33 * LISTVIEW_Notify : most notifications from children (editbox and header)
36 * LISTVIEW_SetItemCount : not completed for non OWNERDATA
38 * Advanced functionality:
39 * LISTVIEW_GetNumberOfWorkAreas : not implemented
40 * LISTVIEW_GetISearchString : not implemented
41 * LISTVIEW_GetBkImage : not implemented
42 * LISTVIEW_SetBkImage : not implemented
43 * LISTVIEW_GetColumnOrderArray : simple hack only
44 * LISTVIEW_SetColumnOrderArray : simple hack only
45 * LISTVIEW_Arrange : empty stub
46 * LISTVIEW_ApproximateViewRect : incomplete
47 * LISTVIEW_Update : not completed
49 * Known differences in message stream from native control (not known if
50 * these differences cause problems):
51 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
52 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
53 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
54 * processing for "USEDOUBLECLICKTIME".
59 #include "wine/port.h"
72 #include "wine/debug.h"
74 WINE_DEFAULT_DEBUG_CHANNEL(listview);
76 /* make sure you set this to 0 for production use! */
77 #define DEBUG_RANGES 1
79 typedef struct tagCOLUMN_INFO
81 RECT rcHeader; /* tracks the header's rectangle */
82 int fmt; /* same as LVCOLUMN.fmt */
85 typedef struct tagITEMHDR
89 } ITEMHDR, *LPITEMHDR;
91 typedef struct tagLISTVIEW_SUBITEM
97 typedef struct tagLISTVIEW_ITEM
105 typedef struct tagRANGE
111 typedef struct tagRANGES
116 typedef struct tagITERATOR
125 typedef struct tagLISTVIEW_INFO
132 COLORREF clrTextBkDefault;
133 HIMAGELIST himlNormal;
134 HIMAGELIST himlSmall;
135 HIMAGELIST himlState;
140 RANGES selectionRanges;
144 RECT rcList; /* This rectangle is really the window
145 * client rectangle possibly reduced by the
146 * horizontal scroll bar and/or header - see
147 * LISTVIEW_UpdateSize. This rectangle offset
148 * by the LISTVIEW_GetOrigin value is in
149 * client coordinates */
150 RECT rcView; /* This rectangle contains all items -
151 * contructed in LISTVIEW_AlignTop and
152 * LISTVIEW_AlignLeft */
161 INT ntmHeight; /* from GetTextMetrics from above font */
166 DWORD dwStyle; /* the cached window GWL_STYLE */
167 DWORD dwLvExStyle; /* extended listview style */
168 INT nItemCount; /* the number of items in the list */
169 HDPA hdpaItems; /* array ITEM_INFO pointers */
170 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
171 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
172 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
173 PFNLVCOMPARE pfnCompare;
180 DWORD lastKeyPressTimestamp;
182 INT nSearchParamLength;
183 WCHAR szSearchParam[ MAX_PATH ];
190 /* How many we debug buffer to allocate */
191 #define DEBUG_BUFFERS 20
192 /* The size of a single debug bbuffer */
193 #define DEBUG_BUFFER_SIZE 256
195 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
196 #define SB_INTERNAL -1
198 /* maximum size of a label */
199 #define DISP_TEXT_SIZE 512
201 /* padding for items in list and small icon display modes */
202 #define WIDTH_PADDING 12
204 /* padding for items in list, report and small icon display modes */
205 #define HEIGHT_PADDING 1
207 /* offset of items in report display mode */
208 #define REPORT_MARGINX 2
210 /* padding for icon in large icon display mode
211 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
212 * that HITTEST will see.
213 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
214 * ICON_TOP_PADDING - sum of the two above.
215 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
216 * LABEL_VERT_PADDING - between bottom of text and end of box
218 * ICON_LR_PADDING - additional width above icon size.
219 * ICON_LR_HALF - half of the above value
221 #define ICON_TOP_PADDING_NOTHITABLE 2
222 #define ICON_TOP_PADDING_HITABLE 2
223 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
224 #define ICON_BOTTOM_PADDING 4
225 #define LABEL_VERT_PADDING 7
226 #define ICON_LR_PADDING 16
227 #define ICON_LR_HALF (ICON_LR_PADDING/2)
229 /* default label width for items in list and small icon display modes */
230 #define DEFAULT_LABEL_WIDTH 40
232 /* default column width for items in list display mode */
233 #define DEFAULT_COLUMN_WIDTH 128
235 /* Size of "line" scroll for V & H scrolls */
236 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
238 /* Padding betwen image and label */
239 #define IMAGE_PADDING 2
241 /* Padding behind the label */
242 #define TRAILING_PADDING 5
244 /* Border for the icon caption */
245 #define CAPTION_BORDER 2
247 /* Standard DrawText flags */
248 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
249 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
250 #define LV_SL_DT_FLAGS (DT_TOP | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
252 /* The time in milisecods to reset the search in the list */
253 #define KEY_DELAY 450
255 /* Dump the LISTVIEW_INFO structure to the debug channel */
256 #define LISTVIEW_DUMP(iP) do { \
257 TRACE("hwndSelf=%08x, clrBk=0x%06lx, clrText=0x%06lx, clrTextBk=0x%06lx, ItemHeight=%d, ItemWidth=%d, Style=0x%08lx\n", \
258 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
259 iP->nItemHeight, iP->nItemWidth, infoPtr->dwStyle); \
260 TRACE("hwndSelf=%08x, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08lx, Focus=%s\n", \
261 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
262 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, \
263 (iP->bFocus) ? "true" : "false"); \
264 TRACE("hwndSelf=%08x, ntmH=%d, icSz.cx=%ld, icSz.cy=%ld, icSp.cx=%ld, icSp.cy=%ld, notifyFmt=%d\n", \
265 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
266 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
267 TRACE("hwndSelf=%08x, rcList=(%d,%d)-(%d,%d), rcView=(%d,%d)-(%d,%d)\n", \
269 iP->rcList.left, iP->rcList.top, iP->rcList.right, iP->rcList.bottom, \
270 iP->rcView.left, iP->rcView.top, iP->rcView.right, iP->rcView.bottom); \
275 * forward declarations
277 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *, LPLVITEMW, BOOL);
278 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *, INT, LPRECT);
279 static void LISTVIEW_AlignLeft(LISTVIEW_INFO *);
280 static void LISTVIEW_AlignTop(LISTVIEW_INFO *);
281 static void LISTVIEW_AddGroupSelection(LISTVIEW_INFO *, INT);
282 static INT LISTVIEW_CalculateMaxHeight(LISTVIEW_INFO *);
283 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *, INT, LPPOINT);
284 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *, INT, LPPOINT);
285 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *, INT, LPRECT);
286 static INT LISTVIEW_CalculateMaxWidth(LISTVIEW_INFO *);
287 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *, INT);
288 static INT LISTVIEW_GetColumnWidth(LISTVIEW_INFO *, INT);
289 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *, LPPOINT);
290 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *, LPRECT);
291 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
292 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, LPLVITEMW, BOOL);
293 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *, INT, POINT);
294 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *);
295 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
296 static BOOL LISTVIEW_UpdateSize(LISTVIEW_INFO *);
297 static void LISTVIEW_UnsupportedStyles(LONG);
298 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
299 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
300 static LRESULT LISTVIEW_SortItems(LISTVIEW_INFO *, PFNLVCOMPARE, LPARAM);
301 static LRESULT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *, LPCWSTR, BOOL);
302 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT);
303 static LRESULT LISTVIEW_GetItemState(LISTVIEW_INFO *, INT, UINT);
304 static LRESULT LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, LPLVITEMW);
305 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
306 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
307 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *);
308 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
309 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
311 /******** Text handling functions *************************************/
313 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
314 * text string. The string may be ANSI or Unicode, in which case
315 * the boolean isW tells us the type of the string.
317 * The name of the function tell what type of strings it expects:
318 * W: Unicode, T: ANSI/Unicode - function of isW
321 static inline BOOL is_textW(LPCWSTR text)
323 return text != NULL && text != LPSTR_TEXTCALLBACKW;
326 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
328 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
329 return is_textW(text);
332 static inline int textlenT(LPCWSTR text, BOOL isW)
334 return !is_textT(text, isW) ? 0 :
335 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
338 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
341 if (isSrcW) lstrcpynW(dest, src, max);
342 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
344 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
345 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
348 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
350 LPWSTR wstr = (LPWSTR)text;
352 if (!isW && is_textT(text, isW))
354 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
355 wstr = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
356 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
358 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
362 static inline void textfreeT(LPWSTR wstr, BOOL isW)
364 if (!isW && is_textT(wstr, isW)) HeapFree(GetProcessHeap(), 0, wstr);
368 * dest is a pointer to a Unicode string
369 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
371 static BOOL textsetptrT(LPWSTR *dest, LPWSTR src, BOOL isW)
375 if (src == LPSTR_TEXTCALLBACKW)
377 if (is_textW(*dest)) COMCTL32_Free(*dest);
378 *dest = LPSTR_TEXTCALLBACKW;
382 LPWSTR pszText = textdupTtoW(src, isW);
383 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
384 bResult = Str_SetPtrW(dest, pszText);
385 textfreeT(pszText, isW);
391 * compares a Unicode to a Unicode/ANSI text string
393 static inline int textcmpWT(LPWSTR aw, LPWSTR bt, BOOL isW)
395 if (!aw) return bt ? -1 : 0;
396 if (!bt) return aw ? 1 : 0;
397 if (aw == LPSTR_TEXTCALLBACKW)
398 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
399 if (bt != LPSTR_TEXTCALLBACKW)
401 LPWSTR bw = textdupTtoW(bt, isW);
402 int r = bw ? lstrcmpW(aw, bw) : 1;
410 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
414 n = min(min(n, strlenW(s1)), strlenW(s2));
415 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
416 return res ? res - sizeof(WCHAR) : res;
419 /******** Debugging functions *****************************************/
421 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
423 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
424 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
427 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
429 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
430 n = min(textlenT(text, isW), n);
431 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
434 static char* debug_getbuf()
436 static int index = 0;
437 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
438 return buffers[index++ % DEBUG_BUFFERS];
441 static inline char* debugrange(const RANGE* lprng)
445 char* buf = debug_getbuf();
446 snprintf(buf, DEBUG_BUFFER_SIZE, "[%d, %d)", lprng->lower, lprng->upper);
448 } else return "(null)";
451 static inline char* debugpoint(const POINT* lppt)
455 char* buf = debug_getbuf();
456 snprintf(buf, DEBUG_BUFFER_SIZE, "(%ld, %ld)", lppt->x, lppt->y);
458 } else return "(null)";
461 static inline char* debugrect(const RECT* rect)
465 char* buf = debug_getbuf();
466 snprintf(buf, DEBUG_BUFFER_SIZE, "[(%d, %d);(%d, %d)]",
467 rect->left, rect->top, rect->right, rect->bottom);
469 } else return "(null)";
472 static char* debugnmlistview(LPNMLISTVIEW plvnm)
476 char* buf = debug_getbuf();
477 snprintf(buf, DEBUG_BUFFER_SIZE, "iItem=%d, iSubItem=%d, uNewState=0x%x,"
478 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld\n",
479 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
480 plvnm->uChanged, debugpoint(&plvnm->ptAction), plvnm->lParam);
482 } else return "(null)";
485 static char* debuglvitem_t(LPLVITEMW lpLVItem, BOOL isW)
487 char* buf = debug_getbuf(), *text = buf;
488 int len, size = DEBUG_BUFFER_SIZE;
490 if (lpLVItem == NULL) return "(null)";
491 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
492 if (len == -1) goto end; buf += len; size -= len;
493 if (lpLVItem->mask & LVIF_STATE)
494 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
496 if (len == -1) goto end; buf += len; size -= len;
497 if (lpLVItem->mask & LVIF_TEXT)
498 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
500 if (len == -1) goto end; buf += len; size -= len;
501 if (lpLVItem->mask & LVIF_IMAGE)
502 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
504 if (len == -1) goto end; buf += len; size -= len;
505 if (lpLVItem->mask & LVIF_PARAM)
506 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
508 if (len == -1) goto end; buf += len; size -= len;
509 if (lpLVItem->mask & LVIF_INDENT)
510 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
512 if (len == -1) goto end; buf += len; size -= len;
515 buf = text + strlen(text);
517 if (buf - text > 2) buf[-2] = '}'; buf[-1] = 0;
521 static char* debuglvcolumn_t(LPLVCOLUMNW lpColumn, BOOL isW)
523 char* buf = debug_getbuf(), *text = buf;
524 int len, size = DEBUG_BUFFER_SIZE;
526 if (lpColumn == NULL) return "(null)";
527 len = snprintf(buf, size, "{");
528 if (len == -1) goto end; buf += len; size -= len;
529 if (lpColumn->mask & LVCF_SUBITEM)
530 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
532 if (len == -1) goto end; buf += len; size -= len;
533 if (lpColumn->mask & LVCF_FMT)
534 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
536 if (len == -1) goto end; buf += len; size -= len;
537 if (lpColumn->mask & LVCF_WIDTH)
538 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
540 if (len == -1) goto end; buf += len; size -= len;
541 if (lpColumn->mask & LVCF_TEXT)
542 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
544 if (len == -1) goto end; buf += len; size -= len;
545 if (lpColumn->mask & LVCF_IMAGE)
546 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
548 if (len == -1) goto end; buf += len; size -= len;
549 if (lpColumn->mask & LVCF_ORDER)
550 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
552 if (len == -1) goto end; buf += len; size -= len;
555 buf = text + strlen(text);
557 if (buf - text > 2) buf[-2] = '}'; buf[-1] = 0;
562 /******** Notification functions i************************************/
564 static LRESULT notify_hdr(LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
568 TRACE("(code=%d)\n", code);
570 pnmh->hwndFrom = infoPtr->hwndSelf;
571 pnmh->idFrom = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
573 result = SendMessageW(GetParent(infoPtr->hwndSelf), WM_NOTIFY,
574 (WPARAM)pnmh->idFrom, (LPARAM)pnmh);
576 TRACE(" <= %ld\n", result);
581 static inline LRESULT notify(LISTVIEW_INFO *infoPtr, INT code)
584 return notify_hdr(infoPtr, code, &nmh);
587 static inline void notify_itemactivate(LISTVIEW_INFO *infoPtr)
589 notify(infoPtr, LVN_ITEMACTIVATE);
592 static inline LRESULT notify_listview(LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
594 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
595 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
598 static LRESULT notify_click(LISTVIEW_INFO *infoPtr, INT code, LVHITTESTINFO *lvht)
602 ZeroMemory(&nmlv, sizeof(nmlv));
603 nmlv.iItem = lvht->iItem;
604 nmlv.iSubItem = lvht->iSubItem;
605 nmlv.ptAction = lvht->pt;
606 return notify_listview(infoPtr, code, &nmlv);
609 static int get_ansi_notification(INT unicodeNotificationCode)
611 switch (unicodeNotificationCode)
613 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
614 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
615 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
616 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
617 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
618 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
620 ERR("unknown notification %x\n", unicodeNotificationCode);
625 Send notification. depends on dispinfoW having same
626 structure as dispinfoA.
627 infoPtr : listview struct
628 notificationCode : *Unicode* notification code
629 pdi : dispinfo structure (can be unicode or ansi)
630 isW : TRUE if dispinfo is Unicode
632 static BOOL notify_dispinfoT(LISTVIEW_INFO *infoPtr, INT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
634 BOOL bResult = FALSE;
635 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
637 INT cchTempBufMax = 0, savCchTextMax = 0;
638 LPWSTR pszTempBuf = NULL, savPszText = NULL;
640 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
642 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
643 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
646 if (convertToAnsi || convertToUnicode)
648 if (notificationCode != LVN_GETDISPINFOW)
650 cchTempBufMax = convertToUnicode ?
651 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
652 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
656 cchTempBufMax = pdi->item.cchTextMax;
657 *pdi->item.pszText = 0; /* make sure we don't process garbage */
660 pszTempBuf = HeapAlloc(GetProcessHeap(), 0,
661 (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
662 if (!pszTempBuf) return FALSE;
663 if (convertToUnicode)
664 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
665 pszTempBuf, cchTempBufMax);
667 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
668 cchTempBufMax, NULL, NULL);
669 savCchTextMax = pdi->item.cchTextMax;
670 savPszText = pdi->item.pszText;
671 pdi->item.pszText = pszTempBuf;
672 pdi->item.cchTextMax = cchTempBufMax;
675 if (infoPtr->notifyFormat == NFR_ANSI)
676 realNotifCode = get_ansi_notification(notificationCode);
678 realNotifCode = notificationCode;
679 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
680 bResult = notify_hdr(infoPtr, realNotifCode, (LPNMHDR)pdi);
682 if (convertToUnicode || convertToAnsi)
684 if (convertToUnicode) /* note : pointer can be changed by app ! */
685 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
686 savCchTextMax, NULL, NULL);
688 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
689 savPszText, savCchTextMax);
690 pdi->item.pszText = savPszText; /* restores our buffer */
691 pdi->item.cchTextMax = savCchTextMax;
692 HeapFree(GetProcessHeap(), 0, pszTempBuf);
697 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, LISTVIEW_INFO *infoPtr, HDC hdc, LPRECT rcBounds)
699 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
700 lpnmlvcd->nmcd.hdc = hdc;
701 lpnmlvcd->nmcd.rc = *rcBounds;
702 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
703 lpnmlvcd->clrText = infoPtr->clrText;
706 static inline DWORD notify_customdraw (LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
708 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
709 return notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
712 /******** Item iterator functions **********************************/
714 static RANGES ranges_create(int count);
715 static void ranges_destroy(RANGES ranges);
716 static BOOL ranges_add(RANGES ranges, RANGE range);
717 static BOOL ranges_del(RANGES ranges, RANGE range);
718 static void ranges_dump(RANGES ranges);
720 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
722 RANGE range = { nItem, nItem + 1 };
724 return ranges_add(ranges, range);
727 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
729 RANGE range = { nItem, nItem + 1 };
731 return ranges_del(ranges, range);
735 * ITERATOR DOCUMENTATION
737 * The iterator functions allow for easy, and convenient iteration
738 * over items of iterest in the list. Typically, you create a
739 * iterator, use it, and destroy it, as such:
742 * iterator_xxxitems(&i, ...);
743 * while (iterator_{prev,next}(&i)
745 * //code which uses i.nItem
747 * iterator_destroy(&i);
749 * where xxx is either: framed, or visible.
750 * Note that it is important that the code destroys the iterator
751 * after it's done with it, as the creation of the iterator may
752 * allocate memory, which thus needs to be freed.
754 * You can iterate both forwards, and backwards through the list,
755 * by using iterator_next or iterator_prev respectively.
757 * Lower numbered items are draw on top of higher number items in
758 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
759 * items may overlap). So, to test items, you should use
761 * which lists the items top to bottom (in Z-order).
762 * For drawing items, you should use
764 * which lists the items bottom to top (in Z-order).
765 * If you keep iterating over the items after the end-of-items
766 * marker (-1) is returned, the iterator will start from the
767 * beginning. Typically, you don't need to test for -1,
768 * because iterator_{next,prev} will return TRUE if more items
769 * are to be iterated over, or FALSE otherwise.
771 * Note: the iterator is defined to be bidirectional. That is,
772 * any number of prev followed by any number of next, or
773 * five versa, should leave the iterator at the same item:
774 * prev * n, next * n = next * n, prev * n
776 * The iterator has a notion of a out-of-order, special item,
777 * which sits at the start of the list. This is used in
778 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
779 * which needs to be first, as it may overlap other items.
781 * The code is a bit messy because we have:
782 * - a special item to deal with
783 * - simple range, or composite range
785 * If find bugs, or want to add features, please make sure you
786 * always check/modify *both* iterator_prev, and iterator_next.
790 * This function iterates through the items in increasing order,
791 * but prefixed by the special item, then -1. That is:
792 * special, 1, 2, 3, ..., n, -1.
793 * Each item is listed only once.
795 static inline BOOL iterator_next(ITERATOR* i)
799 i->nItem = i->nSpecial;
800 if (i->nItem != -1) return TRUE;
802 if (i->nItem == i->nSpecial)
804 if (i->ranges) i->index = 0;
810 if (i->nItem == i->nSpecial) i->nItem++;
811 if (i->nItem < i->range.upper) return TRUE;
816 if (i->index < i->ranges->hdpa->nItemCount)
817 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
820 else if (i->nItem >= i->range.upper) goto end;
822 i->nItem = i->range.lower;
823 if (i->nItem >= 0) goto testitem;
830 * This function iterates through the items in decreasing order,
831 * followed by the special item, then -1. That is:
832 * n, n-1, ..., 3, 2, 1, special, -1.
833 * Each item is listed only once.
835 static inline BOOL iterator_prev(ITERATOR* i)
842 if (i->ranges) i->index = i->ranges->hdpa->nItemCount;
845 if (i->nItem == i->nSpecial)
853 if (i->nItem == i->nSpecial) i->nItem--;
854 if (i->nItem >= i->range.lower) return TRUE;
860 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
863 else if (!start && i->nItem < i->range.lower) goto end;
865 i->nItem = i->range.upper;
866 if (i->nItem > 0) goto testitem;
868 return (i->nItem = i->nSpecial) != -1;
871 static RANGE iterator_range(ITERATOR* i)
875 if (!i->ranges) return i->range;
877 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
878 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->ranges->hdpa->nItemCount - 1)).upper;
883 * Releases resources associated with this ierator.
885 static inline void iterator_destroy(ITERATOR* i)
887 if (i->ranges) ranges_destroy(i->ranges);
891 * Create an empty iterator.
893 static inline BOOL iterator_empty(ITERATOR* i)
895 ZeroMemory(i, sizeof(*i));
896 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
902 * Create an iterator over a bunch of ranges.
903 * Please note that the iterator will take ownership of the ranges,
904 * and will free them upon destruction.
906 static inline BOOL iterator_ranges(ITERATOR* i, RANGES ranges)
914 * Creates an iterator over the items which intersect lprc.
916 static BOOL iterator_frameditems(ITERATOR* i, LISTVIEW_INFO* infoPtr, const RECT* lprc)
918 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
919 RECT frame = *lprc, rcItem, rcTemp;
922 /* in case we fail, we want to return an empty iterator */
923 if (!iterator_empty(i)) return FALSE;
925 LISTVIEW_GetOrigin(infoPtr, &Origin);
927 TRACE("(lprc=%s)\n", debugrect(lprc));
928 OffsetRect(&frame, -Origin.x, -Origin.y);
930 if (uView == LVS_ICON || uView == LVS_SMALLICON)
934 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
936 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
937 if (IntersectRect(&rcTemp, &rcItem, lprc))
938 i->nSpecial = infoPtr->nFocusedItem;
940 if (!(i->ranges = ranges_create(50))) return FALSE;
941 /* to do better here, we need to have PosX, and PosY sorted */
942 TRACE("building icon ranges:\n");
943 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
945 rcItem.left = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
946 rcItem.top = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
947 rcItem.right = rcItem.left + infoPtr->nItemWidth;
948 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
949 if (IntersectRect(&rcTemp, &rcItem, &frame))
950 ranges_additem(i->ranges, nItem);
952 if (TRACE_ON(listview))
954 TRACE(" icon items:\n");
955 ranges_dump(i->ranges);
959 else if (uView == LVS_REPORT)
963 if (frame.left >= infoPtr->nItemWidth) return TRUE;
964 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
966 lower = max(frame.top / infoPtr->nItemHeight, 0);
967 upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1);
968 if (upper < lower) return TRUE;
969 i->range.lower = lower;
970 i->range.upper = upper + 1;
971 TRACE(" report=%s\n", debugrange(&i->range));
975 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
976 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
977 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
978 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
979 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
980 INT lower = nFirstCol * nPerCol + nFirstRow;
984 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
985 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
987 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
989 if (!(i->ranges = ranges_create(nLastCol - nFirstCol + 1))) return FALSE;
990 TRACE("building list ranges:\n");
991 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
993 item_range.lower = nCol * nPerCol + nFirstRow;
994 if(item_range.lower >= infoPtr->nItemCount) break;
995 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
996 TRACE(" list=%s\n", debugrange(&item_range));
997 ranges_add(i->ranges, item_range);
1005 * Creates an iterator over the items which intersect the visible region of hdc.
1007 static BOOL iterator_visibleitems(ITERATOR* i, LISTVIEW_INFO *infoPtr, HDC hdc)
1009 POINT Origin, Position;
1010 RECT rcItem, rcClip;
1013 rgntype = GetClipBox(hdc, &rcClip);
1014 if (rgntype == NULLREGION) return iterator_empty(i);
1015 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1016 if (rgntype == SIMPLEREGION) return TRUE;
1018 /* first deal with the special item */
1019 if (i->nSpecial != -1)
1021 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1022 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1025 /* if we can't deal with the region, we'll just go with the simple range */
1026 LISTVIEW_GetOrigin(infoPtr, &Origin);
1027 TRACE("building visible range:\n");
1028 if (!i->ranges && i->range.lower < i->range.upper)
1030 if (!(i->ranges = ranges_create(50))) return TRUE;
1031 if (!ranges_add(i->ranges, i->range))
1033 ranges_destroy(i->ranges);
1039 /* now delete the invisible items from the list */
1040 while(iterator_next(i))
1042 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1043 rcItem.left = Position.x + Origin.x;
1044 rcItem.top = Position.y + Origin.y;
1045 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1046 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1047 if (!RectVisible(hdc, &rcItem))
1048 ranges_delitem(i->ranges, i->nItem);
1050 /* the iterator should restart on the next iterator_next */
1056 /******** Misc helper functions ************************************/
1058 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1059 WPARAM wParam, LPARAM lParam, BOOL isW)
1061 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1062 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1065 /******** Internal API functions ************************************/
1067 /* The Invalidate* are macros, so we preserve the caller location */
1068 #define LISTVIEW_InvalidateRect(infoPtr, rect) while(infoPtr->bRedraw) { \
1069 TRACE(" invalidating rect=%s\n", debugrect(rect)); \
1070 InvalidateRect(infoPtr->hwndSelf, rect, TRUE); \
1074 #define LISTVIEW_InvalidateItem(infoPtr, nItem) do { \
1076 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox); \
1077 LISTVIEW_InvalidateRect(infoPtr, &rcBox); \
1080 #define LISTVIEW_InvalidateSubItem(infoPtr, nItem, nSubItem) do { \
1081 POINT Origin, Position; \
1083 LISTVIEW_GetOrigin(infoPtr, &Origin); \
1084 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position); \
1085 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox); \
1086 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y); \
1087 LISTVIEW_InvalidateRect(infoPtr, &rcBox); \
1090 #define LISTVIEW_InvalidateList(infoPtr)\
1091 LISTVIEW_InvalidateRect(infoPtr, NULL)
1094 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(LISTVIEW_INFO *infoPtr, INT nSubItem)
1096 COLUMN_INFO *columnInfo;
1097 assert (nSubItem >= 0 && nSubItem < infoPtr->hdpaColumns->nItemCount);
1098 columnInfo = (COLUMN_INFO *)DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1099 assert (columnInfo);
1103 static inline void LISTVIEW_GetHeaderRect(LISTVIEW_INFO *infoPtr, INT nSubItem, RECT *lprc)
1105 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1108 static inline BOOL LISTVIEW_GetItemW(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1110 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1115 * Retrieves the number of items that can fit vertically in the client area.
1118 * [I] infoPtr : valid pointer to the listview structure
1121 * Number of items per row.
1123 static inline INT LISTVIEW_GetCountPerRow(LISTVIEW_INFO *infoPtr)
1125 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1127 return max(nListWidth/infoPtr->nItemWidth, 1);
1132 * Retrieves the number of items that can fit horizontally in the client
1136 * [I] infoPtr : valid pointer to the listview structure
1139 * Number of items per column.
1141 static inline INT LISTVIEW_GetCountPerColumn(LISTVIEW_INFO *infoPtr)
1143 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1145 return max(nListHeight / infoPtr->nItemHeight, 1);
1149 /*************************************************************************
1150 * LISTVIEW_ProcessLetterKeys
1152 * Processes keyboard messages generated by pressing the letter keys
1154 * What this does is perform a case insensitive search from the
1155 * current position with the following quirks:
1156 * - If two chars or more are pressed in quick succession we search
1157 * for the corresponding string (e.g. 'abc').
1158 * - If there is a delay we wipe away the current search string and
1159 * restart with just that char.
1160 * - If the user keeps pressing the same character, whether slowly or
1161 * fast, so that the search string is entirely composed of this
1162 * character ('aaaaa' for instance), then we search for first item
1163 * that starting with that character.
1164 * - If the user types the above character in quick succession, then
1165 * we must also search for the corresponding string ('aaaaa'), and
1166 * go to that string if there is a match.
1169 * [I] hwnd : handle to the window
1170 * [I] charCode : the character code, the actual character
1171 * [I] keyData : key data
1179 * - The current implementation has a list of characters it will
1180 * accept and it ignores averything else. In particular it will
1181 * ignore accentuated characters which seems to match what
1182 * Windows does. But I'm not sure it makes sense to follow
1184 * - We don't sound a beep when the search fails.
1188 * TREEVIEW_ProcessLetterKeys
1190 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1195 WCHAR buffer[MAX_PATH];
1196 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1198 /* simple parameter checking */
1199 if (!charCode || !keyData) return 0;
1201 /* only allow the valid WM_CHARs through */
1202 if (!isalnum(charCode) &&
1203 charCode != '.' && charCode != '`' && charCode != '!' &&
1204 charCode != '@' && charCode != '#' && charCode != '$' &&
1205 charCode != '%' && charCode != '^' && charCode != '&' &&
1206 charCode != '*' && charCode != '(' && charCode != ')' &&
1207 charCode != '-' && charCode != '_' && charCode != '+' &&
1208 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1209 charCode != '}' && charCode != '[' && charCode != '{' &&
1210 charCode != '/' && charCode != '?' && charCode != '>' &&
1211 charCode != '<' && charCode != ',' && charCode != '~')
1214 /* if there's one item or less, there is no where to go */
1215 if (infoPtr->nItemCount <= 1) return 0;
1217 /* update the search parameters */
1218 infoPtr->lastKeyPressTimestamp = GetTickCount();
1219 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1220 if (infoPtr->nSearchParamLength < MAX_PATH)
1221 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1222 if (infoPtr->charCode != charCode)
1223 infoPtr->charCode = charCode = 0;
1225 infoPtr->charCode=charCode;
1226 infoPtr->szSearchParam[0]=charCode;
1227 infoPtr->nSearchParamLength=1;
1228 /* Redundant with the 1 char string */
1232 /* and search from the current position */
1234 if (infoPtr->nFocusedItem >= 0) {
1235 endidx=infoPtr->nFocusedItem;
1237 /* if looking for single character match,
1238 * then we must always move forward
1240 if (infoPtr->nSearchParamLength == 1)
1243 endidx=infoPtr->nItemCount;
1247 if (idx == infoPtr->nItemCount) {
1248 if (endidx == infoPtr->nItemCount || endidx == 0)
1254 item.mask = LVIF_TEXT;
1257 item.pszText = buffer;
1258 item.cchTextMax = MAX_PATH;
1259 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1261 /* check for a match */
1262 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1265 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1266 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1267 /* This would work but we must keep looking for a longer match */
1271 } while (idx != endidx);
1274 LISTVIEW_KeySelection(infoPtr, nItem);
1279 /*************************************************************************
1280 * LISTVIEW_UpdateHeaderSize [Internal]
1282 * Function to resize the header control
1285 * [I] hwnd : handle to a window
1286 * [I] nNewScrollPos : scroll pos to set
1291 static void LISTVIEW_UpdateHeaderSize(LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1296 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1298 GetWindowRect(infoPtr->hwndHeader, &winRect);
1299 point[0].x = winRect.left;
1300 point[0].y = winRect.top;
1301 point[1].x = winRect.right;
1302 point[1].y = winRect.bottom;
1304 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1305 point[0].x = -nNewScrollPos;
1306 point[1].x += nNewScrollPos;
1308 SetWindowPos(infoPtr->hwndHeader,0,
1309 point[0].x,point[0].y,point[1].x,point[1].y,
1310 SWP_NOZORDER | SWP_NOACTIVATE);
1315 * Update the scrollbars. This functions should be called whenever
1316 * the content, size or view changes.
1319 * [I] infoPtr : valid pointer to the listview structure
1324 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *infoPtr)
1326 LONG lStyle = infoPtr->dwStyle;
1327 UINT uView = lStyle & LVS_TYPEMASK;
1328 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1329 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1330 SCROLLINFO scrollInfo;
1332 if (lStyle & LVS_NOSCROLL) return;
1334 scrollInfo.cbSize = sizeof(SCROLLINFO);
1336 if (uView == LVS_LIST)
1338 /* update horizontal scrollbar */
1339 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1340 INT nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
1342 TRACE("items=%d, perColumn=%d, perRow=%d\n",
1343 infoPtr->nItemCount, nCountPerColumn, nCountPerRow);
1345 scrollInfo.nMin = 0;
1346 scrollInfo.nMax = infoPtr->nItemCount / nCountPerColumn;
1347 if((infoPtr->nItemCount % nCountPerColumn) == 0)
1349 if (scrollInfo.nMax < 0) scrollInfo.nMax = 0;
1350 scrollInfo.nPos = ListView_GetTopIndex(infoPtr->hwndSelf) / nCountPerColumn;
1351 scrollInfo.nPage = nCountPerRow;
1352 scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
1353 SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
1354 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
1356 else if (uView == LVS_REPORT)
1360 /* update vertical scrollbar */
1361 scrollInfo.nMin = 0;
1362 scrollInfo.nMax = infoPtr->nItemCount - 1;
1363 scrollInfo.nPos = ListView_GetTopIndex(infoPtr->hwndSelf);
1364 scrollInfo.nPage = LISTVIEW_GetCountPerColumn(infoPtr);
1365 scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
1366 test = (scrollInfo.nMin >= scrollInfo.nMax - max((INT)scrollInfo.nPage - 1, 0));
1367 TRACE("LVS_REPORT Vert. nMax=%d, nPage=%d, test=%d\n",
1368 scrollInfo.nMax, scrollInfo.nPage, test);
1369 SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
1370 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, (test) ? FALSE : TRUE);
1372 /* update horizontal scrollbar */
1373 nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1374 scrollInfo.fMask = SIF_POS;
1375 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)
1376 || infoPtr->nItemCount == 0)
1378 scrollInfo.nPos = 0;
1380 scrollInfo.nMin = 0;
1381 scrollInfo.nMax = max(infoPtr->nItemWidth, 0)-1;
1382 scrollInfo.nPage = nListWidth;
1383 scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE ;
1384 test = (scrollInfo.nMin >= scrollInfo.nMax - max((INT)scrollInfo.nPage - 1, 0));
1385 TRACE("LVS_REPORT Horz. nMax=%d, nPage=%d, test=%d\n",
1386 scrollInfo.nMax, scrollInfo.nPage, test);
1387 SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
1388 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, (test) ? FALSE : TRUE);
1390 /* Update the Header Control */
1391 scrollInfo.fMask = SIF_POS;
1392 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo);
1393 LISTVIEW_UpdateHeaderSize(infoPtr, scrollInfo.nPos);
1400 if (LISTVIEW_GetViewRect(infoPtr, &rcView))
1402 INT nViewWidth = rcView.right - rcView.left;
1403 INT nViewHeight = rcView.bottom - rcView.top;
1405 /* Update Horizontal Scrollbar */
1406 scrollInfo.fMask = SIF_POS;
1407 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)
1408 || infoPtr->nItemCount == 0)
1410 scrollInfo.nPos = 0;
1412 scrollInfo.nMin = 0;
1413 scrollInfo.nMax = max(nViewWidth, 0)-1;
1414 scrollInfo.nPage = nListWidth;
1415 scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
1416 TRACE("LVS_ICON/SMALLICON Horz.\n");
1417 SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
1419 /* Update Vertical Scrollbar */
1420 nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1421 scrollInfo.fMask = SIF_POS;
1422 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)
1423 || infoPtr->nItemCount == 0)
1425 scrollInfo.nPos = 0;
1427 scrollInfo.nMin = 0;
1428 scrollInfo.nMax = max(nViewHeight,0)-1;
1429 scrollInfo.nPage = nListHeight;
1430 scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
1431 TRACE("LVS_ICON/SMALLICON Vert.\n");
1432 SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
1440 * Shows/hides the focus rectangle.
1443 * [I] infoPtr : valid pointer to the listview structure
1444 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1449 static void LISTVIEW_ShowFocusRect(LISTVIEW_INFO *infoPtr, BOOL fShow)
1451 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1454 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1456 if (infoPtr->nFocusedItem < 0) return;
1458 /* we need some gymnastics in ICON mode to handle large items */
1459 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1463 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1464 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1466 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1471 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1473 /* for some reason, owner draw should work only in report mode */
1474 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1479 item.iItem = infoPtr->nFocusedItem;
1481 item.mask = LVIF_PARAM;
1482 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1484 ZeroMemory(&dis, sizeof(dis));
1485 dis.CtlType = ODT_LISTVIEW;
1486 dis.CtlID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
1487 dis.itemID = item.iItem;
1488 dis.itemAction = ODA_FOCUS;
1489 if (fShow) dis.itemState |= ODS_FOCUS;
1490 dis.hwndItem = infoPtr->hwndSelf;
1492 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1493 dis.itemData = item.lParam;
1495 SendMessageW(GetParent(infoPtr->hwndSelf), WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1499 DrawFocusRect(hdc, &infoPtr->rcFocus);
1502 ReleaseDC(infoPtr->hwndSelf, hdc);
1506 * Invalidates all visible selected items.
1508 static void LISTVIEW_InvalidateSelectedItems(LISTVIEW_INFO *infoPtr)
1512 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1513 while(iterator_next(&i))
1515 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1516 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1518 iterator_destroy(&i);
1524 * Prints a message for unsupported window styles.
1525 * A kind of TODO list for window styles.
1528 * [I] lStyle : window style
1533 static void LISTVIEW_UnsupportedStyles(LONG lStyle)
1535 if ((LVS_TYPESTYLEMASK & lStyle) == LVS_NOSCROLL)
1536 FIXME(" LVS_NOSCROLL\n");
1538 if (lStyle & LVS_NOLABELWRAP)
1539 FIXME(" LVS_NOLABELWRAP\n");
1541 if (lStyle & LVS_SORTASCENDING)
1542 FIXME(" LVS_SORTASCENDING\n");
1544 if (lStyle & LVS_SORTDESCENDING)
1545 FIXME(" LVS_SORTDESCENDING\n");
1550 * DESCRIPTION: [INTERNAL]
1551 * Computes an item's (left,top) corner, relative to rcView.
1552 * That is, the position has NOT been made relative to the Origin.
1553 * This is deliberate, to avoid computing the Origin over, and
1554 * over again, when this function is call in a loop. Instead,
1555 * one ca factor the computation of the Origin before the loop,
1556 * and offset the value retured by this function, on every iteration.
1559 * [I] infoPtr : valid pointer to the listview structure
1560 * [I] nItem : item number
1561 * [O] lpptOrig : item top, left corner
1566 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1568 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1570 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1572 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1574 lpptPosition->x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1575 lpptPosition->y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1577 else if (uView == LVS_LIST)
1579 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1580 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1581 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1583 else /* LVS_REPORT */
1585 lpptPosition->x = REPORT_MARGINX;
1586 lpptPosition->y = nItem * infoPtr->nItemHeight;
1591 * DESCRIPTION: [INTERNAL]
1592 * Compute the rectangles of an item. This is to localize all
1593 * the computations in one place. If you are not interested in some
1594 * of these values, simply pass in a NULL -- the fucntion is smart
1595 * enough to compute only what's necessary. The function computes
1596 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1597 * one, the BOX rectangle. This rectangle is very cheap to compute,
1598 * and is guaranteed to contain all the other rectangles. Computing
1599 * the ICON rect is also cheap, but all the others are potentaily
1600 * expensive. This gives an easy and effective optimization when
1601 * searching (like point inclusion, or rectangle intersection):
1602 * first test against the BOX, and if TRUE, test agains the desired
1604 * If the function does not have all the necessary information
1605 * to computed the requested rectangles, will crash with a
1606 * failed assertion. This is done so we catch all programming
1607 * errors, given that the function is called only from our code.
1609 * We have the following 'special' meanings for a few fields:
1610 * * If LVIS_FOCUSED is set, we assume the item has the focus
1611 * This is important in ICON mode, where it might get a larger
1612 * then usual rectange
1614 * Please note that subitem support works only in REPORT mode.
1617 * [I] infoPtr : valid pointer to the listview structure
1618 * [I] lpLVItem : item to compute the measures for
1619 * [O] lprcBox : ptr to Box rectangle
1620 * The internal LVIR_BOX rectangle
1621 * [0] lprcState : ptr to State icon rectangle
1622 * The internal LVIR_STATE rectangle
1623 * [O] lprcIcon : ptr to Icon rectangle
1624 * Same as LVM_GETITEMRECT with LVIR_ICON
1625 * [O] lprcLabel : ptr to Label rectangle
1626 * Same as LVM_GETITEMRECT with LVIR_LABEL
1631 static void LISTVIEW_GetItemMetrics(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem,
1632 LPRECT lprcBox, LPRECT lprcState,
1633 LPRECT lprcIcon, LPRECT lprcLabel)
1635 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1636 BOOL doState = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1637 RECT Box, State, Icon, Label;
1638 COLUMN_INFO *lpColumnInfo = NULL;
1640 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1642 /* Be smart and try to figure out the minimum we have to do */
1643 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1644 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1646 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1647 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1649 if (lprcLabel) doLabel = TRUE;
1650 if (doLabel || lprcIcon) doIcon = TRUE;
1651 if (doIcon || lprcState) doState = TRUE;
1653 /************************************************************/
1654 /* compute the box rectangle (it should be cheap to do) */
1655 /************************************************************/
1656 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1657 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1659 if (lpLVItem->iSubItem)
1661 Box = lpColumnInfo->rcHeader;
1666 Box.right = infoPtr->nItemWidth;
1669 Box.bottom = infoPtr->nItemHeight;
1671 /************************************************************/
1672 /* compute STATEICON bounding box */
1673 /************************************************************/
1676 if (uView == LVS_ICON)
1678 State.left = Box.left - infoPtr->iconStateSize.cx - 2;
1679 if (infoPtr->himlNormal)
1680 State.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1681 State.top = Box.top + infoPtr->iconSize.cy - infoPtr->iconStateSize.cy + 4;
1685 /* we need the ident in report mode, if we don't have it, we fail */
1686 State.left = Box.left;
1687 if (uView == LVS_REPORT)
1689 State.left += REPORT_MARGINX;
1690 if (lpLVItem->iSubItem == 0)
1692 assert(lpLVItem->mask & LVIF_INDENT);
1693 State.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
1696 State.top = Box.top;
1698 State.right = State.left;
1699 State.bottom = State.top;
1700 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1702 State.right += infoPtr->iconStateSize.cx;
1703 State.bottom += infoPtr->iconStateSize.cy;
1705 if (lprcState) *lprcState = State;
1706 TRACE(" - state=%s\n", debugrect(&State));
1709 /************************************************************/
1710 /* compute ICON bounding box (ala LVM_GETITEMRECT) */
1711 /************************************************************/
1714 if (uView == LVS_ICON)
1716 Icon.left = Box.left;
1717 if (infoPtr->himlNormal)
1718 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1719 Icon.top = Box.top + ICON_TOP_PADDING;
1720 Icon.right = Icon.left;
1721 Icon.bottom = Icon.top;
1722 if (infoPtr->himlNormal)
1724 Icon.right += infoPtr->iconSize.cx;
1725 Icon.bottom += infoPtr->iconSize.cy;
1728 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1730 Icon.left = State.right;
1731 if (!IsRectEmpty(&State)) Icon.left += IMAGE_PADDING;
1733 Icon.right = Icon.left;
1734 if (infoPtr->himlSmall && (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE)))
1735 Icon.right += infoPtr->iconSize.cx;
1736 Icon.bottom = Icon.top + infoPtr->nItemHeight;
1738 if(lprcIcon) *lprcIcon = Icon;
1739 TRACE(" - icon=%s\n", debugrect(&Icon));
1742 /************************************************************/
1743 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
1744 /************************************************************/
1747 SIZE labelSize = { 0, 0 };
1749 /* calculate how far to the right can the label strech */
1750 Label.right = Box.right;
1751 if (uView == LVS_REPORT)
1753 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
1754 Label.right -= REPORT_MARGINX;
1757 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
1759 labelSize.cx = infoPtr->nItemWidth;
1760 labelSize.cy = infoPtr->nItemHeight;
1764 /* we need the text in non owner draw mode */
1765 assert(lpLVItem->mask & LVIF_TEXT);
1766 if (is_textT(lpLVItem->pszText, TRUE))
1768 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1769 HDC hdc = GetDC(infoPtr->hwndSelf);
1770 HFONT hOldFont = SelectObject(hdc, hFont);
1774 /* compute rough rectangle where the label will go */
1775 SetRectEmpty(&rcText);
1776 rcText.right = infoPtr->nItemWidth - TRAILING_PADDING;
1777 rcText.bottom = infoPtr->nItemHeight;
1778 if (uView == LVS_ICON)
1779 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
1781 /* now figure out the flags */
1782 if (uView == LVS_ICON)
1783 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
1785 uFormat = LV_SL_DT_FLAGS;
1787 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
1789 labelSize.cx = min(rcText.right - rcText.left + TRAILING_PADDING, infoPtr->nItemWidth);
1790 labelSize.cy = rcText.bottom - rcText.top;
1792 SelectObject(hdc, hOldFont);
1793 ReleaseDC(infoPtr->hwndSelf, hdc);
1797 if (uView == LVS_ICON)
1799 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
1800 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
1801 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
1802 Label.right = Label.left + labelSize.cx;
1803 Label.bottom = Label.top + infoPtr->nItemHeight;
1804 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
1806 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
1807 labelSize.cy /= infoPtr->ntmHeight;
1808 labelSize.cy = max(labelSize.cy, 1);
1809 labelSize.cy *= infoPtr->ntmHeight;
1811 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
1813 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1815 Label.left = Icon.right;
1816 if (!IsRectEmpty(&Icon) || !IsRectEmpty(&State)) Label.left += IMAGE_PADDING;
1817 Label.top = Box.top;
1818 Label.right = min(Label.left + labelSize.cx, Label.right);
1819 Label.bottom = Label.top + infoPtr->nItemHeight;
1822 if (lprcLabel) *lprcLabel = Label;
1823 TRACE(" - label=%s\n", debugrect(&Label));
1826 /* Fix the Box if necessary */
1829 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
1830 else *lprcBox = Box;
1832 TRACE(" - box=%s\n", debugrect(&Box));
1836 * DESCRIPTION: [INTERNAL]
1839 * [I] infoPtr : valid pointer to the listview structure
1840 * [I] nItem : item number
1841 * [O] lprcBox : ptr to Box rectangle
1846 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
1848 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1849 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
1850 POINT Position, Origin;
1853 LISTVIEW_GetOrigin(infoPtr, &Origin);
1854 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1856 /* Be smart and try to figure out the minimum we have to do */
1858 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
1859 lvItem.mask |= LVIF_TEXT;
1860 lvItem.iItem = nItem;
1861 lvItem.iSubItem = 0;
1862 lvItem.pszText = szDispText;
1863 lvItem.cchTextMax = DISP_TEXT_SIZE;
1864 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
1865 if (uView == LVS_ICON)
1867 lvItem.mask |= LVIF_STATE;
1868 lvItem.stateMask = LVIS_FOCUSED;
1869 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
1871 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0);
1873 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
1878 * Aligns the items with the top edge of the window.
1881 * [I] infoPtr : valid pointer to the listview structure
1886 static void LISTVIEW_AlignTop(LISTVIEW_INFO *infoPtr)
1888 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1889 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1892 INT i, off_x=0, off_y=0;
1894 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1896 /* Since SetItemPosition uses upper-left of icon, and for
1897 style=LVS_ICON the icon is not left adjusted, get the offset */
1898 if (uView == LVS_ICON)
1900 off_y = ICON_TOP_PADDING;
1901 off_x = (infoPtr->iconSpacing.cx - infoPtr->iconSize.cx) / 2;
1905 ZeroMemory(&rcView, sizeof(RECT));
1906 TRACE("Icon off.x=%d, off.y=%d, left=%d, right=%d\n",
1908 infoPtr->rcList.left, infoPtr->rcList.right);
1910 if (nListWidth > infoPtr->nItemWidth)
1912 for (i = 0; i < infoPtr->nItemCount; i++)
1914 if ((ptItem.x-off_x) + infoPtr->nItemWidth > nListWidth)
1917 ptItem.y += infoPtr->nItemHeight;
1920 LISTVIEW_SetItemPosition(infoPtr, i, ptItem);
1921 ptItem.x += infoPtr->nItemWidth;
1922 rcView.right = max(rcView.right, ptItem.x);
1925 rcView.right -= off_x;
1926 rcView.bottom = (ptItem.y-off_y) + infoPtr->nItemHeight;
1930 for (i = 0; i < infoPtr->nItemCount; i++)
1932 LISTVIEW_SetItemPosition(infoPtr, i, ptItem);
1933 ptItem.y += infoPtr->nItemHeight;
1936 rcView.right = infoPtr->nItemWidth;
1937 rcView.bottom = ptItem.y-off_y;
1940 infoPtr->rcView = rcView;
1946 * Aligns the items with the left edge of the window.
1949 * [I] infoPtr : valid pointer to the listview structure
1954 static void LISTVIEW_AlignLeft(LISTVIEW_INFO *infoPtr)
1956 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1957 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1960 INT i, off_x=0, off_y=0;
1962 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1964 /* Since SetItemPosition uses upper-left of icon, and for
1965 style=LVS_ICON the icon is not left adjusted, get the offset */
1966 if (uView == LVS_ICON)
1968 off_y = ICON_TOP_PADDING;
1969 off_x = (infoPtr->iconSpacing.cx - infoPtr->iconSize.cx) / 2;
1973 ZeroMemory(&rcView, sizeof(RECT));
1974 TRACE("Icon off.x=%d, off.y=%d\n", off_x, off_y);
1976 if (nListHeight > infoPtr->nItemHeight)
1978 for (i = 0; i < infoPtr->nItemCount; i++)
1980 if (ptItem.y + infoPtr->nItemHeight > nListHeight)
1983 ptItem.x += infoPtr->nItemWidth;
1986 LISTVIEW_SetItemPosition(infoPtr, i, ptItem);
1987 ptItem.y += infoPtr->nItemHeight;
1988 rcView.bottom = max(rcView.bottom, ptItem.y);
1991 rcView.right = ptItem.x + infoPtr->nItemWidth;
1995 for (i = 0; i < infoPtr->nItemCount; i++)
1997 LISTVIEW_SetItemPosition(infoPtr, i, ptItem);
1998 ptItem.x += infoPtr->nItemWidth;
2001 rcView.bottom = infoPtr->nItemHeight;
2002 rcView.right = ptItem.x;
2005 infoPtr->rcView = rcView;
2012 * Retrieves the bounding rectangle of all the items.
2015 * [I] infoPtr : valid pointer to the listview structure
2016 * [O] lprcView : bounding rectangle
2022 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2026 TRACE("(lprcView=%p)\n", lprcView);
2028 if (!lprcView) return FALSE;
2030 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2032 *lprcView = infoPtr->rcView;
2033 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2035 TRACE("lprcView=%s\n", debugrect(lprcView));
2042 * Retrieves the subitem pointer associated with the subitem index.
2045 * [I] hdpaSubItems : DPA handle for a specific item
2046 * [I] nSubItem : index of subitem
2049 * SUCCESS : subitem pointer
2052 static LISTVIEW_SUBITEM* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2054 LISTVIEW_SUBITEM *lpSubItem;
2057 /* we should binary search here if need be */
2058 for (i = 1; i < hdpaSubItems->nItemCount; i++)
2060 lpSubItem = (LISTVIEW_SUBITEM *) DPA_GetPtr(hdpaSubItems, i);
2061 if (lpSubItem && (lpSubItem->iSubItem == nSubItem))
2071 * Calculates the width of a specific item.
2074 * [I] infoPtr : valid pointer to the listview structure
2075 * [I] nItem : item to calculate width, or -1 for max of all
2078 * Returns the width of an item width an item.
2080 static INT LISTVIEW_CalculateItemWidth(LISTVIEW_INFO *infoPtr, INT nItem)
2082 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2083 INT nItemWidth = 0, i;
2085 if (uView == LVS_ICON)
2086 nItemWidth = infoPtr->iconSpacing.cx;
2087 else if (uView == LVS_REPORT)
2091 /* calculate width of header */
2092 for (i = 0; i < infoPtr->hdpaColumns->nItemCount; i++)
2094 LISTVIEW_GetHeaderRect(infoPtr, i, &rcHeaderItem);
2095 nItemWidth += (rcHeaderItem.right - rcHeaderItem.left);
2102 if (infoPtr->nItemCount == 0) return DEFAULT_COLUMN_WIDTH;
2104 /* get width of string */
2107 for (i = 0; i < infoPtr->nItemCount; i++)
2109 nLabelWidth = LISTVIEW_GetLabelWidth(infoPtr, i);
2110 nItemWidth = max(nItemWidth, nLabelWidth);
2114 nItemWidth = LISTVIEW_GetLabelWidth(infoPtr, nItem);
2115 if (!nItemWidth) return DEFAULT_COLUMN_WIDTH;
2116 nItemWidth += WIDTH_PADDING;
2117 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2118 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2119 if (nItem == -1) nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth);
2122 return max(nItemWidth, 1);
2127 * Calculates the max width of any item in the list.
2130 * [I] infoPtr : valid pointer to the listview structure
2133 * Returns item width.
2135 static inline INT LISTVIEW_CalculateMaxWidth(LISTVIEW_INFO *infoPtr)
2137 return LISTVIEW_CalculateItemWidth(infoPtr, -1);
2142 * Retrieves and saves important text metrics info for the current
2146 * [I] infoPtr : valid pointer to the listview structure
2149 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2151 HDC hdc = GetDC(infoPtr->hwndSelf);
2152 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2153 HFONT hOldFont = SelectObject(hdc, hFont);
2156 if (GetTextMetricsW(hdc, &tm))
2157 infoPtr->ntmHeight = tm.tmHeight;
2158 SelectObject(hdc, hOldFont);
2159 ReleaseDC(infoPtr->hwndSelf, hdc);
2161 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2167 * Calculates the height of an item.
2170 * [I] infoPtr : valid pointer to the listview structure
2173 * Returns item height.
2175 static INT LISTVIEW_CalculateMaxHeight(LISTVIEW_INFO *infoPtr)
2179 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
2180 nItemHeight = infoPtr->iconSpacing.cy;
2183 nItemHeight = infoPtr->ntmHeight;
2184 if (infoPtr->himlState)
2185 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2186 if (infoPtr->himlSmall)
2187 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2188 if (infoPtr->himlState || infoPtr->himlSmall)
2189 nItemHeight += HEIGHT_PADDING;
2196 * A compare function for ranges
2199 * [I] range1 : pointer to range 1;
2200 * [I] range2 : pointer to range 2;
2204 * > 0 : if range 1 > range 2
2205 * < 0 : if range 2 > range 1
2206 * = 0 : if range intersects range 2
2208 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2212 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2214 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2219 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2225 #define ranges_check ranges_assert
2227 #define ranges_check(ranges, desc) do { } while(0)
2230 static void ranges_assert(RANGES ranges, LPCSTR desc)
2236 assert (ranges->hdpa->nItemCount >= 0);
2237 if (ranges->hdpa->nItemCount == 0) return;
2238 TRACE("*** Checking %s ***\n", desc);
2239 ranges_dump(ranges);
2240 assert ((prev = (RANGE *)DPA_GetPtr(ranges->hdpa, 0))->lower >= 0);
2241 /* assert (((RANGE *)DPA_GetPtr(ranges->hdpa, ranges->hdpa->nItemCount - 1))->upper <= nUpper); */
2242 for (i = 1; i < ranges->hdpa->nItemCount; i++)
2244 curr = (RANGE *)DPA_GetPtr(ranges->hdpa, i);
2245 assert (prev->upper <= curr->lower);
2248 TRACE("--- Done checking---\n");
2251 static RANGES ranges_create(int count)
2253 RANGES ranges = (RANGES)COMCTL32_Alloc(sizeof(struct tagRANGES));
2254 if (!ranges) return NULL;
2255 ranges->hdpa = DPA_Create(count);
2256 if (ranges->hdpa) return ranges;
2257 COMCTL32_Free(ranges);
2261 static void ranges_destroy(RANGES ranges)
2263 if (!ranges) return;
2268 for(i = 0; i < ranges->hdpa->nItemCount; i++)
2269 COMCTL32_Free(DPA_GetPtr(ranges->hdpa, i));
2270 DPA_Destroy(ranges->hdpa);
2271 ranges->hdpa = NULL;
2273 COMCTL32_Free(ranges);
2276 static RANGES ranges_clone(RANGES ranges)
2281 if (!ranges) return NULL;
2282 clone = ranges_create(ranges->hdpa->nItemCount);
2283 if (!clone) return NULL;
2285 for (i = 0; i < ranges->hdpa->nItemCount; i++)
2287 RANGE *newrng = (RANGE *)COMCTL32_Alloc(sizeof(RANGE));
2290 ranges_destroy(clone);
2293 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2294 DPA_InsertPtr(clone->hdpa, i, newrng);
2299 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2303 if (!ranges || !sub) return ranges;
2305 for (i = 0; i < sub->hdpa->nItemCount; i++)
2306 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2311 static void ranges_dump(RANGES ranges)
2315 if (!ranges) return;
2316 for (i = 0; i < ranges->hdpa->nItemCount; i++)
2317 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2320 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2322 RANGE srchrng = { nItem, nItem + 1 };
2324 TRACE("(nItem=%d)\n", nItem);
2325 if (!ranges) return FALSE;
2326 if (TRACE_ON(listview)) ranges_dump(ranges);
2327 ranges_check(ranges, "before contain");
2328 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2331 static INT ranges_itemcount(RANGES ranges)
2335 if (!ranges) return 0;
2336 for (i = 0; i < ranges->hdpa->nItemCount; i++)
2338 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2339 count += sel->upper - sel->lower;
2345 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2347 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2350 if (!ranges) return FALSE;
2351 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2352 if (index == -1) return TRUE;
2354 for (;index < ranges->hdpa->nItemCount; index++)
2356 chkrng = DPA_GetPtr(ranges->hdpa, index);
2357 if (chkrng->lower >= nItem)
2358 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2359 if (chkrng->upper > nItem)
2360 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2365 static BOOL ranges_add(RANGES ranges, RANGE range)
2370 TRACE("(%s)\n", debugrange(&range));
2371 ranges_check(ranges, "before add");
2372 if (!ranges) goto fail;
2374 /* try find overlapping regions first */
2375 srchrgn.lower = range.lower - 1;
2376 srchrgn.upper = range.upper + 1;
2377 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2383 TRACE("Adding new range\n");
2385 /* create the brand new range to insert */
2386 newrgn = (RANGE *)COMCTL32_Alloc(sizeof(RANGE));
2387 if(!newrgn) goto fail;
2390 /* figure out where to insert it */
2391 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2392 TRACE("index=%d\n", index);
2393 if (index == -1) index = 0;
2395 /* and get it over with */
2396 DPA_InsertPtr(ranges->hdpa, index, newrgn);
2400 RANGE *chkrgn, *mrgrgn;
2401 INT fromindex, mergeindex;
2403 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2404 if (!chkrgn) goto fail;
2405 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2407 chkrgn->lower = min(range.lower, chkrgn->lower);
2408 chkrgn->upper = max(range.upper, chkrgn->upper);
2410 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2412 /* merge now common anges */
2414 srchrgn.lower = chkrgn->lower - 1;
2415 srchrgn.upper = chkrgn->upper + 1;
2419 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2420 if (mergeindex == -1) break;
2421 if (mergeindex == index)
2423 fromindex = index + 1;
2427 TRACE("Merge with index %i\n", mergeindex);
2429 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2430 if (!mrgrgn) goto fail;
2432 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2433 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2434 COMCTL32_Free(mrgrgn);
2435 DPA_DeletePtr(ranges->hdpa, mergeindex);
2436 if (mergeindex < index) index --;
2440 ranges_check(ranges, "after add");
2441 if (TRACE_ON(listview)) ranges_dump(ranges);
2445 ranges_check(ranges, "failed add");
2449 static BOOL ranges_del(RANGES ranges, RANGE range)
2454 TRACE("(%s)\n", debugrange(&range));
2455 ranges_check(ranges, "before del");
2456 if (!ranges) goto fail;
2458 /* we don't use DPAS_SORTED here, since we need *
2459 * to find the first overlapping range */
2460 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2463 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2464 if (!chkrgn) goto fail;
2466 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2468 /* case 1: Same range */
2469 if ( (chkrgn->upper == range.upper) &&
2470 (chkrgn->lower == range.lower) )
2472 DPA_DeletePtr(ranges->hdpa, index);
2475 /* case 2: engulf */
2476 else if ( (chkrgn->upper <= range.upper) &&
2477 (chkrgn->lower >= range.lower) )
2479 DPA_DeletePtr(ranges->hdpa, index);
2481 /* case 3: overlap upper */
2482 else if ( (chkrgn->upper <= range.upper) &&
2483 (chkrgn->lower < range.lower) )
2485 chkrgn->upper = range.lower;
2487 /* case 4: overlap lower */
2488 else if ( (chkrgn->upper > range.upper) &&
2489 (chkrgn->lower >= range.lower) )
2491 chkrgn->lower = range.upper;
2494 /* case 5: fully internal */
2497 RANGE tmprgn = *chkrgn, *newrgn;
2499 if (!(newrgn = (RANGE *)COMCTL32_Alloc(sizeof(RANGE)))) goto fail;
2500 newrgn->lower = chkrgn->lower;
2501 newrgn->upper = range.lower;
2502 chkrgn->lower = range.upper;
2503 DPA_InsertPtr(ranges->hdpa, index, newrgn);
2508 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2511 ranges_check(ranges, "after del");
2515 ranges_check(ranges, "failed del");
2521 * Removes all selection ranges
2524 * [I] infoPtr : valid pointer to the listview structure
2525 * [I] toSkip : item range to skip removing the selection
2531 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2537 if (TRACE_ON(listview)) ranges_dump(toSkip);
2540 lvItem.stateMask = LVIS_SELECTED;
2542 /* need to clone the DPA because callbacks can change it */
2543 iterator_ranges(&i, ranges_diff(ranges_clone(infoPtr->selectionRanges), toSkip));
2544 while(iterator_next(&i))
2545 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2546 /* note that the iterator destructor will free the cloned range */
2547 iterator_destroy(&i);
2552 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2554 RANGES toSkip = ranges_create(1);
2556 if (!toSkip) return FALSE;
2557 if (nItem != -1) ranges_additem(toSkip, nItem);
2558 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2559 ranges_destroy(toSkip);
2563 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2565 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2570 * Retrieves the number of items that are marked as selected.
2573 * [I] infoPtr : valid pointer to the listview structure
2576 * Number of items selected.
2578 static INT LISTVIEW_GetSelectedCount(LISTVIEW_INFO *infoPtr)
2580 INT nSelectedCount = 0;
2582 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2585 for (i = 0; i < infoPtr->nItemCount; i++)
2587 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2592 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2594 TRACE("nSelectedCount=%d\n", nSelectedCount);
2595 return nSelectedCount;
2600 * Manages the item focus.
2603 * [I] infoPtr : valid pointer to the listview structure
2604 * [I] nItem : item index
2607 * TRUE : focused item changed
2608 * FALSE : focused item has NOT changed
2610 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2612 INT oldFocus = infoPtr->nFocusedItem;
2615 if (nItem == infoPtr->nFocusedItem) return FALSE;
2617 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2618 lvItem.stateMask = LVIS_FOCUSED;
2619 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
2621 return oldFocus != infoPtr->nFocusedItem;
2624 /* Helper function for LISTVIEW_ShiftIndices *only* */
2625 static INT shift_item(LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2627 if (nShiftItem < nItem) return nShiftItem;
2629 if (nShiftItem > nItem) return nShiftItem + direction;
2631 if (direction > 0) return nShiftItem + direction;
2633 return min(nShiftItem, infoPtr->nItemCount - 1);
2638 * Updates the various indices after an item has been inserted or deleted.
2641 * [I] infoPtr : valid pointer to the listview structure
2642 * [I] nItem : item index
2643 * [I] direction : Direction of shift, +1 or -1.
2648 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
2652 TRACE("Shifting %iu, %i steps\n", nItem, direction);
2654 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
2656 assert(abs(direction) == 1);
2658 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
2660 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
2661 if (nNewFocus != infoPtr->nFocusedItem)
2662 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
2664 /* But we are not supposed to modify nHotItem! */
2670 * Adds a block of selections.
2673 * [I] infoPtr : valid pointer to the listview structure
2674 * [I] nItem : item index
2679 static void LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2681 INT nFirst = min(infoPtr->nSelectionMark, nItem);
2682 INT nLast = max(infoPtr->nSelectionMark, nItem);
2689 item.state = LVIS_SELECTED;
2690 item.stateMask = LVIS_SELECTED;
2692 /* FIXME: this is not correct LVS_OWNERDATA
2693 * See docu for LVN_ITEMCHANGED. Is there something similar for
2694 * RemoveGroupSelection (is there such a thing?)?
2696 for (i = nFirst; i <= nLast; i++)
2697 LISTVIEW_SetItemState(infoPtr,i,&item);
2699 LISTVIEW_SetItemFocus(infoPtr, nItem);
2700 infoPtr->nSelectionMark = nItem;
2706 * Sets a single group selection.
2709 * [I] infoPtr : valid pointer to the listview structure
2710 * [I] nItem : item index
2715 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2717 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2722 if (!(selection = ranges_create(100))) return;
2724 item.state = LVIS_SELECTED;
2725 item.stateMask = LVIS_SELECTED;
2727 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
2729 if (infoPtr->nSelectionMark == -1)
2731 infoPtr->nSelectionMark = nItem;
2732 ranges_additem(selection, nItem);
2738 sel.lower = min(infoPtr->nSelectionMark, nItem);
2739 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
2740 ranges_add(selection, sel);
2745 RECT rcItem, rcSel, rcSelMark;
2748 rcItem.left = LVIR_BOUNDS;
2749 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
2750 rcSelMark.left = LVIR_BOUNDS;
2751 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
2752 UnionRect(&rcSel, &rcItem, &rcSelMark);
2753 iterator_frameditems(&i, infoPtr, &rcSel);
2754 while(iterator_next(&i))
2756 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
2757 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
2759 iterator_destroy(&i);
2762 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
2763 iterator_ranges(&i, selection);
2764 while(iterator_next(&i))
2765 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
2766 /* this will also destroy the selection */
2767 iterator_destroy(&i);
2769 LISTVIEW_SetItemFocus(infoPtr, nItem);
2774 * Sets a single selection.
2777 * [I] infoPtr : valid pointer to the listview structure
2778 * [I] nItem : item index
2783 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2787 TRACE("nItem=%d\n", nItem);
2789 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
2791 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
2792 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
2793 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
2795 infoPtr->nSelectionMark = nItem;
2800 * Set selection(s) with keyboard.
2803 * [I] infoPtr : valid pointer to the listview structure
2804 * [I] INT : item index
2807 * SUCCESS : TRUE (needs to be repainted)
2808 * FAILURE : FALSE (nothing has changed)
2810 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
2812 /* FIXME: pass in the state */
2813 LONG lStyle = infoPtr->dwStyle;
2814 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
2815 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
2816 BOOL bResult = FALSE;
2818 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
2820 if (lStyle & LVS_SINGLESEL)
2823 LISTVIEW_SetSelection(infoPtr, nItem);
2830 LISTVIEW_SetGroupSelection(infoPtr, nItem);
2834 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
2839 LISTVIEW_SetSelection(infoPtr, nItem);
2842 ListView_EnsureVisible(infoPtr->hwndSelf, nItem, FALSE);
2845 UpdateWindow(infoPtr->hwndSelf); /* update client area */
2852 * Called when the mouse is being actively tracked and has hovered for a specified
2856 * [I] infoPtr : valid pointer to the listview structure
2857 * [I] fwKeys : key indicator
2858 * [I] pts : mouse position
2861 * 0 if the message was processed, non-zero if there was an error
2864 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
2865 * over the item for a certain period of time.
2868 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKyes, POINTS pts)
2870 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
2871 /* FIXME: select the item!!! */
2872 /*LISTVIEW_GetItemAtPt(infoPtr, pt)*/;
2879 * Called whenever WM_MOUSEMOVE is received.
2882 * [I] infoPtr : valid pointer to the listview structure
2883 * [I] fwKeys : key indicator
2884 * [I] pts : mouse position
2887 * 0 if the message is processed, non-zero if there was an error
2889 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, POINTS pts)
2891 TRACKMOUSEEVENT trackinfo;
2893 /* see if we are supposed to be tracking mouse hovering */
2894 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
2895 /* fill in the trackinfo struct */
2896 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
2897 trackinfo.dwFlags = TME_QUERY;
2898 trackinfo.hwndTrack = infoPtr->hwndSelf;
2899 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
2901 /* see if we are already tracking this hwnd */
2902 _TrackMouseEvent(&trackinfo);
2904 if(!(trackinfo.dwFlags & TME_HOVER)) {
2905 trackinfo.dwFlags = TME_HOVER;
2907 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
2908 _TrackMouseEvent(&trackinfo);
2917 * Tests wheather the item is assignable to a list with style lStyle
2919 static inline BOOL is_assignable_item(LPLVITEMW lpLVItem, LONG lStyle)
2921 if ( (lpLVItem->mask & LVIF_TEXT) &&
2922 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
2923 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
2931 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
2934 * [I] infoPtr : valid pointer to the listview structure
2935 * [I] lpLVItem : valid pointer to new item atttributes
2936 * [I] isNew : the item being set is being inserted
2937 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
2938 * [O] bChanged : will be set to TRUE if the item really changed
2944 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
2946 LISTVIEW_ITEM *lpItem;
2953 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
2955 if (lpLVItem->mask == 0) return TRUE;
2957 if (infoPtr->dwStyle & LVS_OWNERDATA)
2959 /* a virtual listview we stores only selection and focus */
2960 if ((lpLVItem->mask & ~LVIF_STATE) || (lpLVItem->stateMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
2966 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
2967 if (!hdpaSubItems) return FALSE;
2968 if (!(lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0))) return FALSE;
2971 /* we need to get the lParam and state of the item */
2972 item.iItem = lpLVItem->iItem;
2973 item.iSubItem = lpLVItem->iSubItem;
2974 item.mask = LVIF_STATE | LVIF_PARAM;
2975 item.stateMask = ~0;
2978 if (!isNew && !LISTVIEW_GetItemT(infoPtr, &item, TRUE)) return FALSE;
2980 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
2981 /* determine what fields will change */
2982 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
2983 uChanged |= LVIF_STATE;
2985 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
2986 uChanged |= LVIF_IMAGE;
2988 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
2989 uChanged |= LVIF_PARAM;
2991 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
2992 uChanged |= LVIF_INDENT;
2994 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
2995 uChanged |= LVIF_TEXT;
2997 TRACE("uChanged=0x%x\n", uChanged);
2998 if (!uChanged) return TRUE;
3001 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3002 nmlv.iItem = lpLVItem->iItem;
3003 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3004 nmlv.uOldState = item.state;
3005 nmlv.uChanged = uChanged;
3006 nmlv.lParam = item.lParam;
3008 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3009 /* and we are _NOT_ virtual (LVS_OWERNDATA) */
3010 if(lpItem && !isNew && notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3013 /* copy information */
3014 if (lpLVItem->mask & LVIF_TEXT)
3015 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3017 if (lpLVItem->mask & LVIF_IMAGE)
3018 lpItem->hdr.iImage = lpLVItem->iImage;
3020 if (lpLVItem->mask & LVIF_PARAM)
3021 lpItem->lParam = lpLVItem->lParam;
3023 if (lpLVItem->mask & LVIF_INDENT)
3024 lpItem->iIndent = lpLVItem->iIndent;
3026 if (uChanged & LVIF_STATE)
3028 if (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED))
3030 lpItem->state &= ~lpLVItem->stateMask;
3031 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3033 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3035 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3036 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3038 else if (lpLVItem->stateMask & LVIS_SELECTED)
3039 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3041 /* if we are asked to change focus, and we manage it, do it */
3042 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3044 if (lpLVItem->state & LVIS_FOCUSED)
3046 LISTVIEW_SetItemFocus(infoPtr, -1);
3047 infoPtr->nFocusedItem = lpLVItem->iItem;
3048 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, FALSE);
3050 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3051 infoPtr->nFocusedItem = -1;
3055 /* if we're inserting the item, we're done */
3056 if (isNew) return TRUE;
3058 /* send LVN_ITEMCHANGED notification */
3059 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3060 notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3067 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3070 * [I] infoPtr : valid pointer to the listview structure
3071 * [I] lpLVItem : valid pointer to new subitem atttributes
3072 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3073 * [O] bChanged : will be set to TRUE if the item really changed
3079 static BOOL set_sub_item(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW, BOOL *bChanged)
3082 LISTVIEW_SUBITEM *lpSubItem;
3084 /* we do not support subitems for virtual listviews */
3085 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3087 /* set subitem only if column is present */
3088 if (lpLVItem->iSubItem >= infoPtr->hdpaColumns->nItemCount) return FALSE;
3090 /* First do some sanity checks */
3091 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE)) return FALSE;
3092 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE))) return TRUE;
3094 /* get the subitem structure, and create it if not there */
3095 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3096 if (!hdpaSubItems) return FALSE;
3098 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3101 LISTVIEW_SUBITEM *tmpSubItem;
3104 lpSubItem = (LISTVIEW_SUBITEM *)COMCTL32_Alloc(sizeof(LISTVIEW_SUBITEM));
3105 if (!lpSubItem) return FALSE;
3106 /* we could binary search here, if need be...*/
3107 for (i = 1; i < hdpaSubItems->nItemCount; i++)
3109 tmpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, i);
3110 if (tmpSubItem && tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3112 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3114 COMCTL32_Free(lpSubItem);
3117 lpSubItem->iSubItem = lpLVItem->iSubItem;
3121 if (lpLVItem->mask & LVIF_IMAGE)
3122 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3124 lpSubItem->hdr.iImage = lpLVItem->iImage;
3128 if (lpLVItem->mask & LVIF_TEXT)
3129 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3131 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3140 * Sets item attributes.
3143 * [I] infoPtr : valid pointer to the listview structure
3144 * [I] lpLVItem : new item atttributes
3145 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3151 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
3153 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3154 LPWSTR pszText = NULL;
3155 BOOL bResult, bChanged = FALSE;
3157 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3159 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3162 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3163 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3165 pszText = lpLVItem->pszText;
3166 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3169 /* actually set the fields */
3170 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3172 if (lpLVItem->iSubItem)
3173 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3175 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3177 /* redraw item, if necessary */
3178 if (bChanged && !infoPtr->bIsDrawing)
3180 /* this little optimization eliminates some nasty flicker */
3181 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3182 (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) || lpLVItem->iSubItem) )
3183 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3185 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3190 textfreeT(lpLVItem->pszText, isW);
3191 lpLVItem->pszText = pszText;
3199 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3202 * [I] infoPtr : valid pointer to the listview structure
3207 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *infoPtr)
3209 LONG lStyle = infoPtr->dwStyle;
3210 UINT uView = lStyle & LVS_TYPEMASK;
3212 SCROLLINFO scrollInfo;
3214 scrollInfo.cbSize = sizeof(SCROLLINFO);
3215 scrollInfo.fMask = SIF_POS;
3217 if (uView == LVS_LIST)
3219 if ((lStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3220 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3222 else if (uView == LVS_REPORT)
3224 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3225 nItem = scrollInfo.nPos;
3229 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3230 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3233 TRACE("nItem=%d\n", nItem);
3241 * Erases the background of the given rectangle
3244 * [I] infoPtr : valid pointer to the listview structure
3245 * [I] hdc : device context handle
3246 * [I] lprcBox : clipping rectangle
3252 static inline BOOL LISTVIEW_FillBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT* lprcBox)
3254 if (!infoPtr->hBkBrush) return FALSE;
3256 TRACE("(hdc=%x, lprcBox=%s, hBkBrush=%x)\n", hdc, debugrect(lprcBox), infoPtr->hBkBrush);
3258 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3266 * [I] infoPtr : valid pointer to the listview structure
3267 * [I] hdc : device context handle
3268 * [I] nItem : item index
3269 * [I] nSubItem : subitem index
3270 * [I] pos : item position in client coordinates
3271 * [I] cdmode : custom draw mode
3277 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3279 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3280 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3281 DWORD cditemmode = CDRF_DODEFAULT;
3282 RECT* lprcFocus, rcSelect, rcBox, rcState, rcIcon, rcLabel;
3283 NMLVCUSTOMDRAW nmlvcd;
3287 TRACE("(hdc=%x, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, debugpoint(&pos));
3289 /* get information needed for drawing the item */
3290 lvItem.mask = LVIF_TEXT | LVIF_IMAGE;
3291 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3292 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3293 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3294 lvItem.iItem = nItem;
3295 lvItem.iSubItem = nSubItem;
3296 lvItem.cchTextMax = DISP_TEXT_SIZE;
3297 lvItem.pszText = szDispText;
3298 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3299 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3301 /* now check if we need to update the focus rectangle */
3302 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3304 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3305 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
3306 OffsetRect(&rcBox, pos.x, pos.y);
3307 OffsetRect(&rcState, pos.x, pos.y);
3308 OffsetRect(&rcIcon, pos.x, pos.y);
3309 OffsetRect(&rcLabel, pos.x, pos.y);
3310 TRACE(" rcBox=%s, rcState=%s, rcIcon=%s. rcLabel=%s\n",
3311 debugrect(&rcBox), debugrect(&rcState), debugrect(&rcIcon), debugrect(&rcLabel));
3313 /* fill in the custom draw structure */
3314 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox);
3315 nmlvcd.nmcd.dwItemSpec = lvItem.iItem;
3316 nmlvcd.iSubItem = lvItem.iSubItem;
3317 if (lvItem.state & LVIS_SELECTED) nmlvcd.nmcd.uItemState |= CDIS_SELECTED;
3318 if (lvItem.state & LVIS_FOCUSED) nmlvcd.nmcd.uItemState |= CDIS_FOCUS;
3319 if (lvItem.iItem == infoPtr->nHotItem) nmlvcd.nmcd.uItemState |= CDIS_HOT;
3320 nmlvcd.nmcd.lItemlParam = lvItem.lParam;
3321 if ((lvItem.state & LVIS_SELECTED) &&
3322 (lvItem.iSubItem == 0 ||
3323 (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))))
3325 if (infoPtr->bFocus)
3327 nmlvcd.clrTextBk = comctl32_color.clrHighlight;
3328 nmlvcd.clrText = comctl32_color.clrHighlightText;
3330 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
3332 nmlvcd.clrTextBk = comctl32_color.clr3dFace;
3333 nmlvcd.clrText = comctl32_color.clrBtnText;
3337 if (cdmode & CDRF_NOTIFYITEMDRAW)
3338 cditemmode = notify_customdraw (infoPtr, CDDS_ITEMPREPAINT, &nmlvcd);
3339 if (cditemmode & CDRF_SKIPDEFAULT) goto postpaint;
3342 if (infoPtr->himlState && !IsRectEmpty(&rcState))
3344 UINT uStateImage = (lvItem.state & LVIS_STATEIMAGEMASK) >> 12;
3346 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc, rcState.left, rcState.top, ILD_NORMAL);
3350 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3351 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3352 ImageList_Draw(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3353 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3355 /* Don't bother painting item being edited */
3356 if (infoPtr->hwndEdit && lprcFocus && nSubItem == 0) goto postpaint;
3358 /* Set the text attributes */
3359 SetBkMode(hdc, nmlvcd.clrTextBk == CLR_NONE ? TRANSPARENT : OPAQUE);
3360 if (nmlvcd.clrTextBk != CLR_NONE)
3361 SetBkColor(hdc, nmlvcd.clrTextBk == CLR_DEFAULT ? infoPtr->clrTextBkDefault : nmlvcd.clrTextBk);
3362 SetTextColor(hdc, nmlvcd.clrText);
3364 /* draw the selection background, if we're drawing the main item */
3368 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3369 rcSelect.right = rcBox.right;
3371 if (lvItem.state & LVIS_SELECTED)
3372 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3373 if(lprcFocus) *lprcFocus = rcSelect;
3376 /* figure out the text drawing flags */
3377 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3378 if (uView == LVS_ICON)
3379 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3382 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3384 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3385 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3386 default: uFormat |= DT_LEFT;
3389 if (!(uFormat & (DT_RIGHT | DT_CENTER))) rcLabel.left += 2;
3390 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3393 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3394 notify_customdraw(infoPtr, CDDS_ITEMPOSTPAINT, &nmlvcd);
3400 * Draws listview items when in owner draw mode.
3403 * [I] infoPtr : valid pointer to the listview structure
3404 * [I] hdc : device context handle
3409 static void LISTVIEW_RefreshOwnerDraw(LISTVIEW_INFO *infoPtr, HDC hdc)
3411 UINT uID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
3412 HWND hwndParent = GetParent(infoPtr->hwndSelf);
3413 POINT Origin, Position;
3420 ZeroMemory(&dis, sizeof(dis));
3422 /* Get scroll info once before loop */
3423 LISTVIEW_GetOrigin(infoPtr, &Origin);
3425 /* figure out what we need to draw */
3426 iterator_visibleitems(&i, infoPtr, hdc);
3428 /* send cache hint notification */
3429 if (infoPtr->dwStyle & LVS_OWNERDATA)
3431 RANGE range = iterator_range(&i);
3434 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
3435 nmlv.iFrom = range.lower;
3436 nmlv.iTo = range.upper - 1;
3437 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
3440 /* iterate through the invalidated rows */
3441 while(iterator_prev(&i))
3443 item.iItem = i.nItem;
3445 item.mask = LVIF_PARAM | LVIF_STATE;
3446 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3447 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3449 dis.CtlType = ODT_LISTVIEW;
3451 dis.itemID = item.iItem;
3452 dis.itemAction = ODA_DRAWENTIRE;
3454 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3455 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3456 dis.hwndItem = infoPtr->hwndSelf;
3458 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3459 dis.rcItem.left = Position.x + Origin.x;
3460 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3461 dis.rcItem.top = Position.y + Origin.y;
3462 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3463 dis.itemData = item.lParam;
3465 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), debugrect(&dis.rcItem));
3466 SendMessageW(hwndParent, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3468 iterator_destroy(&i);
3473 * Draws listview items when in report display mode.
3476 * [I] infoPtr : valid pointer to the listview structure
3477 * [I] hdc : device context handle
3478 * [I] cdmode : custom draw mode
3483 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD cdmode)
3485 INT rgntype, nFirstCol, nLastCol, nCol;
3486 RECT rcClip, rcItem;
3487 POINT Origin, Position;
3492 /* figure out what to draw */
3493 rgntype = GetClipBox(hdc, &rcClip);
3494 if (rgntype == NULLREGION) return;
3496 /* Get scroll info once before loop */
3497 LISTVIEW_GetOrigin(infoPtr, &Origin);
3499 /* narrow down the columns we need to paint */
3500 for(nFirstCol = 0; nFirstCol < infoPtr->hdpaColumns->nItemCount; nFirstCol++)
3502 LISTVIEW_GetHeaderRect(infoPtr, nFirstCol, &rcItem);
3503 if (rcItem.right + Origin.x >= rcClip.left) break;
3505 for(nLastCol = infoPtr->hdpaColumns->nItemCount - 1; nLastCol >= 0; nLastCol--)
3507 LISTVIEW_GetHeaderRect(infoPtr, nLastCol, &rcItem);
3508 if (rcItem.left + Origin.x < rcClip.right) break;
3511 /* figure out what we need to draw */
3512 iterator_visibleitems(&i, infoPtr, hdc);
3514 /* a last few bits before we start drawing */
3515 TRACE("Colums=(%di - %d)\n", nFirstCol, nLastCol);
3517 /* iterate through the invalidated rows */
3518 while(iterator_prev(&i))
3520 /* iterate through the invalidated columns */
3521 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
3523 LISTVIEW_GetItemOrigin(infoPtr, i.nItem, &Position);
3524 Position.x += Origin.x;
3525 Position.y += Origin.y;
3527 if (rgntype == COMPLEXREGION)
3529 LISTVIEW_GetHeaderRect(infoPtr, nCol, &rcItem);
3531 rcItem.bottom = infoPtr->nItemHeight;
3532 OffsetRect(&rcItem, Position.x, Position.y);
3533 if (!RectVisible(hdc, &rcItem)) continue;
3536 LISTVIEW_DrawItem(infoPtr, hdc, i.nItem, nCol, Position, cdmode);
3539 iterator_destroy(&i);
3544 * Draws listview items when in list display mode.
3547 * [I] infoPtr : valid pointer to the listview structure
3548 * [I] hdc : device context handle
3549 * [I] cdmode : custom draw mode
3554 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD cdmode)
3556 POINT Origin, Position;
3559 /* Get scroll info once before loop */
3560 LISTVIEW_GetOrigin(infoPtr, &Origin);
3562 /* figure out what we need to draw */
3563 iterator_visibleitems(&i, infoPtr, hdc);
3565 while(iterator_prev(&i))
3567 LISTVIEW_GetItemOrigin(infoPtr, i.nItem, &Position);
3568 Position.x += Origin.x;
3569 Position.y += Origin.y;
3571 LISTVIEW_DrawItem(infoPtr, hdc, i.nItem, 0, Position, cdmode);
3573 iterator_destroy(&i);
3579 * Draws listview items.
3582 * [I] infoPtr : valid pointer to the listview structure
3583 * [I] hdc : device context handle
3588 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc)
3590 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3591 COLORREF oldTextColor, oldClrTextBk, oldClrText;
3592 NMLVCUSTOMDRAW nmlvcd;
3598 LISTVIEW_DUMP(infoPtr);
3600 infoPtr->bIsDrawing = TRUE;
3602 /* save dc values we're gonna trash while drawing */
3603 hOldFont = SelectObject(hdc, infoPtr->hFont);
3604 oldBkMode = GetBkMode(hdc);
3605 infoPtr->clrTextBkDefault = GetBkColor(hdc);
3606 oldTextColor = GetTextColor(hdc);
3608 oldClrTextBk = infoPtr->clrTextBk;
3609 oldClrText = infoPtr->clrText;
3611 GetClientRect(infoPtr->hwndSelf, &rcClient);
3612 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient);
3613 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3614 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
3616 /* Use these colors to draw the items */
3617 infoPtr->clrTextBk = nmlvcd.clrTextBk;
3618 infoPtr->clrText = nmlvcd.clrText;
3620 /* nothing to draw */
3621 if(infoPtr->nItemCount == 0) goto enddraw;
3623 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
3624 LISTVIEW_RefreshOwnerDraw(infoPtr, hdc);
3627 if (uView == LVS_REPORT)
3628 LISTVIEW_RefreshReport(infoPtr, hdc, cdmode);
3629 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
3630 LISTVIEW_RefreshList(infoPtr, hdc, cdmode);
3632 /* if we have a focus rect, draw it */
3633 if (infoPtr->bFocus)
3634 DrawFocusRect(hdc, &infoPtr->rcFocus);
3638 if (cdmode & CDRF_NOTIFYPOSTPAINT)
3639 notify_customdraw(infoPtr, CDDS_POSTPAINT, &nmlvcd);
3641 infoPtr->clrTextBk = oldClrTextBk;
3642 infoPtr->clrText = oldClrText;
3644 SelectObject(hdc, hOldFont);
3645 SetBkMode(hdc, oldBkMode);
3646 SetBkColor(hdc, infoPtr->clrTextBkDefault);
3647 SetTextColor(hdc, oldTextColor);
3648 infoPtr->bIsDrawing = FALSE;
3654 * Calculates the approximate width and height of a given number of items.
3657 * [I] infoPtr : valid pointer to the listview structure
3658 * [I] nItemCount : number of items
3659 * [I] wWidth : width
3660 * [I] wHeight : height
3663 * Returns a DWORD. The width in the low word and the height in high word.
3665 static LRESULT LISTVIEW_ApproximateViewRect(LISTVIEW_INFO *infoPtr, INT nItemCount,
3666 WORD wWidth, WORD wHeight)
3668 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3669 INT nItemCountPerColumn = 1;
3670 INT nColumnCount = 0;
3671 DWORD dwViewRect = 0;
3673 if (nItemCount == -1)
3674 nItemCount = infoPtr->nItemCount;
3676 if (uView == LVS_LIST)
3678 if (wHeight == 0xFFFF)
3680 /* use current height */
3681 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
3684 if (wHeight < infoPtr->nItemHeight)
3685 wHeight = infoPtr->nItemHeight;
3689 if (infoPtr->nItemHeight > 0)
3691 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
3692 if (nItemCountPerColumn == 0)
3693 nItemCountPerColumn = 1;
3695 if (nItemCount % nItemCountPerColumn != 0)
3696 nColumnCount = nItemCount / nItemCountPerColumn;
3698 nColumnCount = nItemCount / nItemCountPerColumn + 1;
3702 /* Microsoft padding magic */
3703 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
3704 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
3706 dwViewRect = MAKELONG(wWidth, wHeight);
3708 else if (uView == LVS_REPORT)
3709 FIXME("uView == LVS_REPORT: not implemented\n");
3710 else if (uView == LVS_SMALLICON)
3711 FIXME("uView == LVS_SMALLICON: not implemented\n");
3712 else if (uView == LVS_ICON)
3713 FIXME("uView == LVS_ICON: not implemented\n");
3720 * Arranges listview items in icon display mode.
3723 * [I] infoPtr : valid pointer to the listview structure
3724 * [I] nAlignCode : alignment code
3730 static LRESULT LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
3732 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3733 BOOL bResult = FALSE;
3735 if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
3740 FIXME("nAlignCode=LVA_ALIGNLEFT: not implemented\n");
3743 FIXME("nAlignCode=LVA_ALIGNTOP: not implemented\n");
3746 FIXME("nAlignCode=LVA_DEFAULT: not implemented\n");
3748 case LVA_SNAPTOGRID:
3749 FIXME("nAlignCode=LVA_SNAPTOGRID: not implemented\n");
3757 /* << LISTVIEW_CreateDragImage >> */
3762 * Removes all listview items and subitems.
3765 * [I] infoPtr : valid pointer to the listview structure
3771 static LRESULT LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr)
3773 LONG lStyle = infoPtr->dwStyle;
3774 UINT uView = lStyle & LVS_TYPEMASK;
3775 LISTVIEW_ITEM *lpItem;
3776 LISTVIEW_SUBITEM *lpSubItem;
3779 BOOL bResult = FALSE;
3784 LISTVIEW_DeselectAll(infoPtr);
3785 infoPtr->nSelectionMark=-1;
3786 infoPtr->nFocusedItem=-1;
3787 SetRectEmpty(&infoPtr->rcFocus);
3788 /* But we are supposed to leave nHotItem as is! */
3790 if (lStyle & LVS_OWNERDATA)
3792 infoPtr->nItemCount = 0;
3793 LISTVIEW_InvalidateList(infoPtr);
3797 if (infoPtr->nItemCount > 0)
3801 /* send LVN_DELETEALLITEMS notification */
3802 /* verify if subsequent LVN_DELETEITEM notifications should be
3804 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3806 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
3808 for (i = 0; i < infoPtr->nItemCount; i++)
3810 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
3811 if (hdpaSubItems != NULL)
3813 for (j = 1; j < hdpaSubItems->nItemCount; j++)
3815 lpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, j);
3816 if (lpSubItem != NULL)
3818 /* free subitem string */
3819 if (is_textW(lpSubItem->hdr.pszText))
3820 COMCTL32_Free(lpSubItem->hdr.pszText);
3823 COMCTL32_Free(lpSubItem);
3827 lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0);
3832 /* send LVN_DELETEITEM notification */
3834 nmlv.lParam = lpItem->lParam;
3835 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
3838 /* free item string */
3839 if (is_textW(lpItem->hdr.pszText))
3840 COMCTL32_Free(lpItem->hdr.pszText);
3843 COMCTL32_Free(lpItem);
3846 DPA_Destroy(hdpaSubItems);
3850 /* reinitialize listview memory */
3851 bResult = DPA_DeleteAllPtrs(infoPtr->hdpaItems);
3852 infoPtr->nItemCount = 0;
3853 DPA_DeleteAllPtrs(infoPtr->hdpaPosX);
3854 DPA_DeleteAllPtrs(infoPtr->hdpaPosY);
3856 /* align items (set position of each item) */
3857 if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
3859 if (lStyle & LVS_ALIGNLEFT)
3861 LISTVIEW_AlignLeft(infoPtr);
3865 LISTVIEW_AlignTop(infoPtr);
3869 LISTVIEW_UpdateScroll(infoPtr);
3871 LISTVIEW_InvalidateList(infoPtr);
3879 * Scrolls, and updates the columns, when a column is changing width.
3882 * [I] infoPtr : valid pointer to the listview structure
3883 * [I] nColumn : column to scroll
3884 * [I] dx : amount of scroll, in pixels
3889 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
3891 COLUMN_INFO *lpColumnInfo;
3895 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, infoPtr->hdpaColumns->nItemCount - 1));
3896 rcCol = lpColumnInfo->rcHeader;
3897 if (nColumn >= infoPtr->hdpaColumns->nItemCount)
3898 rcCol.left = rcCol.right;
3900 /* ajust the other columns */
3901 for (nCol = nColumn; nCol < infoPtr->hdpaColumns->nItemCount; nCol++)
3903 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
3904 lpColumnInfo->rcHeader.left += dx;
3905 lpColumnInfo->rcHeader.right += dx;
3908 /* do not update screen if not in report mode */
3909 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
3911 /* if we have a focus, must first erase the focus rect */
3912 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
3914 /* Need to reset the item width when inserting a new column */
3915 infoPtr->nItemWidth += dx;
3917 LISTVIEW_UpdateScroll(infoPtr);
3919 /* scroll to cover the deleted column, and invalidate for redraw */
3920 rcOld = infoPtr->rcList;
3921 rcOld.left = rcCol.left;
3922 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
3924 /* we can restore focus now */
3925 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
3930 * Removes a column from the listview control.
3933 * [I] infoPtr : valid pointer to the listview structure
3934 * [I] nColumn : column index
3940 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
3944 TRACE("nColumn=%d\n", nColumn);
3946 if (nColumn <= 0 || nColumn >= infoPtr->hdpaColumns->nItemCount) return FALSE;
3948 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
3950 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
3953 COMCTL32_Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
3954 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
3956 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
3958 LISTVIEW_SUBITEM *lpSubItem, *lpDelItem;
3960 INT nItem, nSubItem, i;
3962 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
3964 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
3965 if (!hdpaSubItems) continue;
3968 for (i = 1; i < hdpaSubItems->nItemCount; i++)
3970 lpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, i);
3971 if (!lpSubItem) break;
3972 if (lpSubItem->iSubItem == nColumn)
3975 lpDelItem = lpSubItem;
3977 else if (lpSubItem->iSubItem > nColumn)
3979 lpSubItem->iSubItem--;
3983 /* if we found our subitem, zapp it */
3987 if (is_textW(lpDelItem->hdr.pszText))
3988 COMCTL32_Free(lpDelItem->hdr.pszText);
3991 COMCTL32_Free(lpDelItem);
3993 /* free dpa memory */
3994 DPA_DeletePtr(hdpaSubItems, nSubItem);
3999 /* update the other column info */
4000 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4007 * Removes an item from the listview control.
4010 * [I] infoPtr : valid pointer to the listview structure
4011 * [I] nItem : item index
4017 static LRESULT LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4019 LONG lStyle = infoPtr->dwStyle;
4020 UINT uView = lStyle & LVS_TYPEMASK;
4022 BOOL bResult = FALSE;
4024 LISTVIEW_ITEM *lpItem;
4025 LISTVIEW_SUBITEM *lpSubItem;
4029 TRACE("(nItem=%d)\n", nItem);
4031 /* remove selection, and focus */
4033 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4034 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4036 /* send LVN_DELETEITEM notification. */
4037 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
4039 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
4041 if (lStyle & LVS_OWNERDATA)
4043 infoPtr->nItemCount--;
4044 LISTVIEW_InvalidateList(infoPtr); /*FIXME: optimize */
4048 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
4050 /* initialize memory */
4051 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4053 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4054 if (hdpaSubItems != NULL)
4056 infoPtr->nItemCount--;
4057 for (i = 1; i < hdpaSubItems->nItemCount; i++)
4059 lpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, i);
4060 if (lpSubItem != NULL)
4062 /* free item string */
4063 if (is_textW(lpSubItem->hdr.pszText))
4064 COMCTL32_Free(lpSubItem->hdr.pszText);
4067 COMCTL32_Free(lpSubItem);
4071 lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0);
4074 /* free item string */
4075 if (is_textW(lpItem->hdr.pszText))
4076 COMCTL32_Free(lpItem->hdr.pszText);
4079 COMCTL32_Free(lpItem);
4082 bResult = DPA_Destroy(hdpaSubItems);
4083 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4084 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4087 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4089 /* align items (set position of each item) */
4090 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4092 if (lStyle & LVS_ALIGNLEFT)
4093 LISTVIEW_AlignLeft(infoPtr);
4095 LISTVIEW_AlignTop(infoPtr);
4098 LISTVIEW_UpdateScroll(infoPtr);
4100 LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
4109 * Callback implementation for editlabel control
4112 * [I] infoPtr : valid pointer to the listview structure
4113 * [I] pszText : modified text
4114 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4120 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4122 NMLVDISPINFOW dispInfo;
4124 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4126 ZeroMemory(&dispInfo, sizeof(dispInfo));
4127 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
4128 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4129 dispInfo.item.iSubItem = 0;
4130 dispInfo.item.stateMask = ~0;
4131 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4132 /* add the text from the edit in */
4133 dispInfo.item.mask |= LVIF_TEXT;
4134 dispInfo.item.pszText = pszText;
4135 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4137 /* Do we need to update the Item Text */
4138 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4139 if (!pszText) return TRUE;
4141 ZeroMemory(&dispInfo, sizeof(dispInfo));
4142 dispInfo.item.mask = LVIF_TEXT;
4143 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4144 dispInfo.item.iSubItem = 0;
4145 dispInfo.item.pszText = pszText;
4146 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4147 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4152 * Begin in place editing of specified list view item
4155 * [I] infoPtr : valid pointer to the listview structure
4156 * [I] nItem : item index
4157 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4163 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4165 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4166 NMLVDISPINFOW dispInfo;
4169 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4171 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4172 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4174 infoPtr->nEditLabelItem = nItem;
4176 /* Is the EditBox still there, if so remove it */
4177 if(infoPtr->hwndEdit != 0)
4179 SetFocus(infoPtr->hwndSelf);
4180 infoPtr->hwndEdit = 0;
4183 LISTVIEW_SetSelection(infoPtr, nItem);
4184 LISTVIEW_SetItemFocus(infoPtr, nItem);
4186 rect.left = LVIR_LABEL;
4187 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4189 ZeroMemory(&dispInfo, sizeof(dispInfo));
4190 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4191 dispInfo.item.iItem = nItem;
4192 dispInfo.item.iSubItem = 0;
4193 dispInfo.item.stateMask = ~0;
4194 dispInfo.item.pszText = szDispText;
4195 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4196 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4198 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4199 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4200 if (!infoPtr->hwndEdit) return 0;
4202 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4204 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4205 infoPtr->hwndEdit = 0;
4209 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4210 SetFocus(infoPtr->hwndEdit);
4211 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4212 return infoPtr->hwndEdit;
4218 * Ensures the specified item is visible, scrolling into view if necessary.
4221 * [I] infoPtr : valid pointer to the listview structure
4222 * [I] nItem : item index
4223 * [I] bPartial : partially or entirely visible
4229 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4231 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4232 INT nScrollPosHeight = 0;
4233 INT nScrollPosWidth = 0;
4234 INT nHorzAdjust = 0;
4235 INT nVertAdjust = 0;
4238 RECT rcItem, rcTemp;
4240 rcItem.left = LVIR_BOUNDS;
4241 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4243 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4245 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4247 /* scroll left/right, but in LVS_REPORT mode */
4248 if (uView == LVS_LIST)
4249 nScrollPosWidth = infoPtr->nItemWidth;
4250 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4251 nScrollPosWidth = 1;
4253 if (rcItem.left < infoPtr->rcList.left)
4256 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
4261 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
4265 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
4267 /* scroll up/down, but not in LVS_LIST mode */
4268 if (uView == LVS_REPORT)
4269 nScrollPosHeight = infoPtr->nItemHeight;
4270 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
4271 nScrollPosHeight = 1;
4273 if (rcItem.top < infoPtr->rcList.top)
4276 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
4281 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
4285 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
4287 if (nScrollPosWidth)
4289 INT diff = nHorzDiff / nScrollPosWidth;
4290 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
4291 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
4294 if (nScrollPosHeight)
4296 INT diff = nVertDiff / nScrollPosHeight;
4297 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
4298 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
4306 * Searches for an item with specific characteristics.
4309 * [I] hwnd : window handle
4310 * [I] nStart : base item index
4311 * [I] lpFindInfo : item information to look for
4314 * SUCCESS : index of item
4317 static LRESULT LISTVIEW_FindItemW(LISTVIEW_INFO *infoPtr, INT nStart,
4318 LPLVFINDINFOW lpFindInfo)
4320 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4321 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4322 BOOL bWrap = FALSE, bNearest = FALSE;
4323 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
4324 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
4325 POINT Position, Destination;
4328 if (!lpFindInfo || nItem < 0) return -1;
4331 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
4333 lvItem.mask |= LVIF_TEXT;
4334 lvItem.pszText = szDispText;
4335 lvItem.cchTextMax = DISP_TEXT_SIZE;
4338 if (lpFindInfo->flags & LVFI_WRAP)
4341 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
4342 (uView == LVS_ICON || uView ==LVS_SMALLICON))
4346 FIXME("LVFI_NEARESTXY is slow.\n");
4347 LISTVIEW_GetOrigin(infoPtr, &Origin);
4348 Destination.x = lpFindInfo->pt.x - Origin.x;
4349 Destination.y = lpFindInfo->pt.y - Origin.y;
4350 switch(lpFindInfo->vkDirection)
4352 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
4353 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
4354 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
4355 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
4356 case VK_HOME: Destination.x = Destination.y = 0; break;
4357 case VK_END: Destination.x = infoPtr->rcView.right; Destination.y = infoPtr->rcView.bottom; break;
4358 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4359 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4360 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
4365 /* if LVFI_PARAM is specified, all other flags are ignored */
4366 if (lpFindInfo->flags & LVFI_PARAM)
4368 lvItem.mask |= LVIF_PARAM;
4370 lvItem.mask &= ~LVIF_TEXT;
4374 for (; nItem < nLast; nItem++)
4376 lvItem.iItem = nItem;
4377 lvItem.iSubItem = 0;
4378 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
4380 if (lvItem.mask & LVIF_PARAM && lpFindInfo->lParam == lvItem.lParam)
4383 if (lvItem.mask & LVIF_TEXT)
4385 if (lpFindInfo->flags & LVFI_PARTIAL)
4387 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
4391 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
4395 if (!bNearest) return nItem;
4397 /* This is very inefficient. To do a good job here,
4398 * we need a sorted array of (x,y) item positions */
4399 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
4401 /* compute the distance^2 to the destination */
4402 xdist = Destination.x - Position.x;
4403 ydist = Destination.y - Position.y;
4404 dist = xdist * xdist + ydist * ydist;
4406 /* remember the distance, and item if it's closer */
4410 nNearestItem = nItem;
4417 nLast = min(nStart + 1, infoPtr->nItemCount);
4422 return nNearestItem;
4427 * Searches for an item with specific characteristics.
4430 * [I] hwnd : window handle
4431 * [I] nStart : base item index
4432 * [I] lpFindInfo : item information to look for
4435 * SUCCESS : index of item
4438 static LRESULT LISTVIEW_FindItemA(LISTVIEW_INFO *infoPtr, INT nStart,
4439 LPLVFINDINFOA lpFindInfo)
4441 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
4445 memcpy(&fiw, lpFindInfo, sizeof(fiw));
4446 if (hasText) fiw.psz = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
4447 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
4448 if (hasText) textfreeT((LPWSTR)fiw.psz, FALSE);
4454 * Retrieves the background image of the listview control.
4457 * [I] infoPtr : valid pointer to the listview structure
4458 * [O] lpBkImage : background image attributes
4464 /* static LRESULT LISTVIEW_GetBkImage(LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
4466 /* FIXME (listview, "empty stub!\n"); */
4472 * Retrieves column attributes.
4475 * [I] infoPtr : valid pointer to the listview structure
4476 * [I] nColumn : column index
4477 * [IO] lpColumn : column information
4478 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
4479 * otherwise it is in fact a LPLVCOLUMNA
4485 static LRESULT LISTVIEW_GetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
4487 COLUMN_INFO *lpColumnInfo;
4490 if (!lpColumn || nColumn < 0 || nColumn >= infoPtr->hdpaColumns->nItemCount) return FALSE;
4491 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
4493 /* initialize memory */
4494 ZeroMemory(&hdi, sizeof(hdi));
4496 if (lpColumn->mask & LVCF_TEXT)
4498 hdi.mask |= HDI_TEXT;
4499 hdi.pszText = lpColumn->pszText;
4500 hdi.cchTextMax = lpColumn->cchTextMax;
4503 if (lpColumn->mask & LVCF_IMAGE)
4504 hdi.mask |= HDI_IMAGE;
4506 if (lpColumn->mask & LVCF_ORDER)
4507 hdi.mask |= HDI_ORDER;
4509 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
4511 if (lpColumn->mask & LVCF_FMT)
4512 lpColumn->fmt = lpColumnInfo->fmt;
4514 if (lpColumn->mask & LVCF_WIDTH)
4515 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
4517 if (lpColumn->mask & LVCF_IMAGE)
4518 lpColumn->iImage = hdi.iImage;
4520 if (lpColumn->mask & LVCF_ORDER)
4521 lpColumn->iOrder = hdi.iOrder;
4527 static LRESULT LISTVIEW_GetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
4534 /* FIXME: little hack */
4535 for (i = 0; i < iCount; i++)
4543 * Retrieves the column width.
4546 * [I] infoPtr : valid pointer to the listview structure
4547 * [I] int : column index
4550 * SUCCESS : column width
4553 static INT LISTVIEW_GetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn)
4555 INT nColumnWidth = 0;
4558 TRACE("nColumn=%d\n", nColumn);
4560 /* we have a 'column' in LIST and REPORT mode only */
4561 switch(infoPtr->dwStyle & LVS_TYPEMASK)
4564 nColumnWidth = infoPtr->nItemWidth;
4567 if (nColumn < 0 || nColumn >= infoPtr->hdpaColumns->nItemCount) return 0;
4568 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
4569 nColumnWidth = rcHeader.right - rcHeader.left;
4573 TRACE("nColumnWidth=%d\n", nColumnWidth);
4574 return nColumnWidth;
4579 * In list or report display mode, retrieves the number of items that can fit
4580 * vertically in the visible area. In icon or small icon display mode,
4581 * retrieves the total number of visible items.
4584 * [I] infoPtr : valid pointer to the listview structure
4587 * Number of fully visible items.
4589 static LRESULT LISTVIEW_GetCountPerPage(LISTVIEW_INFO *infoPtr)
4591 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4594 if (uView == LVS_LIST)
4596 if (infoPtr->rcList.right > infoPtr->nItemWidth)
4598 nItemCount = LISTVIEW_GetCountPerRow(infoPtr) *
4599 LISTVIEW_GetCountPerColumn(infoPtr);
4602 else if (uView == LVS_REPORT)
4604 nItemCount = LISTVIEW_GetCountPerColumn(infoPtr);
4608 nItemCount = infoPtr->nItemCount;
4617 * Retrieves an image list handle.
4620 * [I] infoPtr : valid pointer to the listview structure
4621 * [I] nImageList : image list identifier
4624 * SUCCESS : image list handle
4627 static LRESULT LISTVIEW_GetImageList(LISTVIEW_INFO *infoPtr, INT nImageList)
4629 HIMAGELIST himl = NULL;
4634 himl = infoPtr->himlNormal;
4637 himl = infoPtr->himlSmall;
4640 himl = infoPtr->himlState;
4644 return (LRESULT)himl;
4647 /* LISTVIEW_GetISearchString */
4651 * Retrieves item attributes.
4654 * [I] hwnd : window handle
4655 * [IO] lpLVItem : item info
4656 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
4657 * if FALSE, the lpLVItem is a LPLVITEMA.
4660 * This is the internal 'GetItem' interface -- it tries to
4661 * be smart, and avoids text copies, if possible, by modifing
4662 * lpLVItem->pszText to point to the text string. Please note
4663 * that this is not always possible (e.g. OWNERDATA), so on
4664 * entry you *must* supply valid values for pszText, and cchTextMax.
4665 * The only difference to the documented interface is that upon
4666 * return, you should use *only* the lpLVItem->pszText, rather than
4667 * the buffer pointer you provided on input. Most code already does
4668 * that, so it's not a problem.
4669 * For the two cases when the text must be copied (that is,
4670 * for LVM_GETITEM, and LVMGETITEMTEXT), use LISTVIEW_GetItemExtT.
4676 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
4678 NMLVDISPINFOW dispInfo;
4679 LISTVIEW_ITEM *lpItem;
4683 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
4685 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
4688 if (lpLVItem->mask == 0) return TRUE;
4690 /* a quick optimization if all we're asked is the focus state
4691 * these queries are worth optimising since they are common,
4692 * and can be answered in constant time, without the heavy accesses */
4693 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
4694 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
4696 lpLVItem->state = 0;
4697 if (infoPtr->nFocusedItem == lpLVItem->iItem)
4698 lpLVItem->state |= LVIS_FOCUSED;
4702 ZeroMemory(&dispInfo, sizeof(dispInfo));
4704 /* if the app stores all the data, handle it separately */
4705 if (infoPtr->dwStyle & LVS_OWNERDATA)
4707 dispInfo.item.state = 0;
4709 /* if we need to callback, do it now */
4710 if ((lpLVItem->mask & ~LVIF_STATE) || infoPtr->uCallbackMask)
4712 /* NOTE: copy only fields which we _know_ are initialized, some apps
4713 * depend on the uninitialized fields being 0 */
4714 dispInfo.item.mask = lpLVItem->mask;
4715 dispInfo.item.iItem = lpLVItem->iItem;
4716 dispInfo.item.iSubItem = lpLVItem->iSubItem;
4717 if (lpLVItem->mask & LVIF_TEXT)
4719 dispInfo.item.pszText = lpLVItem->pszText;
4720 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
4722 if (lpLVItem->mask & LVIF_STATE)
4723 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
4724 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
4725 dispInfo.item.stateMask = lpLVItem->stateMask;
4726 *lpLVItem = dispInfo.item;
4727 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
4730 /* we store only a little state, so if we're not asked, we're done */
4731 if (!(lpLVItem->mask & LVIF_STATE) || lpLVItem->iSubItem) return TRUE;
4733 /* if focus is handled by us, report it */
4734 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
4736 lpLVItem->state &= ~LVIS_FOCUSED;
4737 if (infoPtr->nFocusedItem == lpLVItem->iItem)
4738 lpLVItem->state |= LVIS_FOCUSED;
4741 /* and do the same for selection, if we handle it */
4742 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
4744 lpLVItem->state &= ~LVIS_SELECTED;
4745 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
4746 lpLVItem->state |= LVIS_SELECTED;
4752 /* find the item and subitem structures before we proceed */
4753 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4754 if (hdpaSubItems == NULL) return FALSE;
4756 if ( !(lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0)) )
4759 if (lpLVItem->iSubItem)
4761 LISTVIEW_SUBITEM *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
4762 if(!lpSubItem) return FALSE;
4763 pItemHdr = &lpSubItem->hdr;
4766 pItemHdr = &lpItem->hdr;
4768 /* Do we need to query the state from the app? */
4769 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && lpLVItem->iSubItem == 0)
4771 dispInfo.item.mask |= LVIF_STATE;
4772 dispInfo.item.stateMask = infoPtr->uCallbackMask;
4775 /* Do we need to enquire about the image? */
4776 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK)
4777 dispInfo.item.mask |= LVIF_IMAGE;
4779 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
4780 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
4782 dispInfo.item.mask |= LVIF_TEXT;
4783 dispInfo.item.pszText = lpLVItem->pszText;
4784 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
4785 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
4786 *dispInfo.item.pszText = '\0';
4789 /* If we don't have all the requested info, query the application */
4790 if (dispInfo.item.mask != 0)
4792 dispInfo.item.iItem = lpLVItem->iItem;
4793 dispInfo.item.iSubItem = lpLVItem->iSubItem;
4794 dispInfo.item.lParam = lpItem->lParam;
4795 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
4796 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
4799 /* Now, handle the iImage field */
4800 if (dispInfo.item.mask & LVIF_IMAGE)
4802 lpLVItem->iImage = dispInfo.item.iImage;
4803 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
4804 pItemHdr->iImage = dispInfo.item.iImage;
4806 else if (lpLVItem->mask & LVIF_IMAGE)
4807 lpLVItem->iImage = pItemHdr->iImage;
4809 /* The pszText field */
4810 if (dispInfo.item.mask & LVIF_TEXT)
4812 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
4813 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
4815 lpLVItem->pszText = dispInfo.item.pszText;
4817 else if (lpLVItem->mask & LVIF_TEXT)
4819 if (isW) lpLVItem->pszText = pItemHdr->pszText;
4820 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
4823 /* if this is a subitem, we're done */
4824 if (lpLVItem->iSubItem) return TRUE;
4826 /* Next is the lParam field */
4827 if (dispInfo.item.mask & LVIF_PARAM)
4829 lpLVItem->lParam = dispInfo.item.lParam;
4830 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
4831 lpItem->lParam = dispInfo.item.lParam;
4833 else if (lpLVItem->mask & LVIF_PARAM)
4834 lpLVItem->lParam = lpItem->lParam;
4836 /* ... the state field (this one is different due to uCallbackmask) */
4837 if (lpLVItem->mask & LVIF_STATE)
4839 lpLVItem->state = lpItem->state;
4840 if (dispInfo.item.mask & LVIF_STATE)
4842 lpLVItem->state &= ~dispInfo.item.stateMask;
4843 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
4845 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
4847 lpLVItem->state &= ~LVIS_FOCUSED;
4848 if (infoPtr->nFocusedItem == lpLVItem->iItem)
4849 lpLVItem->state |= LVIS_FOCUSED;
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;
4859 /* and last, but not least, the indent field */
4860 if (lpLVItem->mask & LVIF_INDENT)
4861 lpLVItem->iIndent = lpItem->iIndent;
4868 * Retrieves item attributes.
4871 * [I] hwnd : window handle
4872 * [IO] lpLVItem : item info
4873 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
4874 * if FALSE, the lpLVItem is a LPLVITEMA.
4877 * This is the external 'GetItem' interface -- it properly copies
4878 * the text in the provided buffer.
4884 static BOOL LISTVIEW_GetItemExtT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
4889 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
4892 pszText = lpLVItem->pszText;
4893 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
4894 if (bResult && lpLVItem->pszText != pszText)
4895 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
4896 lpLVItem->pszText = pszText;
4904 * Retrieves the position (upper-left) of the listview control item.
4905 * Note that for LVS_ICON style, the upper-left is that of the icon
4906 * and not the bounding box.
4909 * [I] infoPtr : valid pointer to the listview structure
4910 * [I] nItem : item index
4911 * [O] lpptPosition : coordinate information
4917 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
4919 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4922 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
4924 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4926 LISTVIEW_GetOrigin(infoPtr, &Origin);
4927 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
4929 if (uView == LVS_ICON)
4931 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
4932 lpptPosition->y += ICON_TOP_PADDING;
4934 lpptPosition->x += Origin.x;
4935 lpptPosition->y += Origin.y;
4937 TRACE (" lpptPosition=%s\n", debugpoint(lpptPosition));
4944 * Retrieves the bounding rectangle for a listview control item.
4947 * [I] infoPtr : valid pointer to the listview structure
4948 * [I] nItem : item index
4949 * [IO] lprc : bounding rectangle coordinates
4950 * lprc->left specifies the portion of the item for which the bounding
4951 * rectangle will be retrieved.
4953 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
4954 * including the icon and label.
4957 * * Experiment shows that native control returns:
4958 * * width = min (48, length of text line)
4959 * * .left = position.x - (width - iconsize.cx)/2
4960 * * .right = .left + width
4961 * * height = #lines of text * ntmHeight + icon height + 8
4962 * * .top = position.y - 2
4963 * * .bottom = .top + height
4964 * * separation between items .y = itemSpacing.cy - height
4965 * * .x = itemSpacing.cx - width
4966 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
4969 * * Experiment shows that native control returns:
4970 * * width = iconSize.cx + 16
4971 * * .left = position.x - (width - iconsize.cx)/2
4972 * * .right = .left + width
4973 * * height = iconSize.cy + 4
4974 * * .top = position.y - 2
4975 * * .bottom = .top + height
4976 * * separation between items .y = itemSpacing.cy - height
4977 * * .x = itemSpacing.cx - width
4978 * LVIR_LABEL Returns the bounding rectangle of the item text.
4981 * * Experiment shows that native control returns:
4982 * * width = text length
4983 * * .left = position.x - width/2
4984 * * .right = .left + width
4985 * * height = ntmH * linecount + 2
4986 * * .top = position.y + iconSize.cy + 6
4987 * * .bottom = .top + height
4988 * * separation between items .y = itemSpacing.cy - height
4989 * * .x = itemSpacing.cx - width
4990 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
4991 * rectangles, but excludes columns in report view.
4998 * Note that the bounding rectangle of the label in the LVS_ICON view depends
4999 * upon whether the window has the focus currently and on whether the item
5000 * is the one with the focus. Ensure that the control's record of which
5001 * item has the focus agrees with the items' records.
5003 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5005 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5006 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5007 BOOL doLabel = TRUE, oversizedBox = FALSE;
5008 POINT Position, Origin;
5012 TRACE("(hwnd=%x, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5014 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5016 LISTVIEW_GetOrigin(infoPtr, &Origin);
5017 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5019 /* Be smart and try to figure out the minimum we have to do */
5020 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5021 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5022 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5023 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5024 oversizedBox = TRUE;
5026 /* get what we need from the item before hand, so we make
5027 * only one request. This can speed up things, if data
5028 * is stored on the app side */
5030 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5031 if (doLabel) lvItem.mask |= LVIF_TEXT;
5032 lvItem.iItem = nItem;
5033 lvItem.iSubItem = 0;
5034 lvItem.pszText = szDispText;
5035 lvItem.cchTextMax = DISP_TEXT_SIZE;
5036 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5037 /* we got the state already up, simulate it here, to avoid a reget */
5038 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5040 lvItem.mask |= LVIF_STATE;
5041 lvItem.stateMask = LVIS_FOCUSED;
5042 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5045 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5046 lprc->left = LVIR_BOUNDS;
5050 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5054 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, lprc);
5058 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5061 case LVIR_SELECTBOUNDS:
5062 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, &label_rect);
5063 UnionRect(lprc, lprc, &label_rect);
5067 WARN("Unknown value: %d\n", lprc->left);
5071 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5073 TRACE(" rect=%s\n", debugrect(lprc));
5080 * Retrieves the spacing between listview control items.
5083 * [I] infoPtr : valid pointer to the listview structure
5084 * [IO] lprc : rectangle to receive the output
5085 * on input, lprc->top = nSubItem
5086 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5088 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5089 * not only those of the first column.
5090 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5096 static BOOL LISTVIEW_GetSubItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5098 POINT Position, Origin;
5101 if (!lprc || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5103 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
5105 LISTVIEW_GetOrigin(infoPtr, &Origin);
5106 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5108 lvItem.mask = lprc->top == 0 ? LVIF_INDENT : 0;
5109 lvItem.iItem = nItem;
5110 lvItem.iSubItem = lprc->top;
5112 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5116 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5121 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5125 ERR("Unknown bounds=%d\n", lprc->left);
5129 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5136 * Retrieves the width of a label.
5139 * [I] infoPtr : valid pointer to the listview structure
5142 * SUCCESS : string width (in pixels)
5145 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *infoPtr, INT nItem)
5147 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5150 TRACE("(nItem=%d)\n", nItem);
5152 lvItem.mask = LVIF_TEXT;
5153 lvItem.iItem = nItem;
5154 lvItem.iSubItem = 0;
5155 lvItem.pszText = szDispText;
5156 lvItem.cchTextMax = DISP_TEXT_SIZE;
5157 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5159 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5164 * Retrieves the spacing between listview control items.
5167 * [I] infoPtr : valid pointer to the listview structure
5168 * [I] bSmall : flag for small or large icon
5171 * Horizontal + vertical spacing
5173 static LRESULT LISTVIEW_GetItemSpacing(LISTVIEW_INFO *infoPtr, BOOL bSmall)
5179 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5183 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5184 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5186 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5193 * Retrieves the state of a listview control item.
5196 * [I] infoPtr : valid pointer to the listview structure
5197 * [I] nItem : item index
5198 * [I] uMask : state mask
5201 * State specified by the mask.
5203 static LRESULT LISTVIEW_GetItemState(LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5207 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5209 lvItem.iItem = nItem;
5210 lvItem.iSubItem = 0;
5211 lvItem.mask = LVIF_STATE;
5212 lvItem.stateMask = uMask;
5213 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5215 return lvItem.state & uMask;
5220 * Retrieves the text of a listview control item or subitem.
5223 * [I] hwnd : window handle
5224 * [I] nItem : item index
5225 * [IO] lpLVItem : item information
5226 * [I] isW : TRUE if lpLVItem is Unicode
5229 * SUCCESS : string length
5232 static LRESULT LISTVIEW_GetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
5234 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5236 lpLVItem->mask = LVIF_TEXT;
5237 lpLVItem->iItem = nItem;
5238 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
5240 return textlenT(lpLVItem->pszText, isW);
5245 * Searches for an item based on properties + relationships.
5248 * [I] infoPtr : valid pointer to the listview structure
5249 * [I] nItem : item index
5250 * [I] uFlags : relationship flag
5253 * This function is very, very inefficient! Needs work.
5256 * SUCCESS : item index
5259 static LRESULT LISTVIEW_GetNextItem(LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
5261 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5263 LVFINDINFOW lvFindInfo;
5264 INT nCountPerColumn;
5267 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
5268 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
5270 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
5272 if (uFlags & LVNI_CUT)
5275 if (uFlags & LVNI_DROPHILITED)
5276 uMask |= LVIS_DROPHILITED;
5278 if (uFlags & LVNI_FOCUSED)
5279 uMask |= LVIS_FOCUSED;
5281 if (uFlags & LVNI_SELECTED)
5282 uMask |= LVIS_SELECTED;
5284 /* if we're asked for the focused item, that's only one,
5285 * so it's worth optimizing */
5286 if (uFlags & LVNI_FOCUSED)
5288 if (!(LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) == uMask) return -1;
5289 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
5292 if (uFlags & LVNI_ABOVE)
5294 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5299 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5305 lvFindInfo.flags = LVFI_NEARESTXY;
5306 lvFindInfo.vkDirection = VK_UP;
5307 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5308 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5310 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5315 else if (uFlags & LVNI_BELOW)
5317 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5319 while (nItem < infoPtr->nItemCount)
5322 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5328 lvFindInfo.flags = LVFI_NEARESTXY;
5329 lvFindInfo.vkDirection = VK_DOWN;
5330 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5331 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5333 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5338 else if (uFlags & LVNI_TOLEFT)
5340 if (uView == LVS_LIST)
5342 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5343 while (nItem - nCountPerColumn >= 0)
5345 nItem -= nCountPerColumn;
5346 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5350 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5352 lvFindInfo.flags = LVFI_NEARESTXY;
5353 lvFindInfo.vkDirection = VK_LEFT;
5354 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5355 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5357 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5362 else if (uFlags & LVNI_TORIGHT)
5364 if (uView == LVS_LIST)
5366 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5367 while (nItem + nCountPerColumn < infoPtr->nItemCount)
5369 nItem += nCountPerColumn;
5370 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5374 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5376 lvFindInfo.flags = LVFI_NEARESTXY;
5377 lvFindInfo.vkDirection = VK_RIGHT;
5378 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5379 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5381 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5390 /* search by index */
5391 for (i = nItem; i < infoPtr->nItemCount; i++)
5393 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
5401 /* LISTVIEW_GetNumberOfWorkAreas */
5405 * Retrieves the origin coordinates when in icon or small icon display mode.
5408 * [I] infoPtr : valid pointer to the listview structure
5409 * [O] lpptOrigin : coordinate information
5414 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
5416 DWORD lStyle = infoPtr->dwStyle;
5417 UINT uView = lStyle & LVS_TYPEMASK;
5418 INT nHorzPos = 0, nVertPos = 0;
5419 SCROLLINFO scrollInfo;
5421 scrollInfo.cbSize = sizeof(SCROLLINFO);
5422 scrollInfo.fMask = SIF_POS;
5424 if ((lStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
5425 nHorzPos = scrollInfo.nPos;
5426 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
5427 nVertPos = scrollInfo.nPos;
5429 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
5431 lpptOrigin->x = infoPtr->rcList.left;
5432 lpptOrigin->y = infoPtr->rcList.top;
5433 if (uView == LVS_LIST)
5434 nHorzPos *= infoPtr->nItemWidth;
5435 else if (uView == LVS_REPORT)
5436 nVertPos *= infoPtr->nItemHeight;
5438 lpptOrigin->x -= nHorzPos;
5439 lpptOrigin->y -= nVertPos;
5441 TRACE(" origin=%s\n", debugpoint(lpptOrigin));
5446 * Retrieves the width of a string.
5449 * [I] hwnd : window handle
5450 * [I] lpszText : text string to process
5451 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
5454 * SUCCESS : string width (in pixels)
5457 static LRESULT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
5462 if (is_textT(lpszText, isW))
5464 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
5465 HDC hdc = GetDC(infoPtr->hwndSelf);
5466 HFONT hOldFont = SelectObject(hdc, hFont);
5469 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
5471 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
5472 SelectObject(hdc, hOldFont);
5473 ReleaseDC(infoPtr->hwndSelf, hdc);
5475 return stringSize.cx;
5480 * Determines which listview item is located at the specified position.
5483 * [I] infoPtr : valid pointer to the listview structure
5484 * [IO] lpht : hit test information
5485 * [I] subitem : fill out iSubItem.
5486 * [I] select : return the index only if the hit selects the item
5489 * (mm 20001022): We must not allow iSubItem to be touched, for
5490 * an app might pass only a structure with space up to iItem!
5491 * (MS Office 97 does that for instance in the file open dialog)
5494 * SUCCESS : item index
5497 static LRESULT LISTVIEW_HitTest(LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
5499 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5500 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5501 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
5502 POINT Origin, Position, opt;
5506 TRACE("(pt=%s, subitem=%d, select=%d)\n", debugpoint(&lpht->pt), subitem, select);
5510 if (subitem) lpht->iSubItem = 0;
5512 if (infoPtr->rcList.left > lpht->pt.x)
5513 lpht->flags |= LVHT_TOLEFT;
5514 else if (infoPtr->rcList.right < lpht->pt.x)
5515 lpht->flags |= LVHT_TORIGHT;
5517 if (infoPtr->rcList.top > lpht->pt.y)
5518 lpht->flags |= LVHT_ABOVE;
5519 else if (infoPtr->rcList.bottom < lpht->pt.y)
5520 lpht->flags |= LVHT_BELOW;
5522 TRACE("lpht->flags=0x%x\n", lpht->flags);
5523 if (lpht->flags) return -1;
5525 lpht->flags |= LVHT_NOWHERE;
5527 LISTVIEW_GetOrigin(infoPtr, &Origin);
5529 /* first deal with the large items */
5530 rcSearch.left = lpht->pt.x;
5531 rcSearch.top = lpht->pt.y;
5532 rcSearch.right = rcSearch.left + 1;
5533 rcSearch.bottom = rcSearch.top + 1;
5535 iterator_frameditems(&i, infoPtr, &rcSearch);
5536 iterator_next(&i); /* go to first item in the sequence */
5537 lpht->iItem = i.nItem;
5538 iterator_destroy(&i);
5540 TRACE("lpht->iItem=%d\n", lpht->iItem);
5541 if (lpht->iItem == -1) return -1;
5543 lvItem.mask = LVIF_STATE | LVIF_TEXT;
5544 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5545 lvItem.stateMask = LVIS_STATEIMAGEMASK;
5546 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
5547 lvItem.iItem = lpht->iItem;
5548 lvItem.iSubItem = 0;
5549 lvItem.pszText = szDispText;
5550 lvItem.cchTextMax = DISP_TEXT_SIZE;
5551 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
5552 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
5554 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
5555 LISTVIEW_GetItemOrigin(infoPtr, lpht->iItem, &Position);
5556 opt.x = lpht->pt.x - Position.x - Origin.x;
5557 opt.y = lpht->pt.y - Position.y - Origin.y;
5559 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
5562 UnionRect(&rcBounds, &rcIcon, &rcLabel);
5563 TRACE("rcBounds=%s\n", debugrect(&rcBounds));
5564 if (!PtInRect(&rcBounds, opt)) return -1;
5566 if (PtInRect(&rcIcon, opt))
5567 lpht->flags |= LVHT_ONITEMICON;
5568 else if (PtInRect(&rcLabel, opt))
5569 lpht->flags |= LVHT_ONITEMLABEL;
5570 else if (infoPtr->himlState && ((lvItem.state & LVIS_STATEIMAGEMASK) >> 12) && PtInRect(&rcState, opt))
5571 lpht->flags |= LVHT_ONITEMSTATEICON;
5572 if (lpht->flags & LVHT_ONITEM)
5573 lpht->flags &= ~LVHT_NOWHERE;
5575 TRACE("lpht->flags=0x%x\n", lpht->flags);
5576 if (uView == LVS_REPORT && lpht->iItem != -1 && subitem)
5580 rcBounds.right = rcBounds.left;
5581 for (j = 0; j < infoPtr->hdpaColumns->nItemCount; j++)
5583 rcBounds.left = rcBounds.right;
5584 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
5585 if (PtInRect(&rcBounds, opt))
5593 if (!select || lpht->iItem == -1) return lpht->iItem;
5595 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)) return lpht->iItem;
5597 if (uView == LVS_REPORT) UnionRect(&rcBounds, &rcIcon, &rcLabel);
5598 return PtInRect(&rcBounds, opt) ? lpht->iItem : -1;
5602 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
5603 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
5604 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
5605 and not during the processing of a LVM_SORTITEMS message. Applications should provide
5606 their own sort proc. when sending LVM_SORTITEMS.
5609 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
5611 LVS_SORTXXX must be specified,
5612 LVS_OWNERDRAW is not set,
5613 <item>.pszText is not LPSTR_TEXTCALLBACK.
5615 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
5616 are sorted based on item text..."
5618 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
5620 LISTVIEW_ITEM* lv_first = (LISTVIEW_ITEM*) DPA_GetPtr( (HDPA)first, 0 );
5621 LISTVIEW_ITEM* lv_second = (LISTVIEW_ITEM*) DPA_GetPtr( (HDPA)second, 0 );
5622 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
5624 /* if we're sorting descending, negate the return value */
5625 return (((LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
5630 * Inserts a new item in the listview control.
5633 * [I] infoPtr : valid pointer to the listview structure
5634 * [I] lpLVItem : item information
5635 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
5638 * SUCCESS : new item index
5641 static LRESULT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5643 LONG lStyle = infoPtr->dwStyle;
5644 UINT uView = lStyle & LVS_TYPEMASK;
5648 LISTVIEW_ITEM *lpItem;
5649 BOOL is_sorted, has_changed;
5651 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5653 if (lStyle & LVS_OWNERDATA)
5655 nItem = infoPtr->nItemCount;
5656 infoPtr->nItemCount++;
5660 /* make sure it's an item, and not a subitem; cannot insert a subitem */
5661 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iSubItem) return -1;
5663 if (!is_assignable_item(lpLVItem, lStyle)) return -1;
5665 if ( !(lpItem = (LISTVIEW_ITEM *)COMCTL32_Alloc(sizeof(LISTVIEW_ITEM))) )
5668 /* insert item in listview control data structure */
5669 if ( (hdpaSubItems = DPA_Create(8)) )
5670 nItem = DPA_InsertPtr(hdpaSubItems, 0, lpItem);
5671 if (nItem == -1) goto fail;
5673 /* FIXME: is the handling of this LVS_OWNERDRAWFIXED correct? */
5674 is_sorted = (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
5675 !(lStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
5677 nItem = is_sorted ? infoPtr->nItemCount + 1 : lpLVItem->iItem;
5678 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
5679 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
5680 TRACE(" result of insert is %d\n", nItem);
5681 if (nItem == -1) goto fail;
5682 /* the array may be sparsly populated, we can't just increment the count here */
5683 infoPtr->nItemCount = infoPtr->hdpaItems->nItemCount;
5684 TRACE(" item count is %d\n", infoPtr->nItemCount);
5686 /* set the item attributes */
5687 if (!set_main_item(infoPtr, lpLVItem, TRUE, isW, &has_changed)) goto undo;
5689 /* if we're sorted, sort the list, and update the index */
5692 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
5693 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
5694 assert(nItem != -1);
5697 /* make room for the position, if we are in the right mode */
5698 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5700 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
5702 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
5704 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
5709 /* Add the subitem list to the items array. Do this last in case we go to
5710 * fail during the above.
5712 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
5714 /* send LVN_INSERTITEM notification */
5715 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
5717 nmlv.lParam = lpItem->lParam;
5718 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
5720 /* align items (set position of each item) */
5721 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5723 if (lStyle & LVS_ALIGNLEFT) LISTVIEW_AlignLeft(infoPtr);
5724 else LISTVIEW_AlignTop(infoPtr);
5727 LISTVIEW_UpdateScroll(infoPtr);
5729 LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
5731 TRACE(" <- %d\n", nItem);
5735 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
5736 infoPtr->nItemCount--;
5738 DPA_DeletePtr(hdpaSubItems, 0);
5739 DPA_Destroy (hdpaSubItems);
5740 COMCTL32_Free (lpItem);
5746 * Redraws a range of items.
5749 * [I] infoPtr : valid pointer to the listview structure
5750 * [I] nFirst : first item
5751 * [I] nLast : last item
5757 static LRESULT LISTVIEW_RedrawItems(LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
5761 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
5762 max(nFirst, nLast) >= infoPtr->nItemCount)
5765 for (i = nFirst; i <= nLast; i++)
5766 LISTVIEW_InvalidateItem(infoPtr, i);
5773 * Scroll the content of a listview.
5776 * [I] infoPtr : valid pointer to the listview structure
5777 * [I] dx : horizontal scroll amount in pixels
5778 * [I] dy : vertical scroll amount in pixels
5785 * If the control is in report mode (LVS_REPORT) the control can
5786 * be scrolled only in line increments. "dy" will be rounded to the
5787 * nearest number of pixels that are a whole line. Ex: if line height
5788 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
5789 * is passed the the scroll will be 0. (per MSDN 7/2002)
5791 * For: (per experimentaion with native control and CSpy ListView)
5792 * LVS_ICON dy=1 = 1 pixel (vertical only)
5794 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
5796 * LVS_LIST dx=1 = 1 column (horizontal only)
5797 * but will only scroll 1 column per message
5798 * no matter what the value.
5799 * dy must be 0 or FALSE returned.
5800 * LVS_REPORT dx=1 = 1 pixel
5804 static LRESULT LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
5806 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
5808 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
5809 dy /= infoPtr->nItemHeight;
5812 if (dy != 0) return FALSE;
5819 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
5820 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
5827 * Sets the background color.
5830 * [I] infoPtr : valid pointer to the listview structure
5831 * [I] clrBk : background color
5837 static LRESULT LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
5839 TRACE("(clrBk=%lx)\n", clrBk);
5841 if(infoPtr->clrBk != clrBk) {
5842 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
5843 infoPtr->clrBk = clrBk;
5844 if (clrBk == CLR_NONE)
5845 infoPtr->hBkBrush = GetClassLongW(infoPtr->hwndSelf, GCL_HBRBACKGROUND);
5847 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
5848 LISTVIEW_InvalidateList(infoPtr);
5854 /* LISTVIEW_SetBkImage */
5856 /*** Helper for {Insert,Set}ColumnT *only* */
5857 static void column_fill_hditem(LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5859 if (lpColumn->mask & LVCF_FMT)
5861 /* format member is valid */
5862 lphdi->mask |= HDI_FORMAT;
5864 /* set text alignment (leftmost column must be left-aligned) */
5865 if (nColumn == 0 || lpColumn->fmt & LVCFMT_LEFT)
5866 lphdi->fmt |= HDF_LEFT;
5867 else if (lpColumn->fmt & LVCFMT_RIGHT)
5868 lphdi->fmt |= HDF_RIGHT;
5869 else if (lpColumn->fmt & LVCFMT_CENTER)
5870 lphdi->fmt |= HDF_CENTER;
5872 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
5873 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
5875 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
5877 lphdi->fmt |= HDF_IMAGE;
5878 lphdi->iImage = I_IMAGECALLBACK;
5882 if (lpColumn->mask & LVCF_WIDTH)
5884 lphdi->mask |= HDI_WIDTH;
5885 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
5887 /* make it fill the remainder of the controls width */
5891 for(item_index = 0; item_index < (nColumn - 1); item_index++)
5893 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
5894 lphdi->cxy += rcHeader.right - rcHeader.left;
5897 /* retrieve the layout of the header */
5898 GetClientRect(infoPtr->hwndSelf, &rcHeader);
5899 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, debugrect(&rcHeader));
5901 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
5904 lphdi->cxy = lpColumn->cx;
5907 if (lpColumn->mask & LVCF_TEXT)
5909 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
5910 lphdi->fmt |= HDF_STRING;
5911 lphdi->pszText = lpColumn->pszText;
5912 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
5915 if (lpColumn->mask & LVCF_IMAGE)
5917 lphdi->mask |= HDI_IMAGE;
5918 lphdi->iImage = lpColumn->iImage;
5921 if (lpColumn->mask & LVCF_ORDER)
5923 lphdi->mask |= HDI_ORDER;
5924 lphdi->iOrder = lpColumn->iOrder;
5931 * Inserts a new column.
5934 * [I] infoPtr : valid pointer to the listview structure
5935 * [I] nColumn : column index
5936 * [I] lpColumn : column information
5937 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
5940 * SUCCESS : new column index
5943 static LRESULT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
5944 LPLVCOLUMNW lpColumn, BOOL isW)
5946 COLUMN_INFO *lpColumnInfo;
5950 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
5952 if (!lpColumn) return -1;
5954 ZeroMemory(&hdi, sizeof(HDITEMW));
5955 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
5957 /* insert item in header control */
5958 nNewColumn = SendMessageW(infoPtr->hwndHeader,
5959 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
5960 (WPARAM)nColumn, (LPARAM)&hdi);
5961 if (nNewColumn == -1) return -1;
5963 /* create our own column info */
5964 if (!(lpColumnInfo = COMCTL32_Alloc(sizeof(COLUMN_INFO)))) goto fail;
5965 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
5967 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
5968 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
5970 /* now we have to actually adjust the data */
5971 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
5973 LISTVIEW_SUBITEM *lpSubItem, *lpMainItem, **lpNewItems = 0;
5977 /* preallocate memory, so we can fail gracefully */
5978 if (nNewColumn == 0)
5980 lpNewItems = COMCTL32_Alloc(sizeof(LISTVIEW_SUBITEM *) * infoPtr->nItemCount);
5981 if (!lpNewItems) goto fail;
5982 for (i = 0; i < infoPtr->nItemCount; i++)
5983 if (!(lpNewItems[i] = COMCTL32_Alloc(sizeof(LISTVIEW_SUBITEM)))) break;
5984 if (i != infoPtr->nItemCount)
5986 for(; i >=0; i--) COMCTL32_Free(lpNewItems[i]);
5987 COMCTL32_Free(lpNewItems);
5992 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
5994 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
5995 if (!hdpaSubItems) continue;
5996 for (i = 1; i < hdpaSubItems->nItemCount; i++)
5998 lpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, i);
5999 if (!lpSubItem) break;
6000 if (lpSubItem->iSubItem >= nNewColumn)
6001 lpSubItem->iSubItem++;
6004 /* if we found our subitem, zapp it */
6005 if (nNewColumn == 0)
6007 lpMainItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, 0);
6008 lpSubItem = lpNewItems[nItem];
6009 lpSubItem->hdr = lpMainItem->hdr;
6010 lpSubItem->iSubItem = 1;
6011 ZeroMemory(&lpMainItem->hdr, sizeof(lpMainItem->hdr));
6012 lpMainItem->iSubItem = 0;
6013 DPA_InsertPtr(hdpaSubItems, 1, lpSubItem);
6017 COMCTL32_Free(lpNewItems);
6020 /* make space for the new column */
6021 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6026 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6029 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6030 COMCTL32_Free(lpColumnInfo);
6037 * Sets the attributes of a header item.
6040 * [I] infoPtr : valid pointer to the listview structure
6041 * [I] nColumn : column index
6042 * [I] lpColumn : column attributes
6043 * [I] isW: if TRUE, the lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6049 static LRESULT LISTVIEW_SetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6050 LPLVCOLUMNW lpColumn, BOOL isW)
6052 HDITEMW hdi, hdiget;
6055 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6057 if (!lpColumn || nColumn < 0 || nColumn >= infoPtr->hdpaColumns->nItemCount) return FALSE;
6059 ZeroMemory(&hdi, sizeof(HDITEMW));
6060 if (lpColumn->mask & LVCF_FMT)
6062 hdi.mask |= HDI_FORMAT;
6063 hdiget.mask = HDI_FORMAT;
6064 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6065 hdi.fmt = hdiget.fmt & HDF_STRING;
6067 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6069 /* set header item attributes */
6070 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6071 if (!bResult) return FALSE;
6073 if (lpColumn->mask & LVCF_FMT)
6074 LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt = lpColumn->fmt;
6081 * Sets the column order array
6084 * [I] infoPtr : valid pointer to the listview structure
6085 * [I] iCount : number of elements in column order array
6086 * [I] lpiArray : pointer to column order array
6092 static LRESULT LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
6094 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6105 * Sets the width of a column
6108 * [I] infoPtr : valid pointer to the listview structure
6109 * [I] iCol : column index
6110 * [I] cx : column width
6116 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT iCol, INT cx)
6120 LONG lStyle = infoPtr->dwStyle;
6121 UINT uView = lStyle & LVS_TYPEMASK;
6126 WCHAR text_buffer[DISP_TEXT_SIZE];
6127 INT header_item_count;
6132 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6134 TRACE("(iCol=%d, cx=%d\n", iCol, cx);
6136 /* set column width only if in report or list mode */
6137 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6139 /* take care of invalid cx values */
6140 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
6141 else if (uView == LVS_LIST && cx < 1) return FALSE;
6143 /* resize all columns if in LVS_LIST mode */
6144 if(uView == LVS_LIST)
6146 infoPtr->nItemWidth = cx;
6147 LISTVIEW_InvalidateList(infoPtr);
6151 /* FIXME: update COLUMN_INFO */
6153 /* autosize based on listview items width */
6154 if(cx == LVSCW_AUTOSIZE)
6156 /* set the width of the column to the width of the widest item */
6157 if (iCol == 0 || uView == LVS_LIST)
6160 for(item_index = 0; item_index < infoPtr->nItemCount; item_index++)
6162 nLabelWidth = LISTVIEW_GetLabelWidth(infoPtr, item_index);
6163 cx = (nLabelWidth>cx)?nLabelWidth:cx;
6165 if (infoPtr->himlSmall)
6166 cx += infoPtr->iconSize.cx + IMAGE_PADDING;
6170 lvItem.iSubItem = iCol;
6171 lvItem.mask = LVIF_TEXT;
6172 lvItem.pszText = szDispText;
6173 lvItem.cchTextMax = DISP_TEXT_SIZE;
6175 for(item_index = 0; item_index < infoPtr->nItemCount; item_index++)
6177 lvItem.iItem = item_index;
6178 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6179 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6180 cx = (nLabelWidth>cx)?nLabelWidth:cx;
6183 cx += TRAILING_PADDING;
6184 } /* autosize based on listview header width */
6185 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
6187 header_item_count = infoPtr->hdpaColumns->nItemCount;
6189 /* if iCol is the last column make it fill the remainder of the controls width */
6190 if(iCol == (header_item_count - 1)) {
6193 for(item_index = 0; item_index < (header_item_count - 1); item_index++)
6195 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6196 cx += rcHeader.right - rcHeader.left;
6199 /* retrieve the layout of the header */
6200 GetWindowRect(infoPtr->hwndHeader, &rcHeader);
6202 cx = (rcHeader.right - rcHeader.left) - cx;
6206 /* Despite what the MS docs say, if this is not the last
6207 column, then MS resizes the column to the width of the
6208 largest text string in the column, including headers
6209 and items. This is different from LVSCW_AUTOSIZE in that
6210 LVSCW_AUTOSIZE ignores the header string length.
6213 /* retrieve header font */
6214 header_font = SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0L, 0L);
6216 /* retrieve header text */
6217 hdi.mask = HDI_TEXT;
6218 hdi.cchTextMax = sizeof(text_buffer)/sizeof(text_buffer[0]);
6219 hdi.pszText = text_buffer;
6221 Header_GetItemW(infoPtr->hwndHeader, iCol, (LPARAM)(&hdi));
6223 /* determine the width of the text in the header */
6224 hdc = GetDC(infoPtr->hwndSelf);
6225 old_font = SelectObject(hdc, header_font); /* select the font into hdc */
6227 GetTextExtentPoint32W(hdc, text_buffer, lstrlenW(text_buffer), &size);
6229 SelectObject(hdc, old_font); /* restore the old font */
6230 ReleaseDC(infoPtr->hwndSelf, hdc);
6232 lvItem.iSubItem = iCol;
6233 lvItem.mask = LVIF_TEXT;
6234 lvItem.pszText = szDispText;
6235 lvItem.cchTextMax = DISP_TEXT_SIZE;
6237 for(item_index = 0; item_index < infoPtr->nItemCount; item_index++)
6239 lvItem.iItem = item_index;
6240 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6241 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6242 nLabelWidth += TRAILING_PADDING;
6243 /* While it is possible for subitems to have icons, even MS messes
6244 up the positioning, so I suspect no applications actually use
6246 if (item_index == 0 && infoPtr->himlSmall)
6247 nLabelWidth += infoPtr->iconSize.cx + IMAGE_PADDING;
6248 cx = (nLabelWidth>cx)?nLabelWidth:cx;
6253 /* call header to update the column change */
6254 hdi.mask = HDI_WIDTH;
6257 lret = Header_SetItemW(infoPtr->hwndHeader, (WPARAM)iCol, (LPARAM)&hdi);
6259 LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
6266 * Sets the extended listview style.
6269 * [I] infoPtr : valid pointer to the listview structure
6271 * [I] dwStyle : style
6274 * SUCCESS : previous style
6277 static LRESULT LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwStyle)
6279 DWORD dwOldStyle = infoPtr->dwLvExStyle;
6283 infoPtr->dwLvExStyle = (dwOldStyle & ~dwMask) | (dwStyle & dwMask);
6285 infoPtr->dwLvExStyle = dwStyle;
6292 * Sets the new hot cursor used during hot tracking and hover selection.
6295 * [I] infoPtr : valid pointer to the listview structure
6296 * [I} hCurosr : the new hot cursor handle
6299 * Returns the previous hot cursor
6301 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
6303 HCURSOR oldCursor = infoPtr->hHotCursor;
6305 infoPtr->hHotCursor = hCursor;
6313 * Sets the hot item index.
6316 * [I] infoPtr : valid pointer to the listview structure
6317 * [I] iIndex : index
6320 * SUCCESS : previous hot item index
6321 * FAILURE : -1 (no hot item)
6323 static LRESULT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
6325 INT iOldIndex = infoPtr->nHotItem;
6327 infoPtr->nHotItem = iIndex;
6335 * Sets the amount of time the cursor must hover over an item before it is selected.
6338 * [I] infoPtr : valid pointer to the listview structure
6339 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
6342 * Returns the previous hover time
6344 static LRESULT LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
6346 DWORD oldHoverTime = infoPtr->dwHoverTime;
6348 infoPtr->dwHoverTime = dwHoverTime;
6350 return oldHoverTime;
6355 * Sets spacing for icons of LVS_ICON style.
6358 * [I] infoPtr : valid pointer to the listview structure
6359 * [I] spacing : MAKELONG(cx, cy)
6362 * MAKELONG(oldcx, oldcy)
6364 static LRESULT LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, DWORD spacing)
6366 INT cy = HIWORD(spacing), cx = LOWORD(spacing);
6367 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6368 LONG lStyle = infoPtr->dwStyle;
6369 UINT uView = lStyle & LVS_TYPEMASK;
6371 TRACE("requested=(%d,%d)\n", cx, cy);
6373 /* this is supported only for LVS_ICON style */
6374 if (uView != LVS_ICON) return oldspacing;
6376 /* set to defaults, if instructed to */
6377 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
6378 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
6380 /* if 0 then compute width
6381 * FIXME: Should scan each item and determine max width of
6382 * icon or label, then make that the width */
6384 cx = infoPtr->iconSpacing.cx;
6386 /* if 0 then compute height */
6388 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
6389 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
6392 infoPtr->iconSpacing.cx = cx;
6393 infoPtr->iconSpacing.cy = cy;
6395 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%ld,%ld), ntmH=%d\n",
6396 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
6397 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
6398 infoPtr->ntmHeight);
6400 /* these depend on the iconSpacing */
6401 infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
6402 infoPtr->nItemHeight = LISTVIEW_CalculateMaxHeight(infoPtr);
6407 inline void update_icon_size(HIMAGELIST himl, BOOL small, SIZE *size)
6411 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
6418 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
6419 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
6428 * [I] infoPtr : valid pointer to the listview structure
6429 * [I] nType : image list type
6430 * [I] himl : image list handle
6433 * SUCCESS : old image list
6436 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
6438 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6439 INT oldHeight = infoPtr->nItemHeight;
6440 HIMAGELIST himlOld = 0;
6445 himlOld = infoPtr->himlNormal;
6446 infoPtr->himlNormal = himl;
6447 if (uView == LVS_ICON) update_icon_size(himl, FALSE, &infoPtr->iconSize);
6448 LISTVIEW_SetIconSpacing(infoPtr, 0);
6452 himlOld = infoPtr->himlSmall;
6453 infoPtr->himlSmall = himl;
6454 if (uView != LVS_ICON) update_icon_size(himl, TRUE, &infoPtr->iconSize);
6458 himlOld = infoPtr->himlState;
6459 infoPtr->himlState = himl;
6460 update_icon_size(himl, TRUE, &infoPtr->iconStateSize);
6461 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
6465 ERR("Unknown icon type=%d\n", nType);
6469 infoPtr->nItemHeight = LISTVIEW_CalculateMaxHeight(infoPtr);
6470 if (infoPtr->nItemHeight != oldHeight)
6471 LISTVIEW_UpdateScroll(infoPtr);
6478 * Preallocates memory (does *not* set the actual count of items !)
6481 * [I] infoPtr : valid pointer to the listview structure
6482 * [I] nItems : item count (projected number of items to allocate)
6483 * [I] dwFlags : update flags
6489 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
6491 TRACE("(nItems=%d, dwFlags=%lx)\n", nItems, dwFlags);
6493 if (infoPtr->dwStyle & LVS_OWNERDATA)
6495 int precount,topvisible;
6497 TRACE("LVS_OWNERDATA is set!\n");
6498 if (dwFlags & (LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL))
6499 FIXME("flags %s %s not implemented\n",
6500 (dwFlags & LVSICF_NOINVALIDATEALL) ? "LVSICF_NOINVALIDATEALL"
6502 (dwFlags & LVSICF_NOSCROLL) ? "LVSICF_NOSCROLL" : "");
6504 LISTVIEW_DeselectAll(infoPtr);
6506 precount = infoPtr->nItemCount;
6507 topvisible = LISTVIEW_GetTopIndex(infoPtr) +
6508 LISTVIEW_GetCountPerColumn(infoPtr) + 1;
6510 infoPtr->nItemCount = nItems;
6511 infoPtr->nItemWidth = max(LISTVIEW_CalculateMaxWidth(infoPtr),
6512 DEFAULT_COLUMN_WIDTH);
6514 LISTVIEW_UpdateSize(infoPtr);
6515 LISTVIEW_UpdateScroll(infoPtr);
6517 if (min(precount,infoPtr->nItemCount) < topvisible)
6518 LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
6522 /* According to MSDN for non-LVS_OWNERDATA this is just
6523 * a performance issue. The control allocates its internal
6524 * data structures for the number of items specified. It
6525 * cuts down on the number of memory allocations. Therefore
6526 * we will just issue a WARN here
6528 WARN("for non-ownerdata performance option not implemented.\n");
6536 * Sets the position of an item.
6539 * [I] infoPtr : valid pointer to the listview structure
6540 * [I] nItem : item index
6541 * [I] pt : coordinate
6547 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
6549 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6552 TRACE("(nItem=%d, &pt=%s\n", nItem, debugpoint(&pt));
6554 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
6555 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
6557 /* This point value seems to be an undocumented feature.
6558 * The best guess is that it means either at the origin,
6559 * or at true beginning of the list. I will assume the origin. */
6560 if ((pt.x == -1) && (pt.y == -1))
6561 LISTVIEW_GetOrigin(infoPtr, &pt);
6562 else if (uView == LVS_ICON)
6564 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
6565 pt.y -= ICON_TOP_PADDING;
6568 /* save the old position */
6569 old.x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
6570 old.y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
6572 /* Is the position changing? */
6573 if (pt.x == old.x && pt.y == old.y) return TRUE;
6575 /* FIXME: shouldn't we invalidate, as the item moved? */
6577 /* Allocating a POINTER for every item is too resource intensive,
6578 * so we'll keep the (x,y) in different arrays */
6579 if (DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)pt.x) &&
6580 DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)pt.y) )
6583 ERR("We should never fail here (nItem=%d, pt=%s), please report.\n",
6584 nItem, debugpoint(&pt));
6590 * Sets the state of one or many items.
6593 * [I] infoPtr : valid pointer to the listview structure
6594 * [I] nItem : item index
6595 * [I] lpLVItem : item or subitem info
6601 static LRESULT LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem)
6603 BOOL bResult = TRUE;
6606 lvItem.iItem = nItem;
6607 lvItem.iSubItem = 0;
6608 lvItem.mask = LVIF_STATE;
6609 lvItem.state = lpLVItem->state;
6610 lvItem.stateMask = lpLVItem->stateMask;
6611 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
6615 /* apply to all items */
6616 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6617 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
6620 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
6627 * Sets the text of an item or subitem.
6630 * [I] hwnd : window handle
6631 * [I] nItem : item index
6632 * [I] lpLVItem : item or subitem info
6633 * [I] isW : TRUE if input is Unicode
6639 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
6643 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
6645 lvItem.iItem = nItem;
6646 lvItem.iSubItem = lpLVItem->iSubItem;
6647 lvItem.mask = LVIF_TEXT;
6648 lvItem.pszText = lpLVItem->pszText;
6649 lvItem.cchTextMax = lpLVItem->cchTextMax;
6651 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
6653 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
6658 * Set item index that marks the start of a multiple selection.
6661 * [I] infoPtr : valid pointer to the listview structure
6662 * [I] nIndex : index
6665 * Index number or -1 if there is no selection mark.
6667 static LRESULT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
6669 INT nOldIndex = infoPtr->nSelectionMark;
6671 TRACE("(nIndex=%d)\n", nIndex);
6673 infoPtr->nSelectionMark = nIndex;
6680 * Sets the text background color.
6683 * [I] infoPtr : valid pointer to the listview structure
6684 * [I] clrTextBk : text background color
6690 static LRESULT LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
6692 TRACE("(clrTextBk=%lx)\n", clrTextBk);
6694 if (infoPtr->clrTextBk != clrTextBk)
6696 infoPtr->clrTextBk = clrTextBk;
6697 LISTVIEW_InvalidateList(infoPtr);
6705 * Sets the text foreground color.
6708 * [I] infoPtr : valid pointer to the listview structure
6709 * [I] clrText : text color
6715 static LRESULT LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
6717 TRACE("(clrText=%lx)\n", clrText);
6719 if (infoPtr->clrText != clrText)
6721 infoPtr->clrText = clrText;
6722 LISTVIEW_InvalidateList(infoPtr);
6728 /* LISTVIEW_SetToolTips */
6729 /* LISTVIEW_SetUnicodeFormat */
6730 /* LISTVIEW_SetWorkAreas */
6734 * Callback internally used by LISTVIEW_SortItems()
6737 * [I] first : pointer to first LISTVIEW_ITEM to compare
6738 * [I] second : pointer to second LISTVIEW_ITEM to compare
6739 * [I] lParam : HWND of control
6742 * if first comes before second : negative
6743 * if first comes after second : positive
6744 * if first and second are equivalent : zero
6746 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
6748 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW((HWND)lParam, 0);
6749 LISTVIEW_ITEM* lv_first = (LISTVIEW_ITEM*) DPA_GetPtr( (HDPA)first, 0 );
6750 LISTVIEW_ITEM* lv_second = (LISTVIEW_ITEM*) DPA_GetPtr( (HDPA)second, 0 );
6752 /* Forward the call to the client defined callback */
6753 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
6758 * Sorts the listview items.
6761 * [I] infoPtr : valid pointer to the listview structure
6762 * [I] pfnCompare : application-defined value
6763 * [I] lParamSort : pointer to comparision callback
6769 static LRESULT LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
6771 UINT lStyle = infoPtr->dwStyle;
6773 LISTVIEW_ITEM *lpItem;
6774 LPVOID selectionMarkItem;
6778 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
6780 if (lStyle & LVS_OWNERDATA) return FALSE;
6782 if (!infoPtr->hdpaItems) return FALSE;
6784 /* if there are 0 or 1 items, there is no need to sort */
6785 if (infoPtr->nItemCount < 2) return TRUE;
6787 if (infoPtr->nFocusedItem >= 0)
6789 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
6790 lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0);
6791 if (lpItem) lpItem->state |= LVIS_FOCUSED;
6793 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
6794 /* clear the lpItem->state for non-selected ones */
6795 /* remove the selection ranges */
6797 infoPtr->pfnCompare = pfnCompare;
6798 infoPtr->lParamSort = lParamSort;
6799 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr->hwndSelf);
6801 /* Adjust selections and indices so that they are the way they should
6802 * be after the sort (otherwise, the list items move around, but
6803 * whatever is at the item's previous original position will be
6806 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
6807 for (i=0; i < infoPtr->nItemCount; i++)
6809 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
6810 lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0);
6812 if (lpItem->state & LVIS_SELECTED)
6814 item.state = LVIS_SELECTED;
6815 item.stateMask = LVIS_SELECTED;
6816 LISTVIEW_SetItemState(infoPtr, i, &item);
6818 if (lpItem->state & LVIS_FOCUSED)
6820 infoPtr->nFocusedItem = i;
6821 lpItem->state &= ~LVIS_FOCUSED;
6824 if (selectionMarkItem != NULL)
6825 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
6826 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
6828 /* align the items */
6829 LISTVIEW_AlignTop(infoPtr);
6831 /* refresh the display */
6832 LISTVIEW_InvalidateList(infoPtr); /* FIXME: display should not change for [SMALL]ICON view */
6839 * Updates an items or rearranges the listview control.
6842 * [I] infoPtr : valid pointer to the listview structure
6843 * [I] nItem : item index
6849 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
6851 LONG lStyle = infoPtr->dwStyle;
6852 UINT uView = lStyle & LVS_TYPEMASK;
6854 TRACE("(nItem=%d)\n", nItem);
6856 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
6858 /* rearrange with default alignment style */
6859 if ((lStyle & LVS_AUTOARRANGE) && ((uView == LVS_ICON) ||(uView == LVS_SMALLICON)))
6860 LISTVIEW_Arrange(infoPtr, 0);
6862 LISTVIEW_InvalidateItem(infoPtr, nItem);
6870 * Creates the listview control.
6873 * [I] hwnd : window handle
6874 * [I] lpcs : the create parameters
6880 static LRESULT LISTVIEW_Create(HWND hwnd, LPCREATESTRUCTW lpcs)
6882 LISTVIEW_INFO *infoPtr;
6883 UINT uView = lpcs->style & LVS_TYPEMASK;
6886 TRACE("(lpcs=%p)\n", lpcs);
6888 /* initialize info pointer */
6889 infoPtr = (LISTVIEW_INFO *)COMCTL32_Alloc(sizeof(LISTVIEW_INFO));
6890 if (!infoPtr) return -1;
6892 SetWindowLongW(hwnd, 0, (LONG)infoPtr);
6894 infoPtr->hwndSelf = hwnd;
6895 infoPtr->dwStyle = lpcs->style;
6896 /* determine the type of structures to use */
6897 infoPtr->notifyFormat = SendMessageW(GetParent(infoPtr->hwndSelf), WM_NOTIFYFORMAT,
6898 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
6900 /* initialize color information */
6901 infoPtr->clrBk = CLR_NONE;
6902 infoPtr->clrText = comctl32_color.clrWindowText;
6903 infoPtr->clrTextBk = CLR_DEFAULT;
6904 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
6906 /* set default values */
6907 infoPtr->nFocusedItem = -1;
6908 infoPtr->nSelectionMark = -1;
6909 infoPtr->nHotItem = -1;
6910 infoPtr->bRedraw = TRUE;
6911 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
6912 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
6913 infoPtr->nEditLabelItem = -1;
6915 /* get default font (icon title) */
6916 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
6917 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
6918 infoPtr->hFont = infoPtr->hDefaultFont;
6919 LISTVIEW_SaveTextMetrics(infoPtr);
6922 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, (LPCWSTR)NULL,
6923 WS_CHILD | HDS_HORZ | (DWORD)((LVS_NOSORTHEADER & lpcs->style)?0:HDS_BUTTONS),
6924 0, 0, 0, 0, hwnd, (HMENU)0,
6925 lpcs->hInstance, NULL);
6927 /* set header unicode format */
6928 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)NULL);
6930 /* set header font */
6931 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
6933 infoPtr->hdpaColumns = DPA_Create(10);
6935 if (uView == LVS_ICON)
6937 infoPtr->iconSize.cx = GetSystemMetrics(SM_CXICON);
6938 infoPtr->iconSize.cy = GetSystemMetrics(SM_CYICON);
6940 else if (uView == LVS_REPORT)
6942 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
6944 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
6948 /* set HDS_HIDDEN flag to hide the header bar */
6949 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE,
6950 GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE) | HDS_HIDDEN);
6954 infoPtr->iconSize.cx = GetSystemMetrics(SM_CXSMICON);
6955 infoPtr->iconSize.cy = GetSystemMetrics(SM_CYSMICON);
6959 infoPtr->iconSize.cx = GetSystemMetrics(SM_CXSMICON);
6960 infoPtr->iconSize.cy = GetSystemMetrics(SM_CYSMICON);
6963 infoPtr->iconStateSize.cx = GetSystemMetrics(SM_CXSMICON);
6964 infoPtr->iconStateSize.cy = GetSystemMetrics(SM_CYSMICON);
6966 /* display unsupported listview window styles */
6967 LISTVIEW_UnsupportedStyles(lpcs->style);
6969 /* allocate memory for the data structure */
6970 infoPtr->hdpaItems = DPA_Create(10);
6971 infoPtr->hdpaPosX = DPA_Create(10);
6972 infoPtr->hdpaPosY = DPA_Create(10);
6974 /* allocate memory for the selection ranges */
6975 infoPtr->selectionRanges = ranges_create(10);
6977 /* initialize size of items */
6978 infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
6979 infoPtr->nItemHeight = LISTVIEW_CalculateMaxHeight(infoPtr);
6981 /* initialize the hover time to -1(indicating the default system hover time) */
6982 infoPtr->dwHoverTime = -1;
6989 * Erases the background of the listview control.
6992 * [I] infoPtr : valid pointer to the listview structure
6993 * [I] hdc : device context handle
6999 static inline BOOL LISTVIEW_EraseBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc)
7003 TRACE("(hdc=%x)\n", hdc);
7005 if (!GetClipBox(hdc, &rc)) return FALSE;
7007 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
7013 * Helper function for LISTVIEW_[HV]Scroll *only*.
7014 * Performs vertical/horizontal scrolling by a give amount.
7017 * [I] infoPtr : valid pointer to the listview structure
7018 * [I] dx : amount of horizontal scroll
7019 * [I] dy : amount of vertical scroll
7021 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7023 /* now we can scroll the list */
7024 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
7025 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
7026 /* if we have focus, adjust rect */
7027 OffsetRect(&infoPtr->rcFocus, dx, dy);
7028 UpdateWindow(infoPtr->hwndSelf);
7033 * Performs vertical scrolling.
7036 * [I] infoPtr : valid pointer to the listview structure
7037 * [I] nScrollCode : scroll code
7038 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7039 * [I] hScrollWnd : scrollbar control window handle
7045 * SB_LINEUP/SB_LINEDOWN:
7046 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
7047 * for LVS_REPORT is 1 line
7048 * for LVS_LIST cannot occur
7051 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7052 INT nScrollDiff, HWND hScrollWnd)
7054 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7055 INT nOldScrollPos, nNewScrollPos;
7056 SCROLLINFO scrollInfo;
7059 TRACE("(nScrollCode=%d, nScrollDiff=%d)\n", nScrollCode, nScrollDiff);
7061 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7063 scrollInfo.cbSize = sizeof(SCROLLINFO);
7064 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7066 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
7068 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
7070 nOldScrollPos = scrollInfo.nPos;
7071 switch (nScrollCode)
7077 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
7081 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
7085 nScrollDiff = -scrollInfo.nPage;
7089 nScrollDiff = scrollInfo.nPage;
7092 case SB_THUMBPOSITION:
7094 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7101 /* quit right away if pos isn't changing */
7102 if (nScrollDiff == 0) return 0;
7104 /* calculate new position, and handle overflows */
7105 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7106 if (nScrollDiff > 0) {
7107 if (nNewScrollPos < nOldScrollPos ||
7108 nNewScrollPos > scrollInfo.nMax)
7109 nNewScrollPos = scrollInfo.nMax;
7111 if (nNewScrollPos > nOldScrollPos ||
7112 nNewScrollPos < scrollInfo.nMin)
7113 nNewScrollPos = scrollInfo.nMin;
7116 /* set the new position, and reread in case it changed */
7117 scrollInfo.fMask = SIF_POS;
7118 scrollInfo.nPos = nNewScrollPos;
7119 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
7121 /* carry on only if it really changed */
7122 if (nNewScrollPos == nOldScrollPos) return 0;
7124 /* now adjust to client coordinates */
7125 nScrollDiff = nOldScrollPos - nNewScrollPos;
7126 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
7128 /* and scroll the window */
7129 scroll_list(infoPtr, 0, nScrollDiff);
7136 * Performs horizontal scrolling.
7139 * [I] infoPtr : valid pointer to the listview structure
7140 * [I] nScrollCode : scroll code
7141 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7142 * [I] hScrollWnd : scrollbar control window handle
7148 * SB_LINELEFT/SB_LINERIGHT:
7149 * for LVS_ICON, LVS_SMALLICON 1 pixel
7150 * for LVS_REPORT is 1 pixel
7151 * for LVS_LIST is 1 column --> which is a 1 because the
7152 * scroll is based on columns not pixels
7155 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7156 INT nScrollDiff, HWND hScrollWnd)
7158 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7159 INT nOldScrollPos, nNewScrollPos;
7160 SCROLLINFO scrollInfo;
7162 TRACE("(nScrollCode=%d, nScrollDiff=%d)\n", nScrollCode, nScrollDiff);
7164 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7166 scrollInfo.cbSize = sizeof(SCROLLINFO);
7167 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7169 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
7171 nOldScrollPos = scrollInfo.nPos;
7173 switch (nScrollCode)
7187 nScrollDiff = -scrollInfo.nPage;
7191 nScrollDiff = scrollInfo.nPage;
7194 case SB_THUMBPOSITION:
7196 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7203 /* quit right away if pos isn't changing */
7204 if (nScrollDiff == 0) return 0;
7206 /* calculate new position, and handle overflows */
7207 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7208 if (nScrollDiff > 0) {
7209 if (nNewScrollPos < nOldScrollPos ||
7210 nNewScrollPos > scrollInfo.nMax)
7211 nNewScrollPos = scrollInfo.nMax;
7213 if (nNewScrollPos > nOldScrollPos ||
7214 nNewScrollPos < scrollInfo.nMin)
7215 nNewScrollPos = scrollInfo.nMin;
7218 /* set the new position, and reread in case it changed */
7219 scrollInfo.fMask = SIF_POS;
7220 scrollInfo.nPos = nNewScrollPos;
7221 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
7223 /* carry on only if it really changed */
7224 if (nNewScrollPos == nOldScrollPos) return 0;
7226 if(uView == LVS_REPORT)
7227 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
7229 /* now adjust to client coordinates */
7230 nScrollDiff = nOldScrollPos - nNewScrollPos;
7231 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
7233 /* and scroll the window */
7234 scroll_list(infoPtr, nScrollDiff, 0);
7239 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
7241 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7242 INT gcWheelDelta = 0;
7243 UINT pulScrollLines = 3;
7244 SCROLLINFO scrollInfo;
7246 TRACE("(wheelDelta=%d)\n", wheelDelta);
7248 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
7249 gcWheelDelta -= wheelDelta;
7251 scrollInfo.cbSize = sizeof(SCROLLINFO);
7252 scrollInfo.fMask = SIF_POS;
7259 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
7260 * should be fixed in the future.
7262 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
7263 LISTVIEW_VScroll(infoPtr, SB_THUMBPOSITION,
7264 scrollInfo.nPos + (gcWheelDelta < 0) ?
7265 LISTVIEW_SCROLL_ICON_LINE_SIZE :
7266 -LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
7270 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
7272 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
7274 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
7275 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
7276 LISTVIEW_VScroll(infoPtr, SB_THUMBPOSITION, scrollInfo.nPos + cLineScroll, 0);
7282 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
7293 * [I] infoPtr : valid pointer to the listview structure
7294 * [I] nVirtualKey : virtual key
7295 * [I] lKeyData : key data
7300 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
7302 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7304 NMLVKEYDOWN nmKeyDown;
7306 TRACE("(nVirtualKey=%d, lKeyData=%ld)\n", nVirtualKey, lKeyData);
7308 /* send LVN_KEYDOWN notification */
7309 nmKeyDown.wVKey = nVirtualKey;
7310 nmKeyDown.flags = 0;
7311 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
7313 switch (nVirtualKey)
7316 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
7318 notify(infoPtr, NM_RETURN);
7319 notify(infoPtr, LVN_ITEMACTIVATE);
7324 if (infoPtr->nItemCount > 0)
7329 if (infoPtr->nItemCount > 0)
7330 nItem = infoPtr->nItemCount - 1;
7334 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
7338 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
7342 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
7346 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
7350 if (uView == LVS_REPORT)
7351 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr);
7353 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
7354 * LISTVIEW_GetCountPerRow(infoPtr);
7355 if(nItem < 0) nItem = 0;
7359 if (uView == LVS_REPORT)
7360 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr);
7362 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
7363 * LISTVIEW_GetCountPerRow(infoPtr);
7364 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
7368 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem))
7369 LISTVIEW_KeySelection(infoPtr, nItem);
7379 * [I] infoPtr : valid pointer to the listview structure
7384 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
7388 /* if we did not have the focus, there's nothing to do */
7389 if (!infoPtr->bFocus) return 0;
7391 /* send NM_KILLFOCUS notification */
7392 notify(infoPtr, NM_KILLFOCUS);
7394 /* if we have a focus rectagle, get rid of it */
7395 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
7397 /* set window focus flag */
7398 infoPtr->bFocus = FALSE;
7400 /* invalidate the selected items before reseting focus flag */
7401 LISTVIEW_InvalidateSelectedItems(infoPtr);
7408 * Processes double click messages (left mouse button).
7411 * [I] infoPtr : valid pointer to the listview structure
7412 * [I] wKey : key flag
7413 * [I] pts : mouse coordinate
7418 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7420 LVHITTESTINFO htInfo;
7422 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7424 /* send NM_RELEASEDCAPTURE notification */
7425 notify(infoPtr, NM_RELEASEDCAPTURE);
7427 htInfo.pt.x = pts.x;
7428 htInfo.pt.y = pts.y;
7430 /* send NM_DBLCLK notification */
7431 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
7432 notify_click(infoPtr, NM_DBLCLK, &htInfo);
7434 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
7435 if(htInfo.iItem != -1) notify(infoPtr, LVN_ITEMACTIVATE);
7442 * Processes mouse down messages (left mouse button).
7445 * [I] infoPtr : valid pointer to the listview structure
7446 * [I] wKey : key flag
7447 * [I] pts : mouse coordinate
7452 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7454 LVHITTESTINFO lvHitTestInfo;
7455 LONG lStyle = infoPtr->dwStyle;
7456 static BOOL bGroupSelect = TRUE;
7457 POINT pt = { pts.x, pts.y };
7460 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7462 /* send NM_RELEASEDCAPTURE notification */
7463 notify(infoPtr, NM_RELEASEDCAPTURE);
7465 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
7467 /* set left button down flag */
7468 infoPtr->bLButtonDown = TRUE;
7470 lvHitTestInfo.pt.x = pts.x;
7471 lvHitTestInfo.pt.y = pts.y;
7473 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
7474 TRACE("at %s, nItem=%d\n", debugpoint(&pt), nItem);
7475 infoPtr->nEditLabelItem = -1;
7476 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
7478 if (lStyle & LVS_SINGLESEL)
7480 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
7481 infoPtr->nEditLabelItem = nItem;
7483 LISTVIEW_SetSelection(infoPtr, nItem);
7487 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
7490 LISTVIEW_AddGroupSelection(infoPtr, nItem);
7495 item.state = LVIS_SELECTED | LVIS_FOCUSED;
7496 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
7498 LISTVIEW_SetItemState(infoPtr,nItem,&item);
7499 infoPtr->nSelectionMark = nItem;
7502 else if (wKey & MK_CONTROL)
7506 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
7508 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
7509 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
7510 LISTVIEW_SetItemState(infoPtr, nItem, &item);
7511 infoPtr->nSelectionMark = nItem;
7513 else if (wKey & MK_SHIFT)
7515 LISTVIEW_SetGroupSelection(infoPtr, nItem);
7519 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
7520 infoPtr->nEditLabelItem = nItem;
7522 /* set selection (clears other pre-existing selections) */
7523 LISTVIEW_SetSelection(infoPtr, nItem);
7529 /* remove all selections */
7530 LISTVIEW_DeselectAll(infoPtr);
7538 * Processes mouse up messages (left mouse button).
7541 * [I] infoPtr : valid pointer to the listview structure
7542 * [I] wKey : key flag
7543 * [I] pts : mouse coordinate
7548 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7550 LVHITTESTINFO lvHitTestInfo;
7552 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7554 if (!infoPtr->bLButtonDown) return 0;
7556 lvHitTestInfo.pt.x = pts.x;
7557 lvHitTestInfo.pt.y = pts.y;
7559 /* send NM_CLICK notification */
7560 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
7561 notify_click(infoPtr, NM_CLICK, &lvHitTestInfo);
7563 /* set left button flag */
7564 infoPtr->bLButtonDown = FALSE;
7566 /* if we clicked on a selected item, edit the label */
7567 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
7568 LISTVIEW_EditLabelT(infoPtr, lvHitTestInfo.iItem, TRUE);
7575 * Destroys the listview control (called after WM_DESTROY).
7578 * [I] infoPtr : valid pointer to the listview structure
7583 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
7585 LONG lStyle = infoPtr->dwStyle;
7589 /* delete all items */
7590 LISTVIEW_DeleteAllItems(infoPtr);
7592 /* destroy data structure */
7593 DPA_Destroy(infoPtr->hdpaItems);
7594 ranges_destroy(infoPtr->selectionRanges);
7596 /* destroy image lists */
7597 if (!(lStyle & LVS_SHAREIMAGELISTS))
7599 if (infoPtr->himlNormal)
7600 ImageList_Destroy(infoPtr->himlNormal);
7601 if (infoPtr->himlSmall)
7602 ImageList_Destroy(infoPtr->himlSmall);
7603 if (infoPtr->himlState)
7604 ImageList_Destroy(infoPtr->himlState);
7607 /* destroy font, bkgnd brush */
7609 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
7610 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
7612 /* free listview info pointer*/
7613 COMCTL32_Free(infoPtr);
7615 SetWindowLongW(infoPtr->hwndSelf, 0, 0);
7621 * Handles notifications from children.
7624 * [I] infoPtr : valid pointer to the listview structure
7625 * [I] nCtrlId : control identifier
7626 * [I] lpnmh : notification information
7631 static LRESULT LISTVIEW_Notify(LISTVIEW_INFO *infoPtr, INT nCtrlId, LPNMHDR lpnmh)
7633 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7635 TRACE("(nCtrlId=%d, lpnmh=%p)\n", nCtrlId, lpnmh);
7637 /* handle notification from header control */
7638 if (lpnmh->hwndFrom == infoPtr->hwndHeader)
7640 LPNMHEADERW lphnm = (LPNMHEADERW)lpnmh;
7642 if (lpnmh->code == HDN_TRACKW || lpnmh->code == HDN_TRACKA ||
7643 lpnmh->code == HDN_ITEMCHANGEDW || lpnmh->code == HDN_ITEMCHANGEDA)
7645 COLUMN_INFO *lpColumnInfo;
7649 if (lphnm->iItem < 0 || lphnm->iItem >= infoPtr->hdpaColumns->nItemCount) return 0;
7650 if (!lphnm->pitem || !(lphnm->pitem->mask & HDI_WIDTH)) return 0;
7652 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lphnm->iItem);
7654 /* determine how much we change since the last know position */
7655 dx = lphnm->pitem->cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
7657 /* ajust the column being tracked */
7658 lpColumnInfo->rcHeader.right += dx;
7660 /* compute the rectangle for the tracked column */
7661 rcCol.left = lpColumnInfo->rcHeader.left;
7662 rcCol.top = infoPtr->rcList.top;
7663 rcCol.right = lpColumnInfo->rcHeader.right;
7664 rcCol.bottom = infoPtr->rcList.bottom;
7666 LISTVIEW_ScrollColumns(infoPtr, lphnm->iItem + 1, dx);
7667 if (uView == LVS_REPORT) LISTVIEW_InvalidateRect(infoPtr, &rcCol);
7669 else if(lpnmh->code == HDN_ITEMCLICKW || lpnmh->code == HDN_ITEMCLICKA)
7671 /* Handle sorting by Header Column */
7674 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
7676 nmlv.iSubItem = lphnm->iItem;
7677 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
7686 * Determines the type of structure to use.
7689 * [I] infoPtr : valid pointer to the listview structureof the sender
7690 * [I] hwndFrom : listview window handle
7691 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
7696 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
7698 TRACE("(hwndFrom=%x, nCommand=%d)\n", hwndFrom, nCommand);
7700 if (nCommand == NF_REQUERY)
7701 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT,
7702 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
7708 * Paints/Repaints the listview control.
7711 * [I] infoPtr : valid pointer to the listview structure
7712 * [I] hdc : device context handle
7717 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
7719 TRACE("(hdc=%x)\n", hdc);
7722 LISTVIEW_Refresh(infoPtr, hdc);
7727 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
7729 if (ps.fErase) LISTVIEW_FillBkgnd(infoPtr, hdc, &ps.rcPaint);
7730 LISTVIEW_Refresh(infoPtr, hdc);
7731 EndPaint(infoPtr->hwndSelf, &ps);
7739 * Processes double click messages (right mouse button).
7742 * [I] infoPtr : valid pointer to the listview structure
7743 * [I] wKey : key flag
7744 * [I] pts : mouse coordinate
7749 static LRESULT LISTVIEW_RButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7751 LVHITTESTINFO lvHitTestInfo;
7753 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
7755 /* send NM_RELEASEDCAPTURE notification */
7756 notify(infoPtr, NM_RELEASEDCAPTURE);
7758 /* send NM_RDBLCLK notification */
7759 lvHitTestInfo.pt.x = pts.x;
7760 lvHitTestInfo.pt.y = pts.y;
7761 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
7762 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
7769 * Processes mouse down messages (right mouse button).
7772 * [I] infoPtr : valid pointer to the listview structure
7773 * [I] wKey : key flag
7774 * [I] pts : mouse coordinate
7779 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7781 LVHITTESTINFO lvHitTestInfo;
7784 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
7786 /* send NM_RELEASEDCAPTURE notification */
7787 notify(infoPtr, NM_RELEASEDCAPTURE);
7789 /* make sure the listview control window has the focus */
7790 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
7792 /* set right button down flag */
7793 infoPtr->bRButtonDown = TRUE;
7795 /* determine the index of the selected item */
7796 lvHitTestInfo.pt.x = pts.x;
7797 lvHitTestInfo.pt.y = pts.y;
7798 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
7800 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
7802 LISTVIEW_SetItemFocus(infoPtr, nItem);
7803 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
7804 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
7805 LISTVIEW_SetSelection(infoPtr, nItem);
7809 LISTVIEW_DeselectAll(infoPtr);
7817 * Processes mouse up messages (right mouse button).
7820 * [I] infoPtr : valid pointer to the listview structure
7821 * [I] wKey : key flag
7822 * [I] pts : mouse coordinate
7827 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7829 LVHITTESTINFO lvHitTestInfo;
7832 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
7834 if (!infoPtr->bRButtonDown) return 0;
7836 /* set button flag */
7837 infoPtr->bRButtonDown = FALSE;
7839 /* Send NM_RClICK notification */
7840 lvHitTestInfo.pt.x = pts.x;
7841 lvHitTestInfo.pt.y = pts.y;
7842 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
7843 notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo);
7845 /* Change to screen coordinate for WM_CONTEXTMENU */
7846 pt = lvHitTestInfo.pt;
7847 ClientToScreen(infoPtr->hwndSelf, &pt);
7849 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
7850 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
7851 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
7862 * [I] infoPtr : valid pointer to the listview structure
7863 * [I] hwnd : window handle of window containing the cursor
7864 * [I] nHittest : hit-test code
7865 * [I] wMouseMsg : ideintifier of the mouse message
7868 * TRUE if cursor is set
7871 static BOOL LISTVIEW_SetCursor(LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
7873 LVHITTESTINFO lvHitTestInfo;
7875 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
7877 if(!infoPtr->hHotCursor) return FALSE;
7879 GetCursorPos(&lvHitTestInfo.pt);
7880 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
7882 SetCursor(infoPtr->hHotCursor);
7892 * [I] infoPtr : valid pointer to the listview structure
7893 * [I] hwndLoseFocus : handle of previously focused window
7898 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
7900 TRACE("(hwndLoseFocus=%x)\n", hwndLoseFocus);
7902 /* if we have the focus already, there's nothing to do */
7903 if (infoPtr->bFocus) return 0;
7905 /* send NM_SETFOCUS notification */
7906 notify(infoPtr, NM_SETFOCUS);
7908 /* set window focus flag */
7909 infoPtr->bFocus = TRUE;
7911 /* put the focus rect back on */
7912 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
7914 /* redraw all visible selected items */
7915 LISTVIEW_InvalidateSelectedItems(infoPtr);
7925 * [I] infoPtr : valid pointer to the listview structure
7926 * [I] fRedraw : font handle
7927 * [I] fRedraw : redraw flag
7932 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
7934 HFONT oldFont = infoPtr->hFont;
7936 TRACE("(hfont=%x,redraw=%hu)\n", hFont, fRedraw);
7938 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
7939 if (infoPtr->hFont == oldFont) return 0;
7941 LISTVIEW_SaveTextMetrics(infoPtr);
7943 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
7944 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
7946 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
7953 * Message handling for WM_SETREDRAW.
7954 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
7957 * [I] infoPtr : valid pointer to the listview structure
7958 * [I] bRedraw: state of redraw flag
7961 * DefWinProc return value
7963 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
7965 infoPtr->bRedraw = bRedraw;
7967 RedrawWindow(infoPtr->hwndSelf, NULL, 0,
7968 RDW_INVALIDATE | RDW_FRAME | RDW_ERASE | RDW_ALLCHILDREN | RDW_ERASENOW);
7974 * Resizes the listview control. This function processes WM_SIZE
7975 * messages. At this time, the width and height are not used.
7978 * [I] infoPtr : valid pointer to the listview structure
7979 * [I] Width : new width
7980 * [I] Height : new height
7985 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
7987 LONG lStyle = infoPtr->dwStyle;
7988 UINT uView = lStyle & LVS_TYPEMASK;
7990 TRACE("(width=%d, height=%d)\n", Width, Height);
7992 if (LISTVIEW_UpdateSize(infoPtr))
7994 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
7996 if (lStyle & LVS_ALIGNLEFT)
7997 LISTVIEW_AlignLeft(infoPtr);
7999 LISTVIEW_AlignTop(infoPtr);
8002 LISTVIEW_UpdateScroll(infoPtr);
8004 LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
8012 * Sets the size information.
8015 * [I] infoPtr : valid pointer to the listview structure
8018 * Zero if no size change
8021 static BOOL LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
8023 LONG lStyle = infoPtr->dwStyle;
8024 UINT uView = lStyle & LVS_TYPEMASK;
8028 GetClientRect(infoPtr->hwndSelf, &rcList);
8029 CopyRect(&rcOld,&(infoPtr->rcList));
8030 infoPtr->rcList.left = 0;
8031 infoPtr->rcList.right = max(rcList.right - rcList.left, 1);
8032 infoPtr->rcList.top = 0;
8033 infoPtr->rcList.bottom = max(rcList.bottom - rcList.top, 1);
8035 if (uView == LVS_LIST)
8037 /* Apparently the "LIST" style is supposed to have the same
8038 * number of items in a column even if there is no scroll bar.
8039 * Since if a scroll bar already exists then the bottom is already
8040 * reduced, only reduce if the scroll bar does not currently exist.
8041 * The "2" is there to mimic the native control. I think it may be
8042 * related to either padding or edges. (GLA 7/2002)
8044 if (!(lStyle & WS_HSCROLL))
8046 INT nHScrollHeight = GetSystemMetrics(SM_CYHSCROLL);
8047 if (infoPtr->rcList.bottom > nHScrollHeight)
8048 infoPtr->rcList.bottom -= (nHScrollHeight + 2);
8052 if (infoPtr->rcList.bottom > 2)
8053 infoPtr->rcList.bottom -= 2;
8056 else if (uView == LVS_REPORT)
8063 Header_Layout(infoPtr->hwndHeader, &hl);
8065 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8067 if (!(LVS_NOCOLUMNHEADER & lStyle))
8068 infoPtr->rcList.top = max(wp.cy, 0);
8070 return (EqualRect(&rcOld,&(infoPtr->rcList)));
8075 * Processes WM_STYLECHANGED messages.
8078 * [I] infoPtr : valid pointer to the listview structure
8079 * [I] wStyleType : window style type (normal or extended)
8080 * [I] lpss : window style information
8085 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
8088 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
8089 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
8091 TRACE("(styletype=%x, styleOld=0x%08lx, styleNew=0x%08lx)\n",
8092 wStyleType, lpss->styleOld, lpss->styleNew);
8094 if (wStyleType != GWL_STYLE) return 0;
8096 /* FIXME: if LVS_NOSORTHEADER changed, update header */
8097 /* what if LVS_OWNERDATA changed? */
8098 /* or LVS_SINGLESEL */
8099 /* or LVS_SORT{AS,DES}CENDING */
8101 infoPtr->dwStyle = lpss->styleNew;
8103 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
8104 ((lpss->styleNew & WS_HSCROLL) == 0))
8105 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
8107 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
8108 ((lpss->styleNew & WS_VSCROLL) == 0))
8109 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
8111 if (uNewView != uOldView)
8113 SIZE oldIconSize = infoPtr->iconSize;
8116 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8117 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8119 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
8120 SetRectEmpty(&infoPtr->rcFocus);
8122 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
8123 update_icon_size(himl, uNewView != LVS_ICON, &infoPtr->iconSize);
8125 if (uNewView == LVS_ICON)
8127 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
8129 TRACE("icon old size=(%ld,%ld), new size=(%ld,%ld)\n",
8130 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
8131 LISTVIEW_SetIconSpacing(infoPtr, 0);
8134 else if (uNewView == LVS_REPORT)
8139 hl.prc = &infoPtr->rcList;
8141 Header_Layout(infoPtr->hwndHeader, &hl);
8142 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8145 infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
8146 infoPtr->nItemHeight = LISTVIEW_CalculateMaxHeight(infoPtr);
8149 if (uNewView == LVS_REPORT)
8150 ShowWindow(infoPtr->hwndHeader, (LVS_NOCOLUMNHEADER & lpss->styleNew) ? SW_HIDE : SW_SHOWNORMAL);
8152 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
8153 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
8155 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
8156 LISTVIEW_AlignLeft(infoPtr);
8158 LISTVIEW_AlignTop(infoPtr);
8161 /* update the size of the client area */
8162 LISTVIEW_UpdateSize(infoPtr);
8164 /* add scrollbars if needed */
8165 LISTVIEW_UpdateScroll(infoPtr);
8167 /* invalidate client area + erase background */
8168 LISTVIEW_InvalidateList(infoPtr);
8170 /* print the list of unsupported window styles */
8171 LISTVIEW_UnsupportedStyles(lpss->styleNew);
8178 * Window procedure of the listview control.
8181 static LRESULT WINAPI
8182 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8184 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(hwnd, 0);
8186 TRACE("(uMsg=%x wParam=%x lParam=%lx)\n", uMsg, wParam, lParam);
8188 if (!infoPtr && (uMsg != WM_CREATE))
8189 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8193 infoPtr->dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
8198 case LVM_APPROXIMATEVIEWRECT:
8199 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
8200 LOWORD(lParam), HIWORD(lParam));
8202 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
8204 /* case LVN_CANCELEDITLABEL */
8206 /* case LVM_CREATEDRAGIMAGE: */
8208 case LVM_DELETEALLITEMS:
8209 return LISTVIEW_DeleteAllItems(infoPtr);
8211 case LVM_DELETECOLUMN:
8212 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
8214 case LVM_DELETEITEM:
8215 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
8217 case LVM_EDITLABELW:
8218 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
8220 case LVM_EDITLABELA:
8221 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
8223 /* case LVN_ENABLEGROUPVIEW: */
8225 case LVM_ENSUREVISIBLE:
8226 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
8229 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
8232 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
8234 case LVM_GETBKCOLOR:
8235 return infoPtr->clrBk;
8237 /* case LVM_GETBKIMAGE: */
8239 case LVM_GETCALLBACKMASK:
8240 return infoPtr->uCallbackMask;
8242 case LVM_GETCOLUMNA:
8243 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8245 case LVM_GETCOLUMNW:
8246 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8248 case LVM_GETCOLUMNORDERARRAY:
8249 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
8251 case LVM_GETCOLUMNWIDTH:
8252 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
8254 case LVM_GETCOUNTPERPAGE:
8255 return LISTVIEW_GetCountPerPage(infoPtr);
8257 case LVM_GETEDITCONTROL:
8258 return (LRESULT)infoPtr->hwndEdit;
8260 case LVM_GETEXTENDEDLISTVIEWSTYLE:
8261 return infoPtr->dwLvExStyle;
8264 return (LRESULT)infoPtr->hwndHeader;
8266 case LVM_GETHOTCURSOR:
8267 return (LRESULT)infoPtr->hHotCursor;
8269 case LVM_GETHOTITEM:
8270 return infoPtr->nHotItem;
8272 case LVM_GETHOVERTIME:
8273 return infoPtr->dwHoverTime;
8275 case LVM_GETIMAGELIST:
8276 return LISTVIEW_GetImageList(infoPtr, (INT)wParam);
8278 /* case LVN_GETINSERTMARK: */
8280 /* case LVN_GETINSERTMARKCOLOR: */
8282 /* case LVN_GETINSERTMARKRECT: */
8284 case LVM_GETISEARCHSTRINGA:
8285 case LVM_GETISEARCHSTRINGW:
8286 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
8290 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
8293 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
8295 case LVM_GETITEMCOUNT:
8296 return infoPtr->nItemCount;
8298 case LVM_GETITEMPOSITION:
8299 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
8301 case LVM_GETITEMRECT:
8302 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
8304 case LVM_GETITEMSPACING:
8305 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
8307 case LVM_GETITEMSTATE:
8308 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
8310 case LVM_GETITEMTEXTA:
8311 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
8313 case LVM_GETITEMTEXTW:
8314 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
8316 case LVM_GETNEXTITEM:
8317 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
8319 case LVM_GETNUMBEROFWORKAREAS:
8320 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
8324 if (!lParam) return FALSE;
8325 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
8328 /* case LVN_GETOUTLINECOLOR: */
8330 /* case LVM_GETSELECTEDCOLUMN: */
8332 case LVM_GETSELECTEDCOUNT:
8333 return LISTVIEW_GetSelectedCount(infoPtr);
8335 case LVM_GETSELECTIONMARK:
8336 return infoPtr->nSelectionMark;
8338 case LVM_GETSTRINGWIDTHA:
8339 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
8341 case LVM_GETSTRINGWIDTHW:
8342 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
8344 case LVM_GETSUBITEMRECT:
8345 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
8347 case LVM_GETTEXTBKCOLOR:
8348 return infoPtr->clrTextBk;
8350 case LVM_GETTEXTCOLOR:
8351 return infoPtr->clrText;
8353 /* case LVN_GETTILEINFO: */
8355 /* case LVN_GETTILEVIEWINFO: */
8357 case LVM_GETTOOLTIPS:
8358 FIXME("LVM_GETTOOLTIPS: unimplemented\n");
8361 case LVM_GETTOPINDEX:
8362 return LISTVIEW_GetTopIndex(infoPtr);
8364 /*case LVM_GETUNICODEFORMAT:
8365 FIXME("LVM_GETUNICODEFORMAT: unimplemented\n");
8368 case LVM_GETVIEWRECT:
8369 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
8371 case LVM_GETWORKAREAS:
8372 FIXME("LVM_GETWORKAREAS: unimplemented\n");
8375 /* case LVN_HASGROUP: */
8378 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
8380 case LVM_INSERTCOLUMNA:
8381 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8383 case LVM_INSERTCOLUMNW:
8384 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8386 /* case LVN_INSERTGROUP: */
8388 /* case LVN_INSERTGROUPSORTED: */
8390 case LVM_INSERTITEMA:
8391 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
8393 case LVM_INSERTITEMW:
8394 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
8396 /* case LVN_INSERTMARKHITTEST: */
8398 /* case LVN_ISGROUPVIEWENABLED: */
8400 /* case LVN_MAPIDTOINDEX: */
8402 /* case LVN_INEDXTOID: */
8404 /* case LVN_MOVEGROUP: */
8406 /* case LVN_MOVEITEMTOGROUP: */
8408 case LVM_REDRAWITEMS:
8409 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
8411 /* case LVN_REMOVEALLGROUPS: */
8413 /* case LVN_REMOVEGROUP: */
8416 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
8418 case LVM_SETBKCOLOR:
8419 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
8421 /* case LVM_SETBKIMAGE: */
8423 case LVM_SETCALLBACKMASK:
8424 infoPtr->uCallbackMask = (UINT)wParam;
8427 case LVM_SETCOLUMNA:
8428 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8430 case LVM_SETCOLUMNW:
8431 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8433 case LVM_SETCOLUMNORDERARRAY:
8434 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
8436 case LVM_SETCOLUMNWIDTH:
8437 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, SLOWORD(lParam));
8439 case LVM_SETEXTENDEDLISTVIEWSTYLE:
8440 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
8442 /* case LVN_SETGROUPINFO: */
8444 /* case LVN_SETGROUPMETRICS: */
8446 case LVM_SETHOTCURSOR:
8447 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
8449 case LVM_SETHOTITEM:
8450 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
8452 case LVM_SETHOVERTIME:
8453 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
8455 case LVM_SETICONSPACING:
8456 return LISTVIEW_SetIconSpacing(infoPtr, (DWORD)lParam);
8458 case LVM_SETIMAGELIST:
8459 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
8461 /* case LVN_SETINFOTIP: */
8463 /* case LVN_SETINSERTMARK: */
8465 /* case LVN_SETINSERTMARKCOLOR: */
8468 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
8471 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
8473 case LVM_SETITEMCOUNT:
8474 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
8476 case LVM_SETITEMPOSITION:
8478 POINT pt = { SLOWORD(lParam), SHIWORD(lParam) };
8479 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
8482 case LVM_SETITEMPOSITION32:
8483 if (lParam == 0) return FALSE;
8484 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
8486 case LVM_SETITEMSTATE:
8487 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
8489 case LVM_SETITEMTEXTA:
8490 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
8492 case LVM_SETITEMTEXTW:
8493 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
8495 /* case LVN_SETOUTLINECOLOR: */
8497 /* case LVN_SETSELECTEDCOLUMN: */
8499 case LVM_SETSELECTIONMARK:
8500 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
8502 case LVM_SETTEXTBKCOLOR:
8503 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
8505 case LVM_SETTEXTCOLOR:
8506 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
8508 /* case LVN_SETTILEINFO: */
8510 /* case LVN_SETTILEVIEWINFO: */
8512 /* case LVN_SETTILEWIDTH: */
8514 /* case LVM_SETTOOLTIPS: */
8516 /* case LVM_SETUNICODEFORMAT: */
8518 /* case LVN_SETVIEW: */
8520 /* case LVM_SETWORKAREAS: */
8522 /* case LVN_SORTGROUPS: */
8525 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
8527 case LVM_SUBITEMHITTEST:
8528 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
8531 return LISTVIEW_Update(infoPtr, (INT)wParam);
8534 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
8537 return LISTVIEW_Command(infoPtr, wParam, lParam);
8540 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
8543 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
8546 return DLGC_WANTCHARS | DLGC_WANTARROWS;
8549 return infoPtr->hFont;
8552 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
8555 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
8558 return LISTVIEW_KillFocus(infoPtr);
8560 case WM_LBUTTONDBLCLK:
8561 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8563 case WM_LBUTTONDOWN:
8564 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8567 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8570 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8573 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8576 return LISTVIEW_NCDestroy(infoPtr);
8579 return LISTVIEW_Notify(infoPtr, (INT)wParam, (LPNMHDR)lParam);
8581 case WM_NOTIFYFORMAT:
8582 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
8585 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
8587 case WM_RBUTTONDBLCLK:
8588 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8590 case WM_RBUTTONDOWN:
8591 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8594 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8597 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
8602 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
8605 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
8608 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
8611 return LISTVIEW_Size(infoPtr, (int)SLOWORD(lParam), (int)SHIWORD(lParam));
8613 case WM_STYLECHANGED:
8614 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
8616 case WM_SYSCOLORCHANGE:
8617 COMCTL32_RefreshSysColors();
8620 /* case WM_TIMER: */
8623 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
8626 if (wParam & (MK_SHIFT | MK_CONTROL))
8627 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8628 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
8630 case WM_WINDOWPOSCHANGED:
8631 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE)) {
8632 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
8633 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
8634 LISTVIEW_UpdateSize(infoPtr);
8635 LISTVIEW_UpdateScroll(infoPtr);
8637 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8639 /* case WM_WININICHANGE: */
8642 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
8643 ERR("unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam);
8646 /* call default window procedure */
8647 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8655 * Registers the window class.
8663 void LISTVIEW_Register(void)
8667 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
8668 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
8669 wndClass.lpfnWndProc = (WNDPROC)LISTVIEW_WindowProc;
8670 wndClass.cbClsExtra = 0;
8671 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
8672 wndClass.hCursor = LoadCursorW(0, IDC_ARROWW);
8673 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
8674 wndClass.lpszClassName = WC_LISTVIEWW;
8675 RegisterClassW(&wndClass);
8680 * Unregisters the window class.
8688 void LISTVIEW_Unregister(void)
8690 UnregisterClassW(WC_LISTVIEWW, (HINSTANCE)NULL);
8695 * Handle any WM_COMMAND messages
8698 * [I] infoPtr : valid pointer to the listview structure
8699 * [I] wParam : the first message parameter
8700 * [I] lParam : the second message parameter
8704 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
8706 switch (HIWORD(wParam))
8711 * Adjust the edit window size
8714 HDC hdc = GetDC(infoPtr->hwndEdit);
8715 HFONT hFont, hOldFont = 0;
8720 if (!infoPtr->hwndEdit || !hdc) return 0;
8721 len = GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
8722 GetWindowRect(infoPtr->hwndEdit, &rect);
8724 /* Select font to get the right dimension of the string */
8725 hFont = SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
8728 hOldFont = SelectObject(hdc, hFont);
8731 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
8733 TEXTMETRICW textMetric;
8735 /* Add Extra spacing for the next character */
8736 GetTextMetricsW(hdc, &textMetric);
8737 sz.cx += (textMetric.tmMaxCharWidth * 2);
8745 rect.bottom - rect.top,
8746 SWP_DRAWFRAME|SWP_NOMOVE);
8749 SelectObject(hdc, hOldFont);
8751 ReleaseDC(infoPtr->hwndSelf, hdc);
8757 return SendMessageW (GetParent (infoPtr->hwndSelf), WM_COMMAND, wParam, lParam);
8766 * Subclassed edit control windproc function
8769 * [I] hwnd : the edit window handle
8770 * [I] uMsg : the message that is to be processed
8771 * [I] wParam : first message parameter
8772 * [I] lParam : second message parameter
8773 * [I] isW : TRUE if input is Unicode
8777 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
8779 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(GetParent(hwnd), 0);
8780 BOOL cancel = FALSE;
8782 TRACE("(hwnd=%x, uMsg=%x, wParam=%x, lParam=%lx, isW=%d)\n",
8783 hwnd, uMsg, wParam, lParam, isW);
8788 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
8795 WNDPROC editProc = infoPtr->EditWndProc;
8796 infoPtr->EditWndProc = 0;
8797 SetWindowLongW(hwnd, GWL_WNDPROC, (LONG)editProc);
8798 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
8802 if (VK_ESCAPE == (INT)wParam)
8807 else if (VK_RETURN == (INT)wParam)
8811 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
8815 if (infoPtr->hwndEdit)
8817 LPWSTR buffer = NULL;
8819 infoPtr->hwndEdit = 0;
8822 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
8826 if ( (buffer = COMCTL32_Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
8828 if (isW) GetWindowTextW(hwnd, buffer, len+1);
8829 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
8833 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
8835 if (buffer) COMCTL32_Free(buffer);
8839 SendMessageW(hwnd, WM_CLOSE, 0, 0);
8845 * Subclassed edit control Unicode windproc function
8848 * [I] hwnd : the edit window handle
8849 * [I] uMsg : the message that is to be processed
8850 * [I] wParam : first message parameter
8851 * [I] lParam : second message parameter
8855 LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8857 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
8862 * Subclassed edit control ANSI windproc function
8865 * [I] hwnd : the edit window handle
8866 * [I] uMsg : the message that is to be processed
8867 * [I] wParam : first message parameter
8868 * [I] lParam : second message parameter
8872 LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8874 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
8879 * Creates a subclassed edit cotrol
8882 * [I] infoPtr : valid pointer to the listview structure
8883 * [I] text : initial text for the edit
8884 * [I] style : the window style
8885 * [I] isW : TRUE if input is Unicode
8889 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
8890 INT x, INT y, INT width, INT height, BOOL isW)
8892 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
8897 TEXTMETRICW textMetric;
8898 HINSTANCE hinst = GetWindowLongW(infoPtr->hwndSelf, GWL_HINSTANCE);
8900 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
8902 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|WS_BORDER;
8903 hdc = GetDC(infoPtr->hwndSelf);
8905 /* Select the font to get appropriate metric dimensions */
8906 if(infoPtr->hFont != 0)
8907 hOldFont = SelectObject(hdc, infoPtr->hFont);
8909 /*Get String Lenght in pixels */
8910 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
8912 /*Add Extra spacing for the next character */
8913 GetTextMetricsW(hdc, &textMetric);
8914 sz.cx += (textMetric.tmMaxCharWidth * 2);
8916 if(infoPtr->hFont != 0)
8917 SelectObject(hdc, hOldFont);
8919 ReleaseDC(infoPtr->hwndSelf, hdc);
8921 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
8923 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
8925 if (!hedit) return 0;
8927 infoPtr->EditWndProc = (WNDPROC)
8928 (isW ? SetWindowLongW(hedit, GWL_WNDPROC, (LONG)EditLblWndProcW) :
8929 SetWindowLongA(hedit, GWL_WNDPROC, (LONG)EditLblWndProcA) );
8931 SendMessageW(hedit, WM_SETFONT, infoPtr->hFont, FALSE);