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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 * This code was audited for completeness against the documented features
27 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
29 * Unless otherwise noted, we believe this code to be complete, as per
30 * the specification mentioned above.
31 * If you discover missing features, or bugs, please note them below.
35 * Default Message Processing
36 * -- EN_KILLFOCUS should be handled in WM_COMMAND
37 * -- WM_CREATE: create the icon and small icon image lists at this point only if
38 * the LVS_SHAREIMAGELISTS style is not specified.
39 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
40 * or small icon and the LVS_AUTOARRANGE style is specified.
45 * -- Hot item handling, mouse hovering
46 * -- Workareas support
51 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
52 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
53 * -- LVA_SNAPTOGRID not implemented
54 * -- LISTVIEW_ApproximateViewRect partially implemented
55 * -- LISTVIEW_[GS]etColumnOrderArray stubs
56 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
57 * -- LISTVIEW_SetIconSpacing is incomplete
58 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
61 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
62 * linear in the number of items in the list, and this is
63 * unacceptable for large lists.
64 * -- if list is sorted by item text LISTVIEW_InsertItemT could use
65 * binary search to calculate item index (e.g. DPA_Search()).
66 * This requires sorted state to be reliably tracked in item modifiers.
67 * -- we should keep an ordered array of coordinates in iconic mode
68 * this would allow to frame items (iterator_frameditems),
69 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
76 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
83 * -- LVS_NOSCROLL (see Q137520)
84 * -- LVS_SORTASCENDING, LVS_SORTDESCENDING
86 * -- LVS_TYPESTYLEMASK
89 * -- LVS_EX_BORDERSELECT
91 * -- LVS_EX_HEADERDRAGDROP
94 * -- LVS_EX_MULTIWORKAREAS
96 * -- LVS_EX_SIMPLESELECT
97 * -- LVS_EX_TWOCLICKACTIVATE
98 * -- LVS_EX_UNDERLINECOLD
99 * -- LVS_EX_UNDERLINEHOT
102 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
105 * -- LVN_MARQUEEBEGIN
111 * -- LVM_CANCELEDITLABEL
112 * -- LVM_ENABLEGROUPVIEW
113 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
114 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
115 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
116 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
117 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
118 * -- LVM_GETINSERTMARKRECT
119 * -- LVM_GETNUMBEROFWORKAREAS
120 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
121 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
122 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
123 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
124 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
125 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
126 * -- LVM_GETVIEW, LVM_SETVIEW
127 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
128 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
129 * -- LVM_INSERTGROUPSORTED
130 * -- LVM_INSERTMARKHITTEST
131 * -- LVM_ISGROUPVIEWENABLED
132 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
134 * -- LVM_MOVEITEMTOGROUP
136 * -- LVM_SETTILEWIDTH
140 * -- ListView_GetHoverTime, ListView_SetHoverTime
141 * -- ListView_GetISearchString
142 * -- ListView_GetNumberOfWorkAreas
143 * -- ListView_GetUnicodeFormat, ListView_SetUnicodeFormat
144 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
149 * Known differences in message stream from native control (not known if
150 * these differences cause problems):
151 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
152 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
153 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
154 * processing for "USEDOUBLECLICKTIME".
158 #include "wine/port.h"
173 #include "commctrl.h"
174 #include "comctl32.h"
177 #include "wine/debug.h"
178 #include "wine/unicode.h"
180 WINE_DEFAULT_DEBUG_CHANNEL(listview);
182 /* make sure you set this to 0 for production use! */
183 #define DEBUG_RANGES 1
185 typedef struct tagCOLUMN_INFO
187 RECT rcHeader; /* tracks the header's rectangle */
188 int fmt; /* same as LVCOLUMN.fmt */
191 typedef struct tagITEMHDR
195 } ITEMHDR, *LPITEMHDR;
197 typedef struct tagSUBITEM_INFO
203 typedef struct tagITEM_INFO
211 typedef struct tagRANGE
217 typedef struct tagRANGES
222 typedef struct tagITERATOR
231 typedef struct tagDELAYED_ITEM_EDIT
237 typedef struct tagLISTVIEW_INFO
244 HIMAGELIST himlNormal;
245 HIMAGELIST himlSmall;
246 HIMAGELIST himlState;
250 POINT ptClickPos; /* point where the user clicked */
251 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
254 RANGES selectionRanges;
259 RECT rcList; /* This rectangle is really the window
260 * client rectangle possibly reduced by the
261 * horizontal scroll bar and/or header - see
262 * LISTVIEW_UpdateSize. This rectangle offset
263 * by the LISTVIEW_GetOrigin value is in
264 * client coordinates */
273 INT ntmHeight; /* Some cached metrics of the font used */
274 INT ntmMaxCharWidth; /* by the listview to draw items */
276 BOOL bRedraw; /* Turns on/off repaints & invalidations */
277 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
279 BOOL bDoChangeNotify; /* send change notification messages? */
282 DWORD dwStyle; /* the cached window GWL_STYLE */
283 DWORD dwLvExStyle; /* extended listview style */
284 INT nItemCount; /* the number of items in the list */
285 HDPA hdpaItems; /* array ITEM_INFO pointers */
286 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
287 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
288 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
289 POINT currIconPos; /* this is the position next icon will be placed */
290 PFNLVCOMPARE pfnCompare;
295 INT nLButtonDownItem; /* tracks item to reset multiselection on WM_LBUTTONUP */
299 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
301 DWORD lastKeyPressTimestamp;
303 INT nSearchParamLength;
304 WCHAR szSearchParam[ MAX_PATH ];
306 INT nMeasureItemHeight;
307 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
308 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
310 DWORD iVersion; /* CCM_[G,S]ETVERSION */
316 /* How many we debug buffer to allocate */
317 #define DEBUG_BUFFERS 20
318 /* The size of a single debug bbuffer */
319 #define DEBUG_BUFFER_SIZE 256
321 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
322 #define SB_INTERNAL -1
324 /* maximum size of a label */
325 #define DISP_TEXT_SIZE 512
327 /* padding for items in list and small icon display modes */
328 #define WIDTH_PADDING 12
330 /* padding for items in list, report and small icon display modes */
331 #define HEIGHT_PADDING 1
333 /* offset of items in report display mode */
334 #define REPORT_MARGINX 2
336 /* padding for icon in large icon display mode
337 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
338 * that HITTEST will see.
339 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
340 * ICON_TOP_PADDING - sum of the two above.
341 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
342 * LABEL_HOR_PADDING - between text and sides of box
343 * LABEL_VERT_PADDING - between bottom of text and end of box
345 * ICON_LR_PADDING - additional width above icon size.
346 * ICON_LR_HALF - half of the above value
348 #define ICON_TOP_PADDING_NOTHITABLE 2
349 #define ICON_TOP_PADDING_HITABLE 2
350 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
351 #define ICON_BOTTOM_PADDING 4
352 #define LABEL_HOR_PADDING 5
353 #define LABEL_VERT_PADDING 7
354 #define ICON_LR_PADDING 16
355 #define ICON_LR_HALF (ICON_LR_PADDING/2)
357 /* default label width for items in list and small icon display modes */
358 #define DEFAULT_LABEL_WIDTH 40
360 /* default column width for items in list display mode */
361 #define DEFAULT_COLUMN_WIDTH 128
363 /* Size of "line" scroll for V & H scrolls */
364 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
366 /* Padding between image and label */
367 #define IMAGE_PADDING 2
369 /* Padding behind the label */
370 #define TRAILING_LABEL_PADDING 12
371 #define TRAILING_HEADER_PADDING 11
373 /* Border for the icon caption */
374 #define CAPTION_BORDER 2
376 /* Standard DrawText flags */
377 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
378 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
379 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
381 /* Image index from state */
382 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
384 /* The time in milliseconds to reset the search in the list */
385 #define KEY_DELAY 450
387 /* Dump the LISTVIEW_INFO structure to the debug channel */
388 #define LISTVIEW_DUMP(iP) do { \
389 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
390 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
391 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
392 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
393 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
394 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
395 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
396 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
397 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
398 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
401 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
404 * forward declarations
406 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
407 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
408 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
409 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
410 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
411 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *, INT);
412 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
413 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
414 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
415 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *, WPARAM, LPARAM);
416 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
417 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
418 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
419 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
420 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
421 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
422 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
423 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
424 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
425 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
427 /******** Text handling functions *************************************/
429 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
430 * text string. The string may be ANSI or Unicode, in which case
431 * the boolean isW tells us the type of the string.
433 * The name of the function tell what type of strings it expects:
434 * W: Unicode, T: ANSI/Unicode - function of isW
437 static inline BOOL is_textW(LPCWSTR text)
439 return text != NULL && text != LPSTR_TEXTCALLBACKW;
442 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
444 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
445 return is_textW(text);
448 static inline int textlenT(LPCWSTR text, BOOL isW)
450 return !is_textT(text, isW) ? 0 :
451 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
454 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
457 if (isSrcW) lstrcpynW(dest, src, max);
458 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
460 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
461 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
464 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
466 LPWSTR wstr = (LPWSTR)text;
468 if (!isW && is_textT(text, isW))
470 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
471 wstr = Alloc(len * sizeof(WCHAR));
472 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
474 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
478 static inline void textfreeT(LPWSTR wstr, BOOL isW)
480 if (!isW && is_textT(wstr, isW)) Free (wstr);
484 * dest is a pointer to a Unicode string
485 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
487 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
491 if (src == LPSTR_TEXTCALLBACKW)
493 if (is_textW(*dest)) Free(*dest);
494 *dest = LPSTR_TEXTCALLBACKW;
498 LPWSTR pszText = textdupTtoW(src, isW);
499 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
500 bResult = Str_SetPtrW(dest, pszText);
501 textfreeT(pszText, isW);
507 * compares a Unicode to a Unicode/ANSI text string
509 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
511 if (!aw) return bt ? -1 : 0;
512 if (!bt) return aw ? 1 : 0;
513 if (aw == LPSTR_TEXTCALLBACKW)
514 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
515 if (bt != LPSTR_TEXTCALLBACKW)
517 LPWSTR bw = textdupTtoW(bt, isW);
518 int r = bw ? lstrcmpW(aw, bw) : 1;
526 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
530 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
531 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
532 return res ? res - sizeof(WCHAR) : res;
535 /******** Debugging functions *****************************************/
537 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
539 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
540 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
543 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
545 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
546 n = min(textlenT(text, isW), n);
547 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
550 static char* debug_getbuf(void)
552 static int index = 0;
553 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
554 return buffers[index++ % DEBUG_BUFFERS];
557 static inline const char* debugrange(const RANGE *lprng)
559 if (!lprng) return "(null)";
560 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
563 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
565 char* buf = debug_getbuf(), *text = buf;
566 int len, size = DEBUG_BUFFER_SIZE;
568 if (pScrollInfo == NULL) return "(null)";
569 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
570 if (len == -1) goto end; buf += len; size -= len;
571 if (pScrollInfo->fMask & SIF_RANGE)
572 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
574 if (len == -1) goto end; buf += len; size -= len;
575 if (pScrollInfo->fMask & SIF_PAGE)
576 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
578 if (len == -1) goto end; buf += len; size -= len;
579 if (pScrollInfo->fMask & SIF_POS)
580 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
582 if (len == -1) goto end; buf += len; size -= len;
583 if (pScrollInfo->fMask & SIF_TRACKPOS)
584 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
586 if (len == -1) goto end; buf += len; size -= len;
589 buf = text + strlen(text);
591 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
595 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
597 if (!plvnm) return "(null)";
598 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
599 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
600 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
601 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
604 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
606 char* buf = debug_getbuf(), *text = buf;
607 int len, size = DEBUG_BUFFER_SIZE;
609 if (lpLVItem == NULL) return "(null)";
610 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
611 if (len == -1) goto end; buf += len; size -= len;
612 if (lpLVItem->mask & LVIF_STATE)
613 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
615 if (len == -1) goto end; buf += len; size -= len;
616 if (lpLVItem->mask & LVIF_TEXT)
617 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
619 if (len == -1) goto end; buf += len; size -= len;
620 if (lpLVItem->mask & LVIF_IMAGE)
621 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
623 if (len == -1) goto end; buf += len; size -= len;
624 if (lpLVItem->mask & LVIF_PARAM)
625 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
627 if (len == -1) goto end; buf += len; size -= len;
628 if (lpLVItem->mask & LVIF_INDENT)
629 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
631 if (len == -1) goto end; buf += len; size -= len;
634 buf = text + strlen(text);
636 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
640 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
642 char* buf = debug_getbuf(), *text = buf;
643 int len, size = DEBUG_BUFFER_SIZE;
645 if (lpColumn == NULL) return "(null)";
646 len = snprintf(buf, size, "{");
647 if (len == -1) goto end; buf += len; size -= len;
648 if (lpColumn->mask & LVCF_SUBITEM)
649 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
651 if (len == -1) goto end; buf += len; size -= len;
652 if (lpColumn->mask & LVCF_FMT)
653 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
655 if (len == -1) goto end; buf += len; size -= len;
656 if (lpColumn->mask & LVCF_WIDTH)
657 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
659 if (len == -1) goto end; buf += len; size -= len;
660 if (lpColumn->mask & LVCF_TEXT)
661 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
663 if (len == -1) goto end; buf += len; size -= len;
664 if (lpColumn->mask & LVCF_IMAGE)
665 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
667 if (len == -1) goto end; buf += len; size -= len;
668 if (lpColumn->mask & LVCF_ORDER)
669 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
671 if (len == -1) goto end; buf += len; size -= len;
674 buf = text + strlen(text);
676 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
680 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
682 if (!lpht) return "(null)";
684 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
685 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
688 /* Return the corresponding text for a given scroll value */
689 static inline LPCSTR debugscrollcode(int nScrollCode)
693 case SB_LINELEFT: return "SB_LINELEFT";
694 case SB_LINERIGHT: return "SB_LINERIGHT";
695 case SB_PAGELEFT: return "SB_PAGELEFT";
696 case SB_PAGERIGHT: return "SB_PAGERIGHT";
697 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
698 case SB_THUMBTRACK: return "SB_THUMBTRACK";
699 case SB_ENDSCROLL: return "SB_ENDSCROLL";
700 case SB_INTERNAL: return "SB_INTERNAL";
701 default: return "unknown";
706 /******** Notification functions ************************************/
708 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
710 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
711 (WPARAM)lpnmh->hdr.idFrom, (LPARAM)lpnmh);
714 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
718 TRACE("(code=%d)\n", code);
720 pnmh->hwndFrom = infoPtr->hwndSelf;
721 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
723 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
725 TRACE(" <= %ld\n", result);
730 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
733 HWND hwnd = infoPtr->hwndSelf;
734 notify_hdr(infoPtr, code, &nmh);
735 return IsWindow(hwnd);
738 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
749 item.mask = LVIF_PARAM|LVIF_STATE;
750 item.iItem = htInfo->iItem;
752 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
753 nmia.lParam = item.lParam;
754 nmia.uOldState = item.state;
755 nmia.uNewState = item.state | LVIS_ACTIVATING;
756 nmia.uChanged = LVIF_STATE;
759 nmia.iItem = htInfo->iItem;
760 nmia.iSubItem = htInfo->iSubItem;
761 nmia.ptAction = htInfo->pt;
763 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
764 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
765 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
767 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
770 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
772 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
773 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
776 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
780 HWND hwnd = infoPtr->hwndSelf;
782 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
783 ZeroMemory(&nmia, sizeof(nmia));
784 nmia.iItem = lvht->iItem;
785 nmia.iSubItem = lvht->iSubItem;
786 nmia.ptAction = lvht->pt;
787 item.mask = LVIF_PARAM;
788 item.iItem = lvht->iItem;
790 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmia.lParam = item.lParam;
791 notify_hdr(infoPtr, code, (LPNMHDR)&nmia);
792 return IsWindow(hwnd);
795 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
799 HWND hwnd = infoPtr->hwndSelf;
801 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
803 item.mask = LVIF_PARAM;
806 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
807 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
808 return IsWindow(hwnd);
811 static int get_ansi_notification(UINT unicodeNotificationCode)
813 switch (unicodeNotificationCode)
815 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
816 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
817 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
818 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
819 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
820 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
822 ERR("unknown notification %x\n", unicodeNotificationCode);
828 Send notification. depends on dispinfoW having same
829 structure as dispinfoA.
830 infoPtr : listview struct
831 notificationCode : *Unicode* notification code
832 pdi : dispinfo structure (can be unicode or ansi)
833 isW : TRUE if dispinfo is Unicode
835 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
837 BOOL bResult = FALSE;
838 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
839 INT cchTempBufMax = 0, savCchTextMax = 0;
841 LPWSTR pszTempBuf = NULL, savPszText = NULL;
843 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
845 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
846 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
849 if (convertToAnsi || convertToUnicode)
851 if (notificationCode != LVN_GETDISPINFOW)
853 cchTempBufMax = convertToUnicode ?
854 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
855 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
859 cchTempBufMax = pdi->item.cchTextMax;
860 *pdi->item.pszText = 0; /* make sure we don't process garbage */
863 pszTempBuf = Alloc( (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
864 if (!pszTempBuf) return FALSE;
866 if (convertToUnicode)
867 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
868 pszTempBuf, cchTempBufMax);
870 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
871 cchTempBufMax, NULL, NULL);
873 savCchTextMax = pdi->item.cchTextMax;
874 savPszText = pdi->item.pszText;
875 pdi->item.pszText = pszTempBuf;
876 pdi->item.cchTextMax = cchTempBufMax;
879 if (infoPtr->notifyFormat == NFR_ANSI)
880 realNotifCode = get_ansi_notification(notificationCode);
882 realNotifCode = notificationCode;
883 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
884 bResult = notify_hdr(infoPtr, realNotifCode, &pdi->hdr);
886 if (convertToUnicode || convertToAnsi)
888 if (convertToUnicode) /* note : pointer can be changed by app ! */
889 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
890 savCchTextMax, NULL, NULL);
892 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
893 savPszText, savCchTextMax);
894 pdi->item.pszText = savPszText; /* restores our buffer */
895 pdi->item.cchTextMax = savCchTextMax;
901 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
902 const RECT *rcBounds, const LVITEMW *lplvItem)
904 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
905 lpnmlvcd->nmcd.hdc = hdc;
906 lpnmlvcd->nmcd.rc = *rcBounds;
907 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
908 lpnmlvcd->clrText = infoPtr->clrText;
909 if (!lplvItem) return;
910 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
911 lpnmlvcd->iSubItem = lplvItem->iSubItem;
912 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
913 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
914 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
915 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
918 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
920 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
923 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
924 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
925 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
926 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
927 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
928 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
932 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
934 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
935 lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
936 if (lpnmlvcd->clrText == CLR_DEFAULT)
937 lpnmlvcd->clrText = comctl32_color.clrWindowText;
939 /* apparently, for selected items, we have to override the returned values */
942 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
946 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
947 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
949 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
951 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
952 lpnmlvcd->clrText = comctl32_color.clrBtnText;
957 /* Set the text attributes */
958 if (lpnmlvcd->clrTextBk != CLR_NONE)
960 SetBkMode(hdc, OPAQUE);
961 SetBkColor(hdc,lpnmlvcd->clrTextBk);
964 SetBkMode(hdc, TRANSPARENT);
965 SetTextColor(hdc, lpnmlvcd->clrText);
968 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
970 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
973 /******** Item iterator functions **********************************/
975 static RANGES ranges_create(int count);
976 static void ranges_destroy(RANGES ranges);
977 static BOOL ranges_add(RANGES ranges, RANGE range);
978 static BOOL ranges_del(RANGES ranges, RANGE range);
979 static void ranges_dump(RANGES ranges);
981 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
983 RANGE range = { nItem, nItem + 1 };
985 return ranges_add(ranges, range);
988 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
990 RANGE range = { nItem, nItem + 1 };
992 return ranges_del(ranges, range);
996 * ITERATOR DOCUMENTATION
998 * The iterator functions allow for easy, and convenient iteration
999 * over items of interest in the list. Typically, you create a
1000 * iterator, use it, and destroy it, as such:
1003 * iterator_xxxitems(&i, ...);
1004 * while (iterator_{prev,next}(&i)
1006 * //code which uses i.nItem
1008 * iterator_destroy(&i);
1010 * where xxx is either: framed, or visible.
1011 * Note that it is important that the code destroys the iterator
1012 * after it's done with it, as the creation of the iterator may
1013 * allocate memory, which thus needs to be freed.
1015 * You can iterate both forwards, and backwards through the list,
1016 * by using iterator_next or iterator_prev respectively.
1018 * Lower numbered items are draw on top of higher number items in
1019 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1020 * items may overlap). So, to test items, you should use
1022 * which lists the items top to bottom (in Z-order).
1023 * For drawing items, you should use
1025 * which lists the items bottom to top (in Z-order).
1026 * If you keep iterating over the items after the end-of-items
1027 * marker (-1) is returned, the iterator will start from the
1028 * beginning. Typically, you don't need to test for -1,
1029 * because iterator_{next,prev} will return TRUE if more items
1030 * are to be iterated over, or FALSE otherwise.
1032 * Note: the iterator is defined to be bidirectional. That is,
1033 * any number of prev followed by any number of next, or
1034 * five versa, should leave the iterator at the same item:
1035 * prev * n, next * n = next * n, prev * n
1037 * The iterator has a notion of an out-of-order, special item,
1038 * which sits at the start of the list. This is used in
1039 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1040 * which needs to be first, as it may overlap other items.
1042 * The code is a bit messy because we have:
1043 * - a special item to deal with
1044 * - simple range, or composite range
1046 * If you find bugs, or want to add features, please make sure you
1047 * always check/modify *both* iterator_prev, and iterator_next.
1051 * This function iterates through the items in increasing order,
1052 * but prefixed by the special item, then -1. That is:
1053 * special, 1, 2, 3, ..., n, -1.
1054 * Each item is listed only once.
1056 static inline BOOL iterator_next(ITERATOR* i)
1060 i->nItem = i->nSpecial;
1061 if (i->nItem != -1) return TRUE;
1063 if (i->nItem == i->nSpecial)
1065 if (i->ranges) i->index = 0;
1071 if (i->nItem == i->nSpecial) i->nItem++;
1072 if (i->nItem < i->range.upper) return TRUE;
1077 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1078 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1081 else if (i->nItem >= i->range.upper) goto end;
1083 i->nItem = i->range.lower;
1084 if (i->nItem >= 0) goto testitem;
1091 * This function iterates through the items in decreasing order,
1092 * followed by the special item, then -1. That is:
1093 * n, n-1, ..., 3, 2, 1, special, -1.
1094 * Each item is listed only once.
1096 static inline BOOL iterator_prev(ITERATOR* i)
1103 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1106 if (i->nItem == i->nSpecial)
1114 if (i->nItem == i->nSpecial) i->nItem--;
1115 if (i->nItem >= i->range.lower) return TRUE;
1121 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1124 else if (!start && i->nItem < i->range.lower) goto end;
1126 i->nItem = i->range.upper;
1127 if (i->nItem > 0) goto testitem;
1129 return (i->nItem = i->nSpecial) != -1;
1132 static RANGE iterator_range(const ITERATOR *i)
1136 if (!i->ranges) return i->range;
1138 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1140 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1141 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1143 else range.lower = range.upper = 0;
1149 * Releases resources associated with this ierator.
1151 static inline void iterator_destroy(const ITERATOR *i)
1153 ranges_destroy(i->ranges);
1157 * Create an empty iterator.
1159 static inline BOOL iterator_empty(ITERATOR* i)
1161 ZeroMemory(i, sizeof(*i));
1162 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1167 * Create an iterator over a range.
1169 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1177 * Create an iterator over a bunch of ranges.
1178 * Please note that the iterator will take ownership of the ranges,
1179 * and will free them upon destruction.
1181 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1189 * Creates an iterator over the items which intersect lprc.
1191 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1193 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1194 RECT frame = *lprc, rcItem, rcTemp;
1197 /* in case we fail, we want to return an empty iterator */
1198 if (!iterator_empty(i)) return FALSE;
1200 LISTVIEW_GetOrigin(infoPtr, &Origin);
1202 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1203 OffsetRect(&frame, -Origin.x, -Origin.y);
1205 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1209 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1211 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1212 if (IntersectRect(&rcTemp, &rcItem, lprc))
1213 i->nSpecial = infoPtr->nFocusedItem;
1215 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1216 /* to do better here, we need to have PosX, and PosY sorted */
1217 TRACE("building icon ranges:\n");
1218 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1220 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1221 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1222 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1223 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1224 if (IntersectRect(&rcTemp, &rcItem, &frame))
1225 ranges_additem(i->ranges, nItem);
1229 else if (uView == LVS_REPORT)
1233 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1234 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1236 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1237 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1238 if (range.upper <= range.lower) return TRUE;
1239 if (!iterator_rangeitems(i, range)) return FALSE;
1240 TRACE(" report=%s\n", debugrange(&i->range));
1244 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1245 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1246 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1247 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1248 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1249 INT lower = nFirstCol * nPerCol + nFirstRow;
1253 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1254 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1256 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1258 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1259 TRACE("building list ranges:\n");
1260 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1262 item_range.lower = nCol * nPerCol + nFirstRow;
1263 if(item_range.lower >= infoPtr->nItemCount) break;
1264 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1265 TRACE(" list=%s\n", debugrange(&item_range));
1266 ranges_add(i->ranges, item_range);
1274 * Creates an iterator over the items which intersect the visible region of hdc.
1276 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1278 POINT Origin, Position;
1279 RECT rcItem, rcClip;
1282 rgntype = GetClipBox(hdc, &rcClip);
1283 if (rgntype == NULLREGION) return iterator_empty(i);
1284 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1285 if (rgntype == SIMPLEREGION) return TRUE;
1287 /* first deal with the special item */
1288 if (i->nSpecial != -1)
1290 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1291 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1294 /* if we can't deal with the region, we'll just go with the simple range */
1295 LISTVIEW_GetOrigin(infoPtr, &Origin);
1296 TRACE("building visible range:\n");
1297 if (!i->ranges && i->range.lower < i->range.upper)
1299 if (!(i->ranges = ranges_create(50))) return TRUE;
1300 if (!ranges_add(i->ranges, i->range))
1302 ranges_destroy(i->ranges);
1308 /* now delete the invisible items from the list */
1309 while(iterator_next(i))
1311 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1312 rcItem.left = Position.x + Origin.x;
1313 rcItem.top = Position.y + Origin.y;
1314 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1315 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1316 if (!RectVisible(hdc, &rcItem))
1317 ranges_delitem(i->ranges, i->nItem);
1319 /* the iterator should restart on the next iterator_next */
1325 /******** Misc helper functions ************************************/
1327 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1328 WPARAM wParam, LPARAM lParam, BOOL isW)
1330 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1331 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1334 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1336 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1338 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1339 (uView == LVS_ICON || uView == LVS_SMALLICON);
1342 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1344 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1345 if(state == 1 || state == 2)
1349 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1350 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1351 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1355 /******** Internal API functions ************************************/
1357 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1359 static COLUMN_INFO mainItem;
1361 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1362 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1363 return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1366 static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr)
1368 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
1371 if (infoPtr->hwndHeader) return 0;
1373 TRACE("Creating header for list %p\n", infoPtr->hwndSelf);
1375 /* setup creation flags */
1376 dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
1377 dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
1379 hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
1382 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
1383 0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
1384 if (!infoPtr->hwndHeader) return -1;
1386 /* set header unicode format */
1387 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
1389 /* set header font */
1390 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
1392 LISTVIEW_UpdateSize(infoPtr);
1397 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1399 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1402 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1404 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1407 /* used to handle collapse main item column case */
1408 static inline BOOL LISTVIEW_DrawFocusRect(const LISTVIEW_INFO *infoPtr, HDC hdc)
1410 return (infoPtr->rcFocus.left < infoPtr->rcFocus.right) ?
1411 DrawFocusRect(hdc, &infoPtr->rcFocus) : FALSE;
1414 /* Listview invalidation functions: use _only_ these functions to invalidate */
1416 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1418 return infoPtr->bRedraw;
1421 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1423 if(!is_redrawing(infoPtr)) return;
1424 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1425 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1428 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1432 if(!is_redrawing(infoPtr)) return;
1433 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1434 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1437 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1439 POINT Origin, Position;
1442 if(!is_redrawing(infoPtr)) return;
1443 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1444 LISTVIEW_GetOrigin(infoPtr, &Origin);
1445 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1446 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1448 rcBox.bottom = infoPtr->nItemHeight;
1449 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1450 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1453 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1455 LISTVIEW_InvalidateRect(infoPtr, NULL);
1458 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1462 if(!is_redrawing(infoPtr)) return;
1463 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1464 rcCol.top = infoPtr->rcList.top;
1465 rcCol.bottom = infoPtr->rcList.bottom;
1466 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1471 * Retrieves the number of items that can fit vertically in the client area.
1474 * [I] infoPtr : valid pointer to the listview structure
1477 * Number of items per row.
1479 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1481 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1483 return max(nListWidth/infoPtr->nItemWidth, 1);
1488 * Retrieves the number of items that can fit horizontally in the client
1492 * [I] infoPtr : valid pointer to the listview structure
1495 * Number of items per column.
1497 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1499 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1501 return max(nListHeight / infoPtr->nItemHeight, 1);
1505 /*************************************************************************
1506 * LISTVIEW_ProcessLetterKeys
1508 * Processes keyboard messages generated by pressing the letter keys
1510 * What this does is perform a case insensitive search from the
1511 * current position with the following quirks:
1512 * - If two chars or more are pressed in quick succession we search
1513 * for the corresponding string (e.g. 'abc').
1514 * - If there is a delay we wipe away the current search string and
1515 * restart with just that char.
1516 * - If the user keeps pressing the same character, whether slowly or
1517 * fast, so that the search string is entirely composed of this
1518 * character ('aaaaa' for instance), then we search for first item
1519 * that starting with that character.
1520 * - If the user types the above character in quick succession, then
1521 * we must also search for the corresponding string ('aaaaa'), and
1522 * go to that string if there is a match.
1525 * [I] hwnd : handle to the window
1526 * [I] charCode : the character code, the actual character
1527 * [I] keyData : key data
1535 * - The current implementation has a list of characters it will
1536 * accept and it ignores everything else. In particular it will
1537 * ignore accentuated characters which seems to match what
1538 * Windows does. But I'm not sure it makes sense to follow
1540 * - We don't sound a beep when the search fails.
1544 * TREEVIEW_ProcessLetterKeys
1546 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1551 WCHAR buffer[MAX_PATH];
1552 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1554 /* simple parameter checking */
1555 if (!charCode || !keyData) return 0;
1557 /* only allow the valid WM_CHARs through */
1558 if (!isalnumW(charCode) &&
1559 charCode != '.' && charCode != '`' && charCode != '!' &&
1560 charCode != '@' && charCode != '#' && charCode != '$' &&
1561 charCode != '%' && charCode != '^' && charCode != '&' &&
1562 charCode != '*' && charCode != '(' && charCode != ')' &&
1563 charCode != '-' && charCode != '_' && charCode != '+' &&
1564 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1565 charCode != '}' && charCode != '[' && charCode != '{' &&
1566 charCode != '/' && charCode != '?' && charCode != '>' &&
1567 charCode != '<' && charCode != ',' && charCode != '~')
1570 /* if there's one item or less, there is no where to go */
1571 if (infoPtr->nItemCount <= 1) return 0;
1573 /* update the search parameters */
1574 infoPtr->lastKeyPressTimestamp = GetTickCount();
1575 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1576 if (infoPtr->nSearchParamLength < MAX_PATH-1)
1577 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1578 if (infoPtr->charCode != charCode)
1579 infoPtr->charCode = charCode = 0;
1581 infoPtr->charCode=charCode;
1582 infoPtr->szSearchParam[0]=charCode;
1583 infoPtr->nSearchParamLength=1;
1584 /* Redundant with the 1 char string */
1588 /* and search from the current position */
1590 if (infoPtr->nFocusedItem >= 0) {
1591 endidx=infoPtr->nFocusedItem;
1593 /* if looking for single character match,
1594 * then we must always move forward
1596 if (infoPtr->nSearchParamLength == 1)
1599 endidx=infoPtr->nItemCount;
1603 /* Let application handle this for virtual listview */
1604 if (infoPtr->dwStyle & LVS_OWNERDATA)
1609 ZeroMemory(&lvfi, sizeof(lvfi));
1610 lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1611 infoPtr->szSearchParam[infoPtr->nSearchParamLength] = '\0';
1612 lvfi.psz = infoPtr->szSearchParam;
1616 nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1619 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1625 if (idx == infoPtr->nItemCount) {
1626 if (endidx == infoPtr->nItemCount || endidx == 0)
1632 item.mask = LVIF_TEXT;
1635 item.pszText = buffer;
1636 item.cchTextMax = MAX_PATH;
1637 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1639 /* check for a match */
1640 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1643 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1644 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1645 /* This would work but we must keep looking for a longer match */
1649 } while (idx != endidx);
1652 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1657 /*************************************************************************
1658 * LISTVIEW_UpdateHeaderSize [Internal]
1660 * Function to resize the header control
1663 * [I] hwnd : handle to a window
1664 * [I] nNewScrollPos : scroll pos to set
1669 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1674 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1676 if (!infoPtr->hwndHeader) return;
1678 GetWindowRect(infoPtr->hwndHeader, &winRect);
1679 point[0].x = winRect.left;
1680 point[0].y = winRect.top;
1681 point[1].x = winRect.right;
1682 point[1].y = winRect.bottom;
1684 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1685 point[0].x = -nNewScrollPos;
1686 point[1].x += nNewScrollPos;
1688 SetWindowPos(infoPtr->hwndHeader,0,
1689 point[0].x,point[0].y,point[1].x,point[1].y,
1690 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
1691 SWP_NOZORDER | SWP_NOACTIVATE);
1696 * Update the scrollbars. This functions should be called whenever
1697 * the content, size or view changes.
1700 * [I] infoPtr : valid pointer to the listview structure
1705 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
1707 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1708 SCROLLINFO horzInfo, vertInfo;
1711 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1713 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1714 horzInfo.cbSize = sizeof(SCROLLINFO);
1715 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1717 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1718 if (uView == LVS_LIST)
1720 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1721 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1723 /* scroll by at least one column per page */
1724 if(horzInfo.nPage < infoPtr->nItemWidth)
1725 horzInfo.nPage = infoPtr->nItemWidth;
1727 horzInfo.nPage /= infoPtr->nItemWidth;
1729 else if (uView == LVS_REPORT)
1731 horzInfo.nMax = infoPtr->nItemWidth;
1733 else /* LVS_ICON, or LVS_SMALLICON */
1737 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1740 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1741 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1742 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
1743 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1744 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1746 /* Setting the horizontal scroll can change the listview size
1747 * (and potentially everything else) so we need to recompute
1748 * everything again for the vertical scroll
1751 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1752 vertInfo.cbSize = sizeof(SCROLLINFO);
1753 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1755 if (uView == LVS_REPORT)
1757 vertInfo.nMax = infoPtr->nItemCount;
1759 /* scroll by at least one page */
1760 if(vertInfo.nPage < infoPtr->nItemHeight)
1761 vertInfo.nPage = infoPtr->nItemHeight;
1763 if (infoPtr->nItemHeight > 0)
1764 vertInfo.nPage /= infoPtr->nItemHeight;
1766 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1770 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1773 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1774 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1775 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
1776 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1777 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1779 /* Change of the range may have changed the scroll pos. If so move the content */
1780 if (dx != 0 || dy != 0)
1783 listRect = infoPtr->rcList;
1784 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
1785 SW_ERASE | SW_INVALIDATE);
1788 /* Update the Header Control */
1789 if (uView == LVS_REPORT)
1791 horzInfo.fMask = SIF_POS;
1792 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1793 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1800 * Shows/hides the focus rectangle.
1803 * [I] infoPtr : valid pointer to the listview structure
1804 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1809 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
1811 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1814 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1816 if (infoPtr->nFocusedItem < 0) return;
1818 /* we need some gymnastics in ICON mode to handle large items */
1819 if (uView == LVS_ICON)
1823 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1824 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1826 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1831 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1833 /* for some reason, owner draw should work only in report mode */
1834 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1839 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1840 HFONT hOldFont = SelectObject(hdc, hFont);
1842 item.iItem = infoPtr->nFocusedItem;
1844 item.mask = LVIF_PARAM;
1845 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1847 ZeroMemory(&dis, sizeof(dis));
1848 dis.CtlType = ODT_LISTVIEW;
1849 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1850 dis.itemID = item.iItem;
1851 dis.itemAction = ODA_FOCUS;
1852 if (fShow) dis.itemState |= ODS_FOCUS;
1853 dis.hwndItem = infoPtr->hwndSelf;
1855 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1856 dis.itemData = item.lParam;
1858 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1860 SelectObject(hdc, hOldFont);
1864 LISTVIEW_DrawFocusRect(infoPtr, hdc);
1867 ReleaseDC(infoPtr->hwndSelf, hdc);
1871 * Invalidates all visible selected items.
1873 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
1877 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1878 while(iterator_next(&i))
1880 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1881 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1883 iterator_destroy(&i);
1888 * DESCRIPTION: [INTERNAL]
1889 * Computes an item's (left,top) corner, relative to rcView.
1890 * That is, the position has NOT been made relative to the Origin.
1891 * This is deliberate, to avoid computing the Origin over, and
1892 * over again, when this function is called in a loop. Instead,
1893 * one can factor the computation of the Origin before the loop,
1894 * and offset the value returned by this function, on every iteration.
1897 * [I] infoPtr : valid pointer to the listview structure
1898 * [I] nItem : item number
1899 * [O] lpptOrig : item top, left corner
1904 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1906 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1908 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1910 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1912 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1913 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1915 else if (uView == LVS_LIST)
1917 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1918 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1919 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1921 else /* LVS_REPORT */
1923 lpptPosition->x = REPORT_MARGINX;
1924 /* item is always at zero indexed column */
1925 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
1926 lpptPosition->x += LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
1927 lpptPosition->y = nItem * infoPtr->nItemHeight;
1932 * DESCRIPTION: [INTERNAL]
1933 * Compute the rectangles of an item. This is to localize all
1934 * the computations in one place. If you are not interested in some
1935 * of these values, simply pass in a NULL -- the function is smart
1936 * enough to compute only what's necessary. The function computes
1937 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1938 * one, the BOX rectangle. This rectangle is very cheap to compute,
1939 * and is guaranteed to contain all the other rectangles. Computing
1940 * the ICON rect is also cheap, but all the others are potentially
1941 * expensive. This gives an easy and effective optimization when
1942 * searching (like point inclusion, or rectangle intersection):
1943 * first test against the BOX, and if TRUE, test against the desired
1945 * If the function does not have all the necessary information
1946 * to computed the requested rectangles, will crash with a
1947 * failed assertion. This is done so we catch all programming
1948 * errors, given that the function is called only from our code.
1950 * We have the following 'special' meanings for a few fields:
1951 * * If LVIS_FOCUSED is set, we assume the item has the focus
1952 * This is important in ICON mode, where it might get a larger
1953 * then usual rectangle
1955 * Please note that subitem support works only in REPORT mode.
1958 * [I] infoPtr : valid pointer to the listview structure
1959 * [I] lpLVItem : item to compute the measures for
1960 * [O] lprcBox : ptr to Box rectangle
1961 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
1962 * [0] lprcSelectBox : ptr to select box rectangle
1963 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
1964 * [O] lprcIcon : ptr to Icon rectangle
1965 * Same as LVM_GETITEMRECT with LVIR_ICON
1966 * [O] lprcStateIcon: ptr to State Icon rectangle
1967 * [O] lprcLabel : ptr to Label rectangle
1968 * Same as LVM_GETITEMRECT with LVIR_LABEL
1973 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1974 LPRECT lprcBox, LPRECT lprcSelectBox,
1975 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
1977 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1978 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1979 RECT Box, SelectBox, Icon, Label;
1980 COLUMN_INFO *lpColumnInfo = NULL;
1981 SIZE labelSize = { 0, 0 };
1983 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1985 /* Be smart and try to figure out the minimum we have to do */
1986 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1987 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1989 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1990 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1992 if (lprcSelectBox) doSelectBox = TRUE;
1993 if (lprcLabel) doLabel = TRUE;
1994 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
2001 /************************************************************/
2002 /* compute the box rectangle (it should be cheap to do) */
2003 /************************************************************/
2004 if (lpLVItem->iSubItem || uView == LVS_REPORT)
2005 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
2007 if (lpLVItem->iSubItem)
2009 Box = lpColumnInfo->rcHeader;
2014 Box.right = infoPtr->nItemWidth;
2017 Box.bottom = infoPtr->nItemHeight;
2019 /******************************************************************/
2020 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2021 /******************************************************************/
2024 LONG state_width = 0;
2026 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
2027 state_width = infoPtr->iconStateSize.cx;
2029 if (uView == LVS_ICON)
2031 Icon.left = Box.left + state_width;
2032 if (infoPtr->himlNormal)
2033 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2034 Icon.top = Box.top + ICON_TOP_PADDING;
2035 Icon.right = Icon.left;
2036 Icon.bottom = Icon.top;
2037 if (infoPtr->himlNormal)
2039 Icon.right += infoPtr->iconSize.cx;
2040 Icon.bottom += infoPtr->iconSize.cy;
2043 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2045 Icon.left = Box.left + state_width;
2047 if (uView == LVS_REPORT && lpLVItem->iSubItem == 0)
2049 /* we need the indent in report mode */
2050 assert(lpLVItem->mask & LVIF_INDENT);
2051 Icon.left += infoPtr->iconSize.cx * lpLVItem->iIndent + REPORT_MARGINX;
2055 Icon.right = Icon.left;
2056 if (infoPtr->himlSmall &&
2057 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2058 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2059 Icon.right += infoPtr->iconSize.cx;
2060 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2062 if(lprcIcon) *lprcIcon = Icon;
2063 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2065 /* TODO: is this correct? */
2068 lprcStateIcon->left = Icon.left - state_width;
2069 lprcStateIcon->right = Icon.left;
2070 lprcStateIcon->top = Icon.top;
2071 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2072 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2075 else Icon.right = 0;
2077 /************************************************************/
2078 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2079 /************************************************************/
2082 /* calculate how far to the right can the label stretch */
2083 Label.right = Box.right;
2084 if (uView == LVS_REPORT)
2086 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
2089 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
2091 labelSize.cx = infoPtr->nItemWidth;
2092 labelSize.cy = infoPtr->nItemHeight;
2096 /* we need the text in non owner draw mode */
2097 assert(lpLVItem->mask & LVIF_TEXT);
2098 if (is_textT(lpLVItem->pszText, TRUE))
2100 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2101 HDC hdc = GetDC(infoPtr->hwndSelf);
2102 HFONT hOldFont = SelectObject(hdc, hFont);
2106 /* compute rough rectangle where the label will go */
2107 SetRectEmpty(&rcText);
2108 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2109 rcText.bottom = infoPtr->nItemHeight;
2110 if (uView == LVS_ICON)
2111 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2113 /* now figure out the flags */
2114 if (uView == LVS_ICON)
2115 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2117 uFormat = LV_SL_DT_FLAGS;
2119 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2121 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2122 labelSize.cy = rcText.bottom - rcText.top;
2124 SelectObject(hdc, hOldFont);
2125 ReleaseDC(infoPtr->hwndSelf, hdc);
2129 if (uView == LVS_ICON)
2131 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2132 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2133 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2134 Label.right = Label.left + labelSize.cx;
2135 Label.bottom = Label.top + infoPtr->nItemHeight;
2136 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2138 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2139 labelSize.cy /= infoPtr->ntmHeight;
2140 labelSize.cy = max(labelSize.cy, 1);
2141 labelSize.cy *= infoPtr->ntmHeight;
2143 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2145 else if (uView == LVS_REPORT)
2147 Label.left = Icon.right;
2148 Label.top = Box.top;
2149 Label.right = lpColumnInfo->rcHeader.right;
2150 Label.bottom = Label.top + infoPtr->nItemHeight;
2152 else /* LVS_SMALLICON or LVS_LIST */
2154 Label.left = Icon.right;
2155 Label.top = Box.top;
2156 Label.right = min(Label.left + labelSize.cx, Label.right);
2157 Label.bottom = Label.top + infoPtr->nItemHeight;
2160 if (lprcLabel) *lprcLabel = Label;
2161 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2164 /************************************************************/
2165 /* compute SELECT bounding box */
2166 /************************************************************/
2169 if (uView == LVS_REPORT)
2171 SelectBox.left = Icon.left;
2172 SelectBox.top = Box.top;
2173 SelectBox.bottom = Box.bottom;
2174 SelectBox.right = min(Label.left + labelSize.cx, Label.right);
2178 UnionRect(&SelectBox, &Icon, &Label);
2180 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2181 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2184 /* Fix the Box if necessary */
2187 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2188 else *lprcBox = Box;
2190 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2194 * DESCRIPTION: [INTERNAL]
2197 * [I] infoPtr : valid pointer to the listview structure
2198 * [I] nItem : item number
2199 * [O] lprcBox : ptr to Box rectangle
2204 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2206 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2207 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2208 POINT Position, Origin;
2211 LISTVIEW_GetOrigin(infoPtr, &Origin);
2212 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2214 /* Be smart and try to figure out the minimum we have to do */
2216 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2217 lvItem.mask |= LVIF_TEXT;
2218 lvItem.iItem = nItem;
2219 lvItem.iSubItem = 0;
2220 lvItem.pszText = szDispText;
2221 lvItem.cchTextMax = DISP_TEXT_SIZE;
2222 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2223 if (uView == LVS_ICON)
2225 lvItem.mask |= LVIF_STATE;
2226 lvItem.stateMask = LVIS_FOCUSED;
2227 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2229 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2231 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2237 * Returns the current icon position, and advances it along the top.
2238 * The returned position is not offset by Origin.
2241 * [I] infoPtr : valid pointer to the listview structure
2242 * [O] lpPos : will get the current icon position
2247 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2249 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2251 *lpPos = infoPtr->currIconPos;
2253 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2254 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2256 infoPtr->currIconPos.x = 0;
2257 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2263 * Returns the current icon position, and advances it down the left edge.
2264 * The returned position is not offset by Origin.
2267 * [I] infoPtr : valid pointer to the listview structure
2268 * [O] lpPos : will get the current icon position
2273 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2275 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2277 *lpPos = infoPtr->currIconPos;
2279 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2280 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2282 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2283 infoPtr->currIconPos.y = 0;
2289 * Moves an icon to the specified position.
2290 * It takes care of invalidating the item, etc.
2293 * [I] infoPtr : valid pointer to the listview structure
2294 * [I] nItem : the item to move
2295 * [I] lpPos : the new icon position
2296 * [I] isNew : flags the item as being new
2302 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2308 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2309 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2311 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2312 LISTVIEW_InvalidateItem(infoPtr, nItem);
2315 /* Allocating a POINTER for every item is too resource intensive,
2316 * so we'll keep the (x,y) in different arrays */
2317 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2318 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2320 LISTVIEW_InvalidateItem(infoPtr, nItem);
2327 * Arranges listview items in icon display mode.
2330 * [I] infoPtr : valid pointer to the listview structure
2331 * [I] nAlignCode : alignment code
2337 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2339 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2340 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2344 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2346 TRACE("nAlignCode=%d\n", nAlignCode);
2348 if (nAlignCode == LVA_DEFAULT)
2350 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2351 else nAlignCode = LVA_ALIGNTOP;
2356 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2357 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2358 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2359 default: return FALSE;
2362 infoPtr->bAutoarrange = TRUE;
2363 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2364 for (i = 0; i < infoPtr->nItemCount; i++)
2366 next_pos(infoPtr, &pos);
2367 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2375 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2376 * For LVS_REPORT always returns empty rectangle.
2379 * [I] infoPtr : valid pointer to the listview structure
2380 * [O] lprcView : bounding rectangle
2386 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2390 SetRectEmpty(lprcView);
2392 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2396 for (i = 0; i < infoPtr->nItemCount; i++)
2398 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2399 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2400 lprcView->right = max(lprcView->right, x);
2401 lprcView->bottom = max(lprcView->bottom, y);
2403 if (infoPtr->nItemCount > 0)
2405 lprcView->right += infoPtr->nItemWidth;
2406 lprcView->bottom += infoPtr->nItemHeight;
2411 y = LISTVIEW_GetCountPerColumn(infoPtr);
2412 x = infoPtr->nItemCount / y;
2413 if (infoPtr->nItemCount % y) x++;
2414 lprcView->right = x * infoPtr->nItemWidth;
2415 lprcView->bottom = y * infoPtr->nItemHeight;
2422 * Retrieves the bounding rectangle of all the items.
2425 * [I] infoPtr : valid pointer to the listview structure
2426 * [O] lprcView : bounding rectangle
2432 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2436 TRACE("(lprcView=%p)\n", lprcView);
2438 if (!lprcView) return FALSE;
2440 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2442 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT)
2444 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2445 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2448 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2455 * Retrieves the subitem pointer associated with the subitem index.
2458 * [I] hdpaSubItems : DPA handle for a specific item
2459 * [I] nSubItem : index of subitem
2462 * SUCCESS : subitem pointer
2465 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2467 SUBITEM_INFO *lpSubItem;
2470 /* we should binary search here if need be */
2471 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2473 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2474 if (lpSubItem->iSubItem == nSubItem)
2484 * Calculates the desired item width.
2487 * [I] infoPtr : valid pointer to the listview structure
2490 * The desired item width.
2492 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2494 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2497 TRACE("uView=%d\n", uView);
2499 if (uView == LVS_ICON)
2500 nItemWidth = infoPtr->iconSpacing.cx;
2501 else if (uView == LVS_REPORT)
2505 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2507 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2508 nItemWidth = rcHeader.right;
2511 else /* LVS_SMALLICON, or LVS_LIST */
2515 for (i = 0; i < infoPtr->nItemCount; i++)
2516 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2518 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2519 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2521 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2524 return max(nItemWidth, 1);
2529 * Calculates the desired item height.
2532 * [I] infoPtr : valid pointer to the listview structure
2535 * The desired item height.
2537 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2539 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2542 TRACE("uView=%d\n", uView);
2544 if (uView == LVS_ICON)
2545 nItemHeight = infoPtr->iconSpacing.cy;
2548 nItemHeight = infoPtr->ntmHeight;
2549 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
2551 if (infoPtr->himlState)
2552 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2553 if (infoPtr->himlSmall)
2554 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2555 if (infoPtr->himlState || infoPtr->himlSmall)
2556 nItemHeight += HEIGHT_PADDING;
2557 if (infoPtr->nMeasureItemHeight > 0)
2558 nItemHeight = infoPtr->nMeasureItemHeight;
2561 return max(nItemHeight, 1);
2566 * Updates the width, and height of an item.
2569 * [I] infoPtr : valid pointer to the listview structure
2574 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2576 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2577 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2583 * Retrieves and saves important text metrics info for the current
2587 * [I] infoPtr : valid pointer to the listview structure
2590 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2592 HDC hdc = GetDC(infoPtr->hwndSelf);
2593 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2594 HFONT hOldFont = SelectObject(hdc, hFont);
2598 if (GetTextMetricsW(hdc, &tm))
2600 infoPtr->ntmHeight = tm.tmHeight;
2601 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2604 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2605 infoPtr->nEllipsisWidth = sz.cx;
2607 SelectObject(hdc, hOldFont);
2608 ReleaseDC(infoPtr->hwndSelf, hdc);
2610 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2615 * A compare function for ranges
2618 * [I] range1 : pointer to range 1;
2619 * [I] range2 : pointer to range 2;
2623 * > 0 : if range 1 > range 2
2624 * < 0 : if range 2 > range 1
2625 * = 0 : if range intersects range 2
2627 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2631 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2633 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2638 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
2644 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2646 #define ranges_check(ranges, desc) do { } while(0)
2649 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2654 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2656 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2657 ranges_dump(ranges);
2658 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2660 prev = DPA_GetPtr(ranges->hdpa, 0);
2661 assert (prev->lower >= 0 && prev->lower < prev->upper);
2662 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2664 curr = DPA_GetPtr(ranges->hdpa, i);
2665 assert (prev->upper <= curr->lower);
2666 assert (curr->lower < curr->upper);
2670 TRACE("--- Done checking---\n");
2673 static RANGES ranges_create(int count)
2675 RANGES ranges = Alloc(sizeof(struct tagRANGES));
2676 if (!ranges) return NULL;
2677 ranges->hdpa = DPA_Create(count);
2678 if (ranges->hdpa) return ranges;
2683 static void ranges_clear(RANGES ranges)
2687 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2688 Free(DPA_GetPtr(ranges->hdpa, i));
2689 DPA_DeleteAllPtrs(ranges->hdpa);
2693 static void ranges_destroy(RANGES ranges)
2695 if (!ranges) return;
2696 ranges_clear(ranges);
2697 DPA_Destroy(ranges->hdpa);
2701 static RANGES ranges_clone(RANGES ranges)
2706 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2708 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2710 RANGE *newrng = Alloc(sizeof(RANGE));
2711 if (!newrng) goto fail;
2712 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2713 DPA_SetPtr(clone->hdpa, i, newrng);
2718 TRACE ("clone failed\n");
2719 ranges_destroy(clone);
2723 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2727 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2728 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2733 static void ranges_dump(RANGES ranges)
2737 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2738 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2741 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2743 RANGE srchrng = { nItem, nItem + 1 };
2745 TRACE("(nItem=%d)\n", nItem);
2746 ranges_check(ranges, "before contain");
2747 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2750 static INT ranges_itemcount(RANGES ranges)
2754 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2756 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2757 count += sel->upper - sel->lower;
2763 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2765 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2768 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2769 if (index == -1) return TRUE;
2771 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2773 chkrng = DPA_GetPtr(ranges->hdpa, index);
2774 if (chkrng->lower >= nItem)
2775 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2776 if (chkrng->upper > nItem)
2777 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2782 static BOOL ranges_add(RANGES ranges, RANGE range)
2787 TRACE("(%s)\n", debugrange(&range));
2788 ranges_check(ranges, "before add");
2790 /* try find overlapping regions first */
2791 srchrgn.lower = range.lower - 1;
2792 srchrgn.upper = range.upper + 1;
2793 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2799 TRACE("Adding new range\n");
2801 /* create the brand new range to insert */
2802 newrgn = Alloc(sizeof(RANGE));
2803 if(!newrgn) goto fail;
2806 /* figure out where to insert it */
2807 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2808 TRACE("index=%d\n", index);
2809 if (index == -1) index = 0;
2811 /* and get it over with */
2812 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2820 RANGE *chkrgn, *mrgrgn;
2821 INT fromindex, mergeindex;
2823 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2824 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2826 chkrgn->lower = min(range.lower, chkrgn->lower);
2827 chkrgn->upper = max(range.upper, chkrgn->upper);
2829 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2831 /* merge now common ranges */
2833 srchrgn.lower = chkrgn->lower - 1;
2834 srchrgn.upper = chkrgn->upper + 1;
2838 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2839 if (mergeindex == -1) break;
2840 if (mergeindex == index)
2842 fromindex = index + 1;
2846 TRACE("Merge with index %i\n", mergeindex);
2848 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2849 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2850 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2852 DPA_DeletePtr(ranges->hdpa, mergeindex);
2853 if (mergeindex < index) index --;
2857 ranges_check(ranges, "after add");
2861 ranges_check(ranges, "failed add");
2865 static BOOL ranges_del(RANGES ranges, RANGE range)
2870 TRACE("(%s)\n", debugrange(&range));
2871 ranges_check(ranges, "before del");
2873 /* we don't use DPAS_SORTED here, since we need *
2874 * to find the first overlapping range */
2875 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2878 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2880 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2882 /* case 1: Same range */
2883 if ( (chkrgn->upper == range.upper) &&
2884 (chkrgn->lower == range.lower) )
2886 DPA_DeletePtr(ranges->hdpa, index);
2889 /* case 2: engulf */
2890 else if ( (chkrgn->upper <= range.upper) &&
2891 (chkrgn->lower >= range.lower) )
2893 DPA_DeletePtr(ranges->hdpa, index);
2895 /* case 3: overlap upper */
2896 else if ( (chkrgn->upper <= range.upper) &&
2897 (chkrgn->lower < range.lower) )
2899 chkrgn->upper = range.lower;
2901 /* case 4: overlap lower */
2902 else if ( (chkrgn->upper > range.upper) &&
2903 (chkrgn->lower >= range.lower) )
2905 chkrgn->lower = range.upper;
2908 /* case 5: fully internal */
2911 RANGE tmprgn = *chkrgn, *newrgn;
2913 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
2914 newrgn->lower = chkrgn->lower;
2915 newrgn->upper = range.lower;
2916 chkrgn->lower = range.upper;
2917 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2926 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2929 ranges_check(ranges, "after del");
2933 ranges_check(ranges, "failed del");
2939 * Removes all selection ranges
2942 * [I] infoPtr : valid pointer to the listview structure
2943 * [I] toSkip : item range to skip removing the selection
2949 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2958 lvItem.stateMask = LVIS_SELECTED;
2960 /* need to clone the DPA because callbacks can change it */
2961 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2962 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2963 while(iterator_next(&i))
2964 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2965 /* note that the iterator destructor will free the cloned range */
2966 iterator_destroy(&i);
2971 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2975 if (!(toSkip = ranges_create(1))) return FALSE;
2976 if (nItem != -1) ranges_additem(toSkip, nItem);
2977 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2978 ranges_destroy(toSkip);
2982 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2984 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2989 * Retrieves the number of items that are marked as selected.
2992 * [I] infoPtr : valid pointer to the listview structure
2995 * Number of items selected.
2997 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
2999 INT nSelectedCount = 0;
3001 if (infoPtr->uCallbackMask & LVIS_SELECTED)
3004 for (i = 0; i < infoPtr->nItemCount; i++)
3006 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3011 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3013 TRACE("nSelectedCount=%d\n", nSelectedCount);
3014 return nSelectedCount;
3019 * Manages the item focus.
3022 * [I] infoPtr : valid pointer to the listview structure
3023 * [I] nItem : item index
3026 * TRUE : focused item changed
3027 * FALSE : focused item has NOT changed
3029 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3031 INT oldFocus = infoPtr->nFocusedItem;
3034 if (nItem == infoPtr->nFocusedItem) return FALSE;
3036 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3037 lvItem.stateMask = LVIS_FOCUSED;
3038 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3040 return oldFocus != infoPtr->nFocusedItem;
3043 /* Helper function for LISTVIEW_ShiftIndices *only* */
3044 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3046 if (nShiftItem < nItem) return nShiftItem;
3048 if (nShiftItem > nItem) return nShiftItem + direction;
3050 if (direction > 0) return nShiftItem + direction;
3052 return min(nShiftItem, infoPtr->nItemCount - 1);
3057 * Updates the various indices after an item has been inserted or deleted.
3060 * [I] infoPtr : valid pointer to the listview structure
3061 * [I] nItem : item index
3062 * [I] direction : Direction of shift, +1 or -1.
3067 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3072 /* temporarily disable change notification while shifting items */
3073 bOldChange = infoPtr->bDoChangeNotify;
3074 infoPtr->bDoChangeNotify = FALSE;
3076 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3078 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3080 assert(abs(direction) == 1);
3082 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3084 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3085 if (nNewFocus != infoPtr->nFocusedItem)
3086 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3088 /* But we are not supposed to modify nHotItem! */
3090 infoPtr->bDoChangeNotify = bOldChange;
3096 * Adds a block of selections.
3099 * [I] infoPtr : valid pointer to the listview structure
3100 * [I] nItem : item index
3103 * Whether the window is still valid.
3105 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3107 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3108 INT nLast = max(infoPtr->nSelectionMark, nItem);
3109 HWND hwndSelf = infoPtr->hwndSelf;
3110 NMLVODSTATECHANGE nmlv;
3115 /* Temporarily disable change notification
3116 * If the control is LVS_OWNERDATA, we need to send
3117 * only one LVN_ODSTATECHANGED notification.
3118 * See MSDN documentation for LVN_ITEMCHANGED.
3120 bOldChange = infoPtr->bDoChangeNotify;
3121 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3123 if (nFirst == -1) nFirst = nItem;
3125 item.state = LVIS_SELECTED;
3126 item.stateMask = LVIS_SELECTED;
3128 for (i = nFirst; i <= nLast; i++)
3129 LISTVIEW_SetItemState(infoPtr,i,&item);
3131 ZeroMemory(&nmlv, sizeof(nmlv));
3132 nmlv.iFrom = nFirst;
3135 nmlv.uOldState = item.state;
3137 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3138 if (!IsWindow(hwndSelf))
3140 infoPtr->bDoChangeNotify = bOldChange;
3147 * Sets a single group selection.
3150 * [I] infoPtr : valid pointer to the listview structure
3151 * [I] nItem : item index
3156 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3158 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3164 if (!(selection = ranges_create(100))) return;
3166 item.state = LVIS_SELECTED;
3167 item.stateMask = LVIS_SELECTED;
3169 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3171 if (infoPtr->nSelectionMark == -1)
3173 infoPtr->nSelectionMark = nItem;
3174 ranges_additem(selection, nItem);
3180 sel.lower = min(infoPtr->nSelectionMark, nItem);
3181 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3182 ranges_add(selection, sel);
3187 RECT rcItem, rcSel, rcSelMark;
3190 rcItem.left = LVIR_BOUNDS;
3191 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3192 rcSelMark.left = LVIR_BOUNDS;
3193 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3194 UnionRect(&rcSel, &rcItem, &rcSelMark);
3195 iterator_frameditems(&i, infoPtr, &rcSel);
3196 while(iterator_next(&i))
3198 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3199 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3201 iterator_destroy(&i);
3204 /* disable per item notifications on LVS_OWNERDATA style
3205 FIXME: single LVN_ODSTATECHANGED should be used */
3206 bOldChange = infoPtr->bDoChangeNotify;
3207 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3209 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3212 iterator_rangesitems(&i, selection);
3213 while(iterator_next(&i))
3214 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3215 /* this will also destroy the selection */
3216 iterator_destroy(&i);
3218 infoPtr->bDoChangeNotify = bOldChange;
3220 LISTVIEW_SetItemFocus(infoPtr, nItem);
3225 * Sets a single selection.
3228 * [I] infoPtr : valid pointer to the listview structure
3229 * [I] nItem : item index
3234 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3238 TRACE("nItem=%d\n", nItem);
3240 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3242 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3243 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3244 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3246 infoPtr->nSelectionMark = nItem;
3251 * Set selection(s) with keyboard.
3254 * [I] infoPtr : valid pointer to the listview structure
3255 * [I] nItem : item index
3256 * [I] space : VK_SPACE code sent
3259 * SUCCESS : TRUE (needs to be repainted)
3260 * FAILURE : FALSE (nothing has changed)
3262 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem, BOOL space)
3264 /* FIXME: pass in the state */
3265 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3266 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3267 BOOL bResult = FALSE;
3269 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3270 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3274 if (infoPtr->dwStyle & LVS_SINGLESEL || (wShift == 0 && wCtrl == 0))
3275 LISTVIEW_SetSelection(infoPtr, nItem);
3279 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3283 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3284 lvItem.stateMask = LVIS_SELECTED;
3287 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3288 if (lvItem.state & LVIS_SELECTED)
3289 infoPtr->nSelectionMark = nItem;
3291 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3294 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3297 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3301 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3303 LVHITTESTINFO lvHitTestInfo;
3305 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3306 lvHitTestInfo.pt.x = pt.x;
3307 lvHitTestInfo.pt.y = pt.y;
3309 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3311 lpLVItem->mask = LVIF_PARAM;
3312 lpLVItem->iItem = lvHitTestInfo.iItem;
3313 lpLVItem->iSubItem = 0;
3315 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3318 static inline BOOL LISTVIEW_isHotTracking(const LISTVIEW_INFO *infoPtr)
3320 return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3321 (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3322 (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3327 * Called when the mouse is being actively tracked and has hovered for a specified
3331 * [I] infoPtr : valid pointer to the listview structure
3332 * [I] fwKeys : key indicator
3333 * [I] x,y : mouse position
3336 * 0 if the message was processed, non-zero if there was an error
3339 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3340 * over the item for a certain period of time.
3343 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3345 if (LISTVIEW_isHotTracking(infoPtr))
3353 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3354 LISTVIEW_SetSelection(infoPtr, item.iItem);
3362 * Called whenever WM_MOUSEMOVE is received.
3365 * [I] infoPtr : valid pointer to the listview structure
3366 * [I] fwKeys : key indicator
3367 * [I] x,y : mouse position
3370 * 0 if the message is processed, non-zero if there was an error
3372 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3374 TRACKMOUSEEVENT trackinfo;
3376 if (!(fwKeys & MK_LBUTTON))
3377 infoPtr->bLButtonDown = FALSE;
3379 if (infoPtr->bLButtonDown)
3383 LVHITTESTINFO lvHitTestInfo;
3384 WORD wDragWidth = GetSystemMetrics(SM_CXDRAG);
3385 WORD wDragHeight= GetSystemMetrics(SM_CYDRAG);
3387 rect.left = infoPtr->ptClickPos.x - wDragWidth;
3388 rect.right = infoPtr->ptClickPos.x + wDragWidth;
3389 rect.top = infoPtr->ptClickPos.y - wDragHeight;
3390 rect.bottom = infoPtr->ptClickPos.y + wDragHeight;
3395 lvHitTestInfo.pt = tmp;
3396 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3398 /* reset item marker */
3399 if (infoPtr->nLButtonDownItem != lvHitTestInfo.iItem)
3400 infoPtr->nLButtonDownItem = -1;
3402 if (!PtInRect(&rect, tmp))
3404 /* this path covers the following:
3405 1. WM_LBUTTONDOWN over selected item (sets focus on it)
3406 2. change focus with keys
3407 3. move mouse over item from step 1 selects it and moves focus on it */
3408 if (infoPtr->nLButtonDownItem != -1 &&
3409 !LISTVIEW_GetItemState(infoPtr, infoPtr->nLButtonDownItem, LVIS_SELECTED))
3413 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3414 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3416 LISTVIEW_SetItemState(infoPtr, infoPtr->nLButtonDownItem, &lvItem);
3417 infoPtr->nLButtonDownItem = -1;
3420 if (!infoPtr->bDragging)
3424 lvHitTestInfo.pt = infoPtr->ptClickPos;
3425 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3427 ZeroMemory(&nmlv, sizeof(nmlv));
3428 nmlv.iItem = lvHitTestInfo.iItem;
3429 nmlv.ptAction = infoPtr->ptClickPos;
3431 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3432 infoPtr->bDragging = TRUE;
3439 /* see if we are supposed to be tracking mouse hovering */
3440 if (LISTVIEW_isHotTracking(infoPtr)) {
3441 /* fill in the trackinfo struct */
3442 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3443 trackinfo.dwFlags = TME_QUERY;
3444 trackinfo.hwndTrack = infoPtr->hwndSelf;
3445 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3447 /* see if we are already tracking this hwnd */
3448 _TrackMouseEvent(&trackinfo);
3450 if(!(trackinfo.dwFlags & TME_HOVER)) {
3451 trackinfo.dwFlags = TME_HOVER;
3453 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3454 _TrackMouseEvent(&trackinfo);
3463 * Tests whether the item is assignable to a list with style lStyle
3465 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3467 if ( (lpLVItem->mask & LVIF_TEXT) &&
3468 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3469 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3477 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3480 * [I] infoPtr : valid pointer to the listview structure
3481 * [I] lpLVItem : valid pointer to new item attributes
3482 * [I] isNew : the item being set is being inserted
3483 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3484 * [O] bChanged : will be set to TRUE if the item really changed
3490 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3492 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3497 /* stateMask is ignored for LVM_INSERTITEM */
3498 UINT stateMask = isNew ? ~0 : lpLVItem->stateMask;
3502 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3504 if (lpLVItem->mask == 0) return TRUE;
3506 if (infoPtr->dwStyle & LVS_OWNERDATA)
3508 /* a virtual listview only stores selection and focus */
3509 if (lpLVItem->mask & ~LVIF_STATE)
3515 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3516 lpItem = DPA_GetPtr(hdpaSubItems, 0);
3520 /* we need to get the lParam and state of the item */
3521 item.iItem = lpLVItem->iItem;
3522 item.iSubItem = lpLVItem->iSubItem;
3523 item.mask = LVIF_STATE | LVIF_PARAM;
3524 item.stateMask = (infoPtr->dwStyle & LVS_OWNERDATA) ? LVIS_FOCUSED | LVIS_SELECTED : ~0;
3528 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3530 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3531 /* determine what fields will change */
3532 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & stateMask & ~infoPtr->uCallbackMask))
3533 uChanged |= LVIF_STATE;
3535 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3536 uChanged |= LVIF_IMAGE;
3538 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3539 uChanged |= LVIF_PARAM;
3541 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3542 uChanged |= LVIF_INDENT;
3544 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3545 uChanged |= LVIF_TEXT;
3547 TRACE("uChanged=0x%x\n", uChanged);
3548 if (!uChanged) return TRUE;
3551 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3552 nmlv.iItem = lpLVItem->iItem;
3553 nmlv.uNewState = (item.state & ~stateMask) | (lpLVItem->state & stateMask);
3554 nmlv.uOldState = item.state;
3555 nmlv.uChanged = uChanged;
3556 nmlv.lParam = item.lParam;
3558 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3559 /* and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications */
3561 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
3563 HWND hwndSelf = infoPtr->hwndSelf;
3565 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3567 if (!IsWindow(hwndSelf))
3571 /* copy information */
3572 if (lpLVItem->mask & LVIF_TEXT)
3573 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3575 if (lpLVItem->mask & LVIF_IMAGE)
3576 lpItem->hdr.iImage = lpLVItem->iImage;
3578 if (lpLVItem->mask & LVIF_PARAM)
3579 lpItem->lParam = lpLVItem->lParam;
3581 if (lpLVItem->mask & LVIF_INDENT)
3582 lpItem->iIndent = lpLVItem->iIndent;
3584 if (uChanged & LVIF_STATE)
3586 if (lpItem && (stateMask & ~infoPtr->uCallbackMask))
3588 lpItem->state &= ~stateMask;
3589 lpItem->state |= (lpLVItem->state & stateMask);
3591 if (lpLVItem->state & stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3593 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3594 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3596 else if (stateMask & LVIS_SELECTED)
3598 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3600 /* if we are asked to change focus, and we manage it, do it */
3601 if (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3603 if (lpLVItem->state & LVIS_FOCUSED)
3605 if (infoPtr->nFocusedItem != -1)
3607 /* remove current focus */
3608 item.mask = LVIF_STATE;
3610 item.stateMask = LVIS_FOCUSED;
3612 /* recurse with redrawing an item */
3613 LISTVIEW_SetItemState(infoPtr, infoPtr->nFocusedItem, &item);
3616 infoPtr->nFocusedItem = lpLVItem->iItem;
3617 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3619 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3621 infoPtr->nFocusedItem = -1;
3626 /* if we're inserting the item, we're done */
3627 if (isNew) return TRUE;
3629 /* send LVN_ITEMCHANGED notification */
3630 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3631 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3638 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3641 * [I] infoPtr : valid pointer to the listview structure
3642 * [I] lpLVItem : valid pointer to new subitem attributes
3643 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3644 * [O] bChanged : will be set to TRUE if the item really changed
3650 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3653 SUBITEM_INFO *lpSubItem;
3655 /* we do not support subitems for virtual listviews */
3656 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3658 /* set subitem only if column is present */
3659 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3661 /* First do some sanity checks */
3662 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
3663 particularly useful. We currently do not actually do anything with
3664 the flag on subitems.
3666 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) return FALSE;
3667 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
3669 /* get the subitem structure, and create it if not there */
3670 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3671 assert (hdpaSubItems);
3673 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3676 SUBITEM_INFO *tmpSubItem;
3679 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
3680 if (!lpSubItem) return FALSE;
3681 /* we could binary search here, if need be...*/
3682 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3684 tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
3685 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3687 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3692 lpSubItem->iSubItem = lpLVItem->iSubItem;
3693 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3697 if (lpLVItem->mask & LVIF_IMAGE)
3698 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3700 lpSubItem->hdr.iImage = lpLVItem->iImage;
3704 if (lpLVItem->mask & LVIF_TEXT)
3705 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3707 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3716 * Sets item attributes.
3719 * [I] infoPtr : valid pointer to the listview structure
3720 * [I] lpLVItem : new item attributes
3721 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3727 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
3729 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3730 HWND hwndSelf = infoPtr->hwndSelf;
3731 LPWSTR pszText = NULL;
3732 BOOL bResult, bChanged = FALSE;
3734 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3736 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3739 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3740 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3742 pszText = lpLVItem->pszText;
3743 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3746 /* actually set the fields */
3747 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3749 if (lpLVItem->iSubItem)
3750 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3752 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3753 if (!IsWindow(hwndSelf))
3756 /* redraw item, if necessary */
3757 if (bChanged && !infoPtr->bIsDrawing)
3759 /* this little optimization eliminates some nasty flicker */
3760 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3761 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
3762 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
3763 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3765 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3770 textfreeT(lpLVItem->pszText, isW);
3771 lpLVItem->pszText = pszText;
3779 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3782 * [I] infoPtr : valid pointer to the listview structure
3787 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
3789 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3791 SCROLLINFO scrollInfo;
3793 scrollInfo.cbSize = sizeof(SCROLLINFO);
3794 scrollInfo.fMask = SIF_POS;
3796 if (uView == LVS_LIST)
3798 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3799 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3801 else if (uView == LVS_REPORT)
3803 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3804 nItem = scrollInfo.nPos;
3808 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3809 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3812 TRACE("nItem=%d\n", nItem);
3820 * Erases the background of the given rectangle
3823 * [I] infoPtr : valid pointer to the listview structure
3824 * [I] hdc : device context handle
3825 * [I] lprcBox : clipping rectangle
3831 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3833 if (!infoPtr->hBkBrush) return FALSE;
3835 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
3837 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3845 * [I] infoPtr : valid pointer to the listview structure
3846 * [I] hdc : device context handle
3847 * [I] nItem : item index
3848 * [I] nSubItem : subitem index
3849 * [I] pos : item position in client coordinates
3850 * [I] cdmode : custom draw mode
3856 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3858 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3859 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3860 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3861 DWORD cdsubitemmode = CDRF_DODEFAULT;
3863 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
3864 NMLVCUSTOMDRAW nmlvcd;
3869 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
3871 /* get information needed for drawing the item */
3872 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
3873 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3874 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3875 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3876 lvItem.iItem = nItem;
3877 lvItem.iSubItem = nSubItem;
3880 lvItem.cchTextMax = DISP_TEXT_SIZE;
3881 lvItem.pszText = szDispText;
3882 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3883 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3884 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3885 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3886 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3888 /* now check if we need to update the focus rectangle */
3889 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3891 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3892 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
3893 OffsetRect(&rcBox, pos.x, pos.y);
3894 OffsetRect(&rcSelect, pos.x, pos.y);
3895 OffsetRect(&rcIcon, pos.x, pos.y);
3896 OffsetRect(&rcStateIcon, pos.x, pos.y);
3897 OffsetRect(&rcLabel, pos.x, pos.y);
3898 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
3899 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
3900 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
3902 /* fill in the custom draw structure */
3903 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3905 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
3906 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3907 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
3908 if (cdmode & CDRF_NOTIFYITEMDRAW)
3909 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3910 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3911 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3912 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3913 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3915 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3916 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3918 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3919 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
3920 else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
3921 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
3923 /* in full row select, subitems, will just use main item's colors */
3924 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3925 nmlvcd.clrTextBk = CLR_NONE;
3928 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
3930 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
3933 TRACE("uStateImage=%d\n", uStateImage);
3934 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
3935 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
3940 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3941 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3943 TRACE("iImage=%d\n", lvItem.iImage);
3944 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3945 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk, CLR_DEFAULT,
3946 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3949 /* Don't bother painting item being edited */
3950 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3952 /* FIXME: temporary hack */
3953 rcSelect.left = rcLabel.left;
3955 /* draw the selection background, if we're drawing the main item */
3958 /* in icon mode, the label rect is really what we want to draw the
3960 if (uView == LVS_ICON)
3963 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3964 rcSelect.right = rcBox.right;
3966 if (nmlvcd.clrTextBk != CLR_NONE)
3967 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3968 /* store new focus rectangle */
3969 if (infoPtr->nFocusedItem == nItem) infoPtr->rcFocus = rcSelect;
3972 /* figure out the text drawing flags */
3973 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3974 if (uView == LVS_ICON)
3975 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3978 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3980 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3981 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3982 default: uFormat |= DT_LEFT;
3985 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3987 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3988 else rcLabel.left += LABEL_HOR_PADDING;
3990 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3992 /* for GRIDLINES reduce the bottom so the text formats correctly */
3993 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
3996 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3999 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
4000 notify_postpaint(infoPtr, &nmlvcd);
4001 if (cdsubitemmode & CDRF_NEWFONT)
4002 SelectObject(hdc, hOldFont);
4008 * Draws listview items when in owner draw mode.
4011 * [I] infoPtr : valid pointer to the listview structure
4012 * [I] hdc : device context handle
4017 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4019 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
4020 DWORD cditemmode = CDRF_DODEFAULT;
4021 NMLVCUSTOMDRAW nmlvcd;
4022 POINT Origin, Position;
4028 ZeroMemory(&dis, sizeof(dis));
4030 /* Get scroll info once before loop */
4031 LISTVIEW_GetOrigin(infoPtr, &Origin);
4033 /* iterate through the invalidated rows */
4034 while(iterator_next(i))
4036 item.iItem = i->nItem;
4038 item.mask = LVIF_PARAM | LVIF_STATE;
4039 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4040 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
4042 dis.CtlType = ODT_LISTVIEW;
4044 dis.itemID = item.iItem;
4045 dis.itemAction = ODA_DRAWENTIRE;
4047 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
4048 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
4049 dis.hwndItem = infoPtr->hwndSelf;
4051 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
4052 dis.rcItem.left = Position.x + Origin.x;
4053 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
4054 dis.rcItem.top = Position.y + Origin.y;
4055 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
4056 dis.itemData = item.lParam;
4058 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
4061 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
4062 * structure for the rest. of the paint cycle
4064 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
4065 if (cdmode & CDRF_NOTIFYITEMDRAW)
4066 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4068 if (!(cditemmode & CDRF_SKIPDEFAULT))
4070 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
4071 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
4074 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
4075 notify_postpaint(infoPtr, &nmlvcd);
4081 * Draws listview items when in report display mode.
4084 * [I] infoPtr : valid pointer to the listview structure
4085 * [I] hdc : device context handle
4086 * [I] cdmode : custom draw mode
4091 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4094 RECT rcClip, rcItem;
4095 POINT Origin, Position;
4101 /* figure out what to draw */
4102 rgntype = GetClipBox(hdc, &rcClip);
4103 if (rgntype == NULLREGION) return;
4105 /* Get scroll info once before loop */
4106 LISTVIEW_GetOrigin(infoPtr, &Origin);
4108 /* narrow down the columns we need to paint */
4109 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4111 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4112 if (rcItem.right + Origin.x >= rcClip.left) break;
4114 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4116 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4117 if (rcItem.left + Origin.x < rcClip.right) break;
4119 iterator_rangeitems(&j, colRange);
4121 /* in full row select, we _have_ to draw the main item */
4122 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4125 /* iterate through the invalidated rows */
4126 while(iterator_next(i))
4128 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4129 Position.x += Origin.x;
4130 Position.y += Origin.y;
4132 /* iterate through the invalidated columns */
4133 while(iterator_next(&j))
4135 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4137 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4139 rcItem.bottom = infoPtr->nItemHeight;
4140 OffsetRect(&rcItem, Position.x, Position.y);
4141 if (!RectVisible(hdc, &rcItem)) continue;
4144 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4147 iterator_destroy(&j);
4152 * Draws the gridlines if necessary when in report display mode.
4155 * [I] infoPtr : valid pointer to the listview structure
4156 * [I] hdc : device context handle
4161 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
4166 RECT rcClip, rcItem = {0};
4174 /* figure out what to draw */
4175 rgntype = GetClipBox(hdc, &rcClip);
4176 if (rgntype == NULLREGION) return;
4178 /* Get scroll info once before loop */
4179 LISTVIEW_GetOrigin(infoPtr, &Origin);
4181 /* narrow down the columns we need to paint */
4182 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4184 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4185 if (rcItem.right + Origin.x >= rcClip.left) break;
4187 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4189 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4190 if (rcItem.left + Origin.x < rcClip.right) break;
4192 /* is right most vertical line visible? */
4193 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
4195 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcItem);
4196 rmost = (rcItem.right + Origin.x < rcClip.right);
4199 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
4201 hOldPen = SelectObject ( hdc, hPen );
4203 /* draw the vertical lines for the columns */
4204 iterator_rangeitems(&j, colRange);
4205 while(iterator_next(&j))
4207 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4208 if (rcItem.left == 0) continue; /* skip first column */
4209 rcItem.left += Origin.x;
4210 rcItem.right += Origin.x;
4211 rcItem.top = infoPtr->rcList.top;
4212 rcItem.bottom = infoPtr->rcList.bottom;
4213 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
4214 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4215 LineTo (hdc, rcItem.left, rcItem.bottom);
4217 iterator_destroy(&j);
4218 /* draw rightmost grid line if visible */
4221 MoveToEx (hdc, rcItem.right, rcItem.top, NULL);
4222 LineTo (hdc, rcItem.right, rcItem.bottom);
4225 /* draw the horizontial lines for the rows */
4226 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
4227 rcItem.left = infoPtr->rcList.left;
4228 rcItem.right = infoPtr->rcList.right;
4229 rcItem.bottom = rcItem.top = Origin.y - 1;
4230 MoveToEx(hdc, rcItem.left, rcItem.top, NULL);
4231 LineTo(hdc, rcItem.right, rcItem.top);
4232 for(y=itemheight-1+Origin.y; y<=infoPtr->rcList.bottom; y+=itemheight)
4234 rcItem.bottom = rcItem.top = y;
4235 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
4236 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4237 LineTo (hdc, rcItem.right, rcItem.top);
4240 SelectObject( hdc, hOldPen );
4241 DeleteObject( hPen );
4247 * Draws listview items when in list display mode.
4250 * [I] infoPtr : valid pointer to the listview structure
4251 * [I] hdc : device context handle
4252 * [I] cdmode : custom draw mode
4257 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4259 POINT Origin, Position;
4261 /* Get scroll info once before loop */
4262 LISTVIEW_GetOrigin(infoPtr, &Origin);
4264 while(iterator_prev(i))
4266 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4267 Position.x += Origin.x;
4268 Position.y += Origin.y;
4270 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4277 * Draws listview items.
4280 * [I] infoPtr : valid pointer to the listview structure
4281 * [I] hdc : device context handle
4282 * [I] prcErase : rect to be erased before refresh (may be NULL)
4287 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
4289 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4290 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
4291 NMLVCUSTOMDRAW nmlvcd;
4298 HBITMAP hbmp = NULL;
4301 LISTVIEW_DUMP(infoPtr);
4303 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4304 TRACE("double buffering\n");
4306 hdc = CreateCompatibleDC(hdcOrig);
4308 ERR("Failed to create DC for backbuffer\n");
4311 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
4312 infoPtr->rcList.bottom);
4314 ERR("Failed to create bitmap for backbuffer\n");
4319 SelectObject(hdc, hbmp);
4320 SelectObject(hdc, infoPtr->hFont);
4322 /* Save dc values we're gonna trash while drawing
4323 * FIXME: Should be done in LISTVIEW_DrawItem() */
4324 hOldFont = SelectObject(hdc, infoPtr->hFont);
4325 oldBkMode = GetBkMode(hdc);
4326 oldBkColor = GetBkColor(hdc);
4327 oldTextColor = GetTextColor(hdc);
4330 infoPtr->bIsDrawing = TRUE;
4333 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
4334 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4335 /* If no erasing was done (usually because RedrawWindow was called
4336 * with RDW_INVALIDATE only) we need to copy the old contents into
4337 * the backbuffer before continuing. */
4338 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
4339 infoPtr->rcList.right - infoPtr->rcList.left,
4340 infoPtr->rcList.bottom - infoPtr->rcList.top,
4341 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4344 /* FIXME: Shouldn't need to do this */
4345 oldClrTextBk = infoPtr->clrTextBk;
4346 oldClrText = infoPtr->clrText;
4348 infoPtr->cditemmode = CDRF_DODEFAULT;
4350 GetClientRect(infoPtr->hwndSelf, &rcClient);
4351 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
4352 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4353 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
4354 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4356 /* Use these colors to draw the items */
4357 infoPtr->clrTextBk = nmlvcd.clrTextBk;
4358 infoPtr->clrText = nmlvcd.clrText;
4360 /* nothing to draw */
4361 if(infoPtr->nItemCount == 0) goto enddraw;
4363 /* figure out what we need to draw */
4364 iterator_visibleitems(&i, infoPtr, hdc);
4365 range = iterator_range(&i);
4367 /* send cache hint notification */
4368 if (infoPtr->dwStyle & LVS_OWNERDATA)
4372 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4373 nmlv.iFrom = range.lower;
4374 nmlv.iTo = range.upper - 1;
4375 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4378 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
4379 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4382 if (uView == LVS_REPORT)
4383 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4384 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
4385 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4387 /* if we have a focus rect and it's visible, draw it */
4388 if (infoPtr->bFocus && range.lower <= infoPtr->nFocusedItem &&
4389 (range.upper - 1) >= infoPtr->nFocusedItem)
4390 LISTVIEW_DrawFocusRect(infoPtr, hdc);
4392 iterator_destroy(&i);
4395 /* For LVS_EX_GRIDLINES go and draw lines */
4396 /* This includes the case where there were *no* items */
4397 if ((uView == LVS_REPORT) && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4398 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
4400 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4401 notify_postpaint(infoPtr, &nmlvcd);
4403 infoPtr->clrTextBk = oldClrTextBk;
4404 infoPtr->clrText = oldClrText;
4407 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
4408 infoPtr->rcList.right - infoPtr->rcList.left,
4409 infoPtr->rcList.bottom - infoPtr->rcList.top,
4410 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4415 SelectObject(hdc, hOldFont);
4416 SetBkMode(hdc, oldBkMode);
4417 SetBkColor(hdc, oldBkColor);
4418 SetTextColor(hdc, oldTextColor);
4421 infoPtr->bIsDrawing = FALSE;
4427 * Calculates the approximate width and height of a given number of items.
4430 * [I] infoPtr : valid pointer to the listview structure
4431 * [I] nItemCount : number of items
4432 * [I] wWidth : width
4433 * [I] wHeight : height
4436 * Returns a DWORD. The width in the low word and the height in high word.
4438 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
4439 WORD wWidth, WORD wHeight)
4441 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4442 INT nItemCountPerColumn = 1;
4443 INT nColumnCount = 0;
4444 DWORD dwViewRect = 0;
4446 if (nItemCount == -1)
4447 nItemCount = infoPtr->nItemCount;
4449 if (uView == LVS_LIST)
4451 if (wHeight == 0xFFFF)
4453 /* use current height */
4454 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4457 if (wHeight < infoPtr->nItemHeight)
4458 wHeight = infoPtr->nItemHeight;
4462 if (infoPtr->nItemHeight > 0)
4464 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4465 if (nItemCountPerColumn == 0)
4466 nItemCountPerColumn = 1;
4468 if (nItemCount % nItemCountPerColumn != 0)
4469 nColumnCount = nItemCount / nItemCountPerColumn;
4471 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4475 /* Microsoft padding magic */
4476 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4477 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4479 dwViewRect = MAKELONG(wWidth, wHeight);
4481 else if (uView == LVS_REPORT)
4485 if (infoPtr->nItemCount > 0)
4487 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4488 wWidth = rcBox.right - rcBox.left;
4489 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4493 /* use current height and width */
4494 if (wHeight == 0xffff)
4495 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4496 if (wWidth == 0xffff)
4497 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4500 dwViewRect = MAKELONG(wWidth, wHeight);
4502 else if (uView == LVS_SMALLICON)
4503 FIXME("uView == LVS_SMALLICON: not implemented\n");
4504 else if (uView == LVS_ICON)
4505 FIXME("uView == LVS_ICON: not implemented\n");
4513 * Create a drag image list for the specified item.
4516 * [I] infoPtr : valid pointer to the listview structure
4517 * [I] iItem : index of item
4518 * [O] lppt : Upper-left corner of the image
4521 * Returns a handle to the image list if successful, NULL otherwise.
4523 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4529 HBITMAP hbmp, hOldbmp;
4530 HIMAGELIST dragList = 0;
4531 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
4533 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4536 rcItem.left = LVIR_BOUNDS;
4537 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4540 lppt->x = rcItem.left;
4541 lppt->y = rcItem.top;
4543 size.cx = rcItem.right - rcItem.left;
4544 size.cy = rcItem.bottom - rcItem.top;
4546 hdcOrig = GetDC(infoPtr->hwndSelf);
4547 hdc = CreateCompatibleDC(hdcOrig);
4548 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4549 hOldbmp = SelectObject(hdc, hbmp);
4551 rcItem.left = rcItem.top = 0;
4552 rcItem.right = size.cx;
4553 rcItem.bottom = size.cy;
4554 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4557 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4559 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4560 SelectObject(hdc, hOldbmp);
4561 ImageList_Add(dragList, hbmp, 0);
4564 SelectObject(hdc, hOldbmp);
4568 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4570 TRACE("ret=%p\n", dragList);
4578 * Removes all listview items and subitems.
4581 * [I] infoPtr : valid pointer to the listview structure
4587 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
4590 HDPA hdpaSubItems = NULL;
4597 /* we do it directly, to avoid notifications */
4598 ranges_clear(infoPtr->selectionRanges);
4599 infoPtr->nSelectionMark = -1;
4600 infoPtr->nFocusedItem = -1;
4601 SetRectEmpty(&infoPtr->rcFocus);
4602 /* But we are supposed to leave nHotItem as is! */
4605 /* send LVN_DELETEALLITEMS notification */
4606 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4608 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4610 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4612 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4614 /* send LVN_DELETEITEM notification, if not suppressed
4615 and if it is not a virtual listview */
4616 if (!bSuppress) notify_deleteitem(infoPtr, i);
4617 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
4618 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4620 hdrItem = DPA_GetPtr(hdpaSubItems, j);
4621 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4624 DPA_Destroy(hdpaSubItems);
4625 DPA_DeletePtr(infoPtr->hdpaItems, i);
4627 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4628 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4629 infoPtr->nItemCount --;
4634 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4635 LISTVIEW_UpdateScroll(infoPtr);
4637 LISTVIEW_InvalidateList(infoPtr);
4644 * Scrolls, and updates the columns, when a column is changing width.
4647 * [I] infoPtr : valid pointer to the listview structure
4648 * [I] nColumn : column to scroll
4649 * [I] dx : amount of scroll, in pixels
4654 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4656 COLUMN_INFO *lpColumnInfo;
4661 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4662 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4663 rcCol = lpColumnInfo->rcHeader;
4664 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4665 rcCol.left = rcCol.right;
4667 /* adjust the other columns */
4668 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4670 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4671 lpColumnInfo->rcHeader.left += dx;
4672 lpColumnInfo->rcHeader.right += dx;
4675 /* do not update screen if not in report mode */
4676 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4678 /* Need to reset the item width when inserting a new column */
4679 infoPtr->nItemWidth += dx;
4681 LISTVIEW_UpdateScroll(infoPtr);
4682 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
4684 /* scroll to cover the deleted column, and invalidate for redraw */
4685 rcOld = infoPtr->rcList;
4686 rcOld.left = ptOrigin.x + rcCol.left + dx;
4687 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4692 * Removes a column from the listview control.
4695 * [I] infoPtr : valid pointer to the listview structure
4696 * [I] nColumn : column index
4702 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4706 TRACE("nColumn=%d\n", nColumn);
4708 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4709 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4711 /* While the MSDN specifically says that column zero should not be deleted,
4712 what actually happens is that the column itself is deleted but no items or subitems
4716 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4718 if (!SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nColumn, 0))
4721 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4722 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4724 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4726 SUBITEM_INFO *lpSubItem, *lpDelItem;
4728 INT nItem, nSubItem, i;
4730 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4732 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
4735 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4737 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
4738 if (lpSubItem->iSubItem == nColumn)
4741 lpDelItem = lpSubItem;
4743 else if (lpSubItem->iSubItem > nColumn)
4745 lpSubItem->iSubItem--;
4749 /* if we found our subitem, zapp it */
4753 if (is_textW(lpDelItem->hdr.pszText))
4754 Free(lpDelItem->hdr.pszText);
4759 /* free dpa memory */
4760 DPA_DeletePtr(hdpaSubItems, nSubItem);
4765 /* update the other column info */
4766 LISTVIEW_UpdateItemSize(infoPtr);
4767 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4768 LISTVIEW_InvalidateList(infoPtr);
4770 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4777 * Invalidates the listview after an item's insertion or deletion.
4780 * [I] infoPtr : valid pointer to the listview structure
4781 * [I] nItem : item index
4782 * [I] dir : -1 if deleting, 1 if inserting
4787 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4789 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4790 INT nPerCol, nItemCol, nItemRow;
4794 /* if we don't refresh, what's the point of scrolling? */
4795 if (!is_redrawing(infoPtr)) return;
4797 assert (abs(dir) == 1);
4799 /* arrange icons if autoarrange is on */
4800 if (is_autoarrange(infoPtr))
4802 BOOL arrange = TRUE;
4803 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4804 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4805 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4808 /* scrollbars need updating */
4809 LISTVIEW_UpdateScroll(infoPtr);
4811 /* figure out the item's position */
4812 if (uView == LVS_REPORT)
4813 nPerCol = infoPtr->nItemCount + 1;
4814 else if (uView == LVS_LIST)
4815 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4816 else /* LVS_ICON, or LVS_SMALLICON */
4819 nItemCol = nItem / nPerCol;
4820 nItemRow = nItem % nPerCol;
4821 LISTVIEW_GetOrigin(infoPtr, &Origin);
4823 /* move the items below up a slot */
4824 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4825 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4826 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4827 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4828 OffsetRect(&rcScroll, Origin.x, Origin.y);
4829 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
4830 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4832 TRACE("Scrolling rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
4833 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4834 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4837 /* report has only that column, so we're done */
4838 if (uView == LVS_REPORT) return;
4840 /* now for LISTs, we have to deal with the columns to the right */
4841 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4843 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4844 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4845 OffsetRect(&rcScroll, Origin.x, Origin.y);
4846 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4847 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4848 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4853 * Removes an item from the listview control.
4856 * [I] infoPtr : valid pointer to the listview structure
4857 * [I] nItem : item index
4863 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4866 const UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4867 const BOOL is_icon = (uView == LVS_SMALLICON || uView == LVS_ICON);
4869 TRACE("(nItem=%d)\n", nItem);
4871 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4873 /* remove selection, and focus */
4875 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4876 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4878 /* send LVN_DELETEITEM notification. */
4879 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
4881 /* we need to do this here, because we'll be deleting stuff */
4883 LISTVIEW_InvalidateItem(infoPtr, nItem);
4885 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4891 hdpaSubItems = DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4892 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4894 hdrItem = DPA_GetPtr(hdpaSubItems, i);
4895 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4898 DPA_Destroy(hdpaSubItems);
4903 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4904 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4907 infoPtr->nItemCount--;
4908 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4910 /* now is the invalidation fun */
4912 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4919 * Callback implementation for editlabel control
4922 * [I] infoPtr : valid pointer to the listview structure
4923 * [I] pszText : modified text
4924 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4930 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4932 HWND hwndSelf = infoPtr->hwndSelf;
4933 NMLVDISPINFOW dispInfo;
4934 INT editedItem = infoPtr->nEditLabelItem;
4937 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4939 infoPtr->nEditLabelItem = -1;
4941 ZeroMemory(&dispInfo, sizeof(dispInfo));
4942 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4943 dispInfo.item.iItem = editedItem;
4944 dispInfo.item.iSubItem = 0;
4945 dispInfo.item.stateMask = ~0;
4946 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4949 bSame = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
4952 LPWSTR tmp = textdupTtoW(pszText, FALSE);
4953 bSame = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
4954 textfreeT(tmp, FALSE);
4956 if (bSame) return TRUE;
4958 /* add the text from the edit in */
4959 dispInfo.item.mask |= LVIF_TEXT;
4960 dispInfo.item.pszText = pszText;
4961 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4963 /* Do we need to update the Item Text */
4964 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4965 if (!IsWindow(hwndSelf))
4967 if (!pszText) return TRUE;
4969 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4971 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, editedItem);
4972 ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
4973 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
4975 LISTVIEW_InvalidateItem(infoPtr, editedItem);
4980 ZeroMemory(&dispInfo, sizeof(dispInfo));
4981 dispInfo.item.mask = LVIF_TEXT;
4982 dispInfo.item.iItem = editedItem;
4983 dispInfo.item.iSubItem = 0;
4984 dispInfo.item.pszText = pszText;
4985 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4986 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4991 * Begin in place editing of specified list view item
4994 * [I] infoPtr : valid pointer to the listview structure
4995 * [I] nItem : item index
4996 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
5002 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
5004 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
5005 NMLVDISPINFOW dispInfo;
5007 HWND hwndSelf = infoPtr->hwndSelf;
5009 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
5011 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
5012 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5013 /* Edit could be created only on focused control and if isn't already created */
5014 if (GetFocus() != infoPtr->hwndSelf && !IsWindow(infoPtr->hwndEdit)) return 0;
5016 infoPtr->nEditLabelItem = nItem;
5018 /* Is the EditBox still there, if so remove it */
5019 if(infoPtr->hwndEdit != 0)
5021 SetFocus(infoPtr->hwndSelf);
5022 infoPtr->hwndEdit = 0;
5025 LISTVIEW_SetSelection(infoPtr, nItem);
5026 LISTVIEW_SetItemFocus(infoPtr, nItem);
5027 LISTVIEW_InvalidateItem(infoPtr, nItem);
5029 rect.left = LVIR_LABEL;
5030 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
5032 ZeroMemory(&dispInfo, sizeof(dispInfo));
5033 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
5034 dispInfo.item.iItem = nItem;
5035 dispInfo.item.iSubItem = 0;
5036 dispInfo.item.stateMask = ~0;
5037 dispInfo.item.pszText = szDispText;
5038 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
5039 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
5041 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
5042 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
5043 if (!infoPtr->hwndEdit) return 0;
5045 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
5047 if (!IsWindow(hwndSelf))
5049 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
5050 infoPtr->hwndEdit = 0;
5054 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
5055 SetFocus(infoPtr->hwndEdit);
5056 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
5057 return infoPtr->hwndEdit;
5063 * Ensures the specified item is visible, scrolling into view if necessary.
5066 * [I] infoPtr : valid pointer to the listview structure
5067 * [I] nItem : item index
5068 * [I] bPartial : partially or entirely visible
5074 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
5076 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5077 INT nScrollPosHeight = 0;
5078 INT nScrollPosWidth = 0;
5079 INT nHorzAdjust = 0;
5080 INT nVertAdjust = 0;
5083 RECT rcItem, rcTemp;
5085 rcItem.left = LVIR_BOUNDS;
5086 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
5088 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
5090 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
5092 /* scroll left/right, but in LVS_REPORT mode */
5093 if (uView == LVS_LIST)
5094 nScrollPosWidth = infoPtr->nItemWidth;
5095 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5096 nScrollPosWidth = 1;
5098 if (rcItem.left < infoPtr->rcList.left)
5101 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
5106 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
5110 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
5112 /* scroll up/down, but not in LVS_LIST mode */
5113 if (uView == LVS_REPORT)
5114 nScrollPosHeight = infoPtr->nItemHeight;
5115 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
5116 nScrollPosHeight = 1;
5118 if (rcItem.top < infoPtr->rcList.top)
5121 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
5126 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
5130 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
5132 if (nScrollPosWidth)
5134 INT diff = nHorzDiff / nScrollPosWidth;
5135 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
5136 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
5139 if (nScrollPosHeight)
5141 INT diff = nVertDiff / nScrollPosHeight;
5142 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
5143 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
5151 * Searches for an item with specific characteristics.
5154 * [I] hwnd : window handle
5155 * [I] nStart : base item index
5156 * [I] lpFindInfo : item information to look for
5159 * SUCCESS : index of item
5162 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
5163 const LVFINDINFOW *lpFindInfo)
5165 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5166 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5167 BOOL bWrap = FALSE, bNearest = FALSE;
5168 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
5169 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
5170 POINT Position, Destination;
5173 /* Search in virtual listviews should be done by application, not by
5174 listview control, so we just send LVN_ODFINDITEMW and return the result */
5175 if (infoPtr->dwStyle & LVS_OWNERDATA)
5179 nmlv.iStart = nStart;
5180 nmlv.lvfi = *lpFindInfo;
5181 return notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
5184 if (!lpFindInfo || nItem < 0) return -1;
5187 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
5189 lvItem.mask |= LVIF_TEXT;
5190 lvItem.pszText = szDispText;
5191 lvItem.cchTextMax = DISP_TEXT_SIZE;
5194 if (lpFindInfo->flags & LVFI_WRAP)
5197 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
5198 (uView == LVS_ICON || uView ==LVS_SMALLICON))
5203 LISTVIEW_GetOrigin(infoPtr, &Origin);
5204 Destination.x = lpFindInfo->pt.x - Origin.x;
5205 Destination.y = lpFindInfo->pt.y - Origin.y;
5206 switch(lpFindInfo->vkDirection)
5208 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
5209 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
5210 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
5211 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
5212 case VK_HOME: Destination.x = Destination.y = 0; break;
5213 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5214 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5216 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
5217 Destination.x = rcArea.right;
5218 Destination.y = rcArea.bottom;
5220 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
5224 else Destination.x = Destination.y = 0;
5226 /* if LVFI_PARAM is specified, all other flags are ignored */
5227 if (lpFindInfo->flags & LVFI_PARAM)
5229 lvItem.mask |= LVIF_PARAM;
5231 lvItem.mask &= ~LVIF_TEXT;
5235 for (; nItem < nLast; nItem++)
5237 lvItem.iItem = nItem;
5238 lvItem.iSubItem = 0;
5239 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
5241 if (lvItem.mask & LVIF_PARAM)
5243 if (lpFindInfo->lParam == lvItem.lParam)
5249 if (lvItem.mask & LVIF_TEXT)
5251 if (lpFindInfo->flags & LVFI_PARTIAL)
5253 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
5257 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
5261 if (!bNearest) return nItem;
5263 /* This is very inefficient. To do a good job here,
5264 * we need a sorted array of (x,y) item positions */
5265 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5267 /* compute the distance^2 to the destination */
5268 xdist = Destination.x - Position.x;
5269 ydist = Destination.y - Position.y;
5270 dist = xdist * xdist + ydist * ydist;
5272 /* remember the distance, and item if it's closer */
5276 nNearestItem = nItem;
5283 nLast = min(nStart + 1, infoPtr->nItemCount);
5288 return nNearestItem;
5293 * Searches for an item with specific characteristics.
5296 * [I] hwnd : window handle
5297 * [I] nStart : base item index
5298 * [I] lpFindInfo : item information to look for
5301 * SUCCESS : index of item
5304 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
5305 const LVFINDINFOA *lpFindInfo)
5307 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
5312 memcpy(&fiw, lpFindInfo, sizeof(fiw));
5313 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
5314 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
5315 textfreeT(strW, FALSE);
5321 * Retrieves the background image of the listview control.
5324 * [I] infoPtr : valid pointer to the listview structure
5325 * [O] lpBkImage : background image attributes
5331 /* static BOOL LISTVIEW_GetBkImage(const LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
5333 /* FIXME (listview, "empty stub!\n"); */
5339 * Retrieves column attributes.
5342 * [I] infoPtr : valid pointer to the listview structure
5343 * [I] nColumn : column index
5344 * [IO] lpColumn : column information
5345 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
5346 * otherwise it is in fact a LPLVCOLUMNA
5352 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5354 COLUMN_INFO *lpColumnInfo;
5357 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5358 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
5360 /* initialize memory */
5361 ZeroMemory(&hdi, sizeof(hdi));
5363 if (lpColumn->mask & LVCF_TEXT)
5365 hdi.mask |= HDI_TEXT;
5366 hdi.pszText = lpColumn->pszText;
5367 hdi.cchTextMax = lpColumn->cchTextMax;
5370 if (lpColumn->mask & LVCF_IMAGE)
5371 hdi.mask |= HDI_IMAGE;
5373 if (lpColumn->mask & LVCF_ORDER)
5374 hdi.mask |= HDI_ORDER;
5376 if (lpColumn->mask & LVCF_SUBITEM)
5377 hdi.mask |= HDI_LPARAM;
5379 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
5381 if (lpColumn->mask & LVCF_FMT)
5382 lpColumn->fmt = lpColumnInfo->fmt;
5384 if (lpColumn->mask & LVCF_WIDTH)
5385 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
5387 if (lpColumn->mask & LVCF_IMAGE)
5388 lpColumn->iImage = hdi.iImage;
5390 if (lpColumn->mask & LVCF_ORDER)
5391 lpColumn->iOrder = hdi.iOrder;
5393 if (lpColumn->mask & LVCF_SUBITEM)
5394 lpColumn->iSubItem = hdi.lParam;
5400 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
5402 TRACE("iCount=%d, lpiArray=%p\n", iCount, lpiArray);
5407 return SendMessageW(infoPtr->hwndHeader, HDM_GETORDERARRAY, iCount, (LPARAM)lpiArray);
5412 * Retrieves the column width.
5415 * [I] infoPtr : valid pointer to the listview structure
5416 * [I] int : column index
5419 * SUCCESS : column width
5422 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
5424 INT nColumnWidth = 0;
5427 TRACE("nColumn=%d\n", nColumn);
5429 /* we have a 'column' in LIST and REPORT mode only */
5430 switch(infoPtr->dwStyle & LVS_TYPEMASK)
5433 nColumnWidth = infoPtr->nItemWidth;
5436 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDM_ITEMCHANGED.
5437 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
5438 * HDM_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
5440 * TODO: should we do the same in LVM_GETCOLUMN?
5442 hdItem.mask = HDI_WIDTH;
5443 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
5445 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
5448 nColumnWidth = hdItem.cxy;
5452 TRACE("nColumnWidth=%d\n", nColumnWidth);
5453 return nColumnWidth;
5458 * In list or report display mode, retrieves the number of items that can fit
5459 * vertically in the visible area. In icon or small icon display mode,
5460 * retrieves the total number of visible items.
5463 * [I] infoPtr : valid pointer to the listview structure
5466 * Number of fully visible items.
5468 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
5470 switch (infoPtr->dwStyle & LVS_TYPEMASK)
5474 return infoPtr->nItemCount;
5476 return LISTVIEW_GetCountPerColumn(infoPtr);
5478 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5486 * Retrieves an image list handle.
5489 * [I] infoPtr : valid pointer to the listview structure
5490 * [I] nImageList : image list identifier
5493 * SUCCESS : image list handle
5496 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
5500 case LVSIL_NORMAL: return infoPtr->himlNormal;
5501 case LVSIL_SMALL: return infoPtr->himlSmall;
5502 case LVSIL_STATE: return infoPtr->himlState;
5507 /* LISTVIEW_GetISearchString */
5511 * Retrieves item attributes.
5514 * [I] hwnd : window handle
5515 * [IO] lpLVItem : item info
5516 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5517 * if FALSE, then lpLVItem is a LPLVITEMA.
5520 * This is the internal 'GetItem' interface -- it tries to
5521 * be smart and avoid text copies, if possible, by modifying
5522 * lpLVItem->pszText to point to the text string. Please note
5523 * that this is not always possible (e.g. OWNERDATA), so on
5524 * entry you *must* supply valid values for pszText, and cchTextMax.
5525 * The only difference to the documented interface is that upon
5526 * return, you should use *only* the lpLVItem->pszText, rather than
5527 * the buffer pointer you provided on input. Most code already does
5528 * that, so it's not a problem.
5529 * For the two cases when the text must be copied (that is,
5530 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5536 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5538 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5539 NMLVDISPINFOW dispInfo;
5545 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5547 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5550 if (lpLVItem->mask == 0) return TRUE;
5552 /* make a local copy */
5553 isubitem = lpLVItem->iSubItem;
5555 /* a quick optimization if all we're asked is the focus state
5556 * these queries are worth optimising since they are common,
5557 * and can be answered in constant time, without the heavy accesses */
5558 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5559 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5561 lpLVItem->state = 0;
5562 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5563 lpLVItem->state |= LVIS_FOCUSED;
5567 ZeroMemory(&dispInfo, sizeof(dispInfo));
5569 /* if the app stores all the data, handle it separately */
5570 if (infoPtr->dwStyle & LVS_OWNERDATA)
5572 dispInfo.item.state = 0;
5574 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
5575 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) ||
5576 ((lpLVItem->mask & LVIF_STATE) && (infoPtr->uCallbackMask & lpLVItem->stateMask)))
5578 UINT mask = lpLVItem->mask;
5580 /* NOTE: copy only fields which we _know_ are initialized, some apps
5581 * depend on the uninitialized fields being 0 */
5582 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5583 dispInfo.item.iItem = lpLVItem->iItem;
5584 dispInfo.item.iSubItem = isubitem;
5585 if (lpLVItem->mask & LVIF_TEXT)
5587 if (lpLVItem->mask & LVIF_NORECOMPUTE)
5589 dispInfo.item.mask &= ~(LVIF_TEXT | LVIF_NORECOMPUTE);
5592 dispInfo.item.pszText = lpLVItem->pszText;
5593 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5596 if (lpLVItem->mask & LVIF_STATE)
5597 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5598 /* could be zeroed on LVIF_NORECOMPUTE case */
5599 if (dispInfo.item.mask != 0)
5601 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5602 dispInfo.item.stateMask = lpLVItem->stateMask;
5603 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5605 /* full size structure expected - _WIN32IE >= 0x560 */
5606 *lpLVItem = dispInfo.item;
5608 else if (lpLVItem->mask & LVIF_INDENT)
5610 /* indent member expected - _WIN32IE >= 0x300 */
5611 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5615 /* minimal structure expected */
5616 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5618 lpLVItem->mask = mask;
5619 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5623 /* make sure lParam is zeroed out */
5624 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5626 /* callback marked pointer required here */
5627 if ((lpLVItem->mask & LVIF_TEXT) && (lpLVItem->mask & LVIF_NORECOMPUTE))
5628 lpLVItem->pszText = LPSTR_TEXTCALLBACKW;
5630 /* we store only a little state, so if we're not asked, we're done */
5631 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5633 /* if focus is handled by us, report it */
5634 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5636 lpLVItem->state &= ~LVIS_FOCUSED;
5637 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5638 lpLVItem->state |= LVIS_FOCUSED;
5641 /* and do the same for selection, if we handle it */
5642 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5644 lpLVItem->state &= ~LVIS_SELECTED;
5645 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5646 lpLVItem->state |= LVIS_SELECTED;
5652 /* find the item and subitem structures before we proceed */
5653 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5654 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5659 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5660 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5663 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5668 pItemHdr = &lpItem->hdr;
5670 /* Do we need to query the state from the app? */
5671 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5673 dispInfo.item.mask |= LVIF_STATE;
5674 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5677 /* Do we need to enquire about the image? */
5678 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5679 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5681 dispInfo.item.mask |= LVIF_IMAGE;
5682 dispInfo.item.iImage = I_IMAGECALLBACK;
5685 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5686 if ((lpLVItem->mask & LVIF_TEXT) && !(lpLVItem->mask & LVIF_NORECOMPUTE) &&
5687 !is_textW(pItemHdr->pszText))
5689 dispInfo.item.mask |= LVIF_TEXT;
5690 dispInfo.item.pszText = lpLVItem->pszText;
5691 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5692 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5693 *dispInfo.item.pszText = '\0';
5696 /* If we don't have all the requested info, query the application */
5697 if (dispInfo.item.mask != 0)
5699 dispInfo.item.iItem = lpLVItem->iItem;
5700 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5701 dispInfo.item.lParam = lpItem->lParam;
5702 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5703 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5706 /* we should not store values for subitems */
5707 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5709 /* Now, handle the iImage field */
5710 if (dispInfo.item.mask & LVIF_IMAGE)
5712 lpLVItem->iImage = dispInfo.item.iImage;
5713 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5714 pItemHdr->iImage = dispInfo.item.iImage;
5716 else if (lpLVItem->mask & LVIF_IMAGE)
5718 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5719 lpLVItem->iImage = pItemHdr->iImage;
5721 lpLVItem->iImage = 0;
5724 /* The pszText field */
5725 if (dispInfo.item.mask & LVIF_TEXT)
5727 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5728 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5730 lpLVItem->pszText = dispInfo.item.pszText;
5732 else if (lpLVItem->mask & LVIF_TEXT)
5734 /* if LVN_GETDISPINFO's disabled with LVIF_NORECOMPUTE return callback placeholder */
5735 if (isW || !is_textW(pItemHdr->pszText)) lpLVItem->pszText = pItemHdr->pszText;
5736 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5739 /* Next is the lParam field */
5740 if (dispInfo.item.mask & LVIF_PARAM)
5742 lpLVItem->lParam = dispInfo.item.lParam;
5743 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5744 lpItem->lParam = dispInfo.item.lParam;
5746 else if (lpLVItem->mask & LVIF_PARAM)
5747 lpLVItem->lParam = lpItem->lParam;
5749 /* if this is a subitem, we're done */
5750 if (isubitem) return TRUE;
5752 /* ... the state field (this one is different due to uCallbackmask) */
5753 if (lpLVItem->mask & LVIF_STATE)
5755 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
5756 if (dispInfo.item.mask & LVIF_STATE)
5758 lpLVItem->state &= ~dispInfo.item.stateMask;
5759 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5761 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5763 lpLVItem->state &= ~LVIS_FOCUSED;
5764 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5765 lpLVItem->state |= LVIS_FOCUSED;
5767 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5769 lpLVItem->state &= ~LVIS_SELECTED;
5770 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5771 lpLVItem->state |= LVIS_SELECTED;
5775 /* and last, but not least, the indent field */
5776 if (lpLVItem->mask & LVIF_INDENT)
5777 lpLVItem->iIndent = lpItem->iIndent;
5784 * Retrieves item attributes.
5787 * [I] hwnd : window handle
5788 * [IO] lpLVItem : item info
5789 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5790 * if FALSE, then lpLVItem is a LPLVITEMA.
5793 * This is the external 'GetItem' interface -- it properly copies
5794 * the text in the provided buffer.
5800 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5805 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5808 pszText = lpLVItem->pszText;
5809 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5810 if (bResult && lpLVItem->pszText != pszText)
5812 if (lpLVItem->pszText != LPSTR_TEXTCALLBACKW)
5813 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5815 pszText = LPSTR_TEXTCALLBACKW;
5817 lpLVItem->pszText = pszText;
5825 * Retrieves the position (upper-left) of the listview control item.
5826 * Note that for LVS_ICON style, the upper-left is that of the icon
5827 * and not the bounding box.
5830 * [I] infoPtr : valid pointer to the listview structure
5831 * [I] nItem : item index
5832 * [O] lpptPosition : coordinate information
5838 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5840 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5843 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5845 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5847 LISTVIEW_GetOrigin(infoPtr, &Origin);
5848 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5850 if (uView == LVS_ICON)
5852 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5853 lpptPosition->y += ICON_TOP_PADDING;
5855 lpptPosition->x += Origin.x;
5856 lpptPosition->y += Origin.y;
5858 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
5865 * Retrieves the bounding rectangle for a listview control item.
5868 * [I] infoPtr : valid pointer to the listview structure
5869 * [I] nItem : item index
5870 * [IO] lprc : bounding rectangle coordinates
5871 * lprc->left specifies the portion of the item for which the bounding
5872 * rectangle will be retrieved.
5874 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5875 * including the icon and label.
5878 * * Experiment shows that native control returns:
5879 * * width = min (48, length of text line)
5880 * * .left = position.x - (width - iconsize.cx)/2
5881 * * .right = .left + width
5882 * * height = #lines of text * ntmHeight + icon height + 8
5883 * * .top = position.y - 2
5884 * * .bottom = .top + height
5885 * * separation between items .y = itemSpacing.cy - height
5886 * * .x = itemSpacing.cx - width
5887 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5890 * * Experiment shows that native control returns:
5891 * * width = iconSize.cx + 16
5892 * * .left = position.x - (width - iconsize.cx)/2
5893 * * .right = .left + width
5894 * * height = iconSize.cy + 4
5895 * * .top = position.y - 2
5896 * * .bottom = .top + height
5897 * * separation between items .y = itemSpacing.cy - height
5898 * * .x = itemSpacing.cx - width
5899 * LVIR_LABEL Returns the bounding rectangle of the item text.
5902 * * Experiment shows that native control returns:
5903 * * width = text length
5904 * * .left = position.x - width/2
5905 * * .right = .left + width
5906 * * height = ntmH * linecount + 2
5907 * * .top = position.y + iconSize.cy + 6
5908 * * .bottom = .top + height
5909 * * separation between items .y = itemSpacing.cy - height
5910 * * .x = itemSpacing.cx - width
5911 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5912 * rectangles, but excludes columns in report view.
5919 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5920 * upon whether the window has the focus currently and on whether the item
5921 * is the one with the focus. Ensure that the control's record of which
5922 * item has the focus agrees with the items' records.
5924 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5926 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5927 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5928 BOOL doLabel = TRUE, oversizedBox = FALSE;
5929 POINT Position, Origin;
5932 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5934 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5936 LISTVIEW_GetOrigin(infoPtr, &Origin);
5937 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5939 /* Be smart and try to figure out the minimum we have to do */
5940 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5941 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5942 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5943 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5944 oversizedBox = TRUE;
5946 /* get what we need from the item before hand, so we make
5947 * only one request. This can speed up things, if data
5948 * is stored on the app side */
5950 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5951 if (doLabel) lvItem.mask |= LVIF_TEXT;
5952 lvItem.iItem = nItem;
5953 lvItem.iSubItem = 0;
5954 lvItem.pszText = szDispText;
5955 lvItem.cchTextMax = DISP_TEXT_SIZE;
5956 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5957 /* we got the state already up, simulate it here, to avoid a reget */
5958 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5960 lvItem.mask |= LVIF_STATE;
5961 lvItem.stateMask = LVIS_FOCUSED;
5962 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5965 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5966 lprc->left = LVIR_BOUNDS;
5970 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5974 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
5978 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5981 case LVIR_SELECTBOUNDS:
5982 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
5986 WARN("Unknown value: %d\n", lprc->left);
5990 if (uView == LVS_REPORT)
5991 OffsetRect(lprc, Origin.x, Position.y + Origin.y);
5993 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5995 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
6002 * Retrieves the spacing between listview control items.
6005 * [I] infoPtr : valid pointer to the listview structure
6006 * [IO] lprc : rectangle to receive the output
6007 * on input, lprc->top = nSubItem
6008 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
6010 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
6011 * not only those of the first column.
6012 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
6018 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
6024 if (!lprc) return FALSE;
6026 nColumn = lprc->top;
6028 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
6029 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
6031 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
6033 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
6035 /* special case for header items */
6038 if (lprc->left != LVIR_BOUNDS)
6040 FIXME("Only LVIR_BOUNDS is implemented for header, got %d\n", lprc->left);
6044 if (infoPtr->hwndHeader)
6045 return SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, lprc->top, (LPARAM)lprc);
6048 memset(lprc, 0, sizeof(RECT));
6053 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
6055 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6058 lvItem.iItem = nItem;
6059 lvItem.iSubItem = nColumn;
6061 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
6065 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
6070 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
6074 ERR("Unknown bounds=%d\n", lprc->left);
6078 OffsetRect(lprc, 0, Position.y);
6085 * Retrieves the width of a label.
6088 * [I] infoPtr : valid pointer to the listview structure
6091 * SUCCESS : string width (in pixels)
6094 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *infoPtr, INT nItem)
6096 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6099 TRACE("(nItem=%d)\n", nItem);
6101 lvItem.mask = LVIF_TEXT;
6102 lvItem.iItem = nItem;
6103 lvItem.iSubItem = 0;
6104 lvItem.pszText = szDispText;
6105 lvItem.cchTextMax = DISP_TEXT_SIZE;
6106 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6108 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6113 * Retrieves the spacing between listview control items.
6116 * [I] infoPtr : valid pointer to the listview structure
6117 * [I] bSmall : flag for small or large icon
6120 * Horizontal + vertical spacing
6122 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
6128 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6132 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
6133 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
6135 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
6142 * Retrieves the state of a listview control item.
6145 * [I] infoPtr : valid pointer to the listview structure
6146 * [I] nItem : item index
6147 * [I] uMask : state mask
6150 * State specified by the mask.
6152 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
6156 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6158 lvItem.iItem = nItem;
6159 lvItem.iSubItem = 0;
6160 lvItem.mask = LVIF_STATE;
6161 lvItem.stateMask = uMask;
6162 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6164 return lvItem.state & uMask;
6169 * Retrieves the text of a listview control item or subitem.
6172 * [I] hwnd : window handle
6173 * [I] nItem : item index
6174 * [IO] lpLVItem : item information
6175 * [I] isW : TRUE if lpLVItem is Unicode
6178 * SUCCESS : string length
6181 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
6183 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6185 lpLVItem->mask = LVIF_TEXT;
6186 lpLVItem->iItem = nItem;
6187 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
6189 return textlenT(lpLVItem->pszText, isW);
6194 * Searches for an item based on properties + relationships.
6197 * [I] infoPtr : valid pointer to the listview structure
6198 * [I] nItem : item index
6199 * [I] uFlags : relationship flag
6202 * SUCCESS : item index
6205 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
6207 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6209 LVFINDINFOW lvFindInfo;
6210 INT nCountPerColumn;
6214 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
6215 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
6217 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
6219 if (uFlags & LVNI_CUT)
6222 if (uFlags & LVNI_DROPHILITED)
6223 uMask |= LVIS_DROPHILITED;
6225 if (uFlags & LVNI_FOCUSED)
6226 uMask |= LVIS_FOCUSED;
6228 if (uFlags & LVNI_SELECTED)
6229 uMask |= LVIS_SELECTED;
6231 /* if we're asked for the focused item, that's only one,
6232 * so it's worth optimizing */
6233 if (uFlags & LVNI_FOCUSED)
6235 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
6236 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
6239 if (uFlags & LVNI_ABOVE)
6241 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6246 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6252 /* Special case for autoarrange - move 'til the top of a list */
6253 if (is_autoarrange(infoPtr))
6255 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6256 while (nItem - nCountPerRow >= 0)
6258 nItem -= nCountPerRow;
6259 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6264 lvFindInfo.flags = LVFI_NEARESTXY;
6265 lvFindInfo.vkDirection = VK_UP;
6266 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6267 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6269 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6274 else if (uFlags & LVNI_BELOW)
6276 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6278 while (nItem < infoPtr->nItemCount)
6281 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6287 /* Special case for autoarrange - move 'til the bottom of a list */
6288 if (is_autoarrange(infoPtr))
6290 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6291 while (nItem + nCountPerRow < infoPtr->nItemCount )
6293 nItem += nCountPerRow;
6294 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6299 lvFindInfo.flags = LVFI_NEARESTXY;
6300 lvFindInfo.vkDirection = VK_DOWN;
6301 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6302 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6304 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6309 else if (uFlags & LVNI_TOLEFT)
6311 if (uView == LVS_LIST)
6313 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6314 while (nItem - nCountPerColumn >= 0)
6316 nItem -= nCountPerColumn;
6317 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6321 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6323 /* Special case for autoarrange - move 'til the beginning of a row */
6324 if (is_autoarrange(infoPtr))
6326 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6327 while (nItem % nCountPerRow > 0)
6330 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6335 lvFindInfo.flags = LVFI_NEARESTXY;
6336 lvFindInfo.vkDirection = VK_LEFT;
6337 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6338 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6340 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6345 else if (uFlags & LVNI_TORIGHT)
6347 if (uView == LVS_LIST)
6349 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6350 while (nItem + nCountPerColumn < infoPtr->nItemCount)
6352 nItem += nCountPerColumn;
6353 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6357 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6359 /* Special case for autoarrange - move 'til the end of a row */
6360 if (is_autoarrange(infoPtr))
6362 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6363 while (nItem % nCountPerRow < nCountPerRow - 1 )
6366 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6371 lvFindInfo.flags = LVFI_NEARESTXY;
6372 lvFindInfo.vkDirection = VK_RIGHT;
6373 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6374 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6376 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6385 /* search by index */
6386 for (i = nItem; i < infoPtr->nItemCount; i++)
6388 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
6396 /* LISTVIEW_GetNumberOfWorkAreas */
6400 * Retrieves the origin coordinates when in icon or small icon display mode.
6403 * [I] infoPtr : valid pointer to the listview structure
6404 * [O] lpptOrigin : coordinate information
6409 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
6411 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6412 INT nHorzPos = 0, nVertPos = 0;
6413 SCROLLINFO scrollInfo;
6415 scrollInfo.cbSize = sizeof(SCROLLINFO);
6416 scrollInfo.fMask = SIF_POS;
6418 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
6419 nHorzPos = scrollInfo.nPos;
6420 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
6421 nVertPos = scrollInfo.nPos;
6423 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
6425 lpptOrigin->x = infoPtr->rcList.left;
6426 lpptOrigin->y = infoPtr->rcList.top;
6427 if (uView == LVS_LIST)
6428 nHorzPos *= infoPtr->nItemWidth;
6429 else if (uView == LVS_REPORT)
6430 nVertPos *= infoPtr->nItemHeight;
6432 lpptOrigin->x -= nHorzPos;
6433 lpptOrigin->y -= nVertPos;
6435 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
6440 * Retrieves the width of a string.
6443 * [I] hwnd : window handle
6444 * [I] lpszText : text string to process
6445 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
6448 * SUCCESS : string width (in pixels)
6451 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
6456 if (is_textT(lpszText, isW))
6458 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
6459 HDC hdc = GetDC(infoPtr->hwndSelf);
6460 HFONT hOldFont = SelectObject(hdc, hFont);
6463 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
6465 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
6466 SelectObject(hdc, hOldFont);
6467 ReleaseDC(infoPtr->hwndSelf, hdc);
6469 return stringSize.cx;
6474 * Determines which listview item is located at the specified position.
6477 * [I] infoPtr : valid pointer to the listview structure
6478 * [IO] lpht : hit test information
6479 * [I] subitem : fill out iSubItem.
6480 * [I] select : return the index only if the hit selects the item
6483 * (mm 20001022): We must not allow iSubItem to be touched, for
6484 * an app might pass only a structure with space up to iItem!
6485 * (MS Office 97 does that for instance in the file open dialog)
6488 * SUCCESS : item index
6491 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6493 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6494 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6495 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6496 POINT Origin, Position, opt;
6501 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
6505 if (subitem) lpht->iSubItem = 0;
6507 if (infoPtr->rcList.left > lpht->pt.x)
6508 lpht->flags |= LVHT_TOLEFT;
6509 else if (infoPtr->rcList.right < lpht->pt.x)
6510 lpht->flags |= LVHT_TORIGHT;
6512 if (infoPtr->rcList.top > lpht->pt.y)
6513 lpht->flags |= LVHT_ABOVE;
6514 else if (infoPtr->rcList.bottom < lpht->pt.y)
6515 lpht->flags |= LVHT_BELOW;
6517 TRACE("lpht->flags=0x%x\n", lpht->flags);
6518 if (lpht->flags) return -1;
6520 lpht->flags |= LVHT_NOWHERE;
6522 LISTVIEW_GetOrigin(infoPtr, &Origin);
6524 /* first deal with the large items */
6525 rcSearch.left = lpht->pt.x;
6526 rcSearch.top = lpht->pt.y;
6527 rcSearch.right = rcSearch.left + 1;
6528 rcSearch.bottom = rcSearch.top + 1;
6530 iterator_frameditems(&i, infoPtr, &rcSearch);
6531 iterator_next(&i); /* go to first item in the sequence */
6533 iterator_destroy(&i);
6535 TRACE("lpht->iItem=%d\n", iItem);
6536 if (iItem == -1) return -1;
6538 if (uView == LVS_REPORT && subitem)
6540 RECT bounds, *pRect;
6543 /* for top/bottom only */
6544 bounds.left = LVIR_BOUNDS;
6545 LISTVIEW_GetItemRect(infoPtr, iItem, &bounds);
6547 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6549 pRect = &LISTVIEW_GetColumnInfo(infoPtr, j)->rcHeader;
6550 bounds.left = pRect->left;
6551 bounds.right = pRect->right;
6553 if (PtInRect(&bounds, lpht->pt))
6559 TRACE("lpht->iSubItem=%d\n", lpht->iSubItem);
6562 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6563 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
6564 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6565 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6566 lvItem.iItem = iItem;
6567 lvItem.iSubItem = subitem ? lpht->iSubItem : 0;
6568 lvItem.pszText = szDispText;
6569 lvItem.cchTextMax = DISP_TEXT_SIZE;
6570 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6571 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6573 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6574 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6575 opt.x = lpht->pt.x - Position.x - Origin.x;
6576 opt.y = lpht->pt.y - Position.y - Origin.y;
6578 if (uView == LVS_REPORT)
6582 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6583 UnionRect(&rcBounds, &rcBounds, &rcState);
6585 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
6586 if (!PtInRect(&rcBounds, opt)) return -1;
6588 if (PtInRect(&rcIcon, opt))
6589 lpht->flags |= LVHT_ONITEMICON;
6590 else if (PtInRect(&rcLabel, opt))
6591 lpht->flags |= LVHT_ONITEMLABEL;
6592 else if (infoPtr->himlState && PtInRect(&rcState, opt))
6593 lpht->flags |= LVHT_ONITEMSTATEICON;
6594 /* special case for LVS_EX_FULLROWSELECT */
6595 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
6596 !(lpht->flags & LVHT_ONITEM))
6598 lpht->flags = LVHT_ONITEM | LVHT_ABOVE;
6600 if (lpht->flags & LVHT_ONITEM)
6601 lpht->flags &= ~LVHT_NOWHERE;
6602 TRACE("lpht->flags=0x%x\n", lpht->flags);
6604 if (select && !(uView == LVS_REPORT &&
6605 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6606 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6608 if (uView == LVS_REPORT)
6610 /* get main item bounds */
6611 lvItem.iSubItem = 0;
6612 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6613 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6614 UnionRect(&rcBounds, &rcBounds, &rcState);
6616 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6618 return lpht->iItem = iItem;
6623 * Inserts a new item in the listview control.
6626 * [I] infoPtr : valid pointer to the listview structure
6627 * [I] lpLVItem : item information
6628 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6631 * SUCCESS : new item index
6634 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6636 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6641 BOOL is_sorted, has_changed;
6643 HWND hwndSelf = infoPtr->hwndSelf;
6645 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6647 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6649 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6650 if (!lpLVItem || lpLVItem->iSubItem) return -1;
6652 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6654 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
6656 /* insert item in listview control data structure */
6657 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6658 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6660 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6661 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6663 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
6665 /* calculate new item index */
6672 while (i < infoPtr->nItemCount)
6674 hItem = DPA_GetPtr( infoPtr->hdpaItems, i);
6675 item_s = (ITEM_INFO*)DPA_GetPtr(hItem, 0);
6677 cmpv = textcmpWT(item_s->hdr.pszText, lpLVItem->pszText, TRUE);
6678 if (infoPtr->dwStyle & LVS_SORTDESCENDING) cmpv *= -1;
6680 if (cmpv >= 0) break;
6686 nItem = min(lpLVItem->iItem, infoPtr->nItemCount);
6688 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6689 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6690 if (nItem == -1) goto fail;
6691 infoPtr->nItemCount++;
6693 /* shift indices first so they don't get tangled */
6694 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6696 /* set the item attributes */
6697 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6699 /* full size structure expected - _WIN32IE >= 0x560 */
6702 else if (lpLVItem->mask & LVIF_INDENT)
6704 /* indent member expected - _WIN32IE >= 0x300 */
6705 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6709 /* minimal structure expected */
6710 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6713 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6715 item.mask |= LVIF_STATE;
6716 item.stateMask |= LVIS_STATEIMAGEMASK;
6717 item.state &= ~LVIS_STATEIMAGEMASK;
6718 item.state |= INDEXTOSTATEIMAGEMASK(1);
6720 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6722 /* make room for the position, if we are in the right mode */
6723 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6725 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6727 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6729 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6734 /* send LVN_INSERTITEM notification */
6735 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6737 nmlv.lParam = lpItem->lParam;
6738 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6739 if (!IsWindow(hwndSelf))
6742 /* align items (set position of each item) */
6743 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6747 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6748 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6750 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6752 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6755 /* now is the invalidation fun */
6756 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6760 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6761 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6762 infoPtr->nItemCount--;
6764 DPA_DeletePtr(hdpaSubItems, 0);
6765 DPA_Destroy (hdpaSubItems);
6772 * Redraws a range of items.
6775 * [I] infoPtr : valid pointer to the listview structure
6776 * [I] nFirst : first item
6777 * [I] nLast : last item
6783 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6787 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6788 max(nFirst, nLast) >= infoPtr->nItemCount)
6791 for (i = nFirst; i <= nLast; i++)
6792 LISTVIEW_InvalidateItem(infoPtr, i);
6799 * Scroll the content of a listview.
6802 * [I] infoPtr : valid pointer to the listview structure
6803 * [I] dx : horizontal scroll amount in pixels
6804 * [I] dy : vertical scroll amount in pixels
6811 * If the control is in report mode (LVS_REPORT) the control can
6812 * be scrolled only in line increments. "dy" will be rounded to the
6813 * nearest number of pixels that are a whole line. Ex: if line height
6814 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6815 * is passed, then the scroll will be 0. (per MSDN 7/2002)
6817 * For: (per experimentation with native control and CSpy ListView)
6818 * LVS_ICON dy=1 = 1 pixel (vertical only)
6820 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6822 * LVS_LIST dx=1 = 1 column (horizontal only)
6823 * but will only scroll 1 column per message
6824 * no matter what the value.
6825 * dy must be 0 or FALSE returned.
6826 * LVS_REPORT dx=1 = 1 pixel
6830 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6832 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6834 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6835 dy /= infoPtr->nItemHeight;
6838 if (dy != 0) return FALSE;
6845 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6846 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6853 * Sets the background color.
6856 * [I] infoPtr : valid pointer to the listview structure
6857 * [I] clrBk : background color
6863 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6865 TRACE("(clrBk=%x)\n", clrBk);
6867 if(infoPtr->clrBk != clrBk) {
6868 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6869 infoPtr->clrBk = clrBk;
6870 if (clrBk == CLR_NONE)
6871 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6873 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6874 LISTVIEW_InvalidateList(infoPtr);
6880 /* LISTVIEW_SetBkImage */
6882 /*** Helper for {Insert,Set}ColumnT *only* */
6883 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
6884 const LVCOLUMNW *lpColumn, BOOL isW)
6886 if (lpColumn->mask & LVCF_FMT)
6888 /* format member is valid */
6889 lphdi->mask |= HDI_FORMAT;
6891 /* set text alignment (leftmost column must be left-aligned) */
6892 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6893 lphdi->fmt |= HDF_LEFT;
6894 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6895 lphdi->fmt |= HDF_RIGHT;
6896 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6897 lphdi->fmt |= HDF_CENTER;
6899 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6900 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6902 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6904 lphdi->fmt |= HDF_IMAGE;
6905 lphdi->iImage = I_IMAGECALLBACK;
6909 if (lpColumn->mask & LVCF_WIDTH)
6911 lphdi->mask |= HDI_WIDTH;
6912 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6914 /* make it fill the remainder of the controls width */
6918 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6920 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6921 lphdi->cxy += rcHeader.right - rcHeader.left;
6924 /* retrieve the layout of the header */
6925 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6926 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
6928 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6931 lphdi->cxy = lpColumn->cx;
6934 if (lpColumn->mask & LVCF_TEXT)
6936 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6937 lphdi->fmt |= HDF_STRING;
6938 lphdi->pszText = lpColumn->pszText;
6939 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6942 if (lpColumn->mask & LVCF_IMAGE)
6944 lphdi->mask |= HDI_IMAGE;
6945 lphdi->iImage = lpColumn->iImage;
6948 if (lpColumn->mask & LVCF_ORDER)
6950 lphdi->mask |= HDI_ORDER;
6951 lphdi->iOrder = lpColumn->iOrder;
6958 * Inserts a new column.
6961 * [I] infoPtr : valid pointer to the listview structure
6962 * [I] nColumn : column index
6963 * [I] lpColumn : column information
6964 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6967 * SUCCESS : new column index
6970 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6971 const LVCOLUMNW *lpColumn, BOOL isW)
6973 COLUMN_INFO *lpColumnInfo;
6976 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6978 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6980 if (!lpColumn || nColumn < 0) return -1;
6981 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6983 ZeroMemory(&hdi, sizeof(HDITEMW));
6984 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6987 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
6988 * (can be seen in SPY) otherwise column never gets added.
6990 if (!(lpColumn->mask & LVCF_WIDTH)) {
6991 hdi.mask |= HDI_WIDTH;
6996 * when the iSubItem is available Windows copies it to the header lParam. It seems
6997 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
6999 if (lpColumn->mask & LVCF_SUBITEM)
7001 hdi.mask |= HDI_LPARAM;
7002 hdi.lParam = lpColumn->iSubItem;
7005 /* create header if not present */
7006 LISTVIEW_CreateHeader(infoPtr);
7007 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle) &&
7008 (LVS_REPORT == uView) && (WS_VISIBLE & infoPtr->dwStyle))
7010 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
7013 /* insert item in header control */
7014 nNewColumn = SendMessageW(infoPtr->hwndHeader,
7015 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
7016 (WPARAM)nColumn, (LPARAM)&hdi);
7017 if (nNewColumn == -1) return -1;
7018 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
7020 /* create our own column info */
7021 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
7022 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
7024 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
7025 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, nNewColumn, (LPARAM)&lpColumnInfo->rcHeader))
7028 /* now we have to actually adjust the data */
7029 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
7031 SUBITEM_INFO *lpSubItem;
7035 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
7037 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
7038 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
7040 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
7041 if (lpSubItem->iSubItem >= nNewColumn)
7042 lpSubItem->iSubItem++;
7047 /* make space for the new column */
7048 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
7049 LISTVIEW_UpdateItemSize(infoPtr);
7054 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
7057 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
7065 * Sets the attributes of a header item.
7068 * [I] infoPtr : valid pointer to the listview structure
7069 * [I] nColumn : column index
7070 * [I] lpColumn : column attributes
7071 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
7077 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
7078 const LVCOLUMNW *lpColumn, BOOL isW)
7080 HDITEMW hdi, hdiget;
7083 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
7085 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7087 ZeroMemory(&hdi, sizeof(HDITEMW));
7088 if (lpColumn->mask & LVCF_FMT)
7090 hdi.mask |= HDI_FORMAT;
7091 hdiget.mask = HDI_FORMAT;
7092 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
7093 hdi.fmt = hdiget.fmt & HDF_STRING;
7095 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
7097 /* set header item attributes */
7098 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
7099 if (!bResult) return FALSE;
7101 if (lpColumn->mask & LVCF_FMT)
7103 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
7104 int oldFmt = lpColumnInfo->fmt;
7106 lpColumnInfo->fmt = lpColumn->fmt;
7107 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
7109 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7110 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
7119 * Sets the column order array
7122 * [I] infoPtr : valid pointer to the listview structure
7123 * [I] iCount : number of elements in column order array
7124 * [I] lpiArray : pointer to column order array
7130 static BOOL LISTVIEW_SetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
7132 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
7143 * Sets the width of a column
7146 * [I] infoPtr : valid pointer to the listview structure
7147 * [I] nColumn : column index
7148 * [I] cx : column width
7154 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
7156 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7157 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
7161 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
7163 /* set column width only if in report or list mode */
7164 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
7166 /* take care of invalid cx values */
7167 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
7168 else if (uView == LVS_LIST && cx < 1) return FALSE;
7170 /* resize all columns if in LVS_LIST mode */
7171 if(uView == LVS_LIST)
7173 infoPtr->nItemWidth = cx;
7174 LISTVIEW_InvalidateList(infoPtr);
7178 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7180 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
7185 lvItem.mask = LVIF_TEXT;
7187 lvItem.iSubItem = nColumn;
7188 lvItem.pszText = szDispText;
7189 lvItem.cchTextMax = DISP_TEXT_SIZE;
7190 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7192 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
7193 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
7194 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
7196 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
7197 max_cx += infoPtr->iconSize.cx;
7198 max_cx += TRAILING_LABEL_PADDING;
7201 /* autosize based on listview items width */
7202 if(cx == LVSCW_AUTOSIZE)
7204 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
7206 /* if iCol is the last column make it fill the remainder of the controls width */
7207 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
7212 LISTVIEW_GetOrigin(infoPtr, &Origin);
7213 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
7215 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
7219 /* Despite what the MS docs say, if this is not the last
7220 column, then MS resizes the column to the width of the
7221 largest text string in the column, including headers
7222 and items. This is different from LVSCW_AUTOSIZE in that
7223 LVSCW_AUTOSIZE ignores the header string length. */
7226 /* retrieve header text */
7227 hdi.mask = HDI_TEXT;
7228 hdi.cchTextMax = DISP_TEXT_SIZE;
7229 hdi.pszText = szDispText;
7230 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdi))
7232 HDC hdc = GetDC(infoPtr->hwndSelf);
7233 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
7236 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
7237 cx = size.cx + TRAILING_HEADER_PADDING;
7238 /* FIXME: Take into account the header image, if one is present */
7239 SelectObject(hdc, old_font);
7240 ReleaseDC(infoPtr->hwndSelf, hdc);
7242 cx = max (cx, max_cx);
7246 if (cx < 0) return FALSE;
7248 /* call header to update the column change */
7249 hdi.mask = HDI_WIDTH;
7251 TRACE("hdi.cxy=%d\n", hdi.cxy);
7252 return Header_SetItemW(infoPtr->hwndHeader, nColumn, &hdi);
7256 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
7259 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
7262 HBITMAP hbm_im, hbm_mask, hbm_orig;
7264 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
7265 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
7268 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
7269 ILC_COLOR | ILC_MASK, 2, 2);
7270 hdc_wnd = GetDC(infoPtr->hwndSelf);
7271 hdc = CreateCompatibleDC(hdc_wnd);
7272 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
7273 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
7274 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
7276 rc.left = rc.top = 0;
7277 rc.right = GetSystemMetrics(SM_CXSMICON);
7278 rc.bottom = GetSystemMetrics(SM_CYSMICON);
7280 hbm_orig = SelectObject(hdc, hbm_mask);
7281 FillRect(hdc, &rc, hbr_white);
7282 InflateRect(&rc, -2, -2);
7283 FillRect(hdc, &rc, hbr_black);
7285 SelectObject(hdc, hbm_im);
7286 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
7287 SelectObject(hdc, hbm_orig);
7288 ImageList_Add(himl, hbm_im, hbm_mask);
7290 SelectObject(hdc, hbm_im);
7291 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
7292 SelectObject(hdc, hbm_orig);
7293 ImageList_Add(himl, hbm_im, hbm_mask);
7295 DeleteObject(hbm_mask);
7296 DeleteObject(hbm_im);
7304 * Sets the extended listview style.
7307 * [I] infoPtr : valid pointer to the listview structure
7309 * [I] dwStyle : style
7312 * SUCCESS : previous style
7315 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwExStyle)
7317 DWORD dwOldExStyle = infoPtr->dwLvExStyle;
7321 infoPtr->dwLvExStyle = (dwOldExStyle & ~dwMask) | (dwExStyle & dwMask);
7323 infoPtr->dwLvExStyle = dwExStyle;
7325 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_CHECKBOXES)
7327 HIMAGELIST himl = 0;
7328 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7331 item.mask = LVIF_STATE;
7332 item.stateMask = LVIS_STATEIMAGEMASK;
7333 item.state = INDEXTOSTATEIMAGEMASK(1);
7334 LISTVIEW_SetItemState(infoPtr, -1, &item);
7336 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
7338 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
7341 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_HEADERDRAGDROP)
7345 /* if not already created */
7346 LISTVIEW_CreateHeader(infoPtr);
7348 dwStyle = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
7349 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
7350 dwStyle |= HDS_DRAGDROP;
7352 dwStyle &= ~HDS_DRAGDROP;
7353 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, dwStyle);
7356 /* GRIDLINES adds decoration at top so changes sizes */
7357 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_GRIDLINES)
7359 LISTVIEW_UpdateSize(infoPtr);
7363 LISTVIEW_InvalidateList(infoPtr);
7364 return dwOldExStyle;
7369 * Sets the new hot cursor used during hot tracking and hover selection.
7372 * [I] infoPtr : valid pointer to the listview structure
7373 * [I] hCursor : the new hot cursor handle
7376 * Returns the previous hot cursor
7378 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
7380 HCURSOR oldCursor = infoPtr->hHotCursor;
7382 infoPtr->hHotCursor = hCursor;
7390 * Sets the hot item index.
7393 * [I] infoPtr : valid pointer to the listview structure
7394 * [I] iIndex : index
7397 * SUCCESS : previous hot item index
7398 * FAILURE : -1 (no hot item)
7400 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
7402 INT iOldIndex = infoPtr->nHotItem;
7404 infoPtr->nHotItem = iIndex;
7412 * Sets the amount of time the cursor must hover over an item before it is selected.
7415 * [I] infoPtr : valid pointer to the listview structure
7416 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
7419 * Returns the previous hover time
7421 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
7423 DWORD oldHoverTime = infoPtr->dwHoverTime;
7425 infoPtr->dwHoverTime = dwHoverTime;
7427 return oldHoverTime;
7432 * Sets spacing for icons of LVS_ICON style.
7435 * [I] infoPtr : valid pointer to the listview structure
7436 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
7437 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
7440 * MAKELONG(oldcx, oldcy)
7442 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
7444 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7445 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7447 TRACE("requested=(%d,%d)\n", cx, cy);
7449 /* this is supported only for LVS_ICON style */
7450 if (uView != LVS_ICON) return oldspacing;
7452 /* set to defaults, if instructed to */
7453 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
7454 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
7456 /* if 0 then compute width
7457 * FIXME: Should scan each item and determine max width of
7458 * icon or label, then make that the width */
7460 cx = infoPtr->iconSpacing.cx;
7462 /* if 0 then compute height */
7464 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
7465 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
7468 infoPtr->iconSpacing.cx = cx;
7469 infoPtr->iconSpacing.cy = cy;
7471 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
7472 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
7473 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
7474 infoPtr->ntmHeight);
7476 /* these depend on the iconSpacing */
7477 LISTVIEW_UpdateItemSize(infoPtr);
7482 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
7486 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
7493 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
7494 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
7503 * [I] infoPtr : valid pointer to the listview structure
7504 * [I] nType : image list type
7505 * [I] himl : image list handle
7508 * SUCCESS : old image list
7511 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
7513 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7514 INT oldHeight = infoPtr->nItemHeight;
7515 HIMAGELIST himlOld = 0;
7517 TRACE("(nType=%d, himl=%p\n", nType, himl);
7522 himlOld = infoPtr->himlNormal;
7523 infoPtr->himlNormal = himl;
7524 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
7525 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
7529 himlOld = infoPtr->himlSmall;
7530 infoPtr->himlSmall = himl;
7531 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
7535 himlOld = infoPtr->himlState;
7536 infoPtr->himlState = himl;
7537 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
7538 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
7542 ERR("Unknown icon type=%d\n", nType);
7546 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
7547 if (infoPtr->nItemHeight != oldHeight)
7548 LISTVIEW_UpdateScroll(infoPtr);
7555 * Preallocates memory (does *not* set the actual count of items !)
7558 * [I] infoPtr : valid pointer to the listview structure
7559 * [I] nItems : item count (projected number of items to allocate)
7560 * [I] dwFlags : update flags
7566 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7568 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
7570 if (infoPtr->dwStyle & LVS_OWNERDATA)
7572 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7573 INT nOldCount = infoPtr->nItemCount;
7575 if (nItems < nOldCount)
7577 RANGE range = { nItems, nOldCount };
7578 ranges_del(infoPtr->selectionRanges, range);
7579 if (infoPtr->nFocusedItem >= nItems)
7581 LISTVIEW_SetItemFocus(infoPtr, -1);
7582 SetRectEmpty(&infoPtr->rcFocus);
7586 infoPtr->nItemCount = nItems;
7587 LISTVIEW_UpdateScroll(infoPtr);
7589 /* the flags are valid only in ownerdata report and list modes */
7590 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
7592 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7593 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7595 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7596 LISTVIEW_InvalidateList(infoPtr);
7603 LISTVIEW_GetOrigin(infoPtr, &Origin);
7604 nFrom = min(nOldCount, nItems);
7605 nTo = max(nOldCount, nItems);
7607 if (uView == LVS_REPORT)
7610 rcErase.top = nFrom * infoPtr->nItemHeight;
7611 rcErase.right = infoPtr->nItemWidth;
7612 rcErase.bottom = nTo * infoPtr->nItemHeight;
7613 OffsetRect(&rcErase, Origin.x, Origin.y);
7614 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7615 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7619 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
7621 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
7622 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
7623 rcErase.right = rcErase.left + infoPtr->nItemWidth;
7624 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7625 OffsetRect(&rcErase, Origin.x, Origin.y);
7626 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7627 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7629 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
7631 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
7632 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7633 OffsetRect(&rcErase, Origin.x, Origin.y);
7634 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7635 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7641 /* According to MSDN for non-LVS_OWNERDATA this is just
7642 * a performance issue. The control allocates its internal
7643 * data structures for the number of items specified. It
7644 * cuts down on the number of memory allocations. Therefore
7645 * we will just issue a WARN here
7647 WARN("for non-ownerdata performance option not implemented.\n");
7655 * Sets the position of an item.
7658 * [I] infoPtr : valid pointer to the listview structure
7659 * [I] nItem : item index
7660 * [I] pt : coordinate
7666 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7668 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7671 TRACE("(nItem=%d, &pt=%s\n", nItem, wine_dbgstr_point(&pt));
7673 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7674 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7676 LISTVIEW_GetOrigin(infoPtr, &Origin);
7678 /* This point value seems to be an undocumented feature.
7679 * The best guess is that it means either at the origin,
7680 * or at true beginning of the list. I will assume the origin. */
7681 if ((pt.x == -1) && (pt.y == -1))
7684 if (uView == LVS_ICON)
7686 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7687 pt.y -= ICON_TOP_PADDING;
7692 infoPtr->bAutoarrange = FALSE;
7694 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7699 * Sets the state of one or many items.
7702 * [I] infoPtr : valid pointer to the listview structure
7703 * [I] nItem : item index
7704 * [I] lpLVItem : item or subitem info
7710 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7712 BOOL bResult = TRUE;
7715 lvItem.iItem = nItem;
7716 lvItem.iSubItem = 0;
7717 lvItem.mask = LVIF_STATE;
7718 lvItem.state = lpLVItem->state;
7719 lvItem.stateMask = lpLVItem->stateMask;
7720 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7724 /* select all isn't allowed in LVS_SINGLESEL */
7725 if ((lvItem.state & lvItem.stateMask & LVIS_SELECTED) && (infoPtr->dwStyle & LVS_SINGLESEL))
7728 /* apply to all items */
7729 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7730 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7733 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7736 * Update selection mark
7738 * Investigation on windows 2k showed that selection mark was updated
7739 * whenever a new selection was made, but if the selected item was
7740 * unselected it was not updated.
7742 * we are probably still not 100% accurate, but this at least sets the
7743 * proper selection mark when it is needed
7746 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7747 (infoPtr->nSelectionMark == -1))
7750 for (i = 0; i < infoPtr->nItemCount; i++)
7752 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7754 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7756 infoPtr->nSelectionMark = i;
7760 else if (ranges_contain(infoPtr->selectionRanges, i))
7762 infoPtr->nSelectionMark = i;
7773 * Sets the text of an item or subitem.
7776 * [I] hwnd : window handle
7777 * [I] nItem : item index
7778 * [I] lpLVItem : item or subitem info
7779 * [I] isW : TRUE if input is Unicode
7785 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7789 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7791 lvItem.iItem = nItem;
7792 lvItem.iSubItem = lpLVItem->iSubItem;
7793 lvItem.mask = LVIF_TEXT;
7794 lvItem.pszText = lpLVItem->pszText;
7795 lvItem.cchTextMax = lpLVItem->cchTextMax;
7797 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7799 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7804 * Set item index that marks the start of a multiple selection.
7807 * [I] infoPtr : valid pointer to the listview structure
7808 * [I] nIndex : index
7811 * Index number or -1 if there is no selection mark.
7813 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7815 INT nOldIndex = infoPtr->nSelectionMark;
7817 TRACE("(nIndex=%d)\n", nIndex);
7819 infoPtr->nSelectionMark = nIndex;
7826 * Sets the text background color.
7829 * [I] infoPtr : valid pointer to the listview structure
7830 * [I] clrTextBk : text background color
7836 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7838 TRACE("(clrTextBk=%x)\n", clrTextBk);
7840 if (infoPtr->clrTextBk != clrTextBk)
7842 infoPtr->clrTextBk = clrTextBk;
7843 LISTVIEW_InvalidateList(infoPtr);
7851 * Sets the text foreground color.
7854 * [I] infoPtr : valid pointer to the listview structure
7855 * [I] clrText : text color
7861 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7863 TRACE("(clrText=%x)\n", clrText);
7865 if (infoPtr->clrText != clrText)
7867 infoPtr->clrText = clrText;
7868 LISTVIEW_InvalidateList(infoPtr);
7876 * Sets new ToolTip window to ListView control.
7879 * [I] infoPtr : valid pointer to the listview structure
7880 * [I] hwndNewToolTip : handle to new ToolTip
7885 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7887 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7888 infoPtr->hwndToolTip = hwndNewToolTip;
7889 return hwndOldToolTip;
7894 * sets the Unicode character format flag for the control
7896 * [I] infoPtr :valid pointer to the listview structure
7897 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
7900 * Old Unicode Format
7902 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL fUnicode)
7904 BOOL rc = infoPtr->notifyFormat;
7905 infoPtr->notifyFormat = (fUnicode)?NFR_UNICODE:NFR_ANSI;
7909 /* LISTVIEW_SetWorkAreas */
7913 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMS
7916 * [I] first : pointer to first ITEM_INFO to compare
7917 * [I] second : pointer to second ITEM_INFO to compare
7918 * [I] lParam : HWND of control
7921 * if first comes before second : negative
7922 * if first comes after second : positive
7923 * if first and second are equivalent : zero
7925 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7927 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7928 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
7929 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
7931 /* Forward the call to the client defined callback */
7932 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7937 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMSEX
7940 * [I] first : pointer to first ITEM_INFO to compare
7941 * [I] second : pointer to second ITEM_INFO to compare
7942 * [I] lParam : HWND of control
7945 * if first comes before second : negative
7946 * if first comes after second : positive
7947 * if first and second are equivalent : zero
7949 static INT WINAPI LISTVIEW_CallBackCompareEx(LPVOID first, LPVOID second, LPARAM lParam)
7951 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7952 INT first_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, first );
7953 INT second_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, second );
7955 /* Forward the call to the client defined callback */
7956 return (infoPtr->pfnCompare)( first_idx, second_idx, infoPtr->lParamSort );
7961 * Sorts the listview items.
7964 * [I] infoPtr : valid pointer to the listview structure
7965 * [I] pfnCompare : application-defined value
7966 * [I] lParamSort : pointer to comparison callback
7967 * [I] IsEx : TRUE when LVM_SORTITEMSEX used
7973 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare,
7974 LPARAM lParamSort, BOOL IsEx)
7976 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7979 LPVOID selectionMarkItem = NULL;
7980 LPVOID focusedItem = NULL;
7983 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7985 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7987 if (!pfnCompare) return FALSE;
7988 if (!infoPtr->hdpaItems) return FALSE;
7990 /* if there are 0 or 1 items, there is no need to sort */
7991 if (infoPtr->nItemCount < 2) return TRUE;
7993 /* clear selection */
7994 ranges_clear(infoPtr->selectionRanges);
7996 /* save selection mark and focused item */
7997 if (infoPtr->nSelectionMark >= 0)
7998 selectionMarkItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark);
7999 if (infoPtr->nFocusedItem >= 0)
8000 focusedItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
8002 infoPtr->pfnCompare = pfnCompare;
8003 infoPtr->lParamSort = lParamSort;
8005 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompareEx, (LPARAM)infoPtr);
8007 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
8009 /* restore selection ranges */
8010 for (i=0; i < infoPtr->nItemCount; i++)
8012 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
8013 lpItem = DPA_GetPtr(hdpaSubItems, 0);
8015 if (lpItem->state & LVIS_SELECTED)
8016 ranges_additem(infoPtr->selectionRanges, i);
8018 /* restore selection mark and focused item */
8019 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
8020 infoPtr->nFocusedItem = DPA_GetPtrIndex(infoPtr->hdpaItems, focusedItem);
8022 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
8024 /* refresh the display */
8025 if (uView != LVS_ICON && uView != LVS_SMALLICON)
8026 LISTVIEW_InvalidateList(infoPtr);
8033 * Update theme handle after a theme change.
8036 * [I] infoPtr : valid pointer to the listview structure
8040 * FAILURE : something else
8042 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
8044 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8045 CloseThemeData(theme);
8046 OpenThemeData(infoPtr->hwndSelf, themeClass);
8052 * Updates an items or rearranges the listview control.
8055 * [I] infoPtr : valid pointer to the listview structure
8056 * [I] nItem : item index
8062 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
8064 TRACE("(nItem=%d)\n", nItem);
8066 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
8068 /* rearrange with default alignment style */
8069 if (is_autoarrange(infoPtr))
8070 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8072 LISTVIEW_InvalidateItem(infoPtr, nItem);
8079 * Draw the track line at the place defined in the infoPtr structure.
8080 * The line is drawn with a XOR pen so drawing the line for the second time
8081 * in the same place erases the line.
8084 * [I] infoPtr : valid pointer to the listview structure
8090 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
8096 if (infoPtr->xTrackLine == -1)
8099 if (!(hdc = GetDC(infoPtr->hwndSelf)))
8101 hOldPen = SelectObject(hdc, GetStockObject(BLACK_PEN));
8102 oldROP = SetROP2(hdc, R2_XORPEN);
8103 MoveToEx(hdc, infoPtr->xTrackLine, infoPtr->rcList.top, NULL);
8104 LineTo(hdc, infoPtr->xTrackLine, infoPtr->rcList.bottom);
8105 SetROP2(hdc, oldROP);
8106 SelectObject(hdc, hOldPen);
8107 ReleaseDC(infoPtr->hwndSelf, hdc);
8113 * Called when an edit control should be displayed. This function is called after
8114 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
8117 * [I] hwnd : Handle to the listview
8118 * [I] uMsg : WM_TIMER (ignored)
8119 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
8120 * [I] dwTimer : The elapsed time (ignored)
8125 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
8127 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
8128 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8130 KillTimer(hwnd, idEvent);
8131 editItem->fEnabled = FALSE;
8132 /* check if the item is still selected */
8133 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
8134 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
8139 * Creates the listview control - the WM_NCCREATE phase.
8142 * [I] hwnd : window handle
8143 * [I] lpcs : the create parameters
8149 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
8151 LISTVIEW_INFO *infoPtr;
8154 TRACE("(lpcs=%p)\n", lpcs);
8156 /* initialize info pointer */
8157 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
8158 if (!infoPtr) return FALSE;
8160 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
8162 infoPtr->hwndSelf = hwnd;
8163 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
8164 /* determine the type of structures to use */
8165 infoPtr->hwndNotify = lpcs->hwndParent;
8166 /* infoPtr->notifyFormat will be filled in WM_CREATE */
8168 /* initialize color information */
8169 infoPtr->clrBk = CLR_NONE;
8170 infoPtr->clrText = CLR_DEFAULT;
8171 infoPtr->clrTextBk = CLR_DEFAULT;
8172 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
8174 /* set default values */
8175 infoPtr->nFocusedItem = -1;
8176 infoPtr->nSelectionMark = -1;
8177 infoPtr->nHotItem = -1;
8178 infoPtr->bRedraw = TRUE;
8179 infoPtr->bNoItemMetrics = TRUE;
8180 infoPtr->bDoChangeNotify = TRUE;
8181 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
8182 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
8183 infoPtr->nEditLabelItem = -1;
8184 infoPtr->nLButtonDownItem = -1;
8185 infoPtr->dwHoverTime = -1; /* default system hover time */
8186 infoPtr->nMeasureItemHeight = 0;
8187 infoPtr->xTrackLine = -1; /* no track line */
8188 infoPtr->itemEdit.fEnabled = FALSE;
8189 infoPtr->iVersion = COMCTL32_VERSION;
8191 /* get default font (icon title) */
8192 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
8193 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
8194 infoPtr->hFont = infoPtr->hDefaultFont;
8195 LISTVIEW_SaveTextMetrics(infoPtr);
8197 /* allocate memory for the data structure */
8198 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
8199 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
8200 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
8201 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
8202 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
8206 DestroyWindow(infoPtr->hwndHeader);
8207 ranges_destroy(infoPtr->selectionRanges);
8208 DPA_Destroy(infoPtr->hdpaItems);
8209 DPA_Destroy(infoPtr->hdpaPosX);
8210 DPA_Destroy(infoPtr->hdpaPosY);
8211 DPA_Destroy(infoPtr->hdpaColumns);
8218 * Creates the listview control - the WM_CREATE phase. Most of the data is
8219 * already set up in LISTVIEW_NCCreate
8222 * [I] hwnd : window handle
8223 * [I] lpcs : the create parameters
8229 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
8231 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8232 UINT uView = lpcs->style & LVS_TYPEMASK;
8234 TRACE("(lpcs=%p)\n", lpcs);
8236 infoPtr->dwStyle = lpcs->style;
8237 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
8238 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
8240 if ((uView == LVS_REPORT) && (lpcs->style & WS_VISIBLE))
8242 if (LISTVIEW_CreateHeader(infoPtr) < 0) return -1;
8245 infoPtr->hwndHeader = 0;
8247 /* init item size to avoid division by 0 */
8248 LISTVIEW_UpdateItemSize (infoPtr);
8250 if (uView == LVS_REPORT)
8252 if (!(LVS_NOCOLUMNHEADER & lpcs->style) && (WS_VISIBLE & lpcs->style))
8254 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8256 LISTVIEW_UpdateScroll(infoPtr);
8259 OpenThemeData(hwnd, themeClass);
8261 /* initialize the icon sizes */
8262 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
8263 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
8269 * Destroys the listview control.
8272 * [I] infoPtr : valid pointer to the listview structure
8278 static LRESULT LISTVIEW_Destroy(const LISTVIEW_INFO *infoPtr)
8280 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8281 CloseThemeData(theme);
8287 * Enables the listview control.
8290 * [I] infoPtr : valid pointer to the listview structure
8291 * [I] bEnable : specifies whether to enable or disable the window
8297 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr, BOOL bEnable)
8299 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
8300 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
8306 * Erases the background of the listview control.
8309 * [I] infoPtr : valid pointer to the listview structure
8310 * [I] hdc : device context handle
8316 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
8320 TRACE("(hdc=%p)\n", hdc);
8322 if (!GetClipBox(hdc, &rc)) return FALSE;
8324 if (infoPtr->clrBk == CLR_NONE)
8325 return SendMessageW(infoPtr->hwndNotify, WM_ERASEBKGND, (WPARAM)hdc, 0);
8327 /* for double buffered controls we need to do this during refresh */
8328 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
8330 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
8336 * Helper function for LISTVIEW_[HV]Scroll *only*.
8337 * Performs vertical/horizontal scrolling by a give amount.
8340 * [I] infoPtr : valid pointer to the listview structure
8341 * [I] dx : amount of horizontal scroll
8342 * [I] dy : amount of vertical scroll
8344 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
8346 /* now we can scroll the list */
8347 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
8348 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
8349 /* if we have focus, adjust rect */
8350 OffsetRect(&infoPtr->rcFocus, dx, dy);
8351 UpdateWindow(infoPtr->hwndSelf);
8356 * Performs vertical scrolling.
8359 * [I] infoPtr : valid pointer to the listview structure
8360 * [I] nScrollCode : scroll code
8361 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8362 * [I] hScrollWnd : scrollbar control window handle
8368 * SB_LINEUP/SB_LINEDOWN:
8369 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
8370 * for LVS_REPORT is 1 line
8371 * for LVS_LIST cannot occur
8374 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8375 INT nScrollDiff, HWND hScrollWnd)
8377 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8378 INT nOldScrollPos, nNewScrollPos;
8379 SCROLLINFO scrollInfo;
8382 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8383 debugscrollcode(nScrollCode), nScrollDiff);
8385 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8387 scrollInfo.cbSize = sizeof(SCROLLINFO);
8388 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8390 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
8392 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
8394 nOldScrollPos = scrollInfo.nPos;
8395 switch (nScrollCode)
8401 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
8405 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
8409 nScrollDiff = -scrollInfo.nPage;
8413 nScrollDiff = scrollInfo.nPage;
8416 case SB_THUMBPOSITION:
8418 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8425 /* quit right away if pos isn't changing */
8426 if (nScrollDiff == 0) return 0;
8428 /* calculate new position, and handle overflows */
8429 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8430 if (nScrollDiff > 0) {
8431 if (nNewScrollPos < nOldScrollPos ||
8432 nNewScrollPos > scrollInfo.nMax)
8433 nNewScrollPos = scrollInfo.nMax;
8435 if (nNewScrollPos > nOldScrollPos ||
8436 nNewScrollPos < scrollInfo.nMin)
8437 nNewScrollPos = scrollInfo.nMin;
8440 /* set the new position, and reread in case it changed */
8441 scrollInfo.fMask = SIF_POS;
8442 scrollInfo.nPos = nNewScrollPos;
8443 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
8445 /* carry on only if it really changed */
8446 if (nNewScrollPos == nOldScrollPos) return 0;
8448 /* now adjust to client coordinates */
8449 nScrollDiff = nOldScrollPos - nNewScrollPos;
8450 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
8452 /* and scroll the window */
8453 scroll_list(infoPtr, 0, nScrollDiff);
8460 * Performs horizontal scrolling.
8463 * [I] infoPtr : valid pointer to the listview structure
8464 * [I] nScrollCode : scroll code
8465 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8466 * [I] hScrollWnd : scrollbar control window handle
8472 * SB_LINELEFT/SB_LINERIGHT:
8473 * for LVS_ICON, LVS_SMALLICON 1 pixel
8474 * for LVS_REPORT is 1 pixel
8475 * for LVS_LIST is 1 column --> which is a 1 because the
8476 * scroll is based on columns not pixels
8479 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8480 INT nScrollDiff, HWND hScrollWnd)
8482 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8483 INT nOldScrollPos, nNewScrollPos;
8484 SCROLLINFO scrollInfo;
8486 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8487 debugscrollcode(nScrollCode), nScrollDiff);
8489 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8491 scrollInfo.cbSize = sizeof(SCROLLINFO);
8492 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8494 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
8496 nOldScrollPos = scrollInfo.nPos;
8498 switch (nScrollCode)
8512 nScrollDiff = -scrollInfo.nPage;
8516 nScrollDiff = scrollInfo.nPage;
8519 case SB_THUMBPOSITION:
8521 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8528 /* quit right away if pos isn't changing */
8529 if (nScrollDiff == 0) return 0;
8531 /* calculate new position, and handle overflows */
8532 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8533 if (nScrollDiff > 0) {
8534 if (nNewScrollPos < nOldScrollPos ||
8535 nNewScrollPos > scrollInfo.nMax)
8536 nNewScrollPos = scrollInfo.nMax;
8538 if (nNewScrollPos > nOldScrollPos ||
8539 nNewScrollPos < scrollInfo.nMin)
8540 nNewScrollPos = scrollInfo.nMin;
8543 /* set the new position, and reread in case it changed */
8544 scrollInfo.fMask = SIF_POS;
8545 scrollInfo.nPos = nNewScrollPos;
8546 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
8548 /* carry on only if it really changed */
8549 if (nNewScrollPos == nOldScrollPos) return 0;
8551 if(uView == LVS_REPORT)
8552 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
8554 /* now adjust to client coordinates */
8555 nScrollDiff = nOldScrollPos - nNewScrollPos;
8556 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
8558 /* and scroll the window */
8559 scroll_list(infoPtr, nScrollDiff, 0);
8564 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
8566 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8567 INT gcWheelDelta = 0;
8568 INT pulScrollLines = 3;
8569 SCROLLINFO scrollInfo;
8571 TRACE("(wheelDelta=%d)\n", wheelDelta);
8573 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
8574 gcWheelDelta -= wheelDelta;
8576 scrollInfo.cbSize = sizeof(SCROLLINFO);
8577 scrollInfo.fMask = SIF_POS;
8584 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
8585 * should be fixed in the future.
8587 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
8588 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
8592 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
8594 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
8595 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
8596 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
8601 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
8612 * [I] infoPtr : valid pointer to the listview structure
8613 * [I] nVirtualKey : virtual key
8614 * [I] lKeyData : key data
8619 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
8621 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8622 HWND hwndSelf = infoPtr->hwndSelf;
8624 NMLVKEYDOWN nmKeyDown;
8626 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
8628 /* send LVN_KEYDOWN notification */
8629 nmKeyDown.wVKey = nVirtualKey;
8630 nmKeyDown.flags = 0;
8631 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
8632 if (!IsWindow(hwndSelf))
8635 switch (nVirtualKey)
8638 nItem = infoPtr->nFocusedItem;
8639 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8640 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
8644 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
8646 if (!notify(infoPtr, NM_RETURN)) return 0;
8647 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
8652 if (infoPtr->nItemCount > 0)
8657 if (infoPtr->nItemCount > 0)
8658 nItem = infoPtr->nItemCount - 1;
8662 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TOLEFT);
8666 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_ABOVE);
8670 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TORIGHT);
8674 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_BELOW);
8678 if (uView == LVS_REPORT)
8680 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8681 if (infoPtr->nFocusedItem == topidx)
8682 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
8687 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
8688 * LISTVIEW_GetCountPerRow(infoPtr);
8689 if(nItem < 0) nItem = 0;
8693 if (uView == LVS_REPORT)
8695 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8696 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
8697 if (infoPtr->nFocusedItem == topidx + cnt - 1)
8698 nItem = infoPtr->nFocusedItem + cnt - 1;
8700 nItem = topidx + cnt - 1;
8703 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
8704 * LISTVIEW_GetCountPerRow(infoPtr);
8705 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
8709 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
8710 LISTVIEW_KeySelection(infoPtr, nItem, nVirtualKey == VK_SPACE);
8720 * [I] infoPtr : valid pointer to the listview structure
8725 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
8729 /* if we did not have the focus, there's nothing to do */
8730 if (!infoPtr->bFocus) return 0;
8732 /* send NM_KILLFOCUS notification */
8733 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
8735 /* if we have a focus rectangle, get rid of it */
8736 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
8738 /* set window focus flag */
8739 infoPtr->bFocus = FALSE;
8741 /* invalidate the selected items before resetting focus flag */
8742 LISTVIEW_InvalidateSelectedItems(infoPtr);
8749 * Processes double click messages (left mouse button).
8752 * [I] infoPtr : valid pointer to the listview structure
8753 * [I] wKey : key flag
8754 * [I] x,y : mouse coordinate
8759 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8761 LVHITTESTINFO htInfo;
8763 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8765 /* Cancel the item edition if any */
8766 if (infoPtr->itemEdit.fEnabled)
8768 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
8769 infoPtr->itemEdit.fEnabled = FALSE;
8772 /* send NM_RELEASEDCAPTURE notification */
8773 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8778 /* send NM_DBLCLK notification */
8779 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
8780 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
8782 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
8783 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
8790 * Processes mouse down messages (left mouse button).
8793 * infoPtr [I ] valid pointer to the listview structure
8794 * wKey [I ] key flag
8795 * x,y [I ] mouse coordinate
8800 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8802 LVHITTESTINFO lvHitTestInfo;
8803 static BOOL bGroupSelect = TRUE;
8804 POINT pt = { x, y };
8807 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8809 /* send NM_RELEASEDCAPTURE notification */
8810 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8812 /* set left button down flag and record the click position */
8813 infoPtr->bLButtonDown = TRUE;
8814 infoPtr->ptClickPos = pt;
8815 infoPtr->bDragging = FALSE;
8817 lvHitTestInfo.pt.x = x;
8818 lvHitTestInfo.pt.y = y;
8820 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8821 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
8822 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8824 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8826 toggle_checkbox_state(infoPtr, nItem);
8830 if (infoPtr->dwStyle & LVS_SINGLESEL)
8832 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8833 infoPtr->nEditLabelItem = nItem;
8835 LISTVIEW_SetSelection(infoPtr, nItem);
8839 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8843 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
8844 LISTVIEW_SetItemFocus(infoPtr, nItem);
8845 infoPtr->nSelectionMark = nItem;
8851 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8852 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8854 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8855 infoPtr->nSelectionMark = nItem;
8858 else if (wKey & MK_CONTROL)
8862 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8864 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8865 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8866 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8867 infoPtr->nSelectionMark = nItem;
8869 else if (wKey & MK_SHIFT)
8871 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8875 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8877 infoPtr->nEditLabelItem = nItem;
8878 infoPtr->nLButtonDownItem = nItem;
8880 LISTVIEW_SetItemFocus(infoPtr, nItem);
8883 /* set selection (clears other pre-existing selections) */
8884 LISTVIEW_SetSelection(infoPtr, nItem);
8888 if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
8889 if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
8893 /* remove all selections */
8894 if (!(wKey & MK_CONTROL) && !(wKey & MK_SHIFT))
8895 LISTVIEW_DeselectAll(infoPtr);
8904 * Processes mouse up messages (left mouse button).
8907 * infoPtr [I ] valid pointer to the listview structure
8908 * wKey [I ] key flag
8909 * x,y [I ] mouse coordinate
8914 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8916 LVHITTESTINFO lvHitTestInfo;
8918 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8920 if (!infoPtr->bLButtonDown) return 0;
8922 lvHitTestInfo.pt.x = x;
8923 lvHitTestInfo.pt.y = y;
8925 /* send NM_CLICK notification */
8926 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8927 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
8929 /* set left button flag */
8930 infoPtr->bLButtonDown = FALSE;
8932 /* set a single selection, reset others */
8933 if(lvHitTestInfo.iItem == infoPtr->nLButtonDownItem && lvHitTestInfo.iItem != -1)
8934 LISTVIEW_SetSelection(infoPtr, infoPtr->nLButtonDownItem);
8935 infoPtr->nLButtonDownItem = -1;
8937 if (infoPtr->bDragging)
8939 infoPtr->bDragging = FALSE;
8943 /* if we clicked on a selected item, edit the label */
8944 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8946 /* we want to make sure the user doesn't want to do a double click. So we will
8947 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
8949 infoPtr->itemEdit.fEnabled = TRUE;
8950 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
8951 SetTimer(infoPtr->hwndSelf,
8952 (UINT_PTR)&infoPtr->itemEdit,
8953 GetDoubleClickTime(),
8954 LISTVIEW_DelayedEditItem);
8957 if (!infoPtr->bFocus)
8958 SetFocus(infoPtr->hwndSelf);
8965 * Destroys the listview control (called after WM_DESTROY).
8968 * [I] infoPtr : valid pointer to the listview structure
8973 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8977 /* delete all items */
8978 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
8980 /* destroy data structure */
8981 DPA_Destroy(infoPtr->hdpaItems);
8982 DPA_Destroy(infoPtr->hdpaPosX);
8983 DPA_Destroy(infoPtr->hdpaPosY);
8984 DPA_Destroy(infoPtr->hdpaColumns);
8985 ranges_destroy(infoPtr->selectionRanges);
8987 /* destroy image lists */
8988 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8990 if (infoPtr->himlNormal)
8991 ImageList_Destroy(infoPtr->himlNormal);
8992 if (infoPtr->himlSmall)
8993 ImageList_Destroy(infoPtr->himlSmall);
8994 if (infoPtr->himlState)
8995 ImageList_Destroy(infoPtr->himlState);
8998 /* destroy font, bkgnd brush */
9000 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
9001 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
9003 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
9005 /* free listview info pointer*/
9013 * Handles notifications from header.
9016 * [I] infoPtr : valid pointer to the listview structure
9017 * [I] nCtrlId : control identifier
9018 * [I] lpnmh : notification information
9023 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
9025 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9026 HWND hwndSelf = infoPtr->hwndSelf;
9028 TRACE("(lpnmh=%p)\n", lpnmh);
9030 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
9032 switch (lpnmh->hdr.code)
9037 COLUMN_INFO *lpColumnInfo;
9041 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
9044 /* remove the old line (if any) */
9045 LISTVIEW_DrawTrackLine(infoPtr);
9047 /* compute & draw the new line */
9048 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
9049 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
9050 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
9051 infoPtr->xTrackLine = x + ptOrigin.x;
9052 LISTVIEW_DrawTrackLine(infoPtr);
9058 /* remove the track line (if any) */
9059 LISTVIEW_DrawTrackLine(infoPtr);
9060 infoPtr->xTrackLine = -1;
9064 FIXME("Changing column order not implemented\n");
9067 case HDN_ITEMCHANGINGW:
9068 case HDN_ITEMCHANGINGA:
9069 return notify_forward_header(infoPtr, lpnmh);
9071 case HDN_ITEMCHANGEDW:
9072 case HDN_ITEMCHANGEDA:
9074 COLUMN_INFO *lpColumnInfo;
9077 notify_forward_header(infoPtr, lpnmh);
9078 if (!IsWindow(hwndSelf))
9081 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
9085 hdi.mask = HDI_WIDTH;
9086 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, &hdi)) return 0;
9090 cxy = lpnmh->pitem->cxy;
9092 /* determine how much we change since the last know position */
9093 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
9094 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
9097 lpColumnInfo->rcHeader.right += dx;
9098 if (lpnmh->iItem + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
9099 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
9102 /* only needs to update the scrolls */
9103 infoPtr->nItemWidth += dx;
9104 LISTVIEW_UpdateScroll(infoPtr);
9106 LISTVIEW_UpdateItemSize(infoPtr);
9107 if (uView == LVS_REPORT && is_redrawing(infoPtr))
9110 RECT rcCol = lpColumnInfo->rcHeader;
9112 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
9113 OffsetRect(&rcCol, ptOrigin.x, 0);
9115 rcCol.top = infoPtr->rcList.top;
9116 rcCol.bottom = infoPtr->rcList.bottom;
9118 /* resizing left-aligned columns leaves most of the left side untouched */
9119 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
9121 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
9124 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
9127 /* when shrinking the last column clear the now unused field */
9128 if (lpnmh->iItem == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
9134 /* deal with right from rightmost column area */
9135 right.left = rcCol.right;
9136 right.top = rcCol.top;
9137 right.bottom = rcCol.bottom;
9138 right.right = infoPtr->rcList.right;
9140 LISTVIEW_InvalidateRect(infoPtr, &right);
9143 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
9149 case HDN_ITEMCLICKW:
9150 case HDN_ITEMCLICKA:
9152 /* Handle sorting by Header Column */
9155 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
9157 nmlv.iSubItem = lpnmh->iItem;
9158 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
9159 notify_forward_header(infoPtr, lpnmh);
9163 case HDN_DIVIDERDBLCLICKW:
9164 case HDN_DIVIDERDBLCLICKA:
9165 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
9174 * Paint non-client area of control.
9177 * [I] infoPtr : valid pointer to the listview structureof the sender
9178 * [I] region : update region
9181 * TRUE - frame was painted
9182 * FALSE - call default window proc
9184 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
9186 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
9190 int cxEdge = GetSystemMetrics (SM_CXEDGE),
9191 cyEdge = GetSystemMetrics (SM_CYEDGE);
9193 if (!theme) return FALSE;
9195 GetWindowRect(infoPtr->hwndSelf, &r);
9197 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
9198 r.right - cxEdge, r.bottom - cyEdge);
9199 if (region != (HRGN)1)
9200 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
9201 OffsetRect(&r, -r.left, -r.top);
9203 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
9204 OffsetRect(&r, -r.left, -r.top);
9206 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
9207 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
9208 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
9209 ReleaseDC(infoPtr->hwndSelf, dc);
9211 /* Call default proc to get the scrollbars etc. painted */
9212 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
9219 * Determines the type of structure to use.
9222 * [I] infoPtr : valid pointer to the listview structureof the sender
9223 * [I] hwndFrom : listview window handle
9224 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
9229 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
9231 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
9233 if (nCommand == NF_REQUERY)
9234 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9236 return infoPtr->notifyFormat;
9241 * Paints/Repaints the listview control.
9244 * [I] infoPtr : valid pointer to the listview structure
9245 * [I] hdc : device context handle
9250 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
9252 TRACE("(hdc=%p)\n", hdc);
9254 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
9256 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9258 infoPtr->bNoItemMetrics = FALSE;
9259 LISTVIEW_UpdateItemSize(infoPtr);
9260 if (uView == LVS_ICON || uView == LVS_SMALLICON)
9261 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9262 LISTVIEW_UpdateScroll(infoPtr);
9265 if (infoPtr->hwndHeader) UpdateWindow(infoPtr->hwndHeader);
9268 LISTVIEW_Refresh(infoPtr, hdc, NULL);
9273 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
9275 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
9276 EndPaint(infoPtr->hwndSelf, &ps);
9285 * Paints/Repaints the listview control.
9288 * [I] infoPtr : valid pointer to the listview structure
9289 * [I] hdc : device context handle
9290 * [I] options : drawing options
9295 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
9297 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
9299 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
9302 if (options & PRF_ERASEBKGND)
9303 LISTVIEW_EraseBkgnd(infoPtr, hdc);
9305 if (options & PRF_CLIENT)
9306 LISTVIEW_Paint(infoPtr, hdc);
9314 * Processes double click messages (right mouse button).
9317 * [I] infoPtr : valid pointer to the listview structure
9318 * [I] wKey : key flag
9319 * [I] x,y : mouse coordinate
9324 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9326 LVHITTESTINFO lvHitTestInfo;
9328 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9330 /* send NM_RELEASEDCAPTURE notification */
9331 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9333 /* send NM_RDBLCLK notification */
9334 lvHitTestInfo.pt.x = x;
9335 lvHitTestInfo.pt.y = y;
9336 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9337 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
9344 * Processes mouse down messages (right mouse button).
9347 * [I] infoPtr : valid pointer to the listview structure
9348 * [I] wKey : key flag
9349 * [I] x,y : mouse coordinate
9354 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9356 LVHITTESTINFO lvHitTestInfo;
9359 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9361 /* send NM_RELEASEDCAPTURE notification */
9362 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9364 /* make sure the listview control window has the focus */
9365 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
9367 /* set right button down flag */
9368 infoPtr->bRButtonDown = TRUE;
9370 /* determine the index of the selected item */
9371 lvHitTestInfo.pt.x = x;
9372 lvHitTestInfo.pt.y = y;
9373 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
9375 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
9377 LISTVIEW_SetItemFocus(infoPtr, nItem);
9378 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
9379 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9380 LISTVIEW_SetSelection(infoPtr, nItem);
9384 LISTVIEW_DeselectAll(infoPtr);
9392 * Processes mouse up messages (right mouse button).
9395 * [I] infoPtr : valid pointer to the listview structure
9396 * [I] wKey : key flag
9397 * [I] x,y : mouse coordinate
9402 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9404 LVHITTESTINFO lvHitTestInfo;
9407 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9409 if (!infoPtr->bRButtonDown) return 0;
9411 /* set button flag */
9412 infoPtr->bRButtonDown = FALSE;
9414 /* Send NM_RCLICK notification */
9415 lvHitTestInfo.pt.x = x;
9416 lvHitTestInfo.pt.y = y;
9417 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9418 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
9420 /* Change to screen coordinate for WM_CONTEXTMENU */
9421 pt = lvHitTestInfo.pt;
9422 ClientToScreen(infoPtr->hwndSelf, &pt);
9424 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
9425 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
9426 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
9437 * [I] infoPtr : valid pointer to the listview structure
9438 * [I] hwnd : window handle of window containing the cursor
9439 * [I] nHittest : hit-test code
9440 * [I] wMouseMsg : ideintifier of the mouse message
9443 * TRUE if cursor is set
9446 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
9448 LVHITTESTINFO lvHitTestInfo;
9450 if(!(LISTVIEW_isHotTracking(infoPtr))) return FALSE;
9452 if(!infoPtr->hHotCursor) return FALSE;
9454 GetCursorPos(&lvHitTestInfo.pt);
9455 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
9457 SetCursor(infoPtr->hHotCursor);
9467 * [I] infoPtr : valid pointer to the listview structure
9468 * [I] hwndLoseFocus : handle of previously focused window
9473 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
9475 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
9477 /* if we have the focus already, there's nothing to do */
9478 if (infoPtr->bFocus) return 0;
9480 /* send NM_SETFOCUS notification */
9481 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
9483 /* set window focus flag */
9484 infoPtr->bFocus = TRUE;
9486 /* put the focus rect back on */
9487 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
9489 /* redraw all visible selected items */
9490 LISTVIEW_InvalidateSelectedItems(infoPtr);
9500 * [I] infoPtr : valid pointer to the listview structure
9501 * [I] fRedraw : font handle
9502 * [I] fRedraw : redraw flag
9507 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
9509 HFONT oldFont = infoPtr->hFont;
9511 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
9513 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
9514 if (infoPtr->hFont == oldFont) return 0;
9516 LISTVIEW_SaveTextMetrics(infoPtr);
9518 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
9520 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
9521 LISTVIEW_UpdateSize(infoPtr);
9522 LISTVIEW_UpdateScroll(infoPtr);
9525 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
9532 * Message handling for WM_SETREDRAW.
9533 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
9536 * [I] infoPtr : valid pointer to the listview structure
9537 * [I] bRedraw: state of redraw flag
9540 * DefWinProc return value
9542 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
9544 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
9546 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
9547 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
9549 infoPtr->bRedraw = bRedraw;
9551 if(!bRedraw) return 0;
9553 if (is_autoarrange(infoPtr))
9554 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9555 LISTVIEW_UpdateScroll(infoPtr);
9557 /* despite what the WM_SETREDRAW docs says, apps expect us
9558 * to invalidate the listview here... stupid! */
9559 LISTVIEW_InvalidateList(infoPtr);
9566 * Resizes the listview control. This function processes WM_SIZE
9567 * messages. At this time, the width and height are not used.
9570 * [I] infoPtr : valid pointer to the listview structure
9571 * [I] Width : new width
9572 * [I] Height : new height
9577 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
9579 RECT rcOld = infoPtr->rcList;
9581 TRACE("(width=%d, height=%d)\n", Width, Height);
9583 LISTVIEW_UpdateSize(infoPtr);
9584 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
9586 /* do not bother with display related stuff if we're not redrawing */
9587 if (!is_redrawing(infoPtr)) return 0;
9589 if (is_autoarrange(infoPtr))
9590 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9592 LISTVIEW_UpdateScroll(infoPtr);
9594 /* refresh all only for lists whose height changed significantly */
9595 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
9596 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
9597 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
9598 LISTVIEW_InvalidateList(infoPtr);
9605 * Sets the size information.
9608 * [I] infoPtr : valid pointer to the listview structure
9613 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
9615 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9617 TRACE("uView=%d, rcList(old)=%s\n", uView, wine_dbgstr_rect(&infoPtr->rcList));
9619 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
9621 if (uView == LVS_LIST)
9623 /* Apparently the "LIST" style is supposed to have the same
9624 * number of items in a column even if there is no scroll bar.
9625 * Since if a scroll bar already exists then the bottom is already
9626 * reduced, only reduce if the scroll bar does not currently exist.
9627 * The "2" is there to mimic the native control. I think it may be
9628 * related to either padding or edges. (GLA 7/2002)
9630 if (!(infoPtr->dwStyle & WS_HSCROLL))
9631 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
9632 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
9634 else if (uView == LVS_REPORT)
9639 hl.prc = &infoPtr->rcList;
9641 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9642 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
9643 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy,
9644 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9645 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9646 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
9648 infoPtr->rcList.top = max(wp.cy, 0);
9649 infoPtr->rcList.top += (infoPtr->dwLvExStyle & LVS_EX_GRIDLINES) ? 2 : 0;
9652 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
9657 * Processes WM_STYLECHANGED messages.
9660 * [I] infoPtr : valid pointer to the listview structure
9661 * [I] wStyleType : window style type (normal or extended)
9662 * [I] lpss : window style information
9667 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9668 const STYLESTRUCT *lpss)
9670 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
9671 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
9674 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9675 wStyleType, lpss->styleOld, lpss->styleNew);
9677 if (wStyleType != GWL_STYLE) return 0;
9679 infoPtr->dwStyle = lpss->styleNew;
9681 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
9682 ((lpss->styleNew & WS_HSCROLL) == 0))
9683 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
9685 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
9686 ((lpss->styleNew & WS_VSCROLL) == 0))
9687 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
9689 if (uNewView != uOldView)
9691 SIZE oldIconSize = infoPtr->iconSize;
9694 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9695 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9697 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9698 SetRectEmpty(&infoPtr->rcFocus);
9700 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9701 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
9703 if (uNewView == LVS_ICON)
9705 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9707 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9708 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9709 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9712 else if (uNewView == LVS_REPORT)
9717 LISTVIEW_CreateHeader( infoPtr );
9719 hl.prc = &infoPtr->rcList;
9721 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9722 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9723 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9724 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9727 LISTVIEW_UpdateItemSize(infoPtr);
9730 if (uNewView == LVS_REPORT)
9732 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
9734 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
9736 /* Turn off the header control */
9737 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
9738 TRACE("Hide header control, was 0x%08x\n", style);
9739 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
9741 /* Turn on the header control */
9742 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
9744 TRACE("Show header control, was 0x%08x\n", style);
9745 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
9751 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
9752 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
9753 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9755 /* update the size of the client area */
9756 LISTVIEW_UpdateSize(infoPtr);
9758 /* add scrollbars if needed */
9759 LISTVIEW_UpdateScroll(infoPtr);
9761 /* invalidate client area + erase background */
9762 LISTVIEW_InvalidateList(infoPtr);
9769 * Processes WM_STYLECHANGING messages.
9772 * [I] infoPtr : valid pointer to the listview structure
9773 * [I] wStyleType : window style type (normal or extended)
9774 * [I0] lpss : window style information
9779 static INT LISTVIEW_StyleChanging(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9782 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9783 wStyleType, lpss->styleOld, lpss->styleNew);
9785 /* don't forward LVS_OWNERDATA only if not already set to */
9786 if ((lpss->styleNew ^ lpss->styleOld) & LVS_OWNERDATA)
9788 if (lpss->styleOld & LVS_OWNERDATA)
9789 lpss->styleNew |= LVS_OWNERDATA;
9791 lpss->styleNew &= ~LVS_OWNERDATA;
9799 * Processes WM_SHOWWINDOW messages.
9802 * [I] infoPtr : valid pointer to the listview structure
9803 * [I] bShown : window is being shown (FALSE when hidden)
9804 * [I] iStatus : window show status
9809 static LRESULT LISTVIEW_ShowWindow(LISTVIEW_INFO *infoPtr, BOOL bShown, INT iStatus)
9811 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9813 /* header delayed creation */
9814 if ((uView == LVS_REPORT) && bShown)
9816 LISTVIEW_CreateHeader(infoPtr);
9818 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle))
9819 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
9827 * Processes CCM_GETVERSION messages.
9830 * [I] infoPtr : valid pointer to the listview structure
9835 static inline LRESULT LISTVIEW_GetVersion(LISTVIEW_INFO *infoPtr)
9837 return infoPtr->iVersion;
9842 * Processes CCM_SETVERSION messages.
9845 * [I] infoPtr : valid pointer to the listview structure
9846 * [I] iVersion : version to be set
9849 * -1 when requested version is greater than DLL version;
9850 * previous version otherwise
9852 static LRESULT LISTVIEW_SetVersion(LISTVIEW_INFO *infoPtr, DWORD iVersion)
9854 INT iOldVersion = infoPtr->iVersion;
9856 if (iVersion > COMCTL32_VERSION)
9859 infoPtr->iVersion = iVersion;
9861 TRACE("new version %d\n", iVersion);
9868 * Window procedure of the listview control.
9871 static LRESULT WINAPI
9872 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9874 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9876 TRACE("(uMsg=%x wParam=%lx lParam=%lx)\n", uMsg, wParam, lParam);
9878 if (!infoPtr && (uMsg != WM_NCCREATE))
9879 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9883 case LVM_APPROXIMATEVIEWRECT:
9884 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
9885 LOWORD(lParam), HIWORD(lParam));
9887 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
9889 /* case LVM_CANCELEDITLABEL: */
9891 case LVM_CREATEDRAGIMAGE:
9892 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
9894 case LVM_DELETEALLITEMS:
9895 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
9897 case LVM_DELETECOLUMN:
9898 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
9900 case LVM_DELETEITEM:
9901 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
9903 case LVM_EDITLABELW:
9904 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
9906 case LVM_EDITLABELA:
9907 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
9909 /* case LVM_ENABLEGROUPVIEW: */
9911 case LVM_ENSUREVISIBLE:
9912 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
9915 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
9918 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
9920 case LVM_GETBKCOLOR:
9921 return infoPtr->clrBk;
9923 /* case LVM_GETBKIMAGE: */
9925 case LVM_GETCALLBACKMASK:
9926 return infoPtr->uCallbackMask;
9928 case LVM_GETCOLUMNA:
9929 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9931 case LVM_GETCOLUMNW:
9932 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9934 case LVM_GETCOLUMNORDERARRAY:
9935 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9937 case LVM_GETCOLUMNWIDTH:
9938 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
9940 case LVM_GETCOUNTPERPAGE:
9941 return LISTVIEW_GetCountPerPage(infoPtr);
9943 case LVM_GETEDITCONTROL:
9944 return (LRESULT)infoPtr->hwndEdit;
9946 case LVM_GETEXTENDEDLISTVIEWSTYLE:
9947 return infoPtr->dwLvExStyle;
9949 /* case LVM_GETGROUPINFO: */
9951 /* case LVM_GETGROUPMETRICS: */
9954 return (LRESULT)infoPtr->hwndHeader;
9956 case LVM_GETHOTCURSOR:
9957 return (LRESULT)infoPtr->hHotCursor;
9959 case LVM_GETHOTITEM:
9960 return infoPtr->nHotItem;
9962 case LVM_GETHOVERTIME:
9963 return infoPtr->dwHoverTime;
9965 case LVM_GETIMAGELIST:
9966 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
9968 /* case LVM_GETINSERTMARK: */
9970 /* case LVM_GETINSERTMARKCOLOR: */
9972 /* case LVM_GETINSERTMARKRECT: */
9974 case LVM_GETISEARCHSTRINGA:
9975 case LVM_GETISEARCHSTRINGW:
9976 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
9980 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
9983 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
9985 case LVM_GETITEMCOUNT:
9986 return infoPtr->nItemCount;
9988 case LVM_GETITEMPOSITION:
9989 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
9991 case LVM_GETITEMRECT:
9992 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
9994 case LVM_GETITEMSPACING:
9995 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
9997 case LVM_GETITEMSTATE:
9998 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
10000 case LVM_GETITEMTEXTA:
10001 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
10003 case LVM_GETITEMTEXTW:
10004 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
10006 case LVM_GETNEXTITEM:
10007 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
10009 case LVM_GETNUMBEROFWORKAREAS:
10010 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
10013 case LVM_GETORIGIN:
10014 if (!lParam) return FALSE;
10015 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT ||
10016 (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST) return FALSE;
10017 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
10020 /* case LVM_GETOUTLINECOLOR: */
10022 /* case LVM_GETSELECTEDCOLUMN: */
10024 case LVM_GETSELECTEDCOUNT:
10025 return LISTVIEW_GetSelectedCount(infoPtr);
10027 case LVM_GETSELECTIONMARK:
10028 return infoPtr->nSelectionMark;
10030 case LVM_GETSTRINGWIDTHA:
10031 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
10033 case LVM_GETSTRINGWIDTHW:
10034 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
10036 case LVM_GETSUBITEMRECT:
10037 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
10039 case LVM_GETTEXTBKCOLOR:
10040 return infoPtr->clrTextBk;
10042 case LVM_GETTEXTCOLOR:
10043 return infoPtr->clrText;
10045 /* case LVM_GETTILEINFO: */
10047 /* case LVM_GETTILEVIEWINFO: */
10049 case LVM_GETTOOLTIPS:
10050 if( !infoPtr->hwndToolTip )
10051 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
10052 return (LRESULT)infoPtr->hwndToolTip;
10054 case LVM_GETTOPINDEX:
10055 return LISTVIEW_GetTopIndex(infoPtr);
10057 case LVM_GETUNICODEFORMAT:
10058 return (infoPtr->notifyFormat == NFR_UNICODE);
10060 /* case LVM_GETVIEW: */
10062 case LVM_GETVIEWRECT:
10063 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
10065 case LVM_GETWORKAREAS:
10066 FIXME("LVM_GETWORKAREAS: unimplemented\n");
10069 /* case LVM_HASGROUP: */
10072 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, TRUE);
10074 case LVM_INSERTCOLUMNA:
10075 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
10077 case LVM_INSERTCOLUMNW:
10078 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
10080 /* case LVM_INSERTGROUP: */
10082 /* case LVM_INSERTGROUPSORTED: */
10084 case LVM_INSERTITEMA:
10085 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
10087 case LVM_INSERTITEMW:
10088 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
10090 /* case LVM_INSERTMARKHITTEST: */
10092 /* case LVM_ISGROUPVIEWENABLED: */
10094 /* case LVM_MAPIDTOINDEX: */
10096 /* case LVM_MAPINDEXTOID: */
10098 /* case LVM_MOVEGROUP: */
10100 /* case LVM_MOVEITEMTOGROUP: */
10102 case LVM_REDRAWITEMS:
10103 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
10105 /* case LVM_REMOVEALLGROUPS: */
10107 /* case LVM_REMOVEGROUP: */
10110 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
10112 case LVM_SETBKCOLOR:
10113 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
10115 /* case LVM_SETBKIMAGE: */
10117 case LVM_SETCALLBACKMASK:
10118 infoPtr->uCallbackMask = (UINT)wParam;
10121 case LVM_SETCOLUMNA:
10122 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
10124 case LVM_SETCOLUMNW:
10125 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
10127 case LVM_SETCOLUMNORDERARRAY:
10128 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
10130 case LVM_SETCOLUMNWIDTH:
10131 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
10133 case LVM_SETEXTENDEDLISTVIEWSTYLE:
10134 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
10136 /* case LVM_SETGROUPINFO: */
10138 /* case LVM_SETGROUPMETRICS: */
10140 case LVM_SETHOTCURSOR:
10141 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
10143 case LVM_SETHOTITEM:
10144 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
10146 case LVM_SETHOVERTIME:
10147 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
10149 case LVM_SETICONSPACING:
10150 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10152 case LVM_SETIMAGELIST:
10153 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
10155 /* case LVM_SETINFOTIP: */
10157 /* case LVM_SETINSERTMARK: */
10159 /* case LVM_SETINSERTMARKCOLOR: */
10164 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
10165 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, (uMsg == LVM_SETITEMW));
10168 case LVM_SETITEMCOUNT:
10169 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
10171 case LVM_SETITEMPOSITION:
10174 pt.x = (short)LOWORD(lParam);
10175 pt.y = (short)HIWORD(lParam);
10176 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
10179 case LVM_SETITEMPOSITION32:
10180 if (lParam == 0) return FALSE;
10181 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
10183 case LVM_SETITEMSTATE:
10184 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
10186 case LVM_SETITEMTEXTA:
10187 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
10189 case LVM_SETITEMTEXTW:
10190 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
10192 /* case LVM_SETOUTLINECOLOR: */
10194 /* case LVM_SETSELECTEDCOLUMN: */
10196 case LVM_SETSELECTIONMARK:
10197 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
10199 case LVM_SETTEXTBKCOLOR:
10200 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
10202 case LVM_SETTEXTCOLOR:
10203 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
10205 /* case LVM_SETTILEINFO: */
10207 /* case LVM_SETTILEVIEWINFO: */
10209 /* case LVM_SETTILEWIDTH: */
10211 case LVM_SETTOOLTIPS:
10212 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
10214 case LVM_SETUNICODEFORMAT:
10215 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
10217 /* case LVM_SETVIEW: */
10219 /* case LVM_SETWORKAREAS: */
10221 /* case LVM_SORTGROUPS: */
10223 case LVM_SORTITEMS:
10224 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam, FALSE);
10226 case LVM_SORTITEMSEX:
10227 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam, TRUE);
10229 case LVM_SUBITEMHITTEST:
10230 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
10233 return LISTVIEW_Update(infoPtr, (INT)wParam);
10235 case CCM_GETVERSION:
10236 return LISTVIEW_GetVersion(infoPtr);
10238 case CCM_SETVERSION:
10239 return LISTVIEW_SetVersion(infoPtr, wParam);
10242 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
10245 return LISTVIEW_Command(infoPtr, wParam, lParam);
10248 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
10251 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
10254 return LISTVIEW_Destroy(infoPtr);
10257 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
10259 case WM_ERASEBKGND:
10260 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
10262 case WM_GETDLGCODE:
10263 return DLGC_WANTCHARS | DLGC_WANTARROWS;
10266 return (LRESULT)infoPtr->hFont;
10269 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10272 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
10275 return LISTVIEW_KillFocus(infoPtr);
10277 case WM_LBUTTONDBLCLK:
10278 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10280 case WM_LBUTTONDOWN:
10281 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10284 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10287 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10289 case WM_MOUSEHOVER:
10290 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10293 return LISTVIEW_NCDestroy(infoPtr);
10296 if (LISTVIEW_NCPaint(infoPtr, (HRGN)wParam))
10301 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
10302 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
10305 case WM_NOTIFYFORMAT:
10306 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
10308 case WM_PRINTCLIENT:
10309 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
10312 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
10314 case WM_RBUTTONDBLCLK:
10315 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10317 case WM_RBUTTONDOWN:
10318 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10321 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10324 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
10329 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
10332 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
10335 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
10337 case WM_SHOWWINDOW:
10338 LISTVIEW_ShowWindow(infoPtr, (BOOL)wParam, (INT)lParam);
10339 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10342 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10344 case WM_STYLECHANGED:
10345 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10347 case WM_STYLECHANGING:
10348 return LISTVIEW_StyleChanging(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10350 case WM_SYSCOLORCHANGE:
10351 COMCTL32_RefreshSysColors();
10354 /* case WM_TIMER: */
10355 case WM_THEMECHANGED:
10356 return LISTVIEW_ThemeChanged(infoPtr);
10359 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10361 case WM_MOUSEWHEEL:
10362 if (wParam & (MK_SHIFT | MK_CONTROL))
10363 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10364 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
10366 case WM_WINDOWPOSCHANGED:
10367 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
10369 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
10370 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
10371 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
10373 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
10375 MEASUREITEMSTRUCT mis;
10376 mis.CtlType = ODT_LISTVIEW;
10377 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
10381 mis.itemHeight= infoPtr->nItemHeight;
10382 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
10383 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
10384 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
10387 LISTVIEW_UpdateSize(infoPtr);
10388 LISTVIEW_UpdateScroll(infoPtr);
10390 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10392 /* case WM_WININICHANGE: */
10395 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
10396 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
10399 /* call default window procedure */
10400 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10407 * Registers the window class.
10415 void LISTVIEW_Register(void)
10417 WNDCLASSW wndClass;
10419 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
10420 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
10421 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
10422 wndClass.cbClsExtra = 0;
10423 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
10424 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
10425 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
10426 wndClass.lpszClassName = WC_LISTVIEWW;
10427 RegisterClassW(&wndClass);
10432 * Unregisters the window class.
10440 void LISTVIEW_Unregister(void)
10442 UnregisterClassW(WC_LISTVIEWW, NULL);
10447 * Handle any WM_COMMAND messages
10450 * [I] infoPtr : valid pointer to the listview structure
10451 * [I] wParam : the first message parameter
10452 * [I] lParam : the second message parameter
10457 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10459 switch (HIWORD(wParam))
10464 * Adjust the edit window size
10466 WCHAR buffer[1024];
10467 HDC hdc = GetDC(infoPtr->hwndEdit);
10468 HFONT hFont, hOldFont = 0;
10472 if (!infoPtr->hwndEdit || !hdc) return 0;
10473 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
10474 GetWindowRect(infoPtr->hwndEdit, &rect);
10476 /* Select font to get the right dimension of the string */
10477 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
10480 hOldFont = SelectObject(hdc, hFont);
10483 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
10485 TEXTMETRICW textMetric;
10487 /* Add Extra spacing for the next character */
10488 GetTextMetricsW(hdc, &textMetric);
10489 sz.cx += (textMetric.tmMaxCharWidth * 2);
10497 rect.bottom - rect.top,
10498 SWP_DRAWFRAME|SWP_NOMOVE);
10501 SelectObject(hdc, hOldFont);
10503 ReleaseDC(infoPtr->hwndEdit, hdc);
10509 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
10518 * Subclassed edit control windproc function
10521 * [I] hwnd : the edit window handle
10522 * [I] uMsg : the message that is to be processed
10523 * [I] wParam : first message parameter
10524 * [I] lParam : second message parameter
10525 * [I] isW : TRUE if input is Unicode
10530 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
10532 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
10533 BOOL cancel = FALSE;
10535 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
10536 hwnd, uMsg, wParam, lParam, isW);
10540 case WM_GETDLGCODE:
10541 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
10548 WNDPROC editProc = infoPtr->EditWndProc;
10549 infoPtr->EditWndProc = 0;
10550 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
10551 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
10555 if (VK_ESCAPE == (INT)wParam)
10560 else if (VK_RETURN == (INT)wParam)
10564 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
10567 /* kill the edit */
10568 if (infoPtr->hwndEdit)
10570 LPWSTR buffer = NULL;
10572 infoPtr->hwndEdit = 0;
10575 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
10579 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
10581 if (isW) GetWindowTextW(hwnd, buffer, len+1);
10582 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
10586 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
10591 SendMessageW(hwnd, WM_CLOSE, 0, 0);
10597 * Subclassed edit control Unicode windproc function
10600 * [I] hwnd : the edit window handle
10601 * [I] uMsg : the message that is to be processed
10602 * [I] wParam : first message parameter
10603 * [I] lParam : second message parameter
10607 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10609 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
10614 * Subclassed edit control ANSI windproc function
10617 * [I] hwnd : the edit window handle
10618 * [I] uMsg : the message that is to be processed
10619 * [I] wParam : first message parameter
10620 * [I] lParam : second message parameter
10624 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10626 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
10631 * Creates a subclassed edit control
10634 * [I] infoPtr : valid pointer to the listview structure
10635 * [I] text : initial text for the edit
10636 * [I] style : the window style
10637 * [I] isW : TRUE if input is Unicode
10641 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
10642 INT x, INT y, INT width, INT height, BOOL isW)
10644 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
10649 TEXTMETRICW textMetric;
10650 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
10652 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
10654 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
10655 hdc = GetDC(infoPtr->hwndSelf);
10657 /* Select the font to get appropriate metric dimensions */
10658 if(infoPtr->hFont != 0)
10659 hOldFont = SelectObject(hdc, infoPtr->hFont);
10661 /*Get String Length in pixels */
10662 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
10664 /*Add Extra spacing for the next character */
10665 GetTextMetricsW(hdc, &textMetric);
10666 sz.cx += (textMetric.tmMaxCharWidth * 2);
10668 if(infoPtr->hFont != 0)
10669 SelectObject(hdc, hOldFont);
10671 ReleaseDC(infoPtr->hwndSelf, hdc);
10673 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10675 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10677 if (!hedit) return 0;
10679 infoPtr->EditWndProc = (WNDPROC)
10680 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
10681 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
10683 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);