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 BOOL 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 BOOL 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 BOOL 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 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return FALSE;
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)
936 if (LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem) && IntersectRect(&rcTemp, &rcItem, lprc))
937 i->nSpecial = infoPtr->nFocusedItem;
939 if (!(i->ranges = ranges_create(50))) return FALSE;
940 /* to do better here, we need to have PosX, and PosY sorted */
941 TRACE("building icon ranges:\n");
942 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
944 rcItem.left = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
945 rcItem.top = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
946 rcItem.right = rcItem.left + infoPtr->nItemWidth;
947 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
948 if (IntersectRect(&rcTemp, &rcItem, &frame))
949 ranges_additem(i->ranges, nItem);
951 if (TRACE_ON(listview))
953 TRACE(" icon items:\n");
954 ranges_dump(i->ranges);
958 else if (uView == LVS_REPORT)
962 if (frame.left >= infoPtr->nItemWidth) return TRUE;
963 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
965 lower = max(frame.top / infoPtr->nItemHeight, 0);
966 upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1);
967 if (upper < lower) return TRUE;
968 i->range.lower = lower;
969 i->range.upper = upper + 1;
970 TRACE(" report=%s\n", debugrange(&i->range));
974 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
975 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
976 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
977 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
978 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
979 INT lower = nFirstCol * nPerCol + nFirstRow;
983 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
984 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
986 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
988 if (!(i->ranges = ranges_create(nLastCol - nFirstCol + 1))) return FALSE;
989 TRACE("building list ranges:\n");
990 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
992 item_range.lower = nCol * nPerCol + nFirstRow;
993 if(item_range.lower >= infoPtr->nItemCount) break;
994 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
995 TRACE(" list=%s\n", debugrange(&item_range));
996 ranges_add(i->ranges, item_range);
1004 * Creates an iterator over the items which intersect the visible region of hdc.
1006 static BOOL iterator_visibleitems(ITERATOR* i, LISTVIEW_INFO *infoPtr, HDC hdc)
1008 POINT Origin, Position;
1009 RECT rcItem, rcClip;
1012 rgntype = GetClipBox(hdc, &rcClip);
1013 if (rgntype == NULLREGION) return iterator_empty(i);
1014 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1015 if (rgntype == SIMPLEREGION) return TRUE;
1017 /* first deal with the special item */
1018 if (LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem) && !RectVisible(hdc, &rcItem))
1021 /* if we can't deal with the region, we'll just go with the simple range */
1022 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return TRUE;
1023 TRACE("building visible range:\n");
1024 if (!i->ranges && i->range.lower < i->range.upper)
1026 if (!(i->ranges = ranges_create(50))) return TRUE;
1027 if (!ranges_add(i->ranges, i->range))
1029 ranges_destroy(i->ranges);
1035 /* now delete the invisible items from the list */
1036 while(iterator_next(i))
1038 if (!LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position)) continue;
1039 rcItem.left = Position.x + Origin.x;
1040 rcItem.top = Position.y + Origin.y;
1041 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1042 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1043 if (!RectVisible(hdc, &rcItem))
1044 ranges_delitem(i->ranges, i->nItem);
1046 /* the iterator should restart on the next iterator_next */
1052 /******** Misc helper functions ************************************/
1054 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1055 WPARAM wParam, LPARAM lParam, BOOL isW)
1057 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1058 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1061 /******** Internal API functions ************************************/
1063 /* The Invalidate* are macros, so we preserve the caller location */
1064 #define LISTVIEW_InvalidateRect(infoPtr, rect) while(infoPtr->bRedraw) { \
1065 TRACE(" invalidating rect=%s\n", debugrect(rect)); \
1066 InvalidateRect(infoPtr->hwndSelf, rect, TRUE); \
1070 #define LISTVIEW_InvalidateItem(infoPtr, nItem) do { \
1072 if(LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox)) \
1073 LISTVIEW_InvalidateRect(infoPtr, &rcBox); \
1076 #define LISTVIEW_InvalidateSubItem(infoPtr, nItem, nSubItem) do { \
1077 POINT Origin, Position; \
1079 if (LISTVIEW_GetOrigin(infoPtr, &Origin) && \
1080 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position) && \
1081 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox)) { \
1082 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y); \
1083 LISTVIEW_InvalidateRect(infoPtr, &rcBox); \
1087 #define LISTVIEW_InvalidateList(infoPtr)\
1088 LISTVIEW_InvalidateRect(infoPtr, NULL)
1091 static inline BOOL LISTVIEW_GetHeaderRect(LISTVIEW_INFO *infoPtr, INT nSubItem, RECT *lprc)
1093 COLUMN_INFO *columnInfo;
1095 columnInfo = (COLUMN_INFO *)DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1096 if (!columnInfo) return FALSE;
1097 *lprc = columnInfo->rcHeader;
1101 static inline BOOL LISTVIEW_GetItemW(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1103 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1108 * Retrieves the number of items that can fit vertically in the client area.
1111 * [I] infoPtr : valid pointer to the listview structure
1114 * Number of items per row.
1116 static inline INT LISTVIEW_GetCountPerRow(LISTVIEW_INFO *infoPtr)
1118 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1120 return max(nListWidth/infoPtr->nItemWidth, 1);
1125 * Retrieves the number of items that can fit horizontally in the client
1129 * [I] infoPtr : valid pointer to the listview structure
1132 * Number of items per column.
1134 static inline INT LISTVIEW_GetCountPerColumn(LISTVIEW_INFO *infoPtr)
1136 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1138 return max(nListHeight / infoPtr->nItemHeight, 1);
1142 /*************************************************************************
1143 * LISTVIEW_ProcessLetterKeys
1145 * Processes keyboard messages generated by pressing the letter keys
1147 * What this does is perform a case insensitive search from the
1148 * current position with the following quirks:
1149 * - If two chars or more are pressed in quick succession we search
1150 * for the corresponding string (e.g. 'abc').
1151 * - If there is a delay we wipe away the current search string and
1152 * restart with just that char.
1153 * - If the user keeps pressing the same character, whether slowly or
1154 * fast, so that the search string is entirely composed of this
1155 * character ('aaaaa' for instance), then we search for first item
1156 * that starting with that character.
1157 * - If the user types the above character in quick succession, then
1158 * we must also search for the corresponding string ('aaaaa'), and
1159 * go to that string if there is a match.
1162 * [I] hwnd : handle to the window
1163 * [I] charCode : the character code, the actual character
1164 * [I] keyData : key data
1172 * - The current implementation has a list of characters it will
1173 * accept and it ignores averything else. In particular it will
1174 * ignore accentuated characters which seems to match what
1175 * Windows does. But I'm not sure it makes sense to follow
1177 * - We don't sound a beep when the search fails.
1181 * TREEVIEW_ProcessLetterKeys
1183 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1188 WCHAR buffer[MAX_PATH];
1189 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1191 /* simple parameter checking */
1192 if (!charCode || !keyData) return 0;
1194 /* only allow the valid WM_CHARs through */
1195 if (!isalnum(charCode) &&
1196 charCode != '.' && charCode != '`' && charCode != '!' &&
1197 charCode != '@' && charCode != '#' && charCode != '$' &&
1198 charCode != '%' && charCode != '^' && charCode != '&' &&
1199 charCode != '*' && charCode != '(' && charCode != ')' &&
1200 charCode != '-' && charCode != '_' && charCode != '+' &&
1201 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1202 charCode != '}' && charCode != '[' && charCode != '{' &&
1203 charCode != '/' && charCode != '?' && charCode != '>' &&
1204 charCode != '<' && charCode != ',' && charCode != '~')
1207 /* if there's one item or less, there is no where to go */
1208 if (infoPtr->nItemCount <= 1) return 0;
1210 /* update the search parameters */
1211 infoPtr->lastKeyPressTimestamp = GetTickCount();
1212 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1213 if (infoPtr->nSearchParamLength < MAX_PATH)
1214 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1215 if (infoPtr->charCode != charCode)
1216 infoPtr->charCode = charCode = 0;
1218 infoPtr->charCode=charCode;
1219 infoPtr->szSearchParam[0]=charCode;
1220 infoPtr->nSearchParamLength=1;
1221 /* Redundant with the 1 char string */
1225 /* and search from the current position */
1227 if (infoPtr->nFocusedItem >= 0) {
1228 endidx=infoPtr->nFocusedItem;
1230 /* if looking for single character match,
1231 * then we must always move forward
1233 if (infoPtr->nSearchParamLength == 1)
1236 endidx=infoPtr->nItemCount;
1240 if (idx == infoPtr->nItemCount) {
1241 if (endidx == infoPtr->nItemCount || endidx == 0)
1247 item.mask = LVIF_TEXT;
1250 item.pszText = buffer;
1251 item.cchTextMax = MAX_PATH;
1252 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1254 /* check for a match */
1255 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1258 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1259 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1260 /* This would work but we must keep looking for a longer match */
1264 } while (idx != endidx);
1267 LISTVIEW_KeySelection(infoPtr, nItem);
1272 /*************************************************************************
1273 * LISTVIEW_UpdateHeaderSize [Internal]
1275 * Function to resize the header control
1278 * [I] hwnd : handle to a window
1279 * [I] nNewScrollPos : scroll pos to set
1284 static void LISTVIEW_UpdateHeaderSize(LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1289 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1291 GetWindowRect(infoPtr->hwndHeader, &winRect);
1292 point[0].x = winRect.left;
1293 point[0].y = winRect.top;
1294 point[1].x = winRect.right;
1295 point[1].y = winRect.bottom;
1297 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1298 point[0].x = -nNewScrollPos;
1299 point[1].x += nNewScrollPos;
1301 SetWindowPos(infoPtr->hwndHeader,0,
1302 point[0].x,point[0].y,point[1].x,point[1].y,
1303 SWP_NOZORDER | SWP_NOACTIVATE);
1308 * Update the scrollbars. This functions should be called whenever
1309 * the content, size or view changes.
1312 * [I] infoPtr : valid pointer to the listview structure
1317 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *infoPtr)
1319 LONG lStyle = infoPtr->dwStyle;
1320 UINT uView = lStyle & LVS_TYPEMASK;
1321 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1322 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1323 SCROLLINFO scrollInfo;
1325 if (lStyle & LVS_NOSCROLL) return;
1327 scrollInfo.cbSize = sizeof(SCROLLINFO);
1329 if (uView == LVS_LIST)
1331 /* update horizontal scrollbar */
1332 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1333 INT nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
1335 TRACE("items=%d, perColumn=%d, perRow=%d\n",
1336 infoPtr->nItemCount, nCountPerColumn, nCountPerRow);
1338 scrollInfo.nMin = 0;
1339 scrollInfo.nMax = infoPtr->nItemCount / nCountPerColumn;
1340 if((infoPtr->nItemCount % nCountPerColumn) == 0)
1342 if (scrollInfo.nMax < 0) scrollInfo.nMax = 0;
1343 scrollInfo.nPos = ListView_GetTopIndex(infoPtr->hwndSelf) / nCountPerColumn;
1344 scrollInfo.nPage = nCountPerRow;
1345 scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
1346 SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
1347 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
1349 else if (uView == LVS_REPORT)
1353 /* update vertical scrollbar */
1354 scrollInfo.nMin = 0;
1355 scrollInfo.nMax = infoPtr->nItemCount - 1;
1356 scrollInfo.nPos = ListView_GetTopIndex(infoPtr->hwndSelf);
1357 scrollInfo.nPage = LISTVIEW_GetCountPerColumn(infoPtr);
1358 scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
1359 test = (scrollInfo.nMin >= scrollInfo.nMax - max((INT)scrollInfo.nPage - 1, 0));
1360 TRACE("LVS_REPORT Vert. nMax=%d, nPage=%d, test=%d\n",
1361 scrollInfo.nMax, scrollInfo.nPage, test);
1362 SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
1363 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, (test) ? FALSE : TRUE);
1365 /* update horizontal scrollbar */
1366 nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1367 scrollInfo.fMask = SIF_POS;
1368 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)
1369 || infoPtr->nItemCount == 0)
1371 scrollInfo.nPos = 0;
1373 scrollInfo.nMin = 0;
1374 scrollInfo.nMax = max(infoPtr->nItemWidth, 0)-1;
1375 scrollInfo.nPage = nListWidth;
1376 scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE ;
1377 test = (scrollInfo.nMin >= scrollInfo.nMax - max((INT)scrollInfo.nPage - 1, 0));
1378 TRACE("LVS_REPORT Horz. nMax=%d, nPage=%d, test=%d\n",
1379 scrollInfo.nMax, scrollInfo.nPage, test);
1380 SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
1381 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, (test) ? FALSE : TRUE);
1383 /* Update the Header Control */
1384 scrollInfo.fMask = SIF_POS;
1385 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo);
1386 LISTVIEW_UpdateHeaderSize(infoPtr, scrollInfo.nPos);
1393 if (LISTVIEW_GetViewRect(infoPtr, &rcView))
1395 INT nViewWidth = rcView.right - rcView.left;
1396 INT nViewHeight = rcView.bottom - rcView.top;
1398 /* Update Horizontal Scrollbar */
1399 scrollInfo.fMask = SIF_POS;
1400 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)
1401 || infoPtr->nItemCount == 0)
1403 scrollInfo.nPos = 0;
1405 scrollInfo.nMin = 0;
1406 scrollInfo.nMax = max(nViewWidth, 0)-1;
1407 scrollInfo.nPage = nListWidth;
1408 scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
1409 TRACE("LVS_ICON/SMALLICON Horz.\n");
1410 SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
1412 /* Update Vertical Scrollbar */
1413 nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1414 scrollInfo.fMask = SIF_POS;
1415 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)
1416 || infoPtr->nItemCount == 0)
1418 scrollInfo.nPos = 0;
1420 scrollInfo.nMin = 0;
1421 scrollInfo.nMax = max(nViewHeight,0)-1;
1422 scrollInfo.nPage = nListHeight;
1423 scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
1424 TRACE("LVS_ICON/SMALLICON Vert.\n");
1425 SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
1433 * Shows/hides the focus rectangle.
1436 * [I] infoPtr : valid pointer to the listview structure
1437 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1442 static void LISTVIEW_ShowFocusRect(LISTVIEW_INFO *infoPtr, BOOL fShow)
1444 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1447 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1449 if (infoPtr->nFocusedItem < 0) return;
1451 /* we need some gymnastics in ICON mode to handle large items */
1452 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1456 if (!LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox))
1458 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1460 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1465 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1467 /* for some reason, owner draw should work only in report mode */
1468 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1473 item.iItem = infoPtr->nFocusedItem;
1475 item.mask = LVIF_PARAM;
1476 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1478 ZeroMemory(&dis, sizeof(dis));
1479 dis.CtlType = ODT_LISTVIEW;
1480 dis.CtlID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
1481 dis.itemID = item.iItem;
1482 dis.itemAction = ODA_FOCUS;
1483 if (fShow) dis.itemState |= ODS_FOCUS;
1484 dis.hwndItem = infoPtr->hwndSelf;
1486 if (!LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem)) goto done;
1487 dis.itemData = item.lParam;
1489 SendMessageW(GetParent(infoPtr->hwndSelf), WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1493 DrawFocusRect(hdc, &infoPtr->rcFocus);
1496 ReleaseDC(infoPtr->hwndSelf, hdc);
1500 * Invalidates all visible selected items.
1502 static void LISTVIEW_InvalidateSelectedItems(LISTVIEW_INFO *infoPtr)
1506 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1507 while(iterator_next(&i))
1509 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1510 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1512 iterator_destroy(&i);
1518 * Prints a message for unsupported window styles.
1519 * A kind of TODO list for window styles.
1522 * [I] lStyle : window style
1527 static void LISTVIEW_UnsupportedStyles(LONG lStyle)
1529 if ((LVS_TYPESTYLEMASK & lStyle) == LVS_NOSCROLL)
1530 FIXME(" LVS_NOSCROLL\n");
1532 if (lStyle & LVS_NOLABELWRAP)
1533 FIXME(" LVS_NOLABELWRAP\n");
1535 if (lStyle & LVS_SORTASCENDING)
1536 FIXME(" LVS_SORTASCENDING\n");
1538 if (lStyle & LVS_SORTDESCENDING)
1539 FIXME(" LVS_SORTDESCENDING\n");
1544 * DESCRIPTION: [INTERNAL]
1545 * Computes an item's (left,top) corner, relative to rcView.
1546 * That is, the position has NOT been made relative to the Origin.
1547 * This is deliberate, to avoid computing the Origin over, and
1548 * over again, when this function is call in a loop. Instead,
1549 * one ca factor the computation of the Origin before the loop,
1550 * and offset the value retured by this function, on every iteration.
1553 * [I] infoPtr : valid pointer to the listview structure
1554 * [I] nItem : item number
1555 * [O] lpptOrig : item top, left corner
1558 * TRUE if computations OK
1561 static BOOL LISTVIEW_GetItemOrigin(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1563 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1565 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
1567 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1569 lpptPosition->x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1570 lpptPosition->y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1572 else if (uView == LVS_LIST)
1574 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1575 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1576 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1578 else /* LVS_REPORT */
1580 lpptPosition->x = REPORT_MARGINX;
1581 lpptPosition->y = nItem * infoPtr->nItemHeight;
1588 * DESCRIPTION: [INTERNAL]
1589 * Compute the rectangles of an item. This is to localize all
1590 * the computations in one place. If you are not interested in some
1591 * of these values, simply pass in a NULL -- the fucntion is smart
1592 * enough to compute only what's necessary. The function computes
1593 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1594 * one, the BOX rectangle. This rectangle is very cheap to compute,
1595 * and is guaranteed to contain all the other rectangles. Computing
1596 * the ICON rect is also cheap, but all the others are potentaily
1597 * expensive. This gives an easy and effective optimization when
1598 * searching (like point inclusion, or rectangle intersection):
1599 * first test against the BOX, and if TRUE, test agains the desired
1601 * If the function does not have all the necessary information
1602 * to computed the requested rectangles, will crash with a
1603 * failed assertion. This is done so we catch all programming
1604 * errors, given that the function is called only from our code.
1606 * We have the following 'special' meanings for a few fields:
1607 * * If LVIS_FOCUSED is set, we assume the item has the focus
1608 * This is important in ICON mode, where it might get a larger
1609 * then usual rectange
1611 * Please note that subitem support works only in REPORT mode.
1614 * [I] infoPtr : valid pointer to the listview structure
1615 * [I] lpLVItem : item to compute the measures for
1616 * [O] lprcBox : ptr to Box rectangle
1617 * The internal LVIR_BOX rectangle
1618 * [0] lprcState : ptr to State icon rectangle
1619 * The internal LVIR_STATE rectangle
1620 * [O] lprcIcon : ptr to Icon rectangle
1621 * Same as LVM_GETITEMRECT with LVIR_ICON
1622 * [O] lprcLabel : ptr to Label rectangle
1623 * Same as LVM_GETITEMRECT with LVIR_LABEL
1626 * TRUE if computations OK
1629 static BOOL LISTVIEW_GetItemMetrics(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem,
1630 LPRECT lprcBox, LPRECT lprcState,
1631 LPRECT lprcIcon, LPRECT lprcLabel)
1633 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1634 BOOL doState = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1635 RECT Box, State, Icon, Label;
1636 COLUMN_INFO *lpColumnInfo = NULL;
1638 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1640 /* Be smart and try to figure out the minimum we have to do */
1641 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1642 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1644 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1645 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1647 if (lprcLabel) doLabel = TRUE;
1648 if (doLabel || lprcIcon) doIcon = TRUE;
1649 if (doIcon || lprcState) doState = TRUE;
1651 /************************************************************/
1652 /* compute the box rectangle (it should be cheap to do) */
1653 /************************************************************/
1654 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1656 lpColumnInfo = DPA_GetPtr(infoPtr->hdpaColumns, lpLVItem->iSubItem);
1657 assert(lpColumnInfo);
1660 if (lpLVItem->iSubItem)
1662 Box = lpColumnInfo->rcHeader;
1667 Box.right = infoPtr->nItemWidth;
1670 Box.bottom = infoPtr->nItemHeight;
1672 /************************************************************/
1673 /* compute STATEICON bounding box */
1674 /************************************************************/
1677 if (uView == LVS_ICON)
1679 State.left = Box.left - infoPtr->iconStateSize.cx - 2;
1680 if (infoPtr->himlNormal)
1681 State.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1682 State.top = Box.top + infoPtr->iconSize.cy - infoPtr->iconStateSize.cy + 4;
1686 /* we need the ident in report mode, if we don't have it, we fail */
1687 State.left = Box.left;
1688 if (uView == LVS_REPORT)
1690 State.left += REPORT_MARGINX;
1691 if (lpLVItem->iSubItem == 0)
1693 assert(lpLVItem->mask & LVIF_INDENT);
1694 State.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
1697 State.top = Box.top;
1699 State.right = State.left;
1700 State.bottom = State.top;
1701 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1703 State.right += infoPtr->iconStateSize.cx;
1704 State.bottom += infoPtr->iconStateSize.cy;
1706 if (lprcState) *lprcState = State;
1707 TRACE(" - state=%s\n", debugrect(&State));
1710 /************************************************************/
1711 /* compute ICON bounding box (ala LVM_GETITEMRECT) */
1712 /************************************************************/
1715 if (uView == LVS_ICON)
1717 Icon.left = Box.left;
1718 if (infoPtr->himlNormal)
1719 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1720 Icon.top = Box.top + ICON_TOP_PADDING;
1721 Icon.right = Icon.left;
1722 Icon.bottom = Icon.top;
1723 if (infoPtr->himlNormal)
1725 Icon.right += infoPtr->iconSize.cx;
1726 Icon.bottom += infoPtr->iconSize.cy;
1729 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1731 Icon.left = State.right;
1732 if (!IsRectEmpty(&State)) Icon.left += IMAGE_PADDING;
1734 Icon.right = Icon.left;
1735 if (infoPtr->himlSmall && (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE)))
1736 Icon.right += infoPtr->iconSize.cx;
1737 Icon.bottom = Icon.top + infoPtr->nItemHeight;
1739 if(lprcIcon) *lprcIcon = Icon;
1740 TRACE(" - icon=%s\n", debugrect(&Icon));
1743 /************************************************************/
1744 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
1745 /************************************************************/
1748 SIZE labelSize = { 0, 0 };
1750 /* calculate how far to the right can the label strech */
1751 Label.right = Box.right;
1752 if (uView == LVS_REPORT)
1754 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
1755 Label.right -= REPORT_MARGINX;
1758 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
1760 labelSize.cx = infoPtr->nItemWidth;
1761 labelSize.cy = infoPtr->nItemHeight;
1765 /* we need the text in non owner draw mode */
1766 assert(lpLVItem->mask & LVIF_TEXT);
1767 if (is_textT(lpLVItem->pszText, TRUE))
1769 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1770 HDC hdc = GetDC(infoPtr->hwndSelf);
1771 HFONT hOldFont = SelectObject(hdc, hFont);
1775 /* compute rough rectangle where the label will go */
1776 SetRectEmpty(&rcText);
1777 rcText.right = infoPtr->nItemWidth - TRAILING_PADDING;
1778 rcText.bottom = infoPtr->nItemHeight;
1779 if (uView == LVS_ICON)
1780 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
1782 /* now figure out the flags */
1783 if (uView == LVS_ICON)
1784 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
1786 uFormat = LV_SL_DT_FLAGS;
1788 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
1790 labelSize.cx = min(rcText.right - rcText.left + TRAILING_PADDING, infoPtr->nItemWidth);
1791 labelSize.cy = rcText.bottom - rcText.top;
1793 SelectObject(hdc, hOldFont);
1794 ReleaseDC(infoPtr->hwndSelf, hdc);
1798 if (uView == LVS_ICON)
1800 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
1801 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
1802 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
1803 Label.right = Label.left + labelSize.cx;
1804 Label.bottom = Label.top + infoPtr->nItemHeight;
1805 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
1807 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
1808 labelSize.cy /= infoPtr->ntmHeight;
1809 labelSize.cy = max(labelSize.cy, 1);
1810 labelSize.cy *= infoPtr->ntmHeight;
1812 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
1814 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1816 Label.left = Icon.right;
1817 if (!IsRectEmpty(&Icon) || !IsRectEmpty(&State)) Label.left += IMAGE_PADDING;
1818 Label.top = Box.top;
1819 Label.right = min(Label.left + labelSize.cx, Label.right);
1820 Label.bottom = Label.top + infoPtr->nItemHeight;
1823 if (lprcLabel) *lprcLabel = Label;
1824 TRACE(" - label=%s\n", debugrect(&Label));
1827 /* Fix the Box if necessary */
1830 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
1831 else *lprcBox = Box;
1833 TRACE(" - box=%s\n", debugrect(&Box));
1839 * DESCRIPTION: [INTERNAL]
1842 * [I] infoPtr : valid pointer to the listview structure
1843 * [I] nItem : item number
1844 * [O] lprcBox : ptr to Box rectangle
1847 * TRUE if computations OK
1850 static BOOL LISTVIEW_GetItemBox(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
1852 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1853 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
1854 POINT Position, Origin;
1857 if (!LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position)) return FALSE;
1858 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return FALSE;
1860 /* Be smart and try to figure out the minimum we have to do */
1862 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
1863 lvItem.mask |= LVIF_TEXT;
1864 lvItem.iItem = nItem;
1865 lvItem.iSubItem = 0;
1866 lvItem.pszText = szDispText;
1867 lvItem.cchTextMax = DISP_TEXT_SIZE;
1868 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
1869 if (uView == LVS_ICON)
1871 lvItem.mask |= LVIF_STATE;
1872 lvItem.stateMask = LVIS_FOCUSED;
1873 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
1875 if (!LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0)) return FALSE;
1877 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
1884 * Aligns the items with the top edge of the window.
1887 * [I] infoPtr : valid pointer to the listview structure
1892 static void LISTVIEW_AlignTop(LISTVIEW_INFO *infoPtr)
1894 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1895 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1898 INT i, off_x=0, off_y=0;
1900 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1902 /* Since SetItemPosition uses upper-left of icon, and for
1903 style=LVS_ICON the icon is not left adjusted, get the offset */
1904 if (uView == LVS_ICON)
1906 off_y = ICON_TOP_PADDING;
1907 off_x = (infoPtr->iconSpacing.cx - infoPtr->iconSize.cx) / 2;
1911 ZeroMemory(&rcView, sizeof(RECT));
1912 TRACE("Icon off.x=%d, off.y=%d, left=%d, right=%d\n",
1914 infoPtr->rcList.left, infoPtr->rcList.right);
1916 if (nListWidth > infoPtr->nItemWidth)
1918 for (i = 0; i < infoPtr->nItemCount; i++)
1920 if ((ptItem.x-off_x) + infoPtr->nItemWidth > nListWidth)
1923 ptItem.y += infoPtr->nItemHeight;
1926 LISTVIEW_SetItemPosition(infoPtr, i, ptItem);
1927 ptItem.x += infoPtr->nItemWidth;
1928 rcView.right = max(rcView.right, ptItem.x);
1931 rcView.right -= off_x;
1932 rcView.bottom = (ptItem.y-off_y) + infoPtr->nItemHeight;
1936 for (i = 0; i < infoPtr->nItemCount; i++)
1938 LISTVIEW_SetItemPosition(infoPtr, i, ptItem);
1939 ptItem.y += infoPtr->nItemHeight;
1942 rcView.right = infoPtr->nItemWidth;
1943 rcView.bottom = ptItem.y-off_y;
1946 infoPtr->rcView = rcView;
1952 * Aligns the items with the left edge of the window.
1955 * [I] infoPtr : valid pointer to the listview structure
1960 static void LISTVIEW_AlignLeft(LISTVIEW_INFO *infoPtr)
1962 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1963 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1966 INT i, off_x=0, off_y=0;
1968 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1970 /* Since SetItemPosition uses upper-left of icon, and for
1971 style=LVS_ICON the icon is not left adjusted, get the offset */
1972 if (uView == LVS_ICON)
1974 off_y = ICON_TOP_PADDING;
1975 off_x = (infoPtr->iconSpacing.cx - infoPtr->iconSize.cx) / 2;
1979 ZeroMemory(&rcView, sizeof(RECT));
1980 TRACE("Icon off.x=%d, off.y=%d\n", off_x, off_y);
1982 if (nListHeight > infoPtr->nItemHeight)
1984 for (i = 0; i < infoPtr->nItemCount; i++)
1986 if (ptItem.y + infoPtr->nItemHeight > nListHeight)
1989 ptItem.x += infoPtr->nItemWidth;
1992 LISTVIEW_SetItemPosition(infoPtr, i, ptItem);
1993 ptItem.y += infoPtr->nItemHeight;
1994 rcView.bottom = max(rcView.bottom, ptItem.y);
1997 rcView.right = ptItem.x + infoPtr->nItemWidth;
2001 for (i = 0; i < infoPtr->nItemCount; i++)
2003 LISTVIEW_SetItemPosition(infoPtr, i, ptItem);
2004 ptItem.x += infoPtr->nItemWidth;
2007 rcView.bottom = infoPtr->nItemHeight;
2008 rcView.right = ptItem.x;
2011 infoPtr->rcView = rcView;
2018 * Retrieves the bounding rectangle of all the items.
2021 * [I] infoPtr : valid pointer to the listview structure
2022 * [O] lprcView : bounding rectangle
2028 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2032 TRACE("(lprcView=%p)\n", lprcView);
2034 if (!lprcView) return FALSE;
2036 if (!LISTVIEW_GetOrigin(infoPtr, &ptOrigin)) return FALSE;
2038 *lprcView = infoPtr->rcView;
2039 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2041 TRACE("lprcView=%s\n", debugrect(lprcView));
2048 * Retrieves the subitem pointer associated with the subitem index.
2051 * [I] hdpaSubItems : DPA handle for a specific item
2052 * [I] nSubItem : index of subitem
2055 * SUCCESS : subitem pointer
2058 static LISTVIEW_SUBITEM* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2060 LISTVIEW_SUBITEM *lpSubItem;
2063 /* we should binary search here if need be */
2064 for (i = 1; i < hdpaSubItems->nItemCount; i++)
2066 lpSubItem = (LISTVIEW_SUBITEM *) DPA_GetPtr(hdpaSubItems, i);
2067 if (lpSubItem && (lpSubItem->iSubItem == nSubItem))
2077 * Calculates the width of a specific item.
2080 * [I] infoPtr : valid pointer to the listview structure
2081 * [I] nItem : item to calculate width, or -1 for max of all
2084 * Returns the width of an item width an item.
2086 static INT LISTVIEW_CalculateItemWidth(LISTVIEW_INFO *infoPtr, INT nItem)
2088 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2089 INT nItemWidth = 0, i;
2091 if (uView == LVS_ICON)
2092 nItemWidth = infoPtr->iconSpacing.cx;
2093 else if (uView == LVS_REPORT)
2097 /* calculate width of header */
2098 for (i = 0; i < infoPtr->hdpaColumns->nItemCount; i++)
2099 if (LISTVIEW_GetHeaderRect(infoPtr, i, &rcHeaderItem))
2100 nItemWidth += (rcHeaderItem.right - rcHeaderItem.left);
2106 if (infoPtr->nItemCount == 0) return DEFAULT_COLUMN_WIDTH;
2108 /* get width of string */
2111 for (i = 0; i < infoPtr->nItemCount; i++)
2113 nLabelWidth = LISTVIEW_GetLabelWidth(infoPtr, i);
2114 nItemWidth = max(nItemWidth, nLabelWidth);
2118 nItemWidth = LISTVIEW_GetLabelWidth(infoPtr, nItem);
2119 if (!nItemWidth) return DEFAULT_COLUMN_WIDTH;
2120 nItemWidth += WIDTH_PADDING;
2121 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2122 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2123 if (nItem == -1) nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth);
2126 return max(nItemWidth, 1);
2131 * Calculates the max width of any item in the list.
2134 * [I] infoPtr : valid pointer to the listview structure
2137 * Returns item width.
2139 static inline INT LISTVIEW_CalculateMaxWidth(LISTVIEW_INFO *infoPtr)
2141 return LISTVIEW_CalculateItemWidth(infoPtr, -1);
2146 * Retrieves and saves important text metrics info for the current
2150 * [I] infoPtr : valid pointer to the listview structure
2153 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2155 HDC hdc = GetDC(infoPtr->hwndSelf);
2156 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2157 HFONT hOldFont = SelectObject(hdc, hFont);
2160 if (GetTextMetricsW(hdc, &tm))
2161 infoPtr->ntmHeight = tm.tmHeight;
2162 SelectObject(hdc, hOldFont);
2163 ReleaseDC(infoPtr->hwndSelf, hdc);
2165 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2171 * Calculates the height of an item.
2174 * [I] infoPtr : valid pointer to the listview structure
2177 * Returns item height.
2179 static INT LISTVIEW_CalculateMaxHeight(LISTVIEW_INFO *infoPtr)
2183 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
2184 nItemHeight = infoPtr->iconSpacing.cy;
2187 nItemHeight = infoPtr->ntmHeight;
2188 if (infoPtr->himlState)
2189 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2190 if (infoPtr->himlSmall)
2191 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2192 if (infoPtr->himlState || infoPtr->himlSmall)
2193 nItemHeight += HEIGHT_PADDING;
2200 * A compare function for ranges
2203 * [I] range1 : pointer to range 1;
2204 * [I] range2 : pointer to range 2;
2208 * > 0 : if range 1 > range 2
2209 * < 0 : if range 2 > range 1
2210 * = 0 : if range intersects range 2
2212 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2216 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2218 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2223 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2229 #define ranges_check ranges_assert
2231 #define ranges_check(ranges, desc) do { } while(0)
2234 static void ranges_assert(RANGES ranges, LPCSTR desc)
2240 assert (ranges->hdpa->nItemCount >= 0);
2241 if (ranges->hdpa->nItemCount == 0) return;
2242 TRACE("*** Checking %s ***\n", desc);
2243 ranges_dump(ranges);
2244 assert ((prev = (RANGE *)DPA_GetPtr(ranges->hdpa, 0))->lower >= 0);
2245 /* assert (((RANGE *)DPA_GetPtr(ranges->hdpa, ranges->hdpa->nItemCount - 1))->upper <= nUpper); */
2246 for (i = 1; i < ranges->hdpa->nItemCount; i++)
2248 curr = (RANGE *)DPA_GetPtr(ranges->hdpa, i);
2249 assert (prev->upper <= curr->lower);
2252 TRACE("--- Done checking---\n");
2255 static RANGES ranges_create(int count)
2257 RANGES ranges = (RANGES)COMCTL32_Alloc(sizeof(struct tagRANGES));
2258 if (!ranges) return NULL;
2259 ranges->hdpa = DPA_Create(count);
2260 if (ranges->hdpa) return ranges;
2261 COMCTL32_Free(ranges);
2265 static void ranges_destroy(RANGES ranges)
2267 if (!ranges) return;
2272 for(i = 0; i < ranges->hdpa->nItemCount; i++)
2273 COMCTL32_Free(DPA_GetPtr(ranges->hdpa, i));
2274 DPA_Destroy(ranges->hdpa);
2275 ranges->hdpa = NULL;
2277 COMCTL32_Free(ranges);
2280 static RANGES ranges_clone(RANGES ranges)
2285 if (!ranges) return NULL;
2286 clone = ranges_create(ranges->hdpa->nItemCount);
2287 if (!clone) return NULL;
2289 for (i = 0; i < ranges->hdpa->nItemCount; i++)
2291 RANGE *newrng = (RANGE *)COMCTL32_Alloc(sizeof(RANGE));
2294 ranges_destroy(clone);
2297 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2298 DPA_InsertPtr(clone->hdpa, i, newrng);
2303 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2307 if (!ranges || !sub) return ranges;
2309 for (i = 0; i < sub->hdpa->nItemCount; i++)
2310 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2315 static void ranges_dump(RANGES ranges)
2319 if (!ranges) return;
2320 for (i = 0; i < ranges->hdpa->nItemCount; i++)
2321 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2324 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2326 RANGE srchrng = { nItem, nItem + 1 };
2328 TRACE("(nItem=%d)\n", nItem);
2329 if (!ranges) return FALSE;
2330 if (TRACE_ON(listview)) ranges_dump(ranges);
2331 ranges_check(ranges, "before contain");
2332 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2335 static INT ranges_itemcount(RANGES ranges)
2339 if (!ranges) return 0;
2340 for (i = 0; i < ranges->hdpa->nItemCount; i++)
2342 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2343 count += sel->upper - sel->lower;
2349 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2351 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2354 if (!ranges) return FALSE;
2355 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2356 if (index == -1) return TRUE;
2358 for (;index < ranges->hdpa->nItemCount; index++)
2360 chkrng = DPA_GetPtr(ranges->hdpa, index);
2361 if (chkrng->lower >= nItem)
2362 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2363 if (chkrng->upper > nItem)
2364 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2369 static BOOL ranges_add(RANGES ranges, RANGE range)
2374 TRACE("(%s)\n", debugrange(&range));
2375 ranges_check(ranges, "before add");
2376 if (!ranges) goto fail;
2378 /* try find overlapping regions first */
2379 srchrgn.lower = range.lower - 1;
2380 srchrgn.upper = range.upper + 1;
2381 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2387 TRACE("Adding new range\n");
2389 /* create the brand new range to insert */
2390 newrgn = (RANGE *)COMCTL32_Alloc(sizeof(RANGE));
2391 if(!newrgn) goto fail;
2394 /* figure out where to insert it */
2395 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2396 TRACE("index=%d\n", index);
2397 if (index == -1) index = 0;
2399 /* and get it over with */
2400 DPA_InsertPtr(ranges->hdpa, index, newrgn);
2404 RANGE *chkrgn, *mrgrgn;
2405 INT fromindex, mergeindex;
2407 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2408 if (!chkrgn) goto fail;
2409 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2411 chkrgn->lower = min(range.lower, chkrgn->lower);
2412 chkrgn->upper = max(range.upper, chkrgn->upper);
2414 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2416 /* merge now common anges */
2418 srchrgn.lower = chkrgn->lower - 1;
2419 srchrgn.upper = chkrgn->upper + 1;
2423 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2424 if (mergeindex == -1) break;
2425 if (mergeindex == index)
2427 fromindex = index + 1;
2431 TRACE("Merge with index %i\n", mergeindex);
2433 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2434 if (!mrgrgn) goto fail;
2436 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2437 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2438 COMCTL32_Free(mrgrgn);
2439 DPA_DeletePtr(ranges->hdpa, mergeindex);
2440 if (mergeindex < index) index --;
2444 ranges_check(ranges, "after add");
2445 if (TRACE_ON(listview)) ranges_dump(ranges);
2449 ranges_check(ranges, "failed add");
2453 static BOOL ranges_del(RANGES ranges, RANGE range)
2458 TRACE("(%s)\n", debugrange(&range));
2459 ranges_check(ranges, "before del");
2460 if (!ranges) goto fail;
2462 /* we don't use DPAS_SORTED here, since we need *
2463 * to find the first overlapping range */
2464 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2467 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2468 if (!chkrgn) goto fail;
2470 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2472 /* case 1: Same range */
2473 if ( (chkrgn->upper == range.upper) &&
2474 (chkrgn->lower == range.lower) )
2476 DPA_DeletePtr(ranges->hdpa, index);
2479 /* case 2: engulf */
2480 else if ( (chkrgn->upper <= range.upper) &&
2481 (chkrgn->lower >= range.lower) )
2483 DPA_DeletePtr(ranges->hdpa, index);
2485 /* case 3: overlap upper */
2486 else if ( (chkrgn->upper <= range.upper) &&
2487 (chkrgn->lower < range.lower) )
2489 chkrgn->upper = range.lower;
2491 /* case 4: overlap lower */
2492 else if ( (chkrgn->upper > range.upper) &&
2493 (chkrgn->lower >= range.lower) )
2495 chkrgn->lower = range.upper;
2498 /* case 5: fully internal */
2501 RANGE tmprgn = *chkrgn, *newrgn;
2503 if (!(newrgn = (RANGE *)COMCTL32_Alloc(sizeof(RANGE)))) goto fail;
2504 newrgn->lower = chkrgn->lower;
2505 newrgn->upper = range.lower;
2506 chkrgn->lower = range.upper;
2507 DPA_InsertPtr(ranges->hdpa, index, newrgn);
2512 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2515 ranges_check(ranges, "after del");
2519 ranges_check(ranges, "failed del");
2525 * Removes all selection ranges
2528 * [I] infoPtr : valid pointer to the listview structure
2529 * [I] toSkip : item range to skip removing the selection
2535 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2541 if (TRACE_ON(listview)) ranges_dump(toSkip);
2544 lvItem.stateMask = LVIS_SELECTED;
2546 /* need to clone the DPA because callbacks can change it */
2547 iterator_ranges(&i, ranges_diff(ranges_clone(infoPtr->selectionRanges), toSkip));
2548 while(iterator_next(&i))
2549 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2550 /* note that the iterator destructor will free the cloned range */
2551 iterator_destroy(&i);
2556 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2558 RANGES toSkip = ranges_create(1);
2560 if (!toSkip) return FALSE;
2561 if (nItem != -1) ranges_additem(toSkip, nItem);
2562 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2563 ranges_destroy(toSkip);
2567 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2569 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2574 * Retrieves the number of items that are marked as selected.
2577 * [I] infoPtr : valid pointer to the listview structure
2580 * Number of items selected.
2582 static INT LISTVIEW_GetSelectedCount(LISTVIEW_INFO *infoPtr)
2584 INT nSelectedCount = 0;
2586 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2589 for (i = 0; i < infoPtr->nItemCount; i++)
2591 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2596 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2598 TRACE("nSelectedCount=%d\n", nSelectedCount);
2599 return nSelectedCount;
2604 * Manages the item focus.
2607 * [I] infoPtr : valid pointer to the listview structure
2608 * [I] nItem : item index
2611 * TRUE : focused item changed
2612 * FALSE : focused item has NOT changed
2614 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2616 INT oldFocus = infoPtr->nFocusedItem;
2619 if (nItem == infoPtr->nFocusedItem) return FALSE;
2621 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2622 lvItem.stateMask = LVIS_FOCUSED;
2623 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
2625 return oldFocus != infoPtr->nFocusedItem;
2628 /* Helper function for LISTVIEW_ShiftIndices *only* */
2629 static INT shift_item(LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2631 if (nShiftItem < nItem) return nShiftItem;
2633 if (nShiftItem > nItem) return nShiftItem + direction;
2635 if (direction > 0) return nShiftItem + direction;
2637 return min(nShiftItem, infoPtr->nItemCount - 1);
2642 * Updates the various indices after an item has been inserted or deleted.
2645 * [I] infoPtr : valid pointer to the listview structure
2646 * [I] nItem : item index
2647 * [I] direction : Direction of shift, +1 or -1.
2652 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
2656 TRACE("Shifting %iu, %i steps\n", nItem, direction);
2658 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
2660 assert(abs(direction) == 1);
2662 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
2664 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
2665 if (nNewFocus != infoPtr->nFocusedItem)
2666 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
2668 /* But we are not supposed to modify nHotItem! */
2674 * Adds a block of selections.
2677 * [I] infoPtr : valid pointer to the listview structure
2678 * [I] nItem : item index
2683 static void LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2685 INT nFirst = min(infoPtr->nSelectionMark, nItem);
2686 INT nLast = max(infoPtr->nSelectionMark, nItem);
2693 item.state = LVIS_SELECTED;
2694 item.stateMask = LVIS_SELECTED;
2696 /* FIXME: this is not correct LVS_OWNERDATA
2697 * See docu for LVN_ITEMCHANGED. Is there something similar for
2698 * RemoveGroupSelection (is there such a thing?)?
2700 for (i = nFirst; i <= nLast; i++)
2701 LISTVIEW_SetItemState(infoPtr,i,&item);
2703 LISTVIEW_SetItemFocus(infoPtr, nItem);
2704 infoPtr->nSelectionMark = nItem;
2710 * Sets a single group selection.
2713 * [I] infoPtr : valid pointer to the listview structure
2714 * [I] nItem : item index
2719 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2721 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2726 if (!(selection = ranges_create(100))) return;
2728 item.state = LVIS_SELECTED;
2729 item.stateMask = LVIS_SELECTED;
2731 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
2733 if (infoPtr->nSelectionMark == -1)
2735 infoPtr->nSelectionMark = nItem;
2736 ranges_additem(selection, nItem);
2742 sel.lower = min(infoPtr->nSelectionMark, nItem);
2743 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
2744 ranges_add(selection, sel);
2749 RECT rcItem, rcSel, rcSelMark;
2752 rcItem.left = LVIR_BOUNDS;
2753 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
2754 rcSelMark.left = LVIR_BOUNDS;
2755 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
2756 UnionRect(&rcSel, &rcItem, &rcSelMark);
2757 iterator_frameditems(&i, infoPtr, &rcSel);
2758 while(iterator_next(&i))
2760 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
2761 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
2763 iterator_destroy(&i);
2766 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
2767 iterator_ranges(&i, selection);
2768 while(iterator_next(&i))
2769 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
2770 /* this will also destroy the selection */
2771 iterator_destroy(&i);
2773 LISTVIEW_SetItemFocus(infoPtr, nItem);
2778 * Sets a single selection.
2781 * [I] infoPtr : valid pointer to the listview structure
2782 * [I] nItem : item index
2787 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2791 TRACE("nItem=%d\n", nItem);
2793 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
2795 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
2796 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
2797 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
2799 infoPtr->nSelectionMark = nItem;
2804 * Set selection(s) with keyboard.
2807 * [I] infoPtr : valid pointer to the listview structure
2808 * [I] INT : item index
2811 * SUCCESS : TRUE (needs to be repainted)
2812 * FAILURE : FALSE (nothing has changed)
2814 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
2816 /* FIXME: pass in the state */
2817 LONG lStyle = infoPtr->dwStyle;
2818 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
2819 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
2820 BOOL bResult = FALSE;
2822 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
2824 if (lStyle & LVS_SINGLESEL)
2827 LISTVIEW_SetSelection(infoPtr, nItem);
2834 LISTVIEW_SetGroupSelection(infoPtr, nItem);
2838 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
2843 LISTVIEW_SetSelection(infoPtr, nItem);
2846 ListView_EnsureVisible(infoPtr->hwndSelf, nItem, FALSE);
2849 UpdateWindow(infoPtr->hwndSelf); /* update client area */
2856 * Called when the mouse is being actively tracked and has hovered for a specified
2860 * [I] infoPtr : valid pointer to the listview structure
2861 * [I] fwKeys : key indicator
2862 * [I] pts : mouse position
2865 * 0 if the message was processed, non-zero if there was an error
2868 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
2869 * over the item for a certain period of time.
2872 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKyes, POINTS pts)
2874 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
2875 /* FIXME: select the item!!! */
2876 /*LISTVIEW_GetItemAtPt(infoPtr, pt)*/;
2883 * Called whenever WM_MOUSEMOVE is received.
2886 * [I] infoPtr : valid pointer to the listview structure
2887 * [I] fwKeys : key indicator
2888 * [I] pts : mouse position
2891 * 0 if the message is processed, non-zero if there was an error
2893 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, POINTS pts)
2895 TRACKMOUSEEVENT trackinfo;
2897 /* see if we are supposed to be tracking mouse hovering */
2898 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
2899 /* fill in the trackinfo struct */
2900 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
2901 trackinfo.dwFlags = TME_QUERY;
2902 trackinfo.hwndTrack = infoPtr->hwndSelf;
2903 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
2905 /* see if we are already tracking this hwnd */
2906 _TrackMouseEvent(&trackinfo);
2908 if(!(trackinfo.dwFlags & TME_HOVER)) {
2909 trackinfo.dwFlags = TME_HOVER;
2911 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
2912 _TrackMouseEvent(&trackinfo);
2921 * Tests wheather the item is assignable to a list with style lStyle
2923 static inline BOOL is_assignable_item(LPLVITEMW lpLVItem, LONG lStyle)
2925 if ( (lpLVItem->mask & LVIF_TEXT) &&
2926 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
2927 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
2935 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
2938 * [I] infoPtr : valid pointer to the listview structure
2939 * [I] lpLVItem : valid pointer to new item atttributes
2940 * [I] isNew : the item being set is being inserted
2941 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
2942 * [O] bChanged : will be set to TRUE if the item really changed
2948 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
2950 LISTVIEW_ITEM *lpItem;
2957 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
2959 if (lpLVItem->mask == 0) return TRUE;
2961 if (infoPtr->dwStyle & LVS_OWNERDATA)
2963 /* a virtual listview we stores only selection and focus */
2964 if ((lpLVItem->mask & ~LVIF_STATE) || (lpLVItem->stateMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
2970 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
2971 if (!hdpaSubItems) return FALSE;
2972 if (!(lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0))) return FALSE;
2975 /* we need to get the lParam and state of the item */
2976 item.iItem = lpLVItem->iItem;
2977 item.iSubItem = lpLVItem->iSubItem;
2978 item.mask = LVIF_STATE | LVIF_PARAM;
2979 item.stateMask = ~0;
2982 if (!isNew && !LISTVIEW_GetItemT(infoPtr, &item, TRUE)) return FALSE;
2984 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
2985 /* determine what fields will change */
2986 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
2987 uChanged |= LVIF_STATE;
2989 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
2990 uChanged |= LVIF_IMAGE;
2992 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
2993 uChanged |= LVIF_PARAM;
2995 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
2996 uChanged |= LVIF_INDENT;
2998 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
2999 uChanged |= LVIF_TEXT;
3001 TRACE("uChanged=0x%x\n", uChanged);
3002 if (!uChanged) return TRUE;
3005 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3006 nmlv.iItem = lpLVItem->iItem;
3007 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3008 nmlv.uOldState = item.state;
3009 nmlv.uChanged = uChanged;
3010 nmlv.lParam = item.lParam;
3012 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3013 /* and we are _NOT_ virtual (LVS_OWERNDATA) */
3014 if(lpItem && !isNew && notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3017 /* copy information */
3018 if (lpLVItem->mask & LVIF_TEXT)
3019 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3021 if (lpLVItem->mask & LVIF_IMAGE)
3022 lpItem->hdr.iImage = lpLVItem->iImage;
3024 if (lpLVItem->mask & LVIF_PARAM)
3025 lpItem->lParam = lpLVItem->lParam;
3027 if (lpLVItem->mask & LVIF_INDENT)
3028 lpItem->iIndent = lpLVItem->iIndent;
3030 if (uChanged & LVIF_STATE)
3032 if (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED))
3034 lpItem->state &= ~lpLVItem->stateMask;
3035 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3037 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3039 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3040 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3042 else if (lpLVItem->stateMask & LVIS_SELECTED)
3043 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3045 /* if we are asked to change focus, and we manage it, do it */
3046 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3048 if (lpLVItem->state & LVIS_FOCUSED)
3050 LISTVIEW_SetItemFocus(infoPtr, -1);
3051 infoPtr->nFocusedItem = lpLVItem->iItem;
3052 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, FALSE);
3054 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3055 infoPtr->nFocusedItem = -1;
3059 /* if we're inserting the item, we're done */
3060 if (isNew) return TRUE;
3062 /* send LVN_ITEMCHANGED notification */
3063 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3064 notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3071 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3074 * [I] infoPtr : valid pointer to the listview structure
3075 * [I] lpLVItem : valid pointer to new subitem atttributes
3076 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3077 * [O] bChanged : will be set to TRUE if the item really changed
3083 static BOOL set_sub_item(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW, BOOL *bChanged)
3086 LISTVIEW_SUBITEM *lpSubItem;
3088 /* we do not support subitems for virtual listviews */
3089 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3091 /* set subitem only if column is present */
3092 if (lpLVItem->iSubItem >= infoPtr->hdpaColumns->nItemCount) return FALSE;
3094 /* First do some sanity checks */
3095 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE)) return FALSE;
3096 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE))) return TRUE;
3098 /* get the subitem structure, and create it if not there */
3099 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3100 if (!hdpaSubItems) return FALSE;
3102 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3105 LISTVIEW_SUBITEM *tmpSubItem;
3108 lpSubItem = (LISTVIEW_SUBITEM *)COMCTL32_Alloc(sizeof(LISTVIEW_SUBITEM));
3109 if (!lpSubItem) return FALSE;
3110 /* we could binary search here, if need be...*/
3111 for (i = 1; i < hdpaSubItems->nItemCount; i++)
3113 tmpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, i);
3114 if (tmpSubItem && tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3116 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3118 COMCTL32_Free(lpSubItem);
3121 lpSubItem->iSubItem = lpLVItem->iSubItem;
3125 if (lpLVItem->mask & LVIF_IMAGE)
3126 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3128 lpSubItem->hdr.iImage = lpLVItem->iImage;
3132 if (lpLVItem->mask & LVIF_TEXT)
3133 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3135 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3144 * Sets item attributes.
3147 * [I] infoPtr : valid pointer to the listview structure
3148 * [I] lpLVItem : new item atttributes
3149 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3155 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
3157 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3158 LPWSTR pszText = NULL;
3159 BOOL bResult, bChanged = FALSE;
3161 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3163 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3166 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3167 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3169 pszText = lpLVItem->pszText;
3170 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3173 /* actually set the fields */
3174 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3176 if (lpLVItem->iSubItem)
3177 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3179 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3181 /* redraw item, if necessary */
3182 if (bChanged && !infoPtr->bIsDrawing)
3184 /* this little optimization eliminates some nasty flicker */
3185 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3186 (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) || lpLVItem->iSubItem) )
3187 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3189 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3194 textfreeT(lpLVItem->pszText, isW);
3195 lpLVItem->pszText = pszText;
3203 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3206 * [I] infoPtr : valid pointer to the listview structure
3211 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *infoPtr)
3213 LONG lStyle = infoPtr->dwStyle;
3214 UINT uView = lStyle & LVS_TYPEMASK;
3216 SCROLLINFO scrollInfo;
3218 scrollInfo.cbSize = sizeof(SCROLLINFO);
3219 scrollInfo.fMask = SIF_POS;
3221 if (uView == LVS_LIST)
3223 if ((lStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3224 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3226 else if (uView == LVS_REPORT)
3228 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3229 nItem = scrollInfo.nPos;
3233 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3234 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3237 TRACE("nItem=%d\n", nItem);
3245 * Erases the background of the given rectangle
3248 * [I] infoPtr : valid pointer to the listview structure
3249 * [I] hdc : device context handle
3250 * [I] lprcBox : clipping rectangle
3256 static inline BOOL LISTVIEW_FillBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT* lprcBox)
3258 if (!infoPtr->hBkBrush) return FALSE;
3260 TRACE("(hdc=%x, lprcBox=%s, hBkBrush=%x)\n", hdc, debugrect(lprcBox), infoPtr->hBkBrush);
3262 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3270 * [I] infoPtr : valid pointer to the listview structure
3271 * [I] hdc : device context handle
3272 * [I] nItem : item index
3273 * [I] nSubItem : subitem index
3274 * [I] pos : item position in client coordinates
3275 * [I] cdmode : custom draw mode
3281 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3283 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3284 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3285 DWORD cditemmode = CDRF_DODEFAULT;
3286 RECT* lprcFocus, rcSelect, rcBox, rcState, rcIcon, rcLabel;
3287 NMLVCUSTOMDRAW nmlvcd;
3291 TRACE("(hdc=%x, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, debugpoint(&pos));
3293 /* get information needed for drawing the item */
3294 lvItem.mask = LVIF_TEXT | LVIF_IMAGE;
3295 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3296 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3297 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3298 lvItem.iItem = nItem;
3299 lvItem.iSubItem = nSubItem;
3300 lvItem.cchTextMax = DISP_TEXT_SIZE;
3301 lvItem.pszText = szDispText;
3302 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3303 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3305 /* now check if we need to update the focus rectangle */
3306 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3308 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3309 if (!LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel)) return FALSE;
3310 OffsetRect(&rcBox, pos.x, pos.y);
3311 OffsetRect(&rcState, pos.x, pos.y);
3312 OffsetRect(&rcIcon, pos.x, pos.y);
3313 OffsetRect(&rcLabel, pos.x, pos.y);
3314 TRACE(" rcBox=%s, rcState=%s, rcIcon=%s. rcLabel=%s\n",
3315 debugrect(&rcBox), debugrect(&rcState), debugrect(&rcIcon), debugrect(&rcLabel));
3317 /* fill in the custom draw structure */
3318 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox);
3319 nmlvcd.nmcd.dwItemSpec = lvItem.iItem;
3320 nmlvcd.iSubItem = lvItem.iSubItem;
3321 if (lvItem.state & LVIS_SELECTED) nmlvcd.nmcd.uItemState |= CDIS_SELECTED;
3322 if (lvItem.state & LVIS_FOCUSED) nmlvcd.nmcd.uItemState |= CDIS_FOCUS;
3323 if (lvItem.iItem == infoPtr->nHotItem) nmlvcd.nmcd.uItemState |= CDIS_HOT;
3324 nmlvcd.nmcd.lItemlParam = lvItem.lParam;
3325 if ((lvItem.state & LVIS_SELECTED) &&
3326 (lvItem.iSubItem == 0 ||
3327 (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))))
3329 if (infoPtr->bFocus)
3331 nmlvcd.clrTextBk = comctl32_color.clrHighlight;
3332 nmlvcd.clrText = comctl32_color.clrHighlightText;
3334 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
3336 nmlvcd.clrTextBk = comctl32_color.clr3dFace;
3337 nmlvcd.clrText = comctl32_color.clrBtnText;
3341 if (cdmode & CDRF_NOTIFYITEMDRAW)
3342 cditemmode = notify_customdraw (infoPtr, CDDS_ITEMPREPAINT, &nmlvcd);
3343 if (cditemmode & CDRF_SKIPDEFAULT) goto postpaint;
3346 if (infoPtr->himlState && !IsRectEmpty(&rcState))
3348 UINT uStateImage = (lvItem.state & LVIS_STATEIMAGEMASK) >> 12;
3350 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc, rcState.left, rcState.top, ILD_NORMAL);
3354 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3355 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3356 ImageList_Draw(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3357 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3359 /* Don't bother painting item being edited */
3360 if (infoPtr->hwndEdit && lprcFocus && nSubItem == 0) goto postpaint;
3362 /* Set the text attributes */
3363 SetBkMode(hdc, nmlvcd.clrTextBk == CLR_NONE ? TRANSPARENT : OPAQUE);
3364 if (nmlvcd.clrTextBk != CLR_NONE)
3365 SetBkColor(hdc, nmlvcd.clrTextBk == CLR_DEFAULT ? infoPtr->clrTextBkDefault : nmlvcd.clrTextBk);
3366 SetTextColor(hdc, nmlvcd.clrText);
3368 /* draw the selection background, if we're drawing the main item */
3372 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3373 rcSelect.right = rcBox.right;
3375 if (lvItem.state & LVIS_SELECTED)
3376 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3377 if(lprcFocus) *lprcFocus = rcSelect;
3380 /* figure out the text drawing flags */
3381 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3382 if (uView == LVS_ICON)
3383 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3386 COLUMN_INFO *lpColumnInfo = DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
3388 switch (lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK)
3390 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3391 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3392 default: uFormat |= DT_LEFT;
3395 if (!(uFormat & (DT_RIGHT | DT_CENTER))) rcLabel.left += 2;
3396 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3399 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3400 notify_customdraw(infoPtr, CDDS_ITEMPOSTPAINT, &nmlvcd);
3406 * Draws listview items when in owner draw mode.
3409 * [I] infoPtr : valid pointer to the listview structure
3410 * [I] hdc : device context handle
3415 static void LISTVIEW_RefreshOwnerDraw(LISTVIEW_INFO *infoPtr, HDC hdc)
3417 UINT uID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
3418 HWND hwndParent = GetParent(infoPtr->hwndSelf);
3419 POINT Origin, Position;
3426 ZeroMemory(&dis, sizeof(dis));
3428 /* Get scroll info once before loop */
3429 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return;
3431 /* figure out what we need to draw */
3432 iterator_visibleitems(&i, infoPtr, hdc);
3434 /* send cache hint notification */
3435 if (infoPtr->dwStyle & LVS_OWNERDATA)
3437 RANGE range = iterator_range(&i);
3440 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
3441 nmlv.iFrom = range.lower;
3442 nmlv.iTo = range.upper - 1;
3443 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
3446 /* iterate through the invalidated rows */
3447 while(iterator_prev(&i))
3449 item.iItem = i.nItem;
3451 item.mask = LVIF_PARAM | LVIF_STATE;
3452 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3453 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3455 dis.CtlType = ODT_LISTVIEW;
3457 dis.itemID = item.iItem;
3458 dis.itemAction = ODA_DRAWENTIRE;
3460 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3461 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3462 dis.hwndItem = infoPtr->hwndSelf;
3464 if (!LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position)) continue;
3465 dis.rcItem.left = Position.x + Origin.x;
3466 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3467 dis.rcItem.top = Position.y + Origin.y;
3468 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3469 dis.itemData = item.lParam;
3471 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), debugrect(&dis.rcItem));
3472 SendMessageW(hwndParent, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3474 iterator_destroy(&i);
3479 * Draws listview items when in report display mode.
3482 * [I] infoPtr : valid pointer to the listview structure
3483 * [I] hdc : device context handle
3484 * [I] cdmode : custom draw mode
3489 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD cdmode)
3491 INT rgntype, nFirstCol, nLastCol, nCol;
3492 RECT rcClip, rcItem;
3493 POINT Origin, Position;
3498 /* figure out what to draw */
3499 rgntype = GetClipBox(hdc, &rcClip);
3500 if (rgntype == NULLREGION) return;
3502 /* Get scroll info once before loop */
3503 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return;
3505 /* narrow down the columns we need to paint */
3506 for(nFirstCol = 0; nFirstCol < infoPtr->hdpaColumns->nItemCount; nFirstCol++)
3507 if (LISTVIEW_GetHeaderRect(infoPtr, nFirstCol, &rcItem) &&
3508 rcItem.right + Origin.x >= rcClip.left) break;
3509 for(nLastCol = infoPtr->hdpaColumns->nItemCount - 1; nLastCol >= 0; nLastCol--)
3510 if (LISTVIEW_GetHeaderRect(infoPtr, nLastCol, &rcItem) &&
3511 rcItem.left + Origin.x < rcClip.right) break;
3513 /* figure out what we need to draw */
3514 iterator_visibleitems(&i, infoPtr, hdc);
3516 /* a last few bits before we start drawing */
3517 TRACE("Colums=(%di - %d)\n", nFirstCol, nLastCol);
3519 /* iterate through the invalidated rows */
3520 while(iterator_prev(&i))
3522 /* iterate through the invalidated columns */
3523 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
3525 if (!LISTVIEW_GetItemOrigin(infoPtr, i.nItem, &Position)) continue;
3526 Position.x += Origin.x;
3527 Position.y += Origin.y;
3529 if (rgntype == COMPLEXREGION && LISTVIEW_GetHeaderRect(infoPtr, nCol, &rcItem))
3532 rcItem.bottom = infoPtr->nItemHeight;
3533 OffsetRect(&rcItem, Position.x, Position.y);
3534 if (!RectVisible(hdc, &rcItem)) continue;
3537 LISTVIEW_DrawItem(infoPtr, hdc, i.nItem, nCol, Position, cdmode);
3540 iterator_destroy(&i);
3545 * Draws listview items when in list display mode.
3548 * [I] infoPtr : valid pointer to the listview structure
3549 * [I] hdc : device context handle
3550 * [I] cdmode : custom draw mode
3555 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD cdmode)
3557 POINT Origin, Position;
3560 /* Get scroll info once before loop */
3561 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return;
3563 /* figure out what we need to draw */
3564 iterator_visibleitems(&i, infoPtr, hdc);
3566 while(iterator_prev(&i))
3568 if (!LISTVIEW_GetItemOrigin(infoPtr, i.nItem, &Position)) continue;
3569 Position.x += Origin.x;
3570 Position.y += Origin.y;
3572 LISTVIEW_DrawItem(infoPtr, hdc, i.nItem, 0, Position, cdmode);
3574 iterator_destroy(&i);
3580 * Draws listview items.
3583 * [I] infoPtr : valid pointer to the listview structure
3584 * [I] hdc : device context handle
3589 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc)
3591 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3592 COLORREF oldTextColor, oldClrTextBk, oldClrText;
3593 NMLVCUSTOMDRAW nmlvcd;
3599 LISTVIEW_DUMP(infoPtr);
3601 infoPtr->bIsDrawing = TRUE;
3603 /* save dc values we're gonna trash while drawing */
3604 hOldFont = SelectObject(hdc, infoPtr->hFont);
3605 oldBkMode = GetBkMode(hdc);
3606 infoPtr->clrTextBkDefault = GetBkColor(hdc);
3607 oldTextColor = GetTextColor(hdc);
3609 oldClrTextBk = infoPtr->clrTextBk;
3610 oldClrText = infoPtr->clrText;
3612 GetClientRect(infoPtr->hwndSelf, &rcClient);
3613 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient);
3614 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3615 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
3617 /* Use these colors to draw the items */
3618 infoPtr->clrTextBk = nmlvcd.clrTextBk;
3619 infoPtr->clrText = nmlvcd.clrText;
3621 /* nothing to draw */
3622 if(infoPtr->nItemCount == 0) goto enddraw;
3624 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
3625 LISTVIEW_RefreshOwnerDraw(infoPtr, hdc);
3628 if (uView == LVS_REPORT)
3629 LISTVIEW_RefreshReport(infoPtr, hdc, cdmode);
3630 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
3631 LISTVIEW_RefreshList(infoPtr, hdc, cdmode);
3633 /* if we have a focus rect, draw it */
3634 if (infoPtr->bFocus)
3635 DrawFocusRect(hdc, &infoPtr->rcFocus);
3639 if (cdmode & CDRF_NOTIFYPOSTPAINT)
3640 notify_customdraw(infoPtr, CDDS_POSTPAINT, &nmlvcd);
3642 infoPtr->clrTextBk = oldClrTextBk;
3643 infoPtr->clrText = oldClrText;
3645 SelectObject(hdc, hOldFont);
3646 SetBkMode(hdc, oldBkMode);
3647 SetBkColor(hdc, infoPtr->clrTextBkDefault);
3648 SetTextColor(hdc, oldTextColor);
3649 infoPtr->bIsDrawing = FALSE;
3655 * Calculates the approximate width and height of a given number of items.
3658 * [I] infoPtr : valid pointer to the listview structure
3659 * [I] nItemCount : number of items
3660 * [I] wWidth : width
3661 * [I] wHeight : height
3664 * Returns a DWORD. The width in the low word and the height in high word.
3666 static LRESULT LISTVIEW_ApproximateViewRect(LISTVIEW_INFO *infoPtr, INT nItemCount,
3667 WORD wWidth, WORD wHeight)
3669 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3670 INT nItemCountPerColumn = 1;
3671 INT nColumnCount = 0;
3672 DWORD dwViewRect = 0;
3674 if (nItemCount == -1)
3675 nItemCount = infoPtr->nItemCount;
3677 if (uView == LVS_LIST)
3679 if (wHeight == 0xFFFF)
3681 /* use current height */
3682 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
3685 if (wHeight < infoPtr->nItemHeight)
3686 wHeight = infoPtr->nItemHeight;
3690 if (infoPtr->nItemHeight > 0)
3692 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
3693 if (nItemCountPerColumn == 0)
3694 nItemCountPerColumn = 1;
3696 if (nItemCount % nItemCountPerColumn != 0)
3697 nColumnCount = nItemCount / nItemCountPerColumn;
3699 nColumnCount = nItemCount / nItemCountPerColumn + 1;
3703 /* Microsoft padding magic */
3704 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
3705 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
3707 dwViewRect = MAKELONG(wWidth, wHeight);
3709 else if (uView == LVS_REPORT)
3710 FIXME("uView == LVS_REPORT: not implemented\n");
3711 else if (uView == LVS_SMALLICON)
3712 FIXME("uView == LVS_SMALLICON: not implemented\n");
3713 else if (uView == LVS_ICON)
3714 FIXME("uView == LVS_ICON: not implemented\n");
3721 * Arranges listview items in icon display mode.
3724 * [I] infoPtr : valid pointer to the listview structure
3725 * [I] nAlignCode : alignment code
3731 static LRESULT LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
3733 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3734 BOOL bResult = FALSE;
3736 if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
3741 FIXME("nAlignCode=LVA_ALIGNLEFT: not implemented\n");
3744 FIXME("nAlignCode=LVA_ALIGNTOP: not implemented\n");
3747 FIXME("nAlignCode=LVA_DEFAULT: not implemented\n");
3749 case LVA_SNAPTOGRID:
3750 FIXME("nAlignCode=LVA_SNAPTOGRID: not implemented\n");
3758 /* << LISTVIEW_CreateDragImage >> */
3763 * Removes all listview items and subitems.
3766 * [I] infoPtr : valid pointer to the listview structure
3772 static LRESULT LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr)
3774 LONG lStyle = infoPtr->dwStyle;
3775 UINT uView = lStyle & LVS_TYPEMASK;
3776 LISTVIEW_ITEM *lpItem;
3777 LISTVIEW_SUBITEM *lpSubItem;
3780 BOOL bResult = FALSE;
3785 LISTVIEW_DeselectAll(infoPtr);
3786 infoPtr->nSelectionMark=-1;
3787 infoPtr->nFocusedItem=-1;
3788 SetRectEmpty(&infoPtr->rcFocus);
3789 /* But we are supposed to leave nHotItem as is! */
3791 if (lStyle & LVS_OWNERDATA)
3793 infoPtr->nItemCount = 0;
3794 LISTVIEW_InvalidateList(infoPtr);
3798 if (infoPtr->nItemCount > 0)
3802 /* send LVN_DELETEALLITEMS notification */
3803 /* verify if subsequent LVN_DELETEITEM notifications should be
3805 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3807 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
3809 for (i = 0; i < infoPtr->nItemCount; i++)
3811 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
3812 if (hdpaSubItems != NULL)
3814 for (j = 1; j < hdpaSubItems->nItemCount; j++)
3816 lpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, j);
3817 if (lpSubItem != NULL)
3819 /* free subitem string */
3820 if (is_textW(lpSubItem->hdr.pszText))
3821 COMCTL32_Free(lpSubItem->hdr.pszText);
3824 COMCTL32_Free(lpSubItem);
3828 lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0);
3833 /* send LVN_DELETEITEM notification */
3835 nmlv.lParam = lpItem->lParam;
3836 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
3839 /* free item string */
3840 if (is_textW(lpItem->hdr.pszText))
3841 COMCTL32_Free(lpItem->hdr.pszText);
3844 COMCTL32_Free(lpItem);
3847 DPA_Destroy(hdpaSubItems);
3851 /* reinitialize listview memory */
3852 bResult = DPA_DeleteAllPtrs(infoPtr->hdpaItems);
3853 infoPtr->nItemCount = 0;
3854 DPA_DeleteAllPtrs(infoPtr->hdpaPosX);
3855 DPA_DeleteAllPtrs(infoPtr->hdpaPosY);
3857 /* align items (set position of each item) */
3858 if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
3860 if (lStyle & LVS_ALIGNLEFT)
3862 LISTVIEW_AlignLeft(infoPtr);
3866 LISTVIEW_AlignTop(infoPtr);
3870 LISTVIEW_UpdateScroll(infoPtr);
3872 LISTVIEW_InvalidateList(infoPtr);
3880 * Scrolls, and updates the columns, when a column is changing width.
3883 * [I] infoPtr : valid pointer to the listview structure
3884 * [I] nColumn : column to scroll
3885 * [I] dx : amount of scroll, in pixels
3891 static BOOL LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
3893 COLUMN_INFO *lpColumnInfo;
3897 if ((lpColumnInfo = DPA_GetPtr(infoPtr->hdpaColumns, nColumn)))
3898 rcCol = lpColumnInfo->rcHeader;
3900 /* ajust the other columns */
3901 for (nCol = nColumn; (lpColumnInfo = DPA_GetPtr(infoPtr->hdpaColumns, nCol)); nCol++)
3903 lpColumnInfo->rcHeader.left += dx;
3904 lpColumnInfo->rcHeader.right += dx;
3907 /* do not update screen if not in report mode */
3908 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return TRUE;
3910 /* if we have a focus, must first erase the focus rect */
3911 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
3913 /* Need to reset the item width when inserting a new column */
3914 infoPtr->nItemWidth += dx;
3916 LISTVIEW_UpdateScroll(infoPtr);
3918 /* scroll to cover the deleted column, and invalidate for redraw */
3919 rcOld = infoPtr->rcList;
3920 rcOld.left = rcCol.left;
3921 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
3923 /* we can restore focus now */
3924 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
3931 * Removes a column from the listview control.
3934 * [I] infoPtr : valid pointer to the listview structure
3935 * [I] nColumn : column index
3941 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
3945 TRACE("nColumn=%d\n", nColumn);
3947 if (nColumn <= 0) return FALSE;
3949 if (!LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol))
3952 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
3955 COMCTL32_Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
3956 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
3958 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
3960 LISTVIEW_SUBITEM *lpSubItem, *lpDelItem;
3962 INT nItem, nSubItem, i;
3964 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
3966 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
3967 if (!hdpaSubItems) continue;
3970 for (i = 1; i < hdpaSubItems->nItemCount; i++)
3972 lpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, i);
3973 if (!lpSubItem) break;
3974 if (lpSubItem->iSubItem == nColumn)
3977 lpDelItem = lpSubItem;
3979 else if (lpSubItem->iSubItem > nColumn)
3981 lpSubItem->iSubItem--;
3985 /* if we found our subitem, zapp it */
3989 if (is_textW(lpDelItem->hdr.pszText))
3990 COMCTL32_Free(lpDelItem->hdr.pszText);
3993 COMCTL32_Free(lpDelItem);
3995 /* free dpa memory */
3996 DPA_DeletePtr(hdpaSubItems, nSubItem);
4001 /* update the other column info */
4002 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4009 * Removes an item from the listview control.
4012 * [I] infoPtr : valid pointer to the listview structure
4013 * [I] nItem : item index
4019 static LRESULT LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4021 LONG lStyle = infoPtr->dwStyle;
4022 UINT uView = lStyle & LVS_TYPEMASK;
4024 BOOL bResult = FALSE;
4026 LISTVIEW_ITEM *lpItem;
4027 LISTVIEW_SUBITEM *lpSubItem;
4031 TRACE("(nItem=%d)\n", nItem);
4033 /* remove selection, and focus */
4035 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4036 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4038 /* send LVN_DELETEITEM notification. */
4039 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
4041 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
4043 if (lStyle & LVS_OWNERDATA)
4045 infoPtr->nItemCount--;
4046 LISTVIEW_InvalidateList(infoPtr); /*FIXME: optimize */
4050 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
4052 /* initialize memory */
4053 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4055 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4056 if (hdpaSubItems != NULL)
4058 infoPtr->nItemCount--;
4059 for (i = 1; i < hdpaSubItems->nItemCount; i++)
4061 lpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, i);
4062 if (lpSubItem != NULL)
4064 /* free item string */
4065 if (is_textW(lpSubItem->hdr.pszText))
4066 COMCTL32_Free(lpSubItem->hdr.pszText);
4069 COMCTL32_Free(lpSubItem);
4073 lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0);
4076 /* free item string */
4077 if (is_textW(lpItem->hdr.pszText))
4078 COMCTL32_Free(lpItem->hdr.pszText);
4081 COMCTL32_Free(lpItem);
4084 bResult = DPA_Destroy(hdpaSubItems);
4085 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4086 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4089 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4091 /* align items (set position of each item) */
4092 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4094 if (lStyle & LVS_ALIGNLEFT)
4095 LISTVIEW_AlignLeft(infoPtr);
4097 LISTVIEW_AlignTop(infoPtr);
4100 LISTVIEW_UpdateScroll(infoPtr);
4102 LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
4111 * Callback implementation for editlabel control
4114 * [I] infoPtr : valid pointer to the listview structure
4115 * [I] pszText : modified text
4116 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4122 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4124 NMLVDISPINFOW dispInfo;
4126 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4128 ZeroMemory(&dispInfo, sizeof(dispInfo));
4129 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
4130 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4131 dispInfo.item.iSubItem = 0;
4132 dispInfo.item.stateMask = ~0;
4133 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4134 /* add the text from the edit in */
4135 dispInfo.item.mask |= LVIF_TEXT;
4136 dispInfo.item.pszText = pszText;
4137 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4139 /* Do we need to update the Item Text */
4140 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4141 if (!pszText) return TRUE;
4143 ZeroMemory(&dispInfo, sizeof(dispInfo));
4144 dispInfo.item.mask = LVIF_TEXT;
4145 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4146 dispInfo.item.iSubItem = 0;
4147 dispInfo.item.pszText = pszText;
4148 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4149 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4154 * Begin in place editing of specified list view item
4157 * [I] infoPtr : valid pointer to the listview structure
4158 * [I] nItem : item index
4159 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4165 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4167 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4168 NMLVDISPINFOW dispInfo;
4171 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4173 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4174 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4176 infoPtr->nEditLabelItem = nItem;
4178 /* Is the EditBox still there, if so remove it */
4179 if(infoPtr->hwndEdit != 0)
4181 SetFocus(infoPtr->hwndSelf);
4182 infoPtr->hwndEdit = 0;
4185 LISTVIEW_SetSelection(infoPtr, nItem);
4186 LISTVIEW_SetItemFocus(infoPtr, nItem);
4188 rect.left = LVIR_LABEL;
4189 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4191 ZeroMemory(&dispInfo, sizeof(dispInfo));
4192 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4193 dispInfo.item.iItem = nItem;
4194 dispInfo.item.iSubItem = 0;
4195 dispInfo.item.stateMask = ~0;
4196 dispInfo.item.pszText = szDispText;
4197 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4198 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4200 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4201 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4202 if (!infoPtr->hwndEdit) return 0;
4204 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4206 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4207 infoPtr->hwndEdit = 0;
4211 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4212 SetFocus(infoPtr->hwndEdit);
4213 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4214 return infoPtr->hwndEdit;
4220 * Ensures the specified item is visible, scrolling into view if necessary.
4223 * [I] infoPtr : valid pointer to the listview structure
4224 * [I] nItem : item index
4225 * [I] bPartial : partially or entirely visible
4231 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4233 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4234 INT nScrollPosHeight = 0;
4235 INT nScrollPosWidth = 0;
4236 INT nHorzAdjust = 0;
4237 INT nVertAdjust = 0;
4240 RECT rcItem, rcTemp;
4242 rcItem.left = LVIR_BOUNDS;
4243 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4245 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4247 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4249 /* scroll left/right, but in LVS_REPORT mode */
4250 if (uView == LVS_LIST)
4251 nScrollPosWidth = infoPtr->nItemWidth;
4252 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4253 nScrollPosWidth = 1;
4255 if (rcItem.left < infoPtr->rcList.left)
4258 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
4263 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
4267 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
4269 /* scroll up/down, but not in LVS_LIST mode */
4270 if (uView == LVS_REPORT)
4271 nScrollPosHeight = infoPtr->nItemHeight;
4272 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
4273 nScrollPosHeight = 1;
4275 if (rcItem.top < infoPtr->rcList.top)
4278 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
4283 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
4287 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
4289 if (nScrollPosWidth)
4291 INT diff = nHorzDiff / nScrollPosWidth;
4292 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
4293 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
4296 if (nScrollPosHeight)
4298 INT diff = nVertDiff / nScrollPosHeight;
4299 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
4300 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
4308 * Searches for an item with specific characteristics.
4311 * [I] hwnd : window handle
4312 * [I] nStart : base item index
4313 * [I] lpFindInfo : item information to look for
4316 * SUCCESS : index of item
4319 static LRESULT LISTVIEW_FindItemW(LISTVIEW_INFO *infoPtr, INT nStart,
4320 LPLVFINDINFOW lpFindInfo)
4322 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4323 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4324 BOOL bWrap = FALSE, bNearest = FALSE;
4325 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
4326 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
4327 POINT Position, Destination;
4330 if (!lpFindInfo || nItem < 0) return -1;
4333 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
4335 lvItem.mask |= LVIF_TEXT;
4336 lvItem.pszText = szDispText;
4337 lvItem.cchTextMax = DISP_TEXT_SIZE;
4340 if (lpFindInfo->flags & LVFI_WRAP)
4343 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
4344 (uView == LVS_ICON || uView ==LVS_SMALLICON))
4348 FIXME("LVFI_NEARESTXY is slow.\n");
4349 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return -1;
4350 Destination.x = lpFindInfo->pt.x - Origin.x;
4351 Destination.y = lpFindInfo->pt.y - Origin.y;
4352 switch(lpFindInfo->vkDirection)
4354 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
4355 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
4356 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
4357 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
4358 case VK_HOME: Destination.x = Destination.y = 0; break;
4359 case VK_END: Destination.x = infoPtr->rcView.right; Destination.y = infoPtr->rcView.bottom; break;
4360 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4361 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4362 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
4367 /* if LVFI_PARAM is specified, all other flags are ignored */
4368 if (lpFindInfo->flags & LVFI_PARAM)
4370 lvItem.mask |= LVIF_PARAM;
4372 lvItem.mask &= ~LVIF_TEXT;
4376 for (; nItem < nLast; nItem++)
4378 lvItem.iItem = nItem;
4379 lvItem.iSubItem = 0;
4380 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
4382 if (lvItem.mask & LVIF_PARAM && lpFindInfo->lParam == lvItem.lParam)
4385 if (lvItem.mask & LVIF_TEXT)
4387 if (lpFindInfo->flags & LVFI_PARTIAL)
4389 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
4393 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
4397 if (!bNearest) return nItem;
4399 /* This is very inefficient. To do a good job here,
4400 * we need a sorted array of (x,y) item positions */
4401 if (!LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position)) continue;
4403 /* compute the distance^2 to the destination */
4404 xdist = Destination.x - Position.x;
4405 ydist = Destination.y - Position.y;
4406 dist = xdist * xdist + ydist * ydist;
4408 /* remember the distance, and item if it's closer */
4412 nNearestItem = nItem;
4419 nLast = min(nStart + 1, infoPtr->nItemCount);
4424 return nNearestItem;
4429 * Searches for an item with specific characteristics.
4432 * [I] hwnd : window handle
4433 * [I] nStart : base item index
4434 * [I] lpFindInfo : item information to look for
4437 * SUCCESS : index of item
4440 static LRESULT LISTVIEW_FindItemA(LISTVIEW_INFO *infoPtr, INT nStart,
4441 LPLVFINDINFOA lpFindInfo)
4443 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
4447 memcpy(&fiw, lpFindInfo, sizeof(fiw));
4448 if (hasText) fiw.psz = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
4449 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
4450 if (hasText) textfreeT((LPWSTR)fiw.psz, FALSE);
4456 * Retrieves the background image of the listview control.
4459 * [I] infoPtr : valid pointer to the listview structure
4460 * [O] lpBkImage : background image attributes
4466 /* static LRESULT LISTVIEW_GetBkImage(LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
4468 /* FIXME (listview, "empty stub!\n"); */
4474 * Retrieves column attributes.
4477 * [I] infoPtr : valid pointer to the listview structure
4478 * [I] nColumn : column index
4479 * [IO] lpColumn : column information
4480 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
4481 * otherwise it is in fact a LPLVCOLUMNA
4487 static LRESULT LISTVIEW_GetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
4489 COLUMN_INFO *lpColumnInfo;
4492 if (!lpColumn || nColumn < 0 || nColumn >= infoPtr->hdpaColumns->nItemCount) return FALSE;
4493 if (!(lpColumnInfo = DPA_GetPtr(infoPtr->hdpaColumns, nColumn))) return FALSE;
4495 /* initialize memory */
4496 ZeroMemory(&hdi, sizeof(hdi));
4498 if (lpColumn->mask & LVCF_TEXT)
4500 hdi.mask |= HDI_TEXT;
4501 hdi.pszText = lpColumn->pszText;
4502 hdi.cchTextMax = lpColumn->cchTextMax;
4505 if (lpColumn->mask & LVCF_IMAGE)
4506 hdi.mask |= HDI_IMAGE;
4508 if (lpColumn->mask & LVCF_ORDER)
4509 hdi.mask |= HDI_ORDER;
4511 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
4513 if (lpColumn->mask & LVCF_FMT)
4514 lpColumn->fmt = lpColumnInfo->fmt;
4516 if (lpColumn->mask & LVCF_WIDTH)
4517 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
4519 if (lpColumn->mask & LVCF_IMAGE)
4520 lpColumn->iImage = hdi.iImage;
4522 if (lpColumn->mask & LVCF_ORDER)
4523 lpColumn->iOrder = hdi.iOrder;
4529 static LRESULT LISTVIEW_GetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
4536 /* FIXME: little hack */
4537 for (i = 0; i < iCount; i++)
4545 * Retrieves the column width.
4548 * [I] infoPtr : valid pointer to the listview structure
4549 * [I] int : column index
4552 * SUCCESS : column width
4555 static INT LISTVIEW_GetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn)
4557 INT nColumnWidth = 0;
4560 TRACE("nColumn=%d\n", nColumn);
4562 /* we have a 'column' in LIST and REPORT mode only */
4563 switch(infoPtr->dwStyle & LVS_TYPEMASK)
4566 nColumnWidth = infoPtr->nItemWidth;
4569 if (LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader))
4570 nColumnWidth = rcHeader.right - rcHeader.left;
4574 TRACE("nColumnWidth=%d\n", nColumnWidth);
4575 return nColumnWidth;
4580 * In list or report display mode, retrieves the number of items that can fit
4581 * vertically in the visible area. In icon or small icon display mode,
4582 * retrieves the total number of visible items.
4585 * [I] infoPtr : valid pointer to the listview structure
4588 * Number of fully visible items.
4590 static LRESULT LISTVIEW_GetCountPerPage(LISTVIEW_INFO *infoPtr)
4592 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4595 if (uView == LVS_LIST)
4597 if (infoPtr->rcList.right > infoPtr->nItemWidth)
4599 nItemCount = LISTVIEW_GetCountPerRow(infoPtr) *
4600 LISTVIEW_GetCountPerColumn(infoPtr);
4603 else if (uView == LVS_REPORT)
4605 nItemCount = LISTVIEW_GetCountPerColumn(infoPtr);
4609 nItemCount = infoPtr->nItemCount;
4618 * Retrieves an image list handle.
4621 * [I] infoPtr : valid pointer to the listview structure
4622 * [I] nImageList : image list identifier
4625 * SUCCESS : image list handle
4628 static LRESULT LISTVIEW_GetImageList(LISTVIEW_INFO *infoPtr, INT nImageList)
4630 HIMAGELIST himl = NULL;
4635 himl = infoPtr->himlNormal;
4638 himl = infoPtr->himlSmall;
4641 himl = infoPtr->himlState;
4645 return (LRESULT)himl;
4648 /* LISTVIEW_GetISearchString */
4652 * Retrieves item attributes.
4655 * [I] hwnd : window handle
4656 * [IO] lpLVItem : item info
4657 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
4658 * if FALSE, the lpLVItem is a LPLVITEMA.
4661 * This is the internal 'GetItem' interface -- it tries to
4662 * be smart, and avoids text copies, if possible, by modifing
4663 * lpLVItem->pszText to point to the text string. Please note
4664 * that this is not always possible (e.g. OWNERDATA), so on
4665 * entry you *must* supply valid values for pszText, and cchTextMax.
4666 * The only difference to the documented interface is that upon
4667 * return, you should use *only* the lpLVItem->pszText, rather than
4668 * the buffer pointer you provided on input. Most code already does
4669 * that, so it's not a problem.
4670 * For the two cases when the text must be copied (that is,
4671 * for LVM_GETITEM, and LVMGETITEMTEXT), use LISTVIEW_GetItemExtT.
4677 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
4679 NMLVDISPINFOW dispInfo;
4680 LISTVIEW_ITEM *lpItem;
4684 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
4686 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
4689 if (lpLVItem->mask == 0) return TRUE;
4691 /* a quick optimization if all we're asked is the focus state
4692 * these queries are worth optimising since they are common,
4693 * and can be answered in constant time, without the heavy accesses */
4694 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
4695 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
4697 lpLVItem->state = 0;
4698 if (infoPtr->nFocusedItem == lpLVItem->iItem)
4699 lpLVItem->state |= LVIS_FOCUSED;
4703 ZeroMemory(&dispInfo, sizeof(dispInfo));
4705 /* if the app stores all the data, handle it separately */
4706 if (infoPtr->dwStyle & LVS_OWNERDATA)
4708 dispInfo.item.state = 0;
4710 /* if we need to callback, do it now */
4711 if ((lpLVItem->mask & ~LVIF_STATE) || infoPtr->uCallbackMask)
4713 /* NOTE: copy only fields which we _know_ are initialized, some apps
4714 * depend on the uninitialized fields being 0 */
4715 dispInfo.item.mask = lpLVItem->mask;
4716 dispInfo.item.iItem = lpLVItem->iItem;
4717 dispInfo.item.iSubItem = lpLVItem->iSubItem;
4718 if (lpLVItem->mask & LVIF_TEXT)
4720 dispInfo.item.pszText = lpLVItem->pszText;
4721 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
4723 if (lpLVItem->mask & LVIF_STATE)
4724 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
4725 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
4726 dispInfo.item.stateMask = lpLVItem->stateMask;
4727 *lpLVItem = dispInfo.item;
4728 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
4731 /* we store only a little state, so if we're not asked, we're done */
4732 if (!(lpLVItem->mask & LVIF_STATE) || lpLVItem->iSubItem) return TRUE;
4734 /* if focus is handled by us, report it */
4735 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
4737 lpLVItem->state &= ~LVIS_FOCUSED;
4738 if (infoPtr->nFocusedItem == lpLVItem->iItem)
4739 lpLVItem->state |= LVIS_FOCUSED;
4742 /* and do the same for selection, if we handle it */
4743 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
4745 lpLVItem->state &= ~LVIS_SELECTED;
4746 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
4747 lpLVItem->state |= LVIS_SELECTED;
4753 /* find the item and subitem structures before we proceed */
4754 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4755 if (hdpaSubItems == NULL) return FALSE;
4757 if ( !(lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0)) )
4760 if (lpLVItem->iSubItem)
4762 LISTVIEW_SUBITEM *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
4763 if(!lpSubItem) return FALSE;
4764 pItemHdr = &lpSubItem->hdr;
4767 pItemHdr = &lpItem->hdr;
4769 /* Do we need to query the state from the app? */
4770 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && lpLVItem->iSubItem == 0)
4772 dispInfo.item.mask |= LVIF_STATE;
4773 dispInfo.item.stateMask = infoPtr->uCallbackMask;
4776 /* Do we need to enquire about the image? */
4777 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK)
4778 dispInfo.item.mask |= LVIF_IMAGE;
4780 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
4781 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
4783 dispInfo.item.mask |= LVIF_TEXT;
4784 dispInfo.item.pszText = lpLVItem->pszText;
4785 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
4786 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
4787 *dispInfo.item.pszText = '\0';
4790 /* If we don't have all the requested info, query the application */
4791 if (dispInfo.item.mask != 0)
4793 dispInfo.item.iItem = lpLVItem->iItem;
4794 dispInfo.item.iSubItem = lpLVItem->iSubItem;
4795 dispInfo.item.lParam = lpItem->lParam;
4796 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
4797 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
4800 /* Now, handle the iImage field */
4801 if (dispInfo.item.mask & LVIF_IMAGE)
4803 lpLVItem->iImage = dispInfo.item.iImage;
4804 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
4805 pItemHdr->iImage = dispInfo.item.iImage;
4807 else if (lpLVItem->mask & LVIF_IMAGE)
4808 lpLVItem->iImage = pItemHdr->iImage;
4810 /* The pszText field */
4811 if (dispInfo.item.mask & LVIF_TEXT)
4813 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
4814 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
4816 lpLVItem->pszText = dispInfo.item.pszText;
4818 else if (lpLVItem->mask & LVIF_TEXT)
4820 if (isW) lpLVItem->pszText = pItemHdr->pszText;
4821 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
4824 /* if this is a subitem, we're done */
4825 if (lpLVItem->iSubItem) return TRUE;
4827 /* Next is the lParam field */
4828 if (dispInfo.item.mask & LVIF_PARAM)
4830 lpLVItem->lParam = dispInfo.item.lParam;
4831 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
4832 lpItem->lParam = dispInfo.item.lParam;
4834 else if (lpLVItem->mask & LVIF_PARAM)
4835 lpLVItem->lParam = lpItem->lParam;
4837 /* ... the state field (this one is different due to uCallbackmask) */
4838 if (lpLVItem->mask & LVIF_STATE)
4840 lpLVItem->state = lpItem->state;
4841 if (dispInfo.item.mask & LVIF_STATE)
4843 lpLVItem->state &= ~dispInfo.item.stateMask;
4844 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
4846 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
4848 lpLVItem->state &= ~LVIS_FOCUSED;
4849 if (infoPtr->nFocusedItem == lpLVItem->iItem)
4850 lpLVItem->state |= LVIS_FOCUSED;
4852 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
4854 lpLVItem->state &= ~LVIS_SELECTED;
4855 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
4856 lpLVItem->state |= LVIS_SELECTED;
4860 /* and last, but not least, the indent field */
4861 if (lpLVItem->mask & LVIF_INDENT)
4862 lpLVItem->iIndent = lpItem->iIndent;
4869 * Retrieves item attributes.
4872 * [I] hwnd : window handle
4873 * [IO] lpLVItem : item info
4874 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
4875 * if FALSE, the lpLVItem is a LPLVITEMA.
4878 * This is the external 'GetItem' interface -- it properly copies
4879 * the text in the provided buffer.
4885 static BOOL LISTVIEW_GetItemExtT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
4890 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
4893 pszText = lpLVItem->pszText;
4894 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
4895 if (bResult && lpLVItem->pszText != pszText)
4896 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
4897 lpLVItem->pszText = pszText;
4905 * Retrieves the position (upper-left) of the listview control item.
4906 * Note that for LVS_ICON style, the upper-left is that of the icon
4907 * and not the bounding box.
4910 * [I] infoPtr : valid pointer to the listview structure
4911 * [I] nItem : item index
4912 * [O] lpptPosition : coordinate information
4918 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
4920 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4923 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
4925 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4926 if (!LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition)) return FALSE;
4927 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return FALSE;
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;
5015 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return FALSE;
5016 if (!LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position)) return FALSE;
5018 /* Be smart and try to figure out the minimum we have to do */
5019 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5020 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5021 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5022 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5023 oversizedBox = TRUE;
5025 /* get what we need from the item before hand, so we make
5026 * only one request. This can speed up things, if data
5027 * is stored on the app side */
5029 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5030 if (doLabel) lvItem.mask |= LVIF_TEXT;
5031 lvItem.iItem = nItem;
5032 lvItem.iSubItem = 0;
5033 lvItem.pszText = szDispText;
5034 lvItem.cchTextMax = DISP_TEXT_SIZE;
5035 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5036 /* we got the state already up, simulate it here, to avoid a reget */
5037 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5039 lvItem.mask |= LVIF_STATE;
5040 lvItem.stateMask = LVIS_FOCUSED;
5041 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5044 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5045 lprc->left = LVIR_BOUNDS;
5049 if (!LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL)) return FALSE;
5053 if (!LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, lprc)) return FALSE;
5057 if (!LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL)) return FALSE;
5060 case LVIR_SELECTBOUNDS:
5061 if (!LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, &label_rect)) return FALSE;
5062 UnionRect(lprc, lprc, &label_rect);
5066 WARN("Unknown value: %d\n", lprc->left);
5070 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5072 TRACE(" rect=%s\n", debugrect(lprc));
5079 * Retrieves the spacing between listview control items.
5082 * [I] infoPtr : valid pointer to the listview structure
5083 * [IO] lprc : rectangle to receive the output
5084 * on input, lprc->top = nSubItem
5085 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5087 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5088 * not only those of the first column.
5089 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5095 static BOOL LISTVIEW_GetSubItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5097 POINT Position, Origin;
5100 if (!lprc || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5102 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
5104 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return FALSE;
5105 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5107 lvItem.mask = lprc->top == 0 ? LVIF_INDENT : 0;
5108 lvItem.iItem = nItem;
5109 lvItem.iSubItem = lprc->top;
5111 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5115 if (!LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL)) return FALSE;
5120 if (!LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL)) return FALSE;
5124 ERR("Unknown bounds=%d\n", lprc->left);
5128 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5135 * Retrieves the width of a label.
5138 * [I] infoPtr : valid pointer to the listview structure
5141 * SUCCESS : string width (in pixels)
5144 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *infoPtr, INT nItem)
5146 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5149 TRACE("(nItem=%d)\n", nItem);
5151 lvItem.mask = LVIF_TEXT;
5152 lvItem.iItem = nItem;
5153 lvItem.iSubItem = 0;
5154 lvItem.pszText = szDispText;
5155 lvItem.cchTextMax = DISP_TEXT_SIZE;
5156 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5158 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5163 * Retrieves the spacing between listview control items.
5166 * [I] infoPtr : valid pointer to the listview structure
5167 * [I] bSmall : flag for small or large icon
5170 * Horizontal + vertical spacing
5172 static LRESULT LISTVIEW_GetItemSpacing(LISTVIEW_INFO *infoPtr, BOOL bSmall)
5178 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5182 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5183 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5185 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5192 * Retrieves the state of a listview control item.
5195 * [I] infoPtr : valid pointer to the listview structure
5196 * [I] nItem : item index
5197 * [I] uMask : state mask
5200 * State specified by the mask.
5202 static LRESULT LISTVIEW_GetItemState(LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5206 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5208 lvItem.iItem = nItem;
5209 lvItem.iSubItem = 0;
5210 lvItem.mask = LVIF_STATE;
5211 lvItem.stateMask = uMask;
5212 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5214 return lvItem.state & uMask;
5219 * Retrieves the text of a listview control item or subitem.
5222 * [I] hwnd : window handle
5223 * [I] nItem : item index
5224 * [IO] lpLVItem : item information
5225 * [I] isW : TRUE if lpLVItem is Unicode
5228 * SUCCESS : string length
5231 static LRESULT LISTVIEW_GetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
5233 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5235 lpLVItem->mask = LVIF_TEXT;
5236 lpLVItem->iItem = nItem;
5237 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
5239 return textlenT(lpLVItem->pszText, isW);
5244 * Searches for an item based on properties + relationships.
5247 * [I] infoPtr : valid pointer to the listview structure
5248 * [I] nItem : item index
5249 * [I] uFlags : relationship flag
5252 * This function is very, very inefficient! Needs work.
5255 * SUCCESS : item index
5258 static LRESULT LISTVIEW_GetNextItem(LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
5260 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5262 LVFINDINFOW lvFindInfo;
5263 INT nCountPerColumn;
5266 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
5267 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
5269 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
5271 if (uFlags & LVNI_CUT)
5274 if (uFlags & LVNI_DROPHILITED)
5275 uMask |= LVIS_DROPHILITED;
5277 if (uFlags & LVNI_FOCUSED)
5278 uMask |= LVIS_FOCUSED;
5280 if (uFlags & LVNI_SELECTED)
5281 uMask |= LVIS_SELECTED;
5283 /* if we're asked for the focused item, that's only one,
5284 * so it's worth optimizing */
5285 if (uFlags & LVNI_FOCUSED)
5287 if (!(LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) == uMask) return -1;
5288 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
5291 if (uFlags & LVNI_ABOVE)
5293 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5298 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5304 lvFindInfo.flags = LVFI_NEARESTXY;
5305 lvFindInfo.vkDirection = VK_UP;
5306 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5307 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5309 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5314 else if (uFlags & LVNI_BELOW)
5316 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5318 while (nItem < infoPtr->nItemCount)
5321 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5327 lvFindInfo.flags = LVFI_NEARESTXY;
5328 lvFindInfo.vkDirection = VK_DOWN;
5329 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5330 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5332 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5337 else if (uFlags & LVNI_TOLEFT)
5339 if (uView == LVS_LIST)
5341 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5342 while (nItem - nCountPerColumn >= 0)
5344 nItem -= nCountPerColumn;
5345 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5349 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5351 lvFindInfo.flags = LVFI_NEARESTXY;
5352 lvFindInfo.vkDirection = VK_LEFT;
5353 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5354 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5356 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5361 else if (uFlags & LVNI_TORIGHT)
5363 if (uView == LVS_LIST)
5365 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5366 while (nItem + nCountPerColumn < infoPtr->nItemCount)
5368 nItem += nCountPerColumn;
5369 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5373 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5375 lvFindInfo.flags = LVFI_NEARESTXY;
5376 lvFindInfo.vkDirection = VK_RIGHT;
5377 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5378 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5380 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5389 /* search by index */
5390 for (i = nItem; i < infoPtr->nItemCount; i++)
5392 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
5400 /* LISTVIEW_GetNumberOfWorkAreas */
5404 * Retrieves the origin coordinates when in icon or small icon display mode.
5407 * [I] infoPtr : valid pointer to the listview structure
5408 * [O] lpptOrigin : coordinate information
5414 static BOOL 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 if (!lpptOrigin) return FALSE;
5423 scrollInfo.cbSize = sizeof(SCROLLINFO);
5424 scrollInfo.fMask = SIF_POS;
5426 if ((lStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
5427 nHorzPos = scrollInfo.nPos;
5428 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
5429 nVertPos = scrollInfo.nPos;
5431 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
5433 lpptOrigin->x = infoPtr->rcList.left;
5434 lpptOrigin->y = infoPtr->rcList.top;
5435 if (uView == LVS_LIST)
5436 nHorzPos *= infoPtr->nItemWidth;
5437 else if (uView == LVS_REPORT)
5438 nVertPos *= infoPtr->nItemHeight;
5440 lpptOrigin->x -= nHorzPos;
5441 lpptOrigin->y -= nVertPos;
5443 TRACE(" origin=%s\n", debugpoint(lpptOrigin));
5450 * Retrieves the width of a string.
5453 * [I] hwnd : window handle
5454 * [I] lpszText : text string to process
5455 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
5458 * SUCCESS : string width (in pixels)
5461 static LRESULT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
5466 if (is_textT(lpszText, isW))
5468 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
5469 HDC hdc = GetDC(infoPtr->hwndSelf);
5470 HFONT hOldFont = SelectObject(hdc, hFont);
5473 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
5475 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
5476 SelectObject(hdc, hOldFont);
5477 ReleaseDC(infoPtr->hwndSelf, hdc);
5479 return stringSize.cx;
5484 * Determines which listview item is located at the specified position.
5487 * [I] infoPtr : valid pointer to the listview structure
5488 * [IO] lpht : hit test information
5489 * [I] subitem : fill out iSubItem.
5490 * [I] select : return the index only if the hit selects the item
5493 * (mm 20001022): We must not allow iSubItem to be touched, for
5494 * an app might pass only a structure with space up to iItem!
5495 * (MS Office 97 does that for instance in the file open dialog)
5498 * SUCCESS : item index
5501 static LRESULT LISTVIEW_HitTest(LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
5503 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5504 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5505 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
5506 POINT Origin, Position, opt;
5510 TRACE("(pt=%s, subitem=%d, select=%d)\n", debugpoint(&lpht->pt), subitem, select);
5514 if (subitem) lpht->iSubItem = 0;
5516 if (infoPtr->rcList.left > lpht->pt.x)
5517 lpht->flags |= LVHT_TOLEFT;
5518 else if (infoPtr->rcList.right < lpht->pt.x)
5519 lpht->flags |= LVHT_TORIGHT;
5521 if (infoPtr->rcList.top > lpht->pt.y)
5522 lpht->flags |= LVHT_ABOVE;
5523 else if (infoPtr->rcList.bottom < lpht->pt.y)
5524 lpht->flags |= LVHT_BELOW;
5526 TRACE("lpht->flags=0x%x\n", lpht->flags);
5527 if (lpht->flags) return -1;
5529 lpht->flags |= LVHT_NOWHERE;
5531 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return -1;
5533 /* first deal with the large items */
5534 rcSearch.left = lpht->pt.x;
5535 rcSearch.top = lpht->pt.y;
5536 rcSearch.right = rcSearch.left + 1;
5537 rcSearch.bottom = rcSearch.top + 1;
5539 iterator_frameditems(&i, infoPtr, &rcSearch);
5540 iterator_next(&i); /* go to first item in the sequence */
5541 lpht->iItem = i.nItem;
5542 iterator_destroy(&i);
5544 TRACE("lpht->iItem=%d\n", lpht->iItem);
5545 if (lpht->iItem == -1) return -1;
5547 lvItem.mask = LVIF_STATE | LVIF_TEXT;
5548 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5549 lvItem.stateMask = LVIS_STATEIMAGEMASK;
5550 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
5551 lvItem.iItem = lpht->iItem;
5552 lvItem.iSubItem = 0;
5553 lvItem.pszText = szDispText;
5554 lvItem.cchTextMax = DISP_TEXT_SIZE;
5555 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
5556 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
5558 if (!LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel)) return -1;
5559 if (!LISTVIEW_GetItemOrigin(infoPtr, lpht->iItem, &Position)) return -1;
5560 opt.x = lpht->pt.x - Position.x - Origin.x;
5561 opt.y = lpht->pt.y - Position.y - Origin.y;
5563 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
5566 UnionRect(&rcBounds, &rcIcon, &rcLabel);
5567 TRACE("rcBounds=%s\n", debugrect(&rcBounds));
5568 if (!PtInRect(&rcBounds, opt)) return -1;
5570 if (PtInRect(&rcIcon, opt))
5571 lpht->flags |= LVHT_ONITEMICON;
5572 else if (PtInRect(&rcLabel, opt))
5573 lpht->flags |= LVHT_ONITEMLABEL;
5574 else if (infoPtr->himlState && ((lvItem.state & LVIS_STATEIMAGEMASK) >> 12) && PtInRect(&rcState, opt))
5575 lpht->flags |= LVHT_ONITEMSTATEICON;
5576 if (lpht->flags & LVHT_ONITEM)
5577 lpht->flags &= ~LVHT_NOWHERE;
5579 TRACE("lpht->flags=0x%x\n", lpht->flags);
5580 if (uView == LVS_REPORT && lpht->iItem != -1 && subitem)
5584 rcBounds.right = rcBounds.left;
5585 for (j = 0; j < infoPtr->hdpaColumns->nItemCount; j++)
5587 rcBounds.left = rcBounds.right;
5588 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
5589 if (PtInRect(&rcBounds, opt))
5597 if (!select || lpht->iItem == -1) return lpht->iItem;
5599 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)) return lpht->iItem;
5601 if (uView == LVS_REPORT) UnionRect(&rcBounds, &rcIcon, &rcLabel);
5602 return PtInRect(&rcBounds, opt) ? lpht->iItem : -1;
5606 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
5607 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
5608 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
5609 and not during the processing of a LVM_SORTITEMS message. Applications should provide
5610 their own sort proc. when sending LVM_SORTITEMS.
5613 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
5615 LVS_SORTXXX must be specified,
5616 LVS_OWNERDRAW is not set,
5617 <item>.pszText is not LPSTR_TEXTCALLBACK.
5619 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
5620 are sorted based on item text..."
5622 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
5624 LISTVIEW_ITEM* lv_first = (LISTVIEW_ITEM*) DPA_GetPtr( (HDPA)first, 0 );
5625 LISTVIEW_ITEM* lv_second = (LISTVIEW_ITEM*) DPA_GetPtr( (HDPA)second, 0 );
5626 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
5628 /* if we're sorting descending, negate the return value */
5629 return (((LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
5634 * Inserts a new item in the listview control.
5637 * [I] infoPtr : valid pointer to the listview structure
5638 * [I] lpLVItem : item information
5639 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
5642 * SUCCESS : new item index
5645 static LRESULT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5647 LONG lStyle = infoPtr->dwStyle;
5648 UINT uView = lStyle & LVS_TYPEMASK;
5652 LISTVIEW_ITEM *lpItem;
5653 BOOL is_sorted, has_changed;
5655 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5657 if (lStyle & LVS_OWNERDATA)
5659 nItem = infoPtr->nItemCount;
5660 infoPtr->nItemCount++;
5664 /* make sure it's an item, and not a subitem; cannot insert a subitem */
5665 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iSubItem) return -1;
5667 if (!is_assignable_item(lpLVItem, lStyle)) return -1;
5669 if ( !(lpItem = (LISTVIEW_ITEM *)COMCTL32_Alloc(sizeof(LISTVIEW_ITEM))) )
5672 /* insert item in listview control data structure */
5673 if ( (hdpaSubItems = DPA_Create(8)) )
5674 nItem = DPA_InsertPtr(hdpaSubItems, 0, lpItem);
5675 if (nItem == -1) goto fail;
5677 /* FIXME: is the handling of this LVS_OWNERDRAWFIXED correct? */
5678 is_sorted = (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
5679 !(lStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
5681 nItem = DPA_InsertPtr( infoPtr->hdpaItems,
5682 is_sorted ? infoPtr->nItemCount + 1 : lpLVItem->iItem,
5684 if (nItem == -1) goto fail;
5685 /* the array may be sparsly populated, we can't just increment the count here */
5686 infoPtr->nItemCount = infoPtr->hdpaItems->nItemCount;
5688 /* set the item attributes */
5689 if (!set_main_item(infoPtr, lpLVItem, TRUE, isW, &has_changed)) goto undo;
5691 /* if we're sorted, sort the list, and update the index */
5694 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
5695 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
5696 assert(nItem != -1);
5699 /* make room for the position, if we are in the right mode */
5700 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5702 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
5704 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
5706 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
5711 /* Add the subitem list to the items array. Do this last in case we go to
5712 * fail during the above.
5714 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
5716 /* send LVN_INSERTITEM notification */
5717 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
5719 nmlv.lParam = lpItem->lParam;
5720 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
5722 /* align items (set position of each item) */
5723 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5725 if (lStyle & LVS_ALIGNLEFT) LISTVIEW_AlignLeft(infoPtr);
5726 else LISTVIEW_AlignTop(infoPtr);
5729 LISTVIEW_UpdateScroll(infoPtr);
5731 LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
5733 TRACE(" <- %d\n", nItem);
5737 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
5738 infoPtr->nItemCount--;
5740 DPA_DeletePtr(hdpaSubItems, 0);
5741 DPA_Destroy (hdpaSubItems);
5742 COMCTL32_Free (lpItem);
5748 * Redraws a range of items.
5751 * [I] infoPtr : valid pointer to the listview structure
5752 * [I] nFirst : first item
5753 * [I] nLast : last item
5759 static LRESULT LISTVIEW_RedrawItems(LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
5763 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
5764 max(nFirst, nLast) >= infoPtr->nItemCount)
5767 for (i = nFirst; i <= nLast; i++)
5768 LISTVIEW_InvalidateItem(infoPtr, i);
5775 * Scroll the content of a listview.
5778 * [I] infoPtr : valid pointer to the listview structure
5779 * [I] dx : horizontal scroll amount in pixels
5780 * [I] dy : vertical scroll amount in pixels
5787 * If the control is in report mode (LVS_REPORT) the control can
5788 * be scrolled only in line increments. "dy" will be rounded to the
5789 * nearest number of pixels that are a whole line. Ex: if line height
5790 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
5791 * is passed the the scroll will be 0. (per MSDN 7/2002)
5793 * For: (per experimentaion with native control and CSpy ListView)
5794 * LVS_ICON dy=1 = 1 pixel (vertical only)
5796 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
5798 * LVS_LIST dx=1 = 1 column (horizontal only)
5799 * but will only scroll 1 column per message
5800 * no matter what the value.
5801 * dy must be 0 or FALSE returned.
5802 * LVS_REPORT dx=1 = 1 pixel
5806 static LRESULT LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
5808 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
5810 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
5811 dy /= infoPtr->nItemHeight;
5814 if (dy != 0) return FALSE;
5821 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
5822 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
5829 * Sets the background color.
5832 * [I] infoPtr : valid pointer to the listview structure
5833 * [I] clrBk : background color
5839 static LRESULT LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
5841 TRACE("(clrBk=%lx)\n", clrBk);
5843 if(infoPtr->clrBk != clrBk) {
5844 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
5845 infoPtr->clrBk = clrBk;
5846 if (clrBk == CLR_NONE)
5847 infoPtr->hBkBrush = GetClassLongW(infoPtr->hwndSelf, GCL_HBRBACKGROUND);
5849 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
5850 LISTVIEW_InvalidateList(infoPtr);
5856 /* LISTVIEW_SetBkImage */
5858 /*** Helper for {Insert,Set}ColumnT *only* */
5859 static void column_fill_hditem(LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5861 if (lpColumn->mask & LVCF_FMT)
5863 /* format member is valid */
5864 lphdi->mask |= HDI_FORMAT;
5866 /* set text alignment (leftmost column must be left-aligned) */
5867 if (nColumn == 0 || lpColumn->fmt & LVCFMT_LEFT)
5868 lphdi->fmt |= HDF_LEFT;
5869 else if (lpColumn->fmt & LVCFMT_RIGHT)
5870 lphdi->fmt |= HDF_RIGHT;
5871 else if (lpColumn->fmt & LVCFMT_CENTER)
5872 lphdi->fmt |= HDF_CENTER;
5874 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
5875 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
5877 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
5879 lphdi->fmt |= HDF_IMAGE;
5880 lphdi->iImage = I_IMAGECALLBACK;
5884 if (lpColumn->mask & LVCF_WIDTH)
5886 lphdi->mask |= HDI_WIDTH;
5887 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
5889 /* make it fill the remainder of the controls width */
5893 for(item_index = 0; item_index < (nColumn - 1); item_index++)
5894 if (LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader))
5895 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;
5928 /*** Helper for {Insert,Set}ColumnT *only* */
5929 static BOOL column_fill_info(LISTVIEW_INFO *infoPtr, COLUMN_INFO *lpColumnInfo, INT nColumn, LPLVCOLUMNW lpColumn)
5933 if (!Header_GetItemRect(infoPtr->hwndHeader, nColumn, &rcCol)) return FALSE;
5934 lpColumnInfo->rcHeader = rcCol;
5935 if (lpColumn->mask & LVCF_FMT)
5936 lpColumnInfo->fmt = lpColumn->fmt;
5943 * Inserts a new column.
5946 * [I] infoPtr : valid pointer to the listview structure
5947 * [I] nColumn : column index
5948 * [I] lpColumn : column information
5949 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
5952 * SUCCESS : new column index
5955 static LRESULT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
5956 LPLVCOLUMNW lpColumn, BOOL isW)
5958 COLUMN_INFO *lpColumnInfo;
5962 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
5964 if (!lpColumn) return -1;
5966 ZeroMemory(&hdi, sizeof(HDITEMW));
5967 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
5969 /* insert item in header control */
5970 nNewColumn = SendMessageW(infoPtr->hwndHeader,
5971 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
5972 (WPARAM)nColumn, (LPARAM)&hdi);
5973 if (nNewColumn == -1) return -1;
5975 /* create our own column info */
5976 if (!(lpColumnInfo = COMCTL32_Alloc(sizeof(COLUMN_INFO)))) goto fail;
5977 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
5979 if (!column_fill_info(infoPtr, lpColumnInfo, nNewColumn, lpColumn)) goto fail;
5981 /* now we have to actually adjust the data */
5982 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
5984 LISTVIEW_SUBITEM *lpSubItem, *lpMainItem, **lpNewItems = 0;
5988 /* preallocate memory, so we can fail gracefully */
5989 if (nNewColumn == 0)
5991 lpNewItems = COMCTL32_Alloc(sizeof(LISTVIEW_SUBITEM *) * infoPtr->nItemCount);
5992 if (!lpNewItems) goto fail;
5993 for (i = 0; i < infoPtr->nItemCount; i++)
5994 if (!(lpNewItems[i] = COMCTL32_Alloc(sizeof(LISTVIEW_SUBITEM)))) break;
5995 if (i != infoPtr->nItemCount)
5997 for(; i >=0; i--) COMCTL32_Free(lpNewItems[i]);
5998 COMCTL32_Free(lpNewItems);
6003 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6005 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
6006 if (!hdpaSubItems) continue;
6007 for (i = 1; i < hdpaSubItems->nItemCount; i++)
6009 lpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, i);
6010 if (!lpSubItem) break;
6011 if (lpSubItem->iSubItem >= nNewColumn)
6012 lpSubItem->iSubItem++;
6015 /* if we found our subitem, zapp it */
6016 if (nNewColumn == 0)
6018 lpMainItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, 0);
6019 lpSubItem = lpNewItems[nItem];
6020 lpSubItem->hdr = lpMainItem->hdr;
6021 lpSubItem->iSubItem = 1;
6022 ZeroMemory(&lpMainItem->hdr, sizeof(lpMainItem->hdr));
6023 lpMainItem->iSubItem = 0;
6024 DPA_InsertPtr(hdpaSubItems, 1, lpSubItem);
6028 COMCTL32_Free(lpNewItems);
6031 /* make space for the new column */
6032 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6037 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6040 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6041 COMCTL32_Free(lpColumnInfo);
6048 * Sets the attributes of a header item.
6051 * [I] infoPtr : valid pointer to the listview structure
6052 * [I] nColumn : column index
6053 * [I] lpColumn : column attributes
6054 * [I] isW: if TRUE, the lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6060 static LRESULT LISTVIEW_SetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6061 LPLVCOLUMNW lpColumn, BOOL isW)
6063 COLUMN_INFO *lpColumnInfo;
6064 HDITEMW hdi, hdiget;
6067 if (!lpColumn || nColumn < 0 || nColumn < infoPtr->hdpaColumns->nItemCount) return FALSE;
6069 ZeroMemory(&hdi, sizeof(HDITEMW));
6070 if (lpColumn->mask & LVCF_FMT)
6072 /* format member is valid */
6073 hdi.mask |= HDI_FORMAT;
6075 /* get current format first */
6076 hdiget.mask = HDI_FORMAT;
6077 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6078 /* preserve HDF_STRING if present */
6079 hdi.fmt = hdiget.fmt & HDF_STRING;
6082 if (!(lpColumnInfo = DPA_GetPtr(infoPtr->hdpaColumns, nColumn))) return FALSE;
6084 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6086 /* set header item attributes */
6087 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6088 if (!bResult) return FALSE;
6090 if (!column_fill_info(infoPtr, lpColumnInfo, nColumn, lpColumn)) return FALSE;
6097 * Sets the column order array
6100 * [I] infoPtr : valid pointer to the listview structure
6101 * [I] iCount : number of elements in column order array
6102 * [I] lpiArray : pointer to column order array
6108 static LRESULT LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
6110 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6121 * Sets the width of a column
6124 * [I] infoPtr : valid pointer to the listview structure
6125 * [I] iCol : column index
6126 * [I] cx : column width
6132 static LRESULT LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT iCol, INT cx)
6136 LONG lStyle = infoPtr->dwStyle;
6137 UINT uView = lStyle & LVS_TYPEMASK;
6142 WCHAR text_buffer[DISP_TEXT_SIZE];
6143 INT header_item_count;
6148 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6150 TRACE("(iCol=%d, cx=%d\n", iCol, cx);
6152 /* set column width only if in report or list mode */
6153 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6155 /* take care of invalid cx values */
6156 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
6157 else if (uView == LVS_LIST && cx < 1) return FALSE;
6159 /* resize all columns if in LVS_LIST mode */
6160 if(uView == LVS_LIST)
6162 infoPtr->nItemWidth = cx;
6163 LISTVIEW_InvalidateList(infoPtr);
6167 /* FIXME: update COLUMN_INFO */
6169 /* autosize based on listview items width */
6170 if(cx == LVSCW_AUTOSIZE)
6172 /* set the width of the column to the width of the widest item */
6173 if (iCol == 0 || uView == LVS_LIST)
6176 for(item_index = 0; item_index < infoPtr->nItemCount; item_index++)
6178 nLabelWidth = LISTVIEW_GetLabelWidth(infoPtr, item_index);
6179 cx = (nLabelWidth>cx)?nLabelWidth:cx;
6181 if (infoPtr->himlSmall)
6182 cx += infoPtr->iconSize.cx + IMAGE_PADDING;
6186 lvItem.iSubItem = iCol;
6187 lvItem.mask = LVIF_TEXT;
6188 lvItem.pszText = szDispText;
6189 lvItem.cchTextMax = DISP_TEXT_SIZE;
6191 for(item_index = 0; item_index < infoPtr->nItemCount; item_index++)
6193 lvItem.iItem = item_index;
6194 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6195 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6196 cx = (nLabelWidth>cx)?nLabelWidth:cx;
6199 cx += TRAILING_PADDING;
6200 } /* autosize based on listview header width */
6201 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
6203 header_item_count = infoPtr->hdpaColumns->nItemCount;
6205 /* if iCol is the last column make it fill the remainder of the controls width */
6206 if(iCol == (header_item_count - 1)) {
6209 for(item_index = 0; item_index < (header_item_count - 1); item_index++)
6211 if (LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader))
6212 cx += rcHeader.right - rcHeader.left;
6215 /* retrieve the layout of the header */
6216 GetWindowRect(infoPtr->hwndHeader, &rcHeader);
6218 cx = (rcHeader.right - rcHeader.left) - cx;
6222 /* Despite what the MS docs say, if this is not the last
6223 column, then MS resizes the column to the width of the
6224 largest text string in the column, including headers
6225 and items. This is different from LVSCW_AUTOSIZE in that
6226 LVSCW_AUTOSIZE ignores the header string length.
6229 /* retrieve header font */
6230 header_font = SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0L, 0L);
6232 /* retrieve header text */
6233 hdi.mask = HDI_TEXT;
6234 hdi.cchTextMax = sizeof(text_buffer)/sizeof(text_buffer[0]);
6235 hdi.pszText = text_buffer;
6237 Header_GetItemW(infoPtr->hwndHeader, iCol, (LPARAM)(&hdi));
6239 /* determine the width of the text in the header */
6240 hdc = GetDC(infoPtr->hwndSelf);
6241 old_font = SelectObject(hdc, header_font); /* select the font into hdc */
6243 GetTextExtentPoint32W(hdc, text_buffer, lstrlenW(text_buffer), &size);
6245 SelectObject(hdc, old_font); /* restore the old font */
6246 ReleaseDC(infoPtr->hwndSelf, hdc);
6248 lvItem.iSubItem = iCol;
6249 lvItem.mask = LVIF_TEXT;
6250 lvItem.pszText = szDispText;
6251 lvItem.cchTextMax = DISP_TEXT_SIZE;
6253 for(item_index = 0; item_index < infoPtr->nItemCount; item_index++)
6255 lvItem.iItem = item_index;
6256 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6257 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6258 nLabelWidth += TRAILING_PADDING;
6259 /* While it is possible for subitems to have icons, even MS messes
6260 up the positioning, so I suspect no applications actually use
6262 if (item_index == 0 && infoPtr->himlSmall)
6263 nLabelWidth += infoPtr->iconSize.cx + IMAGE_PADDING;
6264 cx = (nLabelWidth>cx)?nLabelWidth:cx;
6269 /* call header to update the column change */
6270 hdi.mask = HDI_WIDTH;
6273 lret = Header_SetItemW(infoPtr->hwndHeader, (WPARAM)iCol, (LPARAM)&hdi);
6275 LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
6282 * Sets the extended listview style.
6285 * [I] infoPtr : valid pointer to the listview structure
6287 * [I] dwStyle : style
6290 * SUCCESS : previous style
6293 static LRESULT LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwStyle)
6295 DWORD dwOldStyle = infoPtr->dwLvExStyle;
6299 infoPtr->dwLvExStyle = (dwOldStyle & ~dwMask) | (dwStyle & dwMask);
6301 infoPtr->dwLvExStyle = dwStyle;
6308 * Sets the new hot cursor used during hot tracking and hover selection.
6311 * [I] infoPtr : valid pointer to the listview structure
6312 * [I} hCurosr : the new hot cursor handle
6315 * Returns the previous hot cursor
6317 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
6319 HCURSOR oldCursor = infoPtr->hHotCursor;
6321 infoPtr->hHotCursor = hCursor;
6329 * Sets the hot item index.
6332 * [I] infoPtr : valid pointer to the listview structure
6333 * [I] iIndex : index
6336 * SUCCESS : previous hot item index
6337 * FAILURE : -1 (no hot item)
6339 static LRESULT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
6341 INT iOldIndex = infoPtr->nHotItem;
6343 infoPtr->nHotItem = iIndex;
6351 * Sets the amount of time the cursor must hover over an item before it is selected.
6354 * [I] infoPtr : valid pointer to the listview structure
6355 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
6358 * Returns the previous hover time
6360 static LRESULT LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
6362 DWORD oldHoverTime = infoPtr->dwHoverTime;
6364 infoPtr->dwHoverTime = dwHoverTime;
6366 return oldHoverTime;
6371 * Sets spacing for icons of LVS_ICON style.
6374 * [I] infoPtr : valid pointer to the listview structure
6375 * [I] spacing : MAKELONG(cx, cy)
6378 * MAKELONG(oldcx, oldcy)
6380 static LRESULT LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, DWORD spacing)
6382 INT cy = HIWORD(spacing), cx = LOWORD(spacing);
6383 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6384 LONG lStyle = infoPtr->dwStyle;
6385 UINT uView = lStyle & LVS_TYPEMASK;
6387 TRACE("requested=(%d,%d)\n", cx, cy);
6389 /* this is supported only for LVS_ICON style */
6390 if (uView != LVS_ICON) return oldspacing;
6392 /* set to defaults, if instructed to */
6393 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
6394 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
6396 /* if 0 then compute width
6397 * FIXME: Should scan each item and determine max width of
6398 * icon or label, then make that the width */
6400 cx = infoPtr->iconSpacing.cx;
6402 /* if 0 then compute height */
6404 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
6405 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
6408 infoPtr->iconSpacing.cx = cx;
6409 infoPtr->iconSpacing.cy = cy;
6411 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%ld,%ld), ntmH=%d\n",
6412 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
6413 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
6414 infoPtr->ntmHeight);
6416 /* these depend on the iconSpacing */
6417 infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
6418 infoPtr->nItemHeight = LISTVIEW_CalculateMaxHeight(infoPtr);
6423 inline void update_icon_size(HIMAGELIST himl, BOOL small, SIZE *size)
6427 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
6434 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
6435 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
6444 * [I] infoPtr : valid pointer to the listview structure
6445 * [I] nType : image list type
6446 * [I] himl : image list handle
6449 * SUCCESS : old image list
6452 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
6454 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6455 INT oldHeight = infoPtr->nItemHeight;
6456 HIMAGELIST himlOld = 0;
6461 himlOld = infoPtr->himlNormal;
6462 infoPtr->himlNormal = himl;
6463 if (uView == LVS_ICON) update_icon_size(himl, FALSE, &infoPtr->iconSize);
6464 LISTVIEW_SetIconSpacing(infoPtr, 0);
6468 himlOld = infoPtr->himlSmall;
6469 infoPtr->himlSmall = himl;
6470 if (uView != LVS_ICON) update_icon_size(himl, TRUE, &infoPtr->iconSize);
6474 himlOld = infoPtr->himlState;
6475 infoPtr->himlState = himl;
6476 update_icon_size(himl, TRUE, &infoPtr->iconStateSize);
6477 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
6481 ERR("Unknown icon type=%d\n", nType);
6485 infoPtr->nItemHeight = LISTVIEW_CalculateMaxHeight(infoPtr);
6486 if (infoPtr->nItemHeight != oldHeight)
6487 LISTVIEW_UpdateScroll(infoPtr);
6494 * Preallocates memory (does *not* set the actual count of items !)
6497 * [I] infoPtr : valid pointer to the listview structure
6498 * [I] nItems : item count (projected number of items to allocate)
6499 * [I] dwFlags : update flags
6505 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
6507 TRACE("(nItems=%d, dwFlags=%lx)\n", nItems, dwFlags);
6509 if (infoPtr->dwStyle & LVS_OWNERDATA)
6511 int precount,topvisible;
6513 TRACE("LVS_OWNERDATA is set!\n");
6514 if (dwFlags & (LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL))
6515 FIXME("flags %s %s not implemented\n",
6516 (dwFlags & LVSICF_NOINVALIDATEALL) ? "LVSICF_NOINVALIDATEALL"
6518 (dwFlags & LVSICF_NOSCROLL) ? "LVSICF_NOSCROLL" : "");
6520 LISTVIEW_DeselectAll(infoPtr);
6522 precount = infoPtr->nItemCount;
6523 topvisible = LISTVIEW_GetTopIndex(infoPtr) +
6524 LISTVIEW_GetCountPerColumn(infoPtr) + 1;
6526 infoPtr->nItemCount = nItems;
6527 infoPtr->nItemWidth = max(LISTVIEW_CalculateMaxWidth(infoPtr),
6528 DEFAULT_COLUMN_WIDTH);
6530 LISTVIEW_UpdateSize(infoPtr);
6531 LISTVIEW_UpdateScroll(infoPtr);
6533 if (min(precount,infoPtr->nItemCount) < topvisible)
6534 LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
6538 /* According to MSDN for non-LVS_OWNERDATA this is just
6539 * a performance issue. The control allocates its internal
6540 * data structures for the number of items specified. It
6541 * cuts down on the number of memory allocations. Therefore
6542 * we will just issue a WARN here
6544 WARN("for non-ownerdata performance option not implemented.\n");
6552 * Sets the position of an item.
6555 * [I] infoPtr : valid pointer to the listview structure
6556 * [I] nItem : item index
6557 * [I] pt : coordinate
6563 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
6565 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6568 TRACE("(nItem=%d, &pt=%s\n", nItem, debugpoint(&pt));
6570 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
6571 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
6573 /* This point value seems to be an undocumented feature.
6574 * The best guess is that it means either at the origin,
6575 * or at true beginning of the list. I will assume the origin. */
6576 if ((pt.x == -1) && (pt.y == -1))
6577 LISTVIEW_GetOrigin(infoPtr, &pt);
6578 else if (uView == LVS_ICON)
6580 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
6581 pt.y -= ICON_TOP_PADDING;
6584 /* save the old position */
6585 old.x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
6586 old.y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
6588 /* Is the position changing? */
6589 if (pt.x == old.x && pt.y == old.y) return TRUE;
6591 /* FIXME: shouldn't we invalidate, as the item moved? */
6593 /* Allocating a POINTER for every item is too resource intensive,
6594 * so we'll keep the (x,y) in different arrays */
6595 if (DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)pt.x) &&
6596 DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)pt.y) )
6599 ERR("We should never fail here (nItem=%d, pt=%s), please report.\n",
6600 nItem, debugpoint(&pt));
6606 * Sets the state of one or many items.
6609 * [I] infoPtr : valid pointer to the listview structure
6610 * [I] nItem : item index
6611 * [I] lpLVItem : item or subitem info
6617 static LRESULT LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem)
6619 BOOL bResult = TRUE;
6622 lvItem.iItem = nItem;
6623 lvItem.iSubItem = 0;
6624 lvItem.mask = LVIF_STATE;
6625 lvItem.state = lpLVItem->state;
6626 lvItem.stateMask = lpLVItem->stateMask;
6627 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
6631 /* apply to all items */
6632 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6633 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
6636 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
6643 * Sets the text of an item or subitem.
6646 * [I] hwnd : window handle
6647 * [I] nItem : item index
6648 * [I] lpLVItem : item or subitem info
6649 * [I] isW : TRUE if input is Unicode
6655 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
6659 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
6661 lvItem.iItem = nItem;
6662 lvItem.iSubItem = lpLVItem->iSubItem;
6663 lvItem.mask = LVIF_TEXT;
6664 lvItem.pszText = lpLVItem->pszText;
6665 lvItem.cchTextMax = lpLVItem->cchTextMax;
6667 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
6669 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
6674 * Set item index that marks the start of a multiple selection.
6677 * [I] infoPtr : valid pointer to the listview structure
6678 * [I] nIndex : index
6681 * Index number or -1 if there is no selection mark.
6683 static LRESULT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
6685 INT nOldIndex = infoPtr->nSelectionMark;
6687 TRACE("(nIndex=%d)\n", nIndex);
6689 infoPtr->nSelectionMark = nIndex;
6696 * Sets the text background color.
6699 * [I] infoPtr : valid pointer to the listview structure
6700 * [I] clrTextBk : text background color
6706 static LRESULT LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
6708 TRACE("(clrTextBk=%lx)\n", clrTextBk);
6710 if (infoPtr->clrTextBk != clrTextBk)
6712 infoPtr->clrTextBk = clrTextBk;
6713 LISTVIEW_InvalidateList(infoPtr);
6721 * Sets the text foreground color.
6724 * [I] infoPtr : valid pointer to the listview structure
6725 * [I] clrText : text color
6731 static LRESULT LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
6733 TRACE("(clrText=%lx)\n", clrText);
6735 if (infoPtr->clrText != clrText)
6737 infoPtr->clrText = clrText;
6738 LISTVIEW_InvalidateList(infoPtr);
6744 /* LISTVIEW_SetToolTips */
6745 /* LISTVIEW_SetUnicodeFormat */
6746 /* LISTVIEW_SetWorkAreas */
6750 * Callback internally used by LISTVIEW_SortItems()
6753 * [I] first : pointer to first LISTVIEW_ITEM to compare
6754 * [I] second : pointer to second LISTVIEW_ITEM to compare
6755 * [I] lParam : HWND of control
6758 * if first comes before second : negative
6759 * if first comes after second : positive
6760 * if first and second are equivalent : zero
6762 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
6764 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW((HWND)lParam, 0);
6765 LISTVIEW_ITEM* lv_first = (LISTVIEW_ITEM*) DPA_GetPtr( (HDPA)first, 0 );
6766 LISTVIEW_ITEM* lv_second = (LISTVIEW_ITEM*) DPA_GetPtr( (HDPA)second, 0 );
6768 /* Forward the call to the client defined callback */
6769 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
6774 * Sorts the listview items.
6777 * [I] infoPtr : valid pointer to the listview structure
6778 * [I] pfnCompare : application-defined value
6779 * [I] lParamSort : pointer to comparision callback
6785 static LRESULT LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
6787 UINT lStyle = infoPtr->dwStyle;
6789 LISTVIEW_ITEM *lpItem;
6790 LPVOID selectionMarkItem;
6794 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
6796 if (lStyle & LVS_OWNERDATA) return FALSE;
6798 if (!infoPtr->hdpaItems) return FALSE;
6800 /* if there are 0 or 1 items, there is no need to sort */
6801 if (infoPtr->nItemCount < 2) return TRUE;
6803 if (infoPtr->nFocusedItem >= 0)
6805 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
6806 lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0);
6807 if (lpItem) lpItem->state |= LVIS_FOCUSED;
6809 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
6810 /* clear the lpItem->state for non-selected ones */
6811 /* remove the selection ranges */
6813 infoPtr->pfnCompare = pfnCompare;
6814 infoPtr->lParamSort = lParamSort;
6815 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr->hwndSelf);
6817 /* Adjust selections and indices so that they are the way they should
6818 * be after the sort (otherwise, the list items move around, but
6819 * whatever is at the item's previous original position will be
6822 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
6823 for (i=0; i < infoPtr->nItemCount; i++)
6825 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
6826 lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0);
6828 if (lpItem->state & LVIS_SELECTED)
6830 item.state = LVIS_SELECTED;
6831 item.stateMask = LVIS_SELECTED;
6832 LISTVIEW_SetItemState(infoPtr, i, &item);
6834 if (lpItem->state & LVIS_FOCUSED)
6836 infoPtr->nFocusedItem = i;
6837 lpItem->state &= ~LVIS_FOCUSED;
6840 if (selectionMarkItem != NULL)
6841 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
6842 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
6844 /* align the items */
6845 LISTVIEW_AlignTop(infoPtr);
6847 /* refresh the display */
6848 LISTVIEW_InvalidateList(infoPtr); /* FIXME: display should not change for [SMALL]ICON view */
6855 * Updates an items or rearranges the listview control.
6858 * [I] infoPtr : valid pointer to the listview structure
6859 * [I] nItem : item index
6865 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
6867 LONG lStyle = infoPtr->dwStyle;
6868 UINT uView = lStyle & LVS_TYPEMASK;
6870 TRACE("(nItem=%d)\n", nItem);
6872 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
6874 /* rearrange with default alignment style */
6875 if ((lStyle & LVS_AUTOARRANGE) && ((uView == LVS_ICON) ||(uView == LVS_SMALLICON)))
6876 LISTVIEW_Arrange(infoPtr, 0);
6878 LISTVIEW_InvalidateItem(infoPtr, nItem);
6886 * Creates the listview control.
6889 * [I] hwnd : window handle
6890 * [I] lpcs : the create parameters
6896 static LRESULT LISTVIEW_Create(HWND hwnd, LPCREATESTRUCTW lpcs)
6898 LISTVIEW_INFO *infoPtr;
6899 UINT uView = lpcs->style & LVS_TYPEMASK;
6902 TRACE("(lpcs=%p)\n", lpcs);
6904 /* initialize info pointer */
6905 infoPtr = (LISTVIEW_INFO *)COMCTL32_Alloc(sizeof(LISTVIEW_INFO));
6906 if (!infoPtr) return -1;
6908 SetWindowLongW(hwnd, 0, (LONG)infoPtr);
6910 infoPtr->hwndSelf = hwnd;
6911 infoPtr->dwStyle = lpcs->style;
6912 /* determine the type of structures to use */
6913 infoPtr->notifyFormat = SendMessageW(GetParent(infoPtr->hwndSelf), WM_NOTIFYFORMAT,
6914 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
6916 /* initialize color information */
6917 infoPtr->clrBk = CLR_NONE;
6918 infoPtr->clrText = comctl32_color.clrWindowText;
6919 infoPtr->clrTextBk = CLR_DEFAULT;
6920 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
6922 /* set default values */
6923 infoPtr->nFocusedItem = -1;
6924 infoPtr->nSelectionMark = -1;
6925 infoPtr->nHotItem = -1;
6926 infoPtr->bRedraw = TRUE;
6927 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
6928 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
6929 infoPtr->nEditLabelItem = -1;
6931 /* get default font (icon title) */
6932 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
6933 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
6934 infoPtr->hFont = infoPtr->hDefaultFont;
6935 LISTVIEW_SaveTextMetrics(infoPtr);
6938 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, (LPCWSTR)NULL,
6939 WS_CHILD | HDS_HORZ | (DWORD)((LVS_NOSORTHEADER & lpcs->style)?0:HDS_BUTTONS),
6940 0, 0, 0, 0, hwnd, (HMENU)0,
6941 lpcs->hInstance, NULL);
6943 /* set header unicode format */
6944 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)NULL);
6946 /* set header font */
6947 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
6949 infoPtr->hdpaColumns = DPA_Create(10);
6951 if (uView == LVS_ICON)
6953 infoPtr->iconSize.cx = GetSystemMetrics(SM_CXICON);
6954 infoPtr->iconSize.cy = GetSystemMetrics(SM_CYICON);
6956 else if (uView == LVS_REPORT)
6958 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
6960 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
6964 /* set HDS_HIDDEN flag to hide the header bar */
6965 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE,
6966 GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE) | HDS_HIDDEN);
6970 infoPtr->iconSize.cx = GetSystemMetrics(SM_CXSMICON);
6971 infoPtr->iconSize.cy = GetSystemMetrics(SM_CYSMICON);
6975 infoPtr->iconSize.cx = GetSystemMetrics(SM_CXSMICON);
6976 infoPtr->iconSize.cy = GetSystemMetrics(SM_CYSMICON);
6979 infoPtr->iconStateSize.cx = GetSystemMetrics(SM_CXSMICON);
6980 infoPtr->iconStateSize.cy = GetSystemMetrics(SM_CYSMICON);
6982 /* display unsupported listview window styles */
6983 LISTVIEW_UnsupportedStyles(lpcs->style);
6985 /* allocate memory for the data structure */
6986 infoPtr->hdpaItems = DPA_Create(10);
6987 infoPtr->hdpaPosX = DPA_Create(10);
6988 infoPtr->hdpaPosY = DPA_Create(10);
6990 /* allocate memory for the selection ranges */
6991 infoPtr->selectionRanges = ranges_create(10);
6993 /* initialize size of items */
6994 infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
6995 infoPtr->nItemHeight = LISTVIEW_CalculateMaxHeight(infoPtr);
6997 /* initialize the hover time to -1(indicating the default system hover time) */
6998 infoPtr->dwHoverTime = -1;
7005 * Erases the background of the listview control.
7008 * [I] infoPtr : valid pointer to the listview structure
7009 * [I] hdc : device context handle
7015 static inline BOOL LISTVIEW_EraseBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc)
7019 TRACE("(hdc=%x)\n", hdc);
7021 if (!GetClipBox(hdc, &rc)) return FALSE;
7023 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
7029 * Helper function for LISTVIEW_[HV]Scroll *only*.
7030 * Performs vertical/horizontal scrolling by a give amount.
7033 * [I] infoPtr : valid pointer to the listview structure
7034 * [I] dx : amount of horizontal scroll
7035 * [I] dy : amount of vertical scroll
7037 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7039 /* now we can scroll the list */
7040 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
7041 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
7042 /* if we have focus, adjust rect */
7043 OffsetRect(&infoPtr->rcFocus, dx, dy);
7044 UpdateWindow(infoPtr->hwndSelf);
7049 * Performs vertical scrolling.
7052 * [I] infoPtr : valid pointer to the listview structure
7053 * [I] nScrollCode : scroll code
7054 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7055 * [I] hScrollWnd : scrollbar control window handle
7061 * SB_LINEUP/SB_LINEDOWN:
7062 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
7063 * for LVS_REPORT is 1 line
7064 * for LVS_LIST cannot occur
7067 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7068 INT nScrollDiff, HWND hScrollWnd)
7070 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7071 INT nOldScrollPos, nNewScrollPos;
7072 SCROLLINFO scrollInfo;
7075 TRACE("(nScrollCode=%d, nScrollDiff=%d)\n", nScrollCode, nScrollDiff);
7077 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7079 scrollInfo.cbSize = sizeof(SCROLLINFO);
7080 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7082 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
7084 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
7086 nOldScrollPos = scrollInfo.nPos;
7087 switch (nScrollCode)
7093 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
7097 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
7101 nScrollDiff = -scrollInfo.nPage;
7105 nScrollDiff = scrollInfo.nPage;
7108 case SB_THUMBPOSITION:
7110 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7117 /* quit right away if pos isn't changing */
7118 if (nScrollDiff == 0) return 0;
7120 /* calculate new position, and handle overflows */
7121 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7122 if (nScrollDiff > 0) {
7123 if (nNewScrollPos < nOldScrollPos ||
7124 nNewScrollPos > scrollInfo.nMax)
7125 nNewScrollPos = scrollInfo.nMax;
7127 if (nNewScrollPos > nOldScrollPos ||
7128 nNewScrollPos < scrollInfo.nMin)
7129 nNewScrollPos = scrollInfo.nMin;
7132 /* set the new position, and reread in case it changed */
7133 scrollInfo.fMask = SIF_POS;
7134 scrollInfo.nPos = nNewScrollPos;
7135 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
7137 /* carry on only if it really changed */
7138 if (nNewScrollPos == nOldScrollPos) return 0;
7140 /* now adjust to client coordinates */
7141 nScrollDiff = nOldScrollPos - nNewScrollPos;
7142 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
7144 /* and scroll the window */
7145 scroll_list(infoPtr, 0, nScrollDiff);
7152 * Performs horizontal scrolling.
7155 * [I] infoPtr : valid pointer to the listview structure
7156 * [I] nScrollCode : scroll code
7157 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7158 * [I] hScrollWnd : scrollbar control window handle
7164 * SB_LINELEFT/SB_LINERIGHT:
7165 * for LVS_ICON, LVS_SMALLICON 1 pixel
7166 * for LVS_REPORT is 1 pixel
7167 * for LVS_LIST is 1 column --> which is a 1 because the
7168 * scroll is based on columns not pixels
7171 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7172 INT nScrollDiff, HWND hScrollWnd)
7174 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7175 INT nOldScrollPos, nNewScrollPos;
7176 SCROLLINFO scrollInfo;
7178 TRACE("(nScrollCode=%d, nScrollDiff=%d)\n", nScrollCode, nScrollDiff);
7180 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7182 scrollInfo.cbSize = sizeof(SCROLLINFO);
7183 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7185 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
7187 nOldScrollPos = scrollInfo.nPos;
7189 switch (nScrollCode)
7203 nScrollDiff = -scrollInfo.nPage;
7207 nScrollDiff = scrollInfo.nPage;
7210 case SB_THUMBPOSITION:
7212 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7219 /* quit right away if pos isn't changing */
7220 if (nScrollDiff == 0) return 0;
7222 /* calculate new position, and handle overflows */
7223 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7224 if (nScrollDiff > 0) {
7225 if (nNewScrollPos < nOldScrollPos ||
7226 nNewScrollPos > scrollInfo.nMax)
7227 nNewScrollPos = scrollInfo.nMax;
7229 if (nNewScrollPos > nOldScrollPos ||
7230 nNewScrollPos < scrollInfo.nMin)
7231 nNewScrollPos = scrollInfo.nMin;
7234 /* set the new position, and reread in case it changed */
7235 scrollInfo.fMask = SIF_POS;
7236 scrollInfo.nPos = nNewScrollPos;
7237 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
7239 /* carry on only if it really changed */
7240 if (nNewScrollPos == nOldScrollPos) return 0;
7242 if(uView == LVS_REPORT)
7243 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
7245 /* now adjust to client coordinates */
7246 nScrollDiff = nOldScrollPos - nNewScrollPos;
7247 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
7249 /* and scroll the window */
7250 scroll_list(infoPtr, nScrollDiff, 0);
7255 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
7257 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7258 INT gcWheelDelta = 0;
7259 UINT pulScrollLines = 3;
7260 SCROLLINFO scrollInfo;
7262 TRACE("(wheelDelta=%d)\n", wheelDelta);
7264 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
7265 gcWheelDelta -= wheelDelta;
7267 scrollInfo.cbSize = sizeof(SCROLLINFO);
7268 scrollInfo.fMask = SIF_POS;
7275 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
7276 * should be fixed in the future.
7278 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
7279 LISTVIEW_VScroll(infoPtr, SB_THUMBPOSITION,
7280 scrollInfo.nPos + (gcWheelDelta < 0) ?
7281 LISTVIEW_SCROLL_ICON_LINE_SIZE :
7282 -LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
7286 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
7288 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
7290 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
7291 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
7292 LISTVIEW_VScroll(infoPtr, SB_THUMBPOSITION, scrollInfo.nPos + cLineScroll, 0);
7298 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
7309 * [I] infoPtr : valid pointer to the listview structure
7310 * [I] nVirtualKey : virtual key
7311 * [I] lKeyData : key data
7316 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
7318 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7320 NMLVKEYDOWN nmKeyDown;
7322 TRACE("(nVirtualKey=%d, lKeyData=%ld)\n", nVirtualKey, lKeyData);
7324 /* send LVN_KEYDOWN notification */
7325 nmKeyDown.wVKey = nVirtualKey;
7326 nmKeyDown.flags = 0;
7327 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
7329 switch (nVirtualKey)
7332 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
7334 notify(infoPtr, NM_RETURN);
7335 notify(infoPtr, LVN_ITEMACTIVATE);
7340 if (infoPtr->nItemCount > 0)
7345 if (infoPtr->nItemCount > 0)
7346 nItem = infoPtr->nItemCount - 1;
7350 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
7354 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
7358 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
7362 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
7366 if (uView == LVS_REPORT)
7367 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr);
7369 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
7370 * LISTVIEW_GetCountPerRow(infoPtr);
7371 if(nItem < 0) nItem = 0;
7375 if (uView == LVS_REPORT)
7376 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr);
7378 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
7379 * LISTVIEW_GetCountPerRow(infoPtr);
7380 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
7384 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem))
7385 LISTVIEW_KeySelection(infoPtr, nItem);
7395 * [I] infoPtr : valid pointer to the listview structure
7400 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
7404 /* if we did not have the focus, there's nothing to do */
7405 if (!infoPtr->bFocus) return 0;
7407 /* send NM_KILLFOCUS notification */
7408 notify(infoPtr, NM_KILLFOCUS);
7410 /* if we have a focus rectagle, get rid of it */
7411 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
7413 /* set window focus flag */
7414 infoPtr->bFocus = FALSE;
7416 /* invalidate the selected items before reseting focus flag */
7417 LISTVIEW_InvalidateSelectedItems(infoPtr);
7424 * Processes double click messages (left mouse button).
7427 * [I] infoPtr : valid pointer to the listview structure
7428 * [I] wKey : key flag
7429 * [I] pts : mouse coordinate
7434 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7436 LVHITTESTINFO htInfo;
7438 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7440 /* send NM_RELEASEDCAPTURE notification */
7441 notify(infoPtr, NM_RELEASEDCAPTURE);
7443 htInfo.pt.x = pts.x;
7444 htInfo.pt.y = pts.y;
7446 /* send NM_DBLCLK notification */
7447 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
7448 notify_click(infoPtr, NM_DBLCLK, &htInfo);
7450 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
7451 if(htInfo.iItem != -1) notify(infoPtr, LVN_ITEMACTIVATE);
7458 * Processes mouse down messages (left mouse button).
7461 * [I] infoPtr : valid pointer to the listview structure
7462 * [I] wKey : key flag
7463 * [I] pts : mouse coordinate
7468 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7470 LVHITTESTINFO lvHitTestInfo;
7471 LONG lStyle = infoPtr->dwStyle;
7472 static BOOL bGroupSelect = TRUE;
7473 POINT pt = { pts.x, pts.y };
7476 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7478 /* send NM_RELEASEDCAPTURE notification */
7479 notify(infoPtr, NM_RELEASEDCAPTURE);
7481 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
7483 /* set left button down flag */
7484 infoPtr->bLButtonDown = TRUE;
7486 lvHitTestInfo.pt.x = pts.x;
7487 lvHitTestInfo.pt.y = pts.y;
7489 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
7490 TRACE("at %s, nItem=%d\n", debugpoint(&pt), nItem);
7491 infoPtr->nEditLabelItem = -1;
7492 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
7494 if (lStyle & LVS_SINGLESEL)
7496 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
7497 infoPtr->nEditLabelItem = nItem;
7499 LISTVIEW_SetSelection(infoPtr, nItem);
7503 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
7506 LISTVIEW_AddGroupSelection(infoPtr, nItem);
7511 item.state = LVIS_SELECTED | LVIS_FOCUSED;
7512 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
7514 LISTVIEW_SetItemState(infoPtr,nItem,&item);
7515 infoPtr->nSelectionMark = nItem;
7518 else if (wKey & MK_CONTROL)
7522 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
7524 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
7525 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
7526 LISTVIEW_SetItemState(infoPtr, nItem, &item);
7527 infoPtr->nSelectionMark = nItem;
7529 else if (wKey & MK_SHIFT)
7531 LISTVIEW_SetGroupSelection(infoPtr, nItem);
7535 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
7536 infoPtr->nEditLabelItem = nItem;
7538 /* set selection (clears other pre-existing selections) */
7539 LISTVIEW_SetSelection(infoPtr, nItem);
7545 /* remove all selections */
7546 LISTVIEW_DeselectAll(infoPtr);
7554 * Processes mouse up messages (left mouse button).
7557 * [I] infoPtr : valid pointer to the listview structure
7558 * [I] wKey : key flag
7559 * [I] pts : mouse coordinate
7564 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7566 LVHITTESTINFO lvHitTestInfo;
7568 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7570 if (!infoPtr->bLButtonDown) return 0;
7572 lvHitTestInfo.pt.x = pts.x;
7573 lvHitTestInfo.pt.y = pts.y;
7575 /* send NM_CLICK notification */
7576 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
7577 notify_click(infoPtr, NM_CLICK, &lvHitTestInfo);
7579 /* set left button flag */
7580 infoPtr->bLButtonDown = FALSE;
7582 /* if we clicked on a selected item, edit the label */
7583 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
7584 LISTVIEW_EditLabelT(infoPtr, lvHitTestInfo.iItem, TRUE);
7591 * Destroys the listview control (called after WM_DESTROY).
7594 * [I] infoPtr : valid pointer to the listview structure
7599 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
7601 LONG lStyle = infoPtr->dwStyle;
7605 /* delete all items */
7606 LISTVIEW_DeleteAllItems(infoPtr);
7608 /* destroy data structure */
7609 DPA_Destroy(infoPtr->hdpaItems);
7610 ranges_destroy(infoPtr->selectionRanges);
7612 /* destroy image lists */
7613 if (!(lStyle & LVS_SHAREIMAGELISTS))
7615 if (infoPtr->himlNormal)
7616 ImageList_Destroy(infoPtr->himlNormal);
7617 if (infoPtr->himlSmall)
7618 ImageList_Destroy(infoPtr->himlSmall);
7619 if (infoPtr->himlState)
7620 ImageList_Destroy(infoPtr->himlState);
7623 /* destroy font, bkgnd brush */
7625 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
7626 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
7628 /* free listview info pointer*/
7629 COMCTL32_Free(infoPtr);
7631 SetWindowLongW(infoPtr->hwndSelf, 0, 0);
7637 * Handles notifications from children.
7640 * [I] infoPtr : valid pointer to the listview structure
7641 * [I] nCtrlId : control identifier
7642 * [I] lpnmh : notification information
7647 static LRESULT LISTVIEW_Notify(LISTVIEW_INFO *infoPtr, INT nCtrlId, LPNMHDR lpnmh)
7649 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7651 TRACE("(nCtrlId=%d, lpnmh=%p)\n", nCtrlId, lpnmh);
7653 /* handle notification from header control */
7654 if (lpnmh->hwndFrom == infoPtr->hwndHeader)
7656 LPNMHEADERW lphnm = (LPNMHEADERW)lpnmh;
7658 if (lpnmh->code == HDN_TRACKW || lpnmh->code == HDN_TRACKA)
7660 COLUMN_INFO *lpColumnInfo;
7664 if (!(lpColumnInfo = DPA_GetPtr(infoPtr->hdpaColumns, lphnm->iItem))) return 0;
7665 if (!(lphnm->pitem->mask & HDI_WIDTH)) return 0;
7667 /* determine how much we change since the last know position */
7668 dx = lphnm->pitem->cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
7670 /* ajust the column being tracked */
7671 lpColumnInfo->rcHeader.right += dx;
7673 /* compute the rectangle for the tracked column */
7674 rcCol.left = lpColumnInfo->rcHeader.left;
7675 rcCol.top = infoPtr->rcList.top;
7676 rcCol.right = lpColumnInfo->rcHeader.right;
7677 rcCol.bottom = infoPtr->rcList.bottom;
7679 LISTVIEW_ScrollColumns(infoPtr, lphnm->iItem + 1, dx);
7680 if (uView == LVS_REPORT) LISTVIEW_InvalidateRect(infoPtr, &rcCol);
7682 else if(lpnmh->code == HDN_ITEMCLICKW || lpnmh->code == HDN_ITEMCLICKA)
7684 /* Handle sorting by Header Column */
7687 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
7689 nmlv.iSubItem = lphnm->iItem;
7690 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
7699 * Determines the type of structure to use.
7702 * [I] infoPtr : valid pointer to the listview structureof the sender
7703 * [I] hwndFrom : listview window handle
7704 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
7709 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
7711 TRACE("(hwndFrom=%x, nCommand=%d)\n", hwndFrom, nCommand);
7713 if (nCommand == NF_REQUERY)
7714 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT,
7715 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
7721 * Paints/Repaints the listview control.
7724 * [I] infoPtr : valid pointer to the listview structure
7725 * [I] hdc : device context handle
7730 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
7732 TRACE("(hdc=%x)\n", hdc);
7735 LISTVIEW_Refresh(infoPtr, hdc);
7740 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
7742 if (ps.fErase) LISTVIEW_FillBkgnd(infoPtr, hdc, &ps.rcPaint);
7743 LISTVIEW_Refresh(infoPtr, hdc);
7744 EndPaint(infoPtr->hwndSelf, &ps);
7752 * Processes double click messages (right mouse button).
7755 * [I] infoPtr : valid pointer to the listview structure
7756 * [I] wKey : key flag
7757 * [I] pts : mouse coordinate
7762 static LRESULT LISTVIEW_RButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7764 LVHITTESTINFO lvHitTestInfo;
7766 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
7768 /* send NM_RELEASEDCAPTURE notification */
7769 notify(infoPtr, NM_RELEASEDCAPTURE);
7771 /* send NM_RDBLCLK notification */
7772 lvHitTestInfo.pt.x = pts.x;
7773 lvHitTestInfo.pt.y = pts.y;
7774 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
7775 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
7782 * Processes mouse down messages (right mouse button).
7785 * [I] infoPtr : valid pointer to the listview structure
7786 * [I] wKey : key flag
7787 * [I] pts : mouse coordinate
7792 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7794 LVHITTESTINFO lvHitTestInfo;
7797 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
7799 /* send NM_RELEASEDCAPTURE notification */
7800 notify(infoPtr, NM_RELEASEDCAPTURE);
7802 /* make sure the listview control window has the focus */
7803 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
7805 /* set right button down flag */
7806 infoPtr->bRButtonDown = TRUE;
7808 /* determine the index of the selected item */
7809 lvHitTestInfo.pt.x = pts.x;
7810 lvHitTestInfo.pt.y = pts.y;
7811 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
7813 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
7815 LISTVIEW_SetItemFocus(infoPtr, nItem);
7816 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
7817 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
7818 LISTVIEW_SetSelection(infoPtr, nItem);
7822 LISTVIEW_DeselectAll(infoPtr);
7830 * Processes mouse up messages (right mouse button).
7833 * [I] infoPtr : valid pointer to the listview structure
7834 * [I] wKey : key flag
7835 * [I] pts : mouse coordinate
7840 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7842 LVHITTESTINFO lvHitTestInfo;
7845 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
7847 if (!infoPtr->bRButtonDown) return 0;
7849 /* set button flag */
7850 infoPtr->bRButtonDown = FALSE;
7852 /* Send NM_RClICK notification */
7853 lvHitTestInfo.pt.x = pts.x;
7854 lvHitTestInfo.pt.y = pts.y;
7855 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
7856 notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo);
7858 /* Change to screen coordinate for WM_CONTEXTMENU */
7859 pt = lvHitTestInfo.pt;
7860 ClientToScreen(infoPtr->hwndSelf, &pt);
7862 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
7863 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
7864 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
7875 * [I] infoPtr : valid pointer to the listview structure
7876 * [I] hwnd : window handle of window containing the cursor
7877 * [I] nHittest : hit-test code
7878 * [I] wMouseMsg : ideintifier of the mouse message
7881 * TRUE if cursor is set
7884 static BOOL LISTVIEW_SetCursor(LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
7886 LVHITTESTINFO lvHitTestInfo;
7888 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
7890 if(!infoPtr->hHotCursor) return FALSE;
7892 GetCursorPos(&lvHitTestInfo.pt);
7893 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
7895 SetCursor(infoPtr->hHotCursor);
7905 * [I] infoPtr : valid pointer to the listview structure
7906 * [I] hwndLoseFocus : handle of previously focused window
7911 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
7913 TRACE("(hwndLoseFocus=%x)\n", hwndLoseFocus);
7915 /* if we have the focus already, there's nothing to do */
7916 if (infoPtr->bFocus) return 0;
7918 /* send NM_SETFOCUS notification */
7919 notify(infoPtr, NM_SETFOCUS);
7921 /* set window focus flag */
7922 infoPtr->bFocus = TRUE;
7924 /* put the focus rect back on */
7925 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
7927 /* redraw all visible selected items */
7928 LISTVIEW_InvalidateSelectedItems(infoPtr);
7938 * [I] infoPtr : valid pointer to the listview structure
7939 * [I] fRedraw : font handle
7940 * [I] fRedraw : redraw flag
7945 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
7947 HFONT oldFont = infoPtr->hFont;
7949 TRACE("(hfont=%x,redraw=%hu)\n", hFont, fRedraw);
7951 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
7952 if (infoPtr->hFont == oldFont) return 0;
7954 LISTVIEW_SaveTextMetrics(infoPtr);
7956 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
7957 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
7959 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
7966 * Message handling for WM_SETREDRAW.
7967 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
7970 * [I] infoPtr : valid pointer to the listview structure
7971 * [I] bRedraw: state of redraw flag
7974 * DefWinProc return value
7976 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
7978 infoPtr->bRedraw = bRedraw;
7980 RedrawWindow(infoPtr->hwndSelf, NULL, 0,
7981 RDW_INVALIDATE | RDW_FRAME | RDW_ERASE | RDW_ALLCHILDREN | RDW_ERASENOW);
7987 * Resizes the listview control. This function processes WM_SIZE
7988 * messages. At this time, the width and height are not used.
7991 * [I] infoPtr : valid pointer to the listview structure
7992 * [I] Width : new width
7993 * [I] Height : new height
7998 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
8000 LONG lStyle = infoPtr->dwStyle;
8001 UINT uView = lStyle & LVS_TYPEMASK;
8003 TRACE("(width=%d, height=%d)\n", Width, Height);
8005 if (LISTVIEW_UpdateSize(infoPtr))
8007 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
8009 if (lStyle & LVS_ALIGNLEFT)
8010 LISTVIEW_AlignLeft(infoPtr);
8012 LISTVIEW_AlignTop(infoPtr);
8015 LISTVIEW_UpdateScroll(infoPtr);
8017 LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
8025 * Sets the size information.
8028 * [I] infoPtr : valid pointer to the listview structure
8031 * Zero if no size change
8034 static BOOL LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
8036 LONG lStyle = infoPtr->dwStyle;
8037 UINT uView = lStyle & LVS_TYPEMASK;
8041 GetClientRect(infoPtr->hwndSelf, &rcList);
8042 CopyRect(&rcOld,&(infoPtr->rcList));
8043 infoPtr->rcList.left = 0;
8044 infoPtr->rcList.right = max(rcList.right - rcList.left, 1);
8045 infoPtr->rcList.top = 0;
8046 infoPtr->rcList.bottom = max(rcList.bottom - rcList.top, 1);
8048 if (uView == LVS_LIST)
8050 /* Apparently the "LIST" style is supposed to have the same
8051 * number of items in a column even if there is no scroll bar.
8052 * Since if a scroll bar already exists then the bottom is already
8053 * reduced, only reduce if the scroll bar does not currently exist.
8054 * The "2" is there to mimic the native control. I think it may be
8055 * related to either padding or edges. (GLA 7/2002)
8057 if (!(lStyle & WS_HSCROLL))
8059 INT nHScrollHeight = GetSystemMetrics(SM_CYHSCROLL);
8060 if (infoPtr->rcList.bottom > nHScrollHeight)
8061 infoPtr->rcList.bottom -= (nHScrollHeight + 2);
8065 if (infoPtr->rcList.bottom > 2)
8066 infoPtr->rcList.bottom -= 2;
8069 else if (uView == LVS_REPORT)
8076 Header_Layout(infoPtr->hwndHeader, &hl);
8078 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8080 if (!(LVS_NOCOLUMNHEADER & lStyle))
8081 infoPtr->rcList.top = max(wp.cy, 0);
8083 return (EqualRect(&rcOld,&(infoPtr->rcList)));
8088 * Processes WM_STYLECHANGED messages.
8091 * [I] infoPtr : valid pointer to the listview structure
8092 * [I] wStyleType : window style type (normal or extended)
8093 * [I] lpss : window style information
8098 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
8101 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
8102 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
8104 TRACE("(styletype=%x, styleOld=0x%08lx, styleNew=0x%08lx)\n",
8105 wStyleType, lpss->styleOld, lpss->styleNew);
8107 if (wStyleType != GWL_STYLE) return 0;
8109 /* FIXME: if LVS_NOSORTHEADER changed, update header */
8110 /* what if LVS_OWNERDATA changed? */
8111 /* or LVS_SINGLESEL */
8112 /* or LVS_SORT{AS,DES}CENDING */
8114 infoPtr->dwStyle = lpss->styleNew;
8116 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
8117 ((lpss->styleNew & WS_HSCROLL) == 0))
8118 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
8120 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
8121 ((lpss->styleNew & WS_VSCROLL) == 0))
8122 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
8124 if (uNewView != uOldView)
8126 SIZE oldIconSize = infoPtr->iconSize;
8129 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8130 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8132 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
8133 SetRectEmpty(&infoPtr->rcFocus);
8135 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
8136 update_icon_size(himl, uNewView != LVS_ICON, &infoPtr->iconSize);
8138 if (uNewView == LVS_ICON)
8140 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
8142 TRACE("icon old size=(%ld,%ld), new size=(%ld,%ld)\n",
8143 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
8144 LISTVIEW_SetIconSpacing(infoPtr, 0);
8147 else if (uNewView == LVS_REPORT)
8152 hl.prc = &infoPtr->rcList;
8154 Header_Layout(infoPtr->hwndHeader, &hl);
8155 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8158 infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
8159 infoPtr->nItemHeight = LISTVIEW_CalculateMaxHeight(infoPtr);
8162 if (uNewView == LVS_REPORT)
8163 ShowWindow(infoPtr->hwndHeader, (LVS_NOCOLUMNHEADER & lpss->styleNew) ? SW_HIDE : SW_SHOWNORMAL);
8165 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
8166 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
8168 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
8169 LISTVIEW_AlignLeft(infoPtr);
8171 LISTVIEW_AlignTop(infoPtr);
8174 /* update the size of the client area */
8175 LISTVIEW_UpdateSize(infoPtr);
8177 /* add scrollbars if needed */
8178 LISTVIEW_UpdateScroll(infoPtr);
8180 /* invalidate client area + erase background */
8181 LISTVIEW_InvalidateList(infoPtr);
8183 /* print the list of unsupported window styles */
8184 LISTVIEW_UnsupportedStyles(lpss->styleNew);
8191 * Window procedure of the listview control.
8194 static LRESULT WINAPI
8195 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8197 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(hwnd, 0);
8199 TRACE("(uMsg=%x wParam=%x lParam=%lx)\n", uMsg, wParam, lParam);
8201 if (!infoPtr && (uMsg != WM_CREATE))
8202 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8206 infoPtr->dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
8211 case LVM_APPROXIMATEVIEWRECT:
8212 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
8213 LOWORD(lParam), HIWORD(lParam));
8215 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
8217 /* case LVN_CANCELEDITLABEL */
8219 /* case LVM_CREATEDRAGIMAGE: */
8221 case LVM_DELETEALLITEMS:
8222 return LISTVIEW_DeleteAllItems(infoPtr);
8224 case LVM_DELETECOLUMN:
8225 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
8227 case LVM_DELETEITEM:
8228 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
8230 case LVM_EDITLABELW:
8231 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
8233 case LVM_EDITLABELA:
8234 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
8236 /* case LVN_ENABLEGROUPVIEW: */
8238 case LVM_ENSUREVISIBLE:
8239 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
8242 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
8245 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
8247 case LVM_GETBKCOLOR:
8248 return infoPtr->clrBk;
8250 /* case LVM_GETBKIMAGE: */
8252 case LVM_GETCALLBACKMASK:
8253 return infoPtr->uCallbackMask;
8255 case LVM_GETCOLUMNA:
8256 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8258 case LVM_GETCOLUMNW:
8259 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8261 case LVM_GETCOLUMNORDERARRAY:
8262 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
8264 case LVM_GETCOLUMNWIDTH:
8265 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
8267 case LVM_GETCOUNTPERPAGE:
8268 return LISTVIEW_GetCountPerPage(infoPtr);
8270 case LVM_GETEDITCONTROL:
8271 return (LRESULT)infoPtr->hwndEdit;
8273 case LVM_GETEXTENDEDLISTVIEWSTYLE:
8274 return infoPtr->dwLvExStyle;
8277 return (LRESULT)infoPtr->hwndHeader;
8279 case LVM_GETHOTCURSOR:
8280 return (LRESULT)infoPtr->hHotCursor;
8282 case LVM_GETHOTITEM:
8283 return infoPtr->nHotItem;
8285 case LVM_GETHOVERTIME:
8286 return infoPtr->dwHoverTime;
8288 case LVM_GETIMAGELIST:
8289 return LISTVIEW_GetImageList(infoPtr, (INT)wParam);
8291 /* case LVN_GETINSERTMARK: */
8293 /* case LVN_GETINSERTMARKCOLOR: */
8295 /* case LVN_GETINSERTMARKRECT: */
8297 case LVM_GETISEARCHSTRINGA:
8298 case LVM_GETISEARCHSTRINGW:
8299 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
8303 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
8306 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
8308 case LVM_GETITEMCOUNT:
8309 return infoPtr->nItemCount;
8311 case LVM_GETITEMPOSITION:
8312 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
8314 case LVM_GETITEMRECT:
8315 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
8317 case LVM_GETITEMSPACING:
8318 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
8320 case LVM_GETITEMSTATE:
8321 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
8323 case LVM_GETITEMTEXTA:
8324 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
8326 case LVM_GETITEMTEXTW:
8327 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
8329 case LVM_GETNEXTITEM:
8330 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
8332 case LVM_GETNUMBEROFWORKAREAS:
8333 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
8337 return LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
8339 /* case LVN_GETOUTLINECOLOR: */
8341 /* case LVM_GETSELECTEDCOLUMN: */
8343 case LVM_GETSELECTEDCOUNT:
8344 return LISTVIEW_GetSelectedCount(infoPtr);
8346 case LVM_GETSELECTIONMARK:
8347 return infoPtr->nSelectionMark;
8349 case LVM_GETSTRINGWIDTHA:
8350 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
8352 case LVM_GETSTRINGWIDTHW:
8353 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
8355 case LVM_GETSUBITEMRECT:
8356 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
8358 case LVM_GETTEXTBKCOLOR:
8359 return infoPtr->clrTextBk;
8361 case LVM_GETTEXTCOLOR:
8362 return infoPtr->clrText;
8364 /* case LVN_GETTILEINFO: */
8366 /* case LVN_GETTILEVIEWINFO: */
8368 case LVM_GETTOOLTIPS:
8369 FIXME("LVM_GETTOOLTIPS: unimplemented\n");
8372 case LVM_GETTOPINDEX:
8373 return LISTVIEW_GetTopIndex(infoPtr);
8375 /*case LVM_GETUNICODEFORMAT:
8376 FIXME("LVM_GETUNICODEFORMAT: unimplemented\n");
8379 case LVM_GETVIEWRECT:
8380 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
8382 case LVM_GETWORKAREAS:
8383 FIXME("LVM_GETWORKAREAS: unimplemented\n");
8386 /* case LVN_HASGROUP: */
8389 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
8391 case LVM_INSERTCOLUMNA:
8392 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8394 case LVM_INSERTCOLUMNW:
8395 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8397 /* case LVN_INSERTGROUP: */
8399 /* case LVN_INSERTGROUPSORTED: */
8401 case LVM_INSERTITEMA:
8402 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
8404 case LVM_INSERTITEMW:
8405 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
8407 /* case LVN_INSERTMARKHITTEST: */
8409 /* case LVN_ISGROUPVIEWENABLED: */
8411 /* case LVN_MAPIDTOINDEX: */
8413 /* case LVN_INEDXTOID: */
8415 /* case LVN_MOVEGROUP: */
8417 /* case LVN_MOVEITEMTOGROUP: */
8419 case LVM_REDRAWITEMS:
8420 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
8422 /* case LVN_REMOVEALLGROUPS: */
8424 /* case LVN_REMOVEGROUP: */
8427 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
8429 case LVM_SETBKCOLOR:
8430 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
8432 /* case LVM_SETBKIMAGE: */
8434 case LVM_SETCALLBACKMASK:
8435 infoPtr->uCallbackMask = (UINT)wParam;
8438 case LVM_SETCOLUMNA:
8439 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8441 case LVM_SETCOLUMNW:
8442 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8444 case LVM_SETCOLUMNORDERARRAY:
8445 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
8447 case LVM_SETCOLUMNWIDTH:
8448 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, SLOWORD(lParam));
8450 case LVM_SETEXTENDEDLISTVIEWSTYLE:
8451 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
8453 /* case LVN_SETGROUPINFO: */
8455 /* case LVN_SETGROUPMETRICS: */
8457 case LVM_SETHOTCURSOR:
8458 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
8460 case LVM_SETHOTITEM:
8461 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
8463 case LVM_SETHOVERTIME:
8464 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
8466 case LVM_SETICONSPACING:
8467 return LISTVIEW_SetIconSpacing(infoPtr, (DWORD)lParam);
8469 case LVM_SETIMAGELIST:
8470 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
8472 /* case LVN_SETINFOTIP: */
8474 /* case LVN_SETINSERTMARK: */
8476 /* case LVN_SETINSERTMARKCOLOR: */
8479 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
8482 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
8484 case LVM_SETITEMCOUNT:
8485 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
8487 case LVM_SETITEMPOSITION:
8489 POINT pt = { SLOWORD(lParam), SHIWORD(lParam) };
8490 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
8493 case LVM_SETITEMPOSITION32:
8494 if (lParam == 0) return FALSE;
8495 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
8497 case LVM_SETITEMSTATE:
8498 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
8500 case LVM_SETITEMTEXTA:
8501 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
8503 case LVM_SETITEMTEXTW:
8504 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
8506 /* case LVN_SETOUTLINECOLOR: */
8508 /* case LVN_SETSELECTEDCOLUMN: */
8510 case LVM_SETSELECTIONMARK:
8511 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
8513 case LVM_SETTEXTBKCOLOR:
8514 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
8516 case LVM_SETTEXTCOLOR:
8517 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
8519 /* case LVN_SETTILEINFO: */
8521 /* case LVN_SETTILEVIEWINFO: */
8523 /* case LVN_SETTILEWIDTH: */
8525 /* case LVM_SETTOOLTIPS: */
8527 /* case LVM_SETUNICODEFORMAT: */
8529 /* case LVN_SETVIEW: */
8531 /* case LVM_SETWORKAREAS: */
8533 /* case LVN_SORTGROUPS: */
8536 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
8538 case LVM_SUBITEMHITTEST:
8539 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
8542 return LISTVIEW_Update(infoPtr, (INT)wParam);
8545 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
8548 return LISTVIEW_Command(infoPtr, wParam, lParam);
8551 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
8554 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
8557 return DLGC_WANTCHARS | DLGC_WANTARROWS;
8560 return infoPtr->hFont;
8563 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
8566 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
8569 return LISTVIEW_KillFocus(infoPtr);
8571 case WM_LBUTTONDBLCLK:
8572 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8574 case WM_LBUTTONDOWN:
8575 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8578 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8581 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8584 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8587 return LISTVIEW_NCDestroy(infoPtr);
8590 return LISTVIEW_Notify(infoPtr, (INT)wParam, (LPNMHDR)lParam);
8592 case WM_NOTIFYFORMAT:
8593 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
8596 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
8598 case WM_RBUTTONDBLCLK:
8599 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8601 case WM_RBUTTONDOWN:
8602 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8605 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8608 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
8613 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
8616 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
8619 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
8622 return LISTVIEW_Size(infoPtr, (int)SLOWORD(lParam), (int)SHIWORD(lParam));
8624 case WM_STYLECHANGED:
8625 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
8627 case WM_SYSCOLORCHANGE:
8628 COMCTL32_RefreshSysColors();
8631 /* case WM_TIMER: */
8634 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
8637 if (wParam & (MK_SHIFT | MK_CONTROL))
8638 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8639 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
8641 case WM_WINDOWPOSCHANGED:
8642 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE)) {
8643 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
8644 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
8645 LISTVIEW_UpdateSize(infoPtr);
8646 LISTVIEW_UpdateScroll(infoPtr);
8648 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8650 /* case WM_WININICHANGE: */
8653 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
8654 ERR("unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam);
8657 /* call default window procedure */
8658 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8666 * Registers the window class.
8674 void LISTVIEW_Register(void)
8678 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
8679 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
8680 wndClass.lpfnWndProc = (WNDPROC)LISTVIEW_WindowProc;
8681 wndClass.cbClsExtra = 0;
8682 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
8683 wndClass.hCursor = LoadCursorW(0, IDC_ARROWW);
8684 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
8685 wndClass.lpszClassName = WC_LISTVIEWW;
8686 RegisterClassW(&wndClass);
8691 * Unregisters the window class.
8699 void LISTVIEW_Unregister(void)
8701 UnregisterClassW(WC_LISTVIEWW, (HINSTANCE)NULL);
8706 * Handle any WM_COMMAND messages
8709 * [I] infoPtr : valid pointer to the listview structure
8710 * [I] wParam : the first message parameter
8711 * [I] lParam : the second message parameter
8715 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
8717 switch (HIWORD(wParam))
8722 * Adjust the edit window size
8725 HDC hdc = GetDC(infoPtr->hwndEdit);
8726 HFONT hFont, hOldFont = 0;
8731 if (!infoPtr->hwndEdit || !hdc) return 0;
8732 len = GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
8733 GetWindowRect(infoPtr->hwndEdit, &rect);
8735 /* Select font to get the right dimension of the string */
8736 hFont = SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
8739 hOldFont = SelectObject(hdc, hFont);
8742 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
8744 TEXTMETRICW textMetric;
8746 /* Add Extra spacing for the next character */
8747 GetTextMetricsW(hdc, &textMetric);
8748 sz.cx += (textMetric.tmMaxCharWidth * 2);
8756 rect.bottom - rect.top,
8757 SWP_DRAWFRAME|SWP_NOMOVE);
8760 SelectObject(hdc, hOldFont);
8762 ReleaseDC(infoPtr->hwndSelf, hdc);
8768 return SendMessageW (GetParent (infoPtr->hwndSelf), WM_COMMAND, wParam, lParam);
8777 * Subclassed edit control windproc function
8780 * [I] hwnd : the edit window handle
8781 * [I] uMsg : the message that is to be processed
8782 * [I] wParam : first message parameter
8783 * [I] lParam : second message parameter
8784 * [I] isW : TRUE if input is Unicode
8788 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
8790 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(GetParent(hwnd), 0);
8791 BOOL cancel = FALSE;
8793 TRACE("(hwnd=%x, uMsg=%x, wParam=%x, lParam=%lx, isW=%d)\n",
8794 hwnd, uMsg, wParam, lParam, isW);
8799 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
8806 WNDPROC editProc = infoPtr->EditWndProc;
8807 infoPtr->EditWndProc = 0;
8808 SetWindowLongW(hwnd, GWL_WNDPROC, (LONG)editProc);
8809 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
8813 if (VK_ESCAPE == (INT)wParam)
8818 else if (VK_RETURN == (INT)wParam)
8822 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
8826 if (infoPtr->hwndEdit)
8828 LPWSTR buffer = NULL;
8830 infoPtr->hwndEdit = 0;
8833 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
8837 if ( (buffer = COMCTL32_Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
8839 if (isW) GetWindowTextW(hwnd, buffer, len+1);
8840 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
8844 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
8846 if (buffer) COMCTL32_Free(buffer);
8850 SendMessageW(hwnd, WM_CLOSE, 0, 0);
8856 * Subclassed edit control Unicode windproc function
8859 * [I] hwnd : the edit window handle
8860 * [I] uMsg : the message that is to be processed
8861 * [I] wParam : first message parameter
8862 * [I] lParam : second message parameter
8866 LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8868 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
8873 * Subclassed edit control ANSI windproc function
8876 * [I] hwnd : the edit window handle
8877 * [I] uMsg : the message that is to be processed
8878 * [I] wParam : first message parameter
8879 * [I] lParam : second message parameter
8883 LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8885 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
8890 * Creates a subclassed edit cotrol
8893 * [I] infoPtr : valid pointer to the listview structure
8894 * [I] text : initial text for the edit
8895 * [I] style : the window style
8896 * [I] isW : TRUE if input is Unicode
8900 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
8901 INT x, INT y, INT width, INT height, BOOL isW)
8903 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
8908 TEXTMETRICW textMetric;
8909 HINSTANCE hinst = GetWindowLongW(infoPtr->hwndSelf, GWL_HINSTANCE);
8911 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
8913 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|WS_BORDER;
8914 hdc = GetDC(infoPtr->hwndSelf);
8916 /* Select the font to get appropriate metric dimensions */
8917 if(infoPtr->hFont != 0)
8918 hOldFont = SelectObject(hdc, infoPtr->hFont);
8920 /*Get String Lenght in pixels */
8921 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
8923 /*Add Extra spacing for the next character */
8924 GetTextMetricsW(hdc, &textMetric);
8925 sz.cx += (textMetric.tmMaxCharWidth * 2);
8927 if(infoPtr->hFont != 0)
8928 SelectObject(hdc, hOldFont);
8930 ReleaseDC(infoPtr->hwndSelf, hdc);
8932 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
8934 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
8936 if (!hedit) return 0;
8938 infoPtr->EditWndProc = (WNDPROC)
8939 (isW ? SetWindowLongW(hedit, GWL_WNDPROC, (LONG)EditLblWndProcW) :
8940 SetWindowLongA(hedit, GWL_WNDPROC, (LONG)EditLblWndProcA) );
8942 SendMessageW(hedit, WM_SETFONT, infoPtr->hFont, FALSE);