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 * -- WM_CREATE: create the icon and small icon image lists at this point only if
37 * the LVS_SHAREIMAGELISTS style is not specified.
38 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
39 * or small icon and the LVS_AUTOARRANGE style is specified.
44 * -- Hot item handling, mouse hovering
45 * -- Workareas support
50 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
51 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
52 * -- LVA_SNAPTOGRID not implemented
53 * -- LISTVIEW_ApproximateViewRect partially implemented
54 * -- LISTVIEW_[GS]etColumnOrderArray stubs
55 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
56 * -- LISTVIEW_SetIconSpacing is incomplete
57 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
60 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
61 * linear in the number of items in the list, and this is
62 * unacceptable for large lists.
63 * -- if list is sorted by item text LISTVIEW_InsertItemT could use
64 * binary search to calculate item index (e.g. DPA_Search()).
65 * This requires sorted state to be reliably tracked in item modifiers.
66 * -- we should keep an ordered array of coordinates in iconic mode
67 * this would allow to frame items (iterator_frameditems),
68 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
75 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
82 * -- LVS_NOSCROLL (see Q137520)
86 * -- LVS_EX_BORDERSELECT
88 * -- LVS_EX_HEADERDRAGDROP
91 * -- LVS_EX_MULTIWORKAREAS
93 * -- LVS_EX_SIMPLESELECT
94 * -- LVS_EX_TWOCLICKACTIVATE
95 * -- LVS_EX_UNDERLINECOLD
96 * -- LVS_EX_UNDERLINEHOT
99 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
102 * -- LVN_MARQUEEBEGIN
108 * -- LVM_CANCELEDITLABEL
109 * -- LVM_ENABLEGROUPVIEW
110 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
111 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
112 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
113 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
114 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
115 * -- LVM_GETINSERTMARKRECT
116 * -- LVM_GETNUMBEROFWORKAREAS
117 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
118 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
119 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
120 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
121 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
122 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
123 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
124 * -- LVM_INSERTGROUPSORTED
125 * -- LVM_INSERTMARKHITTEST
126 * -- LVM_ISGROUPVIEWENABLED
127 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
129 * -- LVM_MOVEITEMTOGROUP
131 * -- LVM_SETTILEWIDTH
135 * -- ListView_GetHoverTime, ListView_SetHoverTime
136 * -- ListView_GetISearchString
137 * -- ListView_GetNumberOfWorkAreas
138 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
143 * Known differences in message stream from native control (not known if
144 * these differences cause problems):
145 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
146 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
147 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
148 * processing for "USEDOUBLECLICKTIME".
152 #include "wine/port.h"
167 #include "commctrl.h"
168 #include "comctl32.h"
171 #include "wine/debug.h"
172 #include "wine/unicode.h"
174 WINE_DEFAULT_DEBUG_CHANNEL(listview);
176 /* make sure you set this to 0 for production use! */
177 #define DEBUG_RANGES 1
179 typedef struct tagCOLUMN_INFO
181 RECT rcHeader; /* tracks the header's rectangle */
182 int fmt; /* same as LVCOLUMN.fmt */
185 typedef struct tagITEMHDR
189 } ITEMHDR, *LPITEMHDR;
191 typedef struct tagSUBITEM_INFO
197 typedef struct tagITEM_INFO
205 typedef struct tagRANGE
211 typedef struct tagRANGES
216 typedef struct tagITERATOR
225 typedef struct tagDELAYED_ITEM_EDIT
231 typedef struct tagLISTVIEW_INFO
238 HIMAGELIST himlNormal;
239 HIMAGELIST himlSmall;
240 HIMAGELIST himlState;
244 POINT ptClickPos; /* point where the user clicked */
245 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
248 RANGES selectionRanges;
253 RECT rcList; /* This rectangle is really the window
254 * client rectangle possibly reduced by the
255 * horizontal scroll bar and/or header - see
256 * LISTVIEW_UpdateSize. This rectangle offset
257 * by the LISTVIEW_GetOrigin value is in
258 * client coordinates */
267 INT ntmHeight; /* Some cached metrics of the font used */
268 INT ntmMaxCharWidth; /* by the listview to draw items */
270 BOOL bRedraw; /* Turns on/off repaints & invalidations */
271 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
273 BOOL bDoChangeNotify; /* send change notification messages? */
276 DWORD dwStyle; /* the cached window GWL_STYLE */
277 DWORD dwLvExStyle; /* extended listview style */
278 DWORD uView; /* current view available through LVM_[G,S]ETVIEW */
279 INT nItemCount; /* the number of items in the list */
280 HDPA hdpaItems; /* array ITEM_INFO pointers */
281 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
282 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
283 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
284 POINT currIconPos; /* this is the position next icon will be placed */
285 PFNLVCOMPARE pfnCompare;
290 INT nLButtonDownItem; /* tracks item to reset multiselection on WM_LBUTTONUP */
294 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
296 DWORD lastKeyPressTimestamp;
298 INT nSearchParamLength;
299 WCHAR szSearchParam[ MAX_PATH ];
301 INT nMeasureItemHeight;
302 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
303 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
305 DWORD iVersion; /* CCM_[G,S]ETVERSION */
311 /* How many we debug buffer to allocate */
312 #define DEBUG_BUFFERS 20
313 /* The size of a single debug bbuffer */
314 #define DEBUG_BUFFER_SIZE 256
316 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
317 #define SB_INTERNAL -1
319 /* maximum size of a label */
320 #define DISP_TEXT_SIZE 512
322 /* padding for items in list and small icon display modes */
323 #define WIDTH_PADDING 12
325 /* padding for items in list, report and small icon display modes */
326 #define HEIGHT_PADDING 1
328 /* offset of items in report display mode */
329 #define REPORT_MARGINX 2
331 /* padding for icon in large icon display mode
332 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
333 * that HITTEST will see.
334 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
335 * ICON_TOP_PADDING - sum of the two above.
336 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
337 * LABEL_HOR_PADDING - between text and sides of box
338 * LABEL_VERT_PADDING - between bottom of text and end of box
340 * ICON_LR_PADDING - additional width above icon size.
341 * ICON_LR_HALF - half of the above value
343 #define ICON_TOP_PADDING_NOTHITABLE 2
344 #define ICON_TOP_PADDING_HITABLE 2
345 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
346 #define ICON_BOTTOM_PADDING 4
347 #define LABEL_HOR_PADDING 5
348 #define LABEL_VERT_PADDING 7
349 #define ICON_LR_PADDING 16
350 #define ICON_LR_HALF (ICON_LR_PADDING/2)
352 /* default label width for items in list and small icon display modes */
353 #define DEFAULT_LABEL_WIDTH 40
355 /* default column width for items in list display mode */
356 #define DEFAULT_COLUMN_WIDTH 128
358 /* Size of "line" scroll for V & H scrolls */
359 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
361 /* Padding between image and label */
362 #define IMAGE_PADDING 2
364 /* Padding behind the label */
365 #define TRAILING_LABEL_PADDING 12
366 #define TRAILING_HEADER_PADDING 11
368 /* Border for the icon caption */
369 #define CAPTION_BORDER 2
371 /* Standard DrawText flags */
372 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
373 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
374 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
376 /* Image index from state */
377 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
379 /* The time in milliseconds to reset the search in the list */
380 #define KEY_DELAY 450
382 /* Dump the LISTVIEW_INFO structure to the debug channel */
383 #define LISTVIEW_DUMP(iP) do { \
384 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
385 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
386 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
387 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
388 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
389 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
390 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
391 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
392 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
393 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
396 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
399 * forward declarations
401 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
402 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
403 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
404 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
405 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
406 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *, INT);
407 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
408 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
409 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
410 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
411 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
412 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
413 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
414 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
415 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
416 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
417 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
418 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, BOOL);
419 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
420 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
422 /******** Text handling functions *************************************/
424 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
425 * text string. The string may be ANSI or Unicode, in which case
426 * the boolean isW tells us the type of the string.
428 * The name of the function tell what type of strings it expects:
429 * W: Unicode, T: ANSI/Unicode - function of isW
432 static inline BOOL is_textW(LPCWSTR text)
434 return text != NULL && text != LPSTR_TEXTCALLBACKW;
437 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
439 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
440 return is_textW(text);
443 static inline int textlenT(LPCWSTR text, BOOL isW)
445 return !is_textT(text, isW) ? 0 :
446 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
449 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
452 if (isSrcW) lstrcpynW(dest, src, max);
453 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
455 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
456 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
459 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
461 LPWSTR wstr = (LPWSTR)text;
463 if (!isW && is_textT(text, isW))
465 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
466 wstr = Alloc(len * sizeof(WCHAR));
467 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
469 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
473 static inline void textfreeT(LPWSTR wstr, BOOL isW)
475 if (!isW && is_textT(wstr, isW)) Free (wstr);
479 * dest is a pointer to a Unicode string
480 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
482 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
486 if (src == LPSTR_TEXTCALLBACKW)
488 if (is_textW(*dest)) Free(*dest);
489 *dest = LPSTR_TEXTCALLBACKW;
493 LPWSTR pszText = textdupTtoW(src, isW);
494 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
495 bResult = Str_SetPtrW(dest, pszText);
496 textfreeT(pszText, isW);
502 * compares a Unicode to a Unicode/ANSI text string
504 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
506 if (!aw) return bt ? -1 : 0;
507 if (!bt) return aw ? 1 : 0;
508 if (aw == LPSTR_TEXTCALLBACKW)
509 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
510 if (bt != LPSTR_TEXTCALLBACKW)
512 LPWSTR bw = textdupTtoW(bt, isW);
513 int r = bw ? lstrcmpW(aw, bw) : 1;
521 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
525 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
526 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
527 return res ? res - sizeof(WCHAR) : res;
530 /******** Debugging functions *****************************************/
532 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
534 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
535 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
538 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
540 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
541 n = min(textlenT(text, isW), n);
542 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
545 static char* debug_getbuf(void)
547 static int index = 0;
548 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
549 return buffers[index++ % DEBUG_BUFFERS];
552 static inline const char* debugrange(const RANGE *lprng)
554 if (!lprng) return "(null)";
555 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
558 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
560 char* buf = debug_getbuf(), *text = buf;
561 int len, size = DEBUG_BUFFER_SIZE;
563 if (pScrollInfo == NULL) return "(null)";
564 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
565 if (len == -1) goto end; buf += len; size -= len;
566 if (pScrollInfo->fMask & SIF_RANGE)
567 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
569 if (len == -1) goto end; buf += len; size -= len;
570 if (pScrollInfo->fMask & SIF_PAGE)
571 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
573 if (len == -1) goto end; buf += len; size -= len;
574 if (pScrollInfo->fMask & SIF_POS)
575 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
577 if (len == -1) goto end; buf += len; size -= len;
578 if (pScrollInfo->fMask & SIF_TRACKPOS)
579 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
581 if (len == -1) goto end; buf += len; size -= len;
584 buf = text + strlen(text);
586 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
590 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
592 if (!plvnm) return "(null)";
593 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
594 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
595 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
596 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
599 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
601 char* buf = debug_getbuf(), *text = buf;
602 int len, size = DEBUG_BUFFER_SIZE;
604 if (lpLVItem == NULL) return "(null)";
605 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
606 if (len == -1) goto end; buf += len; size -= len;
607 if (lpLVItem->mask & LVIF_STATE)
608 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
610 if (len == -1) goto end; buf += len; size -= len;
611 if (lpLVItem->mask & LVIF_TEXT)
612 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
614 if (len == -1) goto end; buf += len; size -= len;
615 if (lpLVItem->mask & LVIF_IMAGE)
616 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
618 if (len == -1) goto end; buf += len; size -= len;
619 if (lpLVItem->mask & LVIF_PARAM)
620 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
622 if (len == -1) goto end; buf += len; size -= len;
623 if (lpLVItem->mask & LVIF_INDENT)
624 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
626 if (len == -1) goto end; buf += len; size -= len;
629 buf = text + strlen(text);
631 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
635 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
637 char* buf = debug_getbuf(), *text = buf;
638 int len, size = DEBUG_BUFFER_SIZE;
640 if (lpColumn == NULL) return "(null)";
641 len = snprintf(buf, size, "{");
642 if (len == -1) goto end; buf += len; size -= len;
643 if (lpColumn->mask & LVCF_SUBITEM)
644 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
646 if (len == -1) goto end; buf += len; size -= len;
647 if (lpColumn->mask & LVCF_FMT)
648 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
650 if (len == -1) goto end; buf += len; size -= len;
651 if (lpColumn->mask & LVCF_WIDTH)
652 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
654 if (len == -1) goto end; buf += len; size -= len;
655 if (lpColumn->mask & LVCF_TEXT)
656 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
658 if (len == -1) goto end; buf += len; size -= len;
659 if (lpColumn->mask & LVCF_IMAGE)
660 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
662 if (len == -1) goto end; buf += len; size -= len;
663 if (lpColumn->mask & LVCF_ORDER)
664 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
666 if (len == -1) goto end; buf += len; size -= len;
669 buf = text + strlen(text);
671 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
675 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
677 if (!lpht) return "(null)";
679 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
680 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
683 /* Return the corresponding text for a given scroll value */
684 static inline LPCSTR debugscrollcode(int nScrollCode)
688 case SB_LINELEFT: return "SB_LINELEFT";
689 case SB_LINERIGHT: return "SB_LINERIGHT";
690 case SB_PAGELEFT: return "SB_PAGELEFT";
691 case SB_PAGERIGHT: return "SB_PAGERIGHT";
692 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
693 case SB_THUMBTRACK: return "SB_THUMBTRACK";
694 case SB_ENDSCROLL: return "SB_ENDSCROLL";
695 case SB_INTERNAL: return "SB_INTERNAL";
696 default: return "unknown";
701 /******** Notification functions ************************************/
703 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
705 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
706 (WPARAM)lpnmh->hdr.idFrom, (LPARAM)lpnmh);
709 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
713 TRACE("(code=%d)\n", code);
715 pnmh->hwndFrom = infoPtr->hwndSelf;
716 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
718 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
720 TRACE(" <= %ld\n", result);
725 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
728 HWND hwnd = infoPtr->hwndSelf;
729 notify_hdr(infoPtr, code, &nmh);
730 return IsWindow(hwnd);
733 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
744 item.mask = LVIF_PARAM|LVIF_STATE;
745 item.iItem = htInfo->iItem;
747 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
748 nmia.lParam = item.lParam;
749 nmia.uOldState = item.state;
750 nmia.uNewState = item.state | LVIS_ACTIVATING;
751 nmia.uChanged = LVIF_STATE;
754 nmia.iItem = htInfo->iItem;
755 nmia.iSubItem = htInfo->iSubItem;
756 nmia.ptAction = htInfo->pt;
758 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
759 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
760 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
762 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
765 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
767 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
768 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
771 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
775 HWND hwnd = infoPtr->hwndSelf;
777 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
778 ZeroMemory(&nmia, sizeof(nmia));
779 nmia.iItem = lvht->iItem;
780 nmia.iSubItem = lvht->iSubItem;
781 nmia.ptAction = lvht->pt;
782 item.mask = LVIF_PARAM;
783 item.iItem = lvht->iItem;
785 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmia.lParam = item.lParam;
786 notify_hdr(infoPtr, code, (LPNMHDR)&nmia);
787 return IsWindow(hwnd);
790 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
794 HWND hwnd = infoPtr->hwndSelf;
796 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
798 item.mask = LVIF_PARAM;
801 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
802 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
803 return IsWindow(hwnd);
806 static int get_ansi_notification(UINT unicodeNotificationCode)
808 switch (unicodeNotificationCode)
810 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
811 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
812 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
813 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
814 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
815 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
817 ERR("unknown notification %x\n", unicodeNotificationCode);
823 Send notification. depends on dispinfoW having same
824 structure as dispinfoA.
825 infoPtr : listview struct
826 notificationCode : *Unicode* notification code
827 pdi : dispinfo structure (can be unicode or ansi)
828 isW : TRUE if dispinfo is Unicode
830 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
832 BOOL bResult = FALSE;
833 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
834 INT cchTempBufMax = 0, savCchTextMax = 0;
836 LPWSTR pszTempBuf = NULL, savPszText = NULL;
838 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
840 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
841 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
844 if (convertToAnsi || convertToUnicode)
846 if (notificationCode != LVN_GETDISPINFOW)
848 cchTempBufMax = convertToUnicode ?
849 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
850 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
854 cchTempBufMax = pdi->item.cchTextMax;
855 *pdi->item.pszText = 0; /* make sure we don't process garbage */
858 pszTempBuf = Alloc( (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
859 if (!pszTempBuf) return FALSE;
861 if (convertToUnicode)
862 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
863 pszTempBuf, cchTempBufMax);
865 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
866 cchTempBufMax, NULL, NULL);
868 savCchTextMax = pdi->item.cchTextMax;
869 savPszText = pdi->item.pszText;
870 pdi->item.pszText = pszTempBuf;
871 pdi->item.cchTextMax = cchTempBufMax;
874 if (infoPtr->notifyFormat == NFR_ANSI)
875 realNotifCode = get_ansi_notification(notificationCode);
877 realNotifCode = notificationCode;
878 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
879 bResult = notify_hdr(infoPtr, realNotifCode, &pdi->hdr);
881 if (convertToUnicode || convertToAnsi)
883 if (convertToUnicode) /* note : pointer can be changed by app ! */
884 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
885 savCchTextMax, NULL, NULL);
887 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
888 savPszText, savCchTextMax);
889 pdi->item.pszText = savPszText; /* restores our buffer */
890 pdi->item.cchTextMax = savCchTextMax;
896 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
897 const RECT *rcBounds, const LVITEMW *lplvItem)
899 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
900 lpnmlvcd->nmcd.hdc = hdc;
901 lpnmlvcd->nmcd.rc = *rcBounds;
902 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
903 lpnmlvcd->clrText = infoPtr->clrText;
904 if (!lplvItem) return;
905 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
906 lpnmlvcd->iSubItem = lplvItem->iSubItem;
907 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
908 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
909 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
910 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
913 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
915 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
918 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
919 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
920 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
921 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
922 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
923 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
927 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
929 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
930 lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
931 if (lpnmlvcd->clrText == CLR_DEFAULT)
932 lpnmlvcd->clrText = comctl32_color.clrWindowText;
934 /* apparently, for selected items, we have to override the returned values */
937 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
941 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
942 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
944 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
946 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
947 lpnmlvcd->clrText = comctl32_color.clrBtnText;
952 /* Set the text attributes */
953 if (lpnmlvcd->clrTextBk != CLR_NONE)
955 SetBkMode(hdc, OPAQUE);
956 SetBkColor(hdc,lpnmlvcd->clrTextBk);
959 SetBkMode(hdc, TRANSPARENT);
960 SetTextColor(hdc, lpnmlvcd->clrText);
963 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
965 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
968 /******** Item iterator functions **********************************/
970 static RANGES ranges_create(int count);
971 static void ranges_destroy(RANGES ranges);
972 static BOOL ranges_add(RANGES ranges, RANGE range);
973 static BOOL ranges_del(RANGES ranges, RANGE range);
974 static void ranges_dump(RANGES ranges);
976 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
978 RANGE range = { nItem, nItem + 1 };
980 return ranges_add(ranges, range);
983 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
985 RANGE range = { nItem, nItem + 1 };
987 return ranges_del(ranges, range);
991 * ITERATOR DOCUMENTATION
993 * The iterator functions allow for easy, and convenient iteration
994 * over items of interest in the list. Typically, you create a
995 * iterator, use it, and destroy it, as such:
998 * iterator_xxxitems(&i, ...);
999 * while (iterator_{prev,next}(&i)
1001 * //code which uses i.nItem
1003 * iterator_destroy(&i);
1005 * where xxx is either: framed, or visible.
1006 * Note that it is important that the code destroys the iterator
1007 * after it's done with it, as the creation of the iterator may
1008 * allocate memory, which thus needs to be freed.
1010 * You can iterate both forwards, and backwards through the list,
1011 * by using iterator_next or iterator_prev respectively.
1013 * Lower numbered items are draw on top of higher number items in
1014 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1015 * items may overlap). So, to test items, you should use
1017 * which lists the items top to bottom (in Z-order).
1018 * For drawing items, you should use
1020 * which lists the items bottom to top (in Z-order).
1021 * If you keep iterating over the items after the end-of-items
1022 * marker (-1) is returned, the iterator will start from the
1023 * beginning. Typically, you don't need to test for -1,
1024 * because iterator_{next,prev} will return TRUE if more items
1025 * are to be iterated over, or FALSE otherwise.
1027 * Note: the iterator is defined to be bidirectional. That is,
1028 * any number of prev followed by any number of next, or
1029 * five versa, should leave the iterator at the same item:
1030 * prev * n, next * n = next * n, prev * n
1032 * The iterator has a notion of an out-of-order, special item,
1033 * which sits at the start of the list. This is used in
1034 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1035 * which needs to be first, as it may overlap other items.
1037 * The code is a bit messy because we have:
1038 * - a special item to deal with
1039 * - simple range, or composite range
1041 * If you find bugs, or want to add features, please make sure you
1042 * always check/modify *both* iterator_prev, and iterator_next.
1046 * This function iterates through the items in increasing order,
1047 * but prefixed by the special item, then -1. That is:
1048 * special, 1, 2, 3, ..., n, -1.
1049 * Each item is listed only once.
1051 static inline BOOL iterator_next(ITERATOR* i)
1055 i->nItem = i->nSpecial;
1056 if (i->nItem != -1) return TRUE;
1058 if (i->nItem == i->nSpecial)
1060 if (i->ranges) i->index = 0;
1066 if (i->nItem == i->nSpecial) i->nItem++;
1067 if (i->nItem < i->range.upper) return TRUE;
1072 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1073 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1076 else if (i->nItem >= i->range.upper) goto end;
1078 i->nItem = i->range.lower;
1079 if (i->nItem >= 0) goto testitem;
1086 * This function iterates through the items in decreasing order,
1087 * followed by the special item, then -1. That is:
1088 * n, n-1, ..., 3, 2, 1, special, -1.
1089 * Each item is listed only once.
1091 static inline BOOL iterator_prev(ITERATOR* i)
1098 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1101 if (i->nItem == i->nSpecial)
1109 if (i->nItem == i->nSpecial) i->nItem--;
1110 if (i->nItem >= i->range.lower) return TRUE;
1116 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1119 else if (!start && i->nItem < i->range.lower) goto end;
1121 i->nItem = i->range.upper;
1122 if (i->nItem > 0) goto testitem;
1124 return (i->nItem = i->nSpecial) != -1;
1127 static RANGE iterator_range(const ITERATOR *i)
1131 if (!i->ranges) return i->range;
1133 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1135 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1136 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1138 else range.lower = range.upper = 0;
1144 * Releases resources associated with this ierator.
1146 static inline void iterator_destroy(const ITERATOR *i)
1148 ranges_destroy(i->ranges);
1152 * Create an empty iterator.
1154 static inline BOOL iterator_empty(ITERATOR* i)
1156 ZeroMemory(i, sizeof(*i));
1157 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1162 * Create an iterator over a range.
1164 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1172 * Create an iterator over a bunch of ranges.
1173 * Please note that the iterator will take ownership of the ranges,
1174 * and will free them upon destruction.
1176 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1184 * Creates an iterator over the items which intersect lprc.
1186 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1188 RECT frame = *lprc, rcItem, rcTemp;
1191 /* in case we fail, we want to return an empty iterator */
1192 if (!iterator_empty(i)) return FALSE;
1194 LISTVIEW_GetOrigin(infoPtr, &Origin);
1196 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1197 OffsetRect(&frame, -Origin.x, -Origin.y);
1199 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
1203 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->nFocusedItem != -1)
1205 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1206 if (IntersectRect(&rcTemp, &rcItem, lprc))
1207 i->nSpecial = infoPtr->nFocusedItem;
1209 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1210 /* to do better here, we need to have PosX, and PosY sorted */
1211 TRACE("building icon ranges:\n");
1212 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1214 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1215 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1216 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1217 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1218 if (IntersectRect(&rcTemp, &rcItem, &frame))
1219 ranges_additem(i->ranges, nItem);
1223 else if (infoPtr->uView == LV_VIEW_DETAILS)
1227 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1228 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1230 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1231 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1232 if (range.upper <= range.lower) return TRUE;
1233 if (!iterator_rangeitems(i, range)) return FALSE;
1234 TRACE(" report=%s\n", debugrange(&i->range));
1238 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1239 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1240 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1241 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1242 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1243 INT lower = nFirstCol * nPerCol + nFirstRow;
1247 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1248 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1250 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1252 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1253 TRACE("building list ranges:\n");
1254 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1256 item_range.lower = nCol * nPerCol + nFirstRow;
1257 if(item_range.lower >= infoPtr->nItemCount) break;
1258 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1259 TRACE(" list=%s\n", debugrange(&item_range));
1260 ranges_add(i->ranges, item_range);
1268 * Creates an iterator over the items which intersect the visible region of hdc.
1270 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1272 POINT Origin, Position;
1273 RECT rcItem, rcClip;
1276 rgntype = GetClipBox(hdc, &rcClip);
1277 if (rgntype == NULLREGION) return iterator_empty(i);
1278 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1279 if (rgntype == SIMPLEREGION) return TRUE;
1281 /* first deal with the special item */
1282 if (i->nSpecial != -1)
1284 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1285 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1288 /* if we can't deal with the region, we'll just go with the simple range */
1289 LISTVIEW_GetOrigin(infoPtr, &Origin);
1290 TRACE("building visible range:\n");
1291 if (!i->ranges && i->range.lower < i->range.upper)
1293 if (!(i->ranges = ranges_create(50))) return TRUE;
1294 if (!ranges_add(i->ranges, i->range))
1296 ranges_destroy(i->ranges);
1302 /* now delete the invisible items from the list */
1303 while(iterator_next(i))
1305 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1306 rcItem.left = Position.x + Origin.x;
1307 rcItem.top = Position.y + Origin.y;
1308 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1309 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1310 if (!RectVisible(hdc, &rcItem))
1311 ranges_delitem(i->ranges, i->nItem);
1313 /* the iterator should restart on the next iterator_next */
1319 /******** Misc helper functions ************************************/
1321 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1322 WPARAM wParam, LPARAM lParam, BOOL isW)
1324 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1325 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1328 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1330 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1331 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON);
1334 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1336 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1337 if(state == 1 || state == 2)
1341 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1342 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1343 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1347 /* this should be called after window style got updated,
1348 it used to reset view state to match current window style */
1349 static inline void map_style_view(LISTVIEW_INFO *infoPtr)
1351 switch (infoPtr->dwStyle & LVS_TYPEMASK)
1354 infoPtr->uView = LV_VIEW_ICON;
1357 infoPtr->uView = LV_VIEW_DETAILS;
1360 infoPtr->uView = LV_VIEW_SMALLICON;
1363 infoPtr->uView = LV_VIEW_LIST;
1367 /******** Internal API functions ************************************/
1369 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1371 static COLUMN_INFO mainItem;
1373 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1374 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1375 return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1378 static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr)
1380 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
1383 if (infoPtr->hwndHeader) return 0;
1385 TRACE("Creating header for list %p\n", infoPtr->hwndSelf);
1387 /* setup creation flags */
1388 dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
1389 dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
1391 hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
1394 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
1395 0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
1396 if (!infoPtr->hwndHeader) return -1;
1398 /* set header unicode format */
1399 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
1401 /* set header font */
1402 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
1404 LISTVIEW_UpdateSize(infoPtr);
1409 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1411 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1414 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1416 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1419 /* used to handle collapse main item column case */
1420 static inline BOOL LISTVIEW_DrawFocusRect(const LISTVIEW_INFO *infoPtr, HDC hdc)
1422 return (infoPtr->rcFocus.left < infoPtr->rcFocus.right) ?
1423 DrawFocusRect(hdc, &infoPtr->rcFocus) : FALSE;
1426 /* Listview invalidation functions: use _only_ these functions to invalidate */
1428 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1430 return infoPtr->bRedraw;
1433 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1435 if(!is_redrawing(infoPtr)) return;
1436 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1437 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1440 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1444 if(!is_redrawing(infoPtr)) return;
1445 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1446 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1449 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1451 POINT Origin, Position;
1454 if(!is_redrawing(infoPtr)) return;
1455 assert (infoPtr->uView == LV_VIEW_DETAILS);
1456 LISTVIEW_GetOrigin(infoPtr, &Origin);
1457 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1458 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1460 rcBox.bottom = infoPtr->nItemHeight;
1461 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1462 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1465 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1467 LISTVIEW_InvalidateRect(infoPtr, NULL);
1470 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1474 if(!is_redrawing(infoPtr)) return;
1475 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1476 rcCol.top = infoPtr->rcList.top;
1477 rcCol.bottom = infoPtr->rcList.bottom;
1478 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1483 * Retrieves the number of items that can fit vertically in the client area.
1486 * [I] infoPtr : valid pointer to the listview structure
1489 * Number of items per row.
1491 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1493 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1495 return max(nListWidth/infoPtr->nItemWidth, 1);
1500 * Retrieves the number of items that can fit horizontally in the client
1504 * [I] infoPtr : valid pointer to the listview structure
1507 * Number of items per column.
1509 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1511 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1513 return max(nListHeight / infoPtr->nItemHeight, 1);
1517 /*************************************************************************
1518 * LISTVIEW_ProcessLetterKeys
1520 * Processes keyboard messages generated by pressing the letter keys
1522 * What this does is perform a case insensitive search from the
1523 * current position with the following quirks:
1524 * - If two chars or more are pressed in quick succession we search
1525 * for the corresponding string (e.g. 'abc').
1526 * - If there is a delay we wipe away the current search string and
1527 * restart with just that char.
1528 * - If the user keeps pressing the same character, whether slowly or
1529 * fast, so that the search string is entirely composed of this
1530 * character ('aaaaa' for instance), then we search for first item
1531 * that starting with that character.
1532 * - If the user types the above character in quick succession, then
1533 * we must also search for the corresponding string ('aaaaa'), and
1534 * go to that string if there is a match.
1537 * [I] hwnd : handle to the window
1538 * [I] charCode : the character code, the actual character
1539 * [I] keyData : key data
1547 * - The current implementation has a list of characters it will
1548 * accept and it ignores everything else. In particular it will
1549 * ignore accentuated characters which seems to match what
1550 * Windows does. But I'm not sure it makes sense to follow
1552 * - We don't sound a beep when the search fails.
1556 * TREEVIEW_ProcessLetterKeys
1558 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1563 WCHAR buffer[MAX_PATH];
1564 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1566 /* simple parameter checking */
1567 if (!charCode || !keyData) return 0;
1569 /* only allow the valid WM_CHARs through */
1570 if (!isalnumW(charCode) &&
1571 charCode != '.' && charCode != '`' && charCode != '!' &&
1572 charCode != '@' && charCode != '#' && charCode != '$' &&
1573 charCode != '%' && charCode != '^' && charCode != '&' &&
1574 charCode != '*' && charCode != '(' && charCode != ')' &&
1575 charCode != '-' && charCode != '_' && charCode != '+' &&
1576 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1577 charCode != '}' && charCode != '[' && charCode != '{' &&
1578 charCode != '/' && charCode != '?' && charCode != '>' &&
1579 charCode != '<' && charCode != ',' && charCode != '~')
1582 /* if there's one item or less, there is no where to go */
1583 if (infoPtr->nItemCount <= 1) return 0;
1585 /* update the search parameters */
1586 infoPtr->lastKeyPressTimestamp = GetTickCount();
1587 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1588 if (infoPtr->nSearchParamLength < MAX_PATH-1)
1589 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1590 if (infoPtr->charCode != charCode)
1591 infoPtr->charCode = charCode = 0;
1593 infoPtr->charCode=charCode;
1594 infoPtr->szSearchParam[0]=charCode;
1595 infoPtr->nSearchParamLength=1;
1596 /* Redundant with the 1 char string */
1600 /* and search from the current position */
1602 if (infoPtr->nFocusedItem >= 0) {
1603 endidx=infoPtr->nFocusedItem;
1605 /* if looking for single character match,
1606 * then we must always move forward
1608 if (infoPtr->nSearchParamLength == 1)
1611 endidx=infoPtr->nItemCount;
1615 /* Let application handle this for virtual listview */
1616 if (infoPtr->dwStyle & LVS_OWNERDATA)
1621 ZeroMemory(&lvfi, sizeof(lvfi));
1622 lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1623 infoPtr->szSearchParam[infoPtr->nSearchParamLength] = '\0';
1624 lvfi.psz = infoPtr->szSearchParam;
1628 nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1631 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1637 if (idx == infoPtr->nItemCount) {
1638 if (endidx == infoPtr->nItemCount || endidx == 0)
1644 item.mask = LVIF_TEXT;
1647 item.pszText = buffer;
1648 item.cchTextMax = MAX_PATH;
1649 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1651 /* check for a match */
1652 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1655 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1656 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1657 /* This would work but we must keep looking for a longer match */
1661 } while (idx != endidx);
1664 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1669 /*************************************************************************
1670 * LISTVIEW_UpdateHeaderSize [Internal]
1672 * Function to resize the header control
1675 * [I] hwnd : handle to a window
1676 * [I] nNewScrollPos : scroll pos to set
1681 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1686 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1688 if (!infoPtr->hwndHeader) return;
1690 GetWindowRect(infoPtr->hwndHeader, &winRect);
1691 point[0].x = winRect.left;
1692 point[0].y = winRect.top;
1693 point[1].x = winRect.right;
1694 point[1].y = winRect.bottom;
1696 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1697 point[0].x = -nNewScrollPos;
1698 point[1].x += nNewScrollPos;
1700 SetWindowPos(infoPtr->hwndHeader,0,
1701 point[0].x,point[0].y,point[1].x,point[1].y,
1702 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
1703 SWP_NOZORDER | SWP_NOACTIVATE);
1708 * Update the scrollbars. This functions should be called whenever
1709 * the content, size or view changes.
1712 * [I] infoPtr : valid pointer to the listview structure
1717 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
1719 SCROLLINFO horzInfo, vertInfo;
1722 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1724 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1725 horzInfo.cbSize = sizeof(SCROLLINFO);
1726 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1728 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1729 if (infoPtr->uView == LV_VIEW_LIST)
1731 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1732 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1734 /* scroll by at least one column per page */
1735 if(horzInfo.nPage < infoPtr->nItemWidth)
1736 horzInfo.nPage = infoPtr->nItemWidth;
1738 horzInfo.nPage /= infoPtr->nItemWidth;
1740 else if (infoPtr->uView == LV_VIEW_DETAILS)
1742 horzInfo.nMax = infoPtr->nItemWidth;
1744 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
1748 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1751 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1752 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1753 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
1754 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1755 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1757 /* Setting the horizontal scroll can change the listview size
1758 * (and potentially everything else) so we need to recompute
1759 * everything again for the vertical scroll
1762 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1763 vertInfo.cbSize = sizeof(SCROLLINFO);
1764 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1766 if (infoPtr->uView == LV_VIEW_DETAILS)
1768 vertInfo.nMax = infoPtr->nItemCount;
1770 /* scroll by at least one page */
1771 if(vertInfo.nPage < infoPtr->nItemHeight)
1772 vertInfo.nPage = infoPtr->nItemHeight;
1774 if (infoPtr->nItemHeight > 0)
1775 vertInfo.nPage /= infoPtr->nItemHeight;
1777 else if (infoPtr->uView != LV_VIEW_LIST) /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
1781 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1784 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1785 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1786 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
1787 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1788 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1790 /* Change of the range may have changed the scroll pos. If so move the content */
1791 if (dx != 0 || dy != 0)
1794 listRect = infoPtr->rcList;
1795 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
1796 SW_ERASE | SW_INVALIDATE);
1799 /* Update the Header Control */
1800 if (infoPtr->uView == LV_VIEW_DETAILS)
1802 horzInfo.fMask = SIF_POS;
1803 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1804 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1811 * Shows/hides the focus rectangle.
1814 * [I] infoPtr : valid pointer to the listview structure
1815 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1820 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
1824 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1826 if (infoPtr->nFocusedItem < 0) return;
1828 /* we need some gymnastics in ICON mode to handle large items */
1829 if (infoPtr->uView == LV_VIEW_ICON)
1833 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1834 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1836 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1841 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1843 /* for some reason, owner draw should work only in report mode */
1844 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
1849 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1850 HFONT hOldFont = SelectObject(hdc, hFont);
1852 item.iItem = infoPtr->nFocusedItem;
1854 item.mask = LVIF_PARAM;
1855 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1857 ZeroMemory(&dis, sizeof(dis));
1858 dis.CtlType = ODT_LISTVIEW;
1859 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1860 dis.itemID = item.iItem;
1861 dis.itemAction = ODA_FOCUS;
1862 if (fShow) dis.itemState |= ODS_FOCUS;
1863 dis.hwndItem = infoPtr->hwndSelf;
1865 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1866 dis.itemData = item.lParam;
1868 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1870 SelectObject(hdc, hOldFont);
1874 LISTVIEW_DrawFocusRect(infoPtr, hdc);
1877 ReleaseDC(infoPtr->hwndSelf, hdc);
1881 * Invalidates all visible selected items.
1883 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
1887 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1888 while(iterator_next(&i))
1890 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1891 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1893 iterator_destroy(&i);
1898 * DESCRIPTION: [INTERNAL]
1899 * Computes an item's (left,top) corner, relative to rcView.
1900 * That is, the position has NOT been made relative to the Origin.
1901 * This is deliberate, to avoid computing the Origin over, and
1902 * over again, when this function is called in a loop. Instead,
1903 * one can factor the computation of the Origin before the loop,
1904 * and offset the value returned by this function, on every iteration.
1907 * [I] infoPtr : valid pointer to the listview structure
1908 * [I] nItem : item number
1909 * [O] lpptOrig : item top, left corner
1914 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1916 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1918 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
1920 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1921 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1923 else if (infoPtr->uView == LV_VIEW_LIST)
1925 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1926 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1927 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1929 else /* LV_VIEW_DETAILS */
1931 lpptPosition->x = REPORT_MARGINX;
1932 /* item is always at zero indexed column */
1933 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
1934 lpptPosition->x += LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
1935 lpptPosition->y = nItem * infoPtr->nItemHeight;
1940 * DESCRIPTION: [INTERNAL]
1941 * Compute the rectangles of an item. This is to localize all
1942 * the computations in one place. If you are not interested in some
1943 * of these values, simply pass in a NULL -- the function is smart
1944 * enough to compute only what's necessary. The function computes
1945 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1946 * one, the BOX rectangle. This rectangle is very cheap to compute,
1947 * and is guaranteed to contain all the other rectangles. Computing
1948 * the ICON rect is also cheap, but all the others are potentially
1949 * expensive. This gives an easy and effective optimization when
1950 * searching (like point inclusion, or rectangle intersection):
1951 * first test against the BOX, and if TRUE, test against the desired
1953 * If the function does not have all the necessary information
1954 * to computed the requested rectangles, will crash with a
1955 * failed assertion. This is done so we catch all programming
1956 * errors, given that the function is called only from our code.
1958 * We have the following 'special' meanings for a few fields:
1959 * * If LVIS_FOCUSED is set, we assume the item has the focus
1960 * This is important in ICON mode, where it might get a larger
1961 * then usual rectangle
1963 * Please note that subitem support works only in REPORT mode.
1966 * [I] infoPtr : valid pointer to the listview structure
1967 * [I] lpLVItem : item to compute the measures for
1968 * [O] lprcBox : ptr to Box rectangle
1969 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
1970 * [0] lprcSelectBox : ptr to select box rectangle
1971 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
1972 * [O] lprcIcon : ptr to Icon rectangle
1973 * Same as LVM_GETITEMRECT with LVIR_ICON
1974 * [O] lprcStateIcon: ptr to State Icon rectangle
1975 * [O] lprcLabel : ptr to Label rectangle
1976 * Same as LVM_GETITEMRECT with LVIR_LABEL
1981 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1982 LPRECT lprcBox, LPRECT lprcSelectBox,
1983 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
1985 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1986 RECT Box, SelectBox, Icon, Label;
1987 COLUMN_INFO *lpColumnInfo = NULL;
1988 SIZE labelSize = { 0, 0 };
1990 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1992 /* Be smart and try to figure out the minimum we have to do */
1993 if (lpLVItem->iSubItem) assert(infoPtr->uView == LV_VIEW_DETAILS);
1994 if (infoPtr->uView == LV_VIEW_ICON && (lprcBox || lprcLabel))
1996 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1997 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1999 if (lprcSelectBox) doSelectBox = TRUE;
2000 if (lprcLabel) doLabel = TRUE;
2001 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
2008 /************************************************************/
2009 /* compute the box rectangle (it should be cheap to do) */
2010 /************************************************************/
2011 if (lpLVItem->iSubItem || infoPtr->uView == LV_VIEW_DETAILS)
2012 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
2014 if (lpLVItem->iSubItem)
2016 Box = lpColumnInfo->rcHeader;
2021 Box.right = infoPtr->nItemWidth;
2024 Box.bottom = infoPtr->nItemHeight;
2026 /******************************************************************/
2027 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2028 /******************************************************************/
2031 LONG state_width = 0;
2033 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
2034 state_width = infoPtr->iconStateSize.cx;
2036 if (infoPtr->uView == LV_VIEW_ICON)
2038 Icon.left = Box.left + state_width;
2039 if (infoPtr->himlNormal)
2040 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2041 Icon.top = Box.top + ICON_TOP_PADDING;
2042 Icon.right = Icon.left;
2043 Icon.bottom = Icon.top;
2044 if (infoPtr->himlNormal)
2046 Icon.right += infoPtr->iconSize.cx;
2047 Icon.bottom += infoPtr->iconSize.cy;
2050 else /* LV_VIEW_SMALLICON, LV_VIEW_LIST or LV_VIEW_DETAILS */
2052 Icon.left = Box.left + state_width;
2054 if (infoPtr->uView == LV_VIEW_DETAILS && lpLVItem->iSubItem == 0)
2056 /* we need the indent in report mode */
2057 assert(lpLVItem->mask & LVIF_INDENT);
2058 Icon.left += infoPtr->iconSize.cx * lpLVItem->iIndent + REPORT_MARGINX;
2062 Icon.right = Icon.left;
2063 if (infoPtr->himlSmall &&
2064 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2065 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2066 Icon.right += infoPtr->iconSize.cx;
2067 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2069 if(lprcIcon) *lprcIcon = Icon;
2070 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2072 /* TODO: is this correct? */
2075 lprcStateIcon->left = Icon.left - state_width;
2076 lprcStateIcon->right = Icon.left;
2077 lprcStateIcon->top = Icon.top;
2078 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2079 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2082 else Icon.right = 0;
2084 /************************************************************/
2085 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2086 /************************************************************/
2089 /* calculate how far to the right can the label stretch */
2090 Label.right = Box.right;
2091 if (infoPtr->uView == LV_VIEW_DETAILS)
2093 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
2096 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && infoPtr->uView == LV_VIEW_DETAILS))
2098 labelSize.cx = infoPtr->nItemWidth;
2099 labelSize.cy = infoPtr->nItemHeight;
2103 /* we need the text in non owner draw mode */
2104 assert(lpLVItem->mask & LVIF_TEXT);
2105 if (is_textT(lpLVItem->pszText, TRUE))
2107 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2108 HDC hdc = GetDC(infoPtr->hwndSelf);
2109 HFONT hOldFont = SelectObject(hdc, hFont);
2113 /* compute rough rectangle where the label will go */
2114 SetRectEmpty(&rcText);
2115 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2116 rcText.bottom = infoPtr->nItemHeight;
2117 if (infoPtr->uView == LV_VIEW_ICON)
2118 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2120 /* now figure out the flags */
2121 if (infoPtr->uView == LV_VIEW_ICON)
2122 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2124 uFormat = LV_SL_DT_FLAGS;
2126 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2128 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2129 labelSize.cy = rcText.bottom - rcText.top;
2131 SelectObject(hdc, hOldFont);
2132 ReleaseDC(infoPtr->hwndSelf, hdc);
2136 if (infoPtr->uView == LV_VIEW_ICON)
2138 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2139 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2140 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2141 Label.right = Label.left + labelSize.cx;
2142 Label.bottom = Label.top + infoPtr->nItemHeight;
2143 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2145 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2146 labelSize.cy /= infoPtr->ntmHeight;
2147 labelSize.cy = max(labelSize.cy, 1);
2148 labelSize.cy *= infoPtr->ntmHeight;
2150 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2152 else if (infoPtr->uView == LV_VIEW_DETAILS)
2154 Label.left = Icon.right;
2155 Label.top = Box.top;
2156 Label.right = lpColumnInfo->rcHeader.right;
2157 Label.bottom = Label.top + infoPtr->nItemHeight;
2159 else /* LV_VIEW_SMALLICON or LV_VIEW_LIST */
2161 Label.left = Icon.right;
2162 Label.top = Box.top;
2163 Label.right = min(Label.left + labelSize.cx, Label.right);
2164 Label.bottom = Label.top + infoPtr->nItemHeight;
2167 if (lprcLabel) *lprcLabel = Label;
2168 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2171 /************************************************************/
2172 /* compute SELECT bounding box */
2173 /************************************************************/
2176 if (infoPtr->uView == LV_VIEW_DETAILS)
2178 SelectBox.left = Icon.left;
2179 SelectBox.top = Box.top;
2180 SelectBox.bottom = Box.bottom;
2181 SelectBox.right = min(Label.left + labelSize.cx, Label.right);
2185 UnionRect(&SelectBox, &Icon, &Label);
2187 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2188 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2191 /* Fix the Box if necessary */
2194 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2195 else *lprcBox = Box;
2197 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2201 * DESCRIPTION: [INTERNAL]
2204 * [I] infoPtr : valid pointer to the listview structure
2205 * [I] nItem : item number
2206 * [O] lprcBox : ptr to Box rectangle
2211 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2213 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2214 POINT Position, Origin;
2217 LISTVIEW_GetOrigin(infoPtr, &Origin);
2218 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2220 /* Be smart and try to figure out the minimum we have to do */
2222 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2223 lvItem.mask |= LVIF_TEXT;
2224 lvItem.iItem = nItem;
2225 lvItem.iSubItem = 0;
2226 lvItem.pszText = szDispText;
2227 lvItem.cchTextMax = DISP_TEXT_SIZE;
2228 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2229 if (infoPtr->uView == LV_VIEW_ICON)
2231 lvItem.mask |= LVIF_STATE;
2232 lvItem.stateMask = LVIS_FOCUSED;
2233 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2235 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2237 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2243 * Returns the current icon position, and advances it along the top.
2244 * The returned position is not offset by Origin.
2247 * [I] infoPtr : valid pointer to the listview structure
2248 * [O] lpPos : will get the current icon position
2253 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2255 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2257 *lpPos = infoPtr->currIconPos;
2259 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2260 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2262 infoPtr->currIconPos.x = 0;
2263 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2269 * Returns the current icon position, and advances it down the left edge.
2270 * The returned position is not offset by Origin.
2273 * [I] infoPtr : valid pointer to the listview structure
2274 * [O] lpPos : will get the current icon position
2279 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2281 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2283 *lpPos = infoPtr->currIconPos;
2285 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2286 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2288 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2289 infoPtr->currIconPos.y = 0;
2295 * Moves an icon to the specified position.
2296 * It takes care of invalidating the item, etc.
2299 * [I] infoPtr : valid pointer to the listview structure
2300 * [I] nItem : the item to move
2301 * [I] lpPos : the new icon position
2302 * [I] isNew : flags the item as being new
2308 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2314 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2315 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2317 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2318 LISTVIEW_InvalidateItem(infoPtr, nItem);
2321 /* Allocating a POINTER for every item is too resource intensive,
2322 * so we'll keep the (x,y) in different arrays */
2323 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2324 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2326 LISTVIEW_InvalidateItem(infoPtr, nItem);
2333 * Arranges listview items in icon display mode.
2336 * [I] infoPtr : valid pointer to the listview structure
2337 * [I] nAlignCode : alignment code
2343 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2345 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2349 if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON) return FALSE;
2351 TRACE("nAlignCode=%d\n", nAlignCode);
2353 if (nAlignCode == LVA_DEFAULT)
2355 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2356 else nAlignCode = LVA_ALIGNTOP;
2361 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2362 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2363 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2364 default: return FALSE;
2367 infoPtr->bAutoarrange = TRUE;
2368 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2369 for (i = 0; i < infoPtr->nItemCount; i++)
2371 next_pos(infoPtr, &pos);
2372 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2380 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2381 * For LVS_REPORT always returns empty rectangle.
2384 * [I] infoPtr : valid pointer to the listview structure
2385 * [O] lprcView : bounding rectangle
2391 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2395 SetRectEmpty(lprcView);
2397 switch (infoPtr->uView)
2400 case LV_VIEW_SMALLICON:
2401 for (i = 0; i < infoPtr->nItemCount; i++)
2403 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2404 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2405 lprcView->right = max(lprcView->right, x);
2406 lprcView->bottom = max(lprcView->bottom, y);
2408 if (infoPtr->nItemCount > 0)
2410 lprcView->right += infoPtr->nItemWidth;
2411 lprcView->bottom += infoPtr->nItemHeight;
2416 y = LISTVIEW_GetCountPerColumn(infoPtr);
2417 x = infoPtr->nItemCount / y;
2418 if (infoPtr->nItemCount % y) x++;
2419 lprcView->right = x * infoPtr->nItemWidth;
2420 lprcView->bottom = y * infoPtr->nItemHeight;
2427 * Retrieves the bounding rectangle of all the items.
2430 * [I] infoPtr : valid pointer to the listview structure
2431 * [O] lprcView : bounding rectangle
2437 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2441 TRACE("(lprcView=%p)\n", lprcView);
2443 if (!lprcView) return FALSE;
2445 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2447 if (infoPtr->uView != LV_VIEW_DETAILS)
2449 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2450 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2453 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2460 * Retrieves the subitem pointer associated with the subitem index.
2463 * [I] hdpaSubItems : DPA handle for a specific item
2464 * [I] nSubItem : index of subitem
2467 * SUCCESS : subitem pointer
2470 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2472 SUBITEM_INFO *lpSubItem;
2475 /* we should binary search here if need be */
2476 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2478 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2479 if (lpSubItem->iSubItem == nSubItem)
2489 * Calculates the desired item width.
2492 * [I] infoPtr : valid pointer to the listview structure
2495 * The desired item width.
2497 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2501 TRACE("uView=%d\n", infoPtr->uView);
2503 if (infoPtr->uView == LV_VIEW_ICON)
2504 nItemWidth = infoPtr->iconSpacing.cx;
2505 else if (infoPtr->uView == LV_VIEW_DETAILS)
2509 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2511 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2512 nItemWidth = rcHeader.right;
2515 else /* LV_VIEW_SMALLICON, or LV_VIEW_LIST */
2519 for (i = 0; i < infoPtr->nItemCount; i++)
2520 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2522 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2523 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2525 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2528 return max(nItemWidth, 1);
2533 * Calculates the desired item height.
2536 * [I] infoPtr : valid pointer to the listview structure
2539 * The desired item height.
2541 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2545 TRACE("uView=%d\n", infoPtr->uView);
2547 if (infoPtr->uView == LV_VIEW_ICON)
2548 nItemHeight = infoPtr->iconSpacing.cy;
2551 nItemHeight = infoPtr->ntmHeight;
2552 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
2554 if (infoPtr->himlState)
2555 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2556 if (infoPtr->himlSmall)
2557 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2558 if (infoPtr->himlState || infoPtr->himlSmall)
2559 nItemHeight += HEIGHT_PADDING;
2560 if (infoPtr->nMeasureItemHeight > 0)
2561 nItemHeight = infoPtr->nMeasureItemHeight;
2564 return max(nItemHeight, 1);
2569 * Updates the width, and height of an item.
2572 * [I] infoPtr : valid pointer to the listview structure
2577 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2579 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2580 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2586 * Retrieves and saves important text metrics info for the current
2590 * [I] infoPtr : valid pointer to the listview structure
2593 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2595 HDC hdc = GetDC(infoPtr->hwndSelf);
2596 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2597 HFONT hOldFont = SelectObject(hdc, hFont);
2601 if (GetTextMetricsW(hdc, &tm))
2603 infoPtr->ntmHeight = tm.tmHeight;
2604 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2607 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2608 infoPtr->nEllipsisWidth = sz.cx;
2610 SelectObject(hdc, hOldFont);
2611 ReleaseDC(infoPtr->hwndSelf, hdc);
2613 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2618 * A compare function for ranges
2621 * [I] range1 : pointer to range 1;
2622 * [I] range2 : pointer to range 2;
2626 * > 0 : if range 1 > range 2
2627 * < 0 : if range 2 > range 1
2628 * = 0 : if range intersects range 2
2630 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2634 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2636 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2641 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
2647 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2649 #define ranges_check(ranges, desc) do { } while(0)
2652 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2657 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2659 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2660 ranges_dump(ranges);
2661 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2663 prev = DPA_GetPtr(ranges->hdpa, 0);
2664 assert (prev->lower >= 0 && prev->lower < prev->upper);
2665 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2667 curr = DPA_GetPtr(ranges->hdpa, i);
2668 assert (prev->upper <= curr->lower);
2669 assert (curr->lower < curr->upper);
2673 TRACE("--- Done checking---\n");
2676 static RANGES ranges_create(int count)
2678 RANGES ranges = Alloc(sizeof(struct tagRANGES));
2679 if (!ranges) return NULL;
2680 ranges->hdpa = DPA_Create(count);
2681 if (ranges->hdpa) return ranges;
2686 static void ranges_clear(RANGES ranges)
2690 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2691 Free(DPA_GetPtr(ranges->hdpa, i));
2692 DPA_DeleteAllPtrs(ranges->hdpa);
2696 static void ranges_destroy(RANGES ranges)
2698 if (!ranges) return;
2699 ranges_clear(ranges);
2700 DPA_Destroy(ranges->hdpa);
2704 static RANGES ranges_clone(RANGES ranges)
2709 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2711 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2713 RANGE *newrng = Alloc(sizeof(RANGE));
2714 if (!newrng) goto fail;
2715 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2716 DPA_SetPtr(clone->hdpa, i, newrng);
2721 TRACE ("clone failed\n");
2722 ranges_destroy(clone);
2726 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2730 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2731 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2736 static void ranges_dump(RANGES ranges)
2740 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2741 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2744 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2746 RANGE srchrng = { nItem, nItem + 1 };
2748 TRACE("(nItem=%d)\n", nItem);
2749 ranges_check(ranges, "before contain");
2750 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2753 static INT ranges_itemcount(RANGES ranges)
2757 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2759 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2760 count += sel->upper - sel->lower;
2766 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2768 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2771 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2772 if (index == -1) return TRUE;
2774 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2776 chkrng = DPA_GetPtr(ranges->hdpa, index);
2777 if (chkrng->lower >= nItem)
2778 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2779 if (chkrng->upper > nItem)
2780 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2785 static BOOL ranges_add(RANGES ranges, RANGE range)
2790 TRACE("(%s)\n", debugrange(&range));
2791 ranges_check(ranges, "before add");
2793 /* try find overlapping regions first */
2794 srchrgn.lower = range.lower - 1;
2795 srchrgn.upper = range.upper + 1;
2796 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2802 TRACE("Adding new range\n");
2804 /* create the brand new range to insert */
2805 newrgn = Alloc(sizeof(RANGE));
2806 if(!newrgn) goto fail;
2809 /* figure out where to insert it */
2810 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2811 TRACE("index=%d\n", index);
2812 if (index == -1) index = 0;
2814 /* and get it over with */
2815 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2823 RANGE *chkrgn, *mrgrgn;
2824 INT fromindex, mergeindex;
2826 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2827 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2829 chkrgn->lower = min(range.lower, chkrgn->lower);
2830 chkrgn->upper = max(range.upper, chkrgn->upper);
2832 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2834 /* merge now common ranges */
2836 srchrgn.lower = chkrgn->lower - 1;
2837 srchrgn.upper = chkrgn->upper + 1;
2841 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2842 if (mergeindex == -1) break;
2843 if (mergeindex == index)
2845 fromindex = index + 1;
2849 TRACE("Merge with index %i\n", mergeindex);
2851 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2852 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2853 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2855 DPA_DeletePtr(ranges->hdpa, mergeindex);
2856 if (mergeindex < index) index --;
2860 ranges_check(ranges, "after add");
2864 ranges_check(ranges, "failed add");
2868 static BOOL ranges_del(RANGES ranges, RANGE range)
2873 TRACE("(%s)\n", debugrange(&range));
2874 ranges_check(ranges, "before del");
2876 /* we don't use DPAS_SORTED here, since we need *
2877 * to find the first overlapping range */
2878 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2881 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2883 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2885 /* case 1: Same range */
2886 if ( (chkrgn->upper == range.upper) &&
2887 (chkrgn->lower == range.lower) )
2889 DPA_DeletePtr(ranges->hdpa, index);
2892 /* case 2: engulf */
2893 else if ( (chkrgn->upper <= range.upper) &&
2894 (chkrgn->lower >= range.lower) )
2896 DPA_DeletePtr(ranges->hdpa, index);
2898 /* case 3: overlap upper */
2899 else if ( (chkrgn->upper <= range.upper) &&
2900 (chkrgn->lower < range.lower) )
2902 chkrgn->upper = range.lower;
2904 /* case 4: overlap lower */
2905 else if ( (chkrgn->upper > range.upper) &&
2906 (chkrgn->lower >= range.lower) )
2908 chkrgn->lower = range.upper;
2911 /* case 5: fully internal */
2914 RANGE tmprgn = *chkrgn, *newrgn;
2916 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
2917 newrgn->lower = chkrgn->lower;
2918 newrgn->upper = range.lower;
2919 chkrgn->lower = range.upper;
2920 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2929 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2932 ranges_check(ranges, "after del");
2936 ranges_check(ranges, "failed del");
2942 * Removes all selection ranges
2945 * [I] infoPtr : valid pointer to the listview structure
2946 * [I] toSkip : item range to skip removing the selection
2952 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2961 lvItem.stateMask = LVIS_SELECTED;
2963 /* need to clone the DPA because callbacks can change it */
2964 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2965 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2966 while(iterator_next(&i))
2967 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2968 /* note that the iterator destructor will free the cloned range */
2969 iterator_destroy(&i);
2974 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2978 if (!(toSkip = ranges_create(1))) return FALSE;
2979 if (nItem != -1) ranges_additem(toSkip, nItem);
2980 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2981 ranges_destroy(toSkip);
2985 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2987 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2992 * Retrieves the number of items that are marked as selected.
2995 * [I] infoPtr : valid pointer to the listview structure
2998 * Number of items selected.
3000 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
3002 INT nSelectedCount = 0;
3004 if (infoPtr->uCallbackMask & LVIS_SELECTED)
3007 for (i = 0; i < infoPtr->nItemCount; i++)
3009 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3014 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3016 TRACE("nSelectedCount=%d\n", nSelectedCount);
3017 return nSelectedCount;
3022 * Manages the item focus.
3025 * [I] infoPtr : valid pointer to the listview structure
3026 * [I] nItem : item index
3029 * TRUE : focused item changed
3030 * FALSE : focused item has NOT changed
3032 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3034 INT oldFocus = infoPtr->nFocusedItem;
3037 if (nItem == infoPtr->nFocusedItem) return FALSE;
3039 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3040 lvItem.stateMask = LVIS_FOCUSED;
3041 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3043 return oldFocus != infoPtr->nFocusedItem;
3046 /* Helper function for LISTVIEW_ShiftIndices *only* */
3047 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3049 if (nShiftItem < nItem) return nShiftItem;
3051 if (nShiftItem > nItem) return nShiftItem + direction;
3053 if (direction > 0) return nShiftItem + direction;
3055 return min(nShiftItem, infoPtr->nItemCount - 1);
3060 * Updates the various indices after an item has been inserted or deleted.
3063 * [I] infoPtr : valid pointer to the listview structure
3064 * [I] nItem : item index
3065 * [I] direction : Direction of shift, +1 or -1.
3070 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3075 /* temporarily disable change notification while shifting items */
3076 bOldChange = infoPtr->bDoChangeNotify;
3077 infoPtr->bDoChangeNotify = FALSE;
3079 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3081 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3083 assert(abs(direction) == 1);
3085 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3087 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3088 if (nNewFocus != infoPtr->nFocusedItem)
3089 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3091 /* But we are not supposed to modify nHotItem! */
3093 infoPtr->bDoChangeNotify = bOldChange;
3099 * Adds a block of selections.
3102 * [I] infoPtr : valid pointer to the listview structure
3103 * [I] nItem : item index
3106 * Whether the window is still valid.
3108 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3110 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3111 INT nLast = max(infoPtr->nSelectionMark, nItem);
3112 HWND hwndSelf = infoPtr->hwndSelf;
3113 NMLVODSTATECHANGE nmlv;
3118 /* Temporarily disable change notification
3119 * If the control is LVS_OWNERDATA, we need to send
3120 * only one LVN_ODSTATECHANGED notification.
3121 * See MSDN documentation for LVN_ITEMCHANGED.
3123 bOldChange = infoPtr->bDoChangeNotify;
3124 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3126 if (nFirst == -1) nFirst = nItem;
3128 item.state = LVIS_SELECTED;
3129 item.stateMask = LVIS_SELECTED;
3131 for (i = nFirst; i <= nLast; i++)
3132 LISTVIEW_SetItemState(infoPtr,i,&item);
3134 ZeroMemory(&nmlv, sizeof(nmlv));
3135 nmlv.iFrom = nFirst;
3138 nmlv.uOldState = item.state;
3140 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3141 if (!IsWindow(hwndSelf))
3143 infoPtr->bDoChangeNotify = bOldChange;
3150 * Sets a single group selection.
3153 * [I] infoPtr : valid pointer to the listview structure
3154 * [I] nItem : item index
3159 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3166 if (!(selection = ranges_create(100))) return;
3168 item.state = LVIS_SELECTED;
3169 item.stateMask = LVIS_SELECTED;
3171 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
3173 if (infoPtr->nSelectionMark == -1)
3175 infoPtr->nSelectionMark = nItem;
3176 ranges_additem(selection, nItem);
3182 sel.lower = min(infoPtr->nSelectionMark, nItem);
3183 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3184 ranges_add(selection, sel);
3189 RECT rcItem, rcSel, rcSelMark;
3192 rcItem.left = LVIR_BOUNDS;
3193 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3194 rcSelMark.left = LVIR_BOUNDS;
3195 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3196 UnionRect(&rcSel, &rcItem, &rcSelMark);
3197 iterator_frameditems(&i, infoPtr, &rcSel);
3198 while(iterator_next(&i))
3200 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3201 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3203 iterator_destroy(&i);
3206 /* disable per item notifications on LVS_OWNERDATA style
3207 FIXME: single LVN_ODSTATECHANGED should be used */
3208 bOldChange = infoPtr->bDoChangeNotify;
3209 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3211 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3214 iterator_rangesitems(&i, selection);
3215 while(iterator_next(&i))
3216 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3217 /* this will also destroy the selection */
3218 iterator_destroy(&i);
3220 infoPtr->bDoChangeNotify = bOldChange;
3222 LISTVIEW_SetItemFocus(infoPtr, nItem);
3227 * Sets a single selection.
3230 * [I] infoPtr : valid pointer to the listview structure
3231 * [I] nItem : item index
3236 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3240 TRACE("nItem=%d\n", nItem);
3242 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3244 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3245 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3246 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3248 infoPtr->nSelectionMark = nItem;
3253 * Set selection(s) with keyboard.
3256 * [I] infoPtr : valid pointer to the listview structure
3257 * [I] nItem : item index
3258 * [I] space : VK_SPACE code sent
3261 * SUCCESS : TRUE (needs to be repainted)
3262 * FAILURE : FALSE (nothing has changed)
3264 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem, BOOL space)
3266 /* FIXME: pass in the state */
3267 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3268 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3269 BOOL bResult = FALSE;
3271 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3272 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3276 if (infoPtr->dwStyle & LVS_SINGLESEL || (wShift == 0 && wCtrl == 0))
3277 LISTVIEW_SetSelection(infoPtr, nItem);
3281 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3285 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3286 lvItem.stateMask = LVIS_SELECTED;
3289 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3290 if (lvItem.state & LVIS_SELECTED)
3291 infoPtr->nSelectionMark = nItem;
3293 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3296 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3299 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3303 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3305 LVHITTESTINFO lvHitTestInfo;
3307 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3308 lvHitTestInfo.pt.x = pt.x;
3309 lvHitTestInfo.pt.y = pt.y;
3311 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3313 lpLVItem->mask = LVIF_PARAM;
3314 lpLVItem->iItem = lvHitTestInfo.iItem;
3315 lpLVItem->iSubItem = 0;
3317 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3320 static inline BOOL LISTVIEW_isHotTracking(const LISTVIEW_INFO *infoPtr)
3322 return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3323 (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3324 (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3329 * Called when the mouse is being actively tracked and has hovered for a specified
3333 * [I] infoPtr : valid pointer to the listview structure
3334 * [I] fwKeys : key indicator
3335 * [I] x,y : mouse position
3338 * 0 if the message was processed, non-zero if there was an error
3341 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3342 * over the item for a certain period of time.
3345 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3347 if (LISTVIEW_isHotTracking(infoPtr))
3355 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3356 LISTVIEW_SetSelection(infoPtr, item.iItem);
3364 * Called whenever WM_MOUSEMOVE is received.
3367 * [I] infoPtr : valid pointer to the listview structure
3368 * [I] fwKeys : key indicator
3369 * [I] x,y : mouse position
3372 * 0 if the message is processed, non-zero if there was an error
3374 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3376 TRACKMOUSEEVENT trackinfo;
3378 if (!(fwKeys & MK_LBUTTON))
3379 infoPtr->bLButtonDown = FALSE;
3381 if (infoPtr->bLButtonDown)
3385 LVHITTESTINFO lvHitTestInfo;
3386 WORD wDragWidth = GetSystemMetrics(SM_CXDRAG);
3387 WORD wDragHeight= GetSystemMetrics(SM_CYDRAG);
3389 rect.left = infoPtr->ptClickPos.x - wDragWidth;
3390 rect.right = infoPtr->ptClickPos.x + wDragWidth;
3391 rect.top = infoPtr->ptClickPos.y - wDragHeight;
3392 rect.bottom = infoPtr->ptClickPos.y + wDragHeight;
3397 lvHitTestInfo.pt = tmp;
3398 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3400 /* reset item marker */
3401 if (infoPtr->nLButtonDownItem != lvHitTestInfo.iItem)
3402 infoPtr->nLButtonDownItem = -1;
3404 if (!PtInRect(&rect, tmp))
3406 /* this path covers the following:
3407 1. WM_LBUTTONDOWN over selected item (sets focus on it)
3408 2. change focus with keys
3409 3. move mouse over item from step 1 selects it and moves focus on it */
3410 if (infoPtr->nLButtonDownItem != -1 &&
3411 !LISTVIEW_GetItemState(infoPtr, infoPtr->nLButtonDownItem, LVIS_SELECTED))
3415 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3416 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3418 LISTVIEW_SetItemState(infoPtr, infoPtr->nLButtonDownItem, &lvItem);
3419 infoPtr->nLButtonDownItem = -1;
3422 if (!infoPtr->bDragging)
3426 lvHitTestInfo.pt = infoPtr->ptClickPos;
3427 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3429 ZeroMemory(&nmlv, sizeof(nmlv));
3430 nmlv.iItem = lvHitTestInfo.iItem;
3431 nmlv.ptAction = infoPtr->ptClickPos;
3433 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3434 infoPtr->bDragging = TRUE;
3441 /* see if we are supposed to be tracking mouse hovering */
3442 if (LISTVIEW_isHotTracking(infoPtr)) {
3443 /* fill in the trackinfo struct */
3444 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3445 trackinfo.dwFlags = TME_QUERY;
3446 trackinfo.hwndTrack = infoPtr->hwndSelf;
3447 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3449 /* see if we are already tracking this hwnd */
3450 _TrackMouseEvent(&trackinfo);
3452 if(!(trackinfo.dwFlags & TME_HOVER)) {
3453 trackinfo.dwFlags = TME_HOVER;
3455 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3456 _TrackMouseEvent(&trackinfo);
3465 * Tests whether the item is assignable to a list with style lStyle
3467 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3469 if ( (lpLVItem->mask & LVIF_TEXT) &&
3470 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3471 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3479 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3482 * [I] infoPtr : valid pointer to the listview structure
3483 * [I] lpLVItem : valid pointer to new item attributes
3484 * [I] isNew : the item being set is being inserted
3485 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3486 * [O] bChanged : will be set to TRUE if the item really changed
3492 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3498 /* stateMask is ignored for LVM_INSERTITEM */
3499 UINT stateMask = isNew ? ~0 : lpLVItem->stateMask;
3503 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3505 if (lpLVItem->mask == 0) return TRUE;
3507 if (infoPtr->dwStyle & LVS_OWNERDATA)
3509 /* a virtual listview only stores selection and focus */
3510 if (lpLVItem->mask & ~LVIF_STATE)
3516 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3517 lpItem = DPA_GetPtr(hdpaSubItems, 0);
3521 /* we need to get the lParam and state of the item */
3522 item.iItem = lpLVItem->iItem;
3523 item.iSubItem = lpLVItem->iSubItem;
3524 item.mask = LVIF_STATE | LVIF_PARAM;
3525 item.stateMask = (infoPtr->dwStyle & LVS_OWNERDATA) ? LVIS_FOCUSED | LVIS_SELECTED : ~0;
3529 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3531 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3532 /* determine what fields will change */
3533 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & stateMask & ~infoPtr->uCallbackMask))
3534 uChanged |= LVIF_STATE;
3536 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3537 uChanged |= LVIF_IMAGE;
3539 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3540 uChanged |= LVIF_PARAM;
3542 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3543 uChanged |= LVIF_INDENT;
3545 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3546 uChanged |= LVIF_TEXT;
3548 TRACE("uChanged=0x%x\n", uChanged);
3549 if (!uChanged) return TRUE;
3552 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3553 nmlv.iItem = lpLVItem->iItem;
3554 nmlv.uNewState = (item.state & ~stateMask) | (lpLVItem->state & stateMask);
3555 nmlv.uOldState = item.state;
3556 nmlv.uChanged = uChanged;
3557 nmlv.lParam = item.lParam;
3559 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3560 /* and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications */
3562 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
3564 HWND hwndSelf = infoPtr->hwndSelf;
3566 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3568 if (!IsWindow(hwndSelf))
3572 /* copy information */
3573 if (lpLVItem->mask & LVIF_TEXT)
3574 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3576 if (lpLVItem->mask & LVIF_IMAGE)
3577 lpItem->hdr.iImage = lpLVItem->iImage;
3579 if (lpLVItem->mask & LVIF_PARAM)
3580 lpItem->lParam = lpLVItem->lParam;
3582 if (lpLVItem->mask & LVIF_INDENT)
3583 lpItem->iIndent = lpLVItem->iIndent;
3585 if (uChanged & LVIF_STATE)
3587 if (lpItem && (stateMask & ~infoPtr->uCallbackMask))
3589 lpItem->state &= ~stateMask;
3590 lpItem->state |= (lpLVItem->state & stateMask);
3592 if (lpLVItem->state & stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3594 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3595 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3597 else if (stateMask & LVIS_SELECTED)
3599 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3601 /* if we are asked to change focus, and we manage it, do it */
3602 if (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3604 if (lpLVItem->state & LVIS_FOCUSED)
3606 if (infoPtr->nFocusedItem != -1)
3608 /* remove current focus */
3609 item.mask = LVIF_STATE;
3611 item.stateMask = LVIS_FOCUSED;
3613 /* recurse with redrawing an item */
3614 LISTVIEW_SetItemState(infoPtr, infoPtr->nFocusedItem, &item);
3617 infoPtr->nFocusedItem = lpLVItem->iItem;
3618 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, infoPtr->uView == LV_VIEW_LIST);
3620 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3622 infoPtr->nFocusedItem = -1;
3627 /* if we're inserting the item, we're done */
3628 if (isNew) return TRUE;
3630 /* send LVN_ITEMCHANGED notification */
3631 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3632 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3639 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3642 * [I] infoPtr : valid pointer to the listview structure
3643 * [I] lpLVItem : valid pointer to new subitem attributes
3644 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3645 * [O] bChanged : will be set to TRUE if the item really changed
3651 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3654 SUBITEM_INFO *lpSubItem;
3656 /* we do not support subitems for virtual listviews */
3657 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3659 /* set subitem only if column is present */
3660 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3662 /* First do some sanity checks */
3663 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
3664 particularly useful. We currently do not actually do anything with
3665 the flag on subitems.
3667 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) return FALSE;
3668 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
3670 /* get the subitem structure, and create it if not there */
3671 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3672 assert (hdpaSubItems);
3674 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3677 SUBITEM_INFO *tmpSubItem;
3680 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
3681 if (!lpSubItem) return FALSE;
3682 /* we could binary search here, if need be...*/
3683 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3685 tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
3686 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3688 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3693 lpSubItem->iSubItem = lpLVItem->iSubItem;
3694 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3698 if (lpLVItem->mask & LVIF_IMAGE)
3699 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3701 lpSubItem->hdr.iImage = lpLVItem->iImage;
3705 if (lpLVItem->mask & LVIF_TEXT)
3706 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3708 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3717 * Sets item attributes.
3720 * [I] infoPtr : valid pointer to the listview structure
3721 * [I] lpLVItem : new item attributes
3722 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3728 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
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 ( infoPtr->uView == LV_VIEW_DETAILS && !(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)
3790 SCROLLINFO scrollInfo;
3792 scrollInfo.cbSize = sizeof(SCROLLINFO);
3793 scrollInfo.fMask = SIF_POS;
3795 if (infoPtr->uView == LV_VIEW_LIST)
3797 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3798 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3800 else if (infoPtr->uView == LV_VIEW_DETAILS)
3802 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3803 nItem = scrollInfo.nPos;
3807 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3808 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3811 TRACE("nItem=%d\n", nItem);
3819 * Erases the background of the given rectangle
3822 * [I] infoPtr : valid pointer to the listview structure
3823 * [I] hdc : device context handle
3824 * [I] lprcBox : clipping rectangle
3830 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3832 if (!infoPtr->hBkBrush) return FALSE;
3834 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
3836 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3844 * [I] infoPtr : valid pointer to the listview structure
3845 * [I] hdc : device context handle
3846 * [I] nItem : item index
3847 * [I] nSubItem : subitem index
3848 * [I] pos : item position in client coordinates
3849 * [I] cdmode : custom draw mode
3855 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3858 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3859 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3860 DWORD cdsubitemmode = CDRF_DODEFAULT;
3862 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
3863 NMLVCUSTOMDRAW nmlvcd;
3868 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
3870 /* get information needed for drawing the item */
3871 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
3872 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3873 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
3874 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3875 lvItem.iItem = nItem;
3876 lvItem.iSubItem = nSubItem;
3879 lvItem.cchTextMax = DISP_TEXT_SIZE;
3880 lvItem.pszText = szDispText;
3881 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3882 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3883 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3884 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3885 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3887 /* now check if we need to update the focus rectangle */
3888 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3890 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3891 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
3892 OffsetRect(&rcBox, pos.x, pos.y);
3893 OffsetRect(&rcSelect, pos.x, pos.y);
3894 OffsetRect(&rcIcon, pos.x, pos.y);
3895 OffsetRect(&rcStateIcon, pos.x, pos.y);
3896 OffsetRect(&rcLabel, pos.x, pos.y);
3897 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
3898 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
3899 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
3901 /* fill in the custom draw structure */
3902 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3904 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
3905 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3906 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
3907 if (cdmode & CDRF_NOTIFYITEMDRAW)
3908 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3909 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3910 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3911 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3912 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3914 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3915 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3917 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3918 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
3919 else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
3920 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
3922 /* in full row select, subitems, will just use main item's colors */
3923 if (nSubItem && infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3924 nmlvcd.clrTextBk = CLR_NONE;
3927 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
3929 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
3932 TRACE("uStateImage=%d\n", uStateImage);
3933 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
3934 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
3939 himl = (infoPtr->uView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3940 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3942 TRACE("iImage=%d\n", lvItem.iImage);
3943 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3944 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk, CLR_DEFAULT,
3945 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3948 /* Don't bother painting item being edited */
3949 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3951 /* FIXME: temporary hack */
3952 rcSelect.left = rcLabel.left;
3954 /* draw the selection background, if we're drawing the main item */
3957 /* in icon mode, the label rect is really what we want to draw the
3959 if (infoPtr->uView == LV_VIEW_ICON)
3962 if (infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3963 rcSelect.right = rcBox.right;
3965 if (nmlvcd.clrTextBk != CLR_NONE)
3966 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3967 /* store new focus rectangle */
3968 if (infoPtr->nFocusedItem == nItem) infoPtr->rcFocus = rcSelect;
3971 /* figure out the text drawing flags */
3972 uFormat = (infoPtr->uView == LV_VIEW_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3973 if (infoPtr->uView == LV_VIEW_ICON)
3974 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3977 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3979 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3980 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3981 default: uFormat |= DT_LEFT;
3984 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3986 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3987 else rcLabel.left += LABEL_HOR_PADDING;
3989 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3991 /* for GRIDLINES reduce the bottom so the text formats correctly */
3992 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
3995 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3998 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3999 notify_postpaint(infoPtr, &nmlvcd);
4000 if (cdsubitemmode & CDRF_NEWFONT)
4001 SelectObject(hdc, hOldFont);
4007 * Draws listview items when in owner draw mode.
4010 * [I] infoPtr : valid pointer to the listview structure
4011 * [I] hdc : device context handle
4016 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4018 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
4019 DWORD cditemmode = CDRF_DODEFAULT;
4020 NMLVCUSTOMDRAW nmlvcd;
4021 POINT Origin, Position;
4027 ZeroMemory(&dis, sizeof(dis));
4029 /* Get scroll info once before loop */
4030 LISTVIEW_GetOrigin(infoPtr, &Origin);
4032 /* iterate through the invalidated rows */
4033 while(iterator_next(i))
4035 item.iItem = i->nItem;
4037 item.mask = LVIF_PARAM | LVIF_STATE;
4038 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4039 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
4041 dis.CtlType = ODT_LISTVIEW;
4043 dis.itemID = item.iItem;
4044 dis.itemAction = ODA_DRAWENTIRE;
4046 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
4047 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
4048 dis.hwndItem = infoPtr->hwndSelf;
4050 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
4051 dis.rcItem.left = Position.x + Origin.x;
4052 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
4053 dis.rcItem.top = Position.y + Origin.y;
4054 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
4055 dis.itemData = item.lParam;
4057 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
4060 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
4061 * structure for the rest. of the paint cycle
4063 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
4064 if (cdmode & CDRF_NOTIFYITEMDRAW)
4065 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4067 if (!(cditemmode & CDRF_SKIPDEFAULT))
4069 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
4070 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
4073 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
4074 notify_postpaint(infoPtr, &nmlvcd);
4080 * Draws listview items when in report display mode.
4083 * [I] infoPtr : valid pointer to the listview structure
4084 * [I] hdc : device context handle
4085 * [I] cdmode : custom draw mode
4090 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4093 RECT rcClip, rcItem;
4094 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 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
4110 /* narrow down the columns we need to paint */
4111 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
4113 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
4115 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4116 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
4117 ranges_additem(colRanges, index);
4119 iterator_rangesitems(&j, colRanges);
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)
4167 RECT rcClip, rcItem = {0};
4175 /* figure out what to draw */
4176 rgntype = GetClipBox(hdc, &rcClip);
4177 if (rgntype == NULLREGION) return;
4179 /* Get scroll info once before loop */
4180 LISTVIEW_GetOrigin(infoPtr, &Origin);
4182 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
4184 /* narrow down the columns we need to paint */
4185 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
4187 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
4189 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4190 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
4191 ranges_additem(colRanges, index);
4194 /* is right most vertical line visible? */
4195 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
4197 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4198 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4199 rmost = (rcItem.right + Origin.x < rcClip.right);
4202 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
4204 hOldPen = SelectObject ( hdc, hPen );
4206 /* draw the vertical lines for the columns */
4207 iterator_rangesitems(&j, colRanges);
4208 while(iterator_next(&j))
4210 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4211 if (rcItem.left == 0) continue; /* skip leftmost column */
4212 rcItem.left += Origin.x;
4213 rcItem.right += Origin.x;
4214 rcItem.top = infoPtr->rcList.top;
4215 rcItem.bottom = infoPtr->rcList.bottom;
4216 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
4217 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4218 LineTo (hdc, rcItem.left, rcItem.bottom);
4220 iterator_destroy(&j);
4221 /* draw rightmost grid line if visible */
4224 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
4225 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4226 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4228 rcItem.right += Origin.x;
4230 MoveToEx (hdc, rcItem.right, infoPtr->rcList.top, NULL);
4231 LineTo (hdc, rcItem.right, infoPtr->rcList.bottom);
4234 /* draw the horizontial lines for the rows */
4235 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
4236 rcItem.left = infoPtr->rcList.left;
4237 rcItem.right = infoPtr->rcList.right;
4238 rcItem.bottom = rcItem.top = Origin.y - 1;
4239 MoveToEx(hdc, rcItem.left, rcItem.top, NULL);
4240 LineTo(hdc, rcItem.right, rcItem.top);
4241 for(y=itemheight-1+Origin.y; y<=infoPtr->rcList.bottom; y+=itemheight)
4243 rcItem.bottom = rcItem.top = y;
4244 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
4245 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4246 LineTo (hdc, rcItem.right, rcItem.top);
4249 SelectObject( hdc, hOldPen );
4250 DeleteObject( hPen );
4256 * Draws listview items when in list display mode.
4259 * [I] infoPtr : valid pointer to the listview structure
4260 * [I] hdc : device context handle
4261 * [I] cdmode : custom draw mode
4266 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4268 POINT Origin, Position;
4270 /* Get scroll info once before loop */
4271 LISTVIEW_GetOrigin(infoPtr, &Origin);
4273 while(iterator_prev(i))
4275 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4276 Position.x += Origin.x;
4277 Position.y += Origin.y;
4279 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4286 * Draws listview items.
4289 * [I] infoPtr : valid pointer to the listview structure
4290 * [I] hdc : device context handle
4291 * [I] prcErase : rect to be erased before refresh (may be NULL)
4296 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
4298 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
4299 NMLVCUSTOMDRAW nmlvcd;
4306 HBITMAP hbmp = NULL;
4309 LISTVIEW_DUMP(infoPtr);
4311 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4312 TRACE("double buffering\n");
4314 hdc = CreateCompatibleDC(hdcOrig);
4316 ERR("Failed to create DC for backbuffer\n");
4319 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
4320 infoPtr->rcList.bottom);
4322 ERR("Failed to create bitmap for backbuffer\n");
4327 SelectObject(hdc, hbmp);
4328 SelectObject(hdc, infoPtr->hFont);
4330 /* Save dc values we're gonna trash while drawing
4331 * FIXME: Should be done in LISTVIEW_DrawItem() */
4332 hOldFont = SelectObject(hdc, infoPtr->hFont);
4333 oldBkMode = GetBkMode(hdc);
4334 oldBkColor = GetBkColor(hdc);
4335 oldTextColor = GetTextColor(hdc);
4338 infoPtr->bIsDrawing = TRUE;
4341 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
4342 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4343 /* If no erasing was done (usually because RedrawWindow was called
4344 * with RDW_INVALIDATE only) we need to copy the old contents into
4345 * the backbuffer before continuing. */
4346 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
4347 infoPtr->rcList.right - infoPtr->rcList.left,
4348 infoPtr->rcList.bottom - infoPtr->rcList.top,
4349 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4352 /* FIXME: Shouldn't need to do this */
4353 oldClrTextBk = infoPtr->clrTextBk;
4354 oldClrText = infoPtr->clrText;
4356 infoPtr->cditemmode = CDRF_DODEFAULT;
4358 GetClientRect(infoPtr->hwndSelf, &rcClient);
4359 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
4360 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4361 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
4362 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4364 /* Use these colors to draw the items */
4365 infoPtr->clrTextBk = nmlvcd.clrTextBk;
4366 infoPtr->clrText = nmlvcd.clrText;
4368 /* nothing to draw */
4369 if(infoPtr->nItemCount == 0) goto enddraw;
4371 /* figure out what we need to draw */
4372 iterator_visibleitems(&i, infoPtr, hdc);
4373 range = iterator_range(&i);
4375 /* send cache hint notification */
4376 if (infoPtr->dwStyle & LVS_OWNERDATA)
4380 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4381 nmlv.iFrom = range.lower;
4382 nmlv.iTo = range.upper - 1;
4383 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4386 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
4387 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4390 if (infoPtr->uView == LV_VIEW_DETAILS)
4391 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4392 else /* LV_VIEW_LIST, LV_VIEW_ICON or LV_VIEW_SMALLICON */
4393 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4395 /* if we have a focus rect and it's visible, draw it */
4396 if (infoPtr->bFocus && range.lower <= infoPtr->nFocusedItem &&
4397 (range.upper - 1) >= infoPtr->nFocusedItem)
4398 LISTVIEW_DrawFocusRect(infoPtr, hdc);
4400 iterator_destroy(&i);
4403 /* For LVS_EX_GRIDLINES go and draw lines */
4404 /* This includes the case where there were *no* items */
4405 if ((infoPtr->uView == LV_VIEW_DETAILS) && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4406 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
4408 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4409 notify_postpaint(infoPtr, &nmlvcd);
4411 infoPtr->clrTextBk = oldClrTextBk;
4412 infoPtr->clrText = oldClrText;
4415 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
4416 infoPtr->rcList.right - infoPtr->rcList.left,
4417 infoPtr->rcList.bottom - infoPtr->rcList.top,
4418 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4423 SelectObject(hdc, hOldFont);
4424 SetBkMode(hdc, oldBkMode);
4425 SetBkColor(hdc, oldBkColor);
4426 SetTextColor(hdc, oldTextColor);
4429 infoPtr->bIsDrawing = FALSE;
4435 * Calculates the approximate width and height of a given number of items.
4438 * [I] infoPtr : valid pointer to the listview structure
4439 * [I] nItemCount : number of items
4440 * [I] wWidth : width
4441 * [I] wHeight : height
4444 * Returns a DWORD. The width in the low word and the height in high word.
4446 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
4447 WORD wWidth, WORD wHeight)
4449 INT nItemCountPerColumn = 1;
4450 INT nColumnCount = 0;
4451 DWORD dwViewRect = 0;
4453 if (nItemCount == -1)
4454 nItemCount = infoPtr->nItemCount;
4456 if (infoPtr->uView == LV_VIEW_LIST)
4458 if (wHeight == 0xFFFF)
4460 /* use current height */
4461 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4464 if (wHeight < infoPtr->nItemHeight)
4465 wHeight = infoPtr->nItemHeight;
4469 if (infoPtr->nItemHeight > 0)
4471 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4472 if (nItemCountPerColumn == 0)
4473 nItemCountPerColumn = 1;
4475 if (nItemCount % nItemCountPerColumn != 0)
4476 nColumnCount = nItemCount / nItemCountPerColumn;
4478 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4482 /* Microsoft padding magic */
4483 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4484 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4486 dwViewRect = MAKELONG(wWidth, wHeight);
4488 else if (infoPtr->uView == LV_VIEW_DETAILS)
4492 if (infoPtr->nItemCount > 0)
4494 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4495 wWidth = rcBox.right - rcBox.left;
4496 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4500 /* use current height and width */
4501 if (wHeight == 0xffff)
4502 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4503 if (wWidth == 0xffff)
4504 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4507 dwViewRect = MAKELONG(wWidth, wHeight);
4509 else if (infoPtr->uView == LV_VIEW_SMALLICON)
4510 FIXME("uView == LV_VIEW_SMALLICON: not implemented\n");
4511 else if (infoPtr->uView == LV_VIEW_ICON)
4512 FIXME("uView == LV_VIEW_ICON: not implemented\n");
4520 * Create a drag image list for the specified item.
4523 * [I] infoPtr : valid pointer to the listview structure
4524 * [I] iItem : index of item
4525 * [O] lppt : Upper-left corner of the image
4528 * Returns a handle to the image list if successful, NULL otherwise.
4530 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4536 HBITMAP hbmp, hOldbmp;
4537 HIMAGELIST dragList = 0;
4538 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
4540 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4543 rcItem.left = LVIR_BOUNDS;
4544 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4547 lppt->x = rcItem.left;
4548 lppt->y = rcItem.top;
4550 size.cx = rcItem.right - rcItem.left;
4551 size.cy = rcItem.bottom - rcItem.top;
4553 hdcOrig = GetDC(infoPtr->hwndSelf);
4554 hdc = CreateCompatibleDC(hdcOrig);
4555 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4556 hOldbmp = SelectObject(hdc, hbmp);
4558 rcItem.left = rcItem.top = 0;
4559 rcItem.right = size.cx;
4560 rcItem.bottom = size.cy;
4561 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4564 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4566 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4567 SelectObject(hdc, hOldbmp);
4568 ImageList_Add(dragList, hbmp, 0);
4571 SelectObject(hdc, hOldbmp);
4575 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4577 TRACE("ret=%p\n", dragList);
4585 * Removes all listview items and subitems.
4588 * [I] infoPtr : valid pointer to the listview structure
4594 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
4597 HDPA hdpaSubItems = NULL;
4604 /* we do it directly, to avoid notifications */
4605 ranges_clear(infoPtr->selectionRanges);
4606 infoPtr->nSelectionMark = -1;
4607 infoPtr->nFocusedItem = -1;
4608 SetRectEmpty(&infoPtr->rcFocus);
4609 /* But we are supposed to leave nHotItem as is! */
4612 /* send LVN_DELETEALLITEMS notification */
4613 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4615 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4617 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4619 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4621 /* send LVN_DELETEITEM notification, if not suppressed
4622 and if it is not a virtual listview */
4623 if (!bSuppress) notify_deleteitem(infoPtr, i);
4624 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
4625 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4627 hdrItem = DPA_GetPtr(hdpaSubItems, j);
4628 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4631 DPA_Destroy(hdpaSubItems);
4632 DPA_DeletePtr(infoPtr->hdpaItems, i);
4634 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4635 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4636 infoPtr->nItemCount --;
4641 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4642 LISTVIEW_UpdateScroll(infoPtr);
4644 LISTVIEW_InvalidateList(infoPtr);
4651 * Scrolls, and updates the columns, when a column is changing width.
4654 * [I] infoPtr : valid pointer to the listview structure
4655 * [I] nColumn : column to scroll
4656 * [I] dx : amount of scroll, in pixels
4661 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4663 COLUMN_INFO *lpColumnInfo;
4668 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4669 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4670 rcCol = lpColumnInfo->rcHeader;
4671 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4672 rcCol.left = rcCol.right;
4674 /* adjust the other columns */
4675 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4677 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4678 lpColumnInfo->rcHeader.left += dx;
4679 lpColumnInfo->rcHeader.right += dx;
4682 /* do not update screen if not in report mode */
4683 if (!is_redrawing(infoPtr) || infoPtr->uView != LV_VIEW_DETAILS) return;
4685 /* Need to reset the item width when inserting a new column */
4686 infoPtr->nItemWidth += dx;
4688 LISTVIEW_UpdateScroll(infoPtr);
4689 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
4691 /* scroll to cover the deleted column, and invalidate for redraw */
4692 rcOld = infoPtr->rcList;
4693 rcOld.left = ptOrigin.x + rcCol.left + dx;
4694 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4699 * Removes a column from the listview control.
4702 * [I] infoPtr : valid pointer to the listview structure
4703 * [I] nColumn : column index
4709 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4713 TRACE("nColumn=%d\n", nColumn);
4715 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4716 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4718 /* While the MSDN specifically says that column zero should not be deleted,
4719 what actually happens is that the column itself is deleted but no items or subitems
4723 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4725 if (!SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nColumn, 0))
4728 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4729 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4731 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4733 SUBITEM_INFO *lpSubItem, *lpDelItem;
4735 INT nItem, nSubItem, i;
4737 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4739 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
4742 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4744 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
4745 if (lpSubItem->iSubItem == nColumn)
4748 lpDelItem = lpSubItem;
4750 else if (lpSubItem->iSubItem > nColumn)
4752 lpSubItem->iSubItem--;
4756 /* if we found our subitem, zapp it */
4760 if (is_textW(lpDelItem->hdr.pszText))
4761 Free(lpDelItem->hdr.pszText);
4766 /* free dpa memory */
4767 DPA_DeletePtr(hdpaSubItems, nSubItem);
4772 /* update the other column info */
4773 LISTVIEW_UpdateItemSize(infoPtr);
4774 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4775 LISTVIEW_InvalidateList(infoPtr);
4777 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4784 * Invalidates the listview after an item's insertion or deletion.
4787 * [I] infoPtr : valid pointer to the listview structure
4788 * [I] nItem : item index
4789 * [I] dir : -1 if deleting, 1 if inserting
4794 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4796 INT nPerCol, nItemCol, nItemRow;
4800 /* if we don't refresh, what's the point of scrolling? */
4801 if (!is_redrawing(infoPtr)) return;
4803 assert (abs(dir) == 1);
4805 /* arrange icons if autoarrange is on */
4806 if (is_autoarrange(infoPtr))
4808 BOOL arrange = TRUE;
4809 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4810 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4811 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4814 /* scrollbars need updating */
4815 LISTVIEW_UpdateScroll(infoPtr);
4817 /* figure out the item's position */
4818 if (infoPtr->uView == LV_VIEW_DETAILS)
4819 nPerCol = infoPtr->nItemCount + 1;
4820 else if (infoPtr->uView == LV_VIEW_LIST)
4821 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4822 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
4825 nItemCol = nItem / nPerCol;
4826 nItemRow = nItem % nPerCol;
4827 LISTVIEW_GetOrigin(infoPtr, &Origin);
4829 /* move the items below up a slot */
4830 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4831 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4832 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4833 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4834 OffsetRect(&rcScroll, Origin.x, Origin.y);
4835 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
4836 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4838 TRACE("Scrolling rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
4839 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4840 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4843 /* report has only that column, so we're done */
4844 if (infoPtr->uView == LV_VIEW_DETAILS) return;
4846 /* now for LISTs, we have to deal with the columns to the right */
4847 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4849 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4850 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4851 OffsetRect(&rcScroll, Origin.x, Origin.y);
4852 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4853 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4854 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4859 * Removes an item from the listview control.
4862 * [I] infoPtr : valid pointer to the listview structure
4863 * [I] nItem : item index
4869 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4872 const BOOL is_icon = (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON);
4874 TRACE("(nItem=%d)\n", nItem);
4876 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4878 /* remove selection, and focus */
4880 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4881 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4883 /* send LVN_DELETEITEM notification. */
4884 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
4886 /* we need to do this here, because we'll be deleting stuff */
4888 LISTVIEW_InvalidateItem(infoPtr, nItem);
4890 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4896 hdpaSubItems = DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4897 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4899 hdrItem = DPA_GetPtr(hdpaSubItems, i);
4900 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4903 DPA_Destroy(hdpaSubItems);
4908 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4909 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4912 infoPtr->nItemCount--;
4913 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4915 /* now is the invalidation fun */
4917 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4924 * Callback implementation for editlabel control
4927 * [I] infoPtr : valid pointer to the listview structure
4928 * [I] storeText : store edit box text as item text
4929 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4935 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, BOOL storeText, BOOL isW)
4937 HWND hwndSelf = infoPtr->hwndSelf;
4938 NMLVDISPINFOW dispInfo;
4939 INT editedItem = infoPtr->nEditLabelItem;
4941 WCHAR *pszText = NULL;
4946 DWORD len = isW ? GetWindowTextLengthW(infoPtr->hwndEdit) : GetWindowTextLengthA(infoPtr->hwndEdit);
4950 if ((pszText = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))))
4952 if (isW) GetWindowTextW(infoPtr->hwndEdit, pszText, len+1);
4953 else GetWindowTextA(infoPtr->hwndEdit, (CHAR*)pszText, len+1);
4958 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4960 infoPtr->nEditLabelItem = -1;
4961 infoPtr->hwndEdit = 0;
4963 ZeroMemory(&dispInfo, sizeof(dispInfo));
4964 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4965 dispInfo.item.iItem = editedItem;
4966 dispInfo.item.iSubItem = 0;
4967 dispInfo.item.stateMask = ~0;
4968 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item))
4975 bSame = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
4978 LPWSTR tmp = textdupTtoW(pszText, FALSE);
4979 bSame = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
4980 textfreeT(tmp, FALSE);
4988 /* add the text from the edit in */
4989 dispInfo.item.mask |= LVIF_TEXT;
4990 dispInfo.item.pszText = pszText;
4991 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4993 /* Do we need to update the Item Text */
4994 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW))
4999 if (!IsWindow(hwndSelf))
5004 if (!pszText) return TRUE;
5006 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5008 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, editedItem);
5009 ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
5010 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
5012 LISTVIEW_InvalidateItem(infoPtr, editedItem);
5018 ZeroMemory(&dispInfo, sizeof(dispInfo));
5019 dispInfo.item.mask = LVIF_TEXT;
5020 dispInfo.item.iItem = editedItem;
5021 dispInfo.item.iSubItem = 0;
5022 dispInfo.item.pszText = pszText;
5023 dispInfo.item.cchTextMax = textlenT(pszText, isW);
5024 res = LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
5034 * Begin in place editing of specified list view item
5037 * [I] infoPtr : valid pointer to the listview structure
5038 * [I] nItem : item index
5039 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
5045 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
5047 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
5048 NMLVDISPINFOW dispInfo;
5051 HWND hwndSelf = infoPtr->hwndSelf;
5053 HFONT hOldFont = NULL;
5054 TEXTMETRICW textMetric;
5056 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
5058 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
5060 /* Is the EditBox still there, if so remove it */
5061 if(infoPtr->hwndEdit != 0)
5063 SetFocus(infoPtr->hwndSelf);
5064 infoPtr->hwndEdit = 0;
5067 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5069 infoPtr->nEditLabelItem = nItem;
5071 LISTVIEW_SetSelection(infoPtr, nItem);
5072 LISTVIEW_SetItemFocus(infoPtr, nItem);
5073 LISTVIEW_InvalidateItem(infoPtr, nItem);
5075 rect.left = LVIR_LABEL;
5076 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
5078 ZeroMemory(&dispInfo, sizeof(dispInfo));
5079 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
5080 dispInfo.item.iItem = nItem;
5081 dispInfo.item.iSubItem = 0;
5082 dispInfo.item.stateMask = ~0;
5083 dispInfo.item.pszText = szDispText;
5084 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
5085 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
5087 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE, isW);
5088 if (!infoPtr->hwndEdit) return 0;
5090 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
5092 if (!IsWindow(hwndSelf))
5094 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
5095 infoPtr->hwndEdit = 0;
5099 /* Now position and display edit box */
5100 hdc = GetDC(infoPtr->hwndSelf);
5102 /* Select the font to get appropriate metric dimensions */
5103 if(infoPtr->hFont != 0)
5104 hOldFont = SelectObject(hdc, infoPtr->hFont);
5106 /* Get String Length in pixels */
5107 GetTextExtentPoint32W(hdc, dispInfo.item.pszText, lstrlenW(dispInfo.item.pszText), &sz);
5109 /* Add Extra spacing for the next character */
5110 GetTextMetricsW(hdc, &textMetric);
5111 sz.cx += (textMetric.tmMaxCharWidth * 2);
5113 if(infoPtr->hFont != 0)
5114 SelectObject(hdc, hOldFont);
5116 ReleaseDC(infoPtr->hwndSelf, hdc);
5118 MoveWindow(infoPtr->hwndEdit, rect.left - 2, rect.top - 1, sz.cx,
5119 rect.bottom - rect.top + 2, FALSE);
5120 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
5121 SetFocus(infoPtr->hwndEdit);
5122 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
5123 return infoPtr->hwndEdit;
5129 * Ensures the specified item is visible, scrolling into view if necessary.
5132 * [I] infoPtr : valid pointer to the listview structure
5133 * [I] nItem : item index
5134 * [I] bPartial : partially or entirely visible
5140 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
5142 INT nScrollPosHeight = 0;
5143 INT nScrollPosWidth = 0;
5144 INT nHorzAdjust = 0;
5145 INT nVertAdjust = 0;
5148 RECT rcItem, rcTemp;
5150 rcItem.left = LVIR_BOUNDS;
5151 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
5153 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
5155 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
5157 /* scroll left/right, but in LV_VIEW_DETAILS mode */
5158 if (infoPtr->uView == LV_VIEW_LIST)
5159 nScrollPosWidth = infoPtr->nItemWidth;
5160 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
5161 nScrollPosWidth = 1;
5163 if (rcItem.left < infoPtr->rcList.left)
5166 if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.left - infoPtr->rcList.left;
5171 if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.right - infoPtr->rcList.right;
5175 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
5177 /* scroll up/down, but not in LVS_LIST mode */
5178 if (infoPtr->uView == LV_VIEW_DETAILS)
5179 nScrollPosHeight = infoPtr->nItemHeight;
5180 else if ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON))
5181 nScrollPosHeight = 1;
5183 if (rcItem.top < infoPtr->rcList.top)
5186 if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
5191 if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
5195 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
5197 if (nScrollPosWidth)
5199 INT diff = nHorzDiff / nScrollPosWidth;
5200 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
5201 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
5204 if (nScrollPosHeight)
5206 INT diff = nVertDiff / nScrollPosHeight;
5207 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
5208 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
5216 * Searches for an item with specific characteristics.
5219 * [I] hwnd : window handle
5220 * [I] nStart : base item index
5221 * [I] lpFindInfo : item information to look for
5224 * SUCCESS : index of item
5227 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
5228 const LVFINDINFOW *lpFindInfo)
5230 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5231 BOOL bWrap = FALSE, bNearest = FALSE;
5232 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
5233 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
5234 POINT Position, Destination;
5237 /* Search in virtual listviews should be done by application, not by
5238 listview control, so we just send LVN_ODFINDITEMW and return the result */
5239 if (infoPtr->dwStyle & LVS_OWNERDATA)
5243 nmlv.iStart = nStart;
5244 nmlv.lvfi = *lpFindInfo;
5245 return notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
5248 if (!lpFindInfo || nItem < 0) return -1;
5251 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
5253 lvItem.mask |= LVIF_TEXT;
5254 lvItem.pszText = szDispText;
5255 lvItem.cchTextMax = DISP_TEXT_SIZE;
5258 if (lpFindInfo->flags & LVFI_WRAP)
5261 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
5262 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON))
5267 LISTVIEW_GetOrigin(infoPtr, &Origin);
5268 Destination.x = lpFindInfo->pt.x - Origin.x;
5269 Destination.y = lpFindInfo->pt.y - Origin.y;
5270 switch(lpFindInfo->vkDirection)
5272 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
5273 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
5274 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
5275 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
5276 case VK_HOME: Destination.x = Destination.y = 0; break;
5277 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5278 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5280 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
5281 Destination.x = rcArea.right;
5282 Destination.y = rcArea.bottom;
5284 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
5288 else Destination.x = Destination.y = 0;
5290 /* if LVFI_PARAM is specified, all other flags are ignored */
5291 if (lpFindInfo->flags & LVFI_PARAM)
5293 lvItem.mask |= LVIF_PARAM;
5295 lvItem.mask &= ~LVIF_TEXT;
5299 for (; nItem < nLast; nItem++)
5301 lvItem.iItem = nItem;
5302 lvItem.iSubItem = 0;
5303 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
5305 if (lvItem.mask & LVIF_PARAM)
5307 if (lpFindInfo->lParam == lvItem.lParam)
5313 if (lvItem.mask & LVIF_TEXT)
5315 if (lpFindInfo->flags & LVFI_PARTIAL)
5317 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
5321 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
5325 if (!bNearest) return nItem;
5327 /* This is very inefficient. To do a good job here,
5328 * we need a sorted array of (x,y) item positions */
5329 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5331 /* compute the distance^2 to the destination */
5332 xdist = Destination.x - Position.x;
5333 ydist = Destination.y - Position.y;
5334 dist = xdist * xdist + ydist * ydist;
5336 /* remember the distance, and item if it's closer */
5340 nNearestItem = nItem;
5347 nLast = min(nStart + 1, infoPtr->nItemCount);
5352 return nNearestItem;
5357 * Searches for an item with specific characteristics.
5360 * [I] hwnd : window handle
5361 * [I] nStart : base item index
5362 * [I] lpFindInfo : item information to look for
5365 * SUCCESS : index of item
5368 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
5369 const LVFINDINFOA *lpFindInfo)
5371 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
5376 memcpy(&fiw, lpFindInfo, sizeof(fiw));
5377 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
5378 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
5379 textfreeT(strW, FALSE);
5385 * Retrieves the background image of the listview control.
5388 * [I] infoPtr : valid pointer to the listview structure
5389 * [O] lpBkImage : background image attributes
5395 /* static BOOL LISTVIEW_GetBkImage(const LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
5397 /* FIXME (listview, "empty stub!\n"); */
5403 * Retrieves column attributes.
5406 * [I] infoPtr : valid pointer to the listview structure
5407 * [I] nColumn : column index
5408 * [IO] lpColumn : column information
5409 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
5410 * otherwise it is in fact a LPLVCOLUMNA
5416 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5418 COLUMN_INFO *lpColumnInfo;
5421 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5422 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
5424 /* initialize memory */
5425 ZeroMemory(&hdi, sizeof(hdi));
5427 if (lpColumn->mask & LVCF_TEXT)
5429 hdi.mask |= HDI_TEXT;
5430 hdi.pszText = lpColumn->pszText;
5431 hdi.cchTextMax = lpColumn->cchTextMax;
5434 if (lpColumn->mask & LVCF_IMAGE)
5435 hdi.mask |= HDI_IMAGE;
5437 if (lpColumn->mask & LVCF_ORDER)
5438 hdi.mask |= HDI_ORDER;
5440 if (lpColumn->mask & LVCF_SUBITEM)
5441 hdi.mask |= HDI_LPARAM;
5443 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
5445 if (lpColumn->mask & LVCF_FMT)
5446 lpColumn->fmt = lpColumnInfo->fmt;
5448 if (lpColumn->mask & LVCF_WIDTH)
5449 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
5451 if (lpColumn->mask & LVCF_IMAGE)
5452 lpColumn->iImage = hdi.iImage;
5454 if (lpColumn->mask & LVCF_ORDER)
5455 lpColumn->iOrder = hdi.iOrder;
5457 if (lpColumn->mask & LVCF_SUBITEM)
5458 lpColumn->iSubItem = hdi.lParam;
5464 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
5466 TRACE("iCount=%d, lpiArray=%p\n", iCount, lpiArray);
5471 return SendMessageW(infoPtr->hwndHeader, HDM_GETORDERARRAY, iCount, (LPARAM)lpiArray);
5476 * Retrieves the column width.
5479 * [I] infoPtr : valid pointer to the listview structure
5480 * [I] int : column index
5483 * SUCCESS : column width
5486 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
5488 INT nColumnWidth = 0;
5491 TRACE("nColumn=%d\n", nColumn);
5493 /* we have a 'column' in LIST and REPORT mode only */
5494 switch(infoPtr->uView)
5497 nColumnWidth = infoPtr->nItemWidth;
5499 case LV_VIEW_DETAILS:
5500 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDM_ITEMCHANGED.
5501 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
5502 * HDM_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
5504 * TODO: should we do the same in LVM_GETCOLUMN?
5506 hdItem.mask = HDI_WIDTH;
5507 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
5509 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
5512 nColumnWidth = hdItem.cxy;
5516 TRACE("nColumnWidth=%d\n", nColumnWidth);
5517 return nColumnWidth;
5522 * In list or report display mode, retrieves the number of items that can fit
5523 * vertically in the visible area. In icon or small icon display mode,
5524 * retrieves the total number of visible items.
5527 * [I] infoPtr : valid pointer to the listview structure
5530 * Number of fully visible items.
5532 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
5534 switch (infoPtr->uView)
5537 case LV_VIEW_SMALLICON:
5538 return infoPtr->nItemCount;
5539 case LV_VIEW_DETAILS:
5540 return LISTVIEW_GetCountPerColumn(infoPtr);
5542 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5550 * Retrieves an image list handle.
5553 * [I] infoPtr : valid pointer to the listview structure
5554 * [I] nImageList : image list identifier
5557 * SUCCESS : image list handle
5560 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
5564 case LVSIL_NORMAL: return infoPtr->himlNormal;
5565 case LVSIL_SMALL: return infoPtr->himlSmall;
5566 case LVSIL_STATE: return infoPtr->himlState;
5571 /* LISTVIEW_GetISearchString */
5575 * Retrieves item attributes.
5578 * [I] hwnd : window handle
5579 * [IO] lpLVItem : item info
5580 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5581 * if FALSE, then lpLVItem is a LPLVITEMA.
5584 * This is the internal 'GetItem' interface -- it tries to
5585 * be smart and avoid text copies, if possible, by modifying
5586 * lpLVItem->pszText to point to the text string. Please note
5587 * that this is not always possible (e.g. OWNERDATA), so on
5588 * entry you *must* supply valid values for pszText, and cchTextMax.
5589 * The only difference to the documented interface is that upon
5590 * return, you should use *only* the lpLVItem->pszText, rather than
5591 * the buffer pointer you provided on input. Most code already does
5592 * that, so it's not a problem.
5593 * For the two cases when the text must be copied (that is,
5594 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5600 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5602 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5603 NMLVDISPINFOW dispInfo;
5609 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5611 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5614 if (lpLVItem->mask == 0) return TRUE;
5616 /* make a local copy */
5617 isubitem = lpLVItem->iSubItem;
5619 /* a quick optimization if all we're asked is the focus state
5620 * these queries are worth optimising since they are common,
5621 * and can be answered in constant time, without the heavy accesses */
5622 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5623 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5625 lpLVItem->state = 0;
5626 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5627 lpLVItem->state |= LVIS_FOCUSED;
5631 ZeroMemory(&dispInfo, sizeof(dispInfo));
5633 /* if the app stores all the data, handle it separately */
5634 if (infoPtr->dwStyle & LVS_OWNERDATA)
5636 dispInfo.item.state = 0;
5638 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
5639 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) ||
5640 ((lpLVItem->mask & LVIF_STATE) && (infoPtr->uCallbackMask & lpLVItem->stateMask)))
5642 UINT mask = lpLVItem->mask;
5644 /* NOTE: copy only fields which we _know_ are initialized, some apps
5645 * depend on the uninitialized fields being 0 */
5646 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5647 dispInfo.item.iItem = lpLVItem->iItem;
5648 dispInfo.item.iSubItem = isubitem;
5649 if (lpLVItem->mask & LVIF_TEXT)
5651 if (lpLVItem->mask & LVIF_NORECOMPUTE)
5653 dispInfo.item.mask &= ~(LVIF_TEXT | LVIF_NORECOMPUTE);
5656 dispInfo.item.pszText = lpLVItem->pszText;
5657 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5660 if (lpLVItem->mask & LVIF_STATE)
5661 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5662 /* could be zeroed on LVIF_NORECOMPUTE case */
5663 if (dispInfo.item.mask != 0)
5665 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5666 dispInfo.item.stateMask = lpLVItem->stateMask;
5667 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5669 /* full size structure expected - _WIN32IE >= 0x560 */
5670 *lpLVItem = dispInfo.item;
5672 else if (lpLVItem->mask & LVIF_INDENT)
5674 /* indent member expected - _WIN32IE >= 0x300 */
5675 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5679 /* minimal structure expected */
5680 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5682 lpLVItem->mask = mask;
5683 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5687 /* make sure lParam is zeroed out */
5688 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5690 /* callback marked pointer required here */
5691 if ((lpLVItem->mask & LVIF_TEXT) && (lpLVItem->mask & LVIF_NORECOMPUTE))
5692 lpLVItem->pszText = LPSTR_TEXTCALLBACKW;
5694 /* we store only a little state, so if we're not asked, we're done */
5695 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5697 /* if focus is handled by us, report it */
5698 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5700 lpLVItem->state &= ~LVIS_FOCUSED;
5701 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5702 lpLVItem->state |= LVIS_FOCUSED;
5705 /* and do the same for selection, if we handle it */
5706 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5708 lpLVItem->state &= ~LVIS_SELECTED;
5709 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5710 lpLVItem->state |= LVIS_SELECTED;
5716 /* find the item and subitem structures before we proceed */
5717 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5718 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5723 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5724 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5727 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5732 pItemHdr = &lpItem->hdr;
5734 /* Do we need to query the state from the app? */
5735 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5737 dispInfo.item.mask |= LVIF_STATE;
5738 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5741 /* Do we need to enquire about the image? */
5742 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5743 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5745 dispInfo.item.mask |= LVIF_IMAGE;
5746 dispInfo.item.iImage = I_IMAGECALLBACK;
5749 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5750 if ((lpLVItem->mask & LVIF_TEXT) && !(lpLVItem->mask & LVIF_NORECOMPUTE) &&
5751 !is_textW(pItemHdr->pszText))
5753 dispInfo.item.mask |= LVIF_TEXT;
5754 dispInfo.item.pszText = lpLVItem->pszText;
5755 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5756 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5757 *dispInfo.item.pszText = '\0';
5760 /* If we don't have all the requested info, query the application */
5761 if (dispInfo.item.mask != 0)
5763 dispInfo.item.iItem = lpLVItem->iItem;
5764 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5765 dispInfo.item.lParam = lpItem->lParam;
5766 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5767 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5770 /* we should not store values for subitems */
5771 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5773 /* Now, handle the iImage field */
5774 if (dispInfo.item.mask & LVIF_IMAGE)
5776 lpLVItem->iImage = dispInfo.item.iImage;
5777 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5778 pItemHdr->iImage = dispInfo.item.iImage;
5780 else if (lpLVItem->mask & LVIF_IMAGE)
5782 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5783 lpLVItem->iImage = pItemHdr->iImage;
5785 lpLVItem->iImage = 0;
5788 /* The pszText field */
5789 if (dispInfo.item.mask & LVIF_TEXT)
5791 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5792 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5794 lpLVItem->pszText = dispInfo.item.pszText;
5796 else if (lpLVItem->mask & LVIF_TEXT)
5798 /* if LVN_GETDISPINFO's disabled with LVIF_NORECOMPUTE return callback placeholder */
5799 if (isW || !is_textW(pItemHdr->pszText)) lpLVItem->pszText = pItemHdr->pszText;
5800 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5803 /* Next is the lParam field */
5804 if (dispInfo.item.mask & LVIF_PARAM)
5806 lpLVItem->lParam = dispInfo.item.lParam;
5807 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5808 lpItem->lParam = dispInfo.item.lParam;
5810 else if (lpLVItem->mask & LVIF_PARAM)
5811 lpLVItem->lParam = lpItem->lParam;
5813 /* if this is a subitem, we're done */
5814 if (isubitem) return TRUE;
5816 /* ... the state field (this one is different due to uCallbackmask) */
5817 if (lpLVItem->mask & LVIF_STATE)
5819 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
5820 if (dispInfo.item.mask & LVIF_STATE)
5822 lpLVItem->state &= ~dispInfo.item.stateMask;
5823 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5825 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5827 lpLVItem->state &= ~LVIS_FOCUSED;
5828 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5829 lpLVItem->state |= LVIS_FOCUSED;
5831 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5833 lpLVItem->state &= ~LVIS_SELECTED;
5834 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5835 lpLVItem->state |= LVIS_SELECTED;
5839 /* and last, but not least, the indent field */
5840 if (lpLVItem->mask & LVIF_INDENT)
5841 lpLVItem->iIndent = lpItem->iIndent;
5848 * Retrieves item attributes.
5851 * [I] hwnd : window handle
5852 * [IO] lpLVItem : item info
5853 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5854 * if FALSE, then lpLVItem is a LPLVITEMA.
5857 * This is the external 'GetItem' interface -- it properly copies
5858 * the text in the provided buffer.
5864 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5869 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5872 pszText = lpLVItem->pszText;
5873 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5874 if (bResult && lpLVItem->pszText != pszText)
5876 if (lpLVItem->pszText != LPSTR_TEXTCALLBACKW)
5877 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5879 pszText = LPSTR_TEXTCALLBACKW;
5881 lpLVItem->pszText = pszText;
5889 * Retrieves the position (upper-left) of the listview control item.
5890 * Note that for LVS_ICON style, the upper-left is that of the icon
5891 * and not the bounding box.
5894 * [I] infoPtr : valid pointer to the listview structure
5895 * [I] nItem : item index
5896 * [O] lpptPosition : coordinate information
5902 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5906 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5908 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5910 LISTVIEW_GetOrigin(infoPtr, &Origin);
5911 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5913 if (infoPtr->uView == LV_VIEW_ICON)
5915 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5916 lpptPosition->y += ICON_TOP_PADDING;
5918 lpptPosition->x += Origin.x;
5919 lpptPosition->y += Origin.y;
5921 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
5928 * Retrieves the bounding rectangle for a listview control item.
5931 * [I] infoPtr : valid pointer to the listview structure
5932 * [I] nItem : item index
5933 * [IO] lprc : bounding rectangle coordinates
5934 * lprc->left specifies the portion of the item for which the bounding
5935 * rectangle will be retrieved.
5937 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5938 * including the icon and label.
5941 * * Experiment shows that native control returns:
5942 * * width = min (48, length of text line)
5943 * * .left = position.x - (width - iconsize.cx)/2
5944 * * .right = .left + width
5945 * * height = #lines of text * ntmHeight + icon height + 8
5946 * * .top = position.y - 2
5947 * * .bottom = .top + height
5948 * * separation between items .y = itemSpacing.cy - height
5949 * * .x = itemSpacing.cx - width
5950 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5953 * * Experiment shows that native control returns:
5954 * * width = iconSize.cx + 16
5955 * * .left = position.x - (width - iconsize.cx)/2
5956 * * .right = .left + width
5957 * * height = iconSize.cy + 4
5958 * * .top = position.y - 2
5959 * * .bottom = .top + height
5960 * * separation between items .y = itemSpacing.cy - height
5961 * * .x = itemSpacing.cx - width
5962 * LVIR_LABEL Returns the bounding rectangle of the item text.
5965 * * Experiment shows that native control returns:
5966 * * width = text length
5967 * * .left = position.x - width/2
5968 * * .right = .left + width
5969 * * height = ntmH * linecount + 2
5970 * * .top = position.y + iconSize.cy + 6
5971 * * .bottom = .top + height
5972 * * separation between items .y = itemSpacing.cy - height
5973 * * .x = itemSpacing.cx - width
5974 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5975 * rectangles, but excludes columns in report view.
5982 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5983 * upon whether the window has the focus currently and on whether the item
5984 * is the one with the focus. Ensure that the control's record of which
5985 * item has the focus agrees with the items' records.
5987 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5989 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5990 BOOL doLabel = TRUE, oversizedBox = FALSE;
5991 POINT Position, Origin;
5994 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5996 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5998 LISTVIEW_GetOrigin(infoPtr, &Origin);
5999 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
6001 /* Be smart and try to figure out the minimum we have to do */
6002 if (lprc->left == LVIR_ICON) doLabel = FALSE;
6003 if (infoPtr->uView == LV_VIEW_DETAILS && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
6004 if (infoPtr->uView == LV_VIEW_ICON && lprc->left != LVIR_ICON &&
6005 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
6006 oversizedBox = TRUE;
6008 /* get what we need from the item before hand, so we make
6009 * only one request. This can speed up things, if data
6010 * is stored on the app side */
6012 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
6013 if (doLabel) lvItem.mask |= LVIF_TEXT;
6014 lvItem.iItem = nItem;
6015 lvItem.iSubItem = 0;
6016 lvItem.pszText = szDispText;
6017 lvItem.cchTextMax = DISP_TEXT_SIZE;
6018 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
6019 /* we got the state already up, simulate it here, to avoid a reget */
6020 if (infoPtr->uView == LV_VIEW_ICON && (lprc->left != LVIR_ICON))
6022 lvItem.mask |= LVIF_STATE;
6023 lvItem.stateMask = LVIS_FOCUSED;
6024 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
6027 if (infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
6028 lprc->left = LVIR_BOUNDS;
6032 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
6036 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
6040 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
6043 case LVIR_SELECTBOUNDS:
6044 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
6048 WARN("Unknown value: %d\n", lprc->left);
6052 if (infoPtr->uView == LV_VIEW_DETAILS)
6053 OffsetRect(lprc, Origin.x, Position.y + Origin.y);
6055 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
6057 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
6064 * Retrieves the spacing between listview control items.
6067 * [I] infoPtr : valid pointer to the listview structure
6068 * [IO] lprc : rectangle to receive the output
6069 * on input, lprc->top = nSubItem
6070 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
6072 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
6073 * not only those of the first column.
6074 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
6080 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
6086 if (!lprc) return FALSE;
6088 nColumn = lprc->top;
6090 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
6091 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
6093 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
6095 if (infoPtr->uView != LV_VIEW_DETAILS) return FALSE;
6097 /* special case for header items */
6100 if (lprc->left != LVIR_BOUNDS)
6102 FIXME("Only LVIR_BOUNDS is implemented for header, got %d\n", lprc->left);
6106 if (infoPtr->hwndHeader)
6107 return SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, lprc->top, (LPARAM)lprc);
6110 memset(lprc, 0, sizeof(RECT));
6115 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
6117 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6120 lvItem.iItem = nItem;
6121 lvItem.iSubItem = nColumn;
6123 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
6127 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
6132 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
6136 ERR("Unknown bounds=%d\n", lprc->left);
6140 OffsetRect(lprc, 0, Position.y);
6147 * Retrieves the width of a label.
6150 * [I] infoPtr : valid pointer to the listview structure
6153 * SUCCESS : string width (in pixels)
6156 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *infoPtr, INT nItem)
6158 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6161 TRACE("(nItem=%d)\n", nItem);
6163 lvItem.mask = LVIF_TEXT;
6164 lvItem.iItem = nItem;
6165 lvItem.iSubItem = 0;
6166 lvItem.pszText = szDispText;
6167 lvItem.cchTextMax = DISP_TEXT_SIZE;
6168 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6170 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6175 * Retrieves the spacing between listview control items.
6178 * [I] infoPtr : valid pointer to the listview structure
6179 * [I] bSmall : flag for small or large icon
6182 * Horizontal + vertical spacing
6184 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
6190 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6194 if (infoPtr->uView == LV_VIEW_ICON)
6195 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
6197 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
6204 * Retrieves the state of a listview control item.
6207 * [I] infoPtr : valid pointer to the listview structure
6208 * [I] nItem : item index
6209 * [I] uMask : state mask
6212 * State specified by the mask.
6214 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
6218 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6220 lvItem.iItem = nItem;
6221 lvItem.iSubItem = 0;
6222 lvItem.mask = LVIF_STATE;
6223 lvItem.stateMask = uMask;
6224 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6226 return lvItem.state & uMask;
6231 * Retrieves the text of a listview control item or subitem.
6234 * [I] hwnd : window handle
6235 * [I] nItem : item index
6236 * [IO] lpLVItem : item information
6237 * [I] isW : TRUE if lpLVItem is Unicode
6240 * SUCCESS : string length
6243 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
6245 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6247 lpLVItem->mask = LVIF_TEXT;
6248 lpLVItem->iItem = nItem;
6249 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
6251 return textlenT(lpLVItem->pszText, isW);
6256 * Searches for an item based on properties + relationships.
6259 * [I] infoPtr : valid pointer to the listview structure
6260 * [I] nItem : item index
6261 * [I] uFlags : relationship flag
6264 * SUCCESS : item index
6267 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
6270 LVFINDINFOW lvFindInfo;
6271 INT nCountPerColumn;
6275 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
6276 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
6278 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
6280 if (uFlags & LVNI_CUT)
6283 if (uFlags & LVNI_DROPHILITED)
6284 uMask |= LVIS_DROPHILITED;
6286 if (uFlags & LVNI_FOCUSED)
6287 uMask |= LVIS_FOCUSED;
6289 if (uFlags & LVNI_SELECTED)
6290 uMask |= LVIS_SELECTED;
6292 /* if we're asked for the focused item, that's only one,
6293 * so it's worth optimizing */
6294 if (uFlags & LVNI_FOCUSED)
6296 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
6297 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
6300 if (uFlags & LVNI_ABOVE)
6302 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
6307 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6313 /* Special case for autoarrange - move 'til the top of a list */
6314 if (is_autoarrange(infoPtr))
6316 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6317 while (nItem - nCountPerRow >= 0)
6319 nItem -= nCountPerRow;
6320 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6325 lvFindInfo.flags = LVFI_NEARESTXY;
6326 lvFindInfo.vkDirection = VK_UP;
6327 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6328 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6330 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6335 else if (uFlags & LVNI_BELOW)
6337 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
6339 while (nItem < infoPtr->nItemCount)
6342 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6348 /* Special case for autoarrange - move 'til the bottom of a list */
6349 if (is_autoarrange(infoPtr))
6351 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6352 while (nItem + nCountPerRow < infoPtr->nItemCount )
6354 nItem += nCountPerRow;
6355 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6360 lvFindInfo.flags = LVFI_NEARESTXY;
6361 lvFindInfo.vkDirection = VK_DOWN;
6362 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6363 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6365 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6370 else if (uFlags & LVNI_TOLEFT)
6372 if (infoPtr->uView == LV_VIEW_LIST)
6374 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6375 while (nItem - nCountPerColumn >= 0)
6377 nItem -= nCountPerColumn;
6378 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6382 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
6384 /* Special case for autoarrange - move 'til the beginning of a row */
6385 if (is_autoarrange(infoPtr))
6387 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6388 while (nItem % nCountPerRow > 0)
6391 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6396 lvFindInfo.flags = LVFI_NEARESTXY;
6397 lvFindInfo.vkDirection = VK_LEFT;
6398 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6399 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6401 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6406 else if (uFlags & LVNI_TORIGHT)
6408 if (infoPtr->uView == LV_VIEW_LIST)
6410 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6411 while (nItem + nCountPerColumn < infoPtr->nItemCount)
6413 nItem += nCountPerColumn;
6414 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6418 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
6420 /* Special case for autoarrange - move 'til the end of a row */
6421 if (is_autoarrange(infoPtr))
6423 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6424 while (nItem % nCountPerRow < nCountPerRow - 1 )
6427 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6432 lvFindInfo.flags = LVFI_NEARESTXY;
6433 lvFindInfo.vkDirection = VK_RIGHT;
6434 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6435 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6437 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6446 /* search by index */
6447 for (i = nItem; i < infoPtr->nItemCount; i++)
6449 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
6457 /* LISTVIEW_GetNumberOfWorkAreas */
6461 * Retrieves the origin coordinates when in icon or small icon display mode.
6464 * [I] infoPtr : valid pointer to the listview structure
6465 * [O] lpptOrigin : coordinate information
6470 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
6472 INT nHorzPos = 0, nVertPos = 0;
6473 SCROLLINFO scrollInfo;
6475 scrollInfo.cbSize = sizeof(SCROLLINFO);
6476 scrollInfo.fMask = SIF_POS;
6478 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
6479 nHorzPos = scrollInfo.nPos;
6480 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
6481 nVertPos = scrollInfo.nPos;
6483 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
6485 lpptOrigin->x = infoPtr->rcList.left;
6486 lpptOrigin->y = infoPtr->rcList.top;
6487 if (infoPtr->uView == LV_VIEW_LIST)
6488 nHorzPos *= infoPtr->nItemWidth;
6489 else if (infoPtr->uView == LV_VIEW_DETAILS)
6490 nVertPos *= infoPtr->nItemHeight;
6492 lpptOrigin->x -= nHorzPos;
6493 lpptOrigin->y -= nVertPos;
6495 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
6500 * Retrieves the width of a string.
6503 * [I] hwnd : window handle
6504 * [I] lpszText : text string to process
6505 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
6508 * SUCCESS : string width (in pixels)
6511 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
6516 if (is_textT(lpszText, isW))
6518 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
6519 HDC hdc = GetDC(infoPtr->hwndSelf);
6520 HFONT hOldFont = SelectObject(hdc, hFont);
6523 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
6525 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
6526 SelectObject(hdc, hOldFont);
6527 ReleaseDC(infoPtr->hwndSelf, hdc);
6529 return stringSize.cx;
6534 * Determines which listview item is located at the specified position.
6537 * [I] infoPtr : valid pointer to the listview structure
6538 * [IO] lpht : hit test information
6539 * [I] subitem : fill out iSubItem.
6540 * [I] select : return the index only if the hit selects the item
6543 * (mm 20001022): We must not allow iSubItem to be touched, for
6544 * an app might pass only a structure with space up to iItem!
6545 * (MS Office 97 does that for instance in the file open dialog)
6548 * SUCCESS : item index
6551 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6553 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6554 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6555 POINT Origin, Position, opt;
6560 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
6564 if (subitem) lpht->iSubItem = 0;
6566 if (infoPtr->rcList.left > lpht->pt.x)
6567 lpht->flags |= LVHT_TOLEFT;
6568 else if (infoPtr->rcList.right < lpht->pt.x)
6569 lpht->flags |= LVHT_TORIGHT;
6571 if (infoPtr->rcList.top > lpht->pt.y)
6572 lpht->flags |= LVHT_ABOVE;
6573 else if (infoPtr->rcList.bottom < lpht->pt.y)
6574 lpht->flags |= LVHT_BELOW;
6576 TRACE("lpht->flags=0x%x\n", lpht->flags);
6577 if (lpht->flags) return -1;
6579 lpht->flags |= LVHT_NOWHERE;
6581 LISTVIEW_GetOrigin(infoPtr, &Origin);
6583 /* first deal with the large items */
6584 rcSearch.left = lpht->pt.x;
6585 rcSearch.top = lpht->pt.y;
6586 rcSearch.right = rcSearch.left + 1;
6587 rcSearch.bottom = rcSearch.top + 1;
6589 iterator_frameditems(&i, infoPtr, &rcSearch);
6590 iterator_next(&i); /* go to first item in the sequence */
6592 iterator_destroy(&i);
6594 TRACE("lpht->iItem=%d\n", iItem);
6595 if (iItem == -1) return -1;
6597 if (infoPtr->uView == LV_VIEW_DETAILS && subitem)
6599 RECT bounds, *pRect;
6602 /* for top/bottom only */
6603 bounds.left = LVIR_BOUNDS;
6604 LISTVIEW_GetItemRect(infoPtr, iItem, &bounds);
6606 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6608 pRect = &LISTVIEW_GetColumnInfo(infoPtr, j)->rcHeader;
6609 bounds.left = pRect->left;
6610 bounds.right = pRect->right;
6612 if (PtInRect(&bounds, lpht->pt))
6618 TRACE("lpht->iSubItem=%d\n", lpht->iSubItem);
6621 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6622 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
6623 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6624 if (infoPtr->uView == LV_VIEW_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6625 lvItem.iItem = iItem;
6626 lvItem.iSubItem = subitem ? lpht->iSubItem : 0;
6627 lvItem.pszText = szDispText;
6628 lvItem.cchTextMax = DISP_TEXT_SIZE;
6629 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6630 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6632 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6633 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6634 opt.x = lpht->pt.x - Position.x - Origin.x;
6635 opt.y = lpht->pt.y - Position.y - Origin.y;
6637 if (infoPtr->uView == LV_VIEW_DETAILS)
6641 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6642 UnionRect(&rcBounds, &rcBounds, &rcState);
6644 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
6645 if (!PtInRect(&rcBounds, opt)) return -1;
6647 if (PtInRect(&rcIcon, opt))
6648 lpht->flags |= LVHT_ONITEMICON;
6649 else if (PtInRect(&rcLabel, opt))
6650 lpht->flags |= LVHT_ONITEMLABEL;
6651 else if (infoPtr->himlState && PtInRect(&rcState, opt))
6652 lpht->flags |= LVHT_ONITEMSTATEICON;
6653 /* special case for LVS_EX_FULLROWSELECT */
6654 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
6655 !(lpht->flags & LVHT_ONITEM))
6657 lpht->flags = LVHT_ONITEM | LVHT_ABOVE;
6659 if (lpht->flags & LVHT_ONITEM)
6660 lpht->flags &= ~LVHT_NOWHERE;
6661 TRACE("lpht->flags=0x%x\n", lpht->flags);
6663 if (select && !(infoPtr->uView == LV_VIEW_DETAILS &&
6664 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6665 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6667 if (infoPtr->uView == LV_VIEW_DETAILS)
6669 /* get main item bounds */
6670 lvItem.iSubItem = 0;
6671 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6672 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6673 UnionRect(&rcBounds, &rcBounds, &rcState);
6675 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6677 return lpht->iItem = iItem;
6682 * Inserts a new item in the listview control.
6685 * [I] infoPtr : valid pointer to the listview structure
6686 * [I] lpLVItem : item information
6687 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6690 * SUCCESS : new item index
6693 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6699 BOOL is_sorted, has_changed;
6701 HWND hwndSelf = infoPtr->hwndSelf;
6703 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6705 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6707 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6708 if (!lpLVItem || lpLVItem->iSubItem) return -1;
6710 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6712 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
6714 /* insert item in listview control data structure */
6715 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6716 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6718 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6719 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6721 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
6723 /* calculate new item index */
6730 while (i < infoPtr->nItemCount)
6732 hItem = DPA_GetPtr( infoPtr->hdpaItems, i);
6733 item_s = (ITEM_INFO*)DPA_GetPtr(hItem, 0);
6735 cmpv = textcmpWT(item_s->hdr.pszText, lpLVItem->pszText, TRUE);
6736 if (infoPtr->dwStyle & LVS_SORTDESCENDING) cmpv *= -1;
6738 if (cmpv >= 0) break;
6744 nItem = min(lpLVItem->iItem, infoPtr->nItemCount);
6746 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6747 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6748 if (nItem == -1) goto fail;
6749 infoPtr->nItemCount++;
6751 /* shift indices first so they don't get tangled */
6752 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6754 /* set the item attributes */
6755 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6757 /* full size structure expected - _WIN32IE >= 0x560 */
6760 else if (lpLVItem->mask & LVIF_INDENT)
6762 /* indent member expected - _WIN32IE >= 0x300 */
6763 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6767 /* minimal structure expected */
6768 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6771 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6773 item.mask |= LVIF_STATE;
6774 item.stateMask |= LVIS_STATEIMAGEMASK;
6775 item.state &= ~LVIS_STATEIMAGEMASK;
6776 item.state |= INDEXTOSTATEIMAGEMASK(1);
6778 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6780 /* make room for the position, if we are in the right mode */
6781 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
6783 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6785 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6787 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6792 /* send LVN_INSERTITEM notification */
6793 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6795 nmlv.lParam = lpItem->lParam;
6796 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6797 if (!IsWindow(hwndSelf))
6800 /* align items (set position of each item) */
6801 if (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON)
6805 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6806 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6808 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6810 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6813 /* now is the invalidation fun */
6814 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6818 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6819 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6820 infoPtr->nItemCount--;
6822 DPA_DeletePtr(hdpaSubItems, 0);
6823 DPA_Destroy (hdpaSubItems);
6830 * Redraws a range of items.
6833 * [I] infoPtr : valid pointer to the listview structure
6834 * [I] nFirst : first item
6835 * [I] nLast : last item
6841 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6845 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6846 max(nFirst, nLast) >= infoPtr->nItemCount)
6849 for (i = nFirst; i <= nLast; i++)
6850 LISTVIEW_InvalidateItem(infoPtr, i);
6857 * Scroll the content of a listview.
6860 * [I] infoPtr : valid pointer to the listview structure
6861 * [I] dx : horizontal scroll amount in pixels
6862 * [I] dy : vertical scroll amount in pixels
6869 * If the control is in report view (LV_VIEW_DETAILS) the control can
6870 * be scrolled only in line increments. "dy" will be rounded to the
6871 * nearest number of pixels that are a whole line. Ex: if line height
6872 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6873 * is passed, then the scroll will be 0. (per MSDN 7/2002)
6875 * For: (per experimentation with native control and CSpy ListView)
6876 * LV_VIEW_ICON dy=1 = 1 pixel (vertical only)
6878 * LV_VIEW_SMALLICON dy=1 = 1 pixel (vertical only)
6880 * LV_VIEW_LIST dx=1 = 1 column (horizontal only)
6881 * but will only scroll 1 column per message
6882 * no matter what the value.
6883 * dy must be 0 or FALSE returned.
6884 * LV_VIEW_DETAILS dx=1 = 1 pixel
6888 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6890 switch(infoPtr->uView) {
6891 case LV_VIEW_DETAILS:
6892 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6893 dy /= infoPtr->nItemHeight;
6896 if (dy != 0) return FALSE;
6903 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6904 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6911 * Sets the background color.
6914 * [I] infoPtr : valid pointer to the listview structure
6915 * [I] clrBk : background color
6921 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6923 TRACE("(clrBk=%x)\n", clrBk);
6925 if(infoPtr->clrBk != clrBk) {
6926 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6927 infoPtr->clrBk = clrBk;
6928 if (clrBk == CLR_NONE)
6929 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6931 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6932 LISTVIEW_InvalidateList(infoPtr);
6938 /* LISTVIEW_SetBkImage */
6940 /*** Helper for {Insert,Set}ColumnT *only* */
6941 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
6942 const LVCOLUMNW *lpColumn, BOOL isW)
6944 if (lpColumn->mask & LVCF_FMT)
6946 /* format member is valid */
6947 lphdi->mask |= HDI_FORMAT;
6949 /* set text alignment (leftmost column must be left-aligned) */
6950 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6951 lphdi->fmt |= HDF_LEFT;
6952 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6953 lphdi->fmt |= HDF_RIGHT;
6954 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6955 lphdi->fmt |= HDF_CENTER;
6957 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6958 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6960 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6962 lphdi->fmt |= HDF_IMAGE;
6963 lphdi->iImage = I_IMAGECALLBACK;
6967 if (lpColumn->mask & LVCF_WIDTH)
6969 lphdi->mask |= HDI_WIDTH;
6970 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6972 /* make it fill the remainder of the controls width */
6976 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6978 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6979 lphdi->cxy += rcHeader.right - rcHeader.left;
6982 /* retrieve the layout of the header */
6983 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6984 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
6986 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6989 lphdi->cxy = lpColumn->cx;
6992 if (lpColumn->mask & LVCF_TEXT)
6994 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6995 lphdi->fmt |= HDF_STRING;
6996 lphdi->pszText = lpColumn->pszText;
6997 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
7000 if (lpColumn->mask & LVCF_IMAGE)
7002 lphdi->mask |= HDI_IMAGE;
7003 lphdi->iImage = lpColumn->iImage;
7006 if (lpColumn->mask & LVCF_ORDER)
7008 lphdi->mask |= HDI_ORDER;
7009 lphdi->iOrder = lpColumn->iOrder;
7016 * Inserts a new column.
7019 * [I] infoPtr : valid pointer to the listview structure
7020 * [I] nColumn : column index
7021 * [I] lpColumn : column information
7022 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
7025 * SUCCESS : new column index
7028 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
7029 const LVCOLUMNW *lpColumn, BOOL isW)
7031 COLUMN_INFO *lpColumnInfo;
7035 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
7037 if (!lpColumn || nColumn < 0) return -1;
7038 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
7040 ZeroMemory(&hdi, sizeof(HDITEMW));
7041 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
7044 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
7045 * (can be seen in SPY) otherwise column never gets added.
7047 if (!(lpColumn->mask & LVCF_WIDTH)) {
7048 hdi.mask |= HDI_WIDTH;
7053 * when the iSubItem is available Windows copies it to the header lParam. It seems
7054 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
7056 if (lpColumn->mask & LVCF_SUBITEM)
7058 hdi.mask |= HDI_LPARAM;
7059 hdi.lParam = lpColumn->iSubItem;
7062 /* create header if not present */
7063 LISTVIEW_CreateHeader(infoPtr);
7064 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle) &&
7065 (infoPtr->uView == LV_VIEW_DETAILS) && (WS_VISIBLE & infoPtr->dwStyle))
7067 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
7070 /* insert item in header control */
7071 nNewColumn = SendMessageW(infoPtr->hwndHeader,
7072 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
7073 (WPARAM)nColumn, (LPARAM)&hdi);
7074 if (nNewColumn == -1) return -1;
7075 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
7077 /* create our own column info */
7078 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
7079 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
7081 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
7082 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, nNewColumn, (LPARAM)&lpColumnInfo->rcHeader))
7085 /* now we have to actually adjust the data */
7086 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
7088 SUBITEM_INFO *lpSubItem;
7092 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
7094 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
7095 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
7097 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
7098 if (lpSubItem->iSubItem >= nNewColumn)
7099 lpSubItem->iSubItem++;
7104 /* make space for the new column */
7105 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
7106 LISTVIEW_UpdateItemSize(infoPtr);
7111 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
7114 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
7122 * Sets the attributes of a header item.
7125 * [I] infoPtr : valid pointer to the listview structure
7126 * [I] nColumn : column index
7127 * [I] lpColumn : column attributes
7128 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
7134 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
7135 const LVCOLUMNW *lpColumn, BOOL isW)
7137 HDITEMW hdi, hdiget;
7140 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
7142 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7144 ZeroMemory(&hdi, sizeof(HDITEMW));
7145 if (lpColumn->mask & LVCF_FMT)
7147 hdi.mask |= HDI_FORMAT;
7148 hdiget.mask = HDI_FORMAT;
7149 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
7150 hdi.fmt = hdiget.fmt & HDF_STRING;
7152 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
7154 /* set header item attributes */
7155 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
7156 if (!bResult) return FALSE;
7158 if (lpColumn->mask & LVCF_FMT)
7160 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
7161 int oldFmt = lpColumnInfo->fmt;
7163 lpColumnInfo->fmt = lpColumn->fmt;
7164 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
7166 if (infoPtr->uView == LV_VIEW_DETAILS) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
7175 * Sets the column order array
7178 * [I] infoPtr : valid pointer to the listview structure
7179 * [I] iCount : number of elements in column order array
7180 * [I] lpiArray : pointer to column order array
7186 static BOOL LISTVIEW_SetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
7188 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
7199 * Sets the width of a column
7202 * [I] infoPtr : valid pointer to the listview structure
7203 * [I] nColumn : column index
7204 * [I] cx : column width
7210 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
7212 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
7216 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
7218 /* set column width only if in report or list mode */
7219 if (infoPtr->uView != LV_VIEW_DETAILS && infoPtr->uView != LV_VIEW_LIST) return FALSE;
7221 /* take care of invalid cx values */
7222 if(infoPtr->uView == LV_VIEW_DETAILS && cx < -2) cx = LVSCW_AUTOSIZE;
7223 else if (infoPtr->uView == LV_VIEW_LIST && cx < 1) return FALSE;
7225 /* resize all columns if in LV_VIEW_LIST mode */
7226 if(infoPtr->uView == LV_VIEW_LIST)
7228 infoPtr->nItemWidth = cx;
7229 LISTVIEW_InvalidateList(infoPtr);
7233 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7235 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
7240 lvItem.mask = LVIF_TEXT;
7242 lvItem.iSubItem = nColumn;
7243 lvItem.pszText = szDispText;
7244 lvItem.cchTextMax = DISP_TEXT_SIZE;
7245 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7247 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
7248 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
7249 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
7251 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
7252 max_cx += infoPtr->iconSize.cx;
7253 max_cx += TRAILING_LABEL_PADDING;
7256 /* autosize based on listview items width */
7257 if(cx == LVSCW_AUTOSIZE)
7259 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
7261 /* if iCol is the last column make it fill the remainder of the controls width */
7262 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
7267 LISTVIEW_GetOrigin(infoPtr, &Origin);
7268 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
7270 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
7274 /* Despite what the MS docs say, if this is not the last
7275 column, then MS resizes the column to the width of the
7276 largest text string in the column, including headers
7277 and items. This is different from LVSCW_AUTOSIZE in that
7278 LVSCW_AUTOSIZE ignores the header string length. */
7281 /* retrieve header text */
7282 hdi.mask = HDI_TEXT;
7283 hdi.cchTextMax = DISP_TEXT_SIZE;
7284 hdi.pszText = szDispText;
7285 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdi))
7287 HDC hdc = GetDC(infoPtr->hwndSelf);
7288 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
7291 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
7292 cx = size.cx + TRAILING_HEADER_PADDING;
7293 /* FIXME: Take into account the header image, if one is present */
7294 SelectObject(hdc, old_font);
7295 ReleaseDC(infoPtr->hwndSelf, hdc);
7297 cx = max (cx, max_cx);
7301 if (cx < 0) return FALSE;
7303 /* call header to update the column change */
7304 hdi.mask = HDI_WIDTH;
7306 TRACE("hdi.cxy=%d\n", hdi.cxy);
7307 return Header_SetItemW(infoPtr->hwndHeader, nColumn, &hdi);
7311 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
7314 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
7317 HBITMAP hbm_im, hbm_mask, hbm_orig;
7319 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
7320 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
7323 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
7324 ILC_COLOR | ILC_MASK, 2, 2);
7325 hdc_wnd = GetDC(infoPtr->hwndSelf);
7326 hdc = CreateCompatibleDC(hdc_wnd);
7327 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
7328 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
7329 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
7331 rc.left = rc.top = 0;
7332 rc.right = GetSystemMetrics(SM_CXSMICON);
7333 rc.bottom = GetSystemMetrics(SM_CYSMICON);
7335 hbm_orig = SelectObject(hdc, hbm_mask);
7336 FillRect(hdc, &rc, hbr_white);
7337 InflateRect(&rc, -2, -2);
7338 FillRect(hdc, &rc, hbr_black);
7340 SelectObject(hdc, hbm_im);
7341 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
7342 SelectObject(hdc, hbm_orig);
7343 ImageList_Add(himl, hbm_im, hbm_mask);
7345 SelectObject(hdc, hbm_im);
7346 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
7347 SelectObject(hdc, hbm_orig);
7348 ImageList_Add(himl, hbm_im, hbm_mask);
7350 DeleteObject(hbm_mask);
7351 DeleteObject(hbm_im);
7359 * Sets the extended listview style.
7362 * [I] infoPtr : valid pointer to the listview structure
7364 * [I] dwStyle : style
7367 * SUCCESS : previous style
7370 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwExStyle)
7372 DWORD dwOldExStyle = infoPtr->dwLvExStyle;
7376 infoPtr->dwLvExStyle = (dwOldExStyle & ~dwMask) | (dwExStyle & dwMask);
7378 infoPtr->dwLvExStyle = dwExStyle;
7380 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_CHECKBOXES)
7382 HIMAGELIST himl = 0;
7383 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7386 item.mask = LVIF_STATE;
7387 item.stateMask = LVIS_STATEIMAGEMASK;
7388 item.state = INDEXTOSTATEIMAGEMASK(1);
7389 LISTVIEW_SetItemState(infoPtr, -1, &item);
7391 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
7393 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
7396 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_HEADERDRAGDROP)
7400 /* if not already created */
7401 LISTVIEW_CreateHeader(infoPtr);
7403 dwStyle = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
7404 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
7405 dwStyle |= HDS_DRAGDROP;
7407 dwStyle &= ~HDS_DRAGDROP;
7408 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, dwStyle);
7411 /* GRIDLINES adds decoration at top so changes sizes */
7412 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_GRIDLINES)
7414 LISTVIEW_UpdateSize(infoPtr);
7418 LISTVIEW_InvalidateList(infoPtr);
7419 return dwOldExStyle;
7424 * Sets the new hot cursor used during hot tracking and hover selection.
7427 * [I] infoPtr : valid pointer to the listview structure
7428 * [I] hCursor : the new hot cursor handle
7431 * Returns the previous hot cursor
7433 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
7435 HCURSOR oldCursor = infoPtr->hHotCursor;
7437 infoPtr->hHotCursor = hCursor;
7445 * Sets the hot item index.
7448 * [I] infoPtr : valid pointer to the listview structure
7449 * [I] iIndex : index
7452 * SUCCESS : previous hot item index
7453 * FAILURE : -1 (no hot item)
7455 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
7457 INT iOldIndex = infoPtr->nHotItem;
7459 infoPtr->nHotItem = iIndex;
7467 * Sets the amount of time the cursor must hover over an item before it is selected.
7470 * [I] infoPtr : valid pointer to the listview structure
7471 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
7474 * Returns the previous hover time
7476 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
7478 DWORD oldHoverTime = infoPtr->dwHoverTime;
7480 infoPtr->dwHoverTime = dwHoverTime;
7482 return oldHoverTime;
7487 * Sets spacing for icons of LVS_ICON style.
7490 * [I] infoPtr : valid pointer to the listview structure
7491 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
7492 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
7495 * MAKELONG(oldcx, oldcy)
7497 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
7499 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7501 TRACE("requested=(%d,%d)\n", cx, cy);
7503 /* this is supported only for LVS_ICON style */
7504 if (infoPtr->uView != LV_VIEW_ICON) return oldspacing;
7506 /* set to defaults, if instructed to */
7507 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
7508 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
7510 /* if 0 then compute width
7511 * FIXME: Should scan each item and determine max width of
7512 * icon or label, then make that the width */
7514 cx = infoPtr->iconSpacing.cx;
7516 /* if 0 then compute height */
7518 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
7519 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
7522 infoPtr->iconSpacing.cx = cx;
7523 infoPtr->iconSpacing.cy = cy;
7525 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
7526 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
7527 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
7528 infoPtr->ntmHeight);
7530 /* these depend on the iconSpacing */
7531 LISTVIEW_UpdateItemSize(infoPtr);
7536 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
7540 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
7547 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
7548 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
7557 * [I] infoPtr : valid pointer to the listview structure
7558 * [I] nType : image list type
7559 * [I] himl : image list handle
7562 * SUCCESS : old image list
7565 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
7567 INT oldHeight = infoPtr->nItemHeight;
7568 HIMAGELIST himlOld = 0;
7570 TRACE("(nType=%d, himl=%p\n", nType, himl);
7575 himlOld = infoPtr->himlNormal;
7576 infoPtr->himlNormal = himl;
7577 if (infoPtr->uView == LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
7578 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
7582 himlOld = infoPtr->himlSmall;
7583 infoPtr->himlSmall = himl;
7584 if (infoPtr->uView != LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
7588 himlOld = infoPtr->himlState;
7589 infoPtr->himlState = himl;
7590 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
7591 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
7595 ERR("Unknown icon type=%d\n", nType);
7599 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
7600 if (infoPtr->nItemHeight != oldHeight)
7601 LISTVIEW_UpdateScroll(infoPtr);
7608 * Preallocates memory (does *not* set the actual count of items !)
7611 * [I] infoPtr : valid pointer to the listview structure
7612 * [I] nItems : item count (projected number of items to allocate)
7613 * [I] dwFlags : update flags
7619 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7621 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
7623 if (infoPtr->dwStyle & LVS_OWNERDATA)
7625 INT nOldCount = infoPtr->nItemCount;
7627 if (nItems < nOldCount)
7629 RANGE range = { nItems, nOldCount };
7630 ranges_del(infoPtr->selectionRanges, range);
7631 if (infoPtr->nFocusedItem >= nItems)
7633 LISTVIEW_SetItemFocus(infoPtr, -1);
7634 SetRectEmpty(&infoPtr->rcFocus);
7638 infoPtr->nItemCount = nItems;
7639 LISTVIEW_UpdateScroll(infoPtr);
7641 /* the flags are valid only in ownerdata report and list modes */
7642 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON) dwFlags = 0;
7644 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7645 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7647 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7648 LISTVIEW_InvalidateList(infoPtr);
7655 LISTVIEW_GetOrigin(infoPtr, &Origin);
7656 nFrom = min(nOldCount, nItems);
7657 nTo = max(nOldCount, nItems);
7659 if (infoPtr->uView == LV_VIEW_DETAILS)
7662 rcErase.top = nFrom * infoPtr->nItemHeight;
7663 rcErase.right = infoPtr->nItemWidth;
7664 rcErase.bottom = nTo * infoPtr->nItemHeight;
7665 OffsetRect(&rcErase, Origin.x, Origin.y);
7666 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7667 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7669 else /* LV_VIEW_LIST */
7671 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
7673 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
7674 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
7675 rcErase.right = rcErase.left + infoPtr->nItemWidth;
7676 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7677 OffsetRect(&rcErase, Origin.x, Origin.y);
7678 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7679 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7681 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
7683 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
7684 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7685 OffsetRect(&rcErase, Origin.x, Origin.y);
7686 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7687 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7693 /* According to MSDN for non-LVS_OWNERDATA this is just
7694 * a performance issue. The control allocates its internal
7695 * data structures for the number of items specified. It
7696 * cuts down on the number of memory allocations. Therefore
7697 * we will just issue a WARN here
7699 WARN("for non-ownerdata performance option not implemented.\n");
7707 * Sets the position of an item.
7710 * [I] infoPtr : valid pointer to the listview structure
7711 * [I] nItem : item index
7712 * [I] pt : coordinate
7718 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7722 TRACE("(nItem=%d, &pt=%s\n", nItem, wine_dbgstr_point(&pt));
7724 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7725 !(infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)) return FALSE;
7727 LISTVIEW_GetOrigin(infoPtr, &Origin);
7729 /* This point value seems to be an undocumented feature.
7730 * The best guess is that it means either at the origin,
7731 * or at true beginning of the list. I will assume the origin. */
7732 if ((pt.x == -1) && (pt.y == -1))
7735 if (infoPtr->uView == LV_VIEW_ICON)
7737 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7738 pt.y -= ICON_TOP_PADDING;
7743 infoPtr->bAutoarrange = FALSE;
7745 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7750 * Sets the state of one or many items.
7753 * [I] infoPtr : valid pointer to the listview structure
7754 * [I] nItem : item index
7755 * [I] lpLVItem : item or subitem info
7761 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7763 BOOL bResult = TRUE;
7766 lvItem.iItem = nItem;
7767 lvItem.iSubItem = 0;
7768 lvItem.mask = LVIF_STATE;
7769 lvItem.state = lpLVItem->state;
7770 lvItem.stateMask = lpLVItem->stateMask;
7771 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7775 /* select all isn't allowed in LVS_SINGLESEL */
7776 if ((lvItem.state & lvItem.stateMask & LVIS_SELECTED) && (infoPtr->dwStyle & LVS_SINGLESEL))
7779 /* apply to all items */
7780 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7781 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7784 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7787 * Update selection mark
7789 * Investigation on windows 2k showed that selection mark was updated
7790 * whenever a new selection was made, but if the selected item was
7791 * unselected it was not updated.
7793 * we are probably still not 100% accurate, but this at least sets the
7794 * proper selection mark when it is needed
7797 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7798 (infoPtr->nSelectionMark == -1))
7801 for (i = 0; i < infoPtr->nItemCount; i++)
7803 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7805 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7807 infoPtr->nSelectionMark = i;
7811 else if (ranges_contain(infoPtr->selectionRanges, i))
7813 infoPtr->nSelectionMark = i;
7824 * Sets the text of an item or subitem.
7827 * [I] hwnd : window handle
7828 * [I] nItem : item index
7829 * [I] lpLVItem : item or subitem info
7830 * [I] isW : TRUE if input is Unicode
7836 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7840 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7842 lvItem.iItem = nItem;
7843 lvItem.iSubItem = lpLVItem->iSubItem;
7844 lvItem.mask = LVIF_TEXT;
7845 lvItem.pszText = lpLVItem->pszText;
7846 lvItem.cchTextMax = lpLVItem->cchTextMax;
7848 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7850 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7855 * Set item index that marks the start of a multiple selection.
7858 * [I] infoPtr : valid pointer to the listview structure
7859 * [I] nIndex : index
7862 * Index number or -1 if there is no selection mark.
7864 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7866 INT nOldIndex = infoPtr->nSelectionMark;
7868 TRACE("(nIndex=%d)\n", nIndex);
7870 infoPtr->nSelectionMark = nIndex;
7877 * Sets the text background color.
7880 * [I] infoPtr : valid pointer to the listview structure
7881 * [I] clrTextBk : text background color
7887 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7889 TRACE("(clrTextBk=%x)\n", clrTextBk);
7891 if (infoPtr->clrTextBk != clrTextBk)
7893 infoPtr->clrTextBk = clrTextBk;
7894 LISTVIEW_InvalidateList(infoPtr);
7902 * Sets the text foreground color.
7905 * [I] infoPtr : valid pointer to the listview structure
7906 * [I] clrText : text color
7912 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7914 TRACE("(clrText=%x)\n", clrText);
7916 if (infoPtr->clrText != clrText)
7918 infoPtr->clrText = clrText;
7919 LISTVIEW_InvalidateList(infoPtr);
7927 * Sets new ToolTip window to ListView control.
7930 * [I] infoPtr : valid pointer to the listview structure
7931 * [I] hwndNewToolTip : handle to new ToolTip
7936 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7938 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7939 infoPtr->hwndToolTip = hwndNewToolTip;
7940 return hwndOldToolTip;
7945 * sets the Unicode character format flag for the control
7947 * [I] infoPtr :valid pointer to the listview structure
7948 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
7951 * Old Unicode Format
7953 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL fUnicode)
7955 SHORT rc = infoPtr->notifyFormat;
7956 infoPtr->notifyFormat = (fUnicode) ? NFR_UNICODE : NFR_ANSI;
7957 return rc == NFR_UNICODE;
7962 * sets the control view mode
7964 * [I] infoPtr :valid pointer to the listview structure
7965 * [I] nView :new view mode value
7971 static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView)
7973 SIZE oldIconSize = infoPtr->iconSize;
7976 if (infoPtr->uView == nView) return 1;
7978 if ((INT)nView < 0 || nView > LV_VIEW_MAX) return -1;
7979 if (nView == LV_VIEW_TILE)
7981 FIXME("View LV_VIEW_TILE unimplemented\n");
7985 infoPtr->uView = nView;
7987 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7988 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
7990 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
7991 SetRectEmpty(&infoPtr->rcFocus);
7993 himl = (nView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
7994 set_icon_size(&infoPtr->iconSize, himl, nView != LV_VIEW_ICON);
7999 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
8001 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
8002 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
8003 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
8005 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8007 case LV_VIEW_SMALLICON:
8008 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8010 case LV_VIEW_DETAILS:
8015 LISTVIEW_CreateHeader( infoPtr );
8017 hl.prc = &infoPtr->rcList;
8019 SendMessageW(infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl);
8020 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
8021 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
8028 LISTVIEW_UpdateItemSize(infoPtr);
8029 LISTVIEW_UpdateSize(infoPtr);
8030 LISTVIEW_UpdateScroll(infoPtr);
8031 LISTVIEW_InvalidateList(infoPtr);
8033 TRACE("nView=%d\n", nView);
8038 /* LISTVIEW_SetWorkAreas */
8042 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMS
8045 * [I] first : pointer to first ITEM_INFO to compare
8046 * [I] second : pointer to second ITEM_INFO to compare
8047 * [I] lParam : HWND of control
8050 * if first comes before second : negative
8051 * if first comes after second : positive
8052 * if first and second are equivalent : zero
8054 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
8056 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
8057 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
8058 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
8060 /* Forward the call to the client defined callback */
8061 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
8066 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMSEX
8069 * [I] first : pointer to first ITEM_INFO to compare
8070 * [I] second : pointer to second ITEM_INFO to compare
8071 * [I] lParam : HWND of control
8074 * if first comes before second : negative
8075 * if first comes after second : positive
8076 * if first and second are equivalent : zero
8078 static INT WINAPI LISTVIEW_CallBackCompareEx(LPVOID first, LPVOID second, LPARAM lParam)
8080 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
8081 INT first_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, first );
8082 INT second_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, second );
8084 /* Forward the call to the client defined callback */
8085 return (infoPtr->pfnCompare)( first_idx, second_idx, infoPtr->lParamSort );
8090 * Sorts the listview items.
8093 * [I] infoPtr : valid pointer to the listview structure
8094 * [I] pfnCompare : application-defined value
8095 * [I] lParamSort : pointer to comparison callback
8096 * [I] IsEx : TRUE when LVM_SORTITEMSEX used
8102 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare,
8103 LPARAM lParamSort, BOOL IsEx)
8107 LPVOID selectionMarkItem = NULL;
8108 LPVOID focusedItem = NULL;
8111 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
8113 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
8115 if (!pfnCompare) return FALSE;
8116 if (!infoPtr->hdpaItems) return FALSE;
8118 /* if there are 0 or 1 items, there is no need to sort */
8119 if (infoPtr->nItemCount < 2) return TRUE;
8121 /* clear selection */
8122 ranges_clear(infoPtr->selectionRanges);
8124 /* save selection mark and focused item */
8125 if (infoPtr->nSelectionMark >= 0)
8126 selectionMarkItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark);
8127 if (infoPtr->nFocusedItem >= 0)
8128 focusedItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
8130 infoPtr->pfnCompare = pfnCompare;
8131 infoPtr->lParamSort = lParamSort;
8133 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompareEx, (LPARAM)infoPtr);
8135 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
8137 /* restore selection ranges */
8138 for (i=0; i < infoPtr->nItemCount; i++)
8140 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
8141 lpItem = DPA_GetPtr(hdpaSubItems, 0);
8143 if (lpItem->state & LVIS_SELECTED)
8144 ranges_additem(infoPtr->selectionRanges, i);
8146 /* restore selection mark and focused item */
8147 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
8148 infoPtr->nFocusedItem = DPA_GetPtrIndex(infoPtr->hdpaItems, focusedItem);
8150 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
8152 /* refresh the display */
8153 if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON)
8154 LISTVIEW_InvalidateList(infoPtr);
8161 * Update theme handle after a theme change.
8164 * [I] infoPtr : valid pointer to the listview structure
8168 * FAILURE : something else
8170 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
8172 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8173 CloseThemeData(theme);
8174 OpenThemeData(infoPtr->hwndSelf, themeClass);
8180 * Updates an items or rearranges the listview control.
8183 * [I] infoPtr : valid pointer to the listview structure
8184 * [I] nItem : item index
8190 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
8192 TRACE("(nItem=%d)\n", nItem);
8194 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
8196 /* rearrange with default alignment style */
8197 if (is_autoarrange(infoPtr))
8198 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8200 LISTVIEW_InvalidateItem(infoPtr, nItem);
8207 * Draw the track line at the place defined in the infoPtr structure.
8208 * The line is drawn with a XOR pen so drawing the line for the second time
8209 * in the same place erases the line.
8212 * [I] infoPtr : valid pointer to the listview structure
8218 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
8224 if (infoPtr->xTrackLine == -1)
8227 if (!(hdc = GetDC(infoPtr->hwndSelf)))
8229 hOldPen = SelectObject(hdc, GetStockObject(BLACK_PEN));
8230 oldROP = SetROP2(hdc, R2_XORPEN);
8231 MoveToEx(hdc, infoPtr->xTrackLine, infoPtr->rcList.top, NULL);
8232 LineTo(hdc, infoPtr->xTrackLine, infoPtr->rcList.bottom);
8233 SetROP2(hdc, oldROP);
8234 SelectObject(hdc, hOldPen);
8235 ReleaseDC(infoPtr->hwndSelf, hdc);
8241 * Called when an edit control should be displayed. This function is called after
8242 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
8245 * [I] hwnd : Handle to the listview
8246 * [I] uMsg : WM_TIMER (ignored)
8247 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
8248 * [I] dwTimer : The elapsed time (ignored)
8253 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
8255 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
8256 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8258 KillTimer(hwnd, idEvent);
8259 editItem->fEnabled = FALSE;
8260 /* check if the item is still selected */
8261 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
8262 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
8267 * Creates the listview control - the WM_NCCREATE phase.
8270 * [I] hwnd : window handle
8271 * [I] lpcs : the create parameters
8277 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
8279 LISTVIEW_INFO *infoPtr;
8282 TRACE("(lpcs=%p)\n", lpcs);
8284 /* initialize info pointer */
8285 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
8286 if (!infoPtr) return FALSE;
8288 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
8290 infoPtr->hwndSelf = hwnd;
8291 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
8292 map_style_view(infoPtr);
8293 /* determine the type of structures to use */
8294 infoPtr->hwndNotify = lpcs->hwndParent;
8295 /* infoPtr->notifyFormat will be filled in WM_CREATE */
8297 /* initialize color information */
8298 infoPtr->clrBk = CLR_NONE;
8299 infoPtr->clrText = CLR_DEFAULT;
8300 infoPtr->clrTextBk = CLR_DEFAULT;
8301 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
8303 /* set default values */
8304 infoPtr->nFocusedItem = -1;
8305 infoPtr->nSelectionMark = -1;
8306 infoPtr->nHotItem = -1;
8307 infoPtr->bRedraw = TRUE;
8308 infoPtr->bNoItemMetrics = TRUE;
8309 infoPtr->bDoChangeNotify = TRUE;
8310 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
8311 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
8312 infoPtr->nEditLabelItem = -1;
8313 infoPtr->nLButtonDownItem = -1;
8314 infoPtr->dwHoverTime = -1; /* default system hover time */
8315 infoPtr->nMeasureItemHeight = 0;
8316 infoPtr->xTrackLine = -1; /* no track line */
8317 infoPtr->itemEdit.fEnabled = FALSE;
8318 infoPtr->iVersion = COMCTL32_VERSION;
8320 /* get default font (icon title) */
8321 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
8322 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
8323 infoPtr->hFont = infoPtr->hDefaultFont;
8324 LISTVIEW_SaveTextMetrics(infoPtr);
8326 /* allocate memory for the data structure */
8327 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
8328 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
8329 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
8330 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
8331 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
8335 DestroyWindow(infoPtr->hwndHeader);
8336 ranges_destroy(infoPtr->selectionRanges);
8337 DPA_Destroy(infoPtr->hdpaItems);
8338 DPA_Destroy(infoPtr->hdpaPosX);
8339 DPA_Destroy(infoPtr->hdpaPosY);
8340 DPA_Destroy(infoPtr->hdpaColumns);
8347 * Creates the listview control - the WM_CREATE phase. Most of the data is
8348 * already set up in LISTVIEW_NCCreate
8351 * [I] hwnd : window handle
8352 * [I] lpcs : the create parameters
8358 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
8360 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8362 TRACE("(lpcs=%p)\n", lpcs);
8364 infoPtr->dwStyle = lpcs->style;
8365 map_style_view(infoPtr);
8367 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
8368 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
8369 /* on error defaulting to ANSI notifications */
8370 if (infoPtr->notifyFormat == 0) infoPtr->notifyFormat = NFR_ANSI;
8372 if ((infoPtr->uView == LV_VIEW_DETAILS) && (lpcs->style & WS_VISIBLE))
8374 if (LISTVIEW_CreateHeader(infoPtr) < 0) return -1;
8377 infoPtr->hwndHeader = 0;
8379 /* init item size to avoid division by 0 */
8380 LISTVIEW_UpdateItemSize (infoPtr);
8382 if (infoPtr->uView == LV_VIEW_DETAILS)
8384 if (!(LVS_NOCOLUMNHEADER & lpcs->style) && (WS_VISIBLE & lpcs->style))
8386 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8388 LISTVIEW_UpdateScroll(infoPtr);
8391 OpenThemeData(hwnd, themeClass);
8393 /* initialize the icon sizes */
8394 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, infoPtr->uView != LV_VIEW_ICON);
8395 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
8401 * Destroys the listview control.
8404 * [I] infoPtr : valid pointer to the listview structure
8410 static LRESULT LISTVIEW_Destroy(const LISTVIEW_INFO *infoPtr)
8412 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8413 CloseThemeData(theme);
8419 * Enables the listview control.
8422 * [I] infoPtr : valid pointer to the listview structure
8423 * [I] bEnable : specifies whether to enable or disable the window
8429 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr, BOOL bEnable)
8431 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
8432 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
8438 * Erases the background of the listview control.
8441 * [I] infoPtr : valid pointer to the listview structure
8442 * [I] hdc : device context handle
8448 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
8452 TRACE("(hdc=%p)\n", hdc);
8454 if (!GetClipBox(hdc, &rc)) return FALSE;
8456 if (infoPtr->clrBk == CLR_NONE)
8457 return SendMessageW(infoPtr->hwndNotify, WM_ERASEBKGND, (WPARAM)hdc, 0);
8459 /* for double buffered controls we need to do this during refresh */
8460 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
8462 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
8468 * Helper function for LISTVIEW_[HV]Scroll *only*.
8469 * Performs vertical/horizontal scrolling by a give amount.
8472 * [I] infoPtr : valid pointer to the listview structure
8473 * [I] dx : amount of horizontal scroll
8474 * [I] dy : amount of vertical scroll
8476 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
8478 /* now we can scroll the list */
8479 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
8480 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
8481 /* if we have focus, adjust rect */
8482 OffsetRect(&infoPtr->rcFocus, dx, dy);
8483 UpdateWindow(infoPtr->hwndSelf);
8488 * Performs vertical scrolling.
8491 * [I] infoPtr : valid pointer to the listview structure
8492 * [I] nScrollCode : scroll code
8493 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8494 * [I] hScrollWnd : scrollbar control window handle
8500 * SB_LINEUP/SB_LINEDOWN:
8501 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
8502 * for LVS_REPORT is 1 line
8503 * for LVS_LIST cannot occur
8506 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8507 INT nScrollDiff, HWND hScrollWnd)
8509 INT nOldScrollPos, nNewScrollPos;
8510 SCROLLINFO scrollInfo;
8513 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8514 debugscrollcode(nScrollCode), nScrollDiff);
8516 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8518 scrollInfo.cbSize = sizeof(SCROLLINFO);
8519 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8521 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
8523 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
8525 nOldScrollPos = scrollInfo.nPos;
8526 switch (nScrollCode)
8532 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
8536 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
8540 nScrollDiff = -scrollInfo.nPage;
8544 nScrollDiff = scrollInfo.nPage;
8547 case SB_THUMBPOSITION:
8549 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8556 /* quit right away if pos isn't changing */
8557 if (nScrollDiff == 0) return 0;
8559 /* calculate new position, and handle overflows */
8560 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8561 if (nScrollDiff > 0) {
8562 if (nNewScrollPos < nOldScrollPos ||
8563 nNewScrollPos > scrollInfo.nMax)
8564 nNewScrollPos = scrollInfo.nMax;
8566 if (nNewScrollPos > nOldScrollPos ||
8567 nNewScrollPos < scrollInfo.nMin)
8568 nNewScrollPos = scrollInfo.nMin;
8571 /* set the new position, and reread in case it changed */
8572 scrollInfo.fMask = SIF_POS;
8573 scrollInfo.nPos = nNewScrollPos;
8574 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
8576 /* carry on only if it really changed */
8577 if (nNewScrollPos == nOldScrollPos) return 0;
8579 /* now adjust to client coordinates */
8580 nScrollDiff = nOldScrollPos - nNewScrollPos;
8581 if (infoPtr->uView == LV_VIEW_DETAILS) nScrollDiff *= infoPtr->nItemHeight;
8583 /* and scroll the window */
8584 scroll_list(infoPtr, 0, nScrollDiff);
8591 * Performs horizontal scrolling.
8594 * [I] infoPtr : valid pointer to the listview structure
8595 * [I] nScrollCode : scroll code
8596 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8597 * [I] hScrollWnd : scrollbar control window handle
8603 * SB_LINELEFT/SB_LINERIGHT:
8604 * for LVS_ICON, LVS_SMALLICON 1 pixel
8605 * for LVS_REPORT is 1 pixel
8606 * for LVS_LIST is 1 column --> which is a 1 because the
8607 * scroll is based on columns not pixels
8610 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8611 INT nScrollDiff, HWND hScrollWnd)
8613 INT nOldScrollPos, nNewScrollPos;
8614 SCROLLINFO scrollInfo;
8616 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8617 debugscrollcode(nScrollCode), nScrollDiff);
8619 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8621 scrollInfo.cbSize = sizeof(SCROLLINFO);
8622 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8624 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
8626 nOldScrollPos = scrollInfo.nPos;
8628 switch (nScrollCode)
8642 nScrollDiff = -scrollInfo.nPage;
8646 nScrollDiff = scrollInfo.nPage;
8649 case SB_THUMBPOSITION:
8651 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8658 /* quit right away if pos isn't changing */
8659 if (nScrollDiff == 0) return 0;
8661 /* calculate new position, and handle overflows */
8662 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8663 if (nScrollDiff > 0) {
8664 if (nNewScrollPos < nOldScrollPos ||
8665 nNewScrollPos > scrollInfo.nMax)
8666 nNewScrollPos = scrollInfo.nMax;
8668 if (nNewScrollPos > nOldScrollPos ||
8669 nNewScrollPos < scrollInfo.nMin)
8670 nNewScrollPos = scrollInfo.nMin;
8673 /* set the new position, and reread in case it changed */
8674 scrollInfo.fMask = SIF_POS;
8675 scrollInfo.nPos = nNewScrollPos;
8676 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
8678 /* carry on only if it really changed */
8679 if (nNewScrollPos == nOldScrollPos) return 0;
8681 if (infoPtr->uView == LV_VIEW_DETAILS)
8682 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
8684 /* now adjust to client coordinates */
8685 nScrollDiff = nOldScrollPos - nNewScrollPos;
8686 if (infoPtr->uView == LV_VIEW_LIST) nScrollDiff *= infoPtr->nItemWidth;
8688 /* and scroll the window */
8689 scroll_list(infoPtr, nScrollDiff, 0);
8694 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
8696 INT gcWheelDelta = 0;
8697 INT pulScrollLines = 3;
8698 SCROLLINFO scrollInfo;
8700 TRACE("(wheelDelta=%d)\n", wheelDelta);
8702 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
8703 gcWheelDelta -= wheelDelta;
8705 scrollInfo.cbSize = sizeof(SCROLLINFO);
8706 scrollInfo.fMask = SIF_POS;
8708 switch(infoPtr->uView)
8711 case LV_VIEW_SMALLICON:
8713 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
8714 * should be fixed in the future.
8716 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
8717 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
8720 case LV_VIEW_DETAILS:
8721 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
8723 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
8724 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
8725 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
8730 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
8741 * [I] infoPtr : valid pointer to the listview structure
8742 * [I] nVirtualKey : virtual key
8743 * [I] lKeyData : key data
8748 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
8750 HWND hwndSelf = infoPtr->hwndSelf;
8752 NMLVKEYDOWN nmKeyDown;
8754 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
8756 /* send LVN_KEYDOWN notification */
8757 nmKeyDown.wVKey = nVirtualKey;
8758 nmKeyDown.flags = 0;
8759 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
8760 if (!IsWindow(hwndSelf))
8763 switch (nVirtualKey)
8766 nItem = infoPtr->nFocusedItem;
8767 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8768 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
8772 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
8774 if (!notify(infoPtr, NM_RETURN)) return 0;
8775 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
8780 if (infoPtr->nItemCount > 0)
8785 if (infoPtr->nItemCount > 0)
8786 nItem = infoPtr->nItemCount - 1;
8790 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TOLEFT);
8794 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_ABOVE);
8798 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TORIGHT);
8802 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_BELOW);
8806 if (infoPtr->uView == LV_VIEW_DETAILS)
8808 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8809 if (infoPtr->nFocusedItem == topidx)
8810 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
8815 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
8816 * LISTVIEW_GetCountPerRow(infoPtr);
8817 if(nItem < 0) nItem = 0;
8821 if (infoPtr->uView == LV_VIEW_DETAILS)
8823 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8824 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
8825 if (infoPtr->nFocusedItem == topidx + cnt - 1)
8826 nItem = infoPtr->nFocusedItem + cnt - 1;
8828 nItem = topidx + cnt - 1;
8831 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
8832 * LISTVIEW_GetCountPerRow(infoPtr);
8833 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
8837 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
8838 LISTVIEW_KeySelection(infoPtr, nItem, nVirtualKey == VK_SPACE);
8848 * [I] infoPtr : valid pointer to the listview structure
8853 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
8857 /* if we did not have the focus, there's nothing to do */
8858 if (!infoPtr->bFocus) return 0;
8860 /* send NM_KILLFOCUS notification */
8861 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
8863 /* if we have a focus rectangle, get rid of it */
8864 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
8866 /* set window focus flag */
8867 infoPtr->bFocus = FALSE;
8869 /* invalidate the selected items before resetting focus flag */
8870 LISTVIEW_InvalidateSelectedItems(infoPtr);
8877 * Processes double click messages (left mouse button).
8880 * [I] infoPtr : valid pointer to the listview structure
8881 * [I] wKey : key flag
8882 * [I] x,y : mouse coordinate
8887 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8889 LVHITTESTINFO htInfo;
8891 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8893 /* Cancel the item edition if any */
8894 if (infoPtr->itemEdit.fEnabled)
8896 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
8897 infoPtr->itemEdit.fEnabled = FALSE;
8900 /* send NM_RELEASEDCAPTURE notification */
8901 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8906 /* send NM_DBLCLK notification */
8907 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
8908 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
8910 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
8911 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
8918 * Processes mouse down messages (left mouse button).
8921 * infoPtr [I ] valid pointer to the listview structure
8922 * wKey [I ] key flag
8923 * x,y [I ] mouse coordinate
8928 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8930 LVHITTESTINFO lvHitTestInfo;
8931 static BOOL bGroupSelect = TRUE;
8932 POINT pt = { x, y };
8935 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8937 /* send NM_RELEASEDCAPTURE notification */
8938 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8940 /* set left button down flag and record the click position */
8941 infoPtr->bLButtonDown = TRUE;
8942 infoPtr->ptClickPos = pt;
8943 infoPtr->bDragging = FALSE;
8945 lvHitTestInfo.pt.x = x;
8946 lvHitTestInfo.pt.y = y;
8948 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8949 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
8950 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8952 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8954 toggle_checkbox_state(infoPtr, nItem);
8958 if (infoPtr->dwStyle & LVS_SINGLESEL)
8960 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8961 infoPtr->nEditLabelItem = nItem;
8963 LISTVIEW_SetSelection(infoPtr, nItem);
8967 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8971 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
8972 LISTVIEW_SetItemFocus(infoPtr, nItem);
8973 infoPtr->nSelectionMark = nItem;
8979 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8980 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8982 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8983 infoPtr->nSelectionMark = nItem;
8986 else if (wKey & MK_CONTROL)
8990 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8992 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8993 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8994 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8995 infoPtr->nSelectionMark = nItem;
8997 else if (wKey & MK_SHIFT)
8999 LISTVIEW_SetGroupSelection(infoPtr, nItem);
9003 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9005 infoPtr->nEditLabelItem = nItem;
9006 infoPtr->nLButtonDownItem = nItem;
9008 LISTVIEW_SetItemFocus(infoPtr, nItem);
9011 /* set selection (clears other pre-existing selections) */
9012 LISTVIEW_SetSelection(infoPtr, nItem);
9016 if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
9017 if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
9021 /* remove all selections */
9022 if (!(wKey & MK_CONTROL) && !(wKey & MK_SHIFT))
9023 LISTVIEW_DeselectAll(infoPtr);
9032 * Processes mouse up messages (left mouse button).
9035 * infoPtr [I ] valid pointer to the listview structure
9036 * wKey [I ] key flag
9037 * x,y [I ] mouse coordinate
9042 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9044 LVHITTESTINFO lvHitTestInfo;
9046 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
9048 if (!infoPtr->bLButtonDown) return 0;
9050 lvHitTestInfo.pt.x = x;
9051 lvHitTestInfo.pt.y = y;
9053 /* send NM_CLICK notification */
9054 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9055 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
9057 /* set left button flag */
9058 infoPtr->bLButtonDown = FALSE;
9060 /* set a single selection, reset others */
9061 if(lvHitTestInfo.iItem == infoPtr->nLButtonDownItem && lvHitTestInfo.iItem != -1)
9062 LISTVIEW_SetSelection(infoPtr, infoPtr->nLButtonDownItem);
9063 infoPtr->nLButtonDownItem = -1;
9065 if (infoPtr->bDragging)
9067 infoPtr->bDragging = FALSE;
9071 /* if we clicked on a selected item, edit the label */
9072 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
9074 /* we want to make sure the user doesn't want to do a double click. So we will
9075 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
9077 infoPtr->itemEdit.fEnabled = TRUE;
9078 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
9079 SetTimer(infoPtr->hwndSelf,
9080 (UINT_PTR)&infoPtr->itemEdit,
9081 GetDoubleClickTime(),
9082 LISTVIEW_DelayedEditItem);
9085 if (!infoPtr->bFocus)
9086 SetFocus(infoPtr->hwndSelf);
9093 * Destroys the listview control (called after WM_DESTROY).
9096 * [I] infoPtr : valid pointer to the listview structure
9101 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
9105 /* delete all items */
9106 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
9108 /* destroy data structure */
9109 DPA_Destroy(infoPtr->hdpaItems);
9110 DPA_Destroy(infoPtr->hdpaPosX);
9111 DPA_Destroy(infoPtr->hdpaPosY);
9112 DPA_Destroy(infoPtr->hdpaColumns);
9113 ranges_destroy(infoPtr->selectionRanges);
9115 /* destroy image lists */
9116 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
9118 if (infoPtr->himlNormal)
9119 ImageList_Destroy(infoPtr->himlNormal);
9120 if (infoPtr->himlSmall)
9121 ImageList_Destroy(infoPtr->himlSmall);
9122 if (infoPtr->himlState)
9123 ImageList_Destroy(infoPtr->himlState);
9126 /* destroy font, bkgnd brush */
9128 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
9129 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
9131 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
9133 /* free listview info pointer*/
9141 * Handles notifications from header.
9144 * [I] infoPtr : valid pointer to the listview structure
9145 * [I] nCtrlId : control identifier
9146 * [I] lpnmh : notification information
9151 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
9153 HWND hwndSelf = infoPtr->hwndSelf;
9155 TRACE("(lpnmh=%p)\n", lpnmh);
9157 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
9159 switch (lpnmh->hdr.code)
9164 COLUMN_INFO *lpColumnInfo;
9168 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
9171 /* remove the old line (if any) */
9172 LISTVIEW_DrawTrackLine(infoPtr);
9174 /* compute & draw the new line */
9175 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
9176 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
9177 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
9178 infoPtr->xTrackLine = x + ptOrigin.x;
9179 LISTVIEW_DrawTrackLine(infoPtr);
9185 /* remove the track line (if any) */
9186 LISTVIEW_DrawTrackLine(infoPtr);
9187 infoPtr->xTrackLine = -1;
9191 FIXME("Changing column order not implemented\n");
9194 case HDN_ITEMCHANGINGW:
9195 case HDN_ITEMCHANGINGA:
9196 return notify_forward_header(infoPtr, lpnmh);
9198 case HDN_ITEMCHANGEDW:
9199 case HDN_ITEMCHANGEDA:
9201 COLUMN_INFO *lpColumnInfo;
9204 notify_forward_header(infoPtr, lpnmh);
9205 if (!IsWindow(hwndSelf))
9208 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
9212 hdi.mask = HDI_WIDTH;
9213 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, &hdi)) return 0;
9217 cxy = lpnmh->pitem->cxy;
9219 /* determine how much we change since the last know position */
9220 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
9221 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
9224 lpColumnInfo->rcHeader.right += dx;
9225 if (lpnmh->iItem + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
9226 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
9229 /* only needs to update the scrolls */
9230 infoPtr->nItemWidth += dx;
9231 LISTVIEW_UpdateScroll(infoPtr);
9233 LISTVIEW_UpdateItemSize(infoPtr);
9234 if (infoPtr->uView == LV_VIEW_DETAILS && is_redrawing(infoPtr))
9237 RECT rcCol = lpColumnInfo->rcHeader;
9239 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
9240 OffsetRect(&rcCol, ptOrigin.x, 0);
9242 rcCol.top = infoPtr->rcList.top;
9243 rcCol.bottom = infoPtr->rcList.bottom;
9245 /* resizing left-aligned columns leaves most of the left side untouched */
9246 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
9248 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
9251 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
9254 /* when shrinking the last column clear the now unused field */
9255 if (lpnmh->iItem == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
9261 /* deal with right from rightmost column area */
9262 right.left = rcCol.right;
9263 right.top = rcCol.top;
9264 right.bottom = rcCol.bottom;
9265 right.right = infoPtr->rcList.right;
9267 LISTVIEW_InvalidateRect(infoPtr, &right);
9270 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
9276 case HDN_ITEMCLICKW:
9277 case HDN_ITEMCLICKA:
9279 /* Handle sorting by Header Column */
9282 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
9284 nmlv.iSubItem = lpnmh->iItem;
9285 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
9286 notify_forward_header(infoPtr, lpnmh);
9290 case HDN_DIVIDERDBLCLICKW:
9291 case HDN_DIVIDERDBLCLICKA:
9292 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
9301 * Paint non-client area of control.
9304 * [I] infoPtr : valid pointer to the listview structureof the sender
9305 * [I] region : update region
9308 * TRUE - frame was painted
9309 * FALSE - call default window proc
9311 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
9313 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
9317 int cxEdge = GetSystemMetrics (SM_CXEDGE),
9318 cyEdge = GetSystemMetrics (SM_CYEDGE);
9320 if (!theme) return FALSE;
9322 GetWindowRect(infoPtr->hwndSelf, &r);
9324 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
9325 r.right - cxEdge, r.bottom - cyEdge);
9326 if (region != (HRGN)1)
9327 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
9328 OffsetRect(&r, -r.left, -r.top);
9330 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
9331 OffsetRect(&r, -r.left, -r.top);
9333 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
9334 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
9335 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
9336 ReleaseDC(infoPtr->hwndSelf, dc);
9338 /* Call default proc to get the scrollbars etc. painted */
9339 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
9346 * Determines the type of structure to use.
9349 * [I] infoPtr : valid pointer to the listview structureof the sender
9350 * [I] hwndFrom : listview window handle
9351 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
9356 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
9358 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
9360 if (nCommand == NF_REQUERY)
9361 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9363 return infoPtr->notifyFormat;
9368 * Paints/Repaints the listview control.
9371 * [I] infoPtr : valid pointer to the listview structure
9372 * [I] hdc : device context handle
9377 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
9379 TRACE("(hdc=%p)\n", hdc);
9381 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
9383 infoPtr->bNoItemMetrics = FALSE;
9384 LISTVIEW_UpdateItemSize(infoPtr);
9385 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
9386 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9387 LISTVIEW_UpdateScroll(infoPtr);
9390 if (infoPtr->hwndHeader) UpdateWindow(infoPtr->hwndHeader);
9393 LISTVIEW_Refresh(infoPtr, hdc, NULL);
9398 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
9400 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
9401 EndPaint(infoPtr->hwndSelf, &ps);
9410 * Paints/Repaints the listview control.
9413 * [I] infoPtr : valid pointer to the listview structure
9414 * [I] hdc : device context handle
9415 * [I] options : drawing options
9420 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
9422 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
9424 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
9427 if (options & PRF_ERASEBKGND)
9428 LISTVIEW_EraseBkgnd(infoPtr, hdc);
9430 if (options & PRF_CLIENT)
9431 LISTVIEW_Paint(infoPtr, hdc);
9439 * Processes double click messages (right mouse button).
9442 * [I] infoPtr : valid pointer to the listview structure
9443 * [I] wKey : key flag
9444 * [I] x,y : mouse coordinate
9449 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9451 LVHITTESTINFO lvHitTestInfo;
9453 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9455 /* send NM_RELEASEDCAPTURE notification */
9456 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9458 /* send NM_RDBLCLK notification */
9459 lvHitTestInfo.pt.x = x;
9460 lvHitTestInfo.pt.y = y;
9461 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9462 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
9469 * Processes mouse down messages (right mouse button).
9472 * [I] infoPtr : valid pointer to the listview structure
9473 * [I] wKey : key flag
9474 * [I] x,y : mouse coordinate
9479 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9481 LVHITTESTINFO lvHitTestInfo;
9484 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9486 /* send NM_RELEASEDCAPTURE notification */
9487 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9489 /* make sure the listview control window has the focus */
9490 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
9492 /* set right button down flag */
9493 infoPtr->bRButtonDown = TRUE;
9495 /* determine the index of the selected item */
9496 lvHitTestInfo.pt.x = x;
9497 lvHitTestInfo.pt.y = y;
9498 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
9500 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
9502 LISTVIEW_SetItemFocus(infoPtr, nItem);
9503 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
9504 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9505 LISTVIEW_SetSelection(infoPtr, nItem);
9509 LISTVIEW_DeselectAll(infoPtr);
9517 * Processes mouse up messages (right mouse button).
9520 * [I] infoPtr : valid pointer to the listview structure
9521 * [I] wKey : key flag
9522 * [I] x,y : mouse coordinate
9527 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9529 LVHITTESTINFO lvHitTestInfo;
9532 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9534 if (!infoPtr->bRButtonDown) return 0;
9536 /* set button flag */
9537 infoPtr->bRButtonDown = FALSE;
9539 /* Send NM_RCLICK notification */
9540 lvHitTestInfo.pt.x = x;
9541 lvHitTestInfo.pt.y = y;
9542 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9543 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
9545 /* Change to screen coordinate for WM_CONTEXTMENU */
9546 pt = lvHitTestInfo.pt;
9547 ClientToScreen(infoPtr->hwndSelf, &pt);
9549 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
9550 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
9551 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
9562 * [I] infoPtr : valid pointer to the listview structure
9563 * [I] hwnd : window handle of window containing the cursor
9564 * [I] nHittest : hit-test code
9565 * [I] wMouseMsg : ideintifier of the mouse message
9568 * TRUE if cursor is set
9571 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
9573 LVHITTESTINFO lvHitTestInfo;
9575 if(!(LISTVIEW_isHotTracking(infoPtr))) return FALSE;
9577 if(!infoPtr->hHotCursor) return FALSE;
9579 GetCursorPos(&lvHitTestInfo.pt);
9580 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
9582 SetCursor(infoPtr->hHotCursor);
9592 * [I] infoPtr : valid pointer to the listview structure
9593 * [I] hwndLoseFocus : handle of previously focused window
9598 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
9600 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
9602 /* if we have the focus already, there's nothing to do */
9603 if (infoPtr->bFocus) return 0;
9605 /* send NM_SETFOCUS notification */
9606 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
9608 /* set window focus flag */
9609 infoPtr->bFocus = TRUE;
9611 /* put the focus rect back on */
9612 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
9614 /* redraw all visible selected items */
9615 LISTVIEW_InvalidateSelectedItems(infoPtr);
9625 * [I] infoPtr : valid pointer to the listview structure
9626 * [I] fRedraw : font handle
9627 * [I] fRedraw : redraw flag
9632 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
9634 HFONT oldFont = infoPtr->hFont;
9636 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
9638 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
9639 if (infoPtr->hFont == oldFont) return 0;
9641 LISTVIEW_SaveTextMetrics(infoPtr);
9643 if (infoPtr->uView == LV_VIEW_DETAILS)
9645 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
9646 LISTVIEW_UpdateSize(infoPtr);
9647 LISTVIEW_UpdateScroll(infoPtr);
9650 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
9657 * Message handling for WM_SETREDRAW.
9658 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
9661 * [I] infoPtr : valid pointer to the listview structure
9662 * [I] bRedraw: state of redraw flag
9665 * DefWinProc return value
9667 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
9669 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
9671 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
9672 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
9674 infoPtr->bRedraw = bRedraw;
9676 if(!bRedraw) return 0;
9678 if (is_autoarrange(infoPtr))
9679 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9680 LISTVIEW_UpdateScroll(infoPtr);
9682 /* despite what the WM_SETREDRAW docs says, apps expect us
9683 * to invalidate the listview here... stupid! */
9684 LISTVIEW_InvalidateList(infoPtr);
9691 * Resizes the listview control. This function processes WM_SIZE
9692 * messages. At this time, the width and height are not used.
9695 * [I] infoPtr : valid pointer to the listview structure
9696 * [I] Width : new width
9697 * [I] Height : new height
9702 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
9704 RECT rcOld = infoPtr->rcList;
9706 TRACE("(width=%d, height=%d)\n", Width, Height);
9708 LISTVIEW_UpdateSize(infoPtr);
9709 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
9711 /* do not bother with display related stuff if we're not redrawing */
9712 if (!is_redrawing(infoPtr)) return 0;
9714 if (is_autoarrange(infoPtr))
9715 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9717 LISTVIEW_UpdateScroll(infoPtr);
9719 /* refresh all only for lists whose height changed significantly */
9720 if ((infoPtr->uView == LV_VIEW_LIST) &&
9721 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
9722 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
9723 LISTVIEW_InvalidateList(infoPtr);
9730 * Sets the size information.
9733 * [I] infoPtr : valid pointer to the listview structure
9738 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
9740 TRACE("uView=%d, rcList(old)=%s\n", infoPtr->uView, wine_dbgstr_rect(&infoPtr->rcList));
9742 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
9744 if (infoPtr->uView == LV_VIEW_LIST)
9746 /* Apparently the "LIST" style is supposed to have the same
9747 * number of items in a column even if there is no scroll bar.
9748 * Since if a scroll bar already exists then the bottom is already
9749 * reduced, only reduce if the scroll bar does not currently exist.
9750 * The "2" is there to mimic the native control. I think it may be
9751 * related to either padding or edges. (GLA 7/2002)
9753 if (!(infoPtr->dwStyle & WS_HSCROLL))
9754 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
9755 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
9757 else if (infoPtr->uView == LV_VIEW_DETAILS)
9762 hl.prc = &infoPtr->rcList;
9764 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9765 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
9766 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy,
9767 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9768 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9769 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
9771 infoPtr->rcList.top = max(wp.cy, 0);
9772 infoPtr->rcList.top += (infoPtr->dwLvExStyle & LVS_EX_GRIDLINES) ? 2 : 0;
9775 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
9780 * Processes WM_STYLECHANGED messages.
9783 * [I] infoPtr : valid pointer to the listview structure
9784 * [I] wStyleType : window style type (normal or extended)
9785 * [I] lpss : window style information
9790 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9791 const STYLESTRUCT *lpss)
9793 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
9794 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
9797 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9798 wStyleType, lpss->styleOld, lpss->styleNew);
9800 if (wStyleType != GWL_STYLE) return 0;
9802 infoPtr->dwStyle = lpss->styleNew;
9803 map_style_view(infoPtr);
9805 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
9806 ((lpss->styleNew & WS_HSCROLL) == 0))
9807 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
9809 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
9810 ((lpss->styleNew & WS_VSCROLL) == 0))
9811 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
9813 if (uNewView != uOldView)
9815 SIZE oldIconSize = infoPtr->iconSize;
9818 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9819 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9821 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9822 SetRectEmpty(&infoPtr->rcFocus);
9824 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9825 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
9827 if (uNewView == LVS_ICON)
9829 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9831 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9832 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9833 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9836 else if (uNewView == LVS_REPORT)
9841 LISTVIEW_CreateHeader( infoPtr );
9843 hl.prc = &infoPtr->rcList;
9845 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9846 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9847 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9848 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9851 LISTVIEW_UpdateItemSize(infoPtr);
9854 if (uNewView == LVS_REPORT)
9856 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
9858 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
9860 /* Turn off the header control */
9861 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
9862 TRACE("Hide header control, was 0x%08x\n", style);
9863 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
9865 /* Turn on the header control */
9866 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
9868 TRACE("Show header control, was 0x%08x\n", style);
9869 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
9875 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
9876 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
9877 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9879 /* update the size of the client area */
9880 LISTVIEW_UpdateSize(infoPtr);
9882 /* add scrollbars if needed */
9883 LISTVIEW_UpdateScroll(infoPtr);
9885 /* invalidate client area + erase background */
9886 LISTVIEW_InvalidateList(infoPtr);
9893 * Processes WM_STYLECHANGING messages.
9896 * [I] infoPtr : valid pointer to the listview structure
9897 * [I] wStyleType : window style type (normal or extended)
9898 * [I0] lpss : window style information
9903 static INT LISTVIEW_StyleChanging(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9906 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9907 wStyleType, lpss->styleOld, lpss->styleNew);
9909 /* don't forward LVS_OWNERDATA only if not already set to */
9910 if ((lpss->styleNew ^ lpss->styleOld) & LVS_OWNERDATA)
9912 if (lpss->styleOld & LVS_OWNERDATA)
9913 lpss->styleNew |= LVS_OWNERDATA;
9915 lpss->styleNew &= ~LVS_OWNERDATA;
9923 * Processes WM_SHOWWINDOW messages.
9926 * [I] infoPtr : valid pointer to the listview structure
9927 * [I] bShown : window is being shown (FALSE when hidden)
9928 * [I] iStatus : window show status
9933 static LRESULT LISTVIEW_ShowWindow(LISTVIEW_INFO *infoPtr, BOOL bShown, INT iStatus)
9935 /* header delayed creation */
9936 if ((infoPtr->uView == LV_VIEW_DETAILS) && bShown)
9938 LISTVIEW_CreateHeader(infoPtr);
9940 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle))
9941 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
9949 * Processes CCM_GETVERSION messages.
9952 * [I] infoPtr : valid pointer to the listview structure
9957 static inline LRESULT LISTVIEW_GetVersion(const LISTVIEW_INFO *infoPtr)
9959 return infoPtr->iVersion;
9964 * Processes CCM_SETVERSION messages.
9967 * [I] infoPtr : valid pointer to the listview structure
9968 * [I] iVersion : version to be set
9971 * -1 when requested version is greater than DLL version;
9972 * previous version otherwise
9974 static LRESULT LISTVIEW_SetVersion(LISTVIEW_INFO *infoPtr, DWORD iVersion)
9976 INT iOldVersion = infoPtr->iVersion;
9978 if (iVersion > COMCTL32_VERSION)
9981 infoPtr->iVersion = iVersion;
9983 TRACE("new version %d\n", iVersion);
9990 * Window procedure of the listview control.
9993 static LRESULT WINAPI
9994 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9996 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9998 TRACE("(uMsg=%x wParam=%lx lParam=%lx)\n", uMsg, wParam, lParam);
10000 if (!infoPtr && (uMsg != WM_NCCREATE))
10001 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10005 case LVM_APPROXIMATEVIEWRECT:
10006 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
10007 LOWORD(lParam), HIWORD(lParam));
10009 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
10011 /* case LVM_CANCELEDITLABEL: */
10013 case LVM_CREATEDRAGIMAGE:
10014 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
10016 case LVM_DELETEALLITEMS:
10017 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
10019 case LVM_DELETECOLUMN:
10020 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
10022 case LVM_DELETEITEM:
10023 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
10025 case LVM_EDITLABELW:
10026 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
10028 case LVM_EDITLABELA:
10029 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
10031 /* case LVM_ENABLEGROUPVIEW: */
10033 case LVM_ENSUREVISIBLE:
10034 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
10036 case LVM_FINDITEMW:
10037 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
10039 case LVM_FINDITEMA:
10040 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
10042 case LVM_GETBKCOLOR:
10043 return infoPtr->clrBk;
10045 /* case LVM_GETBKIMAGE: */
10047 case LVM_GETCALLBACKMASK:
10048 return infoPtr->uCallbackMask;
10050 case LVM_GETCOLUMNA:
10051 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
10053 case LVM_GETCOLUMNW:
10054 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
10056 case LVM_GETCOLUMNORDERARRAY:
10057 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
10059 case LVM_GETCOLUMNWIDTH:
10060 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
10062 case LVM_GETCOUNTPERPAGE:
10063 return LISTVIEW_GetCountPerPage(infoPtr);
10065 case LVM_GETEDITCONTROL:
10066 return (LRESULT)infoPtr->hwndEdit;
10068 case LVM_GETEXTENDEDLISTVIEWSTYLE:
10069 return infoPtr->dwLvExStyle;
10071 /* case LVM_GETGROUPINFO: */
10073 /* case LVM_GETGROUPMETRICS: */
10075 case LVM_GETHEADER:
10076 return (LRESULT)infoPtr->hwndHeader;
10078 case LVM_GETHOTCURSOR:
10079 return (LRESULT)infoPtr->hHotCursor;
10081 case LVM_GETHOTITEM:
10082 return infoPtr->nHotItem;
10084 case LVM_GETHOVERTIME:
10085 return infoPtr->dwHoverTime;
10087 case LVM_GETIMAGELIST:
10088 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
10090 /* case LVM_GETINSERTMARK: */
10092 /* case LVM_GETINSERTMARKCOLOR: */
10094 /* case LVM_GETINSERTMARKRECT: */
10096 case LVM_GETISEARCHSTRINGA:
10097 case LVM_GETISEARCHSTRINGW:
10098 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
10102 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
10105 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
10107 case LVM_GETITEMCOUNT:
10108 return infoPtr->nItemCount;
10110 case LVM_GETITEMPOSITION:
10111 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
10113 case LVM_GETITEMRECT:
10114 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
10116 case LVM_GETITEMSPACING:
10117 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
10119 case LVM_GETITEMSTATE:
10120 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
10122 case LVM_GETITEMTEXTA:
10123 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
10125 case LVM_GETITEMTEXTW:
10126 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
10128 case LVM_GETNEXTITEM:
10129 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
10131 case LVM_GETNUMBEROFWORKAREAS:
10132 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
10135 case LVM_GETORIGIN:
10136 if (!lParam) return FALSE;
10137 if (infoPtr->uView == LV_VIEW_DETAILS ||
10138 infoPtr->uView == LV_VIEW_LIST) return FALSE;
10139 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
10142 /* case LVM_GETOUTLINECOLOR: */
10144 /* case LVM_GETSELECTEDCOLUMN: */
10146 case LVM_GETSELECTEDCOUNT:
10147 return LISTVIEW_GetSelectedCount(infoPtr);
10149 case LVM_GETSELECTIONMARK:
10150 return infoPtr->nSelectionMark;
10152 case LVM_GETSTRINGWIDTHA:
10153 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
10155 case LVM_GETSTRINGWIDTHW:
10156 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
10158 case LVM_GETSUBITEMRECT:
10159 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
10161 case LVM_GETTEXTBKCOLOR:
10162 return infoPtr->clrTextBk;
10164 case LVM_GETTEXTCOLOR:
10165 return infoPtr->clrText;
10167 /* case LVM_GETTILEINFO: */
10169 /* case LVM_GETTILEVIEWINFO: */
10171 case LVM_GETTOOLTIPS:
10172 if( !infoPtr->hwndToolTip )
10173 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
10174 return (LRESULT)infoPtr->hwndToolTip;
10176 case LVM_GETTOPINDEX:
10177 return LISTVIEW_GetTopIndex(infoPtr);
10179 case LVM_GETUNICODEFORMAT:
10180 return (infoPtr->notifyFormat == NFR_UNICODE);
10183 return infoPtr->uView;
10185 case LVM_GETVIEWRECT:
10186 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
10188 case LVM_GETWORKAREAS:
10189 FIXME("LVM_GETWORKAREAS: unimplemented\n");
10192 /* case LVM_HASGROUP: */
10195 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, TRUE);
10197 case LVM_INSERTCOLUMNA:
10198 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
10200 case LVM_INSERTCOLUMNW:
10201 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
10203 /* case LVM_INSERTGROUP: */
10205 /* case LVM_INSERTGROUPSORTED: */
10207 case LVM_INSERTITEMA:
10208 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
10210 case LVM_INSERTITEMW:
10211 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
10213 /* case LVM_INSERTMARKHITTEST: */
10215 /* case LVM_ISGROUPVIEWENABLED: */
10217 /* case LVM_MAPIDTOINDEX: */
10219 /* case LVM_MAPINDEXTOID: */
10221 /* case LVM_MOVEGROUP: */
10223 /* case LVM_MOVEITEMTOGROUP: */
10225 case LVM_REDRAWITEMS:
10226 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
10228 /* case LVM_REMOVEALLGROUPS: */
10230 /* case LVM_REMOVEGROUP: */
10233 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
10235 case LVM_SETBKCOLOR:
10236 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
10238 /* case LVM_SETBKIMAGE: */
10240 case LVM_SETCALLBACKMASK:
10241 infoPtr->uCallbackMask = (UINT)wParam;
10244 case LVM_SETCOLUMNA:
10245 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
10247 case LVM_SETCOLUMNW:
10248 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
10250 case LVM_SETCOLUMNORDERARRAY:
10251 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
10253 case LVM_SETCOLUMNWIDTH:
10254 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
10256 case LVM_SETEXTENDEDLISTVIEWSTYLE:
10257 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
10259 /* case LVM_SETGROUPINFO: */
10261 /* case LVM_SETGROUPMETRICS: */
10263 case LVM_SETHOTCURSOR:
10264 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
10266 case LVM_SETHOTITEM:
10267 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
10269 case LVM_SETHOVERTIME:
10270 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
10272 case LVM_SETICONSPACING:
10273 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10275 case LVM_SETIMAGELIST:
10276 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
10278 /* case LVM_SETINFOTIP: */
10280 /* case LVM_SETINSERTMARK: */
10282 /* case LVM_SETINSERTMARKCOLOR: */
10287 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
10288 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, (uMsg == LVM_SETITEMW));
10291 case LVM_SETITEMCOUNT:
10292 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
10294 case LVM_SETITEMPOSITION:
10297 pt.x = (short)LOWORD(lParam);
10298 pt.y = (short)HIWORD(lParam);
10299 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
10302 case LVM_SETITEMPOSITION32:
10303 if (lParam == 0) return FALSE;
10304 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
10306 case LVM_SETITEMSTATE:
10307 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
10309 case LVM_SETITEMTEXTA:
10310 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
10312 case LVM_SETITEMTEXTW:
10313 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
10315 /* case LVM_SETOUTLINECOLOR: */
10317 /* case LVM_SETSELECTEDCOLUMN: */
10319 case LVM_SETSELECTIONMARK:
10320 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
10322 case LVM_SETTEXTBKCOLOR:
10323 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
10325 case LVM_SETTEXTCOLOR:
10326 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
10328 /* case LVM_SETTILEINFO: */
10330 /* case LVM_SETTILEVIEWINFO: */
10332 /* case LVM_SETTILEWIDTH: */
10334 case LVM_SETTOOLTIPS:
10335 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
10337 case LVM_SETUNICODEFORMAT:
10338 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
10341 return LISTVIEW_SetView(infoPtr, wParam);
10343 /* case LVM_SETWORKAREAS: */
10345 /* case LVM_SORTGROUPS: */
10347 case LVM_SORTITEMS:
10348 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam, FALSE);
10350 case LVM_SORTITEMSEX:
10351 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam, TRUE);
10353 case LVM_SUBITEMHITTEST:
10354 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
10357 return LISTVIEW_Update(infoPtr, (INT)wParam);
10359 case CCM_GETVERSION:
10360 return LISTVIEW_GetVersion(infoPtr);
10362 case CCM_SETVERSION:
10363 return LISTVIEW_SetVersion(infoPtr, wParam);
10366 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
10369 return LISTVIEW_Command(infoPtr, wParam, lParam);
10372 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
10375 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
10378 return LISTVIEW_Destroy(infoPtr);
10381 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
10383 case WM_ERASEBKGND:
10384 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
10386 case WM_GETDLGCODE:
10387 return DLGC_WANTCHARS | DLGC_WANTARROWS;
10390 return (LRESULT)infoPtr->hFont;
10393 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10396 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
10399 return LISTVIEW_KillFocus(infoPtr);
10401 case WM_LBUTTONDBLCLK:
10402 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10404 case WM_LBUTTONDOWN:
10405 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10408 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10411 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10413 case WM_MOUSEHOVER:
10414 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10417 return LISTVIEW_NCDestroy(infoPtr);
10420 if (LISTVIEW_NCPaint(infoPtr, (HRGN)wParam))
10425 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
10426 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
10429 case WM_NOTIFYFORMAT:
10430 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
10432 case WM_PRINTCLIENT:
10433 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
10436 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
10438 case WM_RBUTTONDBLCLK:
10439 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10441 case WM_RBUTTONDOWN:
10442 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10445 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10448 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
10453 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
10456 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
10459 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
10461 case WM_SHOWWINDOW:
10462 LISTVIEW_ShowWindow(infoPtr, (BOOL)wParam, (INT)lParam);
10463 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10466 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10468 case WM_STYLECHANGED:
10469 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10471 case WM_STYLECHANGING:
10472 return LISTVIEW_StyleChanging(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10474 case WM_SYSCOLORCHANGE:
10475 COMCTL32_RefreshSysColors();
10478 /* case WM_TIMER: */
10479 case WM_THEMECHANGED:
10480 return LISTVIEW_ThemeChanged(infoPtr);
10483 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10485 case WM_MOUSEWHEEL:
10486 if (wParam & (MK_SHIFT | MK_CONTROL))
10487 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10488 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
10490 case WM_WINDOWPOSCHANGED:
10491 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
10493 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
10494 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
10496 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
10498 MEASUREITEMSTRUCT mis;
10499 mis.CtlType = ODT_LISTVIEW;
10500 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
10504 mis.itemHeight= infoPtr->nItemHeight;
10505 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
10506 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
10507 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
10510 LISTVIEW_UpdateSize(infoPtr);
10511 LISTVIEW_UpdateScroll(infoPtr);
10513 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10515 /* case WM_WININICHANGE: */
10518 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
10519 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
10522 /* call default window procedure */
10523 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10530 * Registers the window class.
10538 void LISTVIEW_Register(void)
10540 WNDCLASSW wndClass;
10542 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
10543 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
10544 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
10545 wndClass.cbClsExtra = 0;
10546 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
10547 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
10548 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
10549 wndClass.lpszClassName = WC_LISTVIEWW;
10550 RegisterClassW(&wndClass);
10555 * Unregisters the window class.
10563 void LISTVIEW_Unregister(void)
10565 UnregisterClassW(WC_LISTVIEWW, NULL);
10570 * Handle any WM_COMMAND messages
10573 * [I] infoPtr : valid pointer to the listview structure
10574 * [I] wParam : the first message parameter
10575 * [I] lParam : the second message parameter
10580 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10582 switch (HIWORD(wParam))
10587 * Adjust the edit window size
10589 WCHAR buffer[1024];
10590 HDC hdc = GetDC(infoPtr->hwndEdit);
10591 HFONT hFont, hOldFont = 0;
10595 if (!infoPtr->hwndEdit || !hdc) return 0;
10596 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
10597 GetWindowRect(infoPtr->hwndEdit, &rect);
10599 /* Select font to get the right dimension of the string */
10600 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
10603 hOldFont = SelectObject(hdc, hFont);
10606 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
10608 TEXTMETRICW textMetric;
10610 /* Add Extra spacing for the next character */
10611 GetTextMetricsW(hdc, &textMetric);
10612 sz.cx += (textMetric.tmMaxCharWidth * 2);
10620 rect.bottom - rect.top,
10621 SWP_DRAWFRAME|SWP_NOMOVE);
10624 SelectObject(hdc, hOldFont);
10626 ReleaseDC(infoPtr->hwndEdit, hdc);
10632 /* handle value will be lost after LISTVIEW_EndEditLabelT */
10633 HWND edit = infoPtr->hwndEdit;
10635 LISTVIEW_EndEditLabelT(infoPtr, TRUE, IsWindowUnicode(infoPtr->hwndEdit));
10636 SendMessageW(edit, WM_CLOSE, 0, 0);
10640 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
10649 * Subclassed edit control windproc function
10652 * [I] hwnd : the edit window handle
10653 * [I] uMsg : the message that is to be processed
10654 * [I] wParam : first message parameter
10655 * [I] lParam : second message parameter
10656 * [I] isW : TRUE if input is Unicode
10661 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
10663 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
10666 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
10667 hwnd, uMsg, wParam, lParam, isW);
10671 case WM_GETDLGCODE:
10672 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
10676 WNDPROC editProc = infoPtr->EditWndProc;
10677 infoPtr->EditWndProc = 0;
10678 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
10679 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
10683 if (VK_ESCAPE == (INT)wParam)
10688 else if (VK_RETURN == (INT)wParam)
10692 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
10695 /* kill the edit */
10696 if (infoPtr->hwndEdit)
10697 LISTVIEW_EndEditLabelT(infoPtr, save, isW);
10699 SendMessageW(hwnd, WM_CLOSE, 0, 0);
10705 * Subclassed edit control Unicode windproc function
10708 * [I] hwnd : the edit window handle
10709 * [I] uMsg : the message that is to be processed
10710 * [I] wParam : first message parameter
10711 * [I] lParam : second message parameter
10715 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10717 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
10722 * Subclassed edit control ANSI windproc function
10725 * [I] hwnd : the edit window handle
10726 * [I] uMsg : the message that is to be processed
10727 * [I] wParam : first message parameter
10728 * [I] lParam : second message parameter
10732 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10734 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
10739 * Creates a subclassed edit control
10742 * [I] infoPtr : valid pointer to the listview structure
10743 * [I] text : initial text for the edit
10744 * [I] style : the window style
10745 * [I] isW : TRUE if input is Unicode
10749 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style, BOOL isW)
10751 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
10753 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
10755 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
10757 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
10759 /* Window will be resized and positioned after LVN_BEGINLABELEDIT */
10761 hedit = CreateWindowW(editName, text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
10763 hedit = CreateWindowA("Edit", (LPCSTR)text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
10765 if (!hedit) return 0;
10767 infoPtr->EditWndProc = (WNDPROC)
10768 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
10769 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
10771 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);