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 typedef struct tagCOLUMNCACHE
80 } COLUMNCACHE, *LPCOLUMNCACHE;
82 typedef struct tagITEMHDR
86 } ITEMHDR, *LPITEMHDR;
88 typedef struct tagLISTVIEW_SUBITEM
94 typedef struct tagLISTVIEW_ITEM
103 typedef struct tagRANGE
109 typedef struct tagRANGES
114 typedef struct tagITERATOR
123 typedef struct tagLISTVIEW_INFO
130 COLORREF clrTextBkDefault;
131 HIMAGELIST himlNormal;
132 HIMAGELIST himlSmall;
133 HIMAGELIST himlState;
138 RANGES selectionRanges;
140 BOOL bRemovingAllSelections;
143 RECT rcList; /* This rectangle is really the window
144 * client rectangle possibly reduced by the
145 * horizontal scroll bar and/or header - see
146 * LISTVIEW_UpdateSize. This rectangle offset
147 * by the LISTVIEW_GetOrigin value is in
148 * client coordinates */
149 RECT rcView; /* This rectangle contains all items -
150 * contructed in LISTVIEW_AlignTop and
151 * LISTVIEW_AlignLeft */
160 INT ntmHeight; /* from GetTextMetrics from above font */
165 DWORD dwStyle; /* the cached window GWL_STYLE */
166 DWORD dwLvExStyle; /* extended listview style */
169 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
170 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
171 PFNLVCOMPARE pfnCompare;
178 DWORD lastKeyPressTimestamp;
180 INT nSearchParamLength;
181 WCHAR szSearchParam[ MAX_PATH ];
188 /* How many we debug buffer to allocate */
189 #define DEBUG_BUFFERS 20
190 /* The size of a single debug bbuffer */
191 #define DEBUG_BUFFER_SIZE 256
193 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
194 #define SB_INTERNAL -1
196 /* maximum size of a label */
197 #define DISP_TEXT_SIZE 512
199 /* padding for items in list and small icon display modes */
200 #define WIDTH_PADDING 12
202 /* padding for items in list, report and small icon display modes */
203 #define HEIGHT_PADDING 1
205 /* offset of items in report display mode */
206 #define REPORT_MARGINX 2
208 /* padding for icon in large icon display mode
209 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
210 * that HITTEST will see.
211 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
212 * ICON_TOP_PADDING - sum of the two above.
213 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
214 * LABEL_VERT_PADDING - between bottom of text and end of box
216 * ICON_LR_PADDING - additional width above icon size.
217 * ICON_LR_HALF - half of the above value
219 #define ICON_TOP_PADDING_NOTHITABLE 2
220 #define ICON_TOP_PADDING_HITABLE 2
221 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
222 #define ICON_BOTTOM_PADDING 4
223 #define LABEL_VERT_PADDING 7
224 #define ICON_LR_PADDING 16
225 #define ICON_LR_HALF (ICON_LR_PADDING/2)
227 /* default label width for items in list and small icon display modes */
228 #define DEFAULT_LABEL_WIDTH 40
230 /* default column width for items in list display mode */
231 #define DEFAULT_COLUMN_WIDTH 128
233 /* Size of "line" scroll for V & H scrolls */
234 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
236 /* Padding betwen image and label */
237 #define IMAGE_PADDING 2
239 /* Padding behind the label */
240 #define TRAILING_PADDING 5
242 /* Border for the icon caption */
243 #define CAPTION_BORDER 2
245 /* Standard DrawText flags */
246 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
247 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
248 #define LV_SL_DT_FLAGS (DT_TOP | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
250 /* The time in milisecods to reset the search in the list */
251 #define KEY_DELAY 450
253 /* Dump the LISTVIEW_INFO structure to the debug channel */
254 #define LISTVIEW_DUMP(iP) do { \
255 TRACE("hwndSelf=%08x, clrBk=0x%06lx, clrText=0x%06lx, clrTextBk=0x%06lx, ItemHeight=%d, ItemWidth=%d, Style=0x%08lx\n", \
256 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
257 iP->nItemHeight, iP->nItemWidth, infoPtr->dwStyle); \
258 TRACE("hwndSelf=%08x, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08lx, Focus=%s\n", \
259 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
260 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, \
261 (iP->bFocus) ? "true" : "false"); \
262 TRACE("hwndSelf=%08x, ntmH=%d, icSz.cx=%ld, icSz.cy=%ld, icSp.cx=%ld, icSp.cy=%ld, notifyFmt=%d\n", \
263 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
264 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
265 TRACE("hwndSelf=%08x, rcList=(%d,%d)-(%d,%d), rcView=(%d,%d)-(%d,%d)\n", \
267 iP->rcList.left, iP->rcList.top, iP->rcList.right, iP->rcList.bottom, \
268 iP->rcView.left, iP->rcView.top, iP->rcView.right, iP->rcView.bottom); \
273 * forward declarations
275 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *, LPLVITEMW, BOOL);
276 static BOOL LISTVIEW_GetItemBox(LISTVIEW_INFO *, INT, LPRECT);
277 static void LISTVIEW_AlignLeft(LISTVIEW_INFO *);
278 static void LISTVIEW_AlignTop(LISTVIEW_INFO *);
279 static void LISTVIEW_AddGroupSelection(LISTVIEW_INFO *, INT);
280 static INT LISTVIEW_CalculateMaxHeight(LISTVIEW_INFO *);
281 static BOOL LISTVIEW_GetItemOrigin(LISTVIEW_INFO *, INT, LPPOINT);
282 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *, INT, LPPOINT);
283 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *, INT, LPRECT);
284 static INT LISTVIEW_CalculateMaxWidth(LISTVIEW_INFO *);
285 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *, INT);
286 static INT LISTVIEW_GetColumnWidth(LISTVIEW_INFO *, INT);
287 static BOOL LISTVIEW_GetOrigin(LISTVIEW_INFO *, LPPOINT);
288 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *, LPRECT);
289 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
290 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, LPLVITEMW, BOOL);
291 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *, INT, POINT);
292 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *);
293 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
294 static BOOL LISTVIEW_UpdateSize(LISTVIEW_INFO *);
295 static void LISTVIEW_UnsupportedStyles(LONG);
296 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
297 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
298 static LRESULT LISTVIEW_SortItems(LISTVIEW_INFO *, PFNLVCOMPARE, LPARAM);
299 static LRESULT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *, LPCWSTR, BOOL);
300 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT);
301 static LRESULT LISTVIEW_GetItemState(LISTVIEW_INFO *, INT, UINT);
302 static LRESULT LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, LPLVITEMW);
303 static LRESULT LISTVIEW_GetColumnT(LISTVIEW_INFO *, INT, LPLVCOLUMNW, BOOL);
304 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
305 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
306 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *);
307 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
308 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
310 /******** Text handling functions *************************************/
312 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
313 * text string. The string may be ANSI or Unicode, in which case
314 * the boolean isW tells us the type of the string.
316 * The name of the function tell what type of strings it expects:
317 * W: Unicode, T: ANSI/Unicode - function of isW
320 static inline BOOL is_textW(LPCWSTR text)
322 return text != NULL && text != LPSTR_TEXTCALLBACKW;
325 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
327 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
328 return is_textW(text);
331 static inline int textlenT(LPCWSTR text, BOOL isW)
333 return !is_textT(text, isW) ? 0 :
334 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
337 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
340 if (isSrcW) lstrcpynW(dest, src, max);
341 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
343 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
344 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
347 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
349 LPWSTR wstr = (LPWSTR)text;
351 if (!isW && is_textT(text, isW))
353 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
354 wstr = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
355 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
357 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
361 static inline void textfreeT(LPWSTR wstr, BOOL isW)
363 if (!isW && is_textT(wstr, isW)) HeapFree(GetProcessHeap(), 0, wstr);
367 * dest is a pointer to a Unicode string
368 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
370 static BOOL textsetptrT(LPWSTR *dest, LPWSTR src, BOOL isW)
374 if (src == LPSTR_TEXTCALLBACKW)
376 if (is_textW(*dest)) COMCTL32_Free(*dest);
377 *dest = LPSTR_TEXTCALLBACKW;
381 LPWSTR pszText = textdupTtoW(src, isW);
382 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
383 bResult = Str_SetPtrW(dest, pszText);
384 textfreeT(pszText, isW);
390 * compares a Unicode to a Unicode/ANSI text string
392 static inline int textcmpWT(LPWSTR aw, LPWSTR bt, BOOL isW)
394 if (!aw) return bt ? -1 : 0;
395 if (!bt) return aw ? 1 : 0;
396 if (aw == LPSTR_TEXTCALLBACKW)
397 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
398 if (bt != LPSTR_TEXTCALLBACKW)
400 LPWSTR bw = textdupTtoW(bt, isW);
401 int r = bw ? lstrcmpW(aw, bw) : 1;
409 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
413 n = min(min(n, strlenW(s1)), strlenW(s2));
414 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
415 return res ? res - sizeof(WCHAR) : res;
418 /******** Debugging functions *****************************************/
420 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
422 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
423 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
426 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
428 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
429 n = min(textlenT(text, isW), n);
430 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
433 static char* debug_getbuf()
435 static int index = 0;
436 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
437 return buffers[index++ % DEBUG_BUFFERS];
440 static inline char* debugrange(const RANGE* lprng)
444 char* buf = debug_getbuf();
445 snprintf(buf, DEBUG_BUFFER_SIZE, "[%d, %d)", lprng->lower, lprng->upper);
447 } else return "(null)";
450 static inline char* debugpoint(const POINT* lppt)
454 char* buf = debug_getbuf();
455 snprintf(buf, DEBUG_BUFFER_SIZE, "(%ld, %ld)", lppt->x, lppt->y);
457 } else return "(null)";
460 static inline char* debugrect(const RECT* rect)
464 char* buf = debug_getbuf();
465 snprintf(buf, DEBUG_BUFFER_SIZE, "[(%d, %d);(%d, %d)]",
466 rect->left, rect->top, rect->right, rect->bottom);
468 } else return "(null)";
471 static char* debuglvitem_t(LPLVITEMW lpLVItem, BOOL isW)
473 char* buf = debug_getbuf(), *text = buf;
474 int len, size = DEBUG_BUFFER_SIZE;
476 if (lpLVItem == NULL) return "(null)";
477 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
478 if (len == -1) goto end; buf += len; size -= len;
479 if (lpLVItem->mask & LVIF_STATE)
480 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
482 if (len == -1) goto end; buf += len; size -= len;
483 if (lpLVItem->mask & LVIF_TEXT)
484 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
486 if (len == -1) goto end; buf += len; size -= len;
487 if (lpLVItem->mask & LVIF_IMAGE)
488 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
490 if (len == -1) goto end; buf += len; size -= len;
491 if (lpLVItem->mask & LVIF_PARAM)
492 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
494 if (len == -1) goto end; buf += len; size -= len;
495 if (lpLVItem->mask & LVIF_INDENT)
496 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
498 if (len == -1) goto end; buf += len; size -= len;
501 buf = text + strlen(text);
503 if (buf - text > 2) buf[-2] = '}'; buf[-1] = 0;
507 static char* debuglvcolumn_t(LPLVCOLUMNW lpColumn, BOOL isW)
509 char* buf = debug_getbuf(), *text = buf;
510 int len, size = DEBUG_BUFFER_SIZE;
512 if (lpColumn == NULL) return "(null)";
513 len = snprintf(buf, size, "{");
514 if (len == -1) goto end; buf += len; size -= len;
515 if (lpColumn->mask & LVCF_SUBITEM)
516 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
518 if (len == -1) goto end; buf += len; size -= len;
519 if (lpColumn->mask & LVCF_FMT)
520 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
522 if (len == -1) goto end; buf += len; size -= len;
523 if (lpColumn->mask & LVCF_WIDTH)
524 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
526 if (len == -1) goto end; buf += len; size -= len;
527 if (lpColumn->mask & LVCF_TEXT)
528 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
530 if (len == -1) goto end; buf += len; size -= len;
531 if (lpColumn->mask & LVCF_IMAGE)
532 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
534 if (len == -1) goto end; buf += len; size -= len;
535 if (lpColumn->mask & LVCF_ORDER)
536 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
538 if (len == -1) goto end; buf += len; size -= len;
541 buf = text + strlen(text);
543 if (buf - text > 2) buf[-2] = '}'; buf[-1] = 0;
548 /******** Notification functions i************************************/
550 static LRESULT notify_hdr(LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
554 TRACE("(code=%d)\n", code);
556 pnmh->hwndFrom = infoPtr->hwndSelf;
557 pnmh->idFrom = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
559 result = SendMessageW(GetParent(infoPtr->hwndSelf), WM_NOTIFY,
560 (WPARAM)pnmh->idFrom, (LPARAM)pnmh);
562 TRACE(" <= %ld\n", result);
567 static inline LRESULT notify(LISTVIEW_INFO *infoPtr, INT code)
570 return notify_hdr(infoPtr, code, &nmh);
573 static inline void notify_itemactivate(LISTVIEW_INFO *infoPtr)
575 notify(infoPtr, LVN_ITEMACTIVATE);
578 static inline LRESULT notify_listview(LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
580 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
583 static LRESULT notify_click(LISTVIEW_INFO *infoPtr, INT code, LVHITTESTINFO *lvht)
587 ZeroMemory(&nmlv, sizeof(nmlv));
588 nmlv.iItem = lvht->iItem;
589 nmlv.iSubItem = lvht->iSubItem;
590 nmlv.ptAction = lvht->pt;
591 return notify_listview(infoPtr, code, &nmlv);
594 static int get_ansi_notification(INT unicodeNotificationCode)
596 switch (unicodeNotificationCode)
598 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
599 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
600 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
601 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
602 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
603 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
605 ERR("unknown notification %x\n", unicodeNotificationCode);
606 return unicodeNotificationCode;
610 Send notification. depends on dispinfoW having same
611 structure as dispinfoA.
612 infoPtr : listview struct
613 notificationCode : *Unicode* notification code
614 pdi : dispinfo structure (can be unicode or ansi)
615 isW : TRUE if dispinfo is Unicode
617 static BOOL notify_dispinfoT(LISTVIEW_INFO *infoPtr, INT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
619 BOOL bResult = FALSE;
620 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
622 INT cchTempBufMax = 0, savCchTextMax = 0;
623 LPWSTR pszTempBuf = NULL, savPszText = NULL;
625 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
627 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
628 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
631 if (convertToAnsi || convertToUnicode)
633 if (notificationCode != LVN_GETDISPINFOW)
635 cchTempBufMax = convertToUnicode ?
636 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
637 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
641 cchTempBufMax = pdi->item.cchTextMax;
642 *pdi->item.pszText = 0; /* make sure we don't process garbage */
645 pszTempBuf = HeapAlloc(GetProcessHeap(), 0,
646 (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
647 if (!pszTempBuf) return FALSE;
648 if (convertToUnicode)
649 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
650 pszTempBuf, cchTempBufMax);
652 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
653 cchTempBufMax, NULL, NULL);
654 savCchTextMax = pdi->item.cchTextMax;
655 savPszText = pdi->item.pszText;
656 pdi->item.pszText = pszTempBuf;
657 pdi->item.cchTextMax = cchTempBufMax;
660 if (infoPtr->notifyFormat == NFR_ANSI)
661 realNotifCode = get_ansi_notification(notificationCode);
663 realNotifCode = notificationCode;
664 bResult = notify_hdr(infoPtr, realNotifCode, (LPNMHDR)pdi);
666 if (convertToUnicode || convertToAnsi)
668 if (convertToUnicode) /* note : pointer can be changed by app ! */
669 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
670 savCchTextMax, NULL, NULL);
672 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
673 savPszText, savCchTextMax);
674 pdi->item.pszText = savPszText; /* restores our buffer */
675 pdi->item.cchTextMax = savCchTextMax;
676 HeapFree(GetProcessHeap(), 0, pszTempBuf);
681 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, LISTVIEW_INFO *infoPtr, HDC hdc, LPRECT rcBounds)
683 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
684 lpnmlvcd->nmcd.hdc = hdc;
685 lpnmlvcd->nmcd.rc = *rcBounds;
686 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
687 lpnmlvcd->clrText = infoPtr->clrText;
690 static inline DWORD notify_customdraw (LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
692 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
693 return notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
696 /******** Item iterator functions **********************************/
698 static RANGES ranges_create(int count);
699 static void ranges_destroy(RANGES ranges);
700 static BOOL ranges_add(RANGES ranges, RANGE range);
701 static BOOL ranges_del(RANGES ranges, RANGE range);
702 static void ranges_dump(RANGES ranges);
704 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
706 RANGE range = { nItem, nItem + 1 };
708 return ranges_add(ranges, range);
711 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
713 RANGE range = { nItem, nItem + 1 };
715 return ranges_del(ranges, range);
719 * ITERATOR DOCUMENTATION
721 * The iterator functions allow for easy, and convenient iteration
722 * over items of iterest in the list. Typically, you create a
723 * iterator, use it, and destroy it, as such:
726 * iterator_xxxitems(&i, ...);
727 * while (iterator_{prev,next}(&i)
729 * //code which uses i.nItem
731 * iterator_destroy(&i);
733 * where xxx is either: framed, or visible.
734 * Note that it is important that the code destroys the iterator
735 * after it's done with it, as the creation of the iterator may
736 * allocate memory, which thus needs to be freed.
738 * You can iterate both forwards, and backwards through the list,
739 * by using iterator_next or iterator_prev respectively.
741 * Lower numbered items are draw on top of higher number items in
742 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
743 * items may overlap). So, to test items, you should use
745 * which lists the items top to bottom (in Z-order).
746 * For drawing items, you should use
748 * which lists the items bottom to top (in Z-order).
749 * If you keep iterating over the items after the end-of-items
750 * marker (-1) is returned, the iterator will start from the
751 * beginning. Typically, you don't need to test for -1,
752 * because iterator_{next,prev} will return TRUE if more items
753 * are to be iterated over, or FALSE otherwise.
755 * Note: the iterator is defined to be bidirectional. That is,
756 * any number of prev followed by any number of next, or
757 * five versa, should leave the iterator at the same item:
758 * prev * n, next * n = next * n, prev * n
760 * The iterator has a notion of a out-of-order, special item,
761 * which sits at the start of the list. This is used in
762 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
763 * which needs to be first, as it may overlap other items.
765 * The code is a bit messy because we have:
766 * - a special item to deal with
767 * - simple range, or composite range
769 * If find bugs, or want to add features, please make sure you
770 * always check/modify *both* iterator_prev, and iterator_next.
774 * This function iterates through the items in increasing order,
775 * but prefixed by the special item, then -1. That is:
776 * special, 1, 2, 3, ..., n, -1.
777 * Each item is listed only once.
779 static inline BOOL iterator_next(ITERATOR* i)
783 i->nItem = i->nSpecial;
784 if (i->nItem != -1) return TRUE;
786 if (i->nItem == i->nSpecial)
788 if (i->ranges) i->index = 0;
794 if (i->nItem == i->nSpecial) i->nItem++;
795 if (i->nItem < i->range.upper) return TRUE;
800 if (i->index < i->ranges->hdpa->nItemCount)
801 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
804 else if (i->nItem >= i->range.upper) goto end;
806 i->nItem = i->range.lower;
807 if (i->nItem >= 0) goto testitem;
814 * This function iterates through the items in decreasing order,
815 * followed by the special item, then -1. That is:
816 * n, n-1, ..., 3, 2, 1, special, -1.
817 * Each item is listed only once.
819 static inline BOOL iterator_prev(ITERATOR* i)
826 if (i->ranges) i->index = i->ranges->hdpa->nItemCount;
829 if (i->nItem == i->nSpecial)
837 if (i->nItem == i->nSpecial) i->nItem--;
838 if (i->nItem >= i->range.lower) return TRUE;
844 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
847 else if (!start && i->nItem < i->range.lower) goto end;
849 i->nItem = i->range.upper;
850 if (i->nItem > 0) goto testitem;
852 return (i->nItem = i->nSpecial) != -1;
855 static RANGE iterator_range(ITERATOR* i)
859 if (!i->ranges) return i->range;
861 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
862 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->ranges->hdpa->nItemCount - 1)).upper;
867 * Releases resources associated with this ierator.
869 static inline void iterator_destroy(ITERATOR* i)
871 if (i->ranges) ranges_destroy(i->ranges);
875 * Create an empty iterator.
877 static inline BOOL iterator_empty(ITERATOR* i)
879 ZeroMemory(i, sizeof(*i));
880 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
886 * Create an iterator over a bunch of ranges.
887 * Please note that the iterator will take ownership of the ranges,
888 * and will free them upon destruction.
890 static inline BOOL iterator_ranges(ITERATOR* i, RANGES ranges)
898 * Creates an iterator over the items which intersect lprc.
900 static BOOL iterator_frameditems(ITERATOR* i, LISTVIEW_INFO* infoPtr, const RECT* lprc)
902 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
903 RECT frame = *lprc, rcItem, rcTemp;
906 /* in case we fail, we want to return an empty iterator */
907 if (!iterator_empty(i)) return FALSE;
909 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return FALSE;
911 OffsetRect(&frame, -Origin.x, -Origin.y);
913 if (uView == LVS_ICON || uView == LVS_SMALLICON)
917 if (uView == LVS_ICON)
919 if (LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem) && IntersectRect(&rcTemp, &rcItem, lprc))
920 i->nSpecial = infoPtr->nFocusedItem;
922 if (!(i->ranges = ranges_create(50))) return FALSE;
923 /* to do better here, we need to have PosX, and PosY sorted */
924 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
926 rcItem.left = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
927 rcItem.top = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
928 rcItem.right = rcItem.left + infoPtr->nItemWidth;
929 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
930 if (IntersectRect(&rcTemp, &rcItem, &frame))
931 ranges_additem(i->ranges, nItem);
933 if (TRACE_ON(listview))
935 TRACE(" icon items:\n");
936 ranges_dump(i->ranges);
940 else if (uView == LVS_REPORT)
944 if (frame.left >= infoPtr->nItemWidth) return TRUE;
945 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
947 lower = max(frame.top / infoPtr->nItemHeight, 0);
948 upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1);
949 if (upper < lower) return TRUE;
950 i->range.lower = lower;
951 i->range.upper = upper + 1;
952 TRACE(" report=%s\n", debugrange(&i->range));
956 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
957 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
958 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
959 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
960 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
961 INT lower = nFirstCol * nPerCol + nFirstRow;
965 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
966 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
968 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
970 if (!(i->ranges = ranges_create(nLastCol - nFirstCol + 1))) return FALSE;
971 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
973 item_range.lower = nCol * nPerCol + nFirstRow;
974 if(item_range.lower >= infoPtr->nItemCount) break;
975 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
976 TRACE(" list=%s\n", debugrange(&item_range));
977 ranges_add(i->ranges, item_range);
985 * Creates an iterator over the items which intersect the visible region of hdc.
987 static BOOL iterator_visibleitems(ITERATOR* i, LISTVIEW_INFO *infoPtr, HDC hdc)
989 POINT Origin, Position;
993 rgntype = GetClipBox(hdc, &rcClip);
994 if (rgntype == NULLREGION) return iterator_empty(i);
995 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
996 if (rgntype == SIMPLEREGION) return TRUE;
998 /* first deal with the special item */
999 if (LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem) && !RectVisible(hdc, &rcItem))
1002 /* if we can't deal with the region, we'll just go with the simple range */
1003 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return TRUE;
1006 if (!(i->ranges = ranges_create(50))) return TRUE;
1007 if (!ranges_add(i->ranges, i->range))
1009 ranges_destroy(i->ranges);
1015 /* now delete the invisible items from the list */
1016 while(iterator_next(i))
1018 if (!LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position)) continue;
1019 rcItem.left = Position.x + Origin.x;
1020 rcItem.top = Position.y + Origin.y;
1021 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1022 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1023 if (!RectVisible(hdc, &rcItem))
1024 ranges_delitem(i->ranges, i->nItem);
1026 /* the iterator should restart on the next iterator_next */
1031 /******** Misc helper functions ************************************/
1033 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1034 WPARAM wParam, LPARAM lParam, BOOL isW)
1036 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1037 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1040 /******** Internal API functions ************************************/
1042 /* The Invalidate* are macros, so we preserve the caller location */
1043 #define LISTVIEW_InvalidateRect(infoPtr, rect) while(infoPtr->bRedraw) { \
1044 TRACE(" invalidating rect=%s\n", debugrect(rect)); \
1045 InvalidateRect(infoPtr->hwndSelf, rect, TRUE); \
1049 #define LISTVIEW_InvalidateItem(infoPtr, nItem) do { \
1051 if(LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox)) \
1052 LISTVIEW_InvalidateRect(infoPtr, &rcBox); \
1055 #define LISTVIEW_InvalidateSubItem(infoPtr, nItem, nSubItem) do { \
1056 POINT Origin, Position; \
1058 if (LISTVIEW_GetOrigin(infoPtr, &Origin) && \
1059 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position) && \
1060 Header_GetItemRect(infoPtr->hwndHeader, nSubItem, &rcBox)) { \
1061 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y); \
1062 LISTVIEW_InvalidateRect(infoPtr, &rcBox); \
1066 #define LISTVIEW_InvalidateList(infoPtr)\
1067 LISTVIEW_InvalidateRect(infoPtr, NULL)
1069 static inline BOOL LISTVIEW_GetItemW(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1071 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1076 * Retrieves the number of items that can fit vertically in the client area.
1079 * [I] infoPtr : valid pointer to the listview structure
1082 * Number of items per row.
1084 static inline INT LISTVIEW_GetCountPerRow(LISTVIEW_INFO *infoPtr)
1086 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1088 return max(nListWidth/infoPtr->nItemWidth, 1);
1093 * Retrieves the number of items that can fit horizontally in the client
1097 * [I] infoPtr : valid pointer to the listview structure
1100 * Number of items per column.
1102 static inline INT LISTVIEW_GetCountPerColumn(LISTVIEW_INFO *infoPtr)
1104 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1106 return max(nListHeight / infoPtr->nItemHeight, 1);
1110 /*************************************************************************
1111 * LISTVIEW_ProcessLetterKeys
1113 * Processes keyboard messages generated by pressing the letter keys
1115 * What this does is perform a case insensitive search from the
1116 * current position with the following quirks:
1117 * - If two chars or more are pressed in quick succession we search
1118 * for the corresponding string (e.g. 'abc').
1119 * - If there is a delay we wipe away the current search string and
1120 * restart with just that char.
1121 * - If the user keeps pressing the same character, whether slowly or
1122 * fast, so that the search string is entirely composed of this
1123 * character ('aaaaa' for instance), then we search for first item
1124 * that starting with that character.
1125 * - If the user types the above character in quick succession, then
1126 * we must also search for the corresponding string ('aaaaa'), and
1127 * go to that string if there is a match.
1130 * [I] hwnd : handle to the window
1131 * [I] charCode : the character code, the actual character
1132 * [I] keyData : key data
1140 * - The current implementation has a list of characters it will
1141 * accept and it ignores averything else. In particular it will
1142 * ignore accentuated characters which seems to match what
1143 * Windows does. But I'm not sure it makes sense to follow
1145 * - We don't sound a beep when the search fails.
1149 * TREEVIEW_ProcessLetterKeys
1151 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1156 WCHAR buffer[MAX_PATH];
1157 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1159 /* simple parameter checking */
1160 if (!charCode || !keyData) return 0;
1162 /* only allow the valid WM_CHARs through */
1163 if (!isalnum(charCode) &&
1164 charCode != '.' && charCode != '`' && charCode != '!' &&
1165 charCode != '@' && charCode != '#' && charCode != '$' &&
1166 charCode != '%' && charCode != '^' && charCode != '&' &&
1167 charCode != '*' && charCode != '(' && charCode != ')' &&
1168 charCode != '-' && charCode != '_' && charCode != '+' &&
1169 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1170 charCode != '}' && charCode != '[' && charCode != '{' &&
1171 charCode != '/' && charCode != '?' && charCode != '>' &&
1172 charCode != '<' && charCode != ',' && charCode != '~')
1175 /* if there's one item or less, there is no where to go */
1176 if (infoPtr->nItemCount <= 1) return 0;
1178 /* update the search parameters */
1179 infoPtr->lastKeyPressTimestamp = GetTickCount();
1180 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1181 if (infoPtr->nSearchParamLength < MAX_PATH)
1182 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1183 if (infoPtr->charCode != charCode)
1184 infoPtr->charCode = charCode = 0;
1186 infoPtr->charCode=charCode;
1187 infoPtr->szSearchParam[0]=charCode;
1188 infoPtr->nSearchParamLength=1;
1189 /* Redundant with the 1 char string */
1193 /* and search from the current position */
1195 if (infoPtr->nFocusedItem >= 0) {
1196 endidx=infoPtr->nFocusedItem;
1198 /* if looking for single character match,
1199 * then we must always move forward
1201 if (infoPtr->nSearchParamLength == 1)
1204 endidx=infoPtr->nItemCount;
1208 if (idx == infoPtr->nItemCount) {
1209 if (endidx == infoPtr->nItemCount || endidx == 0)
1215 item.mask = LVIF_TEXT;
1218 item.pszText = buffer;
1219 item.cchTextMax = MAX_PATH;
1220 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1222 /* check for a match */
1223 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1226 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1227 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1228 /* This would work but we must keep looking for a longer match */
1232 } while (idx != endidx);
1235 LISTVIEW_KeySelection(infoPtr, nItem);
1240 /*************************************************************************
1241 * LISTVIEW_UpdateHeaderSize [Internal]
1243 * Function to resize the header control
1246 * hwnd [I] handle to a window
1247 * nNewScrollPos [I] Scroll Pos to Set
1254 static void LISTVIEW_UpdateHeaderSize(LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1259 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1261 GetWindowRect(infoPtr->hwndHeader, &winRect);
1262 point[0].x = winRect.left;
1263 point[0].y = winRect.top;
1264 point[1].x = winRect.right;
1265 point[1].y = winRect.bottom;
1267 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1268 point[0].x = -nNewScrollPos;
1269 point[1].x += nNewScrollPos;
1271 SetWindowPos(infoPtr->hwndHeader,0,
1272 point[0].x,point[0].y,point[1].x,point[1].y,
1273 SWP_NOZORDER | SWP_NOACTIVATE);
1278 * Update the scrollbars. This functions should be called whenever
1279 * the content, size or view changes.
1282 * [I] infoPtr : valid pointer to the listview structure
1287 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *infoPtr)
1289 LONG lStyle = infoPtr->dwStyle;
1290 UINT uView = lStyle & LVS_TYPEMASK;
1291 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1292 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1293 SCROLLINFO scrollInfo;
1295 if (lStyle & LVS_NOSCROLL) return;
1297 scrollInfo.cbSize = sizeof(SCROLLINFO);
1299 if (uView == LVS_LIST)
1301 /* update horizontal scrollbar */
1302 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1303 INT nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
1305 TRACE("items=%d, perColumn=%d, perRow=%d\n",
1306 infoPtr->nItemCount, nCountPerColumn, nCountPerRow);
1308 scrollInfo.nMin = 0;
1309 scrollInfo.nMax = infoPtr->nItemCount / nCountPerColumn;
1310 if((infoPtr->nItemCount % nCountPerColumn) == 0)
1312 if (scrollInfo.nMax < 0) scrollInfo.nMax = 0;
1313 scrollInfo.nPos = ListView_GetTopIndex(infoPtr->hwndSelf) / nCountPerColumn;
1314 scrollInfo.nPage = nCountPerRow;
1315 scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
1316 SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
1317 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
1319 else if (uView == LVS_REPORT)
1323 /* update vertical scrollbar */
1324 scrollInfo.nMin = 0;
1325 scrollInfo.nMax = infoPtr->nItemCount - 1;
1326 scrollInfo.nPos = ListView_GetTopIndex(infoPtr->hwndSelf);
1327 scrollInfo.nPage = LISTVIEW_GetCountPerColumn(infoPtr);
1328 scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
1329 test = (scrollInfo.nMin >= scrollInfo.nMax - max((INT)scrollInfo.nPage - 1, 0));
1330 TRACE("LVS_REPORT Vert. nMax=%d, nPage=%d, test=%d\n",
1331 scrollInfo.nMax, scrollInfo.nPage, test);
1332 SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
1333 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, (test) ? FALSE : TRUE);
1335 /* update horizontal scrollbar */
1336 nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1337 scrollInfo.fMask = SIF_POS;
1338 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)
1339 || infoPtr->nItemCount == 0)
1341 scrollInfo.nPos = 0;
1343 scrollInfo.nMin = 0;
1344 scrollInfo.nMax = max(infoPtr->nItemWidth, 0)-1;
1345 scrollInfo.nPage = nListWidth;
1346 scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE ;
1347 test = (scrollInfo.nMin >= scrollInfo.nMax - max((INT)scrollInfo.nPage - 1, 0));
1348 TRACE("LVS_REPORT Horz. nMax=%d, nPage=%d, test=%d\n",
1349 scrollInfo.nMax, scrollInfo.nPage, test);
1350 SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
1351 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, (test) ? FALSE : TRUE);
1353 /* Update the Header Control */
1354 scrollInfo.fMask = SIF_POS;
1355 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo);
1356 LISTVIEW_UpdateHeaderSize(infoPtr, scrollInfo.nPos);
1363 if (LISTVIEW_GetViewRect(infoPtr, &rcView))
1365 INT nViewWidth = rcView.right - rcView.left;
1366 INT nViewHeight = rcView.bottom - rcView.top;
1368 /* Update Horizontal Scrollbar */
1369 scrollInfo.fMask = SIF_POS;
1370 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)
1371 || infoPtr->nItemCount == 0)
1373 scrollInfo.nPos = 0;
1375 scrollInfo.nMin = 0;
1376 scrollInfo.nMax = max(nViewWidth, 0)-1;
1377 scrollInfo.nPage = nListWidth;
1378 scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
1379 TRACE("LVS_ICON/SMALLICON Horz.\n");
1380 SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
1382 /* Update Vertical Scrollbar */
1383 nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1384 scrollInfo.fMask = SIF_POS;
1385 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)
1386 || infoPtr->nItemCount == 0)
1388 scrollInfo.nPos = 0;
1390 scrollInfo.nMin = 0;
1391 scrollInfo.nMax = max(nViewHeight,0)-1;
1392 scrollInfo.nPage = nListHeight;
1393 scrollInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
1394 TRACE("LVS_ICON/SMALLICON Vert.\n");
1395 SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
1403 * Shows/hides the focus rectangle.
1406 * [I] infoPtr : valid pointer to the listview structure
1407 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1412 static void LISTVIEW_ShowFocusRect(LISTVIEW_INFO *infoPtr, BOOL fShow)
1414 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1417 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1419 if (infoPtr->nFocusedItem < 0) return;
1421 /* we need some gymnastics in ICON mode to handle large items */
1422 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1426 if (!LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox))
1428 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1430 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1435 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1437 /* for some reason, owner draw should work only in report mode */
1438 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1443 item.iItem = infoPtr->nFocusedItem;
1445 item.mask = LVIF_PARAM;
1446 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1448 ZeroMemory(&dis, sizeof(dis));
1449 dis.CtlType = ODT_LISTVIEW;
1450 dis.CtlID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
1451 dis.itemID = item.iItem;
1452 dis.itemAction = ODA_FOCUS;
1453 if (fShow) dis.itemState |= ODS_FOCUS;
1454 dis.hwndItem = infoPtr->hwndSelf;
1456 if (!LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem)) goto done;
1457 dis.itemData = item.lParam;
1459 SendMessageW(GetParent(infoPtr->hwndSelf), WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1463 DrawFocusRect(hdc, &infoPtr->rcFocus);
1466 ReleaseDC(infoPtr->hwndSelf, hdc);
1470 * Invalidates all visible selected items.
1472 static void LISTVIEW_InvalidateSelectedItems(LISTVIEW_INFO *infoPtr)
1476 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1477 while(iterator_next(&i))
1479 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1480 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1482 iterator_destroy(&i);
1488 * Prints a message for unsupported window styles.
1489 * A kind of TODO list for window styles.
1492 * [I] LONG : window style
1497 static void LISTVIEW_UnsupportedStyles(LONG lStyle)
1499 if ((LVS_TYPESTYLEMASK & lStyle) == LVS_NOSCROLL)
1500 FIXME(" LVS_NOSCROLL\n");
1502 if (lStyle & LVS_NOLABELWRAP)
1503 FIXME(" LVS_NOLABELWRAP\n");
1505 if (lStyle & LVS_SORTASCENDING)
1506 FIXME(" LVS_SORTASCENDING\n");
1508 if (lStyle & LVS_SORTDESCENDING)
1509 FIXME(" LVS_SORTDESCENDING\n");
1514 * DESCRIPTION: [INTERNAL]
1515 * Computes an item's (left,top) corner, relative to rcView.
1516 * That is, the position has NOT been made relative to the Origin.
1517 * This is deliberate, to avoid computing the Origin over, and
1518 * over again, when this function is call in a loop. Instead,
1519 * one ca factor the computation of the Origin before the loop,
1520 * and offset the value retured by this function, on every iteration.
1523 * [I] infoPtr : valid pointer to the listview structure
1524 * [I] nItem : item number
1525 * [O] lpptOrig : item top, left corner
1528 * TRUE if computations OK
1531 static BOOL LISTVIEW_GetItemOrigin(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1533 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1535 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
1537 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1539 lpptPosition->x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1540 lpptPosition->y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1542 else if (uView == LVS_LIST)
1544 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1545 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1546 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1548 else /* LVS_REPORT */
1550 lpptPosition->x = REPORT_MARGINX;
1551 lpptPosition->y = nItem * infoPtr->nItemHeight;
1558 * DESCRIPTION: [INTERNAL]
1559 * Compute the rectangles of an item. This is to localize all
1560 * the computations in one place. If you are not interested in some
1561 * of these values, simply pass in a NULL -- the fucntion is smart
1562 * enough to compute only what's necessary. The function computes
1563 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1564 * one, the BOX rectangle. This rectangle is very cheap to compute,
1565 * and is guaranteed to contain all the other rectangles. Computing
1566 * the ICON rect is also cheap, but all the others are potentaily
1567 * expensive. This gives an easy and effective optimization when
1568 * searching (like point inclusion, or rectangle intersection):
1569 * first test against the BOX, and if TRUE, test agains the desired
1571 * If the function does not have all the necessary information
1572 * to computed the requested rectangles, will crash with a
1573 * failed assertion. This is done so we catch all programming
1574 * errors, given that the function is called only from our code.
1576 * We have the following 'special' meanings for a few fields:
1577 * * If LVIS_FOCUSED is set, we assume the item has the focus
1578 * This is important in ICON mode, where it might get a larger
1579 * then usual rectange
1581 * Please note that subitem support works only in REPORT mode.
1584 * [I] infoPtr : valid pointer to the listview structure
1585 * [I] lpLVItem : item to compute the measures for
1586 * [O] lprcBox : ptr to Box rectangle
1587 * The internal LVIR_BOX rectangle
1588 * [0] lprcState : ptr to State icon rectangle
1589 * The internal LVIR_STATE rectangle
1590 * [O] lprcIcon : ptr to Icon rectangle
1591 * Same as LVM_GETITEMRECT with LVIR_ICON
1592 * [O] lprcLabel : ptr to Label rectangle
1593 * Same as LVM_GETITEMRECT with LVIR_LABEL
1596 * TRUE if computations OK
1599 static BOOL LISTVIEW_GetItemMetrics(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem,
1600 LPRECT lprcBox, LPRECT lprcState,
1601 LPRECT lprcIcon, LPRECT lprcLabel)
1603 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1604 BOOL doState = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1605 RECT Box, State, Icon, Label;
1607 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1609 /* Be smart and try to figure out the minimum we have to do */
1610 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1611 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1613 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1614 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1616 if (lprcLabel) doLabel = TRUE;
1617 if (doLabel || lprcIcon) doIcon = TRUE;
1618 if (doIcon || lprcState) doState = TRUE;
1620 /************************************************************/
1621 /* compute the box rectangle (it should be cheap to do) */
1622 /************************************************************/
1623 if (lpLVItem->iSubItem)
1625 if (!Header_GetItemRect(infoPtr->hwndHeader, lpLVItem->iSubItem, &Box)) return FALSE;
1630 Box.right = infoPtr->nItemWidth;
1633 Box.bottom = infoPtr->nItemHeight;
1635 /************************************************************/
1636 /* compute STATEICON bounding box */
1637 /************************************************************/
1640 if (uView == LVS_ICON)
1642 State.left = Box.left - infoPtr->iconStateSize.cx - 2;
1643 if (infoPtr->himlNormal)
1644 State.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1645 State.top = Box.top + infoPtr->iconSize.cy - infoPtr->iconStateSize.cy + 4;
1649 /* we need the ident in report mode, if we don't have it, we fail */
1650 State.left = Box.left;
1651 if (uView == LVS_REPORT)
1653 State.left += REPORT_MARGINX;
1654 if (lpLVItem->iSubItem == 0)
1656 assert(lpLVItem->mask & LVIF_INDENT);
1657 State.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
1660 State.top = Box.top;
1662 State.right = State.left;
1663 State.bottom = State.top;
1664 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1666 State.right += infoPtr->iconStateSize.cx;
1667 State.bottom += infoPtr->iconStateSize.cy;
1669 if (lprcState) *lprcState = State;
1670 TRACE(" - state=%s\n", debugrect(&State));
1673 /************************************************************/
1674 /* compute ICON bounding box (ala LVM_GETITEMRECT) */
1675 /************************************************************/
1678 if (uView == LVS_ICON)
1680 Icon.left = Box.left;
1681 if (infoPtr->himlNormal)
1682 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1683 Icon.top = Box.top + ICON_TOP_PADDING;
1684 Icon.right = Icon.left;
1685 Icon.bottom = Icon.top;
1686 if (infoPtr->himlNormal)
1688 Icon.right += infoPtr->iconSize.cx;
1689 Icon.bottom += infoPtr->iconSize.cy;
1692 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1694 Icon.left = State.right;
1695 if (!IsRectEmpty(&State)) Icon.left += IMAGE_PADDING;
1697 Icon.right = Icon.left;
1698 /* FIXME: add suport for icons for subitems */
1699 if (infoPtr->himlSmall && lpLVItem->iSubItem == 0) Icon.right += infoPtr->iconSize.cx;
1700 Icon.bottom = Icon.top + infoPtr->nItemHeight;
1702 if(lprcIcon) *lprcIcon = Icon;
1703 TRACE(" - icon=%s\n", debugrect(&Icon));
1706 /************************************************************/
1707 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
1708 /************************************************************/
1711 SIZE labelSize = { 0, 0 };
1713 /* calculate how far to the right can the label strech */
1714 Label.right = Box.right;
1715 if (uView == LVS_REPORT)
1717 if (lpLVItem->iSubItem == 0 && !Header_GetItemRect(infoPtr->hwndHeader, 0, &Label)) return FALSE;
1718 Label.right -= REPORT_MARGINX;
1721 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
1723 labelSize.cx = infoPtr->nItemWidth;
1724 labelSize.cy = infoPtr->nItemHeight;
1728 /* we need the text in non owner draw mode */
1729 assert(lpLVItem->mask & LVIF_TEXT);
1730 if (is_textT(lpLVItem->pszText, TRUE))
1732 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1733 HDC hdc = GetDC(infoPtr->hwndSelf);
1734 HFONT hOldFont = SelectObject(hdc, hFont);
1738 /* compute rough rectangle where the label will go */
1739 SetRectEmpty(&rcText);
1740 rcText.right = infoPtr->nItemWidth - TRAILING_PADDING;
1741 rcText.bottom = infoPtr->nItemHeight;
1742 if (uView == LVS_ICON)
1743 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
1745 /* now figure out the flags */
1746 if (uView == LVS_ICON)
1747 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
1749 uFormat = LV_SL_DT_FLAGS;
1751 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
1753 labelSize.cx = min(rcText.right - rcText.left + TRAILING_PADDING, infoPtr->nItemWidth);
1754 labelSize.cy = rcText.bottom - rcText.top;
1756 SelectObject(hdc, hOldFont);
1757 ReleaseDC(infoPtr->hwndSelf, hdc);
1761 if (uView == LVS_ICON)
1763 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
1764 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
1765 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
1766 Label.right = Label.left + labelSize.cx;
1767 Label.bottom = Label.top + infoPtr->nItemHeight;
1768 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
1770 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
1771 labelSize.cy /= infoPtr->ntmHeight;
1772 labelSize.cy = max(labelSize.cy, 1);
1773 labelSize.cy *= infoPtr->ntmHeight;
1775 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
1777 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1779 Label.left = Icon.right;
1780 if (!IsRectEmpty(&Icon) || !IsRectEmpty(&State)) Label.left += IMAGE_PADDING;
1781 Label.top = Box.top;
1782 Label.right = min(Label.left + labelSize.cx, Label.right);
1783 Label.bottom = Label.top + infoPtr->nItemHeight;
1786 if (lprcLabel) *lprcLabel = Label;
1787 TRACE(" - label=%s\n", debugrect(&Label));
1790 /* Fix the Box if necessary */
1793 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
1794 else *lprcBox = Box;
1796 TRACE(" - box=%s\n", debugrect(&Box));
1802 * DESCRIPTION: [INTERNAL]
1805 * [I] infoPtr : valid pointer to the listview structure
1806 * [I] nItem : item number
1807 * [O] lprcBox : ptr to Box rectangle
1810 * TRUE if computations OK
1813 static BOOL LISTVIEW_GetItemBox(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
1815 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1816 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
1817 POINT Position, Origin;
1820 if (!LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position)) return FALSE;
1821 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return FALSE;
1823 /* Be smart and try to figure out the minimum we have to do */
1825 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
1826 lvItem.mask |= LVIF_TEXT;
1827 lvItem.iItem = nItem;
1828 lvItem.iSubItem = 0;
1829 lvItem.pszText = szDispText;
1830 lvItem.cchTextMax = DISP_TEXT_SIZE;
1831 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
1832 if (uView == LVS_ICON)
1834 lvItem.mask |= LVIF_STATE;
1835 lvItem.stateMask = LVIS_FOCUSED;
1836 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
1838 if (!LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0)) return FALSE;
1840 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
1847 * Aligns the items with the top edge of the window.
1850 * [I] infoPtr : valid pointer to the listview structure
1855 static void LISTVIEW_AlignTop(LISTVIEW_INFO *infoPtr)
1857 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1858 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1861 INT i, off_x=0, off_y=0;
1863 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1865 /* Since SetItemPosition uses upper-left of icon, and for
1866 style=LVS_ICON the icon is not left adjusted, get the offset */
1867 if (uView == LVS_ICON)
1869 off_y = ICON_TOP_PADDING;
1870 off_x = (infoPtr->iconSpacing.cx - infoPtr->iconSize.cx) / 2;
1874 ZeroMemory(&rcView, sizeof(RECT));
1875 TRACE("Icon off.x=%d, off.y=%d, left=%d, right=%d\n",
1877 infoPtr->rcList.left, infoPtr->rcList.right);
1879 if (nListWidth > infoPtr->nItemWidth)
1881 for (i = 0; i < infoPtr->nItemCount; i++)
1883 if ((ptItem.x-off_x) + infoPtr->nItemWidth > nListWidth)
1886 ptItem.y += infoPtr->nItemHeight;
1889 LISTVIEW_SetItemPosition(infoPtr, i, ptItem);
1890 ptItem.x += infoPtr->nItemWidth;
1891 rcView.right = max(rcView.right, ptItem.x);
1894 rcView.right -= off_x;
1895 rcView.bottom = (ptItem.y-off_y) + infoPtr->nItemHeight;
1899 for (i = 0; i < infoPtr->nItemCount; i++)
1901 LISTVIEW_SetItemPosition(infoPtr, i, ptItem);
1902 ptItem.y += infoPtr->nItemHeight;
1905 rcView.right = infoPtr->nItemWidth;
1906 rcView.bottom = ptItem.y-off_y;
1909 infoPtr->rcView = rcView;
1915 * Aligns the items with the left edge of the window.
1918 * [I] infoPtr : valid pointer to the listview structure
1923 static void LISTVIEW_AlignLeft(LISTVIEW_INFO *infoPtr)
1925 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1926 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1929 INT i, off_x=0, off_y=0;
1931 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1933 /* Since SetItemPosition uses upper-left of icon, and for
1934 style=LVS_ICON the icon is not left adjusted, get the offset */
1935 if (uView == LVS_ICON)
1937 off_y = ICON_TOP_PADDING;
1938 off_x = (infoPtr->iconSpacing.cx - infoPtr->iconSize.cx) / 2;
1942 ZeroMemory(&rcView, sizeof(RECT));
1943 TRACE("Icon off.x=%d, off.y=%d\n", off_x, off_y);
1945 if (nListHeight > infoPtr->nItemHeight)
1947 for (i = 0; i < infoPtr->nItemCount; i++)
1949 if (ptItem.y + infoPtr->nItemHeight > nListHeight)
1952 ptItem.x += infoPtr->nItemWidth;
1955 LISTVIEW_SetItemPosition(infoPtr, i, ptItem);
1956 ptItem.y += infoPtr->nItemHeight;
1957 rcView.bottom = max(rcView.bottom, ptItem.y);
1960 rcView.right = ptItem.x + infoPtr->nItemWidth;
1964 for (i = 0; i < infoPtr->nItemCount; i++)
1966 LISTVIEW_SetItemPosition(infoPtr, i, ptItem);
1967 ptItem.x += infoPtr->nItemWidth;
1970 rcView.bottom = infoPtr->nItemHeight;
1971 rcView.right = ptItem.x;
1974 infoPtr->rcView = rcView;
1981 * Retrieves the bounding rectangle of all the items.
1984 * [I] infoPtr : valid pointer to the listview structure
1985 * [O] lprcView : bounding rectangle
1991 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
1995 TRACE("(lprcView=%p)\n", lprcView);
1997 if (!lprcView) return FALSE;
1999 if (!LISTVIEW_GetOrigin(infoPtr, &ptOrigin)) return FALSE;
2001 *lprcView = infoPtr->rcView;
2002 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2004 TRACE("lprcView=%s\n", debugrect(lprcView));
2011 * Retrieves the subitem pointer associated with the subitem index.
2014 * [I] HDPA : DPA handle for a specific item
2015 * [I] INT : index of subitem
2018 * SUCCESS : subitem pointer
2021 static LISTVIEW_SUBITEM* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2023 LISTVIEW_SUBITEM *lpSubItem;
2026 /* we should binary search here if need be */
2027 for (i = 1; i < hdpaSubItems->nItemCount; i++)
2029 lpSubItem = (LISTVIEW_SUBITEM *) DPA_GetPtr(hdpaSubItems, i);
2030 if (lpSubItem && (lpSubItem->iSubItem == nSubItem))
2040 * Calculates the width of a specific item.
2043 * [I] infoPtr : valid pointer to the listview structure
2044 * [I] nItem : item to calculate width, or -1 for max of all
2047 * Returns the width of an item width an item.
2049 static INT LISTVIEW_CalculateItemWidth(LISTVIEW_INFO *infoPtr, INT nItem)
2051 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2052 INT nItemWidth = 0, i;
2054 if (uView == LVS_ICON)
2055 nItemWidth = infoPtr->iconSpacing.cx;
2056 else if (uView == LVS_REPORT)
2058 INT nHeaderItemCount;
2061 /* calculate width of header */
2062 nHeaderItemCount = Header_GetItemCount(infoPtr->hwndHeader);
2063 for (i = 0; i < nHeaderItemCount; i++)
2064 if (Header_GetItemRect(infoPtr->hwndHeader, i, &rcHeaderItem))
2065 nItemWidth += (rcHeaderItem.right - rcHeaderItem.left);
2071 if (infoPtr->nItemCount == 0) return DEFAULT_COLUMN_WIDTH;
2073 /* get width of string */
2076 for (i = 0; i < infoPtr->nItemCount; i++)
2078 nLabelWidth = LISTVIEW_GetLabelWidth(infoPtr, i);
2079 nItemWidth = max(nItemWidth, nLabelWidth);
2083 nItemWidth = LISTVIEW_GetLabelWidth(infoPtr, nItem);
2084 if (!nItemWidth) return DEFAULT_COLUMN_WIDTH;
2085 nItemWidth += WIDTH_PADDING;
2086 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2087 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2088 if (nItem == -1) nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth);
2091 return max(nItemWidth, 1);
2096 * Calculates the max width of any item in the list.
2099 * [I] infoPtr : valid pointer to the listview structure
2100 * [I] LONG : window style
2103 * Returns item width.
2105 static inline INT LISTVIEW_CalculateMaxWidth(LISTVIEW_INFO *infoPtr)
2107 return LISTVIEW_CalculateItemWidth(infoPtr, -1);
2112 * Retrieves and saves important text metrics info for the current
2116 * [I] infoPtr : valid pointer to the listview structure
2119 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2121 HDC hdc = GetDC(infoPtr->hwndSelf);
2122 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2123 HFONT hOldFont = SelectObject(hdc, hFont);
2126 if (GetTextMetricsW(hdc, &tm))
2127 infoPtr->ntmHeight = tm.tmHeight;
2128 SelectObject(hdc, hOldFont);
2129 ReleaseDC(infoPtr->hwndSelf, hdc);
2131 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2137 * Calculates the height of an item.
2140 * [I] infoPtr : valid pointer to the listview structure
2143 * Returns item height.
2145 static INT LISTVIEW_CalculateMaxHeight(LISTVIEW_INFO *infoPtr)
2149 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
2150 nItemHeight = infoPtr->iconSpacing.cy;
2153 nItemHeight = infoPtr->ntmHeight;
2154 if (infoPtr->himlState)
2155 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2156 if (infoPtr->himlSmall)
2157 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2158 if (infoPtr->himlState || infoPtr->himlSmall)
2159 nItemHeight += HEIGHT_PADDING;
2165 static void LISTVIEW_PrintSelectionRanges(LISTVIEW_INFO *infoPtr)
2167 ERR("Selections are:\n");
2168 ranges_dump(infoPtr->selectionRanges);
2174 * A compare function for ranges
2177 * [I] range1 : pointer to range 1;
2178 * [I] range2 : pointer to range 2;
2182 * > 0 : if range 1 > range 2
2183 * < 0 : if range 2 > range 1
2184 * = 0 : if range intersects range 2
2186 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2188 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2190 if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2195 static RANGES ranges_create(int count)
2197 RANGES ranges = (RANGES)COMCTL32_Alloc(sizeof(struct tagRANGES));
2198 if (!ranges) return NULL;
2199 ranges->hdpa = DPA_Create(count);
2200 if (ranges->hdpa) return ranges;
2201 COMCTL32_Free(ranges);
2205 static void ranges_destroy(RANGES ranges)
2207 if (!ranges) return;
2212 for(i = 0; i < ranges->hdpa->nItemCount; i++)
2213 COMCTL32_Free(DPA_GetPtr(ranges->hdpa, i));
2214 DPA_Destroy(ranges->hdpa);
2215 ranges->hdpa = NULL;
2217 COMCTL32_Free(ranges);
2220 static RANGES ranges_clone(RANGES ranges)
2225 if (!ranges) return NULL;
2226 clone = ranges_create(ranges->hdpa->nItemCount);
2227 if (!clone) return NULL;
2229 for (i = 0; i < ranges->hdpa->nItemCount; i++)
2231 RANGE *newrng = (RANGE *)COMCTL32_Alloc(sizeof(RANGE));
2234 ranges_destroy(clone);
2237 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2238 DPA_InsertPtr(clone->hdpa, i, newrng);
2243 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2247 if (!ranges || !sub) return ranges;
2249 for (i = 0; i < sub->hdpa->nItemCount; i++)
2250 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2255 static void ranges_dump(RANGES ranges)
2259 if (!ranges) return;
2260 for (i = 0; i < ranges->hdpa->nItemCount; i++)
2261 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2264 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2266 RANGE srchrng = { nItem, nItem + 1 };
2268 TRACE("(nItem=%d)\n", nItem);
2269 if (!ranges) return FALSE;
2270 if (TRACE_ON(listview)) ranges_dump(ranges);
2271 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2274 static INT ranges_itemcount(RANGES ranges)
2278 if (!ranges) return 0;
2279 for (i = 0; i < ranges->hdpa->nItemCount; i++)
2281 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2282 count += sel->upper - sel->lower;
2288 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2290 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2293 if (!ranges) return FALSE;
2294 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2295 if (index == -1) return TRUE;
2297 for (;index < ranges->hdpa->nItemCount; index++)
2299 chkrng = DPA_GetPtr(ranges->hdpa, index);
2300 if (chkrng->lower >= nItem)
2301 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2302 if (chkrng->upper > nItem)
2303 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2308 static BOOL ranges_add(RANGES ranges, RANGE range)
2313 TRACE("(%s)\n", debugrange(&range));
2314 if (!ranges) return FALSE;
2315 if (TRACE_ON(listview)) ranges_dump(ranges);
2317 /* try find overlapping regions first */
2318 srchrgn.lower = range.lower - 1;
2319 srchrgn.upper = range.upper + 1;
2320 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, 0);
2326 TRACE("Adding new range\n");
2328 /* create the brand new range to insert */
2329 newrgn = (RANGE *)COMCTL32_Alloc(sizeof(RANGE));
2330 if(!newrgn) return FALSE;
2333 /* figure out where to insert it */
2334 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_INSERTAFTER);
2335 if (index == -1) index = 0;
2337 /* and get it over with */
2338 DPA_InsertPtr(ranges->hdpa, index, newrgn);
2342 RANGE *chkrgn, *mrgrgn;
2343 INT fromindex, mergeindex;
2345 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2346 if (!chkrgn) return FALSE;
2347 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2349 chkrgn->lower = min(range.lower, chkrgn->lower);
2350 chkrgn->upper = max(range.upper, chkrgn->upper);
2352 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2354 /* merge now common anges */
2356 srchrgn.lower = chkrgn->lower - 1;
2357 srchrgn.upper = chkrgn->upper + 1;
2361 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2362 if (mergeindex == -1) break;
2363 if (mergeindex == index)
2365 fromindex = index + 1;
2369 TRACE("Merge with index %i\n", mergeindex);
2371 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2372 if (!mrgrgn) return FALSE;
2374 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2375 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2376 COMCTL32_Free(mrgrgn);
2377 DPA_DeletePtr(ranges->hdpa, mergeindex);
2378 if (mergeindex < index) index --;
2382 if (TRACE_ON(listview)) ranges_dump(ranges);
2386 static BOOL ranges_del(RANGES ranges, RANGE range)
2388 RANGE remrgn, tmprgn, *chkrgn;
2392 TRACE("(%s)\n", debugrange(&range));
2393 if (!ranges) return FALSE;
2398 index = DPA_Search(ranges->hdpa, &remrgn, 0, ranges_cmp, 0, 0);
2399 if (index == -1) return TRUE;
2401 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2402 if (!chkrgn) return FALSE;
2404 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2406 /* case 1: Same range */
2407 if ( (chkrgn->upper == remrgn.upper) &&
2408 (chkrgn->lower == remrgn.lower) )
2410 DPA_DeletePtr(ranges->hdpa, index);
2413 /* case 2: engulf */
2414 else if ( (chkrgn->upper <= remrgn.upper) &&
2415 (chkrgn->lower >= remrgn.lower) )
2417 DPA_DeletePtr(ranges->hdpa, index);
2419 /* case 3: overlap upper */
2420 else if ( (chkrgn->upper <= remrgn.upper) &&
2421 (chkrgn->lower < remrgn.lower) )
2423 chkrgn->upper = remrgn.lower;
2425 /* case 4: overlap lower */
2426 else if ( (chkrgn->upper > remrgn.upper) &&
2427 (chkrgn->lower >= remrgn.lower) )
2429 chkrgn->lower = remrgn.upper;
2431 /* case 5: fully internal */
2434 RANGE *newrgn = (RANGE *)COMCTL32_Alloc(sizeof(RANGE));
2435 if (!newrgn) return FALSE;
2437 newrgn->lower = chkrgn->lower;
2438 newrgn->upper = remrgn.lower;
2439 chkrgn->lower = remrgn.upper;
2440 DPA_InsertPtr(ranges->hdpa, index, newrgn);
2451 * Removes all selection ranges
2454 * [I] infoPtr : valid pointer to the listview structure
2455 * [I] toSkip : item range to skip removing the selection
2461 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2467 if (TRACE_ON(listview)) ranges_dump(toSkip);
2470 lvItem.stateMask = LVIS_SELECTED;
2472 /* need to clone the DPA because callbacks can change it */
2473 iterator_ranges(&i, ranges_diff(ranges_clone(infoPtr->selectionRanges), toSkip));
2474 while(iterator_next(&i))
2475 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2476 /* note that the iterator destructor will free the cloned range */
2477 iterator_destroy(&i);
2482 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2484 RANGES toSkip = ranges_create(1);
2486 if (!toSkip) return FALSE;
2487 ranges_additem(toSkip, nItem);
2488 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2489 ranges_destroy(toSkip);
2493 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2495 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2500 * Retrieves the number of items that are marked as selected.
2503 * [I] infoPtr : valid pointer to the listview structure
2506 * Number of items selected.
2508 static INT LISTVIEW_GetSelectedCount(LISTVIEW_INFO *infoPtr)
2510 INT nSelectedCount = 0;
2512 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2515 for (i = 0; i < infoPtr->nItemCount; i++)
2517 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2522 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2524 TRACE("nSelectedCount=%d\n", nSelectedCount);
2525 return nSelectedCount;
2530 * Manages the item focus.
2533 * [I] infoPtr : valid pointer to the listview structure
2534 * [I] INT : item index
2537 * TRUE : focused item changed
2538 * FALSE : focused item has NOT changed
2540 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2542 INT oldFocus = infoPtr->nFocusedItem;
2545 lvItem.state = LVIS_FOCUSED;
2546 lvItem.stateMask = LVIS_FOCUSED;
2547 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
2549 return oldFocus != infoPtr->nFocusedItem;
2552 /* Helper function for LISTVIEW_ShiftIndices *only* */
2553 static INT shift_item(LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2555 if (nShiftItem < nItem) return nShiftItem;
2557 if (nShiftItem > nItem) return nShiftItem + direction;
2559 if (direction > 0) return nShiftItem + direction;
2561 return min(nShiftItem, infoPtr->nItemCount - 1);
2566 * Updates the various indices after an item has been inserted or deleted.
2569 * [I] infoPtr : valid pointer to the listview structure
2570 * [I] nItem : item index
2571 * [I] direction : Direction of shift, +1 or -1.
2576 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
2580 TRACE("Shifting %iu, %i steps\n", nItem, direction);
2582 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
2584 assert(abs(direction) == 1);
2586 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
2588 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
2589 if (nNewFocus != infoPtr->nFocusedItem)
2590 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
2592 /* But we are not supposed to modify nHotItem! */
2598 * Adds a block of selections.
2601 * [I] infoPtr : valid pointer to the listview structure
2602 * [I] INT : item index
2607 static void LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2609 INT nFirst = min(infoPtr->nSelectionMark, nItem);
2610 INT nLast = max(infoPtr->nSelectionMark, nItem);
2617 item.state = LVIS_SELECTED;
2618 item.stateMask = LVIS_SELECTED;
2620 /* FIXME: this is not correct LVS_OWNERDATA
2621 * See docu for LVN_ITEMCHANGED. Is there something similar for
2622 * RemoveGroupSelection (is there such a thing?)?
2624 for (i = nFirst; i <= nLast; i++)
2625 LISTVIEW_SetItemState(infoPtr,i,&item);
2627 LISTVIEW_SetItemFocus(infoPtr, nItem);
2628 infoPtr->nSelectionMark = nItem;
2634 * Sets a single group selection.
2637 * [I] infoPtr : valid pointer to the listview structure
2638 * [I] INT : item index
2643 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2645 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2650 if (!(selection = ranges_create(100))) return;
2652 item.state = LVIS_SELECTED;
2653 item.stateMask = LVIS_SELECTED;
2655 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
2657 if (infoPtr->nSelectionMark == -1)
2659 infoPtr->nSelectionMark = nItem;
2660 ranges_additem(selection, nItem);
2666 sel.lower = min(infoPtr->nSelectionMark, nItem);
2667 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
2668 ranges_add(selection, sel);
2673 RECT rcItem, rcSel, rcSelMark;
2676 rcItem.left = LVIR_BOUNDS;
2677 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
2678 rcSelMark.left = LVIR_BOUNDS;
2679 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
2680 UnionRect(&rcSel, &rcItem, &rcSelMark);
2681 iterator_frameditems(&i, infoPtr, &rcSel);
2682 while(iterator_next(&i))
2684 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
2685 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
2687 iterator_destroy(&i);
2690 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
2691 iterator_ranges(&i, selection);
2692 while(iterator_next(&i))
2693 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
2694 /* this will also destroy the selection */
2695 iterator_destroy(&i);
2697 LISTVIEW_SetItemFocus(infoPtr, nItem);
2702 * Sets a single selection.
2705 * [I] infoPtr : valid pointer to the listview structure
2706 * [I] INT : item index
2711 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2715 TRACE("nItem=%d\n", nItem);
2717 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
2719 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
2720 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
2721 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
2723 infoPtr->nSelectionMark = nItem;
2728 * Set selection(s) with keyboard.
2731 * [I] infoPtr : valid pointer to the listview structure
2732 * [I] INT : item index
2735 * SUCCESS : TRUE (needs to be repainted)
2736 * FAILURE : FALSE (nothing has changed)
2738 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
2740 /* FIXME: pass in the state */
2741 LONG lStyle = infoPtr->dwStyle;
2742 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
2743 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
2744 BOOL bResult = FALSE;
2746 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
2748 if (lStyle & LVS_SINGLESEL)
2751 LISTVIEW_SetSelection(infoPtr, nItem);
2758 LISTVIEW_SetGroupSelection(infoPtr, nItem);
2762 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
2767 LISTVIEW_SetSelection(infoPtr, nItem);
2770 ListView_EnsureVisible(infoPtr->hwndSelf, nItem, FALSE);
2773 UpdateWindow(infoPtr->hwndSelf); /* update client area */
2780 * Called when the mouse is being actively tracked and has hovered for a specified
2784 * [I] infoPtr : valid pointer to the listview structure
2785 * [I] fwKeys : key indicator
2786 * [I] pts : mouse position
2789 * 0 if the message was processed, non-zero if there was an error
2792 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
2793 * over the item for a certain period of time.
2796 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKyes, POINTS pts)
2798 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
2799 /* FIXME: select the item!!! */
2800 /*LISTVIEW_GetItemAtPt(infoPtr, pt)*/;
2807 * Called whenever WM_MOUSEMOVE is received.
2810 * [I] infoPtr : valid pointer to the listview structure
2811 * [I] fwKeys : key indicator
2812 * [I] pts : mouse position
2815 * 0 if the message is processed, non-zero if there was an error
2817 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, POINTS pts)
2819 TRACKMOUSEEVENT trackinfo;
2821 /* see if we are supposed to be tracking mouse hovering */
2822 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
2823 /* fill in the trackinfo struct */
2824 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
2825 trackinfo.dwFlags = TME_QUERY;
2826 trackinfo.hwndTrack = infoPtr->hwndSelf;
2827 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
2829 /* see if we are already tracking this hwnd */
2830 _TrackMouseEvent(&trackinfo);
2832 if(!(trackinfo.dwFlags & TME_HOVER)) {
2833 trackinfo.dwFlags = TME_HOVER;
2835 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
2836 _TrackMouseEvent(&trackinfo);
2845 * Tests wheather the item is assignable to a list with style lStyle
2847 static inline BOOL is_assignable_item(LPLVITEMW lpLVItem, LONG lStyle)
2849 if ( (lpLVItem->mask & LVIF_TEXT) &&
2850 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
2851 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
2858 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
2861 * [I] infoPtr : valid pointer to the listview structure
2862 * [I] lpLVItem : valid pointer to new item atttributes
2863 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
2864 * [O] bChanged : will be set to TRUE if the item really changed
2870 static BOOL set_owner_item(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW, BOOL *bChanged)
2872 LONG lStyle = infoPtr->dwStyle;
2876 /* a virtual listview stores only the state for the main item */
2877 if (lpLVItem->iSubItem || !(lpLVItem->mask & LVIF_STATE)) return FALSE;
2879 oldState = (LVIS_FOCUSED | LVIS_SELECTED) & ~infoPtr->uCallbackMask;
2880 if (oldState) oldState = LISTVIEW_GetItemState(infoPtr, lpLVItem->iItem, oldState);
2881 TRACE("oldState=%x, newState=%x\n", oldState, lpLVItem->state);
2883 /* we're done if we don't need to change anything we handle */
2884 if ( !((oldState ^ lpLVItem->state) & lpLVItem->stateMask &
2885 ~infoPtr->uCallbackMask & (LVIS_FOCUSED | LVIS_SELECTED))) return TRUE;
2890 * As per MSDN LVN_ITEMCHANGING notifications are _NOT_ sent for
2891 * by LVS_OWERNDATA list controls
2894 /* if we handle the focus, and we're asked to change it, do it now */
2895 if ( lpLVItem->stateMask & LVIS_FOCUSED )
2897 if (lpLVItem->state & LVIS_FOCUSED)
2898 infoPtr->nFocusedItem = lpLVItem->iItem;
2899 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
2900 infoPtr->nFocusedItem = -1;
2903 /* and the selection is the only other state a virtual list may hold */
2904 if (lpLVItem->stateMask & LVIS_SELECTED)
2906 if (lpLVItem->state & LVIS_SELECTED)
2908 if (lStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
2909 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
2912 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
2915 /* notify the parent now that things have changed */
2916 ZeroMemory(&nmlv, sizeof(nmlv));
2917 nmlv.iItem = lpLVItem->iItem;
2918 nmlv.uNewState = lpLVItem->state;
2919 nmlv.uOldState = oldState;
2920 nmlv.uChanged = LVIF_STATE;
2921 notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
2928 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
2931 * [I] infoPtr : valid pointer to the listview structure
2932 * [I] lpLVItem : valid pointer to new item atttributes
2933 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
2934 * [O] bChanged : will be set to TRUE if the item really changed
2940 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW, BOOL *bChanged)
2942 LONG lStyle = infoPtr->dwStyle;
2944 LISTVIEW_ITEM *lpItem;
2946 UINT uChanged = 0, oldState;
2948 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
2949 if (!hdpaSubItems && hdpaSubItems != (HDPA)-1) return FALSE;
2951 lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0);
2952 if (!lpItem) return FALSE;
2954 /* we need to handle the focus, and selection differently */
2955 oldState = (LVIS_FOCUSED | LVIS_SELECTED) & ~infoPtr->uCallbackMask;
2956 if (oldState) oldState = LISTVIEW_GetItemState(infoPtr, lpLVItem->iItem, oldState);
2958 TRACE("oldState=0x%x, state=0x%x\n", oldState, lpItem->state);
2959 /* determine what fields will change */
2960 if ((lpLVItem->mask & LVIF_STATE) && ((oldState ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
2961 uChanged |= LVIF_STATE;
2963 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
2964 uChanged |= LVIF_IMAGE;
2966 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
2967 uChanged |= LVIF_PARAM;
2969 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
2970 uChanged |= LVIF_INDENT;
2972 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
2973 uChanged |= LVIF_TEXT;
2975 TRACE("uChanged=0x%x\n", uChanged);
2976 if (!uChanged) return TRUE;
2979 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
2980 nmlv.iItem = lpLVItem->iItem;
2981 nmlv.uNewState = lpLVItem->state & lpLVItem->stateMask;
2982 nmlv.uOldState = lpItem->state & lpLVItem->stateMask;
2983 nmlv.uChanged = uChanged;
2984 nmlv.lParam = lpItem->lParam;
2986 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
2987 if(lpItem->valid && notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
2990 /* copy information */
2991 if (lpLVItem->mask & LVIF_TEXT)
2992 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
2994 if (lpLVItem->mask & LVIF_IMAGE)
2995 lpItem->hdr.iImage = lpLVItem->iImage;
2997 if (lpLVItem->mask & LVIF_PARAM)
2998 lpItem->lParam = lpLVItem->lParam;
3000 if (lpLVItem->mask & LVIF_INDENT)
3001 lpItem->iIndent = lpLVItem->iIndent;
3003 if (uChanged & LVIF_STATE)
3005 lpItem->state &= ~lpLVItem->stateMask;
3006 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3007 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3009 if (lStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3010 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3012 else if (lpLVItem->stateMask & LVIS_SELECTED)
3013 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3015 /* if we are asked to change focus, and we manage it, do it */
3016 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3018 if (lpLVItem->state & LVIS_FOCUSED)
3020 infoPtr->nFocusedItem = lpLVItem->iItem;
3021 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, FALSE);
3023 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3024 infoPtr->nFocusedItem = -1;
3028 /* if we're inserting the item, we're done */
3029 if (!lpItem->valid) return TRUE;
3031 /* send LVN_ITEMCHANGED notification */
3032 nmlv.lParam = lpItem->lParam;
3033 notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3040 * Helper for LISTVIEW_SetItemT *only*: sets subitem attributes.
3043 * [I] infoPtr : valid pointer to the listview structure
3044 * [I] lpLVItem : valid pointer to new subitem atttributes
3045 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3046 * [O] bChanged : will be set to TRUE if the item really changed
3052 static BOOL set_sub_item(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW, BOOL *bChanged)
3055 LISTVIEW_SUBITEM *lpSubItem;
3057 /* set subitem only if column is present */
3058 if (Header_GetItemCount(infoPtr->hwndHeader) <= lpLVItem->iSubItem)
3061 /* First do some sanity checks */
3062 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE)) return FALSE;
3063 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE))) return TRUE;
3065 /* get the subitem structure, and create it if not there */
3066 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3067 if (!hdpaSubItems) return FALSE;
3069 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3072 LISTVIEW_SUBITEM *tmpSubItem;
3075 lpSubItem = (LISTVIEW_SUBITEM *)COMCTL32_Alloc(sizeof(LISTVIEW_SUBITEM));
3076 if (!lpSubItem) return FALSE;
3077 /* we could binary search here, if need be...*/
3078 for (i = 1; i < hdpaSubItems->nItemCount; i++)
3080 tmpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, i);
3081 if (tmpSubItem && tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3083 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3085 COMCTL32_Free(lpSubItem);
3088 lpSubItem->iSubItem = lpLVItem->iSubItem;
3092 if (lpLVItem->mask & LVIF_IMAGE)
3093 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3095 lpSubItem->hdr.iImage = lpLVItem->iImage;
3099 if (lpLVItem->mask & LVIF_TEXT)
3100 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3102 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3111 * Sets item attributes.
3114 * [I] infoPtr : valid pointer to the listview structure
3115 * [I] LPLVITEM : new item atttributes
3116 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3122 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
3124 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3125 INT nOldFocus = infoPtr->nFocusedItem;
3126 LPWSTR pszText = NULL;
3127 BOOL bResult, bChanged = FALSE;
3129 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3131 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3134 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3135 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3137 pszText = lpLVItem->pszText;
3138 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3141 /* actually set the fields */
3142 if (infoPtr->dwStyle & LVS_OWNERDATA)
3143 bResult = set_owner_item(infoPtr, lpLVItem, TRUE, &bChanged);
3146 /* sanity checks first */
3147 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3149 if (lpLVItem->iSubItem)
3150 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3152 bResult = set_main_item(infoPtr, lpLVItem, TRUE, &bChanged);
3155 /* redraw item, if necessary */
3156 if (bChanged && !infoPtr->bIsDrawing)
3158 if (nOldFocus != infoPtr->nFocusedItem && infoPtr->bFocus)
3159 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->rcFocus);
3161 /* this little optimization eliminates some nasty flicker */
3162 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3163 (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) || lpLVItem->iSubItem) )
3164 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3166 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3171 textfreeT(lpLVItem->pszText, isW);
3172 lpLVItem->pszText = pszText;
3180 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3183 * [I] infoPtr : valid pointer to the listview structure
3188 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *infoPtr)
3190 LONG lStyle = infoPtr->dwStyle;
3191 UINT uView = lStyle & LVS_TYPEMASK;
3193 SCROLLINFO scrollInfo;
3195 scrollInfo.cbSize = sizeof(SCROLLINFO);
3196 scrollInfo.fMask = SIF_POS;
3198 if (uView == LVS_LIST)
3200 if ((lStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3201 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3203 else if (uView == LVS_REPORT)
3205 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3206 nItem = scrollInfo.nPos;
3210 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3211 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3214 TRACE("nItem=%d\n", nItem);
3222 * Erases the background of the given rectangle
3225 * [I] infoPtr : valid pointer to the listview structure
3226 * [I] hdc : device context handle
3227 * [I] lprcBox : clipping rectangle
3233 static inline BOOL LISTVIEW_FillBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT* lprcBox)
3235 if (!infoPtr->hBkBrush) return FALSE;
3237 TRACE("(hdc=%x, lprcBox=%s, hBkBrush=%x)\n", hdc, debugrect(lprcBox), infoPtr->hBkBrush);
3239 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3247 * [I] infoPtr : valid pointer to the listview structure
3248 * [I] hdc : device context handle
3249 * [I] nItem : item index
3250 * [I] nSubItem : subitem index
3251 * [I] pos : item position in client coordinates
3252 * [I] cdmode : custom draw mode
3258 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3260 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3261 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3262 DWORD cditemmode = CDRF_DODEFAULT;
3263 RECT* lprcFocus, rcSelect, rcBox, rcState, rcIcon, rcLabel;
3264 NMLVCUSTOMDRAW nmlvcd;
3268 TRACE("(hdc=%x, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, debugpoint(&pos));
3270 /* get information needed for drawing the item */
3271 lvItem.mask = LVIF_TEXT | LVIF_IMAGE;
3272 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3273 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3274 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3275 lvItem.iItem = nItem;
3276 lvItem.iSubItem = nSubItem;
3277 lvItem.cchTextMax = DISP_TEXT_SIZE;
3278 lvItem.pszText = szDispText;
3279 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3280 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3282 /* now check if we need to update the focus rectangle */
3283 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3285 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3286 if (!LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel)) return FALSE;
3287 OffsetRect(&rcBox, pos.x, pos.y);
3288 OffsetRect(&rcState, pos.x, pos.y);
3289 OffsetRect(&rcIcon, pos.x, pos.y);
3290 OffsetRect(&rcLabel, pos.x, pos.y);
3291 TRACE(" rcBox=%s, rcState=%s, rcIcon=%s. rcLabel=%s\n",
3292 debugrect(&rcBox), debugrect(&rcState), debugrect(&rcIcon), debugrect(&rcLabel));
3294 /* fill in the custom draw structure */
3295 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox);
3296 nmlvcd.nmcd.dwItemSpec = lvItem.iItem;
3297 nmlvcd.iSubItem = lvItem.iSubItem;
3298 if (lvItem.state & LVIS_SELECTED) nmlvcd.nmcd.uItemState |= CDIS_SELECTED;
3299 if (lvItem.state & LVIS_FOCUSED) nmlvcd.nmcd.uItemState |= CDIS_FOCUS;
3300 if (lvItem.iItem == infoPtr->nHotItem) nmlvcd.nmcd.uItemState |= CDIS_HOT;
3301 nmlvcd.nmcd.lItemlParam = lvItem.lParam;
3302 if ((lvItem.state & LVIS_SELECTED) &&
3303 (lvItem.iSubItem == 0 ||
3304 (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))))
3306 if (infoPtr->bFocus)
3308 nmlvcd.clrTextBk = comctl32_color.clrHighlight;
3309 nmlvcd.clrText = comctl32_color.clrHighlightText;
3311 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
3313 nmlvcd.clrTextBk = comctl32_color.clr3dFace;
3314 nmlvcd.clrText = comctl32_color.clrBtnText;
3318 if (cdmode & CDRF_NOTIFYITEMDRAW)
3319 cditemmode = notify_customdraw (infoPtr, CDDS_ITEMPREPAINT, &nmlvcd);
3320 if (cditemmode & CDRF_SKIPDEFAULT) goto postpaint;
3323 if (infoPtr->himlState && !IsRectEmpty(&rcState))
3325 UINT uStateImage = (lvItem.state & LVIS_STATEIMAGEMASK) >> 12;
3327 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc, rcState.left, rcState.top, ILD_NORMAL);
3331 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3332 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3333 ImageList_Draw(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3334 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3336 /* Don't bother painting item being edited */
3337 if (infoPtr->hwndEdit && lprcFocus && nSubItem == 0) goto postpaint;
3339 /* Set the text attributes */
3340 SetBkMode(hdc, nmlvcd.clrTextBk == CLR_NONE ? TRANSPARENT : OPAQUE);
3341 if (nmlvcd.clrTextBk != CLR_NONE)
3342 SetBkColor(hdc, nmlvcd.clrTextBk == CLR_DEFAULT ? infoPtr->clrTextBkDefault : nmlvcd.clrTextBk);
3343 SetTextColor(hdc, nmlvcd.clrText);
3345 /* draw the selection background, if we're drawing the main item */
3349 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3350 rcSelect.right = rcBox.right;
3352 if (lvItem.state & LVIS_SELECTED)
3353 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3354 if(lprcFocus) *lprcFocus = rcSelect;
3357 /* figure out the text drawing flags */
3358 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3359 if (uView == LVS_ICON)
3360 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3363 INT align = DT_LEFT;
3368 lvColumn.mask = LVCF_FMT;
3369 LISTVIEW_GetColumnT(infoPtr, nSubItem, &lvColumn, TRUE);
3370 TRACE("lvColumn=%s\n", debuglvcolumn_t(&lvColumn, TRUE));
3371 if (lvColumn.fmt & LVCFMT_RIGHT) align = DT_RIGHT;
3372 else if (lvColumn.fmt & LVCFMT_CENTER) align = DT_CENTER;
3376 if (!(uFormat & (DT_RIGHT | DT_CENTER))) rcLabel.left += 2;
3377 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3380 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3381 notify_customdraw(infoPtr, CDDS_ITEMPOSTPAINT, &nmlvcd);
3387 * Draws listview items when in owner draw mode.
3390 * [I] infoPtr : valid pointer to the listview structure
3391 * [I] hdc : device context handle
3396 static void LISTVIEW_RefreshOwnerDraw(LISTVIEW_INFO *infoPtr, HDC hdc)
3398 UINT uID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
3399 HWND hwndParent = GetParent(infoPtr->hwndSelf);
3400 POINT Origin, Position;
3407 ZeroMemory(&dis, sizeof(dis));
3409 /* Get scroll info once before loop */
3410 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return;
3412 /* figure out what we need to draw */
3413 iterator_visibleitems(&i, infoPtr, hdc);
3415 /* send cache hint notification */
3416 if (infoPtr->dwStyle & LVS_OWNERDATA)
3418 RANGE range = iterator_range(&i);
3421 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
3422 nmlv.iFrom = range.lower;
3423 nmlv.iTo = range.upper - 1;
3424 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
3427 /* iterate through the invalidated rows */
3428 while(iterator_prev(&i))
3430 item.iItem = i.nItem;
3432 item.mask = LVIF_PARAM | LVIF_STATE;
3433 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3434 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3436 dis.CtlType = ODT_LISTVIEW;
3438 dis.itemID = item.iItem;
3439 dis.itemAction = ODA_DRAWENTIRE;
3441 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3442 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3443 dis.hwndItem = infoPtr->hwndSelf;
3445 if (!LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position)) continue;
3446 dis.rcItem.left = Position.x + Origin.x;
3447 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3448 dis.rcItem.top = Position.y + Origin.y;
3449 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3450 dis.itemData = item.lParam;
3452 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), debugrect(&dis.rcItem));
3453 SendMessageW(hwndParent, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3455 iterator_destroy(&i);
3460 * Draws listview items when in report display mode.
3463 * [I] infoPtr : valid pointer to the listview structure
3464 * [I] hdc : device context handle
3465 * [I] cdmode : custom draw mode
3470 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD cdmode)
3472 INT rgntype, nColumnCount, nFirstCol, nLastCol, nCol;
3474 COLUMNCACHE *lpCols;
3475 POINT Origin, Position;
3480 /* figure out what to draw */
3481 rgntype = GetClipBox(hdc, &rcClip);
3482 if (rgntype == NULLREGION) return;
3484 /* cache column info */
3485 nColumnCount = Header_GetItemCount(infoPtr->hwndHeader);
3486 lpCols = COMCTL32_Alloc(nColumnCount * sizeof(COLUMNCACHE));
3487 if (!lpCols) return;
3488 for (nCol = 0; nCol < nColumnCount; nCol++)
3490 Header_GetItemRect(infoPtr->hwndHeader, nCol, &lpCols[nCol].rc);
3491 TRACE("lpCols[%d].rc=%s\n", nCol, debugrect(&lpCols[nCol].rc));
3494 /* Get scroll info once before loop */
3495 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return;
3497 /* we now narrow the columns as well */
3498 nLastCol = nColumnCount - 1;
3499 for(nFirstCol = 0; nFirstCol < nColumnCount; nFirstCol++)
3500 if (lpCols[nFirstCol].rc.right + Origin.x >= rcClip.left) break;
3501 for(nLastCol = nColumnCount - 1; nLastCol >= 0; nLastCol--)
3502 if (lpCols[nLastCol].rc.left + Origin.x < rcClip.right) break;
3504 /* figure out what we need to draw */
3505 iterator_visibleitems(&i, infoPtr, hdc);
3507 /* a last few bits before we start drawing */
3508 TRACE("Colums=(%di - %d)\n", nFirstCol, nLastCol);
3510 /* iterate through the invalidated rows */
3511 while(iterator_prev(&i))
3513 /* iterate through the invalidated columns */
3514 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
3516 if (!LISTVIEW_GetItemOrigin(infoPtr, i.nItem, &Position)) continue;
3517 Position.x += Origin.x;
3518 Position.y += Origin.y;
3520 if (rgntype == COMPLEXREGION)
3523 rcItem.left = Position.x + lpCols[nCol].rc.left;
3524 rcItem.right = rcItem.left + (lpCols[nCol].rc.right - lpCols[nCol].rc.left);
3525 rcItem.top = Position.y;
3526 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
3527 if (!RectVisible(hdc, &rcItem)) continue;
3530 LISTVIEW_DrawItem(infoPtr, hdc, i.nItem, nCol, Position, cdmode);
3533 iterator_destroy(&i);
3535 /* cleanup the mess */
3536 COMCTL32_Free(lpCols);
3541 * Draws listview items when in list display mode.
3544 * [I] infoPtr : valid pointer to the listview structure
3545 * [I] hdc : device context handle
3546 * [I] cdmode : custom draw mode
3551 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD cdmode)
3553 POINT Origin, Position;
3556 /* Get scroll info once before loop */
3557 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return;
3559 /* figure out what we need to draw */
3560 iterator_visibleitems(&i, infoPtr, hdc);
3562 while(iterator_prev(&i))
3564 if (!LISTVIEW_GetItemOrigin(infoPtr, i.nItem, &Position)) continue;
3565 Position.x += Origin.x;
3566 Position.y += Origin.y;
3568 LISTVIEW_DrawItem(infoPtr, hdc, i.nItem, 0, Position, cdmode);
3570 iterator_destroy(&i);
3576 * Draws listview items.
3579 * [I] infoPtr : valid pointer to the listview structure
3580 * [I] HDC : device context handle
3585 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc)
3587 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3588 COLORREF oldTextColor, oldClrTextBk, oldClrText;
3589 NMLVCUSTOMDRAW nmlvcd;
3595 LISTVIEW_DUMP(infoPtr);
3597 infoPtr->bIsDrawing = TRUE;
3599 /* save dc values we're gonna trash while drawing */
3600 hOldFont = SelectObject(hdc, infoPtr->hFont);
3601 oldBkMode = GetBkMode(hdc);
3602 infoPtr->clrTextBkDefault = GetBkColor(hdc);
3603 oldTextColor = GetTextColor(hdc);
3605 oldClrTextBk = infoPtr->clrTextBk;
3606 oldClrText = infoPtr->clrText;
3608 GetClientRect(infoPtr->hwndSelf, &rcClient);
3609 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient);
3610 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3611 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
3613 /* Use these colors to draw the items */
3614 infoPtr->clrTextBk = nmlvcd.clrTextBk;
3615 infoPtr->clrText = nmlvcd.clrText;
3617 /* nothing to draw */
3618 if(infoPtr->nItemCount == 0) goto enddraw;
3620 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
3621 LISTVIEW_RefreshOwnerDraw(infoPtr, hdc);
3624 if (uView == LVS_REPORT)
3625 LISTVIEW_RefreshReport(infoPtr, hdc, cdmode);
3626 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
3627 LISTVIEW_RefreshList(infoPtr, hdc, cdmode);
3629 /* if we have a focus rect, draw it */
3630 if (infoPtr->bFocus)
3631 DrawFocusRect(hdc, &infoPtr->rcFocus);
3635 if (cdmode & CDRF_NOTIFYPOSTPAINT)
3636 notify_customdraw(infoPtr, CDDS_POSTPAINT, &nmlvcd);
3638 infoPtr->clrTextBk = oldClrTextBk;
3639 infoPtr->clrText = oldClrText;
3641 SelectObject(hdc, hOldFont);
3642 SetBkMode(hdc, oldBkMode);
3643 SetBkColor(hdc, infoPtr->clrTextBkDefault);
3644 SetTextColor(hdc, oldTextColor);
3645 infoPtr->bIsDrawing = FALSE;
3651 * Calculates the approximate width and height of a given number of items.
3654 * [I] infoPtr : valid pointer to the listview structure
3655 * [I] INT : number of items
3660 * Returns a DWORD. The width in the low word and the height in high word.
3662 static LRESULT LISTVIEW_ApproximateViewRect(LISTVIEW_INFO *infoPtr, INT nItemCount,
3663 WORD wWidth, WORD wHeight)
3665 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3666 INT nItemCountPerColumn = 1;
3667 INT nColumnCount = 0;
3668 DWORD dwViewRect = 0;
3670 if (nItemCount == -1)
3671 nItemCount = infoPtr->nItemCount;
3673 if (uView == LVS_LIST)
3675 if (wHeight == 0xFFFF)
3677 /* use current height */
3678 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
3681 if (wHeight < infoPtr->nItemHeight)
3682 wHeight = infoPtr->nItemHeight;
3686 if (infoPtr->nItemHeight > 0)
3688 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
3689 if (nItemCountPerColumn == 0)
3690 nItemCountPerColumn = 1;
3692 if (nItemCount % nItemCountPerColumn != 0)
3693 nColumnCount = nItemCount / nItemCountPerColumn;
3695 nColumnCount = nItemCount / nItemCountPerColumn + 1;
3699 /* Microsoft padding magic */
3700 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
3701 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
3703 dwViewRect = MAKELONG(wWidth, wHeight);
3705 else if (uView == LVS_REPORT)
3706 FIXME("uView == LVS_REPORT: not implemented\n");
3707 else if (uView == LVS_SMALLICON)
3708 FIXME("uView == LVS_SMALLICON: not implemented\n");
3709 else if (uView == LVS_ICON)
3710 FIXME("uView == LVS_ICON: not implemented\n");
3717 * Arranges listview items in icon display mode.
3720 * [I] infoPtr : valid pointer to the listview structure
3721 * [I] INT : alignment code
3727 static LRESULT LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
3729 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3730 BOOL bResult = FALSE;
3732 if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
3737 FIXME("nAlignCode=LVA_ALIGNLEFT: not implemented\n");
3740 FIXME("nAlignCode=LVA_ALIGNTOP: not implemented\n");
3743 FIXME("nAlignCode=LVA_DEFAULT: not implemented\n");
3745 case LVA_SNAPTOGRID:
3746 FIXME("nAlignCode=LVA_SNAPTOGRID: not implemented\n");
3754 /* << LISTVIEW_CreateDragImage >> */
3759 * Removes all listview items and subitems.
3762 * [I] infoPtr : valid pointer to the listview structure
3768 static LRESULT LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr)
3770 LONG lStyle = infoPtr->dwStyle;
3771 UINT uView = lStyle & LVS_TYPEMASK;
3772 LISTVIEW_ITEM *lpItem;
3773 LISTVIEW_SUBITEM *lpSubItem;
3776 BOOL bResult = FALSE;
3781 LISTVIEW_DeselectAll(infoPtr);
3782 infoPtr->nSelectionMark=-1;
3783 infoPtr->nFocusedItem=-1;
3784 SetRectEmpty(&infoPtr->rcFocus);
3785 /* But we are supposed to leave nHotItem as is! */
3787 if (lStyle & LVS_OWNERDATA)
3789 infoPtr->nItemCount = 0;
3790 LISTVIEW_InvalidateList(infoPtr);
3794 if (infoPtr->nItemCount > 0)
3798 /* send LVN_DELETEALLITEMS notification */
3799 /* verify if subsequent LVN_DELETEITEM notifications should be
3801 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3803 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
3805 for (i = 0; i < infoPtr->nItemCount; i++)
3807 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
3808 if (hdpaSubItems != NULL)
3810 for (j = 1; j < hdpaSubItems->nItemCount; j++)
3812 lpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, j);
3813 if (lpSubItem != NULL)
3815 /* free subitem string */
3816 if (is_textW(lpSubItem->hdr.pszText))
3817 COMCTL32_Free(lpSubItem->hdr.pszText);
3820 COMCTL32_Free(lpSubItem);
3824 lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0);
3829 /* send LVN_DELETEITEM notification */
3831 nmlv.lParam = lpItem->lParam;
3832 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
3835 /* free item string */
3836 if (is_textW(lpItem->hdr.pszText))
3837 COMCTL32_Free(lpItem->hdr.pszText);
3840 COMCTL32_Free(lpItem);
3843 DPA_Destroy(hdpaSubItems);
3847 /* reinitialize listview memory */
3848 bResult = DPA_DeleteAllPtrs(infoPtr->hdpaItems);
3849 infoPtr->nItemCount = 0;
3850 DPA_DeleteAllPtrs(infoPtr->hdpaPosX);
3851 DPA_DeleteAllPtrs(infoPtr->hdpaPosY);
3853 /* align items (set position of each item) */
3854 if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
3856 if (lStyle & LVS_ALIGNLEFT)
3858 LISTVIEW_AlignLeft(infoPtr);
3862 LISTVIEW_AlignTop(infoPtr);
3866 LISTVIEW_UpdateScroll(infoPtr);
3868 LISTVIEW_InvalidateList(infoPtr);
3876 * Removes a column from the listview control.
3879 * [I] infoPtr : valid pointer to the listview structure
3880 * [I] INT : column index
3886 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
3888 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3891 TRACE("nColumn=%d\n", nColumn);
3893 if (nColumn <= 0) return FALSE;
3895 if (uView == LVS_REPORT)
3897 if (!Header_GetItemRect(infoPtr->hwndHeader, nColumn, &rcCol))
3900 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
3904 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
3906 LISTVIEW_SUBITEM *lpSubItem, *lpDelItem;
3908 INT nItem, nSubItem, i;
3910 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
3912 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
3913 if (!hdpaSubItems) continue;
3916 for (i = 1; i < hdpaSubItems->nItemCount; i++)
3918 lpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, i);
3919 if (!lpSubItem) break;
3920 if (lpSubItem->iSubItem == nColumn)
3923 lpDelItem = lpSubItem;
3925 else if (lpSubItem->iSubItem > nColumn)
3927 lpSubItem->iSubItem--;
3931 /* if we found our subitem, zapp it */
3935 if (is_textW(lpDelItem->hdr.pszText))
3936 COMCTL32_Free(lpDelItem->hdr.pszText);
3939 COMCTL32_Free(lpDelItem);
3941 /* free dpa memory */
3942 DPA_DeletePtr(hdpaSubItems, nSubItem);
3947 /* we need to worry about display issues in report mode only */
3948 if (uView != LVS_REPORT) return TRUE;
3950 /* if we have a focus, must first erase the focus rect */
3951 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
3953 /* Need to reset the item width when deleting a column */
3954 infoPtr->nItemWidth -= rcCol.right - rcCol.left;
3956 /* update scrollbar(s) */
3957 LISTVIEW_UpdateScroll(infoPtr);
3959 /* scroll to cover the deleted column, and invalidate for redraw */
3960 rcOld = infoPtr->rcList;
3961 rcOld.left = rcCol.left;
3962 ScrollWindowEx(infoPtr->hwndSelf, -(rcCol.right - rcCol.left), 0,
3963 &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
3965 /* we can restore focus now */
3966 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
3973 * Removes an item from the listview control.
3976 * [I] infoPtr : valid pointer to the listview structure
3977 * [I] INT : item index
3983 static LRESULT LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
3985 LONG lStyle = infoPtr->dwStyle;
3986 UINT uView = lStyle & LVS_TYPEMASK;
3988 BOOL bResult = FALSE;
3990 LISTVIEW_ITEM *lpItem;
3991 LISTVIEW_SUBITEM *lpSubItem;
3995 TRACE("(nItem=%d)\n", nItem);
3998 /* First, send LVN_DELETEITEM notification. */
3999 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
4001 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
4003 if (nItem == infoPtr->nFocusedItem)
4005 infoPtr->nFocusedItem = -1;
4006 SetRectEmpty(&infoPtr->rcFocus);
4009 /* remove it from the selection range */
4010 item.state = LVIS_SELECTED;
4011 item.stateMask = LVIS_SELECTED;
4012 LISTVIEW_SetItemState(infoPtr,nItem,&item);
4014 if (lStyle & LVS_OWNERDATA)
4016 infoPtr->nItemCount--;
4017 LISTVIEW_InvalidateList(infoPtr); /*FIXME: optimize */
4021 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
4023 /* initialize memory */
4024 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4026 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4027 if (hdpaSubItems != NULL)
4029 infoPtr->nItemCount--;
4030 for (i = 1; i < hdpaSubItems->nItemCount; i++)
4032 lpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, i);
4033 if (lpSubItem != NULL)
4035 /* free item string */
4036 if (is_textW(lpSubItem->hdr.pszText))
4037 COMCTL32_Free(lpSubItem->hdr.pszText);
4040 COMCTL32_Free(lpSubItem);
4044 lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0);
4047 /* free item string */
4048 if (is_textW(lpItem->hdr.pszText))
4049 COMCTL32_Free(lpItem->hdr.pszText);
4052 COMCTL32_Free(lpItem);
4055 bResult = DPA_Destroy(hdpaSubItems);
4056 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4057 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4060 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4062 /* align items (set position of each item) */
4063 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4065 if (lStyle & LVS_ALIGNLEFT)
4066 LISTVIEW_AlignLeft(infoPtr);
4068 LISTVIEW_AlignTop(infoPtr);
4071 LISTVIEW_UpdateScroll(infoPtr);
4073 LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
4082 * Callback implementation for editlabel control
4085 * [I] infoPtr : valid pointer to the listview structure
4086 * [I] pszText : modified text
4087 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4093 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4095 NMLVDISPINFOW dispInfo;
4097 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4099 ZeroMemory(&dispInfo, sizeof(dispInfo));
4100 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
4101 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4102 dispInfo.item.iSubItem = 0;
4103 dispInfo.item.stateMask = ~0;
4104 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4105 /* add the text from the edit in */
4106 dispInfo.item.mask |= LVIF_TEXT;
4107 dispInfo.item.pszText = pszText;
4108 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4110 /* Do we need to update the Item Text */
4111 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4112 if (!pszText) return TRUE;
4114 ZeroMemory(&dispInfo, sizeof(dispInfo));
4115 dispInfo.item.mask = LVIF_TEXT;
4116 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4117 dispInfo.item.iSubItem = 0;
4118 dispInfo.item.pszText = pszText;
4119 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4120 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4125 * Begin in place editing of specified list view item
4128 * [I] infoPtr : valid pointer to the listview structure
4129 * [I] INT : item index
4130 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4136 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4138 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4139 NMLVDISPINFOW dispInfo;
4142 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4144 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4145 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4147 infoPtr->nEditLabelItem = nItem;
4149 /* Is the EditBox still there, if so remove it */
4150 if(infoPtr->hwndEdit != 0)
4152 SetFocus(infoPtr->hwndSelf);
4153 infoPtr->hwndEdit = 0;
4156 LISTVIEW_SetSelection(infoPtr, nItem);
4157 LISTVIEW_SetItemFocus(infoPtr, nItem);
4159 rect.left = LVIR_LABEL;
4160 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4162 ZeroMemory(&dispInfo, sizeof(dispInfo));
4163 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4164 dispInfo.item.iItem = nItem;
4165 dispInfo.item.iSubItem = 0;
4166 dispInfo.item.stateMask = ~0;
4167 dispInfo.item.pszText = szDispText;
4168 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4169 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4171 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4172 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4173 if (!infoPtr->hwndEdit) return 0;
4175 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4177 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4178 infoPtr->hwndEdit = 0;
4182 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4183 SetFocus(infoPtr->hwndEdit);
4184 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4185 return infoPtr->hwndEdit;
4191 * Ensures the specified item is visible, scrolling into view if necessary.
4194 * [I] infoPtr : valid pointer to the listview structure
4195 * [I] nItem : item index
4196 * [I] bPartial : partially or entirely visible
4202 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4204 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4205 INT nScrollPosHeight = 0;
4206 INT nScrollPosWidth = 0;
4207 INT nHorzAdjust = 0;
4208 INT nVertAdjust = 0;
4211 RECT rcItem, rcTemp;
4213 rcItem.left = LVIR_BOUNDS;
4214 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4216 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4218 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4220 /* scroll left/right, but in LVS_REPORT mode */
4221 if (uView == LVS_LIST)
4222 nScrollPosWidth = infoPtr->nItemWidth;
4223 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4224 nScrollPosWidth = 1;
4226 if (rcItem.left < infoPtr->rcList.left)
4229 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
4234 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
4238 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
4240 /* scroll up/down, but not in LVS_LIST mode */
4241 if (uView == LVS_REPORT)
4242 nScrollPosHeight = infoPtr->nItemHeight;
4243 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
4244 nScrollPosHeight = 1;
4246 if (rcItem.top < infoPtr->rcList.top)
4249 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
4254 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
4258 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
4260 if (nScrollPosWidth)
4262 INT diff = nHorzDiff / nScrollPosWidth;
4263 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
4264 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
4267 if (nScrollPosHeight)
4269 INT diff = nVertDiff / nScrollPosHeight;
4270 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
4271 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
4279 * Searches for an item with specific characteristics.
4282 * [I] hwnd : window handle
4283 * [I] nStart : base item index
4284 * [I] lpFindInfo : item information to look for
4287 * SUCCESS : index of item
4290 static LRESULT LISTVIEW_FindItemW(LISTVIEW_INFO *infoPtr, INT nStart,
4291 LPLVFINDINFOW lpFindInfo)
4293 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4294 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4295 BOOL bWrap = FALSE, bNearest = FALSE;
4296 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
4297 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
4298 POINT Position, Destination;
4301 if (!lpFindInfo || nItem < 0) return -1;
4304 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
4306 lvItem.mask |= LVIF_TEXT;
4307 lvItem.pszText = szDispText;
4308 lvItem.cchTextMax = DISP_TEXT_SIZE;
4311 if (lpFindInfo->flags & LVFI_WRAP)
4314 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
4315 (uView == LVS_ICON || uView ==LVS_SMALLICON))
4319 FIXME("LVFI_NEARESTXY is slow.\n");
4320 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return -1;
4321 Destination.x = lpFindInfo->pt.x - Origin.x;
4322 Destination.y = lpFindInfo->pt.y - Origin.y;
4323 switch(lpFindInfo->vkDirection)
4325 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
4326 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
4327 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
4328 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
4329 case VK_HOME: Destination.x = Destination.y = 0; break;
4330 case VK_END: Destination.x = infoPtr->rcView.right; Destination.y = infoPtr->rcView.bottom; break;
4331 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4332 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4333 default: FIXME("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
4338 /* if LVFI_PARAM is specified, all other flags are ignored */
4339 if (lpFindInfo->flags & LVFI_PARAM)
4341 lvItem.mask |= LVIF_PARAM;
4343 lvItem.mask &= ~LVIF_TEXT;
4347 for (; nItem < nLast; nItem++)
4349 lvItem.iItem = nItem;
4350 lvItem.iSubItem = 0;
4351 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
4353 if (lvItem.mask & LVIF_PARAM && lpFindInfo->lParam == lvItem.lParam)
4356 if (lvItem.mask & LVIF_TEXT)
4358 if (lpFindInfo->flags & LVFI_PARTIAL)
4360 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
4364 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
4368 if (!bNearest) return nItem;
4370 /* This is very inefficient. To do a good job here,
4371 * we need a sorted array of (x,y) item positions */
4372 if (!LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position)) continue;
4374 /* compute the distance^2 to the destination */
4375 xdist = Destination.x - Position.x;
4376 ydist = Destination.y - Position.y;
4377 dist = xdist * xdist + ydist * ydist;
4379 /* remember the distance, and item if it's closer */
4383 nNearestItem = nItem;
4390 nLast = min(nStart + 1, infoPtr->nItemCount);
4395 return nNearestItem;
4400 * Searches for an item with specific characteristics.
4403 * [I] hwnd : window handle
4404 * [I] nStart : base item index
4405 * [I] lpFindInfo : item information to look for
4408 * SUCCESS : index of item
4411 static LRESULT LISTVIEW_FindItemA(LISTVIEW_INFO *infoPtr, INT nStart,
4412 LPLVFINDINFOA lpFindInfo)
4414 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
4418 memcpy(&fiw, lpFindInfo, sizeof(fiw));
4419 if (hasText) fiw.psz = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
4420 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
4421 if (hasText) textfreeT((LPWSTR)fiw.psz, FALSE);
4427 * Retrieves the background image of the listview control.
4430 * [I] infoPtr : valid pointer to the listview structure
4431 * [O] LPLVMKBIMAGE : background image attributes
4437 /* static LRESULT LISTVIEW_GetBkImage(LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
4439 /* FIXME (listview, "empty stub!\n"); */
4445 * Retrieves column attributes.
4448 * [I] infoPtr : valid pointer to the listview structure
4449 * [I] INT : column index
4450 * [IO] LPLVCOLUMNW : column information
4451 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
4452 * otherwise it is in fact a LPLVCOLUMNA
4458 static LRESULT LISTVIEW_GetColumnT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVCOLUMNW lpColumn, BOOL isW)
4461 BOOL bResult = FALSE;
4463 if (lpColumn != NULL)
4466 /* initialize memory */
4467 ZeroMemory(&hdi, sizeof(hdi));
4469 if (lpColumn->mask & LVCF_FMT)
4470 hdi.mask |= HDI_FORMAT;
4472 if (lpColumn->mask & LVCF_WIDTH)
4473 hdi.mask |= HDI_WIDTH;
4475 if (lpColumn->mask & LVCF_TEXT)
4477 hdi.mask |= HDI_TEXT;
4478 hdi.cchTextMax = lpColumn->cchTextMax;
4479 hdi.pszText = lpColumn->pszText;
4482 if (lpColumn->mask & LVCF_IMAGE)
4483 hdi.mask |= HDI_IMAGE;
4485 if (lpColumn->mask & LVCF_ORDER)
4486 hdi.mask |= HDI_ORDER;
4489 bResult = Header_GetItemW(infoPtr->hwndHeader, nItem, &hdi);
4491 bResult = Header_GetItemA(infoPtr->hwndHeader, nItem, &hdi);
4495 if (lpColumn->mask & LVCF_FMT)
4499 if (hdi.fmt & HDF_LEFT)
4500 lpColumn->fmt |= LVCFMT_LEFT;
4501 else if (hdi.fmt & HDF_RIGHT)
4502 lpColumn->fmt |= LVCFMT_RIGHT;
4503 else if (hdi.fmt & HDF_CENTER)
4504 lpColumn->fmt |= LVCFMT_CENTER;
4506 if (hdi.fmt & HDF_IMAGE)
4507 lpColumn->fmt |= LVCFMT_COL_HAS_IMAGES;
4509 if (hdi.fmt & HDF_BITMAP_ON_RIGHT)
4510 lpColumn->fmt |= LVCFMT_BITMAP_ON_RIGHT;
4513 if (lpColumn->mask & LVCF_WIDTH)
4514 lpColumn->cx = hdi.cxy;
4516 if (lpColumn->mask & LVCF_IMAGE)
4517 lpColumn->iImage = hdi.iImage;
4519 if (lpColumn->mask & LVCF_ORDER)
4520 lpColumn->iOrder = hdi.iOrder;
4522 TRACE("(col=%d, lpColumn=%s, isW=%d)\n",
4523 nItem, debuglvcolumn_t(lpColumn, isW), isW);
4532 static LRESULT LISTVIEW_GetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
4539 /* FIXME: little hack */
4540 for (i = 0; i < iCount; i++)
4548 * Retrieves the column width.
4551 * [I] infoPtr : valid pointer to the listview structure
4552 * [I] int : column index
4555 * SUCCESS : column width
4558 static INT LISTVIEW_GetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn)
4560 INT nColumnWidth = 0;
4563 TRACE("nColumn=%d\n", nColumn);
4565 /* we have a 'column' in LIST and REPORT mode only */
4566 switch(infoPtr->dwStyle & LVS_TYPEMASK)
4569 nColumnWidth = infoPtr->nItemWidth;
4572 hdi.mask = HDI_WIDTH;
4573 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdi))
4574 nColumnWidth = hdi.cxy;
4578 TRACE("nColumnWidth=%d\n", nColumnWidth);
4579 return nColumnWidth;
4584 * In list or report display mode, retrieves the number of items that can fit
4585 * vertically in the visible area. In icon or small icon display mode,
4586 * retrieves the total number of visible items.
4589 * [I] infoPtr : valid pointer to the listview structure
4592 * Number of fully visible items.
4594 static LRESULT LISTVIEW_GetCountPerPage(LISTVIEW_INFO *infoPtr)
4596 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4599 if (uView == LVS_LIST)
4601 if (infoPtr->rcList.right > infoPtr->nItemWidth)
4603 nItemCount = LISTVIEW_GetCountPerRow(infoPtr) *
4604 LISTVIEW_GetCountPerColumn(infoPtr);
4607 else if (uView == LVS_REPORT)
4609 nItemCount = LISTVIEW_GetCountPerColumn(infoPtr);
4613 nItemCount = infoPtr->nItemCount;
4622 * Retrieves an image list handle.
4625 * [I] infoPtr : valid pointer to the listview structure
4626 * [I] nImageList : image list identifier
4629 * SUCCESS : image list handle
4632 static LRESULT LISTVIEW_GetImageList(LISTVIEW_INFO *infoPtr, INT nImageList)
4634 HIMAGELIST himl = NULL;
4639 himl = infoPtr->himlNormal;
4642 himl = infoPtr->himlSmall;
4645 himl = infoPtr->himlState;
4649 return (LRESULT)himl;
4652 /* LISTVIEW_GetISearchString */
4656 * Retrieves item attributes.
4659 * [I] hwnd : window handle
4660 * [IO] lpLVItem : item info
4661 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
4662 * if FALSE, the lpLVItem is a LPLVITEMA.
4665 * This is the internal 'GetItem' interface -- it tries to
4666 * be smart, and avoids text copies, if possible, by modifing
4667 * lpLVItem->pszText to point to the text string. Please note
4668 * that this is not always possible (e.g. OWNERDATA), so on
4669 * entry you *must* supply valid values for pszText, and cchTextMax.
4670 * The only difference to the documented interface is that upon
4671 * return, you should use *only* the lpLVItem->pszText, rather than
4672 * the buffer pointer you provided on input. Most code already does
4673 * that, so it's not a problem.
4674 * For the two cases when the text must be copied (that is,
4675 * for LVM_GETITEM, and LVMGETITEMTEXT), use LISTVIEW_GetItemExtT.
4681 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
4683 NMLVDISPINFOW dispInfo;
4684 LISTVIEW_ITEM *lpItem;
4688 /* In the following:
4689 * lpLVItem describes the information requested by the user
4690 * lpItem is what we have
4691 * dispInfo is a structure we use to request the missing
4692 * information from the application
4695 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
4697 if (!lpLVItem || (lpLVItem->iItem < 0) ||
4698 (lpLVItem->iItem >= infoPtr->nItemCount))
4701 /* a quick optimization if all we're asked is the focus state
4702 * these queries are worth optimising since they are common,
4703 * and can be answered in constant time, without the heavy accesses */
4704 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIF_STATE) &&
4705 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
4707 lpLVItem->state = 0;
4708 if (infoPtr->nFocusedItem == lpLVItem->iItem)
4709 lpLVItem->state |= LVIS_FOCUSED;
4713 ZeroMemory(&dispInfo, sizeof(dispInfo));
4715 /* if the app stores all the data, handle it separately */
4716 if (infoPtr->dwStyle & LVS_OWNERDATA)
4718 dispInfo.item.state = 0;
4720 /* if we need to callback, do it now */
4721 if ((lpLVItem->mask & ~LVIF_STATE) || infoPtr->uCallbackMask)
4723 /* NOTE: copy only fields which we _know_ are initialized, some apps
4724 * depend on the uninitialized fields being 0 */
4725 dispInfo.item.mask = lpLVItem->mask;
4726 dispInfo.item.iItem = lpLVItem->iItem;
4727 dispInfo.item.iSubItem = lpLVItem->iSubItem;
4728 if (lpLVItem->mask & LVIF_TEXT)
4730 dispInfo.item.pszText = lpLVItem->pszText;
4731 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
4733 if (lpLVItem->mask & LVIF_STATE)
4734 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
4735 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
4736 dispInfo.item.stateMask = lpLVItem->stateMask;
4737 *lpLVItem = dispInfo.item;
4738 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
4741 /* we store only a little state, so if we're not asked, we're done */
4742 if (!(lpLVItem->mask & LVIF_STATE) || lpLVItem->iSubItem) return TRUE;
4744 /* if focus is handled by us, report it */
4745 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
4747 lpLVItem->state &= ~LVIS_FOCUSED;
4748 if (infoPtr->nFocusedItem == lpLVItem->iItem)
4749 lpLVItem->state |= LVIS_FOCUSED;
4752 /* and do the same for selection, if we handle it */
4753 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
4755 lpLVItem->state &= ~LVIS_SELECTED;
4756 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
4757 lpLVItem->state |= LVIS_SELECTED;
4763 /* find the item and subitem structures before we proceed */
4764 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4765 if (hdpaSubItems == NULL) return FALSE;
4767 if ( !(lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0)) )
4770 if (lpLVItem->iSubItem)
4772 LISTVIEW_SUBITEM *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
4773 if(!lpSubItem) return FALSE;
4774 pItemHdr = &lpSubItem->hdr;
4777 pItemHdr = &lpItem->hdr;
4779 /* Do we need to query the state from the app? */
4780 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && lpLVItem->iSubItem == 0)
4782 dispInfo.item.mask |= LVIF_STATE;
4783 dispInfo.item.stateMask = infoPtr->uCallbackMask;
4786 /* Do we need to enquire about the image? */
4787 if ((lpLVItem->mask & LVIF_IMAGE) && (pItemHdr->iImage==I_IMAGECALLBACK))
4788 dispInfo.item.mask |= LVIF_IMAGE;
4790 /* Do we need to enquire about the text? */
4791 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
4793 dispInfo.item.mask |= LVIF_TEXT;
4794 dispInfo.item.pszText = lpLVItem->pszText;
4795 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
4796 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
4797 *dispInfo.item.pszText = '\0';
4800 /* If we don't have all the requested info, query the application */
4801 if (dispInfo.item.mask != 0)
4803 dispInfo.item.iItem = lpLVItem->iItem;
4804 dispInfo.item.iSubItem = lpLVItem->iSubItem;
4805 dispInfo.item.lParam = lpItem->lParam;
4806 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
4807 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
4810 /* Now, handle the iImage field */
4811 if (dispInfo.item.mask & LVIF_IMAGE)
4813 lpLVItem->iImage = dispInfo.item.iImage;
4814 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && (pItemHdr->iImage==I_IMAGECALLBACK))
4815 pItemHdr->iImage = dispInfo.item.iImage;
4817 else if (lpLVItem->mask & LVIF_IMAGE)
4818 lpLVItem->iImage = pItemHdr->iImage;
4820 /* The pszText field */
4821 if (dispInfo.item.mask & LVIF_TEXT)
4823 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
4824 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
4826 lpLVItem->pszText = dispInfo.item.pszText;
4828 else if (lpLVItem->mask & LVIF_TEXT)
4830 if (isW) lpLVItem->pszText = pItemHdr->pszText;
4831 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
4834 /* if this is a subitem, we're done*/
4835 if (lpLVItem->iSubItem) return TRUE;
4837 /* Next is the lParam field */
4838 if (dispInfo.item.mask & LVIF_PARAM)
4840 lpLVItem->lParam = dispInfo.item.lParam;
4841 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
4842 lpItem->lParam = dispInfo.item.lParam;
4844 else if (lpLVItem->mask & LVIF_PARAM)
4845 lpLVItem->lParam = lpItem->lParam;
4847 /* ... the state field (this one is different due to uCallbackmask) */
4848 if (lpLVItem->mask & LVIF_STATE)
4850 lpLVItem->state = lpItem->state;
4851 if (dispInfo.item.mask & LVIF_STATE)
4853 lpLVItem->state &= ~dispInfo.item.stateMask;
4854 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
4856 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
4858 lpLVItem->state &= ~LVIS_FOCUSED;
4859 if (infoPtr->nFocusedItem == lpLVItem->iItem)
4860 lpLVItem->state |= LVIS_FOCUSED;
4862 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
4864 lpLVItem->state &= ~LVIS_SELECTED;
4865 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
4866 lpLVItem->state |= LVIS_SELECTED;
4870 /* and last, but not least, the indent field */
4871 if (lpLVItem->mask & LVIF_INDENT)
4872 lpLVItem->iIndent = lpItem->iIndent;
4879 * Retrieves item attributes.
4882 * [I] hwnd : window handle
4883 * [IO] lpLVItem : item info
4884 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
4885 * if FALSE, the lpLVItem is a LPLVITEMA.
4888 * This is the external 'GetItem' interface -- it properly copies
4889 * the text in the provided buffer.
4895 static BOOL LISTVIEW_GetItemExtT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
4900 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
4903 pszText = lpLVItem->pszText;
4904 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
4905 if (bResult && lpLVItem->pszText != pszText)
4906 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
4907 lpLVItem->pszText = pszText;
4915 * Retrieves the position (upper-left) of the listview control item.
4916 * Note that for LVS_ICON style, the upper-left is that of the icon
4917 * and not the bounding box.
4920 * [I] infoPtr : valid pointer to the listview structure
4921 * [I] nItem : item index
4922 * [O] lpptPosition : coordinate information
4928 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
4930 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4933 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
4935 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4936 if (!LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition)) return FALSE;
4937 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return FALSE;
4939 if (uView == LVS_ICON)
4941 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
4942 lpptPosition->y += ICON_TOP_PADDING;
4944 lpptPosition->x += Origin.x;
4945 lpptPosition->y += Origin.y;
4947 TRACE (" lpptPosition=%s\n", debugpoint(lpptPosition));
4954 * Retrieves the bounding rectangle for a listview control item.
4957 * [I] infoPtr : valid pointer to the listview structure
4958 * [I] nItem : item index
4959 * [IO] lprc : bounding rectangle coordinates
4960 * lprc->left specifies the portion of the item for which the bounding
4961 * rectangle will be retrieved.
4963 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
4964 * including the icon and label.
4967 * * Experiment shows that native control returns:
4968 * * width = min (48, length of text line)
4969 * * .left = position.x - (width - iconsize.cx)/2
4970 * * .right = .left + width
4971 * * height = #lines of text * ntmHeight + icon height + 8
4972 * * .top = position.y - 2
4973 * * .bottom = .top + height
4974 * * separation between items .y = itemSpacing.cy - height
4975 * * .x = itemSpacing.cx - width
4976 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
4979 * * Experiment shows that native control returns:
4980 * * width = iconSize.cx + 16
4981 * * .left = position.x - (width - iconsize.cx)/2
4982 * * .right = .left + width
4983 * * height = iconSize.cy + 4
4984 * * .top = position.y - 2
4985 * * .bottom = .top + height
4986 * * separation between items .y = itemSpacing.cy - height
4987 * * .x = itemSpacing.cx - width
4988 * LVIR_LABEL Returns the bounding rectangle of the item text.
4991 * * Experiment shows that native control returns:
4992 * * width = text length
4993 * * .left = position.x - width/2
4994 * * .right = .left + width
4995 * * height = ntmH * linecount + 2
4996 * * .top = position.y + iconSize.cy + 6
4997 * * .bottom = .top + height
4998 * * separation between items .y = itemSpacing.cy - height
4999 * * .x = itemSpacing.cx - width
5000 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5001 * rectangles, but excludes columns in report view.
5008 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5009 * upon whether the window has the focus currently and on whether the item
5010 * is the one with the focus. Ensure that the control's record of which
5011 * item has the focus agrees with the items' records.
5013 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5015 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5016 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5017 BOOL doLabel = TRUE, oversizedBox = FALSE;
5018 POINT Position, Origin;
5022 TRACE("(hwnd=%x, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5024 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5025 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return FALSE;
5026 if (!LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position)) return FALSE;
5028 /* Be smart and try to figure out the minimum we have to do */
5029 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5030 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5031 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5032 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5033 oversizedBox = TRUE;
5035 /* get what we need from the item before hand, so we make
5036 * only one request. This can speed up things, if data
5037 * is stored on the app side */
5039 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5040 if (doLabel) lvItem.mask |= LVIF_TEXT;
5041 lvItem.iItem = nItem;
5042 lvItem.iSubItem = 0;
5043 lvItem.pszText = szDispText;
5044 lvItem.cchTextMax = DISP_TEXT_SIZE;
5045 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5046 /* we got the state already up, simulate it here, to avoid a reget */
5047 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5049 lvItem.mask |= LVIF_STATE;
5050 lvItem.stateMask = LVIS_FOCUSED;
5051 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5054 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5055 lprc->left = LVIR_BOUNDS;
5059 if (!LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL)) return FALSE;
5063 if (!LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, lprc)) return FALSE;
5067 if (!LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL)) return FALSE;
5070 case LVIR_SELECTBOUNDS:
5071 if (!LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, &label_rect)) return FALSE;
5072 UnionRect(lprc, lprc, &label_rect);
5076 WARN("Unknown value: %d\n", lprc->left);
5080 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5082 TRACE(" rect=%s\n", debugrect(lprc));
5089 * Retrieves the spacing between listview control items.
5092 * [I] infoPtr : valid pointer to the listview structure
5093 * [IO] lprc : rectangle to receive the output
5094 * on input, lprc->top = nSubItem
5095 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5097 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5098 * not only those of the first column.
5099 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5105 static BOOL LISTVIEW_GetSubItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5107 POINT Position, Origin;
5110 if (!lprc || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5112 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
5114 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return FALSE;
5115 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5117 lvItem.mask = lprc->top == 0 ? LVIF_INDENT : 0;
5118 lvItem.iItem = nItem;
5119 lvItem.iSubItem = lprc->top;
5121 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5125 if (!LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL)) return FALSE;
5130 if (!LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL)) return FALSE;
5134 ERR("Unknown bounds=%d\n", lprc->left);
5138 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5145 * Retrieves the width of a label.
5148 * [I] infoPtr : valid pointer to the listview structure
5151 * SUCCESS : string width (in pixels)
5154 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *infoPtr, INT nItem)
5156 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5159 TRACE("(nItem=%d)\n", nItem);
5161 lvItem.mask = LVIF_TEXT;
5162 lvItem.iItem = nItem;
5163 lvItem.iSubItem = 0;
5164 lvItem.pszText = szDispText;
5165 lvItem.cchTextMax = DISP_TEXT_SIZE;
5166 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5168 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5173 * Retrieves the spacing between listview control items.
5176 * [I] infoPtr : valid pointer to the listview structure
5177 * [I] BOOL : flag for small or large icon
5180 * Horizontal + vertical spacing
5182 static LRESULT LISTVIEW_GetItemSpacing(LISTVIEW_INFO *infoPtr, BOOL bSmall)
5188 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5192 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5193 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5195 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5202 * Retrieves the state of a listview control item.
5205 * [I] infoPtr : valid pointer to the listview structure
5206 * [I] nItem : item index
5207 * [I] uMask : state mask
5210 * State specified by the mask.
5212 static LRESULT LISTVIEW_GetItemState(LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5216 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5218 lvItem.iItem = nItem;
5219 lvItem.iSubItem = 0;
5220 lvItem.mask = LVIF_STATE;
5221 lvItem.stateMask = uMask;
5222 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5224 return lvItem.state & uMask;
5229 * Retrieves the text of a listview control item or subitem.
5232 * [I] hwnd : window handle
5233 * [I] nItem : item index
5234 * [IO] lpLVItem : item information
5235 * [I] isW : TRUE if lpLVItem is Unicode
5238 * SUCCESS : string length
5241 static LRESULT LISTVIEW_GetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
5243 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5245 lpLVItem->mask = LVIF_TEXT;
5246 lpLVItem->iItem = nItem;
5247 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
5249 return textlenT(lpLVItem->pszText, isW);
5254 * Searches for an item based on properties + relationships.
5257 * [I] infoPtr : valid pointer to the listview structure
5258 * [I] nItem : item index
5259 * [I] uFlags : relationship flag
5262 * This function is very, very inefficient! Needs work.
5265 * SUCCESS : item index
5268 static LRESULT LISTVIEW_GetNextItem(LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
5270 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5272 LVFINDINFOW lvFindInfo;
5273 INT nCountPerColumn;
5276 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
5277 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
5279 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
5281 if (uFlags & LVNI_CUT)
5284 if (uFlags & LVNI_DROPHILITED)
5285 uMask |= LVIS_DROPHILITED;
5287 if (uFlags & LVNI_FOCUSED)
5288 uMask |= LVIS_FOCUSED;
5290 if (uFlags & LVNI_SELECTED)
5291 uMask |= LVIS_SELECTED;
5293 /* if we're asked for the focused item, that's only one,
5294 * so it's worth optimizing */
5295 if (uFlags & LVNI_FOCUSED)
5297 if (!(LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) == uMask) return -1;
5298 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
5301 if (uFlags & LVNI_ABOVE)
5303 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5308 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5314 lvFindInfo.flags = LVFI_NEARESTXY;
5315 lvFindInfo.vkDirection = VK_UP;
5316 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5317 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5319 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5324 else if (uFlags & LVNI_BELOW)
5326 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5328 while (nItem < infoPtr->nItemCount)
5331 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5337 lvFindInfo.flags = LVFI_NEARESTXY;
5338 lvFindInfo.vkDirection = VK_DOWN;
5339 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5340 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5342 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5347 else if (uFlags & LVNI_TOLEFT)
5349 if (uView == LVS_LIST)
5351 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5352 while (nItem - nCountPerColumn >= 0)
5354 nItem -= nCountPerColumn;
5355 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5359 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5361 lvFindInfo.flags = LVFI_NEARESTXY;
5362 lvFindInfo.vkDirection = VK_LEFT;
5363 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5364 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5366 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5371 else if (uFlags & LVNI_TORIGHT)
5373 if (uView == LVS_LIST)
5375 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5376 while (nItem + nCountPerColumn < infoPtr->nItemCount)
5378 nItem += nCountPerColumn;
5379 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5383 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5385 lvFindInfo.flags = LVFI_NEARESTXY;
5386 lvFindInfo.vkDirection = VK_RIGHT;
5387 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5388 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5390 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5399 /* search by index */
5400 for (i = nItem; i < infoPtr->nItemCount; i++)
5402 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
5410 /* LISTVIEW_GetNumberOfWorkAreas */
5414 * Retrieves the origin coordinates when in icon or small icon display mode.
5417 * [I] infoPtr : valid pointer to the listview structure
5418 * [O] lpptOrigin : coordinate information
5424 static BOOL LISTVIEW_GetOrigin(LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
5426 DWORD lStyle = infoPtr->dwStyle;
5427 UINT uView = lStyle & LVS_TYPEMASK;
5428 INT nHorzPos = 0, nVertPos = 0;
5429 SCROLLINFO scrollInfo;
5431 if (!lpptOrigin) return FALSE;
5433 scrollInfo.cbSize = sizeof(SCROLLINFO);
5434 scrollInfo.fMask = SIF_POS;
5436 if ((lStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
5437 nHorzPos = scrollInfo.nPos;
5438 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
5439 nVertPos = scrollInfo.nPos;
5441 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
5443 lpptOrigin->x = infoPtr->rcList.left;
5444 lpptOrigin->y = infoPtr->rcList.top;
5445 if (uView == LVS_LIST)
5446 nHorzPos *= infoPtr->nItemWidth;
5447 else if (uView == LVS_REPORT)
5448 nVertPos *= infoPtr->nItemHeight;
5450 lpptOrigin->x -= nHorzPos;
5451 lpptOrigin->y -= nVertPos;
5453 TRACE(" origin=%s\n", debugpoint(lpptOrigin));
5460 * Retrieves the width of a string.
5463 * [I] hwnd : window handle
5464 * [I] lpszText : text string to process
5465 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
5468 * SUCCESS : string width (in pixels)
5471 static LRESULT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
5476 if (is_textT(lpszText, isW))
5478 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
5479 HDC hdc = GetDC(infoPtr->hwndSelf);
5480 HFONT hOldFont = SelectObject(hdc, hFont);
5483 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
5485 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
5486 SelectObject(hdc, hOldFont);
5487 ReleaseDC(infoPtr->hwndSelf, hdc);
5489 return stringSize.cx;
5494 * Determines which listview item is located at the specified position.
5497 * [I] infoPtr : valid pointer to the listview structure
5498 * [IO] lpht : hit test information
5499 * [I] subitem : fill out iSubItem.
5500 * [I] select : return the index only if the hit selects the item
5503 * (mm 20001022): We must not allow iSubItem to be touched, for
5504 * an app might pass only a structure with space up to iItem!
5505 * (MS Office 97 does that for instance in the file open dialog)
5508 * SUCCESS : item index
5511 static LRESULT LISTVIEW_HitTest(LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
5513 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5514 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5515 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
5516 POINT Origin, Position, opt;
5520 TRACE("(pt=%s, subitem=%d, select=%d)\n", debugpoint(&lpht->pt), subitem, select);
5524 if (subitem) lpht->iSubItem = 0;
5526 if (infoPtr->rcList.left > lpht->pt.x)
5527 lpht->flags |= LVHT_TOLEFT;
5528 else if (infoPtr->rcList.right < lpht->pt.x)
5529 lpht->flags |= LVHT_TORIGHT;
5531 if (infoPtr->rcList.top > lpht->pt.y)
5532 lpht->flags |= LVHT_ABOVE;
5533 else if (infoPtr->rcList.bottom < lpht->pt.y)
5534 lpht->flags |= LVHT_BELOW;
5536 TRACE("lpht->flags=0x%x\n", lpht->flags);
5537 if (lpht->flags) return -1;
5539 lpht->flags |= LVHT_NOWHERE;
5541 if (!LISTVIEW_GetOrigin(infoPtr, &Origin)) return -1;
5543 /* first deal with the large items */
5544 rcSearch.left = lpht->pt.x;
5545 rcSearch.top = lpht->pt.y;
5546 rcSearch.right = rcSearch.left + 1;
5547 rcSearch.bottom = rcSearch.top + 1;
5549 iterator_frameditems(&i, infoPtr, &rcSearch);
5550 iterator_next(&i); /* go to first item in the sequence */
5551 lpht->iItem = i.nItem;
5552 iterator_destroy(&i);
5554 TRACE("lpht->iItem=%d\n", lpht->iItem);
5555 if (lpht->iItem == -1) return -1;
5557 lvItem.mask = LVIF_STATE | LVIF_TEXT;
5558 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5559 lvItem.stateMask = LVIS_STATEIMAGEMASK;
5560 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
5561 lvItem.iItem = lpht->iItem;
5562 lvItem.iSubItem = 0;
5563 lvItem.pszText = szDispText;
5564 lvItem.cchTextMax = DISP_TEXT_SIZE;
5565 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
5566 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
5568 if (!LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel)) return -1;
5569 if (!LISTVIEW_GetItemOrigin(infoPtr, lpht->iItem, &Position)) return -1;
5570 opt.x = lpht->pt.x - Position.x - Origin.x;
5571 opt.y = lpht->pt.y - Position.y - Origin.y;
5573 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
5576 UnionRect(&rcBounds, &rcIcon, &rcLabel);
5577 TRACE("rcBounds=%s\n", debugrect(&rcBounds));
5578 if (!PtInRect(&rcBounds, opt)) return -1;
5580 if (PtInRect(&rcIcon, opt))
5581 lpht->flags |= LVHT_ONITEMICON;
5582 else if (PtInRect(&rcLabel, opt))
5583 lpht->flags |= LVHT_ONITEMLABEL;
5584 else if (infoPtr->himlState && ((lvItem.state & LVIS_STATEIMAGEMASK) >> 12) && PtInRect(&rcState, opt))
5585 lpht->flags |= LVHT_ONITEMSTATEICON;
5586 if (lpht->flags & LVHT_ONITEM)
5587 lpht->flags &= ~LVHT_NOWHERE;
5589 TRACE("lpht->flags=0x%x\n", lpht->flags);
5590 if (uView == LVS_REPORT && lpht->iItem != -1 && subitem)
5592 INT j, nColumnCount = Header_GetItemCount(infoPtr->hwndHeader);
5593 rcBounds.right = rcBounds.left;
5594 for (j = 0; j < nColumnCount; j++)
5596 rcBounds.left = rcBounds.right;
5597 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
5598 if (PtInRect(&rcBounds, opt))
5606 if (!select || lpht->iItem == -1) return lpht->iItem;
5608 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)) return lpht->iItem;
5610 if (uView == LVS_REPORT) UnionRect(&rcBounds, &rcIcon, &rcLabel);
5611 return PtInRect(&rcBounds, opt) ? lpht->iItem : -1;
5617 * Inserts a new column.
5620 * [I] infoPtr : valid pointer to the listview structure
5621 * [I] INT : column index
5622 * [I] LPLVCOLUMNW : column information
5625 * SUCCESS : new column index
5628 static LRESULT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
5629 LPLVCOLUMNW lpColumn, BOOL isW)
5635 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
5637 if (!lpColumn) return -1;
5639 hdi.mask = hdi.fmt = 0;
5640 if (lpColumn->mask & LVCF_FMT)
5642 /* format member is valid */
5643 hdi.mask |= HDI_FORMAT;
5645 /* set text alignment (leftmost column must be left-aligned) */
5646 if (nColumn == 0 || lpColumn->fmt & LVCFMT_LEFT)
5647 hdi.fmt |= HDF_LEFT;
5648 else if (lpColumn->fmt & LVCFMT_RIGHT)
5649 hdi.fmt |= HDF_RIGHT;
5650 else if (lpColumn->fmt & LVCFMT_CENTER)
5651 hdi.fmt |= HDF_CENTER;
5653 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
5654 hdi.fmt |= HDF_BITMAP_ON_RIGHT;
5656 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
5658 hdi.fmt |= HDF_IMAGE;
5659 hdi.iImage = I_IMAGECALLBACK;
5662 if (lpColumn->fmt & LVCFMT_IMAGE)
5663 ; /* FIXME: enable images for *(sub)items* this column */
5666 if (lpColumn->mask & LVCF_WIDTH)
5668 hdi.mask |= HDI_WIDTH;
5669 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
5671 /* make it fill the remainder of the controls width */
5676 /* get the width of every item except the current one */
5677 hdit.mask = HDI_WIDTH;
5680 for(item_index = 0; item_index < (nColumn - 1); item_index++)
5681 if (Header_GetItemW(infoPtr->hwndHeader, item_index, (LPARAM)(&hdit)))
5682 hdi.cxy += hdit.cxy;
5684 /* retrieve the layout of the header */
5685 GetClientRect(infoPtr->hwndSelf, &rcHeader);
5686 TRACE("start cxy=%d rcHeader=%s\n", hdi.cxy, debugrect(&rcHeader));
5688 hdi.cxy = (rcHeader.right - rcHeader.left) - hdi.cxy;
5691 hdi.cxy = lpColumn->cx;
5694 if (lpColumn->mask & LVCF_TEXT)
5696 hdi.mask |= HDI_TEXT | HDI_FORMAT;
5697 hdi.fmt |= HDF_STRING;
5698 hdi.pszText = lpColumn->pszText;
5699 hdi.cchTextMax = textlenT(lpColumn->pszText, isW);
5702 if (lpColumn->mask & LVCF_IMAGE)
5704 hdi.mask |= HDI_IMAGE;
5705 hdi.iImage = lpColumn->iImage;
5708 if (lpColumn->mask & LVCF_ORDER)
5710 hdi.mask |= HDI_ORDER;
5711 hdi.iOrder = lpColumn->iOrder;
5714 /* insert item in header control */
5715 nNewColumn = SendMessageW(infoPtr->hwndHeader,
5716 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
5717 (WPARAM)nColumn, (LPARAM)&hdi);
5718 if (nNewColumn == -1) return -1;
5719 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &rcCol)) return -1;
5721 /* now we have to actually adjust the data */
5722 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
5724 LISTVIEW_SUBITEM *lpSubItem, *lpMainItem, **lpNewItems = 0;
5728 /* preallocate memory, so we can fail gracefully */
5729 if (nNewColumn == 0)
5731 lpNewItems = COMCTL32_Alloc(sizeof(LISTVIEW_SUBITEM *) * infoPtr->nItemCount);
5732 if (!lpNewItems) return -1;
5733 for (i = 0; i < infoPtr->nItemCount; i++)
5734 if (!(lpNewItems[i] = COMCTL32_Alloc(sizeof(LISTVIEW_SUBITEM)))) break;
5735 if (i != infoPtr->nItemCount)
5737 for(; i >=0; i--) COMCTL32_Free(lpNewItems[i]);
5738 COMCTL32_Free(lpNewItems);
5743 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
5745 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
5746 if (!hdpaSubItems) continue;
5747 for (i = 1; i < hdpaSubItems->nItemCount; i++)
5749 lpSubItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, i);
5750 if (!lpSubItem) break;
5751 if (lpSubItem->iSubItem >= nNewColumn)
5752 lpSubItem->iSubItem++;
5755 /* if we found our subitem, zapp it */
5756 if (nNewColumn == 0)
5758 lpMainItem = (LISTVIEW_SUBITEM *)DPA_GetPtr(hdpaSubItems, 0);
5759 lpSubItem = lpNewItems[nItem];
5760 lpSubItem->hdr = lpMainItem->hdr;
5761 lpSubItem->iSubItem = 1;
5762 ZeroMemory(&lpMainItem->hdr, sizeof(lpMainItem->hdr));
5763 lpMainItem->iSubItem = 0;
5764 DPA_InsertPtr(hdpaSubItems, 1, lpSubItem);
5768 COMCTL32_Free(lpNewItems);
5771 /* we don't have to worry abiut display issues in non-report mode */
5772 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return nNewColumn;
5774 /* if we have a focus, must first erase the focus rect */
5775 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
5777 /* Need to reset the item width when inserting a new column */
5778 infoPtr->nItemWidth += rcCol.right - rcCol.left;
5780 LISTVIEW_UpdateScroll(infoPtr);
5782 /* scroll to cover the deleted column, and invalidate for redraw */
5783 rcOld = infoPtr->rcList;
5784 rcOld.left = rcCol.left;
5785 ScrollWindowEx(infoPtr->hwndSelf, rcCol.right - rcCol.left, 0,
5786 &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
5788 /* we can restore focus now */
5789 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
5794 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
5795 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
5796 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
5797 and not during the processing of a LVM_SORTITEMS message. Applications should provide
5798 their own sort proc. when sending LVM_SORTITEMS.
5801 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
5803 LVS_SORTXXX must be specified,
5804 LVS_OWNERDRAW is not set,
5805 <item>.pszText is not LPSTR_TEXTCALLBACK.
5807 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
5808 are sorted based on item text..."
5810 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
5812 LISTVIEW_ITEM* lv_first = (LISTVIEW_ITEM*) DPA_GetPtr( (HDPA)first, 0 );
5813 LISTVIEW_ITEM* lv_second = (LISTVIEW_ITEM*) DPA_GetPtr( (HDPA)second, 0 );
5814 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
5816 /* if we're sorting descending, negate the return value */
5817 return (((LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
5822 * Inserts a new item in the listview control.
5825 * [I] infoPtr : valid pointer to the listview structure
5826 * [I] lpLVItem : item information
5827 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
5830 * SUCCESS : new item index
5833 static LRESULT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5835 LONG lStyle = infoPtr->dwStyle;
5836 UINT uView = lStyle & LVS_TYPEMASK;
5840 LISTVIEW_ITEM *lpItem;
5843 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5845 if (lStyle & LVS_OWNERDATA)
5847 nItem = infoPtr->nItemCount;
5848 infoPtr->nItemCount++;
5852 /* make sure it's an item, and not a subitem; cannot insert a subitem */
5853 if (!lpLVItem || lpLVItem->iSubItem) return -1;
5855 if (!is_assignable_item(lpLVItem, lStyle)) return -1;
5857 if ( !(lpItem = (LISTVIEW_ITEM *)COMCTL32_Alloc(sizeof(LISTVIEW_ITEM))) )
5860 /* insert item in listview control data structure */
5861 if ( (hdpaSubItems = DPA_Create(8)) )
5862 nItem = DPA_InsertPtr(hdpaSubItems, 0, lpItem);
5863 if (nItem == -1) goto fail;
5865 /* FIXME: is the handling of this LVS_OWNERDRAWFIXED correct? */
5866 is_sorted = (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
5867 !(lStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
5869 nItem = DPA_InsertPtr( infoPtr->hdpaItems,
5870 is_sorted ? infoPtr->nItemCount + 1 : lpLVItem->iItem,
5872 if (nItem == -1) goto fail;
5873 infoPtr->nItemCount++;
5875 if (!LISTVIEW_SetItemT(infoPtr, lpLVItem, isW))
5878 /* if we're sorted, sort the list, and update the index */
5881 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
5882 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
5885 ERR("We can't find the item we just inserted, possible memory corruption.");
5886 /* we can't remove it from the list if we can't find it, so just fail */
5887 /* we don't deallocate memory here, as it will probably cause more problems */
5892 /* make room for the position, if we are in the right mode */
5893 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5895 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
5897 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
5899 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
5904 /* Add the subitem list to the items array. Do this last in case we go to
5905 * fail during the above.
5907 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
5909 lpItem->valid = TRUE;
5911 /* send LVN_INSERTITEM notification */
5912 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
5914 nmlv.lParam = lpItem->lParam;
5915 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
5917 /* align items (set position of each item) */
5918 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5920 if (lStyle & LVS_ALIGNLEFT) LISTVIEW_AlignLeft(infoPtr);
5921 else LISTVIEW_AlignTop(infoPtr);
5924 LISTVIEW_UpdateScroll(infoPtr);
5926 LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
5928 TRACE(" <- %d\n", nItem);
5932 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
5933 infoPtr->nItemCount--;
5935 DPA_DeletePtr(hdpaSubItems, 0);
5936 DPA_Destroy (hdpaSubItems);
5937 COMCTL32_Free (lpItem);
5943 * Redraws a range of items.
5946 * [I] infoPtr : valid pointer to the listview structure
5947 * [I] INT : first item
5948 * [I] INT : last item
5954 static LRESULT LISTVIEW_RedrawItems(LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
5958 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
5959 max(nFirst, nLast) >= infoPtr->nItemCount)
5962 for (i = nFirst; i <= nLast; i++)
5963 LISTVIEW_InvalidateItem(infoPtr, i);
5970 * Scroll the content of a listview.
5973 * [I] infoPtr : valid pointer to the listview structure
5974 * [I] INT : horizontal scroll amount in pixels
5975 * [I] INT : vertical scroll amount in pixels
5982 * If the control is in report mode (LVS_REPORT) the control can
5983 * be scrolled only in line increments. "dy" will be rounded to the
5984 * nearest number of pixels that are a whole line. Ex: if line height
5985 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
5986 * is passed the the scroll will be 0. (per MSDN 7/2002)
5988 * For: (per experimentaion with native control and CSpy ListView)
5989 * LVS_ICON dy=1 = 1 pixel (vertical only)
5991 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
5993 * LVS_LIST dx=1 = 1 column (horizontal only)
5994 * but will only scroll 1 column per message
5995 * no matter what the value.
5996 * dy must be 0 or FALSE returned.
5997 * LVS_REPORT dx=1 = 1 pixel
6001 static LRESULT LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6003 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6005 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6006 dy /= infoPtr->nItemHeight;
6009 if (dy != 0) return FALSE;
6016 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6017 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6024 * Sets the background color.
6027 * [I] infoPtr : valid pointer to the listview structure
6028 * [I] COLORREF : background color
6034 static LRESULT LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6036 TRACE("(clrBk=%lx)\n", clrBk);
6038 if(infoPtr->clrBk != clrBk) {
6039 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6040 infoPtr->clrBk = clrBk;
6041 if (clrBk == CLR_NONE)
6042 infoPtr->hBkBrush = GetClassLongW(infoPtr->hwndSelf, GCL_HBRBACKGROUND);
6044 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6045 LISTVIEW_InvalidateList(infoPtr);
6051 /* LISTVIEW_SetBkImage */
6055 * Sets the attributes of a header item.
6058 * [I] infoPtr : valid pointer to the listview structure
6059 * [I] INT : column index
6060 * [I] LPLVCOLUMNW : column attributes
6061 * [I] isW: if TRUE, the lpColumn is a LPLVCOLUMNW,
6062 * otherwise it is in fact a LPLVCOLUMNA
6068 static LRESULT LISTVIEW_SetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6069 LPLVCOLUMNW lpColumn, BOOL isW)
6071 BOOL bResult = FALSE;
6072 HDITEMW hdi, hdiget;
6074 if ((lpColumn != NULL) && (nColumn >= 0) &&
6075 (nColumn < Header_GetItemCount(infoPtr->hwndHeader)))
6077 /* initialize memory */
6078 ZeroMemory(&hdi, sizeof(hdi));
6080 if (lpColumn->mask & LVCF_FMT)
6082 /* format member is valid */
6083 hdi.mask |= HDI_FORMAT;
6085 /* get current format first */
6086 hdiget.mask = HDI_FORMAT;
6087 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6088 /* preserve HDF_STRING if present */
6089 hdi.fmt = hdiget.fmt & HDF_STRING;
6091 /* set text alignment (leftmost column must be left-aligned) */
6094 hdi.fmt |= HDF_LEFT;
6098 if (lpColumn->fmt & LVCFMT_LEFT)
6099 hdi.fmt |= HDF_LEFT;
6100 else if (lpColumn->fmt & LVCFMT_RIGHT)
6101 hdi.fmt |= HDF_RIGHT;
6102 else if (lpColumn->fmt & LVCFMT_CENTER)
6103 hdi.fmt |= HDF_CENTER;
6106 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6107 hdi.fmt |= HDF_BITMAP_ON_RIGHT;
6109 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6110 hdi.fmt |= HDF_IMAGE;
6112 if (lpColumn->fmt & LVCFMT_IMAGE)
6114 hdi.fmt |= HDF_IMAGE;
6115 hdi.iImage = I_IMAGECALLBACK;
6119 if (lpColumn->mask & LVCF_WIDTH)
6121 hdi.mask |= HDI_WIDTH;
6122 hdi.cxy = lpColumn->cx;
6125 if (lpColumn->mask & LVCF_TEXT)
6127 hdi.mask |= HDI_TEXT | HDI_FORMAT;
6128 hdi.pszText = lpColumn->pszText;
6129 hdi.cchTextMax = textlenT(lpColumn->pszText, isW);
6130 hdi.fmt |= HDF_STRING;
6133 if (lpColumn->mask & LVCF_IMAGE)
6135 hdi.mask |= HDI_IMAGE;
6136 hdi.iImage = lpColumn->iImage;
6139 if (lpColumn->mask & LVCF_ORDER)
6141 hdi.mask |= HDI_ORDER;
6142 hdi.iOrder = lpColumn->iOrder;
6145 /* set header item attributes */
6147 bResult = Header_SetItemW(infoPtr->hwndHeader, nColumn, &hdi);
6149 bResult = Header_SetItemA(infoPtr->hwndHeader, nColumn, &hdi);
6157 * Sets the column order array
6160 * [I] infoPtr : valid pointer to the listview structure
6161 * [I] INT : number of elements in column order array
6162 * [I] INT : pointer to column order array
6168 static LRESULT LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
6170 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6181 * Sets the width of a column
6184 * [I] infoPtr : valid pointer to the listview structure
6185 * [I] INT : column index
6186 * [I] INT : column width
6192 static LRESULT LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT iCol, INT cx)
6196 LONG lStyle = infoPtr->dwStyle;
6197 UINT uView = lStyle & LVS_TYPEMASK;
6202 WCHAR text_buffer[DISP_TEXT_SIZE];
6203 INT header_item_count;
6208 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6210 TRACE("(iCol=%d, cx=%d\n", iCol, cx);
6212 /* set column width only if in report or list mode */
6213 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6215 /* take care of invalid cx values */
6216 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
6217 else if (uView == LVS_LIST && cx < 1) return FALSE;
6219 /* resize all columns if in LVS_LIST mode */
6220 if(uView == LVS_LIST)
6222 infoPtr->nItemWidth = cx;
6223 LISTVIEW_InvalidateList(infoPtr);
6227 /* autosize based on listview items width */
6228 if(cx == LVSCW_AUTOSIZE)
6230 /* set the width of the column to the width of the widest item */
6231 if (iCol == 0 || uView == LVS_LIST)
6234 for(item_index = 0; item_index < infoPtr->nItemCount; item_index++)
6236 nLabelWidth = LISTVIEW_GetLabelWidth(infoPtr, item_index);
6237 cx = (nLabelWidth>cx)?nLabelWidth:cx;
6239 if (infoPtr->himlSmall)
6240 cx += infoPtr->iconSize.cx + IMAGE_PADDING;
6244 lvItem.iSubItem = iCol;
6245 lvItem.mask = LVIF_TEXT;
6246 lvItem.pszText = szDispText;
6247 lvItem.cchTextMax = DISP_TEXT_SIZE;
6249 for(item_index = 0; item_index < infoPtr->nItemCount; item_index++)
6251 lvItem.iItem = item_index;
6252 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6253 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6254 cx = (nLabelWidth>cx)?nLabelWidth:cx;
6257 cx += TRAILING_PADDING;
6258 } /* autosize based on listview header width */
6259 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
6261 header_item_count = Header_GetItemCount(infoPtr->hwndHeader);
6263 /* if iCol is the last column make it fill the remainder of the controls width */
6264 if(iCol == (header_item_count - 1)) {
6265 /* get the width of every item except the current one */
6266 hdi.mask = HDI_WIDTH;
6269 for(item_index = 0; item_index < (header_item_count - 1); item_index++) {
6270 Header_GetItemW(infoPtr->hwndHeader, item_index, (LPARAM)(&hdi));
6274 /* retrieve the layout of the header */
6275 GetWindowRect(infoPtr->hwndHeader, &rcHeader);
6277 cx = (rcHeader.right - rcHeader.left) - cx;
6281 /* Despite what the MS docs say, if this is not the last
6282 column, then MS resizes the column to the width of the
6283 largest text string in the column, including headers
6284 and items. This is different from LVSCW_AUTOSIZE in that
6285 LVSCW_AUTOSIZE ignores the header string length.
6288 /* retrieve header font */
6289 header_font = SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0L, 0L);
6291 /* retrieve header text */
6292 hdi.mask = HDI_TEXT;
6293 hdi.cchTextMax = sizeof(text_buffer)/sizeof(text_buffer[0]);
6294 hdi.pszText = text_buffer;
6296 Header_GetItemW(infoPtr->hwndHeader, iCol, (LPARAM)(&hdi));
6298 /* determine the width of the text in the header */
6299 hdc = GetDC(infoPtr->hwndSelf);
6300 old_font = SelectObject(hdc, header_font); /* select the font into hdc */
6302 GetTextExtentPoint32W(hdc, text_buffer, lstrlenW(text_buffer), &size);
6304 SelectObject(hdc, old_font); /* restore the old font */
6305 ReleaseDC(infoPtr->hwndSelf, hdc);
6307 lvItem.iSubItem = iCol;
6308 lvItem.mask = LVIF_TEXT;
6309 lvItem.pszText = szDispText;
6310 lvItem.cchTextMax = DISP_TEXT_SIZE;
6312 for(item_index = 0; item_index < infoPtr->nItemCount; item_index++)
6314 lvItem.iItem = item_index;
6315 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6316 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6317 nLabelWidth += TRAILING_PADDING;
6318 /* While it is possible for subitems to have icons, even MS messes
6319 up the positioning, so I suspect no applications actually use
6321 if (item_index == 0 && infoPtr->himlSmall)
6322 nLabelWidth += infoPtr->iconSize.cx + IMAGE_PADDING;
6323 cx = (nLabelWidth>cx)?nLabelWidth:cx;
6328 /* call header to update the column change */
6329 hdi.mask = HDI_WIDTH;
6332 lret = Header_SetItemW(infoPtr->hwndHeader, (WPARAM)iCol, (LPARAM)&hdi);
6334 LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
6341 * Sets the extended listview style.
6344 * [I] infoPtr : valid pointer to the listview structure
6349 * SUCCESS : previous style
6352 static LRESULT LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwStyle)
6354 DWORD dwOldStyle = infoPtr->dwLvExStyle;
6358 infoPtr->dwLvExStyle = (dwOldStyle & ~dwMask) | (dwStyle & dwMask);
6360 infoPtr->dwLvExStyle = dwStyle;
6367 * Sets the new hot cursor used during hot tracking and hover selection.
6370 * [I] infoPtr : valid pointer to the listview structure
6371 * [I} hCurosr : the new hot cursor handle
6374 * Returns the previous hot cursor
6376 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
6378 HCURSOR oldCursor = infoPtr->hHotCursor;
6379 infoPtr->hHotCursor = hCursor;
6386 * Sets the hot item index.
6389 * [I] infoPtr : valid pointer to the listview structure
6393 * SUCCESS : previous hot item index
6394 * FAILURE : -1 (no hot item)
6396 static LRESULT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
6398 INT iOldIndex = infoPtr->nHotItem;
6399 infoPtr->nHotItem = iIndex;
6406 * Sets the amount of time the cursor must hover over an item before it is selected.
6409 * [I] infoPtr : valid pointer to the listview structure
6410 * [I] DWORD : dwHoverTime, if -1 the hover time is set to the default
6413 * Returns the previous hover time
6415 static LRESULT LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
6417 DWORD oldHoverTime = infoPtr->dwHoverTime;
6418 infoPtr->dwHoverTime = dwHoverTime;
6419 return oldHoverTime;
6424 * Sets spacing for icons of LVS_ICON style.
6427 * [I] infoPtr : valid pointer to the listview structure
6428 * [I] DWORD : MAKELONG(cx, cy)
6431 * MAKELONG(oldcx, oldcy)
6433 static LRESULT LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, DWORD spacing)
6435 INT cy = HIWORD(spacing), cx = LOWORD(spacing);
6436 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6437 LONG lStyle = infoPtr->dwStyle;
6438 UINT uView = lStyle & LVS_TYPEMASK;
6440 TRACE("requested=(%d,%d)\n", cx, cy);
6442 /* this is supported only for LVS_ICON style */
6443 if (uView != LVS_ICON) return oldspacing;
6445 /* set to defaults, if instructed to */
6446 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
6447 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
6449 /* if 0 then compute width
6450 * FIXME: Should scan each item and determine max width of
6451 * icon or label, then make that the width */
6453 cx = infoPtr->iconSpacing.cx;
6455 /* if 0 then compute height */
6457 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
6458 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
6461 infoPtr->iconSpacing.cx = cx;
6462 infoPtr->iconSpacing.cy = cy;
6464 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%ld,%ld), ntmH=%d\n",
6465 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
6466 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
6467 infoPtr->ntmHeight);
6469 /* these depend on the iconSpacing */
6470 infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
6471 infoPtr->nItemHeight = LISTVIEW_CalculateMaxHeight(infoPtr);
6476 inline void update_icon_size(HIMAGELIST himl, SIZE *size)
6480 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
6486 size->cx = size->cy = 0;
6494 * [I] infoPtr : valid pointer to the listview structure
6495 * [I] INT : image list type
6496 * [I] HIMAGELIST : image list handle
6499 * SUCCESS : old image list
6502 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
6504 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6505 INT oldHeight = infoPtr->nItemHeight;
6506 HIMAGELIST himlOld = 0;
6511 himlOld = infoPtr->himlNormal;
6512 infoPtr->himlNormal = himl;
6513 if (uView == LVS_ICON) update_icon_size(himl, &infoPtr->iconSize);
6514 LISTVIEW_SetIconSpacing(infoPtr, 0);
6518 himlOld = infoPtr->himlSmall;
6519 infoPtr->himlSmall = himl;
6520 if (uView != LVS_ICON) update_icon_size(himl, &infoPtr->iconSize);
6524 himlOld = infoPtr->himlState;
6525 infoPtr->himlState = himl;
6526 update_icon_size(himl, &infoPtr->iconStateSize);
6527 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
6531 ERR("Unknown icon type=%d\n", nType);
6535 infoPtr->nItemHeight = LISTVIEW_CalculateMaxHeight(infoPtr);
6536 if (infoPtr->nItemHeight != oldHeight)
6537 LISTVIEW_UpdateScroll(infoPtr);
6544 * Preallocates memory (does *not* set the actual count of items !)
6547 * [I] infoPtr : valid pointer to the listview structure
6548 * [I] INT : item count (projected number of items to allocate)
6549 * [I] DWORD : update flags
6555 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
6557 TRACE("(nItems=%d, dwFlags=%lx)\n", nItems, dwFlags);
6559 if (infoPtr->dwStyle & LVS_OWNERDATA)
6561 int precount,topvisible;
6563 TRACE("LVS_OWNERDATA is set!\n");
6564 if (dwFlags & (LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL))
6565 FIXME("flags %s %s not implemented\n",
6566 (dwFlags & LVSICF_NOINVALIDATEALL) ? "LVSICF_NOINVALIDATEALL"
6568 (dwFlags & LVSICF_NOSCROLL) ? "LVSICF_NOSCROLL" : "");
6570 LISTVIEW_DeselectAll(infoPtr);
6572 precount = infoPtr->nItemCount;
6573 topvisible = LISTVIEW_GetTopIndex(infoPtr) +
6574 LISTVIEW_GetCountPerColumn(infoPtr) + 1;
6576 infoPtr->nItemCount = nItems;
6577 infoPtr->nItemWidth = max(LISTVIEW_CalculateMaxWidth(infoPtr),
6578 DEFAULT_COLUMN_WIDTH);
6580 LISTVIEW_UpdateSize(infoPtr);
6581 LISTVIEW_UpdateScroll(infoPtr);
6583 if (min(precount,infoPtr->nItemCount) < topvisible)
6584 LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
6588 /* According to MSDN for non-LVS_OWNERDATA this is just
6589 * a performance issue. The control allocates its internal
6590 * data structures for the number of items specified. It
6591 * cuts down on the number of memory allocations. Therefore
6592 * we will just issue a WARN here
6594 WARN("for non-ownerdata performance option not implemented.\n");
6602 * Sets the position of an item.
6605 * [I] infoPtr : valid pointer to the listview structure
6606 * [I] nItem : item index
6607 * [I] pt : coordinate
6613 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
6615 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6618 TRACE("(nItem=%d, &pt=%s\n", nItem, debugpoint(&pt));
6620 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
6621 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
6623 /* This point value seems to be an undocumented feature.
6624 * The best guess is that it means either at the origin,
6625 * or at true beginning of the list. I will assume the origin. */
6626 if ((pt.x == -1) && (pt.y == -1))
6627 LISTVIEW_GetOrigin(infoPtr, &pt);
6628 else if (uView == LVS_ICON)
6630 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
6631 pt.y -= ICON_TOP_PADDING;
6634 /* save the old position */
6635 old.x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
6636 old.y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
6638 /* Is the position changing? */
6639 if (pt.x == old.x && pt.y == old.y) return TRUE;
6641 /* FIXME: shouldn't we invalidate, as the item moved? */
6643 /* Allocating a POINTER for every item is too resource intensive,
6644 * so we'll keep the (x,y) in different arrays */
6645 if (DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)pt.x) &&
6646 DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)pt.y) )
6649 ERR("We should never fail here (nItem=%d, pt=%s), please report.\n",
6650 nItem, debugpoint(&pt));
6656 * Sets the state of one or many items.
6659 * [I] infoPtr : valid pointer to the listview structure
6660 * [I]INT : item index
6661 * [I] LPLVITEM : item or subitem info
6667 static LRESULT LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem)
6669 BOOL bResult = TRUE;
6672 lvItem.iItem = nItem;
6673 lvItem.iSubItem = 0;
6674 lvItem.mask = LVIF_STATE;
6675 lvItem.state = lpLVItem->state;
6676 lvItem.stateMask = lpLVItem->stateMask;
6677 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
6681 /* apply to all items */
6682 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6683 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
6686 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
6693 * Sets the text of an item or subitem.
6696 * [I] hwnd : window handle
6697 * [I] nItem : item index
6698 * [I] lpLVItem : item or subitem info
6699 * [I] isW : TRUE if input is Unicode
6705 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
6709 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
6711 lvItem.iItem = nItem;
6712 lvItem.iSubItem = lpLVItem->iSubItem;
6713 lvItem.mask = LVIF_TEXT;
6714 lvItem.pszText = lpLVItem->pszText;
6715 lvItem.cchTextMax = lpLVItem->cchTextMax;
6717 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
6719 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
6724 * Set item index that marks the start of a multiple selection.
6727 * [I] infoPtr : valid pointer to the listview structure
6731 * Index number or -1 if there is no selection mark.
6733 static LRESULT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
6735 INT nOldIndex = infoPtr->nSelectionMark;
6737 TRACE("(nIndex=%d)\n", nIndex);
6739 infoPtr->nSelectionMark = nIndex;
6746 * Sets the text background color.
6749 * [I] infoPtr : valid pointer to the listview structure
6750 * [I] COLORREF : text background color
6756 static LRESULT LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
6758 TRACE("(clrTextBk=%lx)\n", clrTextBk);
6760 if (infoPtr->clrTextBk != clrTextBk)
6762 infoPtr->clrTextBk = clrTextBk;
6763 LISTVIEW_InvalidateList(infoPtr);
6771 * Sets the text foreground color.
6774 * [I] infoPtr : valid pointer to the listview structure
6775 * [I] COLORREF : text color
6781 static LRESULT LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
6783 TRACE("(clrText=%lx)\n", clrText);
6785 if (infoPtr->clrText != clrText)
6787 infoPtr->clrText = clrText;
6788 LISTVIEW_InvalidateList(infoPtr);
6794 /* LISTVIEW_SetToolTips */
6795 /* LISTVIEW_SetUnicodeFormat */
6796 /* LISTVIEW_SetWorkAreas */
6800 * Callback internally used by LISTVIEW_SortItems()
6803 * [I] LPVOID : first LISTVIEW_ITEM to compare
6804 * [I] LPVOID : second LISTVIEW_ITEM to compare
6805 * [I] LPARAM : HWND of control
6808 * if first comes before second : negative
6809 * if first comes after second : positive
6810 * if first and second are equivalent : zero
6812 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
6814 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW((HWND)lParam, 0);
6815 LISTVIEW_ITEM* lv_first = (LISTVIEW_ITEM*) DPA_GetPtr( (HDPA)first, 0 );
6816 LISTVIEW_ITEM* lv_second = (LISTVIEW_ITEM*) DPA_GetPtr( (HDPA)second, 0 );
6818 /* Forward the call to the client defined callback */
6819 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
6824 * Sorts the listview items.
6827 * [I] infoPtr : valid pointer to the listview structure
6828 * [I] WPARAM : application-defined value
6829 * [I] LPARAM : pointer to comparision callback
6835 static LRESULT LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
6837 UINT lStyle = infoPtr->dwStyle;
6839 LISTVIEW_ITEM *lpItem;
6840 LPVOID selectionMarkItem;
6844 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
6846 if (lStyle & LVS_OWNERDATA) return FALSE;
6848 if (!infoPtr->hdpaItems) return FALSE;
6850 /* if there are 0 or 1 items, there is no need to sort */
6851 if (infoPtr->nItemCount < 2) return TRUE;
6853 if (infoPtr->nFocusedItem >= 0)
6855 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
6856 lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0);
6857 if (lpItem) lpItem->state |= LVIS_FOCUSED;
6859 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
6860 /* clear the lpItem->state for non-selected ones */
6861 /* remove the selection ranges */
6863 infoPtr->pfnCompare = pfnCompare;
6864 infoPtr->lParamSort = lParamSort;
6865 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr->hwndSelf);
6867 /* Adjust selections and indices so that they are the way they should
6868 * be after the sort (otherwise, the list items move around, but
6869 * whatever is at the item's previous original position will be
6872 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
6873 for (i=0; i < infoPtr->nItemCount; i++)
6875 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
6876 lpItem = (LISTVIEW_ITEM *)DPA_GetPtr(hdpaSubItems, 0);
6878 if (lpItem->state & LVIS_SELECTED)
6880 item.state = LVIS_SELECTED;
6881 item.stateMask = LVIS_SELECTED;
6882 LISTVIEW_SetItemState(infoPtr, i, &item);
6884 if (lpItem->state & LVIS_FOCUSED)
6886 infoPtr->nFocusedItem = i;
6887 lpItem->state &= ~LVIS_FOCUSED;
6890 if (selectionMarkItem != NULL)
6891 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
6892 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
6894 /* align the items */
6895 LISTVIEW_AlignTop(infoPtr);
6897 /* refresh the display */
6898 LISTVIEW_InvalidateList(infoPtr); /* FIXME: display should not change for [SMALL]ICON view */
6905 * Updates an items or rearranges the listview control.
6908 * [I] infoPtr : valid pointer to the listview structure
6909 * [I] INT : item index
6915 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
6917 LONG lStyle = infoPtr->dwStyle;
6918 UINT uView = lStyle & LVS_TYPEMASK;
6920 TRACE("(nItem=%d)\n", nItem);
6922 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
6924 /* rearrange with default alignment style */
6925 if ((lStyle & LVS_AUTOARRANGE) && ((uView == LVS_ICON) ||(uView == LVS_SMALLICON)))
6926 LISTVIEW_Arrange(infoPtr, 0);
6928 LISTVIEW_InvalidateItem(infoPtr, nItem);
6936 * Creates the listview control.
6939 * [I] hwnd : window handle
6940 * [I] lpcs : the create parameters
6946 static LRESULT LISTVIEW_Create(HWND hwnd, LPCREATESTRUCTW lpcs)
6948 LISTVIEW_INFO *infoPtr;
6949 UINT uView = lpcs->style & LVS_TYPEMASK;
6952 TRACE("(lpcs=%p)\n", lpcs);
6954 /* initialize info pointer */
6955 infoPtr = (LISTVIEW_INFO *)COMCTL32_Alloc(sizeof(LISTVIEW_INFO));
6956 if (!infoPtr) return -1;
6958 SetWindowLongW(hwnd, 0, (LONG)infoPtr);
6960 infoPtr->hwndSelf = hwnd;
6961 infoPtr->dwStyle = lpcs->style;
6962 /* determine the type of structures to use */
6963 infoPtr->notifyFormat = SendMessageW(GetParent(infoPtr->hwndSelf), WM_NOTIFYFORMAT,
6964 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
6966 /* initialize color information */
6967 infoPtr->clrBk = CLR_NONE;
6968 infoPtr->clrText = comctl32_color.clrWindowText;
6969 infoPtr->clrTextBk = CLR_DEFAULT;
6970 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
6972 /* set default values */
6973 infoPtr->nFocusedItem = -1;
6974 infoPtr->nSelectionMark = -1;
6975 infoPtr->nHotItem = -1;
6976 infoPtr->bRedraw = TRUE;
6977 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
6978 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
6979 infoPtr->nEditLabelItem = -1;
6981 /* get default font (icon title) */
6982 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
6983 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
6984 infoPtr->hFont = infoPtr->hDefaultFont;
6985 LISTVIEW_SaveTextMetrics(infoPtr);
6988 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, (LPCWSTR)NULL,
6989 WS_CHILD | HDS_HORZ | (DWORD)((LVS_NOSORTHEADER & lpcs->style)?0:HDS_BUTTONS),
6990 0, 0, 0, 0, hwnd, (HMENU)0,
6991 lpcs->hInstance, NULL);
6993 /* set header unicode format */
6994 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT,(WPARAM)TRUE,(LPARAM)NULL);
6996 /* set header font */
6997 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont,
7000 if (uView == LVS_ICON)
7002 infoPtr->iconSize.cx = GetSystemMetrics(SM_CXICON);
7003 infoPtr->iconSize.cy = GetSystemMetrics(SM_CYICON);
7005 else if (uView == LVS_REPORT)
7007 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
7009 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
7013 /* set HDS_HIDDEN flag to hide the header bar */
7014 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE,
7015 GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE) | HDS_HIDDEN);
7019 infoPtr->iconSize.cx = GetSystemMetrics(SM_CXSMICON);
7020 infoPtr->iconSize.cy = GetSystemMetrics(SM_CYSMICON);
7024 infoPtr->iconSize.cx = GetSystemMetrics(SM_CXSMICON);
7025 infoPtr->iconSize.cy = GetSystemMetrics(SM_CYSMICON);
7028 infoPtr->iconStateSize.cx = GetSystemMetrics(SM_CXSMICON);
7029 infoPtr->iconStateSize.cy = GetSystemMetrics(SM_CYSMICON);
7031 /* display unsupported listview window styles */
7032 LISTVIEW_UnsupportedStyles(lpcs->style);
7034 /* allocate memory for the data structure */
7035 infoPtr->hdpaItems = DPA_Create(10);
7036 infoPtr->hdpaPosX = DPA_Create(10);
7037 infoPtr->hdpaPosY = DPA_Create(10);
7039 /* allocate memory for the selection ranges */
7040 infoPtr->selectionRanges = ranges_create(10);
7042 /* initialize size of items */
7043 infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
7044 infoPtr->nItemHeight = LISTVIEW_CalculateMaxHeight(infoPtr);
7046 /* initialize the hover time to -1(indicating the default system hover time) */
7047 infoPtr->dwHoverTime = -1;
7054 * Erases the background of the listview control.
7057 * [I] infoPtr : valid pointer to the listview structure
7058 * [I] hdc : device context handle
7064 static inline BOOL LISTVIEW_EraseBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc)
7068 TRACE("(hdc=%x)\n", hdc);
7070 if (!GetClipBox(hdc, &rc)) return FALSE;
7072 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
7078 * Helper function for LISTVIEW_[HV]Scroll *only*.
7079 * Performs vertical/horizontal scrolling by a give amount.
7082 * [I] infoPtr : valid pointer to the listview structure
7083 * [I] dx : amount of horizontal scroll
7084 * [I] dy : amount of vertical scroll
7086 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7088 /* now we can scroll the list */
7089 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
7090 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
7091 /* if we have focus, adjust rect */
7092 OffsetRect(&infoPtr->rcFocus, dx, dy);
7093 UpdateWindow(infoPtr->hwndSelf);
7098 * Performs vertical scrolling.
7101 * [I] infoPtr : valid pointer to the listview structure
7102 * [I] nScrollCode : scroll code
7103 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7104 * [I] hScrollWnd : scrollbar control window handle
7110 * SB_LINEUP/SB_LINEDOWN:
7111 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
7112 * for LVS_REPORT is 1 line
7113 * for LVS_LIST cannot occur
7116 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7117 INT nScrollDiff, HWND hScrollWnd)
7119 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7120 INT nOldScrollPos, nNewScrollPos;
7121 SCROLLINFO scrollInfo;
7124 TRACE("(nScrollCode=%d, nScrollDiff=%d)\n", nScrollCode, nScrollDiff);
7126 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7128 scrollInfo.cbSize = sizeof(SCROLLINFO);
7129 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7131 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
7133 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
7135 nOldScrollPos = scrollInfo.nPos;
7136 switch (nScrollCode)
7142 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
7146 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
7150 nScrollDiff = -scrollInfo.nPage;
7154 nScrollDiff = scrollInfo.nPage;
7157 case SB_THUMBPOSITION:
7159 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7166 /* quit right away if pos isn't changing */
7167 if (nScrollDiff == 0) return 0;
7169 /* calculate new position, and handle overflows */
7170 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7171 if (nScrollDiff > 0) {
7172 if (nNewScrollPos < nOldScrollPos ||
7173 nNewScrollPos > scrollInfo.nMax)
7174 nNewScrollPos = scrollInfo.nMax;
7176 if (nNewScrollPos > nOldScrollPos ||
7177 nNewScrollPos < scrollInfo.nMin)
7178 nNewScrollPos = scrollInfo.nMin;
7181 /* set the new position, and reread in case it changed */
7182 scrollInfo.fMask = SIF_POS;
7183 scrollInfo.nPos = nNewScrollPos;
7184 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
7186 /* carry on only if it really changed */
7187 if (nNewScrollPos == nOldScrollPos) return 0;
7189 /* now adjust to client coordinates */
7190 nScrollDiff = nOldScrollPos - nNewScrollPos;
7191 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
7193 /* and scroll the window */
7194 scroll_list(infoPtr, 0, nScrollDiff);
7201 * Performs horizontal scrolling.
7204 * [I] infoPtr : valid pointer to the listview structure
7205 * [I] nScrollCode : scroll code
7206 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7207 * [I] hScrollWnd : scrollbar control window handle
7213 * SB_LINELEFT/SB_LINERIGHT:
7214 * for LVS_ICON, LVS_SMALLICON 1 pixel
7215 * for LVS_REPORT is 1 pixel
7216 * for LVS_LIST is 1 column --> which is a 1 because the
7217 * scroll is based on columns not pixels
7220 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7221 INT nScrollDiff, HWND hScrollWnd)
7223 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7224 INT nOldScrollPos, nNewScrollPos;
7225 SCROLLINFO scrollInfo;
7227 TRACE("(nScrollCode=%d, nScrollDiff=%d)\n", nScrollCode, nScrollDiff);
7229 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7231 scrollInfo.cbSize = sizeof(SCROLLINFO);
7232 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7234 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
7236 nOldScrollPos = scrollInfo.nPos;
7238 switch (nScrollCode)
7252 nScrollDiff = -scrollInfo.nPage;
7256 nScrollDiff = scrollInfo.nPage;
7259 case SB_THUMBPOSITION:
7261 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7268 /* quit right away if pos isn't changing */
7269 if (nScrollDiff == 0) return 0;
7271 /* calculate new position, and handle overflows */
7272 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7273 if (nScrollDiff > 0) {
7274 if (nNewScrollPos < nOldScrollPos ||
7275 nNewScrollPos > scrollInfo.nMax)
7276 nNewScrollPos = scrollInfo.nMax;
7278 if (nNewScrollPos > nOldScrollPos ||
7279 nNewScrollPos < scrollInfo.nMin)
7280 nNewScrollPos = scrollInfo.nMin;
7283 /* set the new position, and reread in case it changed */
7284 scrollInfo.fMask = SIF_POS;
7285 scrollInfo.nPos = nNewScrollPos;
7286 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
7288 /* carry on only if it really changed */
7289 if (nNewScrollPos == nOldScrollPos) return 0;
7291 if(uView == LVS_REPORT)
7292 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
7294 /* now adjust to client coordinates */
7295 nScrollDiff = nOldScrollPos - nNewScrollPos;
7296 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
7298 /* and scroll the window */
7299 scroll_list(infoPtr, nScrollDiff, 0);
7304 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
7306 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7307 INT gcWheelDelta = 0;
7308 UINT pulScrollLines = 3;
7309 SCROLLINFO scrollInfo;
7311 TRACE("(wheelDelta=%d)\n", wheelDelta);
7313 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
7314 gcWheelDelta -= wheelDelta;
7316 scrollInfo.cbSize = sizeof(SCROLLINFO);
7317 scrollInfo.fMask = SIF_POS;
7324 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
7325 * should be fixed in the future.
7327 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
7328 LISTVIEW_VScroll(infoPtr, SB_THUMBPOSITION,
7329 scrollInfo.nPos + (gcWheelDelta < 0) ?
7330 LISTVIEW_SCROLL_ICON_LINE_SIZE :
7331 -LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
7335 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
7337 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
7339 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
7340 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
7341 LISTVIEW_VScroll(infoPtr, SB_THUMBPOSITION, scrollInfo.nPos + cLineScroll, 0);
7347 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
7358 * [I] infoPtr : valid pointer to the listview structure
7359 * [I] INT : virtual key
7360 * [I] LONG : key data
7365 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
7367 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7369 NMLVKEYDOWN nmKeyDown;
7371 TRACE("(nVirtualKey=%d, lKeyData=%ld)\n", nVirtualKey, lKeyData);
7373 /* send LVN_KEYDOWN notification */
7374 nmKeyDown.wVKey = nVirtualKey;
7375 nmKeyDown.flags = 0;
7376 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
7378 switch (nVirtualKey)
7381 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
7383 notify(infoPtr, NM_RETURN);
7384 notify(infoPtr, LVN_ITEMACTIVATE);
7389 if (infoPtr->nItemCount > 0)
7394 if (infoPtr->nItemCount > 0)
7395 nItem = infoPtr->nItemCount - 1;
7399 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
7403 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
7407 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
7411 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
7415 if (uView == LVS_REPORT)
7416 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr);
7418 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
7419 * LISTVIEW_GetCountPerRow(infoPtr);
7420 if(nItem < 0) nItem = 0;
7424 if (uView == LVS_REPORT)
7425 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr);
7427 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
7428 * LISTVIEW_GetCountPerRow(infoPtr);
7429 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
7433 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem))
7434 LISTVIEW_KeySelection(infoPtr, nItem);
7444 * [I] infoPtr : valid pointer to the listview structure
7449 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
7453 /* if we did not have the focus, there's nothing to do */
7454 if (!infoPtr->bFocus) return 0;
7456 /* send NM_KILLFOCUS notification */
7457 notify(infoPtr, NM_KILLFOCUS);
7459 /* if we have a focus rectagle, get rid of it */
7460 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
7462 /* set window focus flag */
7463 infoPtr->bFocus = FALSE;
7465 /* invalidate the selected items before reseting focus flag */
7466 LISTVIEW_InvalidateSelectedItems(infoPtr);
7473 * Processes double click messages (left mouse button).
7476 * [I] infoPtr : valid pointer to the listview structure
7477 * [I] wKey : key flag
7478 * [I] pts : mouse coordinate
7483 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7485 LVHITTESTINFO htInfo;
7487 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7489 /* send NM_RELEASEDCAPTURE notification */
7490 notify(infoPtr, NM_RELEASEDCAPTURE);
7492 htInfo.pt.x = pts.x;
7493 htInfo.pt.y = pts.y;
7495 /* send NM_DBLCLK notification */
7496 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
7497 notify_click(infoPtr, NM_DBLCLK, &htInfo);
7499 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
7500 if(htInfo.iItem != -1) notify(infoPtr, LVN_ITEMACTIVATE);
7507 * Processes mouse down messages (left mouse button).
7510 * [I] infoPtr : valid pointer to the listview structure
7511 * [I] wKey : key flag
7512 * [I] pts : mouse coordinate
7517 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7519 LVHITTESTINFO lvHitTestInfo;
7520 LONG lStyle = infoPtr->dwStyle;
7521 static BOOL bGroupSelect = TRUE;
7522 POINT pt = { pts.x, pts.y };
7525 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7527 /* send NM_RELEASEDCAPTURE notification */
7528 notify(infoPtr, NM_RELEASEDCAPTURE);
7530 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
7532 /* set left button down flag */
7533 infoPtr->bLButtonDown = TRUE;
7535 lvHitTestInfo.pt.x = pts.x;
7536 lvHitTestInfo.pt.y = pts.y;
7538 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
7539 TRACE("at %s, nItem=%d\n", debugpoint(&pt), nItem);
7540 infoPtr->nEditLabelItem = -1;
7541 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
7543 if (lStyle & LVS_SINGLESEL)
7545 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
7546 infoPtr->nEditLabelItem = nItem;
7548 LISTVIEW_SetSelection(infoPtr, nItem);
7552 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
7555 LISTVIEW_AddGroupSelection(infoPtr, nItem);
7560 item.state = LVIS_SELECTED | LVIS_FOCUSED;
7561 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
7563 LISTVIEW_SetItemState(infoPtr,nItem,&item);
7564 infoPtr->nSelectionMark = nItem;
7567 else if (wKey & MK_CONTROL)
7571 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
7573 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
7574 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
7575 LISTVIEW_SetItemState(infoPtr, nItem, &item);
7576 infoPtr->nSelectionMark = nItem;
7578 else if (wKey & MK_SHIFT)
7580 LISTVIEW_SetGroupSelection(infoPtr, nItem);
7584 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
7585 infoPtr->nEditLabelItem = nItem;
7587 /* set selection (clears other pre-existing selections) */
7588 LISTVIEW_SetSelection(infoPtr, nItem);
7594 /* remove all selections */
7595 LISTVIEW_DeselectAll(infoPtr);
7603 * Processes mouse up messages (left mouse button).
7606 * [I] infoPtr : valid pointer to the listview structure
7607 * [I] wKey : key flag
7608 * [I] pts : mouse coordinate
7613 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7615 LVHITTESTINFO lvHitTestInfo;
7617 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7619 if (!infoPtr->bLButtonDown) return 0;
7621 lvHitTestInfo.pt.x = pts.x;
7622 lvHitTestInfo.pt.y = pts.y;
7624 /* send NM_CLICK notification */
7625 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
7626 notify_click(infoPtr, NM_CLICK, &lvHitTestInfo);
7628 /* set left button flag */
7629 infoPtr->bLButtonDown = FALSE;
7631 /* if we clicked on a selected item, edit the label */
7632 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
7633 LISTVIEW_EditLabelT(infoPtr, lvHitTestInfo.iItem, TRUE);
7640 * Destroys the listview control (called after WM_DESTROY).
7643 * [I] infoPtr : valid pointer to the listview structure
7648 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
7650 LONG lStyle = infoPtr->dwStyle;
7654 /* delete all items */
7655 LISTVIEW_DeleteAllItems(infoPtr);
7657 /* destroy data structure */
7658 DPA_Destroy(infoPtr->hdpaItems);
7659 ranges_destroy(infoPtr->selectionRanges);
7661 /* destroy image lists */
7662 if (!(lStyle & LVS_SHAREIMAGELISTS))
7664 if (infoPtr->himlNormal)
7665 ImageList_Destroy(infoPtr->himlNormal);
7666 if (infoPtr->himlSmall)
7667 ImageList_Destroy(infoPtr->himlSmall);
7668 if (infoPtr->himlState)
7669 ImageList_Destroy(infoPtr->himlState);
7672 /* destroy font, bkgnd brush */
7674 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
7675 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
7677 /* free listview info pointer*/
7678 COMCTL32_Free(infoPtr);
7680 SetWindowLongW(infoPtr->hwndSelf, 0, 0);
7686 * Handles notifications from children.
7689 * [I] infoPtr : valid pointer to the listview structure
7690 * [I] INT : control identifier
7691 * [I] LPNMHDR : notification information
7696 static LRESULT LISTVIEW_Notify(LISTVIEW_INFO *infoPtr, INT nCtrlId, LPNMHDR lpnmh)
7698 TRACE("(nCtrlId=%d, lpnmh=%p)\n", nCtrlId, lpnmh);
7700 if (lpnmh->hwndFrom == infoPtr->hwndHeader)
7702 /* handle notification from header control */
7703 if (lpnmh->code == HDN_ENDTRACKW)
7705 infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
7706 LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
7708 else if(lpnmh->code == HDN_ITEMCLICKW || lpnmh->code == HDN_ITEMCLICKA)
7710 /* Handle sorting by Header Column */
7713 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
7715 nmlv.iSubItem = ((LPNMHEADERW)lpnmh)->iItem;
7716 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
7718 else if(lpnmh->code == NM_RELEASEDCAPTURE)
7720 /* Idealy this should be done in HDN_ENDTRACKA
7721 * but since SetItemBounds in Header.c is called after
7722 * the notification is sent, it is neccessary to handle the
7723 * update of the scroll bar here (Header.c works fine as it is,
7724 * no need to disturb it)
7726 infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
7727 LISTVIEW_UpdateScroll(infoPtr);
7728 LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
7738 * Determines the type of structure to use.
7741 * [I] infoPtr : valid pointer to the listview structureof the sender
7742 * [I] HWND : listview window handle
7743 * [I] INT : command specifying the nature of the WM_NOTIFYFORMAT
7748 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
7750 TRACE("(hwndFrom=%x, nCommand=%d)\n", hwndFrom, nCommand);
7752 if (nCommand == NF_REQUERY)
7753 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT,
7754 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
7760 * Paints/Repaints the listview control.
7763 * [I] infoPtr : valid pointer to the listview structure
7764 * [I] HDC : device context handle
7769 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
7771 TRACE("(hdc=%x)\n", hdc);
7774 LISTVIEW_Refresh(infoPtr, hdc);
7779 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
7781 if (ps.fErase) LISTVIEW_FillBkgnd(infoPtr, hdc, &ps.rcPaint);
7782 LISTVIEW_Refresh(infoPtr, hdc);
7783 EndPaint(infoPtr->hwndSelf, &ps);
7791 * Processes double click messages (right mouse button).
7794 * [I] infoPtr : valid pointer to the listview structure
7795 * [I] wKey : key flag
7796 * [I] pts : mouse coordinate
7801 static LRESULT LISTVIEW_RButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7803 LVHITTESTINFO lvHitTestInfo;
7805 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
7807 /* send NM_RELEASEDCAPTURE notification */
7808 notify(infoPtr, NM_RELEASEDCAPTURE);
7810 /* send NM_RDBLCLK notification */
7811 lvHitTestInfo.pt.x = pts.x;
7812 lvHitTestInfo.pt.y = pts.y;
7813 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
7814 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
7821 * Processes mouse down messages (right mouse button).
7824 * [I] infoPtr : valid pointer to the listview structure
7825 * [I] wKey : key flag
7826 * [I] pts : mouse coordinate
7831 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7833 LVHITTESTINFO lvHitTestInfo;
7836 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
7838 /* send NM_RELEASEDCAPTURE notification */
7839 notify(infoPtr, NM_RELEASEDCAPTURE);
7841 /* make sure the listview control window has the focus */
7842 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
7844 /* set right button down flag */
7845 infoPtr->bRButtonDown = TRUE;
7847 /* determine the index of the selected item */
7848 lvHitTestInfo.pt.x = pts.x;
7849 lvHitTestInfo.pt.y = pts.y;
7850 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
7852 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
7854 LISTVIEW_SetItemFocus(infoPtr, nItem);
7855 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
7856 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
7857 LISTVIEW_SetSelection(infoPtr, nItem);
7861 LISTVIEW_DeselectAll(infoPtr);
7869 * Processes mouse up messages (right mouse button).
7872 * [I] infoPtr : valid pointer to the listview structure
7873 * [I] wKey : key flag
7874 * [I] pts : mouse coordinate
7879 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7881 LVHITTESTINFO lvHitTestInfo;
7884 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
7886 if (!infoPtr->bRButtonDown) return 0;
7888 /* set button flag */
7889 infoPtr->bRButtonDown = FALSE;
7891 /* Send NM_RClICK notification */
7892 lvHitTestInfo.pt.x = pts.x;
7893 lvHitTestInfo.pt.y = pts.y;
7894 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
7895 notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo);
7897 /* Change to screen coordinate for WM_CONTEXTMENU */
7898 pt = lvHitTestInfo.pt;
7899 ClientToScreen(infoPtr->hwndSelf, &pt);
7901 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
7902 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
7903 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
7914 * [I] infoPtr : valid pointer to the listview structure
7915 * [I] hwnd : window handle of window containing the cursor
7916 * [I] nHittest : hit-test code
7917 * [I] wMouseMsg : ideintifier of the mouse message
7920 * TRUE if cursor is set
7923 static BOOL LISTVIEW_SetCursor(LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
7925 LVHITTESTINFO lvHitTestInfo;
7927 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
7929 if(!infoPtr->hHotCursor) return FALSE;
7931 GetCursorPos(&lvHitTestInfo.pt);
7932 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
7934 SetCursor(infoPtr->hHotCursor);
7944 * [I] infoPtr : valid pointer to the listview structure
7945 * [I] infoPtr : handle of previously focused window
7950 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
7952 TRACE("(hwndLoseFocus=%x)\n", hwndLoseFocus);
7954 /* if we have the focus already, there's nothing to do */
7955 if (infoPtr->bFocus) return 0;
7957 /* send NM_SETFOCUS notification */
7958 notify(infoPtr, NM_SETFOCUS);
7960 /* set window focus flag */
7961 infoPtr->bFocus = TRUE;
7963 /* put the focus rect back on */
7964 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
7966 /* redraw all visible selected items */
7967 LISTVIEW_InvalidateSelectedItems(infoPtr);
7977 * [I] infoPtr : valid pointer to the listview structure
7978 * [I] HFONT : font handle
7979 * [I] WORD : redraw flag
7984 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
7986 HFONT oldFont = infoPtr->hFont;
7988 TRACE("(hfont=%x,redraw=%hu)\n", hFont, fRedraw);
7990 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
7991 if (infoPtr->hFont == oldFont) return 0;
7993 LISTVIEW_SaveTextMetrics(infoPtr);
7995 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
7996 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
7998 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
8005 * Message handling for WM_SETREDRAW.
8006 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
8009 * [I] infoPtr : valid pointer to the listview structure
8010 * [I] bRedraw: state of redraw flag
8013 * DefWinProc return value
8015 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
8017 infoPtr->bRedraw = bRedraw;
8019 RedrawWindow(infoPtr->hwndSelf, NULL, 0,
8020 RDW_INVALIDATE | RDW_FRAME | RDW_ERASE | RDW_ALLCHILDREN | RDW_ERASENOW);
8026 * Resizes the listview control. This function processes WM_SIZE
8027 * messages. At this time, the width and height are not used.
8030 * [I] infoPtr : valid pointer to the listview structure
8031 * [I] WORD : new width
8032 * [I] WORD : new height
8037 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
8039 LONG lStyle = infoPtr->dwStyle;
8040 UINT uView = lStyle & LVS_TYPEMASK;
8042 TRACE("(width=%d, height=%d)\n", Width, Height);
8044 if (LISTVIEW_UpdateSize(infoPtr))
8046 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
8048 if (lStyle & LVS_ALIGNLEFT)
8049 LISTVIEW_AlignLeft(infoPtr);
8051 LISTVIEW_AlignTop(infoPtr);
8054 LISTVIEW_UpdateScroll(infoPtr);
8056 LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
8064 * Sets the size information.
8067 * [I] infoPtr : valid pointer to the listview structure
8070 * Zero if no size change
8073 static BOOL LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
8075 LONG lStyle = infoPtr->dwStyle;
8076 UINT uView = lStyle & LVS_TYPEMASK;
8080 GetClientRect(infoPtr->hwndSelf, &rcList);
8081 CopyRect(&rcOld,&(infoPtr->rcList));
8082 infoPtr->rcList.left = 0;
8083 infoPtr->rcList.right = max(rcList.right - rcList.left, 1);
8084 infoPtr->rcList.top = 0;
8085 infoPtr->rcList.bottom = max(rcList.bottom - rcList.top, 1);
8087 if (uView == LVS_LIST)
8089 /* Apparently the "LIST" style is supposed to have the same
8090 * number of items in a column even if there is no scroll bar.
8091 * Since if a scroll bar already exists then the bottom is already
8092 * reduced, only reduce if the scroll bar does not currently exist.
8093 * The "2" is there to mimic the native control. I think it may be
8094 * related to either padding or edges. (GLA 7/2002)
8096 if (!(lStyle & WS_HSCROLL))
8098 INT nHScrollHeight = GetSystemMetrics(SM_CYHSCROLL);
8099 if (infoPtr->rcList.bottom > nHScrollHeight)
8100 infoPtr->rcList.bottom -= (nHScrollHeight + 2);
8104 if (infoPtr->rcList.bottom > 2)
8105 infoPtr->rcList.bottom -= 2;
8108 else if (uView == LVS_REPORT)
8115 Header_Layout(infoPtr->hwndHeader, &hl);
8117 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8119 if (!(LVS_NOCOLUMNHEADER & lStyle))
8120 infoPtr->rcList.top = max(wp.cy, 0);
8122 return (EqualRect(&rcOld,&(infoPtr->rcList)));
8127 * Processes WM_STYLECHANGED messages.
8130 * [I] infoPtr : valid pointer to the listview structure
8131 * [I] WPARAM : window style type (normal or extended)
8132 * [I] LPSTYLESTRUCT : window style information
8137 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
8140 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
8141 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
8142 RECT rcList = infoPtr->rcList;
8144 TRACE("(styletype=%x, styleOld=0x%08lx, styleNew=0x%08lx)\n",
8145 wStyleType, lpss->styleOld, lpss->styleNew);
8147 /* FIXME: if LVS_NOSORTHEADER changed, update header */
8149 if (wStyleType == GWL_STYLE)
8151 infoPtr->dwStyle = lpss->styleNew;
8153 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
8154 ((lpss->styleNew & WS_HSCROLL) == 0))
8155 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
8157 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
8158 ((lpss->styleNew & WS_VSCROLL) == 0))
8159 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
8161 /* If switching modes, then start with no scroll bars and then
8164 if (uNewView != uOldView)
8166 if (uOldView == LVS_REPORT)
8167 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8169 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
8170 SetRectEmpty(&infoPtr->rcFocus);
8173 if (uNewView == LVS_ICON)
8177 /* First readjust the iconSize and if necessary the iconSpacing */
8178 oldcx = infoPtr->iconSize.cx;
8179 oldcy = infoPtr->iconSize.cy;
8180 infoPtr->iconSize.cx = GetSystemMetrics(SM_CXICON);
8181 infoPtr->iconSize.cy = GetSystemMetrics(SM_CYICON);
8182 if (infoPtr->himlNormal != NULL)
8185 ImageList_GetIconSize(infoPtr->himlNormal, &cx, &cy);
8186 infoPtr->iconSize.cx = cx;
8187 infoPtr->iconSize.cy = cy;
8189 if ((infoPtr->iconSize.cx != oldcx) || (infoPtr->iconSize.cy != oldcy))
8191 TRACE("icon old size=(%d,%d), new size=(%ld,%ld)\n",
8192 oldcx, oldcy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
8193 LISTVIEW_SetIconSpacing(infoPtr,0);
8196 /* Now update the full item width and height */
8197 infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
8198 infoPtr->nItemHeight = LISTVIEW_CalculateMaxHeight(infoPtr);
8199 if (lpss->styleNew & LVS_ALIGNLEFT)
8200 LISTVIEW_AlignLeft(infoPtr);
8202 LISTVIEW_AlignTop(infoPtr);
8204 else if (uNewView == LVS_REPORT)
8211 Header_Layout(infoPtr->hwndHeader, &hl);
8212 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
8214 if (!(LVS_NOCOLUMNHEADER & lpss->styleNew))
8215 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8217 infoPtr->iconSize.cx = GetSystemMetrics(SM_CXSMICON);
8218 infoPtr->iconSize.cy = GetSystemMetrics(SM_CYSMICON);
8219 infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
8220 infoPtr->nItemHeight = LISTVIEW_CalculateMaxHeight(infoPtr);
8222 else if (uNewView == LVS_LIST)
8224 infoPtr->iconSize.cx = GetSystemMetrics(SM_CXSMICON);
8225 infoPtr->iconSize.cy = GetSystemMetrics(SM_CYSMICON);
8226 infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
8227 infoPtr->nItemHeight = LISTVIEW_CalculateMaxHeight(infoPtr);
8231 infoPtr->iconSize.cx = GetSystemMetrics(SM_CXSMICON);
8232 infoPtr->iconSize.cy = GetSystemMetrics(SM_CYSMICON);
8233 infoPtr->nItemWidth = LISTVIEW_CalculateMaxWidth(infoPtr);
8234 infoPtr->nItemHeight = LISTVIEW_CalculateMaxHeight(infoPtr);
8235 if (lpss->styleNew & LVS_ALIGNLEFT)
8236 LISTVIEW_AlignLeft(infoPtr);
8238 LISTVIEW_AlignTop(infoPtr);
8241 /* update the size of the client area */
8242 LISTVIEW_UpdateSize(infoPtr);
8244 /* add scrollbars if needed */
8245 LISTVIEW_UpdateScroll(infoPtr);
8247 /* invalidate client area + erase background */
8248 LISTVIEW_InvalidateList(infoPtr); /* FIXME: optimize */
8250 /* print the list of unsupported window styles */
8251 LISTVIEW_UnsupportedStyles(lpss->styleNew);
8254 /* If they change the view and we have an active edit control
8255 we will need to kill the control since the redraw will
8256 misplace the edit control.
8258 if (infoPtr->hwndEdit &&
8259 ((uNewView & (LVS_ICON|LVS_LIST|LVS_SMALLICON)) !=
8260 ((LVS_ICON|LVS_LIST|LVS_SMALLICON) & uOldView)))
8262 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8270 * Window procedure of the listview control.
8273 static LRESULT WINAPI
8274 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8276 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(hwnd, 0);
8278 TRACE("(uMsg=%x wParam=%x lParam=%lx)\n", uMsg, wParam, lParam);
8280 if (!infoPtr && (uMsg != WM_CREATE))
8281 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8285 infoPtr->dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
8290 case LVM_APPROXIMATEVIEWRECT:
8291 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
8292 LOWORD(lParam), HIWORD(lParam));
8294 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
8296 /* case LVN_CANCELEDITLABEL */
8298 /* case LVM_CREATEDRAGIMAGE: */
8300 case LVM_DELETEALLITEMS:
8301 return LISTVIEW_DeleteAllItems(infoPtr);
8303 case LVM_DELETECOLUMN:
8304 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
8306 case LVM_DELETEITEM:
8307 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
8309 case LVM_EDITLABELW:
8310 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
8312 case LVM_EDITLABELA:
8313 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
8315 /* case LVN_ENABLEGROUPVIEW: */
8317 case LVM_ENSUREVISIBLE:
8318 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
8321 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
8324 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
8326 case LVM_GETBKCOLOR:
8327 return infoPtr->clrBk;
8329 /* case LVM_GETBKIMAGE: */
8331 case LVM_GETCALLBACKMASK:
8332 return infoPtr->uCallbackMask;
8334 case LVM_GETCOLUMNA:
8335 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8337 case LVM_GETCOLUMNW:
8338 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8340 case LVM_GETCOLUMNORDERARRAY:
8341 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
8343 case LVM_GETCOLUMNWIDTH:
8344 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
8346 case LVM_GETCOUNTPERPAGE:
8347 return LISTVIEW_GetCountPerPage(infoPtr);
8349 case LVM_GETEDITCONTROL:
8350 return (LRESULT)infoPtr->hwndEdit;
8352 case LVM_GETEXTENDEDLISTVIEWSTYLE:
8353 return infoPtr->dwLvExStyle;
8356 return (LRESULT)infoPtr->hwndHeader;
8358 case LVM_GETHOTCURSOR:
8359 return (LRESULT)infoPtr->hHotCursor;
8361 case LVM_GETHOTITEM:
8362 return infoPtr->nHotItem;
8364 case LVM_GETHOVERTIME:
8365 return infoPtr->dwHoverTime;
8367 case LVM_GETIMAGELIST:
8368 return LISTVIEW_GetImageList(infoPtr, (INT)wParam);
8370 /* case LVN_GETINSERTMARK: */
8372 /* case LVN_GETINSERTMARKCOLOR: */
8374 /* case LVN_GETINSERTMARKRECT: */
8376 case LVM_GETISEARCHSTRINGA:
8377 case LVM_GETISEARCHSTRINGW:
8378 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
8382 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
8385 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
8387 case LVM_GETITEMCOUNT:
8388 return infoPtr->nItemCount;
8390 case LVM_GETITEMPOSITION:
8391 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
8393 case LVM_GETITEMRECT:
8394 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
8396 case LVM_GETITEMSPACING:
8397 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
8399 case LVM_GETITEMSTATE:
8400 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
8402 case LVM_GETITEMTEXTA:
8403 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
8405 case LVM_GETITEMTEXTW:
8406 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
8408 case LVM_GETNEXTITEM:
8409 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
8411 case LVM_GETNUMBEROFWORKAREAS:
8412 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
8416 return LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
8418 /* case LVN_GETOUTLINECOLOR: */
8420 /* case LVM_GETSELECTEDCOLUMN: */
8422 case LVM_GETSELECTEDCOUNT:
8423 return LISTVIEW_GetSelectedCount(infoPtr);
8425 case LVM_GETSELECTIONMARK:
8426 return infoPtr->nSelectionMark;
8428 case LVM_GETSTRINGWIDTHA:
8429 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
8431 case LVM_GETSTRINGWIDTHW:
8432 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
8434 case LVM_GETSUBITEMRECT:
8435 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
8437 case LVM_GETTEXTBKCOLOR:
8438 return infoPtr->clrTextBk;
8440 case LVM_GETTEXTCOLOR:
8441 return infoPtr->clrText;
8443 /* case LVN_GETTILEINFO: */
8445 /* case LVN_GETTILEVIEWINFO: */
8447 case LVM_GETTOOLTIPS:
8448 FIXME("LVM_GETTOOLTIPS: unimplemented\n");
8451 case LVM_GETTOPINDEX:
8452 return LISTVIEW_GetTopIndex(infoPtr);
8454 /*case LVM_GETUNICODEFORMAT:
8455 FIXME("LVM_GETUNICODEFORMAT: unimplemented\n");
8458 case LVM_GETVIEWRECT:
8459 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
8461 case LVM_GETWORKAREAS:
8462 FIXME("LVM_GETWORKAREAS: unimplemented\n");
8465 /* case LVN_HASGROUP: */
8468 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
8470 case LVM_INSERTCOLUMNA:
8471 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8473 case LVM_INSERTCOLUMNW:
8474 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8476 /* case LVN_INSERTGROUP: */
8478 /* case LVN_INSERTGROUPSORTED: */
8480 case LVM_INSERTITEMA:
8481 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
8483 case LVM_INSERTITEMW:
8484 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
8486 /* case LVN_INSERTMARKHITTEST: */
8488 /* case LVN_ISGROUPVIEWENABLED: */
8490 /* case LVN_MAPIDTOINDEX: */
8492 /* case LVN_INEDXTOID: */
8494 /* case LVN_MOVEGROUP: */
8496 /* case LVN_MOVEITEMTOGROUP: */
8498 case LVM_REDRAWITEMS:
8499 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
8501 /* case LVN_REMOVEALLGROUPS: */
8503 /* case LVN_REMOVEGROUP: */
8506 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
8508 case LVM_SETBKCOLOR:
8509 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
8511 /* case LVM_SETBKIMAGE: */
8513 case LVM_SETCALLBACKMASK:
8514 infoPtr->uCallbackMask = (UINT)wParam;
8517 case LVM_SETCOLUMNA:
8518 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8520 case LVM_SETCOLUMNW:
8521 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8523 case LVM_SETCOLUMNORDERARRAY:
8524 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
8526 case LVM_SETCOLUMNWIDTH:
8527 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, SLOWORD(lParam));
8529 case LVM_SETEXTENDEDLISTVIEWSTYLE:
8530 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
8532 /* case LVN_SETGROUPINFO: */
8534 /* case LVN_SETGROUPMETRICS: */
8536 case LVM_SETHOTCURSOR:
8537 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
8539 case LVM_SETHOTITEM:
8540 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
8542 case LVM_SETHOVERTIME:
8543 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
8545 case LVM_SETICONSPACING:
8546 return LISTVIEW_SetIconSpacing(infoPtr, (DWORD)lParam);
8548 case LVM_SETIMAGELIST:
8549 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
8551 /* case LVN_SETINFOTIP: */
8553 /* case LVN_SETINSERTMARK: */
8555 /* case LVN_SETINSERTMARKCOLOR: */
8558 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
8561 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
8563 case LVM_SETITEMCOUNT:
8564 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
8566 case LVM_SETITEMPOSITION:
8568 POINT pt = { SLOWORD(lParam), SHIWORD(lParam) };
8569 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
8572 case LVM_SETITEMPOSITION32:
8573 if (lParam == 0) return FALSE;
8574 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
8576 case LVM_SETITEMSTATE:
8577 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
8579 case LVM_SETITEMTEXTA:
8580 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
8582 case LVM_SETITEMTEXTW:
8583 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
8585 /* case LVN_SETOUTLINECOLOR: */
8587 /* case LVN_SETSELECTEDCOLUMN: */
8589 case LVM_SETSELECTIONMARK:
8590 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
8592 case LVM_SETTEXTBKCOLOR:
8593 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
8595 case LVM_SETTEXTCOLOR:
8596 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
8598 /* case LVN_SETTILEINFO: */
8600 /* case LVN_SETTILEVIEWINFO: */
8602 /* case LVN_SETTILEWIDTH: */
8604 /* case LVM_SETTOOLTIPS: */
8606 /* case LVM_SETUNICODEFORMAT: */
8608 /* case LVN_SETVIEW: */
8610 /* case LVM_SETWORKAREAS: */
8612 /* case LVN_SORTGROUPS: */
8615 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
8617 case LVM_SUBITEMHITTEST:
8618 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
8621 return LISTVIEW_Update(infoPtr, (INT)wParam);
8624 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
8627 return LISTVIEW_Command(infoPtr, wParam, lParam);
8630 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
8633 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
8636 return DLGC_WANTCHARS | DLGC_WANTARROWS;
8639 return infoPtr->hFont;
8642 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
8645 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
8648 return LISTVIEW_KillFocus(infoPtr);
8650 case WM_LBUTTONDBLCLK:
8651 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8653 case WM_LBUTTONDOWN:
8654 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8657 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8660 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8663 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8666 return LISTVIEW_NCDestroy(infoPtr);
8669 return LISTVIEW_Notify(infoPtr, (INT)wParam, (LPNMHDR)lParam);
8671 case WM_NOTIFYFORMAT:
8672 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
8675 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
8677 case WM_RBUTTONDBLCLK:
8678 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8680 case WM_RBUTTONDOWN:
8681 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8684 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
8687 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
8692 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
8695 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
8698 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
8701 return LISTVIEW_Size(infoPtr, (int)SLOWORD(lParam), (int)SHIWORD(lParam));
8703 case WM_STYLECHANGED:
8704 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
8706 case WM_SYSCOLORCHANGE:
8707 COMCTL32_RefreshSysColors();
8710 /* case WM_TIMER: */
8713 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
8716 if (wParam & (MK_SHIFT | MK_CONTROL))
8717 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8718 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
8720 case WM_WINDOWPOSCHANGED:
8721 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE)) {
8722 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
8723 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
8724 LISTVIEW_UpdateSize(infoPtr);
8725 LISTVIEW_UpdateScroll(infoPtr);
8727 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8729 /* case WM_WININICHANGE: */
8732 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
8733 ERR("unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam);
8736 /* call default window procedure */
8737 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8745 * Registers the window class.
8753 void LISTVIEW_Register(void)
8757 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
8758 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
8759 wndClass.lpfnWndProc = (WNDPROC)LISTVIEW_WindowProc;
8760 wndClass.cbClsExtra = 0;
8761 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
8762 wndClass.hCursor = LoadCursorW(0, IDC_ARROWW);
8763 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
8764 wndClass.lpszClassName = WC_LISTVIEWW;
8765 RegisterClassW(&wndClass);
8770 * Unregisters the window class.
8778 void LISTVIEW_Unregister(void)
8780 UnregisterClassW(WC_LISTVIEWW, (HINSTANCE)NULL);
8785 * Handle any WM_COMMAND messages
8791 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
8793 switch (HIWORD(wParam))
8798 * Adjust the edit window size
8801 HDC hdc = GetDC(infoPtr->hwndEdit);
8802 HFONT hFont, hOldFont = 0;
8807 if (!infoPtr->hwndEdit || !hdc) return 0;
8808 len = GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
8809 GetWindowRect(infoPtr->hwndEdit, &rect);
8811 /* Select font to get the right dimension of the string */
8812 hFont = SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
8815 hOldFont = SelectObject(hdc, hFont);
8818 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
8820 TEXTMETRICW textMetric;
8822 /* Add Extra spacing for the next character */
8823 GetTextMetricsW(hdc, &textMetric);
8824 sz.cx += (textMetric.tmMaxCharWidth * 2);
8832 rect.bottom - rect.top,
8833 SWP_DRAWFRAME|SWP_NOMOVE);
8836 SelectObject(hdc, hOldFont);
8838 ReleaseDC(infoPtr->hwndSelf, hdc);
8844 return SendMessageW (GetParent (infoPtr->hwndSelf), WM_COMMAND, wParam, lParam);
8853 * Subclassed edit control windproc function
8859 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg,
8860 WPARAM wParam, LPARAM lParam, BOOL isW)
8862 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(GetParent(hwnd), 0);
8863 BOOL cancel = FALSE;
8865 TRACE("(hwnd=%x, uMsg=%x, wParam=%x, lParam=%lx, isW=%d)\n",
8866 hwnd, uMsg, wParam, lParam, isW);
8871 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
8878 WNDPROC editProc = infoPtr->EditWndProc;
8879 infoPtr->EditWndProc = 0;
8880 SetWindowLongW(hwnd, GWL_WNDPROC, (LONG)editProc);
8881 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
8885 if (VK_ESCAPE == (INT)wParam)
8890 else if (VK_RETURN == (INT)wParam)
8894 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
8898 if (infoPtr->hwndEdit)
8900 LPWSTR buffer = NULL;
8902 infoPtr->hwndEdit = 0;
8905 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
8909 if ( (buffer = COMCTL32_Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
8911 if (isW) GetWindowTextW(hwnd, buffer, len+1);
8912 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
8916 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
8918 if (buffer) COMCTL32_Free(buffer);
8922 SendMessageW(hwnd, WM_CLOSE, 0, 0);
8928 * Subclassed edit control windproc function
8934 LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8936 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
8941 * Subclassed edit control windproc function
8947 LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8949 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
8954 * Creates a subclassed edit cotrol
8960 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
8961 INT x, INT y, INT width, INT height, BOOL isW)
8963 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
8968 TEXTMETRICW textMetric;
8969 HINSTANCE hinst = GetWindowLongW(infoPtr->hwndSelf, GWL_HINSTANCE);
8971 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
8973 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|WS_BORDER;
8974 hdc = GetDC(infoPtr->hwndSelf);
8976 /* Select the font to get appropriate metric dimensions */
8977 if(infoPtr->hFont != 0)
8978 hOldFont = SelectObject(hdc, infoPtr->hFont);
8980 /*Get String Lenght in pixels */
8981 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
8983 /*Add Extra spacing for the next character */
8984 GetTextMetricsW(hdc, &textMetric);
8985 sz.cx += (textMetric.tmMaxCharWidth * 2);
8987 if(infoPtr->hFont != 0)
8988 SelectObject(hdc, hOldFont);
8990 ReleaseDC(infoPtr->hwndSelf, hdc);
8992 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
8994 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
8996 if (!hedit) return 0;
8998 infoPtr->EditWndProc = (WNDPROC)
8999 (isW ? SetWindowLongW(hedit, GWL_WNDPROC, (LONG)EditLblWndProcW) :
9000 SetWindowLongA(hedit, GWL_WNDPROC, (LONG)EditLblWndProcA) );
9002 SendMessageW(hedit, WM_SETFONT, infoPtr->hFont, FALSE);