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
9 * Copyright 2009-2013 Nikolay Sivov
10 * Copyright 2009 Owen Rudge for CodeWeavers
12 * This library is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public
14 * License as published by the Free Software Foundation; either
15 * version 2.1 of the License, or (at your option) any later version.
17 * This library is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Lesser General Public License for more details.
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this library; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
28 * This code was audited for completeness against the documented features
29 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
31 * Unless otherwise noted, we believe this code to be complete, as per
32 * the specification mentioned above.
33 * If you discover missing features, or bugs, please note them below.
37 * Default Message Processing
38 * -- WM_CREATE: create the icon and small icon image lists at this point only if
39 * the LVS_SHAREIMAGELISTS style is not specified.
40 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
41 * or small icon and the LVS_AUTOARRANGE style is specified.
46 * -- Hot item handling, mouse hovering
47 * -- Workareas support
52 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
53 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
54 * -- LVA_SNAPTOGRID not implemented
55 * -- LISTVIEW_ApproximateViewRect partially implemented
56 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
57 * -- LISTVIEW_SetIconSpacing is incomplete
58 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
61 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
62 * linear in the number of items in the list, and this is
63 * unacceptable for large lists.
64 * -- if list is sorted by item text LISTVIEW_InsertItemT could use
65 * binary search to calculate item index (e.g. DPA_Search()).
66 * This requires sorted state to be reliably tracked in item modifiers.
67 * -- we should keep an ordered array of coordinates in iconic mode
68 * this would allow to frame items (iterator_frameditems),
69 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
76 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
82 * -- LVS_NOSCROLL (see Q137520)
86 * -- LVS_EX_BORDERSELECT
90 * -- LVS_EX_MULTIWORKAREAS
92 * -- LVS_EX_SIMPLESELECT
93 * -- LVS_EX_TWOCLICKACTIVATE
94 * -- LVS_EX_UNDERLINECOLD
95 * -- LVS_EX_UNDERLINEHOT
98 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
105 * -- LVM_ENABLEGROUPVIEW
106 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
107 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
108 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
109 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
110 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
111 * -- LVM_GETINSERTMARKRECT
112 * -- LVM_GETNUMBEROFWORKAREAS
113 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
114 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
115 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
116 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
117 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
118 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
119 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
120 * -- LVM_INSERTGROUPSORTED
121 * -- LVM_INSERTMARKHITTEST
122 * -- LVM_ISGROUPVIEWENABLED
124 * -- LVM_MOVEITEMTOGROUP
126 * -- LVM_SETTILEWIDTH
130 * -- ListView_GetHoverTime, ListView_SetHoverTime
131 * -- ListView_GetISearchString
132 * -- ListView_GetNumberOfWorkAreas
133 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
138 * Known differences in message stream from native control (not known if
139 * these differences cause problems):
140 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
141 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
142 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
143 * processing for "USEDOUBLECLICKTIME".
147 #include "wine/port.h"
162 #include "commctrl.h"
163 #include "comctl32.h"
166 #include "wine/debug.h"
167 #include "wine/unicode.h"
169 WINE_DEFAULT_DEBUG_CHANNEL(listview);
171 typedef struct tagCOLUMN_INFO
173 RECT rcHeader; /* tracks the header's rectangle */
174 INT fmt; /* same as LVCOLUMN.fmt */
178 typedef struct tagITEMHDR
182 } ITEMHDR, *LPITEMHDR;
184 typedef struct tagSUBITEM_INFO
190 typedef struct tagITEM_ID ITEM_ID;
192 typedef struct tagITEM_INFO
203 UINT id; /* item id */
204 HDPA item; /* link to item data */
207 typedef struct tagRANGE
213 typedef struct tagRANGES
218 typedef struct tagITERATOR
227 typedef struct tagDELAYED_ITEM_EDIT
233 typedef struct tagLISTVIEW_INFO
237 RECT rcList; /* This rectangle is really the window
238 * client rectangle possibly reduced by the
239 * horizontal scroll bar and/or header - see
240 * LISTVIEW_UpdateSize. This rectangle offset
241 * by the LISTVIEW_GetOrigin value is in
242 * client coordinates */
244 /* notification window */
247 BOOL bDoChangeNotify; /* send change notification messages? */
254 INT nItemCount; /* the number of items in the list */
255 HDPA hdpaItems; /* array ITEM_INFO pointers */
256 HDPA hdpaItemIds; /* array of ITEM_ID pointers */
257 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
258 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
259 RANGES selectionRanges;
260 INT nSelectionMark; /* item to start next multiselection from */
262 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
265 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
266 BOOL colRectsDirty; /* trigger column rectangles requery from header */
269 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
274 PFNLVCOMPARE pfnCompare; /* sorting callback pointer */
278 DWORD dwStyle; /* the cached window GWL_STYLE */
279 DWORD dwLvExStyle; /* extended listview style */
280 DWORD uView; /* current view available through LVM_[G,S]ETVIEW */
286 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
289 HIMAGELIST himlNormal;
290 HIMAGELIST himlSmall;
291 HIMAGELIST himlState;
295 POINT currIconPos; /* this is the position next icon will be placed */
299 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
301 /* marquee selection */
302 BOOL bMarqueeSelect; /* marquee selection/highlight underway */
304 RECT marqueeRect; /* absolute coordinates of marquee selection */
305 RECT marqueeDrawRect; /* relative coordinates for drawing marquee */
306 POINT marqueeOrigin; /* absolute coordinates of marquee click origin */
309 BOOL bFocus; /* control has focus */
311 RECT rcFocus; /* focus bounds */
322 INT ntmHeight; /* Some cached metrics of the font used */
323 INT ntmMaxCharWidth; /* by the listview to draw items */
326 /* mouse operation */
330 POINT ptClickPos; /* point where the user clicked */
331 INT nLButtonDownItem; /* tracks item to reset multiselection on WM_LBUTTONUP */
335 /* keyboard operation */
336 DWORD lastKeyPressTimestamp;
338 INT nSearchParamLength;
339 WCHAR szSearchParam[ MAX_PATH ];
342 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
343 BOOL bIsDrawing; /* Drawing in progress */
344 INT nMeasureItemHeight; /* WM_MEASUREITEM result */
345 BOOL bRedraw; /* WM_SETREDRAW switch */
348 DWORD iVersion; /* CCM_[G,S]ETVERSION */
354 /* How many we debug buffer to allocate */
355 #define DEBUG_BUFFERS 20
356 /* The size of a single debug buffer */
357 #define DEBUG_BUFFER_SIZE 256
359 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
360 #define SB_INTERNAL -1
362 /* maximum size of a label */
363 #define DISP_TEXT_SIZE 260
365 /* padding for items in list and small icon display modes */
366 #define WIDTH_PADDING 12
368 /* padding for items in list, report and small icon display modes */
369 #define HEIGHT_PADDING 1
371 /* offset of items in report display mode */
372 #define REPORT_MARGINX 2
374 /* padding for icon in large icon display mode
375 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
376 * that HITTEST will see.
377 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
378 * ICON_TOP_PADDING - sum of the two above.
379 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
380 * LABEL_HOR_PADDING - between text and sides of box
381 * LABEL_VERT_PADDING - between bottom of text and end of box
383 * ICON_LR_PADDING - additional width above icon size.
384 * ICON_LR_HALF - half of the above value
386 #define ICON_TOP_PADDING_NOTHITABLE 2
387 #define ICON_TOP_PADDING_HITABLE 2
388 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
389 #define ICON_BOTTOM_PADDING 4
390 #define LABEL_HOR_PADDING 5
391 #define LABEL_VERT_PADDING 7
392 #define ICON_LR_PADDING 16
393 #define ICON_LR_HALF (ICON_LR_PADDING/2)
395 /* default label width for items in list and small icon display modes */
396 #define DEFAULT_LABEL_WIDTH 40
397 /* maximum select rectangle width for empty text item in LV_VIEW_DETAILS */
398 #define MAX_EMPTYTEXT_SELECT_WIDTH 80
400 /* default column width for items in list display mode */
401 #define DEFAULT_COLUMN_WIDTH 128
403 /* Size of "line" scroll for V & H scrolls */
404 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
406 /* Padding between image and label */
407 #define IMAGE_PADDING 2
409 /* Padding behind the label */
410 #define TRAILING_LABEL_PADDING 12
411 #define TRAILING_HEADER_PADDING 11
413 /* Border for the icon caption */
414 #define CAPTION_BORDER 2
416 /* Standard DrawText flags */
417 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
418 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
419 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
421 /* Image index from state */
422 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
424 /* The time in milliseconds to reset the search in the list */
425 #define KEY_DELAY 450
427 /* Dump the LISTVIEW_INFO structure to the debug channel */
428 #define LISTVIEW_DUMP(iP) do { \
429 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
430 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
431 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
432 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
433 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
434 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
435 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
436 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
437 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
438 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
441 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
444 * forward declarations
446 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
447 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
448 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
449 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
450 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
451 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
452 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
453 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
454 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
455 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
456 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
457 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
458 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
459 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT);
460 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT);
461 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
462 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
463 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
464 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *, BOOL, BOOL);
465 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *, INT, INT);
467 /******** Text handling functions *************************************/
469 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
470 * text string. The string may be ANSI or Unicode, in which case
471 * the boolean isW tells us the type of the string.
473 * The name of the function tell what type of strings it expects:
474 * W: Unicode, T: ANSI/Unicode - function of isW
477 static inline BOOL is_text(LPCWSTR text)
479 return text != NULL && text != LPSTR_TEXTCALLBACKW;
482 static inline int textlenT(LPCWSTR text, BOOL isW)
484 return !is_text(text) ? 0 :
485 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
488 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
491 if (isSrcW) lstrcpynW(dest, src, max);
492 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
494 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
495 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
498 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
500 LPWSTR wstr = (LPWSTR)text;
502 if (!isW && is_text(text))
504 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
505 wstr = Alloc(len * sizeof(WCHAR));
506 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
508 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
512 static inline void textfreeT(LPWSTR wstr, BOOL isW)
514 if (!isW && is_text(wstr)) Free (wstr);
518 * dest is a pointer to a Unicode string
519 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
521 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
525 if (src == LPSTR_TEXTCALLBACKW)
527 if (is_text(*dest)) Free(*dest);
528 *dest = LPSTR_TEXTCALLBACKW;
532 LPWSTR pszText = textdupTtoW(src, isW);
533 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
534 bResult = Str_SetPtrW(dest, pszText);
535 textfreeT(pszText, isW);
541 * compares a Unicode to a Unicode/ANSI text string
543 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
545 if (!aw) return bt ? -1 : 0;
547 if (aw == LPSTR_TEXTCALLBACKW)
548 return bt == LPSTR_TEXTCALLBACKW ? 1 : -1;
549 if (bt != LPSTR_TEXTCALLBACKW)
551 LPWSTR bw = textdupTtoW(bt, isW);
552 int r = bw ? lstrcmpW(aw, bw) : 1;
560 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
562 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
563 return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n) - CSTR_EQUAL;
566 /******** Debugging functions *****************************************/
568 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
570 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
571 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
574 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
576 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
577 n = min(textlenT(text, isW), n);
578 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
581 static char* debug_getbuf(void)
583 static int index = 0;
584 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
585 return buffers[index++ % DEBUG_BUFFERS];
588 static inline const char* debugrange(const RANGE *lprng)
590 if (!lprng) return "(null)";
591 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
594 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
596 char* buf = debug_getbuf(), *text = buf;
597 int len, size = DEBUG_BUFFER_SIZE;
599 if (pScrollInfo == NULL) return "(null)";
600 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
601 if (len == -1) goto end; buf += len; size -= len;
602 if (pScrollInfo->fMask & SIF_RANGE)
603 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
605 if (len == -1) goto end; buf += len; size -= len;
606 if (pScrollInfo->fMask & SIF_PAGE)
607 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
609 if (len == -1) goto end; buf += len; size -= len;
610 if (pScrollInfo->fMask & SIF_POS)
611 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
613 if (len == -1) goto end; buf += len; size -= len;
614 if (pScrollInfo->fMask & SIF_TRACKPOS)
615 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
617 if (len == -1) goto end; buf += len;
620 buf = text + strlen(text);
622 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
626 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
628 if (!plvnm) return "(null)";
629 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
630 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
631 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
632 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
635 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
637 char* buf = debug_getbuf(), *text = buf;
638 int len, size = DEBUG_BUFFER_SIZE;
640 if (lpLVItem == NULL) return "(null)";
641 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
642 if (len == -1) goto end; buf += len; size -= len;
643 if (lpLVItem->mask & LVIF_STATE)
644 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
646 if (len == -1) goto end; buf += len; size -= len;
647 if (lpLVItem->mask & LVIF_TEXT)
648 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
650 if (len == -1) goto end; buf += len; size -= len;
651 if (lpLVItem->mask & LVIF_IMAGE)
652 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
654 if (len == -1) goto end; buf += len; size -= len;
655 if (lpLVItem->mask & LVIF_PARAM)
656 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
658 if (len == -1) goto end; buf += len; size -= len;
659 if (lpLVItem->mask & LVIF_INDENT)
660 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
662 if (len == -1) goto end; buf += len;
665 buf = text + strlen(text);
667 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
671 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
673 char* buf = debug_getbuf(), *text = buf;
674 int len, size = DEBUG_BUFFER_SIZE;
676 if (lpColumn == NULL) return "(null)";
677 len = snprintf(buf, size, "{");
678 if (len == -1) goto end; buf += len; size -= len;
679 if (lpColumn->mask & LVCF_SUBITEM)
680 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
682 if (len == -1) goto end; buf += len; size -= len;
683 if (lpColumn->mask & LVCF_FMT)
684 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
686 if (len == -1) goto end; buf += len; size -= len;
687 if (lpColumn->mask & LVCF_WIDTH)
688 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
690 if (len == -1) goto end; buf += len; size -= len;
691 if (lpColumn->mask & LVCF_TEXT)
692 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
694 if (len == -1) goto end; buf += len; size -= len;
695 if (lpColumn->mask & LVCF_IMAGE)
696 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
698 if (len == -1) goto end; buf += len; size -= len;
699 if (lpColumn->mask & LVCF_ORDER)
700 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
702 if (len == -1) goto end; buf += len;
705 buf = text + strlen(text);
707 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
711 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
713 if (!lpht) return "(null)";
715 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
716 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
719 /* Return the corresponding text for a given scroll value */
720 static inline LPCSTR debugscrollcode(int nScrollCode)
724 case SB_LINELEFT: return "SB_LINELEFT";
725 case SB_LINERIGHT: return "SB_LINERIGHT";
726 case SB_PAGELEFT: return "SB_PAGELEFT";
727 case SB_PAGERIGHT: return "SB_PAGERIGHT";
728 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
729 case SB_THUMBTRACK: return "SB_THUMBTRACK";
730 case SB_ENDSCROLL: return "SB_ENDSCROLL";
731 case SB_INTERNAL: return "SB_INTERNAL";
732 default: return "unknown";
737 /******** Notification functions ************************************/
739 static int get_ansi_notification(UINT unicodeNotificationCode)
741 switch (unicodeNotificationCode)
743 case LVN_BEGINLABELEDITA:
744 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
745 case LVN_ENDLABELEDITA:
746 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
747 case LVN_GETDISPINFOA:
748 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
749 case LVN_SETDISPINFOA:
750 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
751 case LVN_ODFINDITEMA:
752 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
753 case LVN_GETINFOTIPA:
754 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
755 /* header forwards */
757 case HDN_TRACKW: return HDN_TRACKA;
759 case HDN_ENDTRACKW: return HDN_ENDTRACKA;
760 case HDN_BEGINDRAG: return HDN_BEGINDRAG;
761 case HDN_ENDDRAG: return HDN_ENDDRAG;
762 case HDN_ITEMCHANGINGA:
763 case HDN_ITEMCHANGINGW: return HDN_ITEMCHANGINGA;
764 case HDN_ITEMCHANGEDA:
765 case HDN_ITEMCHANGEDW: return HDN_ITEMCHANGEDA;
767 case HDN_ITEMCLICKW: return HDN_ITEMCLICKA;
768 case HDN_DIVIDERDBLCLICKA:
769 case HDN_DIVIDERDBLCLICKW: return HDN_DIVIDERDBLCLICKA;
772 FIXME("unknown notification %x\n", unicodeNotificationCode);
773 return unicodeNotificationCode;
776 /* forwards header notifications to listview parent */
777 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, NMHEADERW *lpnmhW)
779 LPCWSTR text = NULL, filter = NULL;
781 NMHEADERA *lpnmh = (NMHEADERA*) lpnmhW;
783 /* on unicode format exit earlier */
784 if (infoPtr->notifyFormat == NFR_UNICODE)
785 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, lpnmh->hdr.idFrom,
788 /* header always supplies unicode notifications,
789 all we have to do is to convert strings to ANSI */
792 /* convert item text */
793 if (lpnmh->pitem->mask & HDI_TEXT)
795 text = (LPCWSTR)lpnmh->pitem->pszText;
796 Str_SetPtrWtoA(&lpnmh->pitem->pszText, text);
798 /* convert filter text */
799 if ((lpnmh->pitem->mask & HDI_FILTER) && (lpnmh->pitem->type == HDFT_ISSTRING) &&
800 lpnmh->pitem->pvFilter)
802 filter = (LPCWSTR)((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText;
803 Str_SetPtrWtoA(&((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText, filter);
806 lpnmh->hdr.code = get_ansi_notification(lpnmh->hdr.code);
808 ret = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, lpnmh->hdr.idFrom,
814 Free(lpnmh->pitem->pszText);
815 lpnmh->pitem->pszText = (LPSTR)text;
819 Free(((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText);
820 ((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText = (LPSTR)filter;
826 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
830 TRACE("(code=%d)\n", code);
832 pnmh->hwndFrom = infoPtr->hwndSelf;
833 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
835 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
837 TRACE(" <= %ld\n", result);
842 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
845 HWND hwnd = infoPtr->hwndSelf;
846 notify_hdr(infoPtr, code, &nmh);
847 return IsWindow(hwnd);
850 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
861 item.mask = LVIF_PARAM|LVIF_STATE;
862 item.iItem = htInfo->iItem;
864 item.stateMask = (UINT)-1;
865 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
866 nmia.lParam = item.lParam;
867 nmia.uOldState = item.state;
868 nmia.uNewState = item.state | LVIS_ACTIVATING;
869 nmia.uChanged = LVIF_STATE;
872 nmia.iItem = htInfo->iItem;
873 nmia.iSubItem = htInfo->iSubItem;
874 nmia.ptAction = htInfo->pt;
876 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
877 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
878 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
880 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
883 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
885 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
886 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
889 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
893 HWND hwnd = infoPtr->hwndSelf;
895 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
896 ZeroMemory(&nmia, sizeof(nmia));
897 nmia.iItem = lvht->iItem;
898 nmia.iSubItem = lvht->iSubItem;
899 nmia.ptAction = lvht->pt;
900 item.mask = LVIF_PARAM;
901 item.iItem = lvht->iItem;
903 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmia.lParam = item.lParam;
904 notify_hdr(infoPtr, code, (LPNMHDR)&nmia);
905 return IsWindow(hwnd);
908 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
912 HWND hwnd = infoPtr->hwndSelf;
914 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
916 item.mask = LVIF_PARAM;
919 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
920 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
921 return IsWindow(hwnd);
925 Send notification. depends on dispinfoW having same
926 structure as dispinfoA.
927 infoPtr : listview struct
928 code : *Unicode* notification code
929 pdi : dispinfo structure (can be unicode or ansi)
930 isW : TRUE if dispinfo is Unicode
932 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT code, LPNMLVDISPINFOW pdi, BOOL isW)
934 INT length = 0, ret_length;
935 LPWSTR buffer = NULL, ret_text;
936 BOOL return_ansi = FALSE;
937 BOOL return_unicode = FALSE;
940 if ((pdi->item.mask & LVIF_TEXT) && is_text(pdi->item.pszText))
942 return_unicode = ( isW && infoPtr->notifyFormat == NFR_ANSI);
943 return_ansi = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
946 ret_length = pdi->item.cchTextMax;
947 ret_text = pdi->item.pszText;
949 if (return_unicode || return_ansi)
951 if (code != LVN_GETDISPINFOW)
953 length = return_ansi ?
954 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
955 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
959 length = pdi->item.cchTextMax;
960 *pdi->item.pszText = 0; /* make sure we don't process garbage */
963 buffer = Alloc( (return_ansi ? sizeof(WCHAR) : sizeof(CHAR)) * length);
964 if (!buffer) return FALSE;
967 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
970 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) buffer,
973 pdi->item.pszText = buffer;
974 pdi->item.cchTextMax = length;
977 if (infoPtr->notifyFormat == NFR_ANSI)
978 code = get_ansi_notification(code);
980 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
981 ret = notify_hdr(infoPtr, code, &pdi->hdr);
982 TRACE(" resulting code=%d\n", pdi->hdr.code);
984 if (return_ansi || return_unicode)
986 if (return_ansi && (pdi->hdr.code == LVN_GETDISPINFOA))
988 strcpy((char*)ret_text, (char*)pdi->item.pszText);
990 else if (return_unicode && (pdi->hdr.code == LVN_GETDISPINFOW))
992 strcpyW(ret_text, pdi->item.pszText);
994 else if (return_ansi) /* note : pointer can be changed by app ! */
996 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) ret_text,
997 ret_length, NULL, NULL);
1000 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
1001 ret_text, ret_length);
1003 pdi->item.pszText = ret_text; /* restores our buffer */
1004 pdi->item.cchTextMax = ret_length;
1010 /* if dipsinfo holder changed notification code then convert */
1011 if (!isW && (pdi->hdr.code == LVN_GETDISPINFOW) && (pdi->item.mask & LVIF_TEXT))
1013 length = WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
1015 buffer = Alloc(length * sizeof(CHAR));
1016 if (!buffer) return FALSE;
1018 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) buffer,
1019 ret_length, NULL, NULL);
1021 strcpy((LPSTR)pdi->item.pszText, (LPSTR)buffer);
1028 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
1029 const RECT *rcBounds, const LVITEMW *lplvItem)
1031 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
1032 lpnmlvcd->nmcd.hdc = hdc;
1033 lpnmlvcd->nmcd.rc = *rcBounds;
1034 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
1035 lpnmlvcd->clrText = infoPtr->clrText;
1036 if (!lplvItem) return;
1037 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
1038 lpnmlvcd->iSubItem = lplvItem->iSubItem;
1039 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
1040 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
1041 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
1042 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
1045 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
1047 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
1050 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
1051 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
1052 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
1053 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
1054 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
1055 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
1059 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
1061 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
1062 lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
1063 if (lpnmlvcd->clrText == CLR_DEFAULT)
1064 lpnmlvcd->clrText = comctl32_color.clrWindowText;
1066 /* apparently, for selected items, we have to override the returned values */
1069 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
1071 if (infoPtr->bFocus)
1073 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
1074 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
1076 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
1078 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
1079 lpnmlvcd->clrText = comctl32_color.clrBtnText;
1084 /* Set the text attributes */
1085 if (lpnmlvcd->clrTextBk != CLR_NONE)
1087 SetBkMode(hdc, OPAQUE);
1088 SetBkColor(hdc,lpnmlvcd->clrTextBk);
1091 SetBkMode(hdc, TRANSPARENT);
1092 SetTextColor(hdc, lpnmlvcd->clrText);
1095 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
1097 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
1100 /* returns TRUE when repaint needed, FALSE otherwise */
1101 static BOOL notify_measureitem(LISTVIEW_INFO *infoPtr)
1103 MEASUREITEMSTRUCT mis;
1104 mis.CtlType = ODT_LISTVIEW;
1105 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1109 mis.itemHeight= infoPtr->nItemHeight;
1110 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
1111 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
1113 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
1119 /******** Item iterator functions **********************************/
1121 static RANGES ranges_create(int count);
1122 static void ranges_destroy(RANGES ranges);
1123 static BOOL ranges_add(RANGES ranges, RANGE range);
1124 static BOOL ranges_del(RANGES ranges, RANGE range);
1125 static void ranges_dump(RANGES ranges);
1127 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
1129 RANGE range = { nItem, nItem + 1 };
1131 return ranges_add(ranges, range);
1134 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1136 RANGE range = { nItem, nItem + 1 };
1138 return ranges_del(ranges, range);
1142 * ITERATOR DOCUMENTATION
1144 * The iterator functions allow for easy, and convenient iteration
1145 * over items of interest in the list. Typically, you create a
1146 * iterator, use it, and destroy it, as such:
1149 * iterator_xxxitems(&i, ...);
1150 * while (iterator_{prev,next}(&i)
1152 * //code which uses i.nItem
1154 * iterator_destroy(&i);
1156 * where xxx is either: framed, or visible.
1157 * Note that it is important that the code destroys the iterator
1158 * after it's done with it, as the creation of the iterator may
1159 * allocate memory, which thus needs to be freed.
1161 * You can iterate both forwards, and backwards through the list,
1162 * by using iterator_next or iterator_prev respectively.
1164 * Lower numbered items are draw on top of higher number items in
1165 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1166 * items may overlap). So, to test items, you should use
1168 * which lists the items top to bottom (in Z-order).
1169 * For drawing items, you should use
1171 * which lists the items bottom to top (in Z-order).
1172 * If you keep iterating over the items after the end-of-items
1173 * marker (-1) is returned, the iterator will start from the
1174 * beginning. Typically, you don't need to test for -1,
1175 * because iterator_{next,prev} will return TRUE if more items
1176 * are to be iterated over, or FALSE otherwise.
1178 * Note: the iterator is defined to be bidirectional. That is,
1179 * any number of prev followed by any number of next, or
1180 * five versa, should leave the iterator at the same item:
1181 * prev * n, next * n = next * n, prev * n
1183 * The iterator has a notion of an out-of-order, special item,
1184 * which sits at the start of the list. This is used in
1185 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1186 * which needs to be first, as it may overlap other items.
1188 * The code is a bit messy because we have:
1189 * - a special item to deal with
1190 * - simple range, or composite range
1192 * If you find bugs, or want to add features, please make sure you
1193 * always check/modify *both* iterator_prev, and iterator_next.
1197 * This function iterates through the items in increasing order,
1198 * but prefixed by the special item, then -1. That is:
1199 * special, 1, 2, 3, ..., n, -1.
1200 * Each item is listed only once.
1202 static inline BOOL iterator_next(ITERATOR* i)
1206 i->nItem = i->nSpecial;
1207 if (i->nItem != -1) return TRUE;
1209 if (i->nItem == i->nSpecial)
1211 if (i->ranges) i->index = 0;
1217 if (i->nItem == i->nSpecial) i->nItem++;
1218 if (i->nItem < i->range.upper) return TRUE;
1223 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1224 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1227 else if (i->nItem >= i->range.upper) goto end;
1229 i->nItem = i->range.lower;
1230 if (i->nItem >= 0) goto testitem;
1237 * This function iterates through the items in decreasing order,
1238 * followed by the special item, then -1. That is:
1239 * n, n-1, ..., 3, 2, 1, special, -1.
1240 * Each item is listed only once.
1242 static inline BOOL iterator_prev(ITERATOR* i)
1249 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1252 if (i->nItem == i->nSpecial)
1260 if (i->nItem == i->nSpecial) i->nItem--;
1261 if (i->nItem >= i->range.lower) return TRUE;
1267 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1270 else if (!start && i->nItem < i->range.lower) goto end;
1272 i->nItem = i->range.upper;
1273 if (i->nItem > 0) goto testitem;
1275 return (i->nItem = i->nSpecial) != -1;
1278 static RANGE iterator_range(const ITERATOR *i)
1282 if (!i->ranges) return i->range;
1284 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1286 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1287 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1289 else range.lower = range.upper = 0;
1295 * Releases resources associated with this iterator.
1297 static inline void iterator_destroy(const ITERATOR *i)
1299 ranges_destroy(i->ranges);
1303 * Create an empty iterator.
1305 static inline BOOL iterator_empty(ITERATOR* i)
1307 ZeroMemory(i, sizeof(*i));
1308 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1313 * Create an iterator over a range.
1315 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1323 * Create an iterator over a bunch of ranges.
1324 * Please note that the iterator will take ownership of the ranges,
1325 * and will free them upon destruction.
1327 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1335 * Creates an iterator over the items which intersect frame.
1336 * Uses absolute coordinates rather than compensating for the current offset.
1338 static BOOL iterator_frameditems_absolute(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *frame)
1340 RECT rcItem, rcTemp;
1342 /* in case we fail, we want to return an empty iterator */
1343 if (!iterator_empty(i)) return FALSE;
1345 TRACE("(frame=%s)\n", wine_dbgstr_rect(frame));
1347 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
1351 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->nFocusedItem != -1)
1353 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1354 if (IntersectRect(&rcTemp, &rcItem, frame))
1355 i->nSpecial = infoPtr->nFocusedItem;
1357 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1358 /* to do better here, we need to have PosX, and PosY sorted */
1359 TRACE("building icon ranges:\n");
1360 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1362 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1363 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1364 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1365 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1366 if (IntersectRect(&rcTemp, &rcItem, frame))
1367 ranges_additem(i->ranges, nItem);
1371 else if (infoPtr->uView == LV_VIEW_DETAILS)
1375 if (frame->left >= infoPtr->nItemWidth) return TRUE;
1376 if (frame->top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1378 range.lower = max(frame->top / infoPtr->nItemHeight, 0);
1379 range.upper = min((frame->bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1380 if (range.upper <= range.lower) return TRUE;
1381 if (!iterator_rangeitems(i, range)) return FALSE;
1382 TRACE(" report=%s\n", debugrange(&i->range));
1386 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1387 INT nFirstRow = max(frame->top / infoPtr->nItemHeight, 0);
1388 INT nLastRow = min((frame->bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1395 if (infoPtr->nItemWidth)
1397 nFirstCol = max(frame->left / infoPtr->nItemWidth, 0);
1398 nLastCol = min((frame->right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1402 nFirstCol = max(frame->left, 0);
1403 nLastCol = min(frame->right - 1, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1406 lower = nFirstCol * nPerCol + nFirstRow;
1408 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1409 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1411 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1413 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1414 TRACE("building list ranges:\n");
1415 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1417 item_range.lower = nCol * nPerCol + nFirstRow;
1418 if(item_range.lower >= infoPtr->nItemCount) break;
1419 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1420 TRACE(" list=%s\n", debugrange(&item_range));
1421 ranges_add(i->ranges, item_range);
1429 * Creates an iterator over the items which intersect lprc.
1431 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1436 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1438 LISTVIEW_GetOrigin(infoPtr, &Origin);
1439 OffsetRect(&frame, -Origin.x, -Origin.y);
1441 return iterator_frameditems_absolute(i, infoPtr, &frame);
1445 * Creates an iterator over the items which intersect the visible region of hdc.
1447 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1449 POINT Origin, Position;
1450 RECT rcItem, rcClip;
1453 rgntype = GetClipBox(hdc, &rcClip);
1454 if (rgntype == NULLREGION) return iterator_empty(i);
1455 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1456 if (rgntype == SIMPLEREGION) return TRUE;
1458 /* first deal with the special item */
1459 if (i->nSpecial != -1)
1461 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1462 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1465 /* if we can't deal with the region, we'll just go with the simple range */
1466 LISTVIEW_GetOrigin(infoPtr, &Origin);
1467 TRACE("building visible range:\n");
1468 if (!i->ranges && i->range.lower < i->range.upper)
1470 if (!(i->ranges = ranges_create(50))) return TRUE;
1471 if (!ranges_add(i->ranges, i->range))
1473 ranges_destroy(i->ranges);
1479 /* now delete the invisible items from the list */
1480 while(iterator_next(i))
1482 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1483 rcItem.left = (infoPtr->uView == LV_VIEW_DETAILS) ? Origin.x : Position.x + Origin.x;
1484 rcItem.top = Position.y + Origin.y;
1485 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1486 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1487 if (!RectVisible(hdc, &rcItem))
1488 ranges_delitem(i->ranges, i->nItem);
1490 /* the iterator should restart on the next iterator_next */
1496 /* Remove common elements from two iterators */
1497 /* Passed iterators have to point on the first elements */
1498 static BOOL iterator_remove_common_items(ITERATOR *iter1, ITERATOR *iter2)
1500 if(!iter1->ranges || !iter2->ranges) {
1503 if(iter1->ranges || iter2->ranges ||
1504 (iter1->range.lower<iter2->range.lower && iter1->range.upper>iter2->range.upper) ||
1505 (iter1->range.lower>iter2->range.lower && iter1->range.upper<iter2->range.upper)) {
1506 ERR("result is not a one range iterator\n");
1510 if(iter1->range.lower==-1 || iter2->range.lower==-1)
1513 lower = iter1->range.lower;
1514 upper = iter1->range.upper;
1516 if(lower < iter2->range.lower)
1517 iter1->range.upper = iter2->range.lower;
1518 else if(upper > iter2->range.upper)
1519 iter1->range.lower = iter2->range.upper;
1521 iter1->range.lower = iter1->range.upper = -1;
1523 if(iter2->range.lower < lower)
1524 iter2->range.upper = lower;
1525 else if(iter2->range.upper > upper)
1526 iter2->range.lower = upper;
1528 iter2->range.lower = iter2->range.upper = -1;
1533 iterator_next(iter1);
1534 iterator_next(iter2);
1537 if(iter1->nItem==-1 || iter2->nItem==-1)
1540 if(iter1->nItem == iter2->nItem) {
1541 int delete = iter1->nItem;
1543 iterator_prev(iter1);
1544 iterator_prev(iter2);
1545 ranges_delitem(iter1->ranges, delete);
1546 ranges_delitem(iter2->ranges, delete);
1547 iterator_next(iter1);
1548 iterator_next(iter2);
1549 } else if(iter1->nItem > iter2->nItem)
1550 iterator_next(iter2);
1552 iterator_next(iter1);
1555 iter1->nItem = iter1->range.lower = iter1->range.upper = -1;
1556 iter2->nItem = iter2->range.lower = iter2->range.upper = -1;
1560 /******** Misc helper functions ************************************/
1562 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1563 WPARAM wParam, LPARAM lParam, BOOL isW)
1565 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1566 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1569 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1571 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1572 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON);
1575 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1577 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1578 if(state == 1 || state == 2)
1582 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1583 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1584 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1588 /* this should be called after window style got updated,
1589 it used to reset view state to match current window style */
1590 static inline void map_style_view(LISTVIEW_INFO *infoPtr)
1592 switch (infoPtr->dwStyle & LVS_TYPEMASK)
1595 infoPtr->uView = LV_VIEW_ICON;
1598 infoPtr->uView = LV_VIEW_DETAILS;
1601 infoPtr->uView = LV_VIEW_SMALLICON;
1604 infoPtr->uView = LV_VIEW_LIST;
1608 /* computes next item id value */
1609 static DWORD get_next_itemid(const LISTVIEW_INFO *infoPtr)
1611 INT count = DPA_GetPtrCount(infoPtr->hdpaItemIds);
1615 ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, count - 1);
1616 return lpID->id + 1;
1621 /******** Internal API functions ************************************/
1623 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1625 static COLUMN_INFO mainItem;
1627 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1628 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1630 /* update cached column rectangles */
1631 if (infoPtr->colRectsDirty)
1634 LISTVIEW_INFO *Ptr = (LISTVIEW_INFO*)infoPtr;
1637 for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++) {
1638 info = DPA_GetPtr(infoPtr->hdpaColumns, i);
1639 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, i, (LPARAM)&info->rcHeader);
1641 Ptr->colRectsDirty = FALSE;
1644 return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1647 static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr)
1649 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
1652 if (infoPtr->hwndHeader) return 0;
1654 TRACE("Creating header for list %p\n", infoPtr->hwndSelf);
1656 /* setup creation flags */
1657 dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
1658 dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
1660 hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
1663 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
1664 0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
1665 if (!infoPtr->hwndHeader) return -1;
1667 /* set header unicode format */
1668 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
1670 /* set header font */
1671 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, TRUE);
1673 LISTVIEW_UpdateSize(infoPtr);
1678 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1680 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1683 static inline BOOL LISTVIEW_IsHeaderEnabled(const LISTVIEW_INFO *infoPtr)
1685 return (infoPtr->uView == LV_VIEW_DETAILS ||
1686 infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS) &&
1687 !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER);
1690 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1692 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1695 /* used to handle collapse main item column case */
1696 static inline BOOL LISTVIEW_DrawFocusRect(const LISTVIEW_INFO *infoPtr, HDC hdc)
1698 return (infoPtr->rcFocus.left < infoPtr->rcFocus.right) ?
1699 DrawFocusRect(hdc, &infoPtr->rcFocus) : FALSE;
1702 /* Listview invalidation functions: use _only_ these functions to invalidate */
1704 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1706 return infoPtr->bRedraw;
1709 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1711 if(!is_redrawing(infoPtr)) return;
1712 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1713 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1716 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1720 if(!is_redrawing(infoPtr)) return;
1721 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1722 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1725 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1727 POINT Origin, Position;
1730 if(!is_redrawing(infoPtr)) return;
1731 assert (infoPtr->uView == LV_VIEW_DETAILS);
1732 LISTVIEW_GetOrigin(infoPtr, &Origin);
1733 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1734 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1736 rcBox.bottom = infoPtr->nItemHeight;
1737 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1738 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1741 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1743 LISTVIEW_InvalidateRect(infoPtr, NULL);
1746 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1750 if(!is_redrawing(infoPtr)) return;
1751 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1752 rcCol.top = infoPtr->rcList.top;
1753 rcCol.bottom = infoPtr->rcList.bottom;
1754 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1759 * Retrieves the number of items that can fit vertically in the client area.
1762 * [I] infoPtr : valid pointer to the listview structure
1765 * Number of items per row.
1767 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1769 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1771 return max(nListWidth/(infoPtr->nItemWidth ? infoPtr->nItemWidth : 1), 1);
1776 * Retrieves the number of items that can fit horizontally in the client
1780 * [I] infoPtr : valid pointer to the listview structure
1783 * Number of items per column.
1785 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1787 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1789 return max(nListHeight / infoPtr->nItemHeight, 1);
1793 /*************************************************************************
1794 * LISTVIEW_ProcessLetterKeys
1796 * Processes keyboard messages generated by pressing the letter keys
1798 * What this does is perform a case insensitive search from the
1799 * current position with the following quirks:
1800 * - If two chars or more are pressed in quick succession we search
1801 * for the corresponding string (e.g. 'abc').
1802 * - If there is a delay we wipe away the current search string and
1803 * restart with just that char.
1804 * - If the user keeps pressing the same character, whether slowly or
1805 * fast, so that the search string is entirely composed of this
1806 * character ('aaaaa' for instance), then we search for first item
1807 * that starting with that character.
1808 * - If the user types the above character in quick succession, then
1809 * we must also search for the corresponding string ('aaaaa'), and
1810 * go to that string if there is a match.
1813 * [I] hwnd : handle to the window
1814 * [I] charCode : the character code, the actual character
1815 * [I] keyData : key data
1823 * - The current implementation has a list of characters it will
1824 * accept and it ignores everything else. In particular it will
1825 * ignore accentuated characters which seems to match what
1826 * Windows does. But I'm not sure it makes sense to follow
1828 * - We don't sound a beep when the search fails.
1832 * TREEVIEW_ProcessLetterKeys
1834 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1836 WCHAR buffer[MAX_PATH];
1837 INT endidx, startidx;
1843 /* simple parameter checking */
1844 if (!charCode || !keyData || infoPtr->nItemCount == 0) return 0;
1846 /* only allow the valid WM_CHARs through */
1847 if (!isalnumW(charCode) &&
1848 charCode != '.' && charCode != '`' && charCode != '!' &&
1849 charCode != '@' && charCode != '#' && charCode != '$' &&
1850 charCode != '%' && charCode != '^' && charCode != '&' &&
1851 charCode != '*' && charCode != '(' && charCode != ')' &&
1852 charCode != '-' && charCode != '_' && charCode != '+' &&
1853 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1854 charCode != '}' && charCode != '[' && charCode != '{' &&
1855 charCode != '/' && charCode != '?' && charCode != '>' &&
1856 charCode != '<' && charCode != ',' && charCode != '~')
1859 /* update the search parameters */
1860 prevTime = infoPtr->lastKeyPressTimestamp;
1861 infoPtr->lastKeyPressTimestamp = GetTickCount();
1862 diff = infoPtr->lastKeyPressTimestamp - prevTime;
1864 if (diff >= 0 && diff < KEY_DELAY)
1866 if (infoPtr->nSearchParamLength < MAX_PATH - 1)
1867 infoPtr->szSearchParam[infoPtr->nSearchParamLength++] = charCode;
1869 if (infoPtr->charCode != charCode)
1870 infoPtr->charCode = charCode = 0;
1874 infoPtr->charCode = charCode;
1875 infoPtr->szSearchParam[0] = charCode;
1876 infoPtr->nSearchParamLength = 1;
1879 /* and search from the current position */
1881 endidx = infoPtr->nItemCount;
1883 /* should start from next after focused item, so next item that matches
1884 will be selected, if there isn't any and focused matches it will be selected
1885 on second search stage from beginning of the list */
1886 if (infoPtr->nFocusedItem >= 0 && infoPtr->nItemCount > 1)
1887 startidx = infoPtr->nFocusedItem + 1;
1891 /* let application handle this for virtual listview */
1892 if (infoPtr->dwStyle & LVS_OWNERDATA)
1896 memset(&nmlv.lvfi, 0, sizeof(nmlv.lvfi));
1897 nmlv.lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1898 nmlv.lvfi.psz = infoPtr->szSearchParam;
1899 nmlv.iStart = startidx;
1901 infoPtr->szSearchParam[infoPtr->nSearchParamLength] = 0;
1903 nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1909 /* first search in [startidx, endidx), on failure continue in [0, startidx) */
1912 /* start from first item if not found with >= startidx */
1913 if (i == infoPtr->nItemCount && startidx > 0)
1919 for (i = startidx; i < endidx; i++)
1922 item.mask = LVIF_TEXT;
1925 item.pszText = buffer;
1926 item.cchTextMax = MAX_PATH;
1927 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1929 if (lstrncmpiW(item.pszText, infoPtr->szSearchParam, infoPtr->nSearchParamLength) == 0)
1934 else if (nItem == -1 && lstrncmpiW(item.pszText, infoPtr->szSearchParam, 1) == 0)
1936 /* this would work but we must keep looking for a longer match */
1941 if ( nItem != -1 || /* found something */
1942 endidx != infoPtr->nItemCount || /* second search done */
1943 (startidx == 0 && endidx == infoPtr->nItemCount) /* full range for first search */ )
1949 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1954 /*************************************************************************
1955 * LISTVIEW_UpdateHeaderSize [Internal]
1957 * Function to resize the header control
1960 * [I] hwnd : handle to a window
1961 * [I] nNewScrollPos : scroll pos to set
1966 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1971 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1973 if (!infoPtr->hwndHeader) return;
1975 GetWindowRect(infoPtr->hwndHeader, &winRect);
1976 point[0].x = winRect.left;
1977 point[0].y = winRect.top;
1978 point[1].x = winRect.right;
1979 point[1].y = winRect.bottom;
1981 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1982 point[0].x = -nNewScrollPos;
1983 point[1].x += nNewScrollPos;
1985 SetWindowPos(infoPtr->hwndHeader,0,
1986 point[0].x,point[0].y,point[1].x,point[1].y,
1987 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
1988 SWP_NOZORDER | SWP_NOACTIVATE);
1993 * Update the scrollbars. This functions should be called whenever
1994 * the content, size or view changes.
1997 * [I] infoPtr : valid pointer to the listview structure
2002 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
2004 SCROLLINFO horzInfo, vertInfo;
2007 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
2009 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
2010 horzInfo.cbSize = sizeof(SCROLLINFO);
2011 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
2013 /* for now, we'll set info.nMax to the _count_, and adjust it later */
2014 if (infoPtr->uView == LV_VIEW_LIST)
2016 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
2017 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
2019 /* scroll by at least one column per page */
2020 if(horzInfo.nPage < infoPtr->nItemWidth)
2021 horzInfo.nPage = infoPtr->nItemWidth;
2023 if (infoPtr->nItemWidth)
2024 horzInfo.nPage /= infoPtr->nItemWidth;
2026 else if (infoPtr->uView == LV_VIEW_DETAILS)
2028 horzInfo.nMax = infoPtr->nItemWidth;
2030 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2034 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
2037 if (LISTVIEW_IsHeaderEnabled(infoPtr))
2039 if (DPA_GetPtrCount(infoPtr->hdpaColumns))
2044 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2045 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2047 LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2048 horzInfo.nMax = rcHeader.right;
2049 TRACE("horzInfo.nMax=%d\n", horzInfo.nMax);
2053 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
2054 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
2055 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
2056 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
2057 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
2059 /* Setting the horizontal scroll can change the listview size
2060 * (and potentially everything else) so we need to recompute
2061 * everything again for the vertical scroll
2064 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
2065 vertInfo.cbSize = sizeof(SCROLLINFO);
2066 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
2068 if (infoPtr->uView == LV_VIEW_DETAILS)
2070 vertInfo.nMax = infoPtr->nItemCount;
2072 /* scroll by at least one page */
2073 if(vertInfo.nPage < infoPtr->nItemHeight)
2074 vertInfo.nPage = infoPtr->nItemHeight;
2076 if (infoPtr->nItemHeight > 0)
2077 vertInfo.nPage /= infoPtr->nItemHeight;
2079 else if (infoPtr->uView != LV_VIEW_LIST) /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2083 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
2086 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
2087 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
2088 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
2089 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
2090 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
2092 /* Change of the range may have changed the scroll pos. If so move the content */
2093 if (dx != 0 || dy != 0)
2096 listRect = infoPtr->rcList;
2097 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
2098 SW_ERASE | SW_INVALIDATE);
2101 /* Update the Header Control */
2102 if (infoPtr->hwndHeader)
2104 horzInfo.fMask = SIF_POS;
2105 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
2106 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
2113 * Shows/hides the focus rectangle.
2116 * [I] infoPtr : valid pointer to the listview structure
2117 * [I] fShow : TRUE to show the focus, FALSE to hide it.
2122 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
2126 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
2128 if (infoPtr->nFocusedItem < 0) return;
2130 /* we need some gymnastics in ICON mode to handle large items */
2131 if (infoPtr->uView == LV_VIEW_ICON)
2135 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
2136 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
2138 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
2143 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
2145 /* for some reason, owner draw should work only in report mode */
2146 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
2151 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2152 HFONT hOldFont = SelectObject(hdc, hFont);
2154 item.iItem = infoPtr->nFocusedItem;
2156 item.mask = LVIF_PARAM;
2157 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
2159 ZeroMemory(&dis, sizeof(dis));
2160 dis.CtlType = ODT_LISTVIEW;
2161 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
2162 dis.itemID = item.iItem;
2163 dis.itemAction = ODA_FOCUS;
2164 if (fShow) dis.itemState |= ODS_FOCUS;
2165 dis.hwndItem = infoPtr->hwndSelf;
2167 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
2168 dis.itemData = item.lParam;
2170 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
2172 SelectObject(hdc, hOldFont);
2176 LISTVIEW_DrawFocusRect(infoPtr, hdc);
2179 ReleaseDC(infoPtr->hwndSelf, hdc);
2183 * Invalidates all visible selected items.
2185 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
2189 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
2190 while(iterator_next(&i))
2192 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
2193 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
2195 iterator_destroy(&i);
2200 * DESCRIPTION: [INTERNAL]
2201 * Computes an item's (left,top) corner, relative to rcView.
2202 * That is, the position has NOT been made relative to the Origin.
2203 * This is deliberate, to avoid computing the Origin over, and
2204 * over again, when this function is called in a loop. Instead,
2205 * one can factor the computation of the Origin before the loop,
2206 * and offset the value returned by this function, on every iteration.
2209 * [I] infoPtr : valid pointer to the listview structure
2210 * [I] nItem : item number
2211 * [O] lpptOrig : item top, left corner
2216 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
2218 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
2220 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
2222 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2223 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2225 else if (infoPtr->uView == LV_VIEW_LIST)
2227 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
2228 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
2229 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
2231 else /* LV_VIEW_DETAILS */
2233 lpptPosition->x = REPORT_MARGINX;
2234 /* item is always at zero indexed column */
2235 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2236 lpptPosition->x += LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
2237 lpptPosition->y = nItem * infoPtr->nItemHeight;
2242 * DESCRIPTION: [INTERNAL]
2243 * Compute the rectangles of an item. This is to localize all
2244 * the computations in one place. If you are not interested in some
2245 * of these values, simply pass in a NULL -- the function is smart
2246 * enough to compute only what's necessary. The function computes
2247 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
2248 * one, the BOX rectangle. This rectangle is very cheap to compute,
2249 * and is guaranteed to contain all the other rectangles. Computing
2250 * the ICON rect is also cheap, but all the others are potentially
2251 * expensive. This gives an easy and effective optimization when
2252 * searching (like point inclusion, or rectangle intersection):
2253 * first test against the BOX, and if TRUE, test against the desired
2255 * If the function does not have all the necessary information
2256 * to computed the requested rectangles, will crash with a
2257 * failed assertion. This is done so we catch all programming
2258 * errors, given that the function is called only from our code.
2260 * We have the following 'special' meanings for a few fields:
2261 * * If LVIS_FOCUSED is set, we assume the item has the focus
2262 * This is important in ICON mode, where it might get a larger
2263 * then usual rectangle
2265 * Please note that subitem support works only in REPORT mode.
2268 * [I] infoPtr : valid pointer to the listview structure
2269 * [I] lpLVItem : item to compute the measures for
2270 * [O] lprcBox : ptr to Box rectangle
2271 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
2272 * [0] lprcSelectBox : ptr to select box rectangle
2273 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
2274 * [O] lprcIcon : ptr to Icon rectangle
2275 * Same as LVM_GETITEMRECT with LVIR_ICON
2276 * [O] lprcStateIcon: ptr to State Icon rectangle
2277 * [O] lprcLabel : ptr to Label rectangle
2278 * Same as LVM_GETITEMRECT with LVIR_LABEL
2283 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
2284 LPRECT lprcBox, LPRECT lprcSelectBox,
2285 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
2287 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
2288 RECT Box, SelectBox, Icon, Label;
2289 COLUMN_INFO *lpColumnInfo = NULL;
2290 SIZE labelSize = { 0, 0 };
2292 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
2294 /* Be smart and try to figure out the minimum we have to do */
2295 if (lpLVItem->iSubItem) assert(infoPtr->uView == LV_VIEW_DETAILS);
2296 if (infoPtr->uView == LV_VIEW_ICON && (lprcBox || lprcLabel))
2298 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
2299 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
2301 if (lprcSelectBox) doSelectBox = TRUE;
2302 if (lprcLabel) doLabel = TRUE;
2303 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
2310 /************************************************************/
2311 /* compute the box rectangle (it should be cheap to do) */
2312 /************************************************************/
2313 if (lpLVItem->iSubItem || infoPtr->uView == LV_VIEW_DETAILS)
2314 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
2316 if (lpLVItem->iSubItem)
2318 Box = lpColumnInfo->rcHeader;
2323 Box.right = infoPtr->nItemWidth;
2326 Box.bottom = infoPtr->nItemHeight;
2328 /******************************************************************/
2329 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2330 /******************************************************************/
2333 LONG state_width = 0;
2335 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
2336 state_width = infoPtr->iconStateSize.cx;
2338 if (infoPtr->uView == LV_VIEW_ICON)
2340 Icon.left = Box.left + state_width;
2341 if (infoPtr->himlNormal)
2342 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2343 Icon.top = Box.top + ICON_TOP_PADDING;
2344 Icon.right = Icon.left;
2345 Icon.bottom = Icon.top;
2346 if (infoPtr->himlNormal)
2348 Icon.right += infoPtr->iconSize.cx;
2349 Icon.bottom += infoPtr->iconSize.cy;
2352 else /* LV_VIEW_SMALLICON, LV_VIEW_LIST or LV_VIEW_DETAILS */
2354 Icon.left = Box.left + state_width;
2356 if (infoPtr->uView == LV_VIEW_DETAILS && lpLVItem->iSubItem == 0)
2358 /* we need the indent in report mode */
2359 assert(lpLVItem->mask & LVIF_INDENT);
2360 Icon.left += infoPtr->iconSize.cx * lpLVItem->iIndent + REPORT_MARGINX;
2364 Icon.right = Icon.left;
2365 if (infoPtr->himlSmall &&
2366 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2367 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2368 Icon.right += infoPtr->iconSize.cx;
2369 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2371 if(lprcIcon) *lprcIcon = Icon;
2372 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2374 /* TODO: is this correct? */
2377 lprcStateIcon->left = Icon.left - state_width;
2378 lprcStateIcon->right = Icon.left;
2379 lprcStateIcon->top = Icon.top;
2380 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2381 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2384 else Icon.right = 0;
2386 /************************************************************/
2387 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2388 /************************************************************/
2391 /* calculate how far to the right can the label stretch */
2392 Label.right = Box.right;
2393 if (infoPtr->uView == LV_VIEW_DETAILS)
2395 if (lpLVItem->iSubItem == 0)
2397 /* we need a zero based rect here */
2398 Label = lpColumnInfo->rcHeader;
2399 OffsetRect(&Label, -Label.left, 0);
2403 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && infoPtr->uView == LV_VIEW_DETAILS))
2405 labelSize.cx = infoPtr->nItemWidth;
2406 labelSize.cy = infoPtr->nItemHeight;
2410 /* we need the text in non owner draw mode */
2411 assert(lpLVItem->mask & LVIF_TEXT);
2412 if (is_text(lpLVItem->pszText))
2414 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2415 HDC hdc = GetDC(infoPtr->hwndSelf);
2416 HFONT hOldFont = SelectObject(hdc, hFont);
2420 /* compute rough rectangle where the label will go */
2421 SetRectEmpty(&rcText);
2422 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2423 rcText.bottom = infoPtr->nItemHeight;
2424 if (infoPtr->uView == LV_VIEW_ICON)
2425 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2427 /* now figure out the flags */
2428 if (infoPtr->uView == LV_VIEW_ICON)
2429 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2431 uFormat = LV_SL_DT_FLAGS;
2433 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2435 if (rcText.right != rcText.left)
2436 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2438 labelSize.cy = rcText.bottom - rcText.top;
2440 SelectObject(hdc, hOldFont);
2441 ReleaseDC(infoPtr->hwndSelf, hdc);
2445 if (infoPtr->uView == LV_VIEW_ICON)
2447 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2448 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2449 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2450 Label.right = Label.left + labelSize.cx;
2451 Label.bottom = Label.top + infoPtr->nItemHeight;
2452 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2454 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2455 labelSize.cy /= infoPtr->ntmHeight;
2456 labelSize.cy = max(labelSize.cy, 1);
2457 labelSize.cy *= infoPtr->ntmHeight;
2459 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2461 else if (infoPtr->uView == LV_VIEW_DETAILS)
2463 Label.left = Icon.right;
2464 Label.top = Box.top;
2465 Label.right = lpLVItem->iSubItem ? lpColumnInfo->rcHeader.right :
2466 lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
2467 Label.bottom = Label.top + infoPtr->nItemHeight;
2469 else /* LV_VIEW_SMALLICON or LV_VIEW_LIST */
2471 Label.left = Icon.right;
2472 Label.top = Box.top;
2473 Label.right = min(Label.left + labelSize.cx, Label.right);
2474 Label.bottom = Label.top + infoPtr->nItemHeight;
2477 if (lprcLabel) *lprcLabel = Label;
2478 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2481 /************************************************************/
2482 /* compute SELECT bounding box */
2483 /************************************************************/
2486 if (infoPtr->uView == LV_VIEW_DETAILS)
2488 SelectBox.left = Icon.left;
2489 SelectBox.top = Box.top;
2490 SelectBox.bottom = Box.bottom;
2493 SelectBox.right = min(Label.left + labelSize.cx, Label.right);
2495 SelectBox.right = min(Label.left + MAX_EMPTYTEXT_SELECT_WIDTH, Label.right);
2499 UnionRect(&SelectBox, &Icon, &Label);
2501 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2502 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2505 /* Fix the Box if necessary */
2508 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2509 else *lprcBox = Box;
2511 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2515 * DESCRIPTION: [INTERNAL]
2518 * [I] infoPtr : valid pointer to the listview structure
2519 * [I] nItem : item number
2520 * [O] lprcBox : ptr to Box rectangle
2525 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2527 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2528 POINT Position, Origin;
2531 LISTVIEW_GetOrigin(infoPtr, &Origin);
2532 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2534 /* Be smart and try to figure out the minimum we have to do */
2536 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2537 lvItem.mask |= LVIF_TEXT;
2538 lvItem.iItem = nItem;
2539 lvItem.iSubItem = 0;
2540 lvItem.pszText = szDispText;
2541 lvItem.cchTextMax = DISP_TEXT_SIZE;
2542 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2543 if (infoPtr->uView == LV_VIEW_ICON)
2545 lvItem.mask |= LVIF_STATE;
2546 lvItem.stateMask = LVIS_FOCUSED;
2547 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2549 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2551 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
2552 SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0))
2554 OffsetRect(lprcBox, Origin.x, Position.y + Origin.y);
2557 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2560 /* LISTVIEW_MapIdToIndex helper */
2561 static INT CALLBACK MapIdSearchCompare(LPVOID p1, LPVOID p2, LPARAM lParam)
2563 ITEM_ID *id1 = (ITEM_ID*)p1;
2564 ITEM_ID *id2 = (ITEM_ID*)p2;
2566 if (id1->id == id2->id) return 0;
2568 return (id1->id < id2->id) ? -1 : 1;
2573 * Returns the item index for id specified.
2576 * [I] infoPtr : valid pointer to the listview structure
2577 * [I] iID : item id to get index for
2580 * Item index, or -1 on failure.
2582 static INT LISTVIEW_MapIdToIndex(const LISTVIEW_INFO *infoPtr, UINT iID)
2587 TRACE("iID=%d\n", iID);
2589 if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2590 if (infoPtr->nItemCount == 0) return -1;
2593 index = DPA_Search(infoPtr->hdpaItemIds, &ID, -1, MapIdSearchCompare, 0, DPAS_SORTED);
2597 ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, index);
2598 return DPA_GetPtrIndex(infoPtr->hdpaItems, lpID->item);
2606 * Returns the item id for index given.
2609 * [I] infoPtr : valid pointer to the listview structure
2610 * [I] iItem : item index to get id for
2615 static DWORD LISTVIEW_MapIndexToId(const LISTVIEW_INFO *infoPtr, INT iItem)
2620 TRACE("iItem=%d\n", iItem);
2622 if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2623 if (iItem < 0 || iItem >= infoPtr->nItemCount) return -1;
2625 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, iItem);
2626 lpItem = DPA_GetPtr(hdpaSubItems, 0);
2628 return lpItem->id->id;
2633 * Returns the current icon position, and advances it along the top.
2634 * The returned position is not offset by Origin.
2637 * [I] infoPtr : valid pointer to the listview structure
2638 * [O] lpPos : will get the current icon position
2643 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2645 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2647 *lpPos = infoPtr->currIconPos;
2649 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2650 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2652 infoPtr->currIconPos.x = 0;
2653 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2659 * Returns the current icon position, and advances it down the left edge.
2660 * The returned position is not offset by Origin.
2663 * [I] infoPtr : valid pointer to the listview structure
2664 * [O] lpPos : will get the current icon position
2669 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2671 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2673 *lpPos = infoPtr->currIconPos;
2675 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2676 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2678 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2679 infoPtr->currIconPos.y = 0;
2685 * Moves an icon to the specified position.
2686 * It takes care of invalidating the item, etc.
2689 * [I] infoPtr : valid pointer to the listview structure
2690 * [I] nItem : the item to move
2691 * [I] lpPos : the new icon position
2692 * [I] isNew : flags the item as being new
2698 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2704 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2705 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2707 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2708 LISTVIEW_InvalidateItem(infoPtr, nItem);
2711 /* Allocating a POINTER for every item is too resource intensive,
2712 * so we'll keep the (x,y) in different arrays */
2713 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2714 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2716 LISTVIEW_InvalidateItem(infoPtr, nItem);
2723 * Arranges listview items in icon display mode.
2726 * [I] infoPtr : valid pointer to the listview structure
2727 * [I] nAlignCode : alignment code
2733 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2735 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2739 if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON) return FALSE;
2741 TRACE("nAlignCode=%d\n", nAlignCode);
2743 if (nAlignCode == LVA_DEFAULT)
2745 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2746 else nAlignCode = LVA_ALIGNTOP;
2751 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2752 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2753 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2754 default: return FALSE;
2757 infoPtr->bAutoarrange = TRUE;
2758 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2759 for (i = 0; i < infoPtr->nItemCount; i++)
2761 next_pos(infoPtr, &pos);
2762 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2770 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2771 * For LVS_REPORT always returns empty rectangle.
2774 * [I] infoPtr : valid pointer to the listview structure
2775 * [O] lprcView : bounding rectangle
2781 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2785 SetRectEmpty(lprcView);
2787 switch (infoPtr->uView)
2790 case LV_VIEW_SMALLICON:
2791 for (i = 0; i < infoPtr->nItemCount; i++)
2793 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2794 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2795 lprcView->right = max(lprcView->right, x);
2796 lprcView->bottom = max(lprcView->bottom, y);
2798 if (infoPtr->nItemCount > 0)
2800 lprcView->right += infoPtr->nItemWidth;
2801 lprcView->bottom += infoPtr->nItemHeight;
2806 y = LISTVIEW_GetCountPerColumn(infoPtr);
2807 x = infoPtr->nItemCount / y;
2808 if (infoPtr->nItemCount % y) x++;
2809 lprcView->right = x * infoPtr->nItemWidth;
2810 lprcView->bottom = y * infoPtr->nItemHeight;
2817 * Retrieves the bounding rectangle of all the items.
2820 * [I] infoPtr : valid pointer to the listview structure
2821 * [O] lprcView : bounding rectangle
2827 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2831 TRACE("(lprcView=%p)\n", lprcView);
2833 if (!lprcView) return FALSE;
2835 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2837 if (infoPtr->uView != LV_VIEW_DETAILS)
2839 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2840 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2843 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2850 * Retrieves the subitem pointer associated with the subitem index.
2853 * [I] hdpaSubItems : DPA handle for a specific item
2854 * [I] nSubItem : index of subitem
2857 * SUCCESS : subitem pointer
2860 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2862 SUBITEM_INFO *lpSubItem;
2865 /* we should binary search here if need be */
2866 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2868 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2869 if (lpSubItem->iSubItem == nSubItem)
2879 * Calculates the desired item width.
2882 * [I] infoPtr : valid pointer to the listview structure
2885 * The desired item width.
2887 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2891 TRACE("uView=%d\n", infoPtr->uView);
2893 if (infoPtr->uView == LV_VIEW_ICON)
2894 nItemWidth = infoPtr->iconSpacing.cx;
2895 else if (infoPtr->uView == LV_VIEW_DETAILS)
2897 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2902 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2903 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2905 LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2906 nItemWidth = rcHeader.right;
2909 else /* LV_VIEW_SMALLICON, or LV_VIEW_LIST */
2911 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2915 lvItem.mask = LVIF_TEXT;
2916 lvItem.iSubItem = 0;
2918 for (i = 0; i < infoPtr->nItemCount; i++)
2921 lvItem.pszText = szDispText;
2922 lvItem.cchTextMax = DISP_TEXT_SIZE;
2923 if (LISTVIEW_GetItemW(infoPtr, &lvItem))
2924 nItemWidth = max(LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE),
2928 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2929 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2931 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2939 * Calculates the desired item height.
2942 * [I] infoPtr : valid pointer to the listview structure
2945 * The desired item height.
2947 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2951 TRACE("uView=%d\n", infoPtr->uView);
2953 if (infoPtr->uView == LV_VIEW_ICON)
2954 nItemHeight = infoPtr->iconSpacing.cy;
2957 nItemHeight = infoPtr->ntmHeight;
2958 if (infoPtr->himlState)
2959 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2960 if (infoPtr->himlSmall)
2961 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2962 nItemHeight += HEIGHT_PADDING;
2963 if (infoPtr->nMeasureItemHeight > 0)
2964 nItemHeight = infoPtr->nMeasureItemHeight;
2967 return max(nItemHeight, 1);
2972 * Updates the width, and height of an item.
2975 * [I] infoPtr : valid pointer to the listview structure
2980 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2982 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2983 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2989 * Retrieves and saves important text metrics info for the current
2993 * [I] infoPtr : valid pointer to the listview structure
2996 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2998 HDC hdc = GetDC(infoPtr->hwndSelf);
2999 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
3000 HFONT hOldFont = SelectObject(hdc, hFont);
3004 if (GetTextMetricsW(hdc, &tm))
3006 infoPtr->ntmHeight = tm.tmHeight;
3007 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
3010 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
3011 infoPtr->nEllipsisWidth = sz.cx;
3013 SelectObject(hdc, hOldFont);
3014 ReleaseDC(infoPtr->hwndSelf, hdc);
3016 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
3021 * A compare function for ranges
3024 * [I] range1 : pointer to range 1;
3025 * [I] range2 : pointer to range 2;
3029 * > 0 : if range 1 > range 2
3030 * < 0 : if range 2 > range 1
3031 * = 0 : if range intersects range 2
3033 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
3037 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
3039 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
3044 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
3049 #define ranges_check(ranges, desc) if (TRACE_ON(listview)) ranges_assert(ranges, desc, __FILE__, __LINE__)
3051 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *file, int line)
3056 TRACE("*** Checking %s:%d:%s ***\n", file, line, desc);
3058 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
3059 ranges_dump(ranges);
3060 if (DPA_GetPtrCount(ranges->hdpa) > 0)
3062 prev = DPA_GetPtr(ranges->hdpa, 0);
3063 assert (prev->lower >= 0 && prev->lower < prev->upper);
3064 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
3066 curr = DPA_GetPtr(ranges->hdpa, i);
3067 assert (prev->upper <= curr->lower);
3068 assert (curr->lower < curr->upper);
3072 TRACE("--- Done checking---\n");
3075 static RANGES ranges_create(int count)
3077 RANGES ranges = Alloc(sizeof(struct tagRANGES));
3078 if (!ranges) return NULL;
3079 ranges->hdpa = DPA_Create(count);
3080 if (ranges->hdpa) return ranges;
3085 static void ranges_clear(RANGES ranges)
3089 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3090 Free(DPA_GetPtr(ranges->hdpa, i));
3091 DPA_DeleteAllPtrs(ranges->hdpa);
3095 static void ranges_destroy(RANGES ranges)
3097 if (!ranges) return;
3098 ranges_clear(ranges);
3099 DPA_Destroy(ranges->hdpa);
3103 static RANGES ranges_clone(RANGES ranges)
3108 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
3110 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3112 RANGE *newrng = Alloc(sizeof(RANGE));
3113 if (!newrng) goto fail;
3114 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
3115 DPA_SetPtr(clone->hdpa, i, newrng);
3120 TRACE ("clone failed\n");
3121 ranges_destroy(clone);
3125 static RANGES ranges_diff(RANGES ranges, RANGES sub)
3129 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
3130 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
3135 static void ranges_dump(RANGES ranges)
3139 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3140 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
3143 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
3145 RANGE srchrng = { nItem, nItem + 1 };
3147 TRACE("(nItem=%d)\n", nItem);
3148 ranges_check(ranges, "before contain");
3149 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
3152 static INT ranges_itemcount(RANGES ranges)
3156 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3158 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
3159 count += sel->upper - sel->lower;
3165 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
3167 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
3170 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3171 if (index == -1) return TRUE;
3173 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
3175 chkrng = DPA_GetPtr(ranges->hdpa, index);
3176 if (chkrng->lower >= nItem)
3177 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
3178 if (chkrng->upper > nItem)
3179 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
3184 static BOOL ranges_add(RANGES ranges, RANGE range)
3189 TRACE("(%s)\n", debugrange(&range));
3190 ranges_check(ranges, "before add");
3192 /* try find overlapping regions first */
3193 srchrgn.lower = range.lower - 1;
3194 srchrgn.upper = range.upper + 1;
3195 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
3201 TRACE("Adding new range\n");
3203 /* create the brand new range to insert */
3204 newrgn = Alloc(sizeof(RANGE));
3205 if(!newrgn) goto fail;
3208 /* figure out where to insert it */
3209 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3210 TRACE("index=%d\n", index);
3211 if (index == -1) index = 0;
3213 /* and get it over with */
3214 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3222 RANGE *chkrgn, *mrgrgn;
3223 INT fromindex, mergeindex;
3225 chkrgn = DPA_GetPtr(ranges->hdpa, index);
3226 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
3228 chkrgn->lower = min(range.lower, chkrgn->lower);
3229 chkrgn->upper = max(range.upper, chkrgn->upper);
3231 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
3233 /* merge now common ranges */
3235 srchrgn.lower = chkrgn->lower - 1;
3236 srchrgn.upper = chkrgn->upper + 1;
3240 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
3241 if (mergeindex == -1) break;
3242 if (mergeindex == index)
3244 fromindex = index + 1;
3248 TRACE("Merge with index %i\n", mergeindex);
3250 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
3251 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
3252 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
3254 DPA_DeletePtr(ranges->hdpa, mergeindex);
3255 if (mergeindex < index) index --;
3259 ranges_check(ranges, "after add");
3263 ranges_check(ranges, "failed add");
3267 static BOOL ranges_del(RANGES ranges, RANGE range)
3272 TRACE("(%s)\n", debugrange(&range));
3273 ranges_check(ranges, "before del");
3275 /* we don't use DPAS_SORTED here, since we need *
3276 * to find the first overlapping range */
3277 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
3280 chkrgn = DPA_GetPtr(ranges->hdpa, index);
3282 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
3284 /* case 1: Same range */
3285 if ( (chkrgn->upper == range.upper) &&
3286 (chkrgn->lower == range.lower) )
3288 DPA_DeletePtr(ranges->hdpa, index);
3292 /* case 2: engulf */
3293 else if ( (chkrgn->upper <= range.upper) &&
3294 (chkrgn->lower >= range.lower) )
3296 DPA_DeletePtr(ranges->hdpa, index);
3299 /* case 3: overlap upper */
3300 else if ( (chkrgn->upper <= range.upper) &&
3301 (chkrgn->lower < range.lower) )
3303 chkrgn->upper = range.lower;
3305 /* case 4: overlap lower */
3306 else if ( (chkrgn->upper > range.upper) &&
3307 (chkrgn->lower >= range.lower) )
3309 chkrgn->lower = range.upper;
3312 /* case 5: fully internal */
3317 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
3318 newrgn->lower = chkrgn->lower;
3319 newrgn->upper = range.lower;
3320 chkrgn->lower = range.upper;
3321 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3329 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
3332 ranges_check(ranges, "after del");
3336 ranges_check(ranges, "failed del");
3342 * Removes all selection ranges
3345 * [I] infoPtr : valid pointer to the listview structure
3346 * [I] toSkip : item range to skip removing the selection
3352 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
3361 lvItem.stateMask = LVIS_SELECTED;
3363 /* need to clone the DPA because callbacks can change it */
3364 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
3365 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
3366 while(iterator_next(&i))
3367 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
3368 /* note that the iterator destructor will free the cloned range */
3369 iterator_destroy(&i);
3374 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
3378 if (!(toSkip = ranges_create(1))) return FALSE;
3379 if (nItem != -1) ranges_additem(toSkip, nItem);
3380 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
3381 ranges_destroy(toSkip);
3385 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
3387 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
3392 * Retrieves the number of items that are marked as selected.
3395 * [I] infoPtr : valid pointer to the listview structure
3398 * Number of items selected.
3400 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
3402 INT nSelectedCount = 0;
3404 if (infoPtr->uCallbackMask & LVIS_SELECTED)
3407 for (i = 0; i < infoPtr->nItemCount; i++)
3409 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3414 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3416 TRACE("nSelectedCount=%d\n", nSelectedCount);
3417 return nSelectedCount;
3422 * Manages the item focus.
3425 * [I] infoPtr : valid pointer to the listview structure
3426 * [I] nItem : item index
3429 * TRUE : focused item changed
3430 * FALSE : focused item has NOT changed
3432 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3434 INT oldFocus = infoPtr->nFocusedItem;
3437 if (nItem == infoPtr->nFocusedItem) return FALSE;
3439 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3440 lvItem.stateMask = LVIS_FOCUSED;
3441 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3443 return oldFocus != infoPtr->nFocusedItem;
3446 /* Helper function for LISTVIEW_ShiftIndices *only* */
3447 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3449 if (nShiftItem < nItem) return nShiftItem;
3451 if (nShiftItem > nItem) return nShiftItem + direction;
3453 if (direction > 0) return nShiftItem + direction;
3455 return min(nShiftItem, infoPtr->nItemCount - 1);
3460 * Updates the various indices after an item has been inserted or deleted.
3463 * [I] infoPtr : valid pointer to the listview structure
3464 * [I] nItem : item index
3465 * [I] direction : Direction of shift, +1 or -1.
3470 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3475 /* temporarily disable change notification while shifting items */
3476 bOldChange = infoPtr->bDoChangeNotify;
3477 infoPtr->bDoChangeNotify = FALSE;
3479 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3481 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3483 assert(abs(direction) == 1);
3485 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3487 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3488 if (nNewFocus != infoPtr->nFocusedItem)
3489 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3491 /* But we are not supposed to modify nHotItem! */
3493 infoPtr->bDoChangeNotify = bOldChange;
3499 * Adds a block of selections.
3502 * [I] infoPtr : valid pointer to the listview structure
3503 * [I] nItem : item index
3506 * Whether the window is still valid.
3508 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3510 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3511 INT nLast = max(infoPtr->nSelectionMark, nItem);
3512 HWND hwndSelf = infoPtr->hwndSelf;
3513 NMLVODSTATECHANGE nmlv;
3518 /* Temporarily disable change notification
3519 * If the control is LVS_OWNERDATA, we need to send
3520 * only one LVN_ODSTATECHANGED notification.
3521 * See MSDN documentation for LVN_ITEMCHANGED.
3523 bOldChange = infoPtr->bDoChangeNotify;
3524 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3526 if (nFirst == -1) nFirst = nItem;
3528 item.state = LVIS_SELECTED;
3529 item.stateMask = LVIS_SELECTED;
3531 for (i = nFirst; i <= nLast; i++)
3532 LISTVIEW_SetItemState(infoPtr,i,&item);
3534 ZeroMemory(&nmlv, sizeof(nmlv));
3535 nmlv.iFrom = nFirst;
3538 nmlv.uOldState = item.state;
3540 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3541 if (!IsWindow(hwndSelf))
3543 infoPtr->bDoChangeNotify = bOldChange;
3550 * Sets a single group selection.
3553 * [I] infoPtr : valid pointer to the listview structure
3554 * [I] nItem : item index
3559 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3566 if (!(selection = ranges_create(100))) return;
3568 item.state = LVIS_SELECTED;
3569 item.stateMask = LVIS_SELECTED;
3571 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
3573 if (infoPtr->nSelectionMark == -1)
3575 infoPtr->nSelectionMark = nItem;
3576 ranges_additem(selection, nItem);
3582 sel.lower = min(infoPtr->nSelectionMark, nItem);
3583 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3584 ranges_add(selection, sel);
3589 RECT rcItem, rcSel, rcSelMark;
3592 rcItem.left = LVIR_BOUNDS;
3593 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3594 rcSelMark.left = LVIR_BOUNDS;
3595 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3596 UnionRect(&rcSel, &rcItem, &rcSelMark);
3597 iterator_frameditems(&i, infoPtr, &rcSel);
3598 while(iterator_next(&i))
3600 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3601 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3603 iterator_destroy(&i);
3606 /* disable per item notifications on LVS_OWNERDATA style
3607 FIXME: single LVN_ODSTATECHANGED should be used */
3608 bOldChange = infoPtr->bDoChangeNotify;
3609 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3611 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3614 iterator_rangesitems(&i, selection);
3615 while(iterator_next(&i))
3616 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3617 /* this will also destroy the selection */
3618 iterator_destroy(&i);
3620 infoPtr->bDoChangeNotify = bOldChange;
3622 LISTVIEW_SetItemFocus(infoPtr, nItem);
3627 * Sets a single selection.
3630 * [I] infoPtr : valid pointer to the listview structure
3631 * [I] nItem : item index
3636 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3640 TRACE("nItem=%d\n", nItem);
3642 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3644 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3645 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3646 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3648 infoPtr->nSelectionMark = nItem;
3653 * Set selection(s) with keyboard.
3656 * [I] infoPtr : valid pointer to the listview structure
3657 * [I] nItem : item index
3658 * [I] space : VK_SPACE code sent
3661 * SUCCESS : TRUE (needs to be repainted)
3662 * FAILURE : FALSE (nothing has changed)
3664 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem, BOOL space)
3666 /* FIXME: pass in the state */
3667 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3668 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3669 BOOL bResult = FALSE;
3671 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3672 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3676 if (infoPtr->dwStyle & LVS_SINGLESEL || (wShift == 0 && wCtrl == 0))
3677 LISTVIEW_SetSelection(infoPtr, nItem);
3681 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3685 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3686 lvItem.stateMask = LVIS_SELECTED;
3689 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3690 if (lvItem.state & LVIS_SELECTED)
3691 infoPtr->nSelectionMark = nItem;
3693 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3696 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3699 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3703 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3705 LVHITTESTINFO lvHitTestInfo;
3707 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3708 lvHitTestInfo.pt.x = pt.x;
3709 lvHitTestInfo.pt.y = pt.y;
3711 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3713 lpLVItem->mask = LVIF_PARAM;
3714 lpLVItem->iItem = lvHitTestInfo.iItem;
3715 lpLVItem->iSubItem = 0;
3717 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3720 static inline BOOL LISTVIEW_IsHotTracking(const LISTVIEW_INFO *infoPtr)
3722 return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3723 (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3724 (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3729 * Called when the mouse is being actively tracked and has hovered for a specified
3733 * [I] infoPtr : valid pointer to the listview structure
3734 * [I] fwKeys : key indicator
3735 * [I] x,y : mouse position
3738 * 0 if the message was processed, non-zero if there was an error
3741 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3742 * over the item for a certain period of time.
3745 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, INT x, INT y)
3749 if (notify_hdr(infoPtr, NM_HOVER, &hdr)) return 0;
3751 if (LISTVIEW_IsHotTracking(infoPtr))
3759 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3760 LISTVIEW_SetSelection(infoPtr, item.iItem);
3762 SetFocus(infoPtr->hwndSelf);
3768 #define SCROLL_LEFT 0x1
3769 #define SCROLL_RIGHT 0x2
3770 #define SCROLL_UP 0x4
3771 #define SCROLL_DOWN 0x8
3775 * Utility routine to draw and highlight items within a marquee selection rectangle.
3778 * [I] infoPtr : valid pointer to the listview structure
3779 * [I] coords_orig : original co-ordinates of the cursor
3780 * [I] coords_offs : offsetted coordinates of the cursor
3781 * [I] offset : offset amount
3782 * [I] scroll : Bitmask of which directions we should scroll, if at all
3787 static void LISTVIEW_MarqueeHighlight(LISTVIEW_INFO *infoPtr, const POINT *coords_orig,
3788 const POINT *coords_offs, const POINT *offset,
3791 BOOL controlDown = FALSE;
3793 ITERATOR old_elems, new_elems;
3796 if (coords_offs->x > infoPtr->marqueeOrigin.x)
3798 rect.left = infoPtr->marqueeOrigin.x;
3799 rect.right = coords_offs->x;
3803 rect.left = coords_offs->x;
3804 rect.right = infoPtr->marqueeOrigin.x;
3807 if (coords_offs->y > infoPtr->marqueeOrigin.y)
3809 rect.top = infoPtr->marqueeOrigin.y;
3810 rect.bottom = coords_offs->y;
3814 rect.top = coords_offs->y;
3815 rect.bottom = infoPtr->marqueeOrigin.y;
3818 /* Cancel out the old marquee rectangle and draw the new one */
3819 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
3821 /* Scroll by the appropriate distance if applicable - speed up scrolling as
3822 the cursor is further away */
3824 if ((scroll & SCROLL_LEFT) && (coords_orig->x <= 0))
3825 LISTVIEW_Scroll(infoPtr, coords_orig->x, 0);
3827 if ((scroll & SCROLL_RIGHT) && (coords_orig->x >= infoPtr->rcList.right))
3828 LISTVIEW_Scroll(infoPtr, (coords_orig->x - infoPtr->rcList.right), 0);
3830 if ((scroll & SCROLL_UP) && (coords_orig->y <= 0))
3831 LISTVIEW_Scroll(infoPtr, 0, coords_orig->y);
3833 if ((scroll & SCROLL_DOWN) && (coords_orig->y >= infoPtr->rcList.bottom))
3834 LISTVIEW_Scroll(infoPtr, 0, (coords_orig->y - infoPtr->rcList.bottom));
3836 iterator_frameditems_absolute(&old_elems, infoPtr, &infoPtr->marqueeRect);
3838 CopyRect(&infoPtr->marqueeRect, &rect);
3840 CopyRect(&infoPtr->marqueeDrawRect, &rect);
3841 OffsetRect(&infoPtr->marqueeDrawRect, offset->x, offset->y);
3843 iterator_frameditems_absolute(&new_elems, infoPtr, &infoPtr->marqueeRect);
3844 iterator_remove_common_items(&old_elems, &new_elems);
3846 /* Iterate over no longer selected items */
3847 while (iterator_next(&old_elems))
3849 if (old_elems.nItem > -1)
3851 if (LISTVIEW_GetItemState(infoPtr, old_elems.nItem, LVIS_SELECTED) == LVIS_SELECTED)
3854 item.state = LVIS_SELECTED;
3856 item.stateMask = LVIS_SELECTED;
3858 LISTVIEW_SetItemState(infoPtr, old_elems.nItem, &item);
3861 iterator_destroy(&old_elems);
3864 /* Iterate over newly selected items */
3865 if (GetKeyState(VK_CONTROL) & 0x8000)
3868 while (iterator_next(&new_elems))
3870 if (new_elems.nItem > -1)
3872 /* If CTRL is pressed, invert. If not, always select the item. */
3873 if ((controlDown) && (LISTVIEW_GetItemState(infoPtr, new_elems.nItem, LVIS_SELECTED)))
3876 item.state = LVIS_SELECTED;
3878 item.stateMask = LVIS_SELECTED;
3880 LISTVIEW_SetItemState(infoPtr, new_elems.nItem, &item);
3883 iterator_destroy(&new_elems);
3885 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
3890 * Called when we are in a marquee selection that involves scrolling the listview (ie,
3891 * the cursor is outside the bounds of the client area). This is a TIMERPROC.
3894 * [I] hwnd : Handle to the listview
3895 * [I] uMsg : WM_TIMER (ignored)
3896 * [I] idEvent : The timer ID interpreted as a pointer to a LISTVIEW_INFO struct
3897 * [I] dwTimer : The elapsed time (ignored)
3902 static VOID CALLBACK LISTVIEW_ScrollTimer(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
3904 LISTVIEW_INFO *infoPtr;
3905 SCROLLINFO scrollInfo;
3911 infoPtr = (LISTVIEW_INFO *) idEvent;
3916 /* Get the current cursor position and convert to client coordinates */
3917 GetCursorPos(&coords_orig);
3918 ScreenToClient(hWnd, &coords_orig);
3920 /* Ensure coordinates are within client bounds */
3921 coords_offs.x = max(min(coords_orig.x, infoPtr->rcList.right), 0);
3922 coords_offs.y = max(min(coords_orig.y, infoPtr->rcList.bottom), 0);
3925 LISTVIEW_GetOrigin(infoPtr, &offset);
3927 /* Offset coordinates by the appropriate amount */
3928 coords_offs.x -= offset.x;
3929 coords_offs.y -= offset.y;
3931 scrollInfo.cbSize = sizeof(SCROLLINFO);
3932 scrollInfo.fMask = SIF_ALL;
3934 /* Work out in which directions we can scroll */
3935 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3937 if (scrollInfo.nPos != scrollInfo.nMin)
3938 scroll |= SCROLL_UP;
3940 if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
3941 scroll |= SCROLL_DOWN;
3944 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3946 if (scrollInfo.nPos != scrollInfo.nMin)
3947 scroll |= SCROLL_LEFT;
3949 if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
3950 scroll |= SCROLL_RIGHT;
3953 if (((coords_orig.x <= 0) && (scroll & SCROLL_LEFT)) ||
3954 ((coords_orig.y <= 0) && (scroll & SCROLL_UP)) ||
3955 ((coords_orig.x >= infoPtr->rcList.right) && (scroll & SCROLL_RIGHT)) ||
3956 ((coords_orig.y >= infoPtr->rcList.bottom) && (scroll & SCROLL_DOWN)))
3958 LISTVIEW_MarqueeHighlight(infoPtr, &coords_orig, &coords_offs, &offset, scroll);
3964 * Called whenever WM_MOUSEMOVE is received.
3967 * [I] infoPtr : valid pointer to the listview structure
3968 * [I] fwKeys : key indicator
3969 * [I] x,y : mouse position
3972 * 0 if the message is processed, non-zero if there was an error
3974 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3976 if (!(fwKeys & MK_LBUTTON))
3977 infoPtr->bLButtonDown = FALSE;
3979 if (infoPtr->bLButtonDown)
3983 LVHITTESTINFO lvHitTestInfo;
3984 WORD wDragWidth = GetSystemMetrics(SM_CXDRAG);
3985 WORD wDragHeight= GetSystemMetrics(SM_CYDRAG);
3987 if (infoPtr->bMarqueeSelect)
3997 LISTVIEW_GetOrigin(infoPtr, &offset);
3999 /* Ensure coordinates are within client bounds */
4000 coords_offs.x = max(min(x, infoPtr->rcList.right), 0);
4001 coords_offs.y = max(min(y, infoPtr->rcList.bottom), 0);
4003 /* Offset coordinates by the appropriate amount */
4004 coords_offs.x -= offset.x;
4005 coords_offs.y -= offset.y;
4007 /* Enable the timer if we're going outside our bounds, in case the user doesn't
4008 move the mouse again */
4010 if ((x <= 0) || (y <= 0) || (x >= infoPtr->rcList.right) ||
4011 (y >= infoPtr->rcList.bottom))
4013 if (!infoPtr->bScrolling)
4015 infoPtr->bScrolling = TRUE;
4016 SetTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr, 1, LISTVIEW_ScrollTimer);
4021 infoPtr->bScrolling = FALSE;
4022 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
4025 LISTVIEW_MarqueeHighlight(infoPtr, &coords_orig, &coords_offs, &offset, 0);
4029 rect.left = infoPtr->ptClickPos.x - wDragWidth;
4030 rect.right = infoPtr->ptClickPos.x + wDragWidth;
4031 rect.top = infoPtr->ptClickPos.y - wDragHeight;
4032 rect.bottom = infoPtr->ptClickPos.y + wDragHeight;
4037 lvHitTestInfo.pt = tmp;
4038 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
4040 /* reset item marker */
4041 if (infoPtr->nLButtonDownItem != lvHitTestInfo.iItem)
4042 infoPtr->nLButtonDownItem = -1;
4044 if (!PtInRect(&rect, tmp))
4046 /* this path covers the following:
4047 1. WM_LBUTTONDOWN over selected item (sets focus on it)
4048 2. change focus with keys
4049 3. move mouse over item from step 1 selects it and moves focus on it */
4050 if (infoPtr->nLButtonDownItem != -1 &&
4051 !LISTVIEW_GetItemState(infoPtr, infoPtr->nLButtonDownItem, LVIS_SELECTED))
4055 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
4056 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
4058 LISTVIEW_SetItemState(infoPtr, infoPtr->nLButtonDownItem, &lvItem);
4059 infoPtr->nLButtonDownItem = -1;
4062 if (!infoPtr->bDragging)
4064 lvHitTestInfo.pt = infoPtr->ptClickPos;
4065 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
4067 /* If the click is outside the range of an item, begin a
4068 highlight. If not, begin an item drag. */
4069 if (lvHitTestInfo.iItem == -1)
4073 /* If we're allowing multiple selections, send notification.
4074 If return value is non-zero, cancel. */
4075 if (!(infoPtr->dwStyle & LVS_SINGLESEL) && (notify_hdr(infoPtr, LVN_MARQUEEBEGIN, &hdr) == 0))
4077 /* Store the absolute coordinates of the click */
4079 LISTVIEW_GetOrigin(infoPtr, &offset);
4081 infoPtr->marqueeOrigin.x = infoPtr->ptClickPos.x - offset.x;
4082 infoPtr->marqueeOrigin.y = infoPtr->ptClickPos.y - offset.y;
4084 /* Begin selection and capture mouse */
4085 infoPtr->bMarqueeSelect = TRUE;
4086 SetCapture(infoPtr->hwndSelf);
4093 ZeroMemory(&nmlv, sizeof(nmlv));
4094 nmlv.iItem = lvHitTestInfo.iItem;
4095 nmlv.ptAction = infoPtr->ptClickPos;
4097 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
4098 infoPtr->bDragging = TRUE;
4106 /* see if we are supposed to be tracking mouse hovering */
4107 if (LISTVIEW_IsHotTracking(infoPtr)) {
4108 TRACKMOUSEEVENT trackinfo;
4110 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
4111 trackinfo.dwFlags = TME_QUERY;
4113 /* see if we are already tracking this hwnd */
4114 _TrackMouseEvent(&trackinfo);
4116 if(!(trackinfo.dwFlags & TME_HOVER) || trackinfo.hwndTrack != infoPtr->hwndSelf) {
4117 trackinfo.dwFlags = TME_HOVER;
4118 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
4119 trackinfo.hwndTrack = infoPtr->hwndSelf;
4121 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
4122 _TrackMouseEvent(&trackinfo);
4131 * Tests whether the item is assignable to a list with style lStyle
4133 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
4135 if ( (lpLVItem->mask & LVIF_TEXT) &&
4136 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
4137 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
4145 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
4148 * [I] infoPtr : valid pointer to the listview structure
4149 * [I] lpLVItem : valid pointer to new item attributes
4150 * [I] isNew : the item being set is being inserted
4151 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4152 * [O] bChanged : will be set to TRUE if the item really changed
4158 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
4164 /* stateMask is ignored for LVM_INSERTITEM */
4165 UINT stateMask = isNew ? ~0 : lpLVItem->stateMask;
4169 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
4171 if (lpLVItem->mask == 0) return TRUE;
4173 if (infoPtr->dwStyle & LVS_OWNERDATA)
4175 /* a virtual listview only stores selection and focus */
4176 if (lpLVItem->mask & ~LVIF_STATE)
4182 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4183 lpItem = DPA_GetPtr(hdpaSubItems, 0);
4187 /* we need to get the lParam and state of the item */
4188 item.iItem = lpLVItem->iItem;
4189 item.iSubItem = lpLVItem->iSubItem;
4190 item.mask = LVIF_STATE | LVIF_PARAM;
4191 item.stateMask = (infoPtr->dwStyle & LVS_OWNERDATA) ? LVIS_FOCUSED | LVIS_SELECTED : ~0;
4195 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
4197 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
4198 /* determine what fields will change */
4199 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & stateMask & ~infoPtr->uCallbackMask))
4200 uChanged |= LVIF_STATE;
4202 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
4203 uChanged |= LVIF_IMAGE;
4205 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
4206 uChanged |= LVIF_PARAM;
4208 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
4209 uChanged |= LVIF_INDENT;
4211 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
4212 uChanged |= LVIF_TEXT;
4214 TRACE("uChanged=0x%x\n", uChanged);
4215 if (!uChanged) return TRUE;
4218 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4219 nmlv.iItem = lpLVItem->iItem;
4220 nmlv.uNewState = (item.state & ~stateMask) | (lpLVItem->state & stateMask);
4221 nmlv.uOldState = item.state;
4222 nmlv.uChanged = uChanged;
4223 nmlv.lParam = item.lParam;
4225 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
4226 /* and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications */
4228 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
4230 HWND hwndSelf = infoPtr->hwndSelf;
4232 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
4234 if (!IsWindow(hwndSelf))
4238 /* copy information */
4239 if (lpLVItem->mask & LVIF_TEXT)
4240 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
4242 if (lpLVItem->mask & LVIF_IMAGE)
4243 lpItem->hdr.iImage = lpLVItem->iImage;
4245 if (lpLVItem->mask & LVIF_PARAM)
4246 lpItem->lParam = lpLVItem->lParam;
4248 if (lpLVItem->mask & LVIF_INDENT)
4249 lpItem->iIndent = lpLVItem->iIndent;
4251 if (uChanged & LVIF_STATE)
4253 if (lpItem && (stateMask & ~infoPtr->uCallbackMask))
4255 lpItem->state &= ~stateMask;
4256 lpItem->state |= (lpLVItem->state & stateMask);
4258 if (lpLVItem->state & stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
4260 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
4261 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
4263 else if (stateMask & LVIS_SELECTED)
4265 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
4267 /* if we are asked to change focus, and we manage it, do it */
4268 if (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
4270 if (lpLVItem->state & LVIS_FOCUSED)
4272 /* update selection mark */
4273 if (infoPtr->nFocusedItem == -1 && infoPtr->nSelectionMark == -1)
4274 infoPtr->nSelectionMark = lpLVItem->iItem;
4276 if (infoPtr->nFocusedItem != -1)
4278 /* remove current focus */
4279 item.mask = LVIF_STATE;
4281 item.stateMask = LVIS_FOCUSED;
4283 /* recurse with redrawing an item */
4284 LISTVIEW_SetItemState(infoPtr, infoPtr->nFocusedItem, &item);
4287 infoPtr->nFocusedItem = lpLVItem->iItem;
4288 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, infoPtr->uView == LV_VIEW_LIST);
4290 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
4292 infoPtr->nFocusedItem = -1;
4297 /* if we're inserting the item, we're done */
4298 if (isNew) return TRUE;
4300 /* send LVN_ITEMCHANGED notification */
4301 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
4302 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
4309 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
4312 * [I] infoPtr : valid pointer to the listview structure
4313 * [I] lpLVItem : valid pointer to new subitem attributes
4314 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4315 * [O] bChanged : will be set to TRUE if the item really changed
4321 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
4324 SUBITEM_INFO *lpSubItem;
4326 /* we do not support subitems for virtual listviews */
4327 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
4329 /* set subitem only if column is present */
4330 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4332 /* First do some sanity checks */
4333 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
4334 particularly useful. We currently do not actually do anything with
4335 the flag on subitems.
4337 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE | LVIF_DI_SETITEM)) return FALSE;
4338 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
4340 /* get the subitem structure, and create it if not there */
4341 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4342 assert (hdpaSubItems);
4344 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
4347 SUBITEM_INFO *tmpSubItem;
4350 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
4351 if (!lpSubItem) return FALSE;
4352 /* we could binary search here, if need be...*/
4353 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4355 tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
4356 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
4358 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
4363 lpSubItem->iSubItem = lpLVItem->iSubItem;
4364 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
4368 if ((lpLVItem->mask & LVIF_IMAGE) && (lpSubItem->hdr.iImage != lpLVItem->iImage))
4370 lpSubItem->hdr.iImage = lpLVItem->iImage;
4374 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpSubItem->hdr.pszText, lpLVItem->pszText, isW))
4376 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
4385 * Sets item attributes.
4388 * [I] infoPtr : valid pointer to the listview structure
4389 * [I] lpLVItem : new item attributes
4390 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4396 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
4398 HWND hwndSelf = infoPtr->hwndSelf;
4399 LPWSTR pszText = NULL;
4400 BOOL bResult, bChanged = FALSE;
4403 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
4405 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
4408 /* Store old item area */
4409 LISTVIEW_GetItemBox(infoPtr, lpLVItem->iItem, &oldItemArea);
4411 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
4412 if ((lpLVItem->mask & LVIF_TEXT) && is_text(lpLVItem->pszText))
4414 pszText = lpLVItem->pszText;
4415 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
4418 /* actually set the fields */
4419 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
4421 if (lpLVItem->iSubItem)
4422 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
4424 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
4425 if (!IsWindow(hwndSelf))
4428 /* redraw item, if necessary */
4429 if (bChanged && !infoPtr->bIsDrawing)
4431 /* this little optimization eliminates some nasty flicker */
4432 if ( infoPtr->uView == LV_VIEW_DETAILS && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
4433 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
4434 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
4435 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
4438 LISTVIEW_InvalidateRect(infoPtr, &oldItemArea);
4439 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
4445 textfreeT(lpLVItem->pszText, isW);
4446 lpLVItem->pszText = pszText;
4454 * Retrieves the index of the item at coordinate (0, 0) of the client area.
4457 * [I] infoPtr : valid pointer to the listview structure
4462 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
4465 SCROLLINFO scrollInfo;
4467 scrollInfo.cbSize = sizeof(SCROLLINFO);
4468 scrollInfo.fMask = SIF_POS;
4470 if (infoPtr->uView == LV_VIEW_LIST)
4472 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
4473 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
4475 else if (infoPtr->uView == LV_VIEW_DETAILS)
4477 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
4478 nItem = scrollInfo.nPos;
4482 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
4483 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
4486 TRACE("nItem=%d\n", nItem);
4494 * Erases the background of the given rectangle
4497 * [I] infoPtr : valid pointer to the listview structure
4498 * [I] hdc : device context handle
4499 * [I] lprcBox : clipping rectangle
4505 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
4507 if (!infoPtr->hBkBrush) return FALSE;
4509 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
4511 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
4519 * [I] infoPtr : valid pointer to the listview structure
4520 * [I] hdc : device context handle
4521 * [I] nItem : item index
4522 * [I] nSubItem : subitem index
4523 * [I] pos : item position in client coordinates
4524 * [I] cdmode : custom draw mode
4530 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
4533 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4534 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
4535 DWORD cdsubitemmode = CDRF_DODEFAULT;
4537 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
4538 NMLVCUSTOMDRAW nmlvcd;
4543 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
4545 /* get information needed for drawing the item */
4546 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
4547 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
4548 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
4549 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK | LVIS_CUT;
4550 lvItem.iItem = nItem;
4551 lvItem.iSubItem = nSubItem;
4554 lvItem.cchTextMax = DISP_TEXT_SIZE;
4555 lvItem.pszText = szDispText;
4556 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
4557 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4558 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
4559 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
4560 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
4562 /* now check if we need to update the focus rectangle */
4563 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
4565 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
4566 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
4567 OffsetRect(&rcBox, pos.x, pos.y);
4568 OffsetRect(&rcSelect, pos.x, pos.y);
4569 OffsetRect(&rcIcon, pos.x, pos.y);
4570 OffsetRect(&rcStateIcon, pos.x, pos.y);
4571 OffsetRect(&rcLabel, pos.x, pos.y);
4572 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
4573 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
4574 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
4576 /* fill in the custom draw structure */
4577 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
4579 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
4580 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
4581 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
4582 if (cdmode & CDRF_NOTIFYITEMDRAW)
4583 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4584 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
4585 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
4586 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
4587 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
4589 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
4590 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
4592 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
4593 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4594 else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
4595 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
4597 /* in full row select, subitems, will just use main item's colors */
4598 if (nSubItem && infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4599 nmlvcd.clrTextBk = CLR_NONE;
4601 /* FIXME: temporary hack */
4602 rcSelect.left = rcLabel.left;
4604 /* draw the selection background, if we're drawing the main item */
4607 /* in icon mode, the label rect is really what we want to draw the
4609 if (infoPtr->uView == LV_VIEW_ICON)
4612 if (infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4614 /* we have to update left focus bound too if item isn't in leftmost column
4615 and reduce right box bound */
4616 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
4620 if ((leftmost = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0)))
4622 INT Originx = pos.x - LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
4623 INT index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
4624 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4626 rcBox.right = LISTVIEW_GetColumnInfo(infoPtr, index)->rcHeader.right + Originx;
4627 rcSelect.left = LISTVIEW_GetColumnInfo(infoPtr, leftmost)->rcHeader.left + Originx;
4631 rcSelect.right = rcBox.right;
4634 if (nmlvcd.clrTextBk != CLR_NONE)
4635 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, NULL, 0, NULL);
4636 /* store new focus rectangle */
4637 if (infoPtr->nFocusedItem == nItem) infoPtr->rcFocus = rcSelect;
4641 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
4643 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
4646 TRACE("uStateImage=%d\n", uStateImage);
4647 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
4648 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
4653 himl = (infoPtr->uView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
4654 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
4658 TRACE("iImage=%d\n", lvItem.iImage);
4660 if (lvItem.state & (LVIS_SELECTED | LVIS_CUT) && infoPtr->bFocus)
4661 style = ILD_SELECTED;
4665 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
4666 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk,
4667 lvItem.state & LVIS_CUT ? RGB(255, 255, 255) : CLR_DEFAULT,
4671 /* Don't bother painting item being edited */
4672 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
4674 /* figure out the text drawing flags */
4675 uFormat = (infoPtr->uView == LV_VIEW_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
4676 if (infoPtr->uView == LV_VIEW_ICON)
4677 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
4680 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
4682 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
4683 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
4684 default: uFormat |= DT_LEFT;
4687 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
4689 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
4690 else rcLabel.left += LABEL_HOR_PADDING;
4692 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
4694 /* for GRIDLINES reduce the bottom so the text formats correctly */
4695 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4698 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
4701 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
4702 notify_postpaint(infoPtr, &nmlvcd);
4703 if (cdsubitemmode & CDRF_NEWFONT)
4704 SelectObject(hdc, hOldFont);
4710 * Draws listview items when in owner draw mode.
4713 * [I] infoPtr : valid pointer to the listview structure
4714 * [I] hdc : device context handle
4719 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4721 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
4722 DWORD cditemmode = CDRF_DODEFAULT;
4723 NMLVCUSTOMDRAW nmlvcd;
4724 POINT Origin, Position;
4730 ZeroMemory(&dis, sizeof(dis));
4732 /* Get scroll info once before loop */
4733 LISTVIEW_GetOrigin(infoPtr, &Origin);
4735 /* iterate through the invalidated rows */
4736 while(iterator_next(i))
4738 item.iItem = i->nItem;
4740 item.mask = LVIF_PARAM | LVIF_STATE;
4741 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4742 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
4744 dis.CtlType = ODT_LISTVIEW;
4746 dis.itemID = item.iItem;
4747 dis.itemAction = ODA_DRAWENTIRE;
4749 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
4750 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
4751 dis.hwndItem = infoPtr->hwndSelf;
4753 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
4754 dis.rcItem.left = Position.x + Origin.x;
4755 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
4756 dis.rcItem.top = Position.y + Origin.y;
4757 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
4758 dis.itemData = item.lParam;
4760 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
4763 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
4764 * structure for the rest. of the paint cycle
4766 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
4767 if (cdmode & CDRF_NOTIFYITEMDRAW)
4768 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4770 if (!(cditemmode & CDRF_SKIPDEFAULT))
4772 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
4773 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
4776 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
4777 notify_postpaint(infoPtr, &nmlvcd);
4783 * Draws listview items when in report display mode.
4786 * [I] infoPtr : valid pointer to the listview structure
4787 * [I] hdc : device context handle
4788 * [I] cdmode : custom draw mode
4793 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4796 RECT rcClip, rcItem;
4797 POINT Origin, Position;
4804 /* figure out what to draw */
4805 rgntype = GetClipBox(hdc, &rcClip);
4806 if (rgntype == NULLREGION) return;
4808 /* Get scroll info once before loop */
4809 LISTVIEW_GetOrigin(infoPtr, &Origin);
4811 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
4813 /* narrow down the columns we need to paint */
4814 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
4816 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
4818 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4819 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
4820 ranges_additem(colRanges, index);
4822 iterator_rangesitems(&j, colRanges);
4824 /* in full row select, we _have_ to draw the main item */
4825 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4828 /* iterate through the invalidated rows */
4829 while(iterator_next(i))
4831 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4832 Position.y += Origin.y;
4834 /* iterate through the invalidated columns */
4835 while(iterator_next(&j))
4837 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4838 Position.x = (j.nItem == 0) ? rcItem.left + Origin.x : Origin.x;
4840 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4843 rcItem.bottom = infoPtr->nItemHeight;
4844 OffsetRect(&rcItem, Origin.x, Position.y);
4845 if (!RectVisible(hdc, &rcItem)) continue;
4848 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4851 iterator_destroy(&j);
4856 * Draws the gridlines if necessary when in report display mode.
4859 * [I] infoPtr : valid pointer to the listview structure
4860 * [I] hdc : device context handle
4865 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
4871 RECT rcClip, rcItem = {0};
4879 /* figure out what to draw */
4880 rgntype = GetClipBox(hdc, &rcClip);
4881 if (rgntype == NULLREGION) return;
4883 /* Get scroll info once before loop */
4884 LISTVIEW_GetOrigin(infoPtr, &Origin);
4886 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
4888 /* narrow down the columns we need to paint */
4889 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
4891 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
4893 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4894 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
4895 ranges_additem(colRanges, index);
4898 /* is right most vertical line visible? */
4899 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
4901 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4902 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4903 rmost = (rcItem.right + Origin.x < rcClip.right);
4906 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
4908 hOldPen = SelectObject ( hdc, hPen );
4910 /* draw the vertical lines for the columns */
4911 iterator_rangesitems(&j, colRanges);
4912 while(iterator_next(&j))
4914 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4915 if (rcItem.left == 0) continue; /* skip leftmost column */
4916 rcItem.left += Origin.x;
4917 rcItem.right += Origin.x;
4918 rcItem.top = infoPtr->rcList.top;
4919 rcItem.bottom = infoPtr->rcList.bottom;
4920 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
4921 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4922 LineTo (hdc, rcItem.left, rcItem.bottom);
4924 iterator_destroy(&j);
4925 /* draw rightmost grid line if visible */
4928 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
4929 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4930 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4932 rcItem.right += Origin.x;
4934 MoveToEx (hdc, rcItem.right, infoPtr->rcList.top, NULL);
4935 LineTo (hdc, rcItem.right, infoPtr->rcList.bottom);
4938 /* draw the horizontal lines for the rows */
4939 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
4940 rcItem.left = infoPtr->rcList.left;
4941 rcItem.right = infoPtr->rcList.right;
4942 for(y = Origin.y > 1 ? Origin.y - 1 : itemheight - 1 + Origin.y % itemheight; y<=infoPtr->rcList.bottom; y+=itemheight)
4944 rcItem.bottom = rcItem.top = y;
4945 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
4946 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4947 LineTo (hdc, rcItem.right, rcItem.top);
4950 SelectObject( hdc, hOldPen );
4951 DeleteObject( hPen );
4954 ranges_destroy(colRanges);
4959 * Draws listview items when in list display mode.
4962 * [I] infoPtr : valid pointer to the listview structure
4963 * [I] hdc : device context handle
4964 * [I] cdmode : custom draw mode
4969 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4971 POINT Origin, Position;
4973 /* Get scroll info once before loop */
4974 LISTVIEW_GetOrigin(infoPtr, &Origin);
4976 while(iterator_prev(i))
4978 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4979 Position.x += Origin.x;
4980 Position.y += Origin.y;
4982 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4989 * Draws listview items.
4992 * [I] infoPtr : valid pointer to the listview structure
4993 * [I] hdc : device context handle
4994 * [I] prcErase : rect to be erased before refresh (may be NULL)
4999 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
5001 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
5002 NMLVCUSTOMDRAW nmlvcd;
5009 HBITMAP hbmp = NULL;
5012 LISTVIEW_DUMP(infoPtr);
5014 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
5015 TRACE("double buffering\n");
5017 hdc = CreateCompatibleDC(hdcOrig);
5019 ERR("Failed to create DC for backbuffer\n");
5022 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
5023 infoPtr->rcList.bottom);
5025 ERR("Failed to create bitmap for backbuffer\n");
5030 SelectObject(hdc, hbmp);
5031 SelectObject(hdc, infoPtr->hFont);
5033 if(GetClipBox(hdcOrig, &rcClient))
5034 IntersectClipRect(hdc, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
5036 /* Save dc values we're gonna trash while drawing
5037 * FIXME: Should be done in LISTVIEW_DrawItem() */
5038 hOldFont = SelectObject(hdc, infoPtr->hFont);
5039 oldBkMode = GetBkMode(hdc);
5040 oldBkColor = GetBkColor(hdc);
5041 oldTextColor = GetTextColor(hdc);
5044 infoPtr->bIsDrawing = TRUE;
5047 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
5048 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
5049 /* If no erasing was done (usually because RedrawWindow was called
5050 * with RDW_INVALIDATE only) we need to copy the old contents into
5051 * the backbuffer before continuing. */
5052 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
5053 infoPtr->rcList.right - infoPtr->rcList.left,
5054 infoPtr->rcList.bottom - infoPtr->rcList.top,
5055 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
5058 /* FIXME: Shouldn't need to do this */
5059 oldClrTextBk = infoPtr->clrTextBk;
5060 oldClrText = infoPtr->clrText;
5062 infoPtr->cditemmode = CDRF_DODEFAULT;
5064 GetClientRect(infoPtr->hwndSelf, &rcClient);
5065 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
5066 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
5067 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
5068 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
5070 /* Use these colors to draw the items */
5071 infoPtr->clrTextBk = nmlvcd.clrTextBk;
5072 infoPtr->clrText = nmlvcd.clrText;
5074 /* nothing to draw */
5075 if(infoPtr->nItemCount == 0) goto enddraw;
5077 /* figure out what we need to draw */
5078 iterator_visibleitems(&i, infoPtr, hdc);
5079 range = iterator_range(&i);
5081 /* send cache hint notification */
5082 if (infoPtr->dwStyle & LVS_OWNERDATA)
5086 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
5087 nmlv.iFrom = range.lower;
5088 nmlv.iTo = range.upper - 1;
5089 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
5092 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
5093 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
5096 if (infoPtr->uView == LV_VIEW_DETAILS)
5097 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
5098 else /* LV_VIEW_LIST, LV_VIEW_ICON or LV_VIEW_SMALLICON */
5099 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
5101 /* if we have a focus rect and it's visible, draw it */
5102 if (infoPtr->bFocus && range.lower <= infoPtr->nFocusedItem &&
5103 (range.upper - 1) >= infoPtr->nFocusedItem)
5104 LISTVIEW_DrawFocusRect(infoPtr, hdc);
5106 iterator_destroy(&i);
5109 /* For LVS_EX_GRIDLINES go and draw lines */
5110 /* This includes the case where there were *no* items */
5111 if ((infoPtr->uView == LV_VIEW_DETAILS) && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
5112 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
5114 /* Draw marquee rectangle if appropriate */
5115 if (infoPtr->bMarqueeSelect)
5116 DrawFocusRect(hdc, &infoPtr->marqueeDrawRect);
5118 if (cdmode & CDRF_NOTIFYPOSTPAINT)
5119 notify_postpaint(infoPtr, &nmlvcd);
5121 infoPtr->clrTextBk = oldClrTextBk;
5122 infoPtr->clrText = oldClrText;
5125 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
5126 infoPtr->rcList.right - infoPtr->rcList.left,
5127 infoPtr->rcList.bottom - infoPtr->rcList.top,
5128 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
5133 SelectObject(hdc, hOldFont);
5134 SetBkMode(hdc, oldBkMode);
5135 SetBkColor(hdc, oldBkColor);
5136 SetTextColor(hdc, oldTextColor);
5139 infoPtr->bIsDrawing = FALSE;
5145 * Calculates the approximate width and height of a given number of items.
5148 * [I] infoPtr : valid pointer to the listview structure
5149 * [I] nItemCount : number of items
5150 * [I] wWidth : width
5151 * [I] wHeight : height
5154 * Returns a DWORD. The width in the low word and the height in high word.
5156 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
5157 WORD wWidth, WORD wHeight)
5159 DWORD dwViewRect = 0;
5161 if (nItemCount == -1)
5162 nItemCount = infoPtr->nItemCount;
5164 if (infoPtr->uView == LV_VIEW_LIST)
5166 INT nItemCountPerColumn = 1;
5167 INT nColumnCount = 0;
5169 if (wHeight == 0xFFFF)
5171 /* use current height */
5172 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
5175 if (wHeight < infoPtr->nItemHeight)
5176 wHeight = infoPtr->nItemHeight;
5180 if (infoPtr->nItemHeight > 0)
5182 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
5183 if (nItemCountPerColumn == 0)
5184 nItemCountPerColumn = 1;
5186 if (nItemCount % nItemCountPerColumn != 0)
5187 nColumnCount = nItemCount / nItemCountPerColumn;
5189 nColumnCount = nItemCount / nItemCountPerColumn + 1;
5193 /* Microsoft padding magic */
5194 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
5195 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
5197 dwViewRect = MAKELONG(wWidth, wHeight);
5199 else if (infoPtr->uView == LV_VIEW_DETAILS)
5203 if (infoPtr->nItemCount > 0)
5205 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
5206 wWidth = rcBox.right - rcBox.left;
5207 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
5211 /* use current height and width */
5212 if (wHeight == 0xffff)
5213 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
5214 if (wWidth == 0xffff)
5215 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
5218 dwViewRect = MAKELONG(wWidth, wHeight);
5220 else if (infoPtr->uView == LV_VIEW_ICON)
5226 nItemWidth = infoPtr->iconSpacing.cx;
5227 nItemHeight = infoPtr->iconSpacing.cy;
5229 if (wWidth == 0xffff)
5230 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
5232 if (wWidth < nItemWidth)
5233 wWidth = nItemWidth;
5235 cols = wWidth / nItemWidth;
5236 if (cols > nItemCount)
5243 rows = nItemCount / cols;
5244 if (nItemCount % cols)
5250 wHeight = (nItemHeight * rows)+2;
5251 wWidth = (nItemWidth * cols)+2;
5253 dwViewRect = MAKELONG(wWidth, wHeight);
5255 else if (infoPtr->uView == LV_VIEW_SMALLICON)
5256 FIXME("uView == LV_VIEW_SMALLICON: not implemented\n");
5263 * Cancel edit label with saving item text.
5266 * [I] infoPtr : valid pointer to the listview structure
5269 * Always returns TRUE.
5271 static LRESULT LISTVIEW_CancelEditLabel(LISTVIEW_INFO *infoPtr)
5273 if (infoPtr->hwndEdit)
5275 /* handle value will be lost after LISTVIEW_EndEditLabelT */
5276 HWND edit = infoPtr->hwndEdit;
5278 LISTVIEW_EndEditLabelT(infoPtr, TRUE, IsWindowUnicode(infoPtr->hwndEdit));
5279 SendMessageW(edit, WM_CLOSE, 0, 0);
5287 * Create a drag image list for the specified item.
5290 * [I] infoPtr : valid pointer to the listview structure
5291 * [I] iItem : index of item
5292 * [O] lppt : Upper-left corner of the image
5295 * Returns a handle to the image list if successful, NULL otherwise.
5297 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
5303 HBITMAP hbmp, hOldbmp;
5304 HIMAGELIST dragList = 0;
5305 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
5307 if (iItem < 0 || iItem >= infoPtr->nItemCount || !lppt)
5310 rcItem.left = LVIR_BOUNDS;
5311 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
5314 lppt->x = rcItem.left;
5315 lppt->y = rcItem.top;
5317 size.cx = rcItem.right - rcItem.left;
5318 size.cy = rcItem.bottom - rcItem.top;
5320 hdcOrig = GetDC(infoPtr->hwndSelf);
5321 hdc = CreateCompatibleDC(hdcOrig);
5322 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
5323 hOldbmp = SelectObject(hdc, hbmp);
5325 rcItem.left = rcItem.top = 0;
5326 rcItem.right = size.cx;
5327 rcItem.bottom = size.cy;
5328 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
5331 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
5333 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
5334 SelectObject(hdc, hOldbmp);
5335 ImageList_Add(dragList, hbmp, 0);
5338 SelectObject(hdc, hOldbmp);
5342 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
5344 TRACE("ret=%p\n", dragList);
5352 * Removes all listview items and subitems.
5355 * [I] infoPtr : valid pointer to the listview structure
5361 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
5364 HDPA hdpaSubItems = NULL;
5373 /* we do it directly, to avoid notifications */
5374 ranges_clear(infoPtr->selectionRanges);
5375 infoPtr->nSelectionMark = -1;
5376 infoPtr->nFocusedItem = -1;
5377 SetRectEmpty(&infoPtr->rcFocus);
5378 /* But we are supposed to leave nHotItem as is! */
5381 /* send LVN_DELETEALLITEMS notification */
5382 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
5384 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
5386 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
5388 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5390 /* send LVN_DELETEITEM notification, if not suppressed
5391 and if it is not a virtual listview */
5392 if (!bSuppress) notify_deleteitem(infoPtr, i);
5393 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
5394 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5395 /* free id struct */
5396 j = DPA_GetPtrIndex(infoPtr->hdpaItemIds, lpItem->id);
5397 lpID = DPA_GetPtr(infoPtr->hdpaItemIds, j);
5398 DPA_DeletePtr(infoPtr->hdpaItemIds, j);
5400 /* both item and subitem start with ITEMHDR header */
5401 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
5403 hdrItem = DPA_GetPtr(hdpaSubItems, j);
5404 if (is_text(hdrItem->pszText)) Free(hdrItem->pszText);
5407 DPA_Destroy(hdpaSubItems);
5408 DPA_DeletePtr(infoPtr->hdpaItems, i);
5410 DPA_DeletePtr(infoPtr->hdpaPosX, i);
5411 DPA_DeletePtr(infoPtr->hdpaPosY, i);
5412 infoPtr->nItemCount --;
5417 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
5418 LISTVIEW_UpdateScroll(infoPtr);
5420 LISTVIEW_InvalidateList(infoPtr);
5427 * Scrolls, and updates the columns, when a column is changing width.
5430 * [I] infoPtr : valid pointer to the listview structure
5431 * [I] nColumn : column to scroll
5432 * [I] dx : amount of scroll, in pixels
5437 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
5439 COLUMN_INFO *lpColumnInfo;
5445 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
5446 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
5447 rcCol = lpColumnInfo->rcHeader;
5448 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
5449 rcCol.left = rcCol.right;
5451 /* adjust the other columns */
5452 hdi.mask = HDI_ORDER;
5453 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdi))
5455 INT nOrder = hdi.iOrder;
5456 for (nCol = 0; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
5458 hdi.mask = HDI_ORDER;
5459 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nCol, (LPARAM)&hdi);
5460 if (hdi.iOrder >= nOrder) {
5461 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
5462 lpColumnInfo->rcHeader.left += dx;
5463 lpColumnInfo->rcHeader.right += dx;
5468 /* do not update screen if not in report mode */
5469 if (!is_redrawing(infoPtr) || infoPtr->uView != LV_VIEW_DETAILS) return;
5471 /* Need to reset the item width when inserting a new column */
5472 infoPtr->nItemWidth += dx;
5474 LISTVIEW_UpdateScroll(infoPtr);
5475 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
5477 /* scroll to cover the deleted column, and invalidate for redraw */
5478 rcOld = infoPtr->rcList;
5479 rcOld.left = ptOrigin.x + rcCol.left + dx;
5480 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
5485 * Removes a column from the listview control.
5488 * [I] infoPtr : valid pointer to the listview structure
5489 * [I] nColumn : column index
5495 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
5499 TRACE("nColumn=%d\n", nColumn);
5501 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
5502 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5504 /* While the MSDN specifically says that column zero should not be deleted,
5505 what actually happens is that the column itself is deleted but no items or subitems
5509 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
5511 if (!SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nColumn, 0))
5514 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
5515 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
5517 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
5519 SUBITEM_INFO *lpSubItem, *lpDelItem;
5521 INT nItem, nSubItem, i;
5523 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
5525 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
5528 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
5530 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
5531 if (lpSubItem->iSubItem == nColumn)
5534 lpDelItem = lpSubItem;
5536 else if (lpSubItem->iSubItem > nColumn)
5538 lpSubItem->iSubItem--;
5542 /* if we found our subitem, zap it */
5546 if (is_text(lpDelItem->hdr.pszText))
5547 Free(lpDelItem->hdr.pszText);
5552 /* free dpa memory */
5553 DPA_DeletePtr(hdpaSubItems, nSubItem);
5558 /* update the other column info */
5559 LISTVIEW_UpdateItemSize(infoPtr);
5560 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
5561 LISTVIEW_InvalidateList(infoPtr);
5563 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
5570 * Invalidates the listview after an item's insertion or deletion.
5573 * [I] infoPtr : valid pointer to the listview structure
5574 * [I] nItem : item index
5575 * [I] dir : -1 if deleting, 1 if inserting
5580 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
5582 INT nPerCol, nItemCol, nItemRow;
5586 /* if we don't refresh, what's the point of scrolling? */
5587 if (!is_redrawing(infoPtr)) return;
5589 assert (abs(dir) == 1);
5591 /* arrange icons if autoarrange is on */
5592 if (is_autoarrange(infoPtr))
5594 BOOL arrange = TRUE;
5595 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
5596 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
5597 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
5600 /* scrollbars need updating */
5601 LISTVIEW_UpdateScroll(infoPtr);
5603 /* figure out the item's position */
5604 if (infoPtr->uView == LV_VIEW_DETAILS)
5605 nPerCol = infoPtr->nItemCount + 1;
5606 else if (infoPtr->uView == LV_VIEW_LIST)
5607 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
5608 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
5611 nItemCol = nItem / nPerCol;
5612 nItemRow = nItem % nPerCol;
5613 LISTVIEW_GetOrigin(infoPtr, &Origin);
5615 /* move the items below up a slot */
5616 rcScroll.left = nItemCol * infoPtr->nItemWidth;
5617 rcScroll.top = nItemRow * infoPtr->nItemHeight;
5618 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
5619 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
5620 OffsetRect(&rcScroll, Origin.x, Origin.y);
5621 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
5622 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
5624 TRACE("Invalidating rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
5625 InvalidateRect(infoPtr->hwndSelf, &rcScroll, TRUE);
5628 /* report has only that column, so we're done */
5629 if (infoPtr->uView == LV_VIEW_DETAILS) return;
5631 /* now for LISTs, we have to deal with the columns to the right */
5632 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
5634 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
5635 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
5636 OffsetRect(&rcScroll, Origin.x, Origin.y);
5637 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
5638 InvalidateRect(infoPtr->hwndSelf, &rcScroll, TRUE);
5643 * Removes an item from the listview control.
5646 * [I] infoPtr : valid pointer to the listview structure
5647 * [I] nItem : item index
5653 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
5656 const BOOL is_icon = (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON);
5658 TRACE("(nItem=%d)\n", nItem);
5660 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5662 /* remove selection, and focus */
5664 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
5665 LISTVIEW_SetItemState(infoPtr, nItem, &item);
5667 /* send LVN_DELETEITEM notification. */
5668 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
5670 /* we need to do this here, because we'll be deleting stuff */
5672 LISTVIEW_InvalidateItem(infoPtr, nItem);
5674 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5682 hdpaSubItems = DPA_DeletePtr(infoPtr->hdpaItems, nItem);
5683 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5685 /* free id struct */
5686 i = DPA_GetPtrIndex(infoPtr->hdpaItemIds, lpItem->id);
5687 lpID = DPA_GetPtr(infoPtr->hdpaItemIds, i);
5688 DPA_DeletePtr(infoPtr->hdpaItemIds, i);
5690 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
5692 hdrItem = DPA_GetPtr(hdpaSubItems, i);
5693 if (is_text(hdrItem->pszText)) Free(hdrItem->pszText);
5696 DPA_Destroy(hdpaSubItems);
5701 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
5702 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
5705 infoPtr->nItemCount--;
5706 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
5708 /* now is the invalidation fun */
5710 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
5717 * Callback implementation for editlabel control
5720 * [I] infoPtr : valid pointer to the listview structure
5721 * [I] storeText : store edit box text as item text
5722 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
5728 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, BOOL storeText, BOOL isW)
5730 HWND hwndSelf = infoPtr->hwndSelf;
5731 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
5732 NMLVDISPINFOW dispInfo;
5733 INT editedItem = infoPtr->nEditLabelItem;
5735 WCHAR *pszText = NULL;
5740 DWORD len = isW ? GetWindowTextLengthW(infoPtr->hwndEdit) : GetWindowTextLengthA(infoPtr->hwndEdit);
5744 if ((pszText = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))))
5746 if (isW) GetWindowTextW(infoPtr->hwndEdit, pszText, len+1);
5747 else GetWindowTextA(infoPtr->hwndEdit, (CHAR*)pszText, len+1);
5752 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
5754 ZeroMemory(&dispInfo, sizeof(dispInfo));
5755 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
5756 dispInfo.item.iItem = editedItem;
5757 dispInfo.item.iSubItem = 0;
5758 dispInfo.item.stateMask = ~0;
5759 dispInfo.item.pszText = szDispText;
5760 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
5761 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW))
5768 same = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
5771 LPWSTR tmp = textdupTtoW(pszText, FALSE);
5772 same = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
5773 textfreeT(tmp, FALSE);
5776 /* add the text from the edit in */
5777 dispInfo.item.mask |= LVIF_TEXT;
5778 dispInfo.item.pszText = same ? NULL : pszText;
5779 dispInfo.item.cchTextMax = textlenT(dispInfo.item.pszText, isW);
5781 /* Do we need to update the Item Text */
5782 res = notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW);
5784 infoPtr->nEditLabelItem = -1;
5785 infoPtr->hwndEdit = 0;
5787 if (!res) goto cleanup;
5789 if (!IsWindow(hwndSelf))
5794 if (!pszText) return TRUE;
5801 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5803 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, editedItem);
5804 ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
5805 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
5807 LISTVIEW_InvalidateItem(infoPtr, editedItem);
5813 ZeroMemory(&dispInfo, sizeof(dispInfo));
5814 dispInfo.item.mask = LVIF_TEXT;
5815 dispInfo.item.iItem = editedItem;
5816 dispInfo.item.iSubItem = 0;
5817 dispInfo.item.pszText = pszText;
5818 dispInfo.item.cchTextMax = textlenT(pszText, isW);
5819 res = LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
5829 * Subclassed edit control windproc function
5832 * [I] hwnd : the edit window handle
5833 * [I] uMsg : the message that is to be processed
5834 * [I] wParam : first message parameter
5835 * [I] lParam : second message parameter
5836 * [I] isW : TRUE if input is Unicode
5841 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
5843 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
5846 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
5847 hwnd, uMsg, wParam, lParam, isW);
5852 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
5856 WNDPROC editProc = infoPtr->EditWndProc;
5857 infoPtr->EditWndProc = 0;
5858 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
5859 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
5863 if (VK_ESCAPE == (INT)wParam)
5868 else if (VK_RETURN == (INT)wParam)
5872 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
5876 if (infoPtr->hwndEdit)
5877 LISTVIEW_EndEditLabelT(infoPtr, save, isW);
5879 SendMessageW(hwnd, WM_CLOSE, 0, 0);
5885 * Subclassed edit control Unicode windproc function
5888 * [I] hwnd : the edit window handle
5889 * [I] uMsg : the message that is to be processed
5890 * [I] wParam : first message parameter
5891 * [I] lParam : second message parameter
5895 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
5897 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
5902 * Subclassed edit control ANSI windproc function
5905 * [I] hwnd : the edit window handle
5906 * [I] uMsg : the message that is to be processed
5907 * [I] wParam : first message parameter
5908 * [I] lParam : second message parameter
5912 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
5914 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
5919 * Creates a subclassed edit control
5922 * [I] infoPtr : valid pointer to the listview structure
5923 * [I] text : initial text for the edit
5924 * [I] style : the window style
5925 * [I] isW : TRUE if input is Unicode
5929 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, BOOL isW)
5931 static const DWORD style = WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER|WS_VISIBLE;
5932 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
5935 TRACE("(%p, text=%s, isW=%d)\n", infoPtr, debugtext_t(text, isW), isW);
5937 /* window will be resized and positioned after LVN_BEGINLABELEDIT */
5939 hedit = CreateWindowW(WC_EDITW, text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
5941 hedit = CreateWindowA(WC_EDITA, (LPCSTR)text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
5943 if (!hedit) return 0;
5945 infoPtr->EditWndProc = (WNDPROC)
5946 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
5947 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
5949 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
5950 SendMessageW(hedit, EM_SETLIMITTEXT, DISP_TEXT_SIZE-1, 0);
5957 * Begin in place editing of specified list view item
5960 * [I] infoPtr : valid pointer to the listview structure
5961 * [I] nItem : item index
5962 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
5968 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
5970 WCHAR disptextW[DISP_TEXT_SIZE] = { 0 };
5971 HWND hwndSelf = infoPtr->hwndSelf;
5972 NMLVDISPINFOW dispInfo;
5973 HFONT hOldFont = NULL;
5979 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
5981 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
5983 /* remove existing edit box */
5984 if (infoPtr->hwndEdit)
5986 SetFocus(infoPtr->hwndSelf);
5987 infoPtr->hwndEdit = 0;
5990 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5992 infoPtr->nEditLabelItem = nItem;
5994 LISTVIEW_SetSelection(infoPtr, nItem);
5995 LISTVIEW_SetItemFocus(infoPtr, nItem);
5996 LISTVIEW_InvalidateItem(infoPtr, nItem);
5998 rect.left = LVIR_LABEL;
5999 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
6001 ZeroMemory(&dispInfo, sizeof(dispInfo));
6002 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
6003 dispInfo.item.iItem = nItem;
6004 dispInfo.item.iSubItem = 0;
6005 dispInfo.item.stateMask = ~0;
6006 dispInfo.item.pszText = disptextW;
6007 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
6008 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
6010 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, isW);
6011 if (!infoPtr->hwndEdit) return 0;
6013 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
6015 if (!IsWindow(hwndSelf))
6017 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
6018 infoPtr->hwndEdit = 0;
6022 TRACE("disp text=%s\n", debugtext_t(dispInfo.item.pszText, isW));
6024 /* position and display edit box */
6025 hdc = GetDC(infoPtr->hwndSelf);
6027 /* select the font to get appropriate metric dimensions */
6029 hOldFont = SelectObject(hdc, infoPtr->hFont);
6031 /* use real edit box content, it could be altered during LVN_BEGINLABELEDIT notification */
6032 GetWindowTextW(infoPtr->hwndEdit, disptextW, DISP_TEXT_SIZE);
6033 TRACE("edit box text=%s\n", debugstr_w(disptextW));
6035 /* get string length in pixels */
6036 GetTextExtentPoint32W(hdc, disptextW, lstrlenW(disptextW), &sz);
6038 /* add extra spacing for the next character */
6039 GetTextMetricsW(hdc, &tm);
6040 sz.cx += tm.tmMaxCharWidth * 2;
6043 SelectObject(hdc, hOldFont);
6045 ReleaseDC(infoPtr->hwndSelf, hdc);
6047 sz.cy = rect.bottom - rect.top + 2;
6050 TRACE("moving edit=(%d,%d)-(%d,%d)\n", rect.left, rect.top, sz.cx, sz.cy);
6051 MoveWindow(infoPtr->hwndEdit, rect.left, rect.top, sz.cx, sz.cy, FALSE);
6052 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
6053 SetFocus(infoPtr->hwndEdit);
6054 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
6055 return infoPtr->hwndEdit;
6061 * Ensures the specified item is visible, scrolling into view if necessary.
6064 * [I] infoPtr : valid pointer to the listview structure
6065 * [I] nItem : item index
6066 * [I] bPartial : partially or entirely visible
6072 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
6074 INT nScrollPosHeight = 0;
6075 INT nScrollPosWidth = 0;
6076 INT nHorzAdjust = 0;
6077 INT nVertAdjust = 0;
6080 RECT rcItem, rcTemp;
6082 rcItem.left = LVIR_BOUNDS;
6083 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
6085 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
6087 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
6089 /* scroll left/right, but in LV_VIEW_DETAILS mode */
6090 if (infoPtr->uView == LV_VIEW_LIST)
6091 nScrollPosWidth = infoPtr->nItemWidth;
6092 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
6093 nScrollPosWidth = 1;
6095 if (rcItem.left < infoPtr->rcList.left)
6098 if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.left - infoPtr->rcList.left;
6103 if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.right - infoPtr->rcList.right;
6107 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
6109 /* scroll up/down, but not in LVS_LIST mode */
6110 if (infoPtr->uView == LV_VIEW_DETAILS)
6111 nScrollPosHeight = infoPtr->nItemHeight;
6112 else if ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON))
6113 nScrollPosHeight = 1;
6115 if (rcItem.top < infoPtr->rcList.top)
6118 if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
6123 if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
6127 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
6129 if (nScrollPosWidth)
6131 INT diff = nHorzDiff / nScrollPosWidth;
6132 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
6133 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff);
6136 if (nScrollPosHeight)
6138 INT diff = nVertDiff / nScrollPosHeight;
6139 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
6140 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff);
6148 * Searches for an item with specific characteristics.
6151 * [I] hwnd : window handle
6152 * [I] nStart : base item index
6153 * [I] lpFindInfo : item information to look for
6156 * SUCCESS : index of item
6159 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
6160 const LVFINDINFOW *lpFindInfo)
6162 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6163 BOOL bWrap = FALSE, bNearest = FALSE;
6164 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
6165 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
6166 POINT Position, Destination;
6169 /* Search in virtual listviews should be done by application, not by
6170 listview control, so we just send LVN_ODFINDITEMW and return the result */
6171 if (infoPtr->dwStyle & LVS_OWNERDATA)
6175 nmlv.iStart = nStart;
6176 nmlv.lvfi = *lpFindInfo;
6177 return notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
6180 if (!lpFindInfo || nItem < 0) return -1;
6183 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL) ||
6184 lpFindInfo->flags & LVFI_SUBSTRING)
6186 lvItem.mask |= LVIF_TEXT;
6187 lvItem.pszText = szDispText;
6188 lvItem.cchTextMax = DISP_TEXT_SIZE;
6191 if (lpFindInfo->flags & LVFI_WRAP)
6194 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
6195 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON))
6200 LISTVIEW_GetOrigin(infoPtr, &Origin);
6201 Destination.x = lpFindInfo->pt.x - Origin.x;
6202 Destination.y = lpFindInfo->pt.y - Origin.y;
6203 switch(lpFindInfo->vkDirection)
6205 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
6206 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
6207 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
6208 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
6209 case VK_HOME: Destination.x = Destination.y = 0; break;
6210 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
6211 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
6213 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
6214 Destination.x = rcArea.right;
6215 Destination.y = rcArea.bottom;
6217 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
6221 else Destination.x = Destination.y = 0;
6223 /* if LVFI_PARAM is specified, all other flags are ignored */
6224 if (lpFindInfo->flags & LVFI_PARAM)
6226 lvItem.mask |= LVIF_PARAM;
6228 lvItem.mask &= ~LVIF_TEXT;
6232 for (; nItem < nLast; nItem++)
6234 lvItem.iItem = nItem;
6235 lvItem.iSubItem = 0;
6236 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6238 if (lvItem.mask & LVIF_PARAM)
6240 if (lpFindInfo->lParam == lvItem.lParam)
6246 if (lvItem.mask & LVIF_TEXT)
6248 if (lpFindInfo->flags & (LVFI_PARTIAL | LVFI_SUBSTRING))
6250 WCHAR *p = strstrW(lvItem.pszText, lpFindInfo->psz);
6251 if (!p || p != lvItem.pszText) continue;
6255 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
6259 if (!bNearest) return nItem;
6261 /* This is very inefficient. To do a good job here,
6262 * we need a sorted array of (x,y) item positions */
6263 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
6265 /* compute the distance^2 to the destination */
6266 xdist = Destination.x - Position.x;
6267 ydist = Destination.y - Position.y;
6268 dist = xdist * xdist + ydist * ydist;
6270 /* remember the distance, and item if it's closer */
6274 nNearestItem = nItem;
6281 nLast = min(nStart + 1, infoPtr->nItemCount);
6286 return nNearestItem;
6291 * Searches for an item with specific characteristics.
6294 * [I] hwnd : window handle
6295 * [I] nStart : base item index
6296 * [I] lpFindInfo : item information to look for
6299 * SUCCESS : index of item
6302 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
6303 const LVFINDINFOA *lpFindInfo)
6305 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL) ||
6306 lpFindInfo->flags & LVFI_SUBSTRING;
6311 memcpy(&fiw, lpFindInfo, sizeof(fiw));
6312 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
6313 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
6314 textfreeT(strW, FALSE);
6320 * Retrieves column attributes.
6323 * [I] infoPtr : valid pointer to the listview structure
6324 * [I] nColumn : column index
6325 * [IO] lpColumn : column information
6326 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
6327 * otherwise it is in fact a LPLVCOLUMNA
6333 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
6335 COLUMN_INFO *lpColumnInfo;
6338 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6339 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6341 /* initialize memory */
6342 ZeroMemory(&hdi, sizeof(hdi));
6344 if (lpColumn->mask & LVCF_TEXT)
6346 hdi.mask |= HDI_TEXT;
6347 hdi.pszText = lpColumn->pszText;
6348 hdi.cchTextMax = lpColumn->cchTextMax;
6351 if (lpColumn->mask & LVCF_IMAGE)
6352 hdi.mask |= HDI_IMAGE;
6354 if (lpColumn->mask & LVCF_ORDER)
6355 hdi.mask |= HDI_ORDER;
6357 if (lpColumn->mask & LVCF_SUBITEM)
6358 hdi.mask |= HDI_LPARAM;
6360 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
6362 if (lpColumn->mask & LVCF_FMT)
6363 lpColumn->fmt = lpColumnInfo->fmt;
6365 if (lpColumn->mask & LVCF_WIDTH)
6366 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
6368 if (lpColumn->mask & LVCF_IMAGE)
6369 lpColumn->iImage = hdi.iImage;
6371 if (lpColumn->mask & LVCF_ORDER)
6372 lpColumn->iOrder = hdi.iOrder;
6374 if (lpColumn->mask & LVCF_SUBITEM)
6375 lpColumn->iSubItem = hdi.lParam;
6377 if (lpColumn->mask & LVCF_MINWIDTH)
6378 lpColumn->cxMin = lpColumnInfo->cxMin;
6384 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
6386 TRACE("iCount=%d, lpiArray=%p\n", iCount, lpiArray);
6391 return SendMessageW(infoPtr->hwndHeader, HDM_GETORDERARRAY, iCount, (LPARAM)lpiArray);
6396 * Retrieves the column width.
6399 * [I] infoPtr : valid pointer to the listview structure
6400 * [I] int : column index
6403 * SUCCESS : column width
6406 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
6408 INT nColumnWidth = 0;
6411 TRACE("nColumn=%d\n", nColumn);
6413 /* we have a 'column' in LIST and REPORT mode only */
6414 switch(infoPtr->uView)
6417 nColumnWidth = infoPtr->nItemWidth;
6419 case LV_VIEW_DETAILS:
6420 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDN_ITEMCHANGED.
6421 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
6422 * HDN_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
6424 * TODO: should we do the same in LVM_GETCOLUMN?
6426 hdItem.mask = HDI_WIDTH;
6427 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
6429 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
6432 nColumnWidth = hdItem.cxy;
6436 TRACE("nColumnWidth=%d\n", nColumnWidth);
6437 return nColumnWidth;
6442 * In list or report display mode, retrieves the number of items that can fit
6443 * vertically in the visible area. In icon or small icon display mode,
6444 * retrieves the total number of visible items.
6447 * [I] infoPtr : valid pointer to the listview structure
6450 * Number of fully visible items.
6452 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
6454 switch (infoPtr->uView)
6457 case LV_VIEW_SMALLICON:
6458 return infoPtr->nItemCount;
6459 case LV_VIEW_DETAILS:
6460 return LISTVIEW_GetCountPerColumn(infoPtr);
6462 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
6470 * Retrieves an image list handle.
6473 * [I] infoPtr : valid pointer to the listview structure
6474 * [I] nImageList : image list identifier
6477 * SUCCESS : image list handle
6480 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
6484 case LVSIL_NORMAL: return infoPtr->himlNormal;
6485 case LVSIL_SMALL: return infoPtr->himlSmall;
6486 case LVSIL_STATE: return infoPtr->himlState;
6487 case LVSIL_GROUPHEADER:
6488 FIXME("LVSIL_GROUPHEADER not supported\n");
6491 WARN("got unknown imagelist index - %d\n", nImageList);
6496 /* LISTVIEW_GetISearchString */
6500 * Retrieves item attributes.
6503 * [I] hwnd : window handle
6504 * [IO] lpLVItem : item info
6505 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
6506 * if FALSE, then lpLVItem is a LPLVITEMA.
6509 * This is the internal 'GetItem' interface -- it tries to
6510 * be smart and avoid text copies, if possible, by modifying
6511 * lpLVItem->pszText to point to the text string. Please note
6512 * that this is not always possible (e.g. OWNERDATA), so on
6513 * entry you *must* supply valid values for pszText, and cchTextMax.
6514 * The only difference to the documented interface is that upon
6515 * return, you should use *only* the lpLVItem->pszText, rather than
6516 * the buffer pointer you provided on input. Most code already does
6517 * that, so it's not a problem.
6518 * For the two cases when the text must be copied (that is,
6519 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
6525 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
6527 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
6528 NMLVDISPINFOW dispInfo;
6534 TRACE("(item=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6536 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
6539 if (lpLVItem->mask == 0) return TRUE;
6540 TRACE("mask=%x\n", lpLVItem->mask);
6542 /* make a local copy */
6543 isubitem = lpLVItem->iSubItem;
6545 /* a quick optimization if all we're asked is the focus state
6546 * these queries are worth optimising since they are common,
6547 * and can be answered in constant time, without the heavy accesses */
6548 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
6549 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
6551 lpLVItem->state = 0;
6552 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6553 lpLVItem->state |= LVIS_FOCUSED;
6557 ZeroMemory(&dispInfo, sizeof(dispInfo));
6559 /* if the app stores all the data, handle it separately */
6560 if (infoPtr->dwStyle & LVS_OWNERDATA)
6562 dispInfo.item.state = 0;
6564 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
6565 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) ||
6566 ((lpLVItem->mask & LVIF_STATE) && (infoPtr->uCallbackMask & lpLVItem->stateMask)))
6568 UINT mask = lpLVItem->mask;
6570 /* NOTE: copy only fields which we _know_ are initialized, some apps
6571 * depend on the uninitialized fields being 0 */
6572 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
6573 dispInfo.item.iItem = lpLVItem->iItem;
6574 dispInfo.item.iSubItem = isubitem;
6575 if (lpLVItem->mask & LVIF_TEXT)
6577 if (lpLVItem->mask & LVIF_NORECOMPUTE)
6579 dispInfo.item.mask &= ~(LVIF_TEXT | LVIF_NORECOMPUTE);
6582 dispInfo.item.pszText = lpLVItem->pszText;
6583 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
6586 if (lpLVItem->mask & LVIF_STATE)
6587 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
6588 /* could be zeroed on LVIF_NORECOMPUTE case */
6589 if (dispInfo.item.mask)
6591 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
6592 dispInfo.item.stateMask = lpLVItem->stateMask;
6593 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6595 /* full size structure expected - _WIN32IE >= 0x560 */
6596 *lpLVItem = dispInfo.item;
6598 else if (lpLVItem->mask & LVIF_INDENT)
6600 /* indent member expected - _WIN32IE >= 0x300 */
6601 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
6605 /* minimal structure expected */
6606 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
6608 lpLVItem->mask = mask;
6609 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
6613 /* make sure lParam is zeroed out */
6614 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
6616 /* callback marked pointer required here */
6617 if ((lpLVItem->mask & LVIF_TEXT) && (lpLVItem->mask & LVIF_NORECOMPUTE))
6618 lpLVItem->pszText = LPSTR_TEXTCALLBACKW;
6620 /* we store only a little state, so if we're not asked, we're done */
6621 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
6623 /* if focus is handled by us, report it */
6624 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
6626 lpLVItem->state &= ~LVIS_FOCUSED;
6627 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6628 lpLVItem->state |= LVIS_FOCUSED;
6631 /* and do the same for selection, if we handle it */
6632 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
6634 lpLVItem->state &= ~LVIS_SELECTED;
6635 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
6636 lpLVItem->state |= LVIS_SELECTED;
6642 /* find the item and subitem structures before we proceed */
6643 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
6644 lpItem = DPA_GetPtr(hdpaSubItems, 0);
6649 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
6650 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
6653 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
6658 pItemHdr = &lpItem->hdr;
6660 /* Do we need to query the state from the app? */
6661 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
6663 dispInfo.item.mask |= LVIF_STATE;
6664 dispInfo.item.stateMask = infoPtr->uCallbackMask;
6667 /* Do we need to enquire about the image? */
6668 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
6669 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
6671 dispInfo.item.mask |= LVIF_IMAGE;
6672 dispInfo.item.iImage = I_IMAGECALLBACK;
6675 /* Only items support indentation */
6676 if ((lpLVItem->mask & LVIF_INDENT) && lpItem->iIndent == I_INDENTCALLBACK &&
6679 dispInfo.item.mask |= LVIF_INDENT;
6680 dispInfo.item.iIndent = I_INDENTCALLBACK;
6683 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
6684 if ((lpLVItem->mask & LVIF_TEXT) && !(lpLVItem->mask & LVIF_NORECOMPUTE) &&
6685 !is_text(pItemHdr->pszText))
6687 dispInfo.item.mask |= LVIF_TEXT;
6688 dispInfo.item.pszText = lpLVItem->pszText;
6689 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
6690 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
6691 *dispInfo.item.pszText = '\0';
6694 /* If we don't have all the requested info, query the application */
6695 if (dispInfo.item.mask)
6697 dispInfo.item.iItem = lpLVItem->iItem;
6698 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
6699 dispInfo.item.lParam = lpItem->lParam;
6700 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
6701 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
6704 /* we should not store values for subitems */
6705 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
6707 /* Now, handle the iImage field */
6708 if (dispInfo.item.mask & LVIF_IMAGE)
6710 lpLVItem->iImage = dispInfo.item.iImage;
6711 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
6712 pItemHdr->iImage = dispInfo.item.iImage;
6714 else if (lpLVItem->mask & LVIF_IMAGE)
6716 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
6717 lpLVItem->iImage = pItemHdr->iImage;
6719 lpLVItem->iImage = 0;
6722 /* The pszText field */
6723 if (dispInfo.item.mask & LVIF_TEXT)
6725 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
6726 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
6728 lpLVItem->pszText = dispInfo.item.pszText;
6730 else if (lpLVItem->mask & LVIF_TEXT)
6732 /* if LVN_GETDISPINFO's disabled with LVIF_NORECOMPUTE return callback placeholder */
6733 if (isW || !is_text(pItemHdr->pszText)) lpLVItem->pszText = pItemHdr->pszText;
6734 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
6737 /* Next is the lParam field */
6738 if (dispInfo.item.mask & LVIF_PARAM)
6740 lpLVItem->lParam = dispInfo.item.lParam;
6741 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
6742 lpItem->lParam = dispInfo.item.lParam;
6744 else if (lpLVItem->mask & LVIF_PARAM)
6745 lpLVItem->lParam = lpItem->lParam;
6747 /* if this is a subitem, we're done */
6748 if (isubitem) return TRUE;
6750 /* ... the state field (this one is different due to uCallbackmask) */
6751 if (lpLVItem->mask & LVIF_STATE)
6753 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
6754 if (dispInfo.item.mask & LVIF_STATE)
6756 lpLVItem->state &= ~dispInfo.item.stateMask;
6757 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
6759 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
6761 lpLVItem->state &= ~LVIS_FOCUSED;
6762 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6763 lpLVItem->state |= LVIS_FOCUSED;
6765 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
6767 lpLVItem->state &= ~LVIS_SELECTED;
6768 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
6769 lpLVItem->state |= LVIS_SELECTED;
6773 /* and last, but not least, the indent field */
6774 if (dispInfo.item.mask & LVIF_INDENT)
6776 lpLVItem->iIndent = dispInfo.item.iIndent;
6777 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && lpItem->iIndent == I_INDENTCALLBACK)
6778 lpItem->iIndent = dispInfo.item.iIndent;
6780 else if (lpLVItem->mask & LVIF_INDENT)
6782 lpLVItem->iIndent = lpItem->iIndent;
6790 * Retrieves item attributes.
6793 * [I] hwnd : window handle
6794 * [IO] lpLVItem : item info
6795 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
6796 * if FALSE, then lpLVItem is a LPLVITEMA.
6799 * This is the external 'GetItem' interface -- it properly copies
6800 * the text in the provided buffer.
6806 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
6811 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
6814 pszText = lpLVItem->pszText;
6815 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
6816 if (bResult && (lpLVItem->mask & LVIF_TEXT) && lpLVItem->pszText != pszText)
6818 if (lpLVItem->pszText != LPSTR_TEXTCALLBACKW)
6819 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
6821 pszText = LPSTR_TEXTCALLBACKW;
6823 lpLVItem->pszText = pszText;
6831 * Retrieves the position (upper-left) of the listview control item.
6832 * Note that for LVS_ICON style, the upper-left is that of the icon
6833 * and not the bounding box.
6836 * [I] infoPtr : valid pointer to the listview structure
6837 * [I] nItem : item index
6838 * [O] lpptPosition : coordinate information
6844 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
6848 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
6850 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
6852 LISTVIEW_GetOrigin(infoPtr, &Origin);
6853 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
6855 if (infoPtr->uView == LV_VIEW_ICON)
6857 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
6858 lpptPosition->y += ICON_TOP_PADDING;
6860 lpptPosition->x += Origin.x;
6861 lpptPosition->y += Origin.y;
6863 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
6870 * Retrieves the bounding rectangle for a listview control item.
6873 * [I] infoPtr : valid pointer to the listview structure
6874 * [I] nItem : item index
6875 * [IO] lprc : bounding rectangle coordinates
6876 * lprc->left specifies the portion of the item for which the bounding
6877 * rectangle will be retrieved.
6879 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
6880 * including the icon and label.
6883 * * Experiment shows that native control returns:
6884 * * width = min (48, length of text line)
6885 * * .left = position.x - (width - iconsize.cx)/2
6886 * * .right = .left + width
6887 * * height = #lines of text * ntmHeight + icon height + 8
6888 * * .top = position.y - 2
6889 * * .bottom = .top + height
6890 * * separation between items .y = itemSpacing.cy - height
6891 * * .x = itemSpacing.cx - width
6892 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
6895 * * Experiment shows that native control returns:
6896 * * width = iconSize.cx + 16
6897 * * .left = position.x - (width - iconsize.cx)/2
6898 * * .right = .left + width
6899 * * height = iconSize.cy + 4
6900 * * .top = position.y - 2
6901 * * .bottom = .top + height
6902 * * separation between items .y = itemSpacing.cy - height
6903 * * .x = itemSpacing.cx - width
6904 * LVIR_LABEL Returns the bounding rectangle of the item text.
6907 * * Experiment shows that native control returns:
6908 * * width = text length
6909 * * .left = position.x - width/2
6910 * * .right = .left + width
6911 * * height = ntmH * linecount + 2
6912 * * .top = position.y + iconSize.cy + 6
6913 * * .bottom = .top + height
6914 * * separation between items .y = itemSpacing.cy - height
6915 * * .x = itemSpacing.cx - width
6916 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
6917 * rectangles, but excludes columns in report view.
6924 * Note that the bounding rectangle of the label in the LVS_ICON view depends
6925 * upon whether the window has the focus currently and on whether the item
6926 * is the one with the focus. Ensure that the control's record of which
6927 * item has the focus agrees with the items' records.
6929 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
6931 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6932 BOOL doLabel = TRUE, oversizedBox = FALSE;
6933 POINT Position, Origin;
6937 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
6939 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
6941 LISTVIEW_GetOrigin(infoPtr, &Origin);
6942 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
6944 /* Be smart and try to figure out the minimum we have to do */
6945 if (lprc->left == LVIR_ICON) doLabel = FALSE;
6946 if (infoPtr->uView == LV_VIEW_DETAILS && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
6947 if (infoPtr->uView == LV_VIEW_ICON && lprc->left != LVIR_ICON &&
6948 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
6949 oversizedBox = TRUE;
6951 /* get what we need from the item before hand, so we make
6952 * only one request. This can speed up things, if data
6953 * is stored on the app side */
6955 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
6956 if (doLabel) lvItem.mask |= LVIF_TEXT;
6957 lvItem.iItem = nItem;
6958 lvItem.iSubItem = 0;
6959 lvItem.pszText = szDispText;
6960 lvItem.cchTextMax = DISP_TEXT_SIZE;
6961 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
6962 /* we got the state already up, simulate it here, to avoid a reget */
6963 if (infoPtr->uView == LV_VIEW_ICON && (lprc->left != LVIR_ICON))
6965 lvItem.mask |= LVIF_STATE;
6966 lvItem.stateMask = LVIS_FOCUSED;
6967 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
6970 if (infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
6971 lprc->left = LVIR_BOUNDS;
6977 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
6981 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
6985 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
6988 case LVIR_SELECTBOUNDS:
6989 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
6993 WARN("Unknown value: %d\n", lprc->left);
6997 if (infoPtr->uView == LV_VIEW_DETAILS)
6999 if (mode != LVIR_BOUNDS)
7000 OffsetRect(lprc, Origin.x + LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left,
7001 Position.y + Origin.y);
7003 OffsetRect(lprc, Origin.x, Position.y + Origin.y);
7006 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
7008 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
7015 * Retrieves the spacing between listview control items.
7018 * [I] infoPtr : valid pointer to the listview structure
7019 * [IO] lprc : rectangle to receive the output
7020 * on input, lprc->top = nSubItem
7021 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
7023 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
7024 * not only those of the first column.
7030 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT item, LPRECT lprc)
7032 RECT rect = { 0, 0, 0, 0 };
7036 if (!lprc) return FALSE;
7038 TRACE("(item=%d, subitem=%d, type=%d)\n", item, lprc->top, lprc->left);
7039 /* Subitem of '0' means item itself, and this works for all control view modes */
7041 return LISTVIEW_GetItemRect(infoPtr, item, lprc);
7043 if (infoPtr->uView != LV_VIEW_DETAILS) return FALSE;
7045 LISTVIEW_GetOrigin(infoPtr, &origin);
7046 /* this works for any item index, no matter if it exists or not */
7047 y = item * infoPtr->nItemHeight + origin.y;
7049 if (infoPtr->hwndHeader && SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, lprc->top, (LPARAM)&rect))
7052 rect.bottom = infoPtr->nItemHeight;
7056 /* Native implementation is broken for this case and garbage is left for left and right fields,
7057 we zero them to get predictable output */
7058 lprc->left = lprc->right = lprc->top = 0;
7059 lprc->bottom = infoPtr->nItemHeight;
7060 OffsetRect(lprc, origin.x, y);
7061 TRACE("return rect %s\n", wine_dbgstr_rect(lprc));
7069 /* it doesn't matter if main item actually has an icon, if imagelist is set icon width is returned */
7070 if (infoPtr->himlSmall)
7071 rect.right = rect.left + infoPtr->iconSize.cx;
7073 rect.right = rect.left;
7075 rect.bottom = rect.top + infoPtr->iconSize.cy;
7083 ERR("Unknown bounds=%d\n", lprc->left);
7087 OffsetRect(&rect, origin.x, y);
7089 TRACE("return rect %s\n", wine_dbgstr_rect(lprc));
7096 * Retrieves the spacing between listview control items.
7099 * [I] infoPtr : valid pointer to the listview structure
7100 * [I] bSmall : flag for small or large icon
7103 * Horizontal + vertical spacing
7105 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
7111 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7115 if (infoPtr->uView == LV_VIEW_ICON)
7116 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
7118 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
7125 * Retrieves the state of a listview control item.
7128 * [I] infoPtr : valid pointer to the listview structure
7129 * [I] nItem : item index
7130 * [I] uMask : state mask
7133 * State specified by the mask.
7135 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
7139 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
7141 lvItem.iItem = nItem;
7142 lvItem.iSubItem = 0;
7143 lvItem.mask = LVIF_STATE;
7144 lvItem.stateMask = uMask;
7145 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
7147 return lvItem.state & uMask;
7152 * Retrieves the text of a listview control item or subitem.
7155 * [I] hwnd : window handle
7156 * [I] nItem : item index
7157 * [IO] lpLVItem : item information
7158 * [I] isW : TRUE if lpLVItem is Unicode
7161 * SUCCESS : string length
7164 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
7166 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
7168 lpLVItem->mask = LVIF_TEXT;
7169 lpLVItem->iItem = nItem;
7170 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
7172 return textlenT(lpLVItem->pszText, isW);
7177 * Searches for an item based on properties + relationships.
7180 * [I] infoPtr : valid pointer to the listview structure
7181 * [I] nItem : item index
7182 * [I] uFlags : relationship flag
7185 * SUCCESS : item index
7188 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
7191 LVFINDINFOW lvFindInfo;
7192 INT nCountPerColumn;
7196 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
7197 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
7199 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
7201 if (uFlags & LVNI_CUT)
7204 if (uFlags & LVNI_DROPHILITED)
7205 uMask |= LVIS_DROPHILITED;
7207 if (uFlags & LVNI_FOCUSED)
7208 uMask |= LVIS_FOCUSED;
7210 if (uFlags & LVNI_SELECTED)
7211 uMask |= LVIS_SELECTED;
7213 /* if we're asked for the focused item, that's only one,
7214 * so it's worth optimizing */
7215 if (uFlags & LVNI_FOCUSED)
7217 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
7218 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
7221 if (uFlags & LVNI_ABOVE)
7223 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
7228 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7234 /* Special case for autoarrange - move 'til the top of a list */
7235 if (is_autoarrange(infoPtr))
7237 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7238 while (nItem - nCountPerRow >= 0)
7240 nItem -= nCountPerRow;
7241 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7246 lvFindInfo.flags = LVFI_NEARESTXY;
7247 lvFindInfo.vkDirection = VK_UP;
7248 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7249 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7251 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7256 else if (uFlags & LVNI_BELOW)
7258 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
7260 while (nItem < infoPtr->nItemCount)
7263 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7269 /* Special case for autoarrange - move 'til the bottom of a list */
7270 if (is_autoarrange(infoPtr))
7272 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7273 while (nItem + nCountPerRow < infoPtr->nItemCount )
7275 nItem += nCountPerRow;
7276 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7281 lvFindInfo.flags = LVFI_NEARESTXY;
7282 lvFindInfo.vkDirection = VK_DOWN;
7283 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7284 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7286 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7291 else if (uFlags & LVNI_TOLEFT)
7293 if (infoPtr->uView == LV_VIEW_LIST)
7295 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
7296 while (nItem - nCountPerColumn >= 0)
7298 nItem -= nCountPerColumn;
7299 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7303 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7305 /* Special case for autoarrange - move 'til the beginning of a row */
7306 if (is_autoarrange(infoPtr))
7308 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7309 while (nItem % nCountPerRow > 0)
7312 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7317 lvFindInfo.flags = LVFI_NEARESTXY;
7318 lvFindInfo.vkDirection = VK_LEFT;
7319 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7320 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7322 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7327 else if (uFlags & LVNI_TORIGHT)
7329 if (infoPtr->uView == LV_VIEW_LIST)
7331 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
7332 while (nItem + nCountPerColumn < infoPtr->nItemCount)
7334 nItem += nCountPerColumn;
7335 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7339 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7341 /* Special case for autoarrange - move 'til the end of a row */
7342 if (is_autoarrange(infoPtr))
7344 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7345 while (nItem % nCountPerRow < nCountPerRow - 1 )
7348 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7353 lvFindInfo.flags = LVFI_NEARESTXY;
7354 lvFindInfo.vkDirection = VK_RIGHT;
7355 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7356 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7358 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7367 /* search by index */
7368 for (i = nItem; i < infoPtr->nItemCount; i++)
7370 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
7378 /* LISTVIEW_GetNumberOfWorkAreas */
7382 * Retrieves the origin coordinates when in icon or small icon display mode.
7385 * [I] infoPtr : valid pointer to the listview structure
7386 * [O] lpptOrigin : coordinate information
7391 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
7393 INT nHorzPos = 0, nVertPos = 0;
7394 SCROLLINFO scrollInfo;
7396 scrollInfo.cbSize = sizeof(SCROLLINFO);
7397 scrollInfo.fMask = SIF_POS;
7399 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
7400 nHorzPos = scrollInfo.nPos;
7401 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
7402 nVertPos = scrollInfo.nPos;
7404 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
7406 lpptOrigin->x = infoPtr->rcList.left;
7407 lpptOrigin->y = infoPtr->rcList.top;
7408 if (infoPtr->uView == LV_VIEW_LIST)
7409 nHorzPos *= infoPtr->nItemWidth;
7410 else if (infoPtr->uView == LV_VIEW_DETAILS)
7411 nVertPos *= infoPtr->nItemHeight;
7413 lpptOrigin->x -= nHorzPos;
7414 lpptOrigin->y -= nVertPos;
7416 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
7421 * Retrieves the width of a string.
7424 * [I] hwnd : window handle
7425 * [I] lpszText : text string to process
7426 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
7429 * SUCCESS : string width (in pixels)
7432 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
7437 if (is_text(lpszText))
7439 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
7440 HDC hdc = GetDC(infoPtr->hwndSelf);
7441 HFONT hOldFont = SelectObject(hdc, hFont);
7444 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
7446 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
7447 SelectObject(hdc, hOldFont);
7448 ReleaseDC(infoPtr->hwndSelf, hdc);
7450 return stringSize.cx;
7455 * Determines which listview item is located at the specified position.
7458 * [I] infoPtr : valid pointer to the listview structure
7459 * [IO] lpht : hit test information
7460 * [I] subitem : fill out iSubItem.
7461 * [I] select : return the index only if the hit selects the item
7464 * (mm 20001022): We must not allow iSubItem to be touched, for
7465 * an app might pass only a structure with space up to iItem!
7466 * (MS Office 97 does that for instance in the file open dialog)
7469 * SUCCESS : item index
7472 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
7474 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
7475 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
7476 POINT Origin, Position, opt;
7481 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
7485 if (subitem) lpht->iSubItem = 0;
7487 LISTVIEW_GetOrigin(infoPtr, &Origin);
7489 /* set whole list relation flags */
7490 if (subitem && infoPtr->uView == LV_VIEW_DETAILS)
7492 /* LVM_SUBITEMHITTEST checks left bound of possible client area */
7493 if (infoPtr->rcList.left > lpht->pt.x && Origin.x < lpht->pt.x)
7494 lpht->flags |= LVHT_TOLEFT;
7496 if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
7497 opt.y = lpht->pt.y + infoPtr->rcList.top;
7501 if (infoPtr->rcList.bottom < opt.y)
7502 lpht->flags |= LVHT_BELOW;
7506 if (infoPtr->rcList.left > lpht->pt.x)
7507 lpht->flags |= LVHT_TOLEFT;
7508 else if (infoPtr->rcList.right < lpht->pt.x)
7509 lpht->flags |= LVHT_TORIGHT;
7511 if (infoPtr->rcList.top > lpht->pt.y)
7512 lpht->flags |= LVHT_ABOVE;
7513 else if (infoPtr->rcList.bottom < lpht->pt.y)
7514 lpht->flags |= LVHT_BELOW;
7517 /* even if item is invalid try to find subitem */
7518 if (infoPtr->uView == LV_VIEW_DETAILS && subitem)
7523 opt.x = lpht->pt.x - Origin.x;
7525 lpht->iSubItem = -1;
7526 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
7528 pRect = &LISTVIEW_GetColumnInfo(infoPtr, j)->rcHeader;
7530 if ((opt.x >= pRect->left) && (opt.x < pRect->right))
7536 TRACE("lpht->iSubItem=%d\n", lpht->iSubItem);
7538 /* if we're outside horizontal columns bounds there's nothing to test further */
7539 if (lpht->iSubItem == -1)
7542 lpht->flags = LVHT_NOWHERE;
7547 TRACE("lpht->flags=0x%x\n", lpht->flags);
7548 if (lpht->flags) return -1;
7550 lpht->flags |= LVHT_NOWHERE;
7552 /* first deal with the large items */
7553 rcSearch.left = lpht->pt.x;
7554 rcSearch.top = lpht->pt.y;
7555 rcSearch.right = rcSearch.left + 1;
7556 rcSearch.bottom = rcSearch.top + 1;
7558 iterator_frameditems(&i, infoPtr, &rcSearch);
7559 iterator_next(&i); /* go to first item in the sequence */
7561 iterator_destroy(&i);
7563 TRACE("lpht->iItem=%d\n", iItem);
7564 if (iItem == -1) return -1;
7566 lvItem.mask = LVIF_STATE | LVIF_TEXT;
7567 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
7568 lvItem.stateMask = LVIS_STATEIMAGEMASK;
7569 if (infoPtr->uView == LV_VIEW_ICON) lvItem.stateMask |= LVIS_FOCUSED;
7570 lvItem.iItem = iItem;
7571 lvItem.iSubItem = subitem ? lpht->iSubItem : 0;
7572 lvItem.pszText = szDispText;
7573 lvItem.cchTextMax = DISP_TEXT_SIZE;
7574 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
7575 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
7577 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
7578 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
7579 opt.x = lpht->pt.x - Position.x - Origin.x;
7581 if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
7582 opt.y = lpht->pt.y - Position.y - Origin.y + infoPtr->rcList.top;
7584 opt.y = lpht->pt.y - Position.y - Origin.y;
7586 if (infoPtr->uView == LV_VIEW_DETAILS)
7589 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
7590 opt.x = lpht->pt.x - Origin.x;
7594 UnionRect(&rcBounds, &rcIcon, &rcLabel);
7595 UnionRect(&rcBounds, &rcBounds, &rcState);
7597 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
7598 if (!PtInRect(&rcBounds, opt)) return -1;
7600 if (PtInRect(&rcIcon, opt))
7601 lpht->flags |= LVHT_ONITEMICON;
7602 else if (PtInRect(&rcLabel, opt))
7603 lpht->flags |= LVHT_ONITEMLABEL;
7604 else if (infoPtr->himlState && PtInRect(&rcState, opt))
7605 lpht->flags |= LVHT_ONITEMSTATEICON;
7606 /* special case for LVS_EX_FULLROWSELECT */
7607 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
7608 !(lpht->flags & LVHT_ONITEM))
7610 lpht->flags = LVHT_ONITEM | LVHT_ABOVE;
7612 if (lpht->flags & LVHT_ONITEM)
7613 lpht->flags &= ~LVHT_NOWHERE;
7614 TRACE("lpht->flags=0x%x\n", lpht->flags);
7616 if (select && !(infoPtr->uView == LV_VIEW_DETAILS &&
7617 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
7618 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
7620 if (infoPtr->uView == LV_VIEW_DETAILS)
7622 /* get main item bounds */
7623 lvItem.iSubItem = 0;
7624 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
7625 UnionRect(&rcBounds, &rcIcon, &rcLabel);
7626 UnionRect(&rcBounds, &rcBounds, &rcState);
7628 if (!PtInRect(&rcBounds, opt)) iItem = -1;
7630 return lpht->iItem = iItem;
7635 * Inserts a new item in the listview control.
7638 * [I] infoPtr : valid pointer to the listview structure
7639 * [I] lpLVItem : item information
7640 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
7643 * SUCCESS : new item index
7646 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
7653 BOOL is_sorted, has_changed;
7655 HWND hwndSelf = infoPtr->hwndSelf;
7657 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
7659 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
7661 /* make sure it's an item, and not a subitem; cannot insert a subitem */
7662 if (!lpLVItem || lpLVItem->iSubItem) return -1;
7664 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
7666 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
7668 /* insert item in listview control data structure */
7669 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
7670 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
7672 /* link with id struct */
7673 if (!(lpID = Alloc(sizeof(ITEM_ID)))) goto fail;
7675 lpID->item = hdpaSubItems;
7676 lpID->id = get_next_itemid(infoPtr);
7677 if ( DPA_InsertPtr(infoPtr->hdpaItemIds, infoPtr->nItemCount, lpID) == -1) goto fail;
7679 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
7680 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
7682 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
7684 /* calculate new item index */
7691 while (i < infoPtr->nItemCount)
7693 hItem = DPA_GetPtr( infoPtr->hdpaItems, i);
7694 item_s = DPA_GetPtr(hItem, 0);
7696 cmpv = textcmpWT(item_s->hdr.pszText, lpLVItem->pszText, isW);
7697 if (infoPtr->dwStyle & LVS_SORTDESCENDING) cmpv *= -1;
7699 if (cmpv >= 0) break;
7705 nItem = min(lpLVItem->iItem, infoPtr->nItemCount);
7707 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
7708 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
7709 if (nItem == -1) goto fail;
7710 infoPtr->nItemCount++;
7712 /* shift indices first so they don't get tangled */
7713 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
7715 /* set the item attributes */
7716 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
7718 /* full size structure expected - _WIN32IE >= 0x560 */
7721 else if (lpLVItem->mask & LVIF_INDENT)
7723 /* indent member expected - _WIN32IE >= 0x300 */
7724 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
7728 /* minimal structure expected */
7729 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
7732 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7734 item.mask |= LVIF_STATE;
7735 item.stateMask |= LVIS_STATEIMAGEMASK;
7736 item.state &= ~LVIS_STATEIMAGEMASK;
7737 item.state |= INDEXTOSTATEIMAGEMASK(1);
7739 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
7741 /* make room for the position, if we are in the right mode */
7742 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7744 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
7746 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
7748 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
7753 /* send LVN_INSERTITEM notification */
7754 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
7756 nmlv.lParam = lpItem->lParam;
7757 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
7758 if (!IsWindow(hwndSelf))
7761 /* align items (set position of each item) */
7762 if (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON)
7766 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
7767 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
7769 LISTVIEW_NextIconPosTop(infoPtr, &pt);
7771 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
7774 /* now is the invalidation fun */
7775 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
7779 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
7780 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
7781 infoPtr->nItemCount--;
7783 DPA_DeletePtr(hdpaSubItems, 0);
7784 DPA_Destroy (hdpaSubItems);
7791 * Checks item visibility.
7794 * [I] infoPtr : valid pointer to the listview structure
7795 * [I] nFirst : item index to check for
7798 * Item visible : TRUE
7799 * Item invisible or failure : FALSE
7801 static BOOL LISTVIEW_IsItemVisible(const LISTVIEW_INFO *infoPtr, INT nItem)
7803 POINT Origin, Position;
7808 TRACE("nItem=%d\n", nItem);
7810 if (nItem < 0 || nItem >= DPA_GetPtrCount(infoPtr->hdpaItems)) return FALSE;
7812 LISTVIEW_GetOrigin(infoPtr, &Origin);
7813 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
7814 rcItem.left = Position.x + Origin.x;
7815 rcItem.top = Position.y + Origin.y;
7816 rcItem.right = rcItem.left + infoPtr->nItemWidth;
7817 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
7819 hdc = GetDC(infoPtr->hwndSelf);
7820 if (!hdc) return FALSE;
7821 ret = RectVisible(hdc, &rcItem);
7822 ReleaseDC(infoPtr->hwndSelf, hdc);
7829 * Redraws a range of items.
7832 * [I] infoPtr : valid pointer to the listview structure
7833 * [I] nFirst : first item
7834 * [I] nLast : last item
7840 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
7844 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
7845 max(nFirst, nLast) >= infoPtr->nItemCount)
7848 for (i = nFirst; i <= nLast; i++)
7849 LISTVIEW_InvalidateItem(infoPtr, i);
7856 * Scroll the content of a listview.
7859 * [I] infoPtr : valid pointer to the listview structure
7860 * [I] dx : horizontal scroll amount in pixels
7861 * [I] dy : vertical scroll amount in pixels
7868 * If the control is in report view (LV_VIEW_DETAILS) the control can
7869 * be scrolled only in line increments. "dy" will be rounded to the
7870 * nearest number of pixels that are a whole line. Ex: if line height
7871 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
7872 * is passed, then the scroll will be 0. (per MSDN 7/2002)
7874 * For: (per experimentation with native control and CSpy ListView)
7875 * LV_VIEW_ICON scrolling in any direction is allowed
7876 * LV_VIEW_SMALLICON scrolling in any direction is allowed
7877 * LV_VIEW_LIST dx=1 = 1 column (horizontal only)
7878 * but will only scroll 1 column per message
7879 * no matter what the value.
7880 * dy must be 0 or FALSE returned.
7881 * LV_VIEW_DETAILS dx=1 = 1 pixel
7885 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7887 switch(infoPtr->uView) {
7888 case LV_VIEW_DETAILS:
7889 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
7890 dy /= infoPtr->nItemHeight;
7893 if (dy != 0) return FALSE;
7899 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx);
7900 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy);
7907 * Sets the background color.
7910 * [I] infoPtr : valid pointer to the listview structure
7911 * [I] color : background color
7917 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
7919 TRACE("(color=%x)\n", color);
7921 if(infoPtr->clrBk != color) {
7922 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
7923 infoPtr->clrBk = color;
7924 if (color == CLR_NONE)
7925 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
7928 infoPtr->hBkBrush = CreateSolidBrush(color);
7929 infoPtr->dwLvExStyle &= ~LVS_EX_TRANSPARENTBKGND;
7936 /* LISTVIEW_SetBkImage */
7938 /*** Helper for {Insert,Set}ColumnT *only* */
7939 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
7940 const LVCOLUMNW *lpColumn, BOOL isW)
7942 if (lpColumn->mask & LVCF_FMT)
7944 /* format member is valid */
7945 lphdi->mask |= HDI_FORMAT;
7947 /* set text alignment (leftmost column must be left-aligned) */
7948 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
7949 lphdi->fmt |= HDF_LEFT;
7950 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
7951 lphdi->fmt |= HDF_RIGHT;
7952 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
7953 lphdi->fmt |= HDF_CENTER;
7955 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
7956 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
7958 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
7960 lphdi->fmt |= HDF_IMAGE;
7961 lphdi->iImage = I_IMAGECALLBACK;
7964 if (lpColumn->fmt & LVCFMT_FIXED_WIDTH)
7965 lphdi->fmt |= HDF_FIXEDWIDTH;
7968 if (lpColumn->mask & LVCF_WIDTH)
7970 lphdi->mask |= HDI_WIDTH;
7971 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
7973 /* make it fill the remainder of the controls width */
7977 for(item_index = 0; item_index < (nColumn - 1); item_index++)
7979 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
7980 lphdi->cxy += rcHeader.right - rcHeader.left;
7983 /* retrieve the layout of the header */
7984 GetClientRect(infoPtr->hwndSelf, &rcHeader);
7985 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
7987 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
7990 lphdi->cxy = lpColumn->cx;
7993 if (lpColumn->mask & LVCF_TEXT)
7995 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
7996 lphdi->fmt |= HDF_STRING;
7997 lphdi->pszText = lpColumn->pszText;
7998 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
8001 if (lpColumn->mask & LVCF_IMAGE)
8003 lphdi->mask |= HDI_IMAGE;
8004 lphdi->iImage = lpColumn->iImage;
8007 if (lpColumn->mask & LVCF_ORDER)
8009 lphdi->mask |= HDI_ORDER;
8010 lphdi->iOrder = lpColumn->iOrder;
8017 * Inserts a new column.
8020 * [I] infoPtr : valid pointer to the listview structure
8021 * [I] nColumn : column index
8022 * [I] lpColumn : column information
8023 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
8026 * SUCCESS : new column index
8029 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
8030 const LVCOLUMNW *lpColumn, BOOL isW)
8032 COLUMN_INFO *lpColumnInfo;
8036 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
8038 if (!lpColumn || nColumn < 0) return -1;
8039 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
8041 ZeroMemory(&hdi, sizeof(HDITEMW));
8042 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
8045 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
8046 * (can be seen in SPY) otherwise column never gets added.
8048 if (!(lpColumn->mask & LVCF_WIDTH)) {
8049 hdi.mask |= HDI_WIDTH;
8054 * when the iSubItem is available Windows copies it to the header lParam. It seems
8055 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
8057 if (lpColumn->mask & LVCF_SUBITEM)
8059 hdi.mask |= HDI_LPARAM;
8060 hdi.lParam = lpColumn->iSubItem;
8063 /* create header if not present */
8064 LISTVIEW_CreateHeader(infoPtr);
8065 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle) &&
8066 (infoPtr->uView == LV_VIEW_DETAILS) && (WS_VISIBLE & infoPtr->dwStyle))
8068 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8071 /* insert item in header control */
8072 nNewColumn = SendMessageW(infoPtr->hwndHeader,
8073 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
8074 nColumn, (LPARAM)&hdi);
8075 if (nNewColumn == -1) return -1;
8076 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
8078 /* create our own column info */
8079 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
8080 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
8082 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
8083 if (lpColumn->mask & LVCF_MINWIDTH) lpColumnInfo->cxMin = lpColumn->cxMin;
8084 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, nNewColumn, (LPARAM)&lpColumnInfo->rcHeader))
8087 /* now we have to actually adjust the data */
8088 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
8090 SUBITEM_INFO *lpSubItem;
8096 item.iSubItem = nNewColumn;
8097 item.mask = LVIF_TEXT | LVIF_IMAGE;
8098 item.iImage = I_IMAGECALLBACK;
8099 item.pszText = LPSTR_TEXTCALLBACKW;
8101 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
8103 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
8104 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
8106 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
8107 if (lpSubItem->iSubItem >= nNewColumn)
8108 lpSubItem->iSubItem++;
8111 /* add new subitem for each item */
8113 set_sub_item(infoPtr, &item, isW, &changed);
8117 /* make space for the new column */
8118 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8119 LISTVIEW_UpdateItemSize(infoPtr);
8124 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
8127 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
8135 * Sets the attributes of a header item.
8138 * [I] infoPtr : valid pointer to the listview structure
8139 * [I] nColumn : column index
8140 * [I] lpColumn : column attributes
8141 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
8147 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
8148 const LVCOLUMNW *lpColumn, BOOL isW)
8150 HDITEMW hdi, hdiget;
8153 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
8155 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
8157 ZeroMemory(&hdi, sizeof(HDITEMW));
8158 if (lpColumn->mask & LVCF_FMT)
8160 hdi.mask |= HDI_FORMAT;
8161 hdiget.mask = HDI_FORMAT;
8162 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdiget))
8163 hdi.fmt = hdiget.fmt & HDF_STRING;
8165 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
8167 /* set header item attributes */
8168 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, nColumn, (LPARAM)&hdi);
8169 if (!bResult) return FALSE;
8171 if (lpColumn->mask & LVCF_FMT)
8173 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
8174 INT oldFmt = lpColumnInfo->fmt;
8176 lpColumnInfo->fmt = lpColumn->fmt;
8177 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
8179 if (infoPtr->uView == LV_VIEW_DETAILS) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
8183 if (lpColumn->mask & LVCF_MINWIDTH)
8184 LISTVIEW_GetColumnInfo(infoPtr, nColumn)->cxMin = lpColumn->cxMin;
8191 * Sets the column order array
8194 * [I] infoPtr : valid pointer to the listview structure
8195 * [I] iCount : number of elements in column order array
8196 * [I] lpiArray : pointer to column order array
8202 static BOOL LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
8204 TRACE("iCount %d lpiArray %p\n", iCount, lpiArray);
8206 if (!lpiArray || !IsWindow(infoPtr->hwndHeader)) return FALSE;
8208 infoPtr->colRectsDirty = TRUE;
8210 return SendMessageW(infoPtr->hwndHeader, HDM_SETORDERARRAY, iCount, (LPARAM)lpiArray);
8215 * Sets the width of a column
8218 * [I] infoPtr : valid pointer to the listview structure
8219 * [I] nColumn : column index
8220 * [I] cx : column width
8226 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
8228 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
8232 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
8234 /* set column width only if in report or list mode */
8235 if (infoPtr->uView != LV_VIEW_DETAILS && infoPtr->uView != LV_VIEW_LIST) return FALSE;
8237 /* take care of invalid cx values */
8238 if(infoPtr->uView == LV_VIEW_DETAILS && cx < -2) cx = LVSCW_AUTOSIZE;
8239 else if (infoPtr->uView == LV_VIEW_LIST && cx < 1) return FALSE;
8241 /* resize all columns if in LV_VIEW_LIST mode */
8242 if(infoPtr->uView == LV_VIEW_LIST)
8244 infoPtr->nItemWidth = cx;
8245 LISTVIEW_InvalidateList(infoPtr);
8249 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
8251 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
8256 lvItem.mask = LVIF_TEXT;
8258 lvItem.iSubItem = nColumn;
8259 lvItem.pszText = szDispText;
8260 lvItem.cchTextMax = DISP_TEXT_SIZE;
8261 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
8263 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
8264 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
8265 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
8267 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
8268 max_cx += infoPtr->iconSize.cx;
8269 max_cx += TRAILING_LABEL_PADDING;
8272 /* autosize based on listview items width */
8273 if(cx == LVSCW_AUTOSIZE)
8275 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
8277 /* if iCol is the last column make it fill the remainder of the controls width */
8278 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
8283 LISTVIEW_GetOrigin(infoPtr, &Origin);
8284 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
8286 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
8290 /* Despite what the MS docs say, if this is not the last
8291 column, then MS resizes the column to the width of the
8292 largest text string in the column, including headers
8293 and items. This is different from LVSCW_AUTOSIZE in that
8294 LVSCW_AUTOSIZE ignores the header string length. */
8297 /* retrieve header text */
8298 hdi.mask = HDI_TEXT;
8299 hdi.cchTextMax = DISP_TEXT_SIZE;
8300 hdi.pszText = szDispText;
8301 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdi))
8303 HDC hdc = GetDC(infoPtr->hwndSelf);
8304 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
8307 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
8308 cx = size.cx + TRAILING_HEADER_PADDING;
8309 /* FIXME: Take into account the header image, if one is present */
8310 SelectObject(hdc, old_font);
8311 ReleaseDC(infoPtr->hwndSelf, hdc);
8313 cx = max (cx, max_cx);
8317 if (cx < 0) return FALSE;
8319 /* call header to update the column change */
8320 hdi.mask = HDI_WIDTH;
8321 hdi.cxy = max(cx, LISTVIEW_GetColumnInfo(infoPtr, nColumn)->cxMin);
8322 TRACE("hdi.cxy=%d\n", hdi.cxy);
8323 return SendMessageW(infoPtr->hwndHeader, HDM_SETITEMW, nColumn, (LPARAM)&hdi);
8327 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
8330 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
8333 HBITMAP hbm_im, hbm_mask, hbm_orig;
8335 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
8336 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
8339 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
8340 ILC_COLOR | ILC_MASK, 2, 2);
8341 hdc_wnd = GetDC(infoPtr->hwndSelf);
8342 hdc = CreateCompatibleDC(hdc_wnd);
8343 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
8344 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
8345 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
8347 rc.left = rc.top = 0;
8348 rc.right = GetSystemMetrics(SM_CXSMICON);
8349 rc.bottom = GetSystemMetrics(SM_CYSMICON);
8351 hbm_orig = SelectObject(hdc, hbm_mask);
8352 FillRect(hdc, &rc, hbr_white);
8353 InflateRect(&rc, -2, -2);
8354 FillRect(hdc, &rc, hbr_black);
8356 SelectObject(hdc, hbm_im);
8357 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
8358 SelectObject(hdc, hbm_orig);
8359 ImageList_Add(himl, hbm_im, hbm_mask);
8361 SelectObject(hdc, hbm_im);
8362 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
8363 SelectObject(hdc, hbm_orig);
8364 ImageList_Add(himl, hbm_im, hbm_mask);
8366 DeleteObject(hbm_mask);
8367 DeleteObject(hbm_im);
8375 * Sets the extended listview style.
8378 * [I] infoPtr : valid pointer to the listview structure
8380 * [I] dwStyle : style
8383 * SUCCESS : previous style
8386 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD mask, DWORD ex_style)
8388 DWORD old_ex_style = infoPtr->dwLvExStyle;
8390 TRACE("mask=0x%08x, ex_style=0x%08x\n", mask, ex_style);
8394 infoPtr->dwLvExStyle = (old_ex_style & ~mask) | (ex_style & mask);
8396 infoPtr->dwLvExStyle = ex_style;
8398 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_CHECKBOXES)
8400 HIMAGELIST himl = 0;
8401 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8404 item.mask = LVIF_STATE;
8405 item.stateMask = LVIS_STATEIMAGEMASK;
8406 item.state = INDEXTOSTATEIMAGEMASK(1);
8407 LISTVIEW_SetItemState(infoPtr, -1, &item);
8409 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
8410 if(!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8411 ImageList_Destroy(infoPtr->himlState);
8413 himl = LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
8414 /* checkbox list replaces previous custom list or... */
8415 if(((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) &&
8416 !(infoPtr->dwStyle & LVS_SHAREIMAGELISTS)) ||
8417 /* ...previous was checkbox list */
8418 (old_ex_style & LVS_EX_CHECKBOXES))
8419 ImageList_Destroy(himl);
8422 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_HEADERDRAGDROP)
8426 /* if not already created */
8427 LISTVIEW_CreateHeader(infoPtr);
8429 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
8430 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
8431 style |= HDS_DRAGDROP;
8433 style &= ~HDS_DRAGDROP;
8434 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style);
8437 /* GRIDLINES adds decoration at top so changes sizes */
8438 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_GRIDLINES)
8440 LISTVIEW_CreateHeader(infoPtr);
8441 LISTVIEW_UpdateSize(infoPtr);
8444 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_FULLROWSELECT)
8446 LISTVIEW_CreateHeader(infoPtr);
8449 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_TRANSPARENTBKGND)
8451 if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
8452 LISTVIEW_SetBkColor(infoPtr, CLR_NONE);
8455 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_HEADERINALLVIEWS)
8457 if (infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS)
8458 LISTVIEW_CreateHeader(infoPtr);
8460 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8461 LISTVIEW_UpdateSize(infoPtr);
8462 LISTVIEW_UpdateScroll(infoPtr);
8465 LISTVIEW_InvalidateList(infoPtr);
8466 return old_ex_style;
8471 * Sets the new hot cursor used during hot tracking and hover selection.
8474 * [I] infoPtr : valid pointer to the listview structure
8475 * [I] hCursor : the new hot cursor handle
8478 * Returns the previous hot cursor
8480 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
8482 HCURSOR oldCursor = infoPtr->hHotCursor;
8484 infoPtr->hHotCursor = hCursor;
8492 * Sets the hot item index.
8495 * [I] infoPtr : valid pointer to the listview structure
8496 * [I] iIndex : index
8499 * SUCCESS : previous hot item index
8500 * FAILURE : -1 (no hot item)
8502 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
8504 INT iOldIndex = infoPtr->nHotItem;
8506 infoPtr->nHotItem = iIndex;
8514 * Sets the amount of time the cursor must hover over an item before it is selected.
8517 * [I] infoPtr : valid pointer to the listview structure
8518 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
8521 * Returns the previous hover time
8523 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
8525 DWORD oldHoverTime = infoPtr->dwHoverTime;
8527 infoPtr->dwHoverTime = dwHoverTime;
8529 return oldHoverTime;
8534 * Sets spacing for icons of LVS_ICON style.
8537 * [I] infoPtr : valid pointer to the listview structure
8538 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
8539 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
8542 * MAKELONG(oldcx, oldcy)
8544 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
8546 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
8548 TRACE("requested=(%d,%d)\n", cx, cy);
8550 /* this is supported only for LVS_ICON style */
8551 if (infoPtr->uView != LV_VIEW_ICON) return oldspacing;
8553 /* set to defaults, if instructed to */
8554 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
8555 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
8557 /* if 0 then compute width
8558 * FIXME: computed cx and cy is not matching native behaviour */
8560 cx = GetSystemMetrics(SM_CXICONSPACING);
8561 if (infoPtr->iconSize.cx + ICON_LR_PADDING > cx)
8562 cx = infoPtr->iconSize.cx + ICON_LR_PADDING;
8565 /* if 0 then compute height */
8567 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
8568 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
8571 infoPtr->iconSpacing.cx = cx;
8572 infoPtr->iconSpacing.cy = cy;
8574 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
8575 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
8576 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
8577 infoPtr->ntmHeight);
8579 /* these depend on the iconSpacing */
8580 LISTVIEW_UpdateItemSize(infoPtr);
8585 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
8589 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
8596 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
8597 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
8606 * [I] infoPtr : valid pointer to the listview structure
8607 * [I] nType : image list type
8608 * [I] himl : image list handle
8611 * SUCCESS : old image list
8614 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
8616 INT oldHeight = infoPtr->nItemHeight;
8617 HIMAGELIST himlOld = 0;
8619 TRACE("(nType=%d, himl=%p\n", nType, himl);
8624 himlOld = infoPtr->himlNormal;
8625 infoPtr->himlNormal = himl;
8626 if (infoPtr->uView == LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
8627 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
8631 himlOld = infoPtr->himlSmall;
8632 infoPtr->himlSmall = himl;
8633 if (infoPtr->uView != LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
8637 himlOld = infoPtr->himlState;
8638 infoPtr->himlState = himl;
8639 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
8640 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
8644 ERR("Unknown icon type=%d\n", nType);
8648 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
8649 if (infoPtr->nItemHeight != oldHeight)
8650 LISTVIEW_UpdateScroll(infoPtr);
8657 * Preallocates memory (does *not* set the actual count of items !)
8660 * [I] infoPtr : valid pointer to the listview structure
8661 * [I] nItems : item count (projected number of items to allocate)
8662 * [I] dwFlags : update flags
8668 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
8670 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
8672 if (infoPtr->dwStyle & LVS_OWNERDATA)
8674 INT nOldCount = infoPtr->nItemCount;
8676 if (nItems < nOldCount)
8678 RANGE range = { nItems, nOldCount };
8679 ranges_del(infoPtr->selectionRanges, range);
8680 if (infoPtr->nFocusedItem >= nItems)
8682 LISTVIEW_SetItemFocus(infoPtr, -1);
8683 SetRectEmpty(&infoPtr->rcFocus);
8687 infoPtr->nItemCount = nItems;
8688 LISTVIEW_UpdateScroll(infoPtr);
8690 /* the flags are valid only in ownerdata report and list modes */
8691 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON) dwFlags = 0;
8693 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
8694 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
8696 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
8697 LISTVIEW_InvalidateList(infoPtr);
8704 LISTVIEW_GetOrigin(infoPtr, &Origin);
8705 nFrom = min(nOldCount, nItems);
8706 nTo = max(nOldCount, nItems);
8708 if (infoPtr->uView == LV_VIEW_DETAILS)
8711 rcErase.top = nFrom * infoPtr->nItemHeight;
8712 rcErase.right = infoPtr->nItemWidth;
8713 rcErase.bottom = nTo * infoPtr->nItemHeight;
8714 OffsetRect(&rcErase, Origin.x, Origin.y);
8715 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8716 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8718 else /* LV_VIEW_LIST */
8720 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
8722 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
8723 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
8724 rcErase.right = rcErase.left + infoPtr->nItemWidth;
8725 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
8726 OffsetRect(&rcErase, Origin.x, Origin.y);
8727 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8728 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8730 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
8732 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
8733 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
8734 OffsetRect(&rcErase, Origin.x, Origin.y);
8735 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8736 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8742 /* According to MSDN for non-LVS_OWNERDATA this is just
8743 * a performance issue. The control allocates its internal
8744 * data structures for the number of items specified. It
8745 * cuts down on the number of memory allocations. Therefore
8746 * we will just issue a WARN here
8748 WARN("for non-ownerdata performance option not implemented.\n");
8756 * Sets the position of an item.
8759 * [I] infoPtr : valid pointer to the listview structure
8760 * [I] nItem : item index
8761 * [I] pt : coordinate
8767 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, const POINT *pt)
8771 TRACE("(nItem=%d, pt=%s\n", nItem, wine_dbgstr_point(pt));
8773 if (!pt || nItem < 0 || nItem >= infoPtr->nItemCount ||
8774 !(infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)) return FALSE;
8777 LISTVIEW_GetOrigin(infoPtr, &Origin);
8779 /* This point value seems to be an undocumented feature.
8780 * The best guess is that it means either at the origin,
8781 * or at true beginning of the list. I will assume the origin. */
8782 if ((Pt.x == -1) && (Pt.y == -1))
8785 if (infoPtr->uView == LV_VIEW_ICON)
8787 Pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
8788 Pt.y -= ICON_TOP_PADDING;
8793 infoPtr->bAutoarrange = FALSE;
8795 return LISTVIEW_MoveIconTo(infoPtr, nItem, &Pt, FALSE);
8800 * Sets the state of one or many items.
8803 * [I] infoPtr : valid pointer to the listview structure
8804 * [I] nItem : item index
8805 * [I] item : item or subitem info
8811 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *item)
8816 if (!item) return FALSE;
8818 lvItem.iItem = nItem;
8819 lvItem.iSubItem = 0;
8820 lvItem.mask = LVIF_STATE;
8821 lvItem.state = item->state;
8822 lvItem.stateMask = item->stateMask;
8823 TRACE("item=%s\n", debuglvitem_t(&lvItem, TRUE));
8830 /* select all isn't allowed in LVS_SINGLESEL */
8831 if ((lvItem.state & lvItem.stateMask & LVIS_SELECTED) && (infoPtr->dwStyle & LVS_SINGLESEL))
8834 /* focus all isn't allowed */
8835 if (lvItem.state & lvItem.stateMask & LVIS_FOCUSED) return FALSE;
8837 notify = infoPtr->bDoChangeNotify;
8838 if (infoPtr->dwStyle & LVS_OWNERDATA)
8840 infoPtr->bDoChangeNotify = FALSE;
8841 if (!(lvItem.state & LVIS_SELECTED) && LISTVIEW_GetSelectedCount(infoPtr))
8842 oldstate |= LVIS_SELECTED;
8843 if (infoPtr->nFocusedItem != -1) oldstate |= LVIS_FOCUSED;
8846 /* apply to all items */
8847 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
8848 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) ret = FALSE;
8850 if (infoPtr->dwStyle & LVS_OWNERDATA)
8854 infoPtr->bDoChangeNotify = notify;
8858 nmlv.uNewState = lvItem.state & lvItem.stateMask;
8859 nmlv.uOldState = oldstate & lvItem.stateMask;
8860 nmlv.uChanged = LVIF_STATE;
8861 nmlv.ptAction.x = nmlv.ptAction.y = 0;
8864 notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
8868 ret = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
8875 * Sets the text of an item or subitem.
8878 * [I] hwnd : window handle
8879 * [I] nItem : item index
8880 * [I] lpLVItem : item or subitem info
8881 * [I] isW : TRUE if input is Unicode
8887 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
8891 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
8892 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
8894 lvItem.iItem = nItem;
8895 lvItem.iSubItem = lpLVItem->iSubItem;
8896 lvItem.mask = LVIF_TEXT;
8897 lvItem.pszText = lpLVItem->pszText;
8898 lvItem.cchTextMax = lpLVItem->cchTextMax;
8900 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
8902 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
8907 * Set item index that marks the start of a multiple selection.
8910 * [I] infoPtr : valid pointer to the listview structure
8911 * [I] nIndex : index
8914 * Index number or -1 if there is no selection mark.
8916 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
8918 INT nOldIndex = infoPtr->nSelectionMark;
8920 TRACE("(nIndex=%d)\n", nIndex);
8922 infoPtr->nSelectionMark = nIndex;
8929 * Sets the text background color.
8932 * [I] infoPtr : valid pointer to the listview structure
8933 * [I] color : text background color
8939 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
8941 TRACE("(color=%x)\n", color);
8943 infoPtr->clrTextBk = color;
8949 * Sets the text foreground color.
8952 * [I] infoPtr : valid pointer to the listview structure
8953 * [I] color : text color
8959 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF color)
8961 TRACE("(color=%x)\n", color);
8963 infoPtr->clrText = color;
8969 * Sets new ToolTip window to ListView control.
8972 * [I] infoPtr : valid pointer to the listview structure
8973 * [I] hwndNewToolTip : handle to new ToolTip
8978 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
8980 HWND hwndOldToolTip = infoPtr->hwndToolTip;
8981 infoPtr->hwndToolTip = hwndNewToolTip;
8982 return hwndOldToolTip;
8987 * sets the Unicode character format flag for the control
8989 * [I] infoPtr :valid pointer to the listview structure
8990 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
8993 * Old Unicode Format
8995 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL unicode)
8997 SHORT rc = infoPtr->notifyFormat;
8998 infoPtr->notifyFormat = (unicode) ? NFR_UNICODE : NFR_ANSI;
8999 return rc == NFR_UNICODE;
9004 * sets the control view mode
9006 * [I] infoPtr :valid pointer to the listview structure
9007 * [I] nView :new view mode value
9013 static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView)
9015 SIZE oldIconSize = infoPtr->iconSize;
9018 if (infoPtr->uView == nView) return 1;
9020 if ((INT)nView < 0 || nView > LV_VIEW_MAX) return -1;
9021 if (nView == LV_VIEW_TILE)
9023 FIXME("View LV_VIEW_TILE unimplemented\n");
9027 infoPtr->uView = nView;
9029 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9030 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9032 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9033 SetRectEmpty(&infoPtr->rcFocus);
9035 himl = (nView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9036 set_icon_size(&infoPtr->iconSize, himl, nView != LV_VIEW_ICON);
9041 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9043 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9044 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9045 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9047 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9049 case LV_VIEW_SMALLICON:
9050 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9052 case LV_VIEW_DETAILS:
9057 LISTVIEW_CreateHeader( infoPtr );
9059 hl.prc = &infoPtr->rcList;
9061 SendMessageW(infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl);
9062 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9063 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9070 LISTVIEW_UpdateItemSize(infoPtr);
9071 LISTVIEW_UpdateSize(infoPtr);
9072 LISTVIEW_UpdateScroll(infoPtr);
9073 LISTVIEW_InvalidateList(infoPtr);
9075 TRACE("nView=%d\n", nView);
9080 /* LISTVIEW_SetWorkAreas */
9084 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMS
9087 * [I] first : pointer to first ITEM_INFO to compare
9088 * [I] second : pointer to second ITEM_INFO to compare
9089 * [I] lParam : HWND of control
9092 * if first comes before second : negative
9093 * if first comes after second : positive
9094 * if first and second are equivalent : zero
9096 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
9098 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
9099 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
9100 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
9102 /* Forward the call to the client defined callback */
9103 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
9108 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMSEX
9111 * [I] first : pointer to first ITEM_INFO to compare
9112 * [I] second : pointer to second ITEM_INFO to compare
9113 * [I] lParam : HWND of control
9116 * if first comes before second : negative
9117 * if first comes after second : positive
9118 * if first and second are equivalent : zero
9120 static INT WINAPI LISTVIEW_CallBackCompareEx(LPVOID first, LPVOID second, LPARAM lParam)
9122 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
9123 INT first_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, first );
9124 INT second_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, second );
9126 /* Forward the call to the client defined callback */
9127 return (infoPtr->pfnCompare)( first_idx, second_idx, infoPtr->lParamSort );
9132 * Sorts the listview items.
9135 * [I] infoPtr : valid pointer to the listview structure
9136 * [I] pfnCompare : application-defined value
9137 * [I] lParamSort : pointer to comparison callback
9138 * [I] IsEx : TRUE when LVM_SORTITEMSEX used
9144 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare,
9145 LPARAM lParamSort, BOOL IsEx)
9149 LPVOID selectionMarkItem = NULL;
9150 LPVOID focusedItem = NULL;
9153 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
9155 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
9157 if (!pfnCompare) return FALSE;
9158 if (!infoPtr->hdpaItems) return FALSE;
9160 /* if there are 0 or 1 items, there is no need to sort */
9161 if (infoPtr->nItemCount < 2) return TRUE;
9163 /* clear selection */
9164 ranges_clear(infoPtr->selectionRanges);
9166 /* save selection mark and focused item */
9167 if (infoPtr->nSelectionMark >= 0)
9168 selectionMarkItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark);
9169 if (infoPtr->nFocusedItem >= 0)
9170 focusedItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
9172 infoPtr->pfnCompare = pfnCompare;
9173 infoPtr->lParamSort = lParamSort;
9175 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompareEx, (LPARAM)infoPtr);
9177 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
9179 /* restore selection ranges */
9180 for (i=0; i < infoPtr->nItemCount; i++)
9182 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
9183 lpItem = DPA_GetPtr(hdpaSubItems, 0);
9185 if (lpItem->state & LVIS_SELECTED)
9186 ranges_additem(infoPtr->selectionRanges, i);
9188 /* restore selection mark and focused item */
9189 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
9190 infoPtr->nFocusedItem = DPA_GetPtrIndex(infoPtr->hdpaItems, focusedItem);
9192 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
9194 /* refresh the display */
9195 LISTVIEW_InvalidateList(infoPtr);
9201 * Update theme handle after a theme change.
9204 * [I] infoPtr : valid pointer to the listview structure
9208 * FAILURE : something else
9210 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
9212 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
9213 CloseThemeData(theme);
9214 OpenThemeData(infoPtr->hwndSelf, themeClass);
9220 * Updates an items or rearranges the listview control.
9223 * [I] infoPtr : valid pointer to the listview structure
9224 * [I] nItem : item index
9230 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
9232 TRACE("(nItem=%d)\n", nItem);
9234 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
9236 /* rearrange with default alignment style */
9237 if (is_autoarrange(infoPtr))
9238 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9240 LISTVIEW_InvalidateItem(infoPtr, nItem);
9247 * Draw the track line at the place defined in the infoPtr structure.
9248 * The line is drawn with a XOR pen so drawing the line for the second time
9249 * in the same place erases the line.
9252 * [I] infoPtr : valid pointer to the listview structure
9258 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
9262 if (infoPtr->xTrackLine == -1)
9265 if (!(hdc = GetDC(infoPtr->hwndSelf)))
9267 PatBlt( hdc, infoPtr->xTrackLine, infoPtr->rcList.top,
9268 1, infoPtr->rcList.bottom - infoPtr->rcList.top, DSTINVERT );
9269 ReleaseDC(infoPtr->hwndSelf, hdc);
9275 * Called when an edit control should be displayed. This function is called after
9276 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
9279 * [I] hwnd : Handle to the listview
9280 * [I] uMsg : WM_TIMER (ignored)
9281 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
9282 * [I] dwTimer : The elapsed time (ignored)
9287 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
9289 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
9290 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9292 KillTimer(hwnd, idEvent);
9293 editItem->fEnabled = FALSE;
9294 /* check if the item is still selected */
9295 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
9296 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
9301 * Creates the listview control - the WM_NCCREATE phase.
9304 * [I] hwnd : window handle
9305 * [I] lpcs : the create parameters
9311 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
9313 LISTVIEW_INFO *infoPtr;
9316 TRACE("(lpcs=%p)\n", lpcs);
9318 /* initialize info pointer */
9319 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
9320 if (!infoPtr) return FALSE;
9322 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
9324 infoPtr->hwndSelf = hwnd;
9325 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
9326 map_style_view(infoPtr);
9327 /* determine the type of structures to use */
9328 infoPtr->hwndNotify = lpcs->hwndParent;
9329 /* infoPtr->notifyFormat will be filled in WM_CREATE */
9331 /* initialize color information */
9332 infoPtr->clrBk = CLR_NONE;
9333 infoPtr->clrText = CLR_DEFAULT;
9334 infoPtr->clrTextBk = CLR_DEFAULT;
9335 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
9337 /* set default values */
9338 infoPtr->nFocusedItem = -1;
9339 infoPtr->nSelectionMark = -1;
9340 infoPtr->nHotItem = -1;
9341 infoPtr->bRedraw = TRUE;
9342 infoPtr->bNoItemMetrics = TRUE;
9343 infoPtr->bDoChangeNotify = TRUE;
9344 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
9345 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
9346 infoPtr->nEditLabelItem = -1;
9347 infoPtr->nLButtonDownItem = -1;
9348 infoPtr->dwHoverTime = HOVER_DEFAULT; /* default system hover time */
9349 infoPtr->nMeasureItemHeight = 0;
9350 infoPtr->xTrackLine = -1; /* no track line */
9351 infoPtr->itemEdit.fEnabled = FALSE;
9352 infoPtr->iVersion = COMCTL32_VERSION;
9353 infoPtr->colRectsDirty = FALSE;
9355 /* get default font (icon title) */
9356 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
9357 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
9358 infoPtr->hFont = infoPtr->hDefaultFont;
9359 LISTVIEW_SaveTextMetrics(infoPtr);
9361 /* allocate memory for the data structure */
9362 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
9363 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
9364 if (!(infoPtr->hdpaItemIds = DPA_Create(10))) goto fail;
9365 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
9366 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
9367 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
9371 DestroyWindow(infoPtr->hwndHeader);
9372 ranges_destroy(infoPtr->selectionRanges);
9373 DPA_Destroy(infoPtr->hdpaItems);
9374 DPA_Destroy(infoPtr->hdpaItemIds);
9375 DPA_Destroy(infoPtr->hdpaPosX);
9376 DPA_Destroy(infoPtr->hdpaPosY);
9377 DPA_Destroy(infoPtr->hdpaColumns);
9384 * Creates the listview control - the WM_CREATE phase. Most of the data is
9385 * already set up in LISTVIEW_NCCreate
9388 * [I] hwnd : window handle
9389 * [I] lpcs : the create parameters
9395 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
9397 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9399 TRACE("(lpcs=%p, style=0x%08x)\n", lpcs, lpcs->style);
9401 infoPtr->dwStyle = lpcs->style;
9402 map_style_view(infoPtr);
9404 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
9405 (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9406 /* on error defaulting to ANSI notifications */
9407 if (infoPtr->notifyFormat == 0) infoPtr->notifyFormat = NFR_ANSI;
9408 TRACE("notify format=%d\n", infoPtr->notifyFormat);
9410 if ((infoPtr->uView == LV_VIEW_DETAILS) && (lpcs->style & WS_VISIBLE))
9412 if (LISTVIEW_CreateHeader(infoPtr) < 0) return -1;
9415 infoPtr->hwndHeader = 0;
9417 /* init item size to avoid division by 0 */
9418 LISTVIEW_UpdateItemSize (infoPtr);
9419 LISTVIEW_UpdateSize (infoPtr);
9421 if (infoPtr->uView == LV_VIEW_DETAILS)
9423 if (!(LVS_NOCOLUMNHEADER & lpcs->style) && (WS_VISIBLE & lpcs->style))
9425 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
9427 LISTVIEW_UpdateScroll(infoPtr);
9428 /* send WM_MEASUREITEM notification */
9429 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED) notify_measureitem(infoPtr);
9432 OpenThemeData(hwnd, themeClass);
9434 /* initialize the icon sizes */
9435 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, infoPtr->uView != LV_VIEW_ICON);
9436 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
9442 * Destroys the listview control.
9445 * [I] infoPtr : valid pointer to the listview structure
9451 static LRESULT LISTVIEW_Destroy(LISTVIEW_INFO *infoPtr)
9453 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
9454 CloseThemeData(theme);
9456 /* delete all items */
9457 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
9464 * Enables the listview control.
9467 * [I] infoPtr : valid pointer to the listview structure
9468 * [I] bEnable : specifies whether to enable or disable the window
9474 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr)
9476 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
9477 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
9483 * Erases the background of the listview control.
9486 * [I] infoPtr : valid pointer to the listview structure
9487 * [I] hdc : device context handle
9493 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
9497 TRACE("(hdc=%p)\n", hdc);
9499 if (!GetClipBox(hdc, &rc)) return FALSE;
9501 if (infoPtr->clrBk == CLR_NONE)
9503 if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
9504 return SendMessageW(infoPtr->hwndNotify, WM_PRINTCLIENT,
9505 (WPARAM)hdc, PRF_ERASEBKGND);
9507 return SendMessageW(infoPtr->hwndNotify, WM_ERASEBKGND, (WPARAM)hdc, 0);
9510 /* for double buffered controls we need to do this during refresh */
9511 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
9513 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
9519 * Helper function for LISTVIEW_[HV]Scroll *only*.
9520 * Performs vertical/horizontal scrolling by a give amount.
9523 * [I] infoPtr : valid pointer to the listview structure
9524 * [I] dx : amount of horizontal scroll
9525 * [I] dy : amount of vertical scroll
9527 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
9529 /* now we can scroll the list */
9530 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
9531 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
9532 /* if we have focus, adjust rect */
9533 OffsetRect(&infoPtr->rcFocus, dx, dy);
9534 UpdateWindow(infoPtr->hwndSelf);
9539 * Performs vertical scrolling.
9542 * [I] infoPtr : valid pointer to the listview structure
9543 * [I] nScrollCode : scroll code
9544 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
9545 * [I] hScrollWnd : scrollbar control window handle
9551 * SB_LINEUP/SB_LINEDOWN:
9552 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
9553 * for LVS_REPORT is 1 line
9554 * for LVS_LIST cannot occur
9557 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
9560 INT nOldScrollPos, nNewScrollPos;
9561 SCROLLINFO scrollInfo;
9564 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
9565 debugscrollcode(nScrollCode), nScrollDiff);
9567 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9569 scrollInfo.cbSize = sizeof(SCROLLINFO);
9570 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
9572 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
9574 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
9576 nOldScrollPos = scrollInfo.nPos;
9577 switch (nScrollCode)
9583 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
9587 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
9591 nScrollDiff = -scrollInfo.nPage;
9595 nScrollDiff = scrollInfo.nPage;
9598 case SB_THUMBPOSITION:
9600 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
9607 /* quit right away if pos isn't changing */
9608 if (nScrollDiff == 0) return 0;
9610 /* calculate new position, and handle overflows */
9611 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
9612 if (nScrollDiff > 0) {
9613 if (nNewScrollPos < nOldScrollPos ||
9614 nNewScrollPos > scrollInfo.nMax)
9615 nNewScrollPos = scrollInfo.nMax;
9617 if (nNewScrollPos > nOldScrollPos ||
9618 nNewScrollPos < scrollInfo.nMin)
9619 nNewScrollPos = scrollInfo.nMin;
9622 /* set the new position, and reread in case it changed */
9623 scrollInfo.fMask = SIF_POS;
9624 scrollInfo.nPos = nNewScrollPos;
9625 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
9627 /* carry on only if it really changed */
9628 if (nNewScrollPos == nOldScrollPos) return 0;
9630 /* now adjust to client coordinates */
9631 nScrollDiff = nOldScrollPos - nNewScrollPos;
9632 if (infoPtr->uView == LV_VIEW_DETAILS) nScrollDiff *= infoPtr->nItemHeight;
9634 /* and scroll the window */
9635 scroll_list(infoPtr, 0, nScrollDiff);
9642 * Performs horizontal scrolling.
9645 * [I] infoPtr : valid pointer to the listview structure
9646 * [I] nScrollCode : scroll code
9647 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
9648 * [I] hScrollWnd : scrollbar control window handle
9654 * SB_LINELEFT/SB_LINERIGHT:
9655 * for LVS_ICON, LVS_SMALLICON 1 pixel
9656 * for LVS_REPORT is 1 pixel
9657 * for LVS_LIST is 1 column --> which is a 1 because the
9658 * scroll is based on columns not pixels
9661 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
9664 INT nOldScrollPos, nNewScrollPos;
9665 SCROLLINFO scrollInfo;
9668 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
9669 debugscrollcode(nScrollCode), nScrollDiff);
9671 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9673 scrollInfo.cbSize = sizeof(SCROLLINFO);
9674 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
9676 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
9678 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
9680 nOldScrollPos = scrollInfo.nPos;
9682 switch (nScrollCode)
9688 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
9692 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
9696 nScrollDiff = -scrollInfo.nPage;
9700 nScrollDiff = scrollInfo.nPage;
9703 case SB_THUMBPOSITION:
9705 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
9712 /* quit right away if pos isn't changing */
9713 if (nScrollDiff == 0) return 0;
9715 /* calculate new position, and handle overflows */
9716 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
9717 if (nScrollDiff > 0) {
9718 if (nNewScrollPos < nOldScrollPos ||
9719 nNewScrollPos > scrollInfo.nMax)
9720 nNewScrollPos = scrollInfo.nMax;
9722 if (nNewScrollPos > nOldScrollPos ||
9723 nNewScrollPos < scrollInfo.nMin)
9724 nNewScrollPos = scrollInfo.nMin;
9727 /* set the new position, and reread in case it changed */
9728 scrollInfo.fMask = SIF_POS;
9729 scrollInfo.nPos = nNewScrollPos;
9730 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
9732 /* carry on only if it really changed */
9733 if (nNewScrollPos == nOldScrollPos) return 0;
9735 if (infoPtr->hwndHeader) LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
9737 /* now adjust to client coordinates */
9738 nScrollDiff = nOldScrollPos - nNewScrollPos;
9739 if (infoPtr->uView == LV_VIEW_LIST) nScrollDiff *= infoPtr->nItemWidth;
9741 /* and scroll the window */
9742 scroll_list(infoPtr, nScrollDiff, 0);
9747 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
9749 INT gcWheelDelta = 0;
9750 INT pulScrollLines = 3;
9752 TRACE("(wheelDelta=%d)\n", wheelDelta);
9754 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
9755 gcWheelDelta -= wheelDelta;
9757 switch(infoPtr->uView)
9760 case LV_VIEW_SMALLICON:
9762 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
9763 * should be fixed in the future.
9765 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
9766 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE);
9769 case LV_VIEW_DETAILS:
9770 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
9772 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
9773 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
9774 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll);
9779 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0);
9790 * [I] infoPtr : valid pointer to the listview structure
9791 * [I] nVirtualKey : virtual key
9792 * [I] lKeyData : key data
9797 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
9799 HWND hwndSelf = infoPtr->hwndSelf;
9801 NMLVKEYDOWN nmKeyDown;
9803 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
9805 /* send LVN_KEYDOWN notification */
9806 nmKeyDown.wVKey = nVirtualKey;
9807 nmKeyDown.flags = 0;
9808 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
9809 if (!IsWindow(hwndSelf))
9812 switch (nVirtualKey)
9815 nItem = infoPtr->nFocusedItem;
9816 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
9817 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
9821 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
9823 if (!notify(infoPtr, NM_RETURN)) return 0;
9824 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
9829 if (infoPtr->nItemCount > 0)
9834 if (infoPtr->nItemCount > 0)
9835 nItem = infoPtr->nItemCount - 1;
9839 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TOLEFT);
9843 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_ABOVE);
9847 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TORIGHT);
9851 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_BELOW);
9855 if (infoPtr->uView == LV_VIEW_DETAILS)
9857 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
9858 if (infoPtr->nFocusedItem == topidx)
9859 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
9864 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
9865 * LISTVIEW_GetCountPerRow(infoPtr);
9866 if(nItem < 0) nItem = 0;
9870 if (infoPtr->uView == LV_VIEW_DETAILS)
9872 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
9873 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
9874 if (infoPtr->nFocusedItem == topidx + cnt - 1)
9875 nItem = infoPtr->nFocusedItem + cnt - 1;
9877 nItem = topidx + cnt - 1;
9880 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
9881 * LISTVIEW_GetCountPerRow(infoPtr);
9882 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
9886 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
9887 LISTVIEW_KeySelection(infoPtr, nItem, nVirtualKey == VK_SPACE);
9897 * [I] infoPtr : valid pointer to the listview structure
9902 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
9906 /* if we did not have the focus, there's nothing to do */
9907 if (!infoPtr->bFocus) return 0;
9909 /* send NM_KILLFOCUS notification */
9910 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
9912 /* if we have a focus rectangle, get rid of it */
9913 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
9915 /* if have a marquee selection, stop it */
9916 if (infoPtr->bMarqueeSelect)
9918 /* Remove the marquee rectangle and release our mouse capture */
9919 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeRect);
9922 SetRect(&infoPtr->marqueeRect, 0, 0, 0, 0);
9924 infoPtr->bMarqueeSelect = FALSE;
9925 infoPtr->bScrolling = FALSE;
9926 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
9929 /* set window focus flag */
9930 infoPtr->bFocus = FALSE;
9932 /* invalidate the selected items before resetting focus flag */
9933 LISTVIEW_InvalidateSelectedItems(infoPtr);
9940 * Processes double click messages (left mouse button).
9943 * [I] infoPtr : valid pointer to the listview structure
9944 * [I] wKey : key flag
9945 * [I] x,y : mouse coordinate
9950 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9952 LVHITTESTINFO htInfo;
9954 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
9956 /* Cancel the item edition if any */
9957 if (infoPtr->itemEdit.fEnabled)
9959 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
9960 infoPtr->itemEdit.fEnabled = FALSE;
9963 /* send NM_RELEASEDCAPTURE notification */
9964 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9969 /* send NM_DBLCLK notification */
9970 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
9971 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
9973 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
9974 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
9981 * Processes mouse down messages (left mouse button).
9984 * infoPtr [I ] valid pointer to the listview structure
9985 * wKey [I ] key flag
9986 * x,y [I ] mouse coordinate
9991 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9993 LVHITTESTINFO lvHitTestInfo;
9994 static BOOL bGroupSelect = TRUE;
9995 POINT pt = { x, y };
9998 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10000 /* send NM_RELEASEDCAPTURE notification */
10001 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10003 /* set left button down flag and record the click position */
10004 infoPtr->bLButtonDown = TRUE;
10005 infoPtr->ptClickPos = pt;
10006 infoPtr->bDragging = FALSE;
10007 infoPtr->bMarqueeSelect = FALSE;
10008 infoPtr->bScrolling = FALSE;
10010 lvHitTestInfo.pt.x = x;
10011 lvHitTestInfo.pt.y = y;
10013 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
10014 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
10015 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
10017 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
10019 toggle_checkbox_state(infoPtr, nItem);
10023 if (infoPtr->dwStyle & LVS_SINGLESEL)
10025 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10026 infoPtr->nEditLabelItem = nItem;
10028 LISTVIEW_SetSelection(infoPtr, nItem);
10032 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
10036 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
10037 LISTVIEW_SetItemFocus(infoPtr, nItem);
10038 infoPtr->nSelectionMark = nItem;
10044 item.state = LVIS_SELECTED | LVIS_FOCUSED;
10045 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
10047 LISTVIEW_SetItemState(infoPtr,nItem,&item);
10048 infoPtr->nSelectionMark = nItem;
10051 else if (wKey & MK_CONTROL)
10055 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
10057 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
10058 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
10059 LISTVIEW_SetItemState(infoPtr, nItem, &item);
10060 infoPtr->nSelectionMark = nItem;
10062 else if (wKey & MK_SHIFT)
10064 LISTVIEW_SetGroupSelection(infoPtr, nItem);
10068 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10070 infoPtr->nEditLabelItem = nItem;
10071 infoPtr->nLButtonDownItem = nItem;
10073 LISTVIEW_SetItemFocus(infoPtr, nItem);
10076 /* set selection (clears other pre-existing selections) */
10077 LISTVIEW_SetSelection(infoPtr, nItem);
10081 if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
10082 if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
10086 if (!infoPtr->bFocus)
10087 SetFocus(infoPtr->hwndSelf);
10089 /* remove all selections */
10090 if (!(wKey & MK_CONTROL) && !(wKey & MK_SHIFT))
10091 LISTVIEW_DeselectAll(infoPtr);
10100 * Processes mouse up messages (left mouse button).
10103 * infoPtr [I ] valid pointer to the listview structure
10104 * wKey [I ] key flag
10105 * x,y [I ] mouse coordinate
10110 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10112 LVHITTESTINFO lvHitTestInfo;
10114 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10116 if (!infoPtr->bLButtonDown) return 0;
10118 lvHitTestInfo.pt.x = x;
10119 lvHitTestInfo.pt.y = y;
10121 /* send NM_CLICK notification */
10122 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10123 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
10125 /* set left button flag */
10126 infoPtr->bLButtonDown = FALSE;
10128 /* set a single selection, reset others */
10129 if(lvHitTestInfo.iItem == infoPtr->nLButtonDownItem && lvHitTestInfo.iItem != -1)
10130 LISTVIEW_SetSelection(infoPtr, infoPtr->nLButtonDownItem);
10131 infoPtr->nLButtonDownItem = -1;
10133 if (infoPtr->bDragging || infoPtr->bMarqueeSelect)
10135 /* Remove the marquee rectangle and release our mouse capture */
10136 if (infoPtr->bMarqueeSelect)
10138 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
10142 SetRect(&infoPtr->marqueeRect, 0, 0, 0, 0);
10143 SetRect(&infoPtr->marqueeDrawRect, 0, 0, 0, 0);
10145 infoPtr->bDragging = FALSE;
10146 infoPtr->bMarqueeSelect = FALSE;
10147 infoPtr->bScrolling = FALSE;
10149 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
10153 /* if we clicked on a selected item, edit the label */
10154 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
10156 /* we want to make sure the user doesn't want to do a double click. So we will
10157 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
10159 infoPtr->itemEdit.fEnabled = TRUE;
10160 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
10161 SetTimer(infoPtr->hwndSelf,
10162 (UINT_PTR)&infoPtr->itemEdit,
10163 GetDoubleClickTime(),
10164 LISTVIEW_DelayedEditItem);
10167 if (!infoPtr->bFocus)
10168 SetFocus(infoPtr->hwndSelf);
10175 * Destroys the listview control (called after WM_DESTROY).
10178 * [I] infoPtr : valid pointer to the listview structure
10183 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
10189 /* destroy data structure */
10190 DPA_Destroy(infoPtr->hdpaItems);
10191 DPA_Destroy(infoPtr->hdpaItemIds);
10192 DPA_Destroy(infoPtr->hdpaPosX);
10193 DPA_Destroy(infoPtr->hdpaPosY);
10195 for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++)
10196 Free(DPA_GetPtr(infoPtr->hdpaColumns, i));
10197 DPA_Destroy(infoPtr->hdpaColumns);
10198 ranges_destroy(infoPtr->selectionRanges);
10200 /* destroy image lists */
10201 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
10203 ImageList_Destroy(infoPtr->himlNormal);
10204 ImageList_Destroy(infoPtr->himlSmall);
10205 ImageList_Destroy(infoPtr->himlState);
10208 /* destroy font, bkgnd brush */
10209 infoPtr->hFont = 0;
10210 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
10211 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
10213 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
10215 /* free listview info pointer*/
10223 * Handles notifications.
10226 * [I] infoPtr : valid pointer to the listview structure
10227 * [I] lpnmhdr : notification information
10232 static LRESULT LISTVIEW_Notify(LISTVIEW_INFO *infoPtr, NMHDR *lpnmhdr)
10236 TRACE("(lpnmhdr=%p)\n", lpnmhdr);
10238 if (!lpnmhdr || lpnmhdr->hwndFrom != infoPtr->hwndHeader) return 0;
10240 /* remember: HDN_LAST < HDN_FIRST */
10241 if (lpnmhdr->code > HDN_FIRST || lpnmhdr->code < HDN_LAST) return 0;
10242 lpnmh = (NMHEADERW *)lpnmhdr;
10244 if (lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
10246 switch (lpnmhdr->code)
10251 COLUMN_INFO *lpColumnInfo;
10255 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
10258 /* remove the old line (if any) */
10259 LISTVIEW_DrawTrackLine(infoPtr);
10261 /* compute & draw the new line */
10262 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
10263 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
10264 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
10265 infoPtr->xTrackLine = x + ptOrigin.x;
10266 LISTVIEW_DrawTrackLine(infoPtr);
10267 return notify_forward_header(infoPtr, lpnmh);
10270 case HDN_ENDTRACKA:
10271 case HDN_ENDTRACKW:
10272 /* remove the track line (if any) */
10273 LISTVIEW_DrawTrackLine(infoPtr);
10274 infoPtr->xTrackLine = -1;
10275 return notify_forward_header(infoPtr, lpnmh);
10277 case HDN_BEGINDRAG:
10278 if ((infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP) == 0) return 1;
10279 return notify_forward_header(infoPtr, lpnmh);
10282 infoPtr->colRectsDirty = TRUE;
10283 LISTVIEW_InvalidateList(infoPtr);
10284 return notify_forward_header(infoPtr, lpnmh);
10286 case HDN_ITEMCHANGEDW:
10287 case HDN_ITEMCHANGEDA:
10289 COLUMN_INFO *lpColumnInfo;
10293 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
10295 hdi.mask = HDI_WIDTH;
10296 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi)) return 0;
10300 cxy = lpnmh->pitem->cxy;
10302 /* determine how much we change since the last know position */
10303 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
10304 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
10307 lpColumnInfo->rcHeader.right += dx;
10309 hdi.mask = HDI_ORDER;
10310 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi);
10312 /* not the rightmost one */
10313 if (hdi.iOrder + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
10315 INT nIndex = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
10316 hdi.iOrder + 1, 0);
10317 LISTVIEW_ScrollColumns(infoPtr, nIndex, dx);
10321 /* only needs to update the scrolls */
10322 infoPtr->nItemWidth += dx;
10323 LISTVIEW_UpdateScroll(infoPtr);
10325 LISTVIEW_UpdateItemSize(infoPtr);
10326 if (infoPtr->uView == LV_VIEW_DETAILS && is_redrawing(infoPtr))
10329 RECT rcCol = lpColumnInfo->rcHeader;
10331 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
10332 OffsetRect(&rcCol, ptOrigin.x, 0);
10334 rcCol.top = infoPtr->rcList.top;
10335 rcCol.bottom = infoPtr->rcList.bottom;
10337 /* resizing left-aligned columns leaves most of the left side untouched */
10338 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
10340 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
10343 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
10346 /* when shrinking the last column clear the now unused field */
10347 if (hdi.iOrder == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
10353 /* deal with right from rightmost column area */
10354 right.left = rcCol.right;
10355 right.top = rcCol.top;
10356 right.bottom = rcCol.bottom;
10357 right.right = infoPtr->rcList.right;
10359 LISTVIEW_InvalidateRect(infoPtr, &right);
10362 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
10368 case HDN_ITEMCLICKW:
10369 case HDN_ITEMCLICKA:
10371 /* Handle sorting by Header Column */
10374 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
10376 nmlv.iSubItem = lpnmh->iItem;
10377 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
10378 return notify_forward_header(infoPtr, lpnmh);
10381 case HDN_DIVIDERDBLCLICKW:
10382 case HDN_DIVIDERDBLCLICKA:
10383 /* FIXME: for LVS_EX_HEADERINALLVIEWS and not LV_VIEW_DETAILS
10384 we should use LVSCW_AUTOSIZE_USEHEADER, helper rework or
10385 split needed for that */
10386 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
10387 return notify_forward_header(infoPtr, lpnmh);
10394 * Paint non-client area of control.
10397 * [I] infoPtr : valid pointer to the listview structureof the sender
10398 * [I] region : update region
10401 * TRUE - frame was painted
10402 * FALSE - call default window proc
10404 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
10406 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
10410 int cxEdge = GetSystemMetrics (SM_CXEDGE),
10411 cyEdge = GetSystemMetrics (SM_CYEDGE);
10414 return DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)region, 0);
10416 GetWindowRect(infoPtr->hwndSelf, &r);
10418 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
10419 r.right - cxEdge, r.bottom - cyEdge);
10420 if (region != (HRGN)1)
10421 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
10422 OffsetRect(&r, -r.left, -r.top);
10424 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
10425 OffsetRect(&r, -r.left, -r.top);
10427 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
10428 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
10429 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
10430 ReleaseDC(infoPtr->hwndSelf, dc);
10432 /* Call default proc to get the scrollbars etc. painted */
10433 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
10440 * Determines the type of structure to use.
10443 * [I] infoPtr : valid pointer to the listview structureof the sender
10444 * [I] hwndFrom : listview window handle
10445 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
10450 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
10452 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
10454 if (nCommand == NF_REQUERY)
10455 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
10457 return infoPtr->notifyFormat;
10462 * Paints/Repaints the listview control. Internal use.
10465 * [I] infoPtr : valid pointer to the listview structure
10466 * [I] hdc : device context handle
10471 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
10473 TRACE("(hdc=%p)\n", hdc);
10475 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
10477 infoPtr->bNoItemMetrics = FALSE;
10478 LISTVIEW_UpdateItemSize(infoPtr);
10479 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
10480 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10481 LISTVIEW_UpdateScroll(infoPtr);
10484 if (infoPtr->hwndHeader) UpdateWindow(infoPtr->hwndHeader);
10487 LISTVIEW_Refresh(infoPtr, hdc, NULL);
10492 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
10493 if (!hdc) return 1;
10494 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
10495 EndPaint(infoPtr->hwndSelf, &ps);
10503 * Paints/Repaints the listview control, WM_PAINT handler.
10506 * [I] infoPtr : valid pointer to the listview structure
10507 * [I] hdc : device context handle
10512 static inline LRESULT LISTVIEW_WMPaint(LISTVIEW_INFO *infoPtr, HDC hdc)
10514 TRACE("(hdc=%p)\n", hdc);
10516 if (!is_redrawing(infoPtr))
10517 return DefWindowProcW (infoPtr->hwndSelf, WM_PAINT, (WPARAM)hdc, 0);
10519 return LISTVIEW_Paint(infoPtr, hdc);
10524 * Paints/Repaints the listview control.
10527 * [I] infoPtr : valid pointer to the listview structure
10528 * [I] hdc : device context handle
10529 * [I] options : drawing options
10534 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
10536 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
10538 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
10541 if (options & PRF_ERASEBKGND)
10542 LISTVIEW_EraseBkgnd(infoPtr, hdc);
10544 if (options & PRF_CLIENT)
10545 LISTVIEW_Paint(infoPtr, hdc);
10553 * Processes double click messages (right mouse button).
10556 * [I] infoPtr : valid pointer to the listview structure
10557 * [I] wKey : key flag
10558 * [I] x,y : mouse coordinate
10563 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10565 LVHITTESTINFO lvHitTestInfo;
10567 TRACE("(key=%hu,X=%u,Y=%u)\n", wKey, x, y);
10569 /* send NM_RELEASEDCAPTURE notification */
10570 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10572 /* send NM_RDBLCLK notification */
10573 lvHitTestInfo.pt.x = x;
10574 lvHitTestInfo.pt.y = y;
10575 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10576 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
10583 * Processes mouse down messages (right mouse button).
10586 * [I] infoPtr : valid pointer to the listview structure
10587 * [I] wKey : key flag
10588 * [I] x,y : mouse coordinate
10593 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10595 LVHITTESTINFO lvHitTestInfo;
10598 TRACE("(key=%hu,X=%u,Y=%u)\n", wKey, x, y);
10600 /* send NM_RELEASEDCAPTURE notification */
10601 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10603 /* make sure the listview control window has the focus */
10604 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
10606 /* set right button down flag */
10607 infoPtr->bRButtonDown = TRUE;
10609 /* determine the index of the selected item */
10610 lvHitTestInfo.pt.x = x;
10611 lvHitTestInfo.pt.y = y;
10612 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
10614 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
10616 LISTVIEW_SetItemFocus(infoPtr, nItem);
10617 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
10618 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10619 LISTVIEW_SetSelection(infoPtr, nItem);
10623 LISTVIEW_DeselectAll(infoPtr);
10631 * Processes mouse up messages (right mouse button).
10634 * [I] infoPtr : valid pointer to the listview structure
10635 * [I] wKey : key flag
10636 * [I] x,y : mouse coordinate
10641 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10643 LVHITTESTINFO lvHitTestInfo;
10646 TRACE("(key=%hu,X=%u,Y=%u)\n", wKey, x, y);
10648 if (!infoPtr->bRButtonDown) return 0;
10650 /* set button flag */
10651 infoPtr->bRButtonDown = FALSE;
10653 /* Send NM_RCLICK notification */
10654 lvHitTestInfo.pt.x = x;
10655 lvHitTestInfo.pt.y = y;
10656 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10657 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
10659 /* Change to screen coordinate for WM_CONTEXTMENU */
10660 pt = lvHitTestInfo.pt;
10661 ClientToScreen(infoPtr->hwndSelf, &pt);
10663 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
10664 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
10665 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
10676 * [I] infoPtr : valid pointer to the listview structure
10677 * [I] hwnd : window handle of window containing the cursor
10678 * [I] nHittest : hit-test code
10679 * [I] wMouseMsg : ideintifier of the mouse message
10682 * TRUE if cursor is set
10685 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10687 LVHITTESTINFO lvHitTestInfo;
10689 if (!LISTVIEW_IsHotTracking(infoPtr)) goto forward;
10691 if (!infoPtr->hHotCursor) goto forward;
10693 GetCursorPos(&lvHitTestInfo.pt);
10694 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) goto forward;
10696 SetCursor(infoPtr->hHotCursor);
10702 return DefWindowProcW(infoPtr->hwndSelf, WM_SETCURSOR, wParam, lParam);
10710 * [I] infoPtr : valid pointer to the listview structure
10711 * [I] hwndLoseFocus : handle of previously focused window
10716 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
10718 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
10720 /* if we have the focus already, there's nothing to do */
10721 if (infoPtr->bFocus) return 0;
10723 /* send NM_SETFOCUS notification */
10724 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
10726 /* set window focus flag */
10727 infoPtr->bFocus = TRUE;
10729 /* put the focus rect back on */
10730 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
10732 /* redraw all visible selected items */
10733 LISTVIEW_InvalidateSelectedItems(infoPtr);
10743 * [I] infoPtr : valid pointer to the listview structure
10744 * [I] fRedraw : font handle
10745 * [I] fRedraw : redraw flag
10750 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
10752 HFONT oldFont = infoPtr->hFont;
10753 INT oldHeight = infoPtr->nItemHeight;
10755 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
10757 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
10758 if (infoPtr->hFont == oldFont) return 0;
10760 LISTVIEW_SaveTextMetrics(infoPtr);
10762 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
10764 if (infoPtr->uView == LV_VIEW_DETAILS)
10766 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
10767 LISTVIEW_UpdateSize(infoPtr);
10768 LISTVIEW_UpdateScroll(infoPtr);
10770 else if (infoPtr->nItemHeight != oldHeight)
10771 LISTVIEW_UpdateScroll(infoPtr);
10773 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
10780 * Message handling for WM_SETREDRAW.
10781 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
10784 * [I] infoPtr : valid pointer to the listview structure
10785 * [I] bRedraw: state of redraw flag
10788 * DefWinProc return value
10790 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
10792 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
10794 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
10795 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
10797 infoPtr->bRedraw = bRedraw;
10799 if(!bRedraw) return 0;
10801 if (is_autoarrange(infoPtr))
10802 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10803 LISTVIEW_UpdateScroll(infoPtr);
10805 /* despite what the WM_SETREDRAW docs says, apps expect us
10806 * to invalidate the listview here... stupid! */
10807 LISTVIEW_InvalidateList(infoPtr);
10814 * Resizes the listview control. This function processes WM_SIZE
10815 * messages. At this time, the width and height are not used.
10818 * [I] infoPtr : valid pointer to the listview structure
10819 * [I] Width : new width
10820 * [I] Height : new height
10825 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
10827 RECT rcOld = infoPtr->rcList;
10829 TRACE("(width=%d, height=%d)\n", Width, Height);
10831 LISTVIEW_UpdateSize(infoPtr);
10832 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
10834 /* do not bother with display related stuff if we're not redrawing */
10835 if (!is_redrawing(infoPtr)) return 0;
10837 if (is_autoarrange(infoPtr))
10838 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10840 LISTVIEW_UpdateScroll(infoPtr);
10842 /* refresh all only for lists whose height changed significantly */
10843 if ((infoPtr->uView == LV_VIEW_LIST) &&
10844 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
10845 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
10846 LISTVIEW_InvalidateList(infoPtr);
10853 * Sets the size information.
10856 * [I] infoPtr : valid pointer to the listview structure
10861 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
10863 TRACE("uView=%d, rcList(old)=%s\n", infoPtr->uView, wine_dbgstr_rect(&infoPtr->rcList));
10865 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
10867 if (infoPtr->uView == LV_VIEW_LIST)
10869 /* Apparently the "LIST" style is supposed to have the same
10870 * number of items in a column even if there is no scroll bar.
10871 * Since if a scroll bar already exists then the bottom is already
10872 * reduced, only reduce if the scroll bar does not currently exist.
10873 * The "2" is there to mimic the native control. I think it may be
10874 * related to either padding or edges. (GLA 7/2002)
10876 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
10877 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
10878 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
10881 /* if control created invisible header isn't created */
10882 if (infoPtr->hwndHeader)
10887 hl.prc = &infoPtr->rcList;
10889 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
10890 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
10892 if (LISTVIEW_IsHeaderEnabled(infoPtr))
10893 wp.flags |= SWP_SHOWWINDOW;
10896 wp.flags |= SWP_HIDEWINDOW;
10900 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
10901 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
10903 infoPtr->rcList.top = max(wp.cy, 0);
10905 /* extra padding for grid */
10906 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
10907 infoPtr->rcList.top += 2;
10909 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
10914 * Processes WM_STYLECHANGED messages.
10917 * [I] infoPtr : valid pointer to the listview structure
10918 * [I] wStyleType : window style type (normal or extended)
10919 * [I] lpss : window style information
10924 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
10925 const STYLESTRUCT *lpss)
10927 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
10928 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
10931 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
10932 wStyleType, lpss->styleOld, lpss->styleNew);
10934 if (wStyleType != GWL_STYLE) return 0;
10936 infoPtr->dwStyle = lpss->styleNew;
10937 map_style_view(infoPtr);
10939 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
10940 ((lpss->styleNew & WS_HSCROLL) == 0))
10941 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
10943 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
10944 ((lpss->styleNew & WS_VSCROLL) == 0))
10945 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
10947 if (uNewView != uOldView)
10949 SIZE oldIconSize = infoPtr->iconSize;
10952 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
10953 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
10955 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
10956 SetRectEmpty(&infoPtr->rcFocus);
10958 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
10959 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
10961 if (uNewView == LVS_ICON)
10963 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
10965 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
10966 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
10967 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
10970 else if (uNewView == LVS_REPORT)
10975 LISTVIEW_CreateHeader( infoPtr );
10977 hl.prc = &infoPtr->rcList;
10979 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
10980 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
10981 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
10982 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
10985 LISTVIEW_UpdateItemSize(infoPtr);
10988 if (uNewView == LVS_REPORT || infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS)
10990 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
10992 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
10994 /* Turn off the header control */
10995 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
10996 TRACE("Hide header control, was 0x%08x\n", style);
10997 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
10999 /* Turn on the header control */
11000 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
11002 TRACE("Show header control, was 0x%08x\n", style);
11003 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
11009 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
11010 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
11011 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
11013 /* update the size of the client area */
11014 LISTVIEW_UpdateSize(infoPtr);
11016 /* add scrollbars if needed */
11017 LISTVIEW_UpdateScroll(infoPtr);
11019 /* invalidate client area + erase background */
11020 LISTVIEW_InvalidateList(infoPtr);
11027 * Processes WM_STYLECHANGING messages.
11030 * [I] wStyleType : window style type (normal or extended)
11031 * [I0] lpss : window style information
11036 static INT LISTVIEW_StyleChanging(WPARAM wStyleType,
11039 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
11040 wStyleType, lpss->styleOld, lpss->styleNew);
11042 /* don't forward LVS_OWNERDATA only if not already set to */
11043 if ((lpss->styleNew ^ lpss->styleOld) & LVS_OWNERDATA)
11045 if (lpss->styleOld & LVS_OWNERDATA)
11046 lpss->styleNew |= LVS_OWNERDATA;
11048 lpss->styleNew &= ~LVS_OWNERDATA;
11056 * Processes WM_SHOWWINDOW messages.
11059 * [I] infoPtr : valid pointer to the listview structure
11060 * [I] bShown : window is being shown (FALSE when hidden)
11061 * [I] iStatus : window show status
11066 static LRESULT LISTVIEW_ShowWindow(LISTVIEW_INFO *infoPtr, WPARAM bShown, LPARAM iStatus)
11068 /* header delayed creation */
11069 if ((infoPtr->uView == LV_VIEW_DETAILS) && bShown)
11071 LISTVIEW_CreateHeader(infoPtr);
11073 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle))
11074 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
11077 return DefWindowProcW(infoPtr->hwndSelf, WM_SHOWWINDOW, bShown, iStatus);
11082 * Processes CCM_GETVERSION messages.
11085 * [I] infoPtr : valid pointer to the listview structure
11090 static inline LRESULT LISTVIEW_GetVersion(const LISTVIEW_INFO *infoPtr)
11092 return infoPtr->iVersion;
11097 * Processes CCM_SETVERSION messages.
11100 * [I] infoPtr : valid pointer to the listview structure
11101 * [I] iVersion : version to be set
11104 * -1 when requested version is greater than DLL version;
11105 * previous version otherwise
11107 static LRESULT LISTVIEW_SetVersion(LISTVIEW_INFO *infoPtr, DWORD iVersion)
11109 INT iOldVersion = infoPtr->iVersion;
11111 if (iVersion > COMCTL32_VERSION)
11114 infoPtr->iVersion = iVersion;
11116 TRACE("new version %d\n", iVersion);
11118 return iOldVersion;
11123 * Window procedure of the listview control.
11126 static LRESULT WINAPI
11127 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
11129 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
11131 TRACE("(hwnd=%p uMsg=%x wParam=%lx lParam=%lx)\n", hwnd, uMsg, wParam, lParam);
11133 if (!infoPtr && (uMsg != WM_NCCREATE))
11134 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11138 case LVM_APPROXIMATEVIEWRECT:
11139 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
11140 LOWORD(lParam), HIWORD(lParam));
11142 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
11144 case LVM_CANCELEDITLABEL:
11145 return LISTVIEW_CancelEditLabel(infoPtr);
11147 case LVM_CREATEDRAGIMAGE:
11148 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
11150 case LVM_DELETEALLITEMS:
11151 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
11153 case LVM_DELETECOLUMN:
11154 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
11156 case LVM_DELETEITEM:
11157 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
11159 case LVM_EDITLABELA:
11160 case LVM_EDITLABELW:
11161 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam,
11162 uMsg == LVM_EDITLABELW);
11163 /* case LVM_ENABLEGROUPVIEW: */
11165 case LVM_ENSUREVISIBLE:
11166 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
11168 case LVM_FINDITEMW:
11169 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
11171 case LVM_FINDITEMA:
11172 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
11174 case LVM_GETBKCOLOR:
11175 return infoPtr->clrBk;
11177 /* case LVM_GETBKIMAGE: */
11179 case LVM_GETCALLBACKMASK:
11180 return infoPtr->uCallbackMask;
11182 case LVM_GETCOLUMNA:
11183 case LVM_GETCOLUMNW:
11184 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11185 uMsg == LVM_GETCOLUMNW);
11187 case LVM_GETCOLUMNORDERARRAY:
11188 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
11190 case LVM_GETCOLUMNWIDTH:
11191 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
11193 case LVM_GETCOUNTPERPAGE:
11194 return LISTVIEW_GetCountPerPage(infoPtr);
11196 case LVM_GETEDITCONTROL:
11197 return (LRESULT)infoPtr->hwndEdit;
11199 case LVM_GETEXTENDEDLISTVIEWSTYLE:
11200 return infoPtr->dwLvExStyle;
11202 /* case LVM_GETGROUPINFO: */
11204 /* case LVM_GETGROUPMETRICS: */
11206 case LVM_GETHEADER:
11207 return (LRESULT)infoPtr->hwndHeader;
11209 case LVM_GETHOTCURSOR:
11210 return (LRESULT)infoPtr->hHotCursor;
11212 case LVM_GETHOTITEM:
11213 return infoPtr->nHotItem;
11215 case LVM_GETHOVERTIME:
11216 return infoPtr->dwHoverTime;
11218 case LVM_GETIMAGELIST:
11219 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
11221 /* case LVM_GETINSERTMARK: */
11223 /* case LVM_GETINSERTMARKCOLOR: */
11225 /* case LVM_GETINSERTMARKRECT: */
11227 case LVM_GETISEARCHSTRINGA:
11228 case LVM_GETISEARCHSTRINGW:
11229 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
11234 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, uMsg == LVM_GETITEMW);
11236 case LVM_GETITEMCOUNT:
11237 return infoPtr->nItemCount;
11239 case LVM_GETITEMPOSITION:
11240 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
11242 case LVM_GETITEMRECT:
11243 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
11245 case LVM_GETITEMSPACING:
11246 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
11248 case LVM_GETITEMSTATE:
11249 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
11251 case LVM_GETITEMTEXTA:
11252 case LVM_GETITEMTEXTW:
11253 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam,
11254 uMsg == LVM_GETITEMTEXTW);
11256 case LVM_GETNEXTITEM:
11257 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
11259 case LVM_GETNUMBEROFWORKAREAS:
11260 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
11263 case LVM_GETORIGIN:
11264 if (!lParam) return FALSE;
11265 if (infoPtr->uView == LV_VIEW_DETAILS ||
11266 infoPtr->uView == LV_VIEW_LIST) return FALSE;
11267 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
11270 /* case LVM_GETOUTLINECOLOR: */
11272 /* case LVM_GETSELECTEDCOLUMN: */
11274 case LVM_GETSELECTEDCOUNT:
11275 return LISTVIEW_GetSelectedCount(infoPtr);
11277 case LVM_GETSELECTIONMARK:
11278 return infoPtr->nSelectionMark;
11280 case LVM_GETSTRINGWIDTHA:
11281 case LVM_GETSTRINGWIDTHW:
11282 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam,
11283 uMsg == LVM_GETSTRINGWIDTHW);
11285 case LVM_GETSUBITEMRECT:
11286 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
11288 case LVM_GETTEXTBKCOLOR:
11289 return infoPtr->clrTextBk;
11291 case LVM_GETTEXTCOLOR:
11292 return infoPtr->clrText;
11294 /* case LVM_GETTILEINFO: */
11296 /* case LVM_GETTILEVIEWINFO: */
11298 case LVM_GETTOOLTIPS:
11299 if( !infoPtr->hwndToolTip )
11300 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
11301 return (LRESULT)infoPtr->hwndToolTip;
11303 case LVM_GETTOPINDEX:
11304 return LISTVIEW_GetTopIndex(infoPtr);
11306 case LVM_GETUNICODEFORMAT:
11307 return (infoPtr->notifyFormat == NFR_UNICODE);
11310 return infoPtr->uView;
11312 case LVM_GETVIEWRECT:
11313 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
11315 case LVM_GETWORKAREAS:
11316 FIXME("LVM_GETWORKAREAS: unimplemented\n");
11319 /* case LVM_HASGROUP: */
11322 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, TRUE);
11324 case LVM_INSERTCOLUMNA:
11325 case LVM_INSERTCOLUMNW:
11326 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11327 uMsg == LVM_INSERTCOLUMNW);
11329 /* case LVM_INSERTGROUP: */
11331 /* case LVM_INSERTGROUPSORTED: */
11333 case LVM_INSERTITEMA:
11334 case LVM_INSERTITEMW:
11335 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, uMsg == LVM_INSERTITEMW);
11337 /* case LVM_INSERTMARKHITTEST: */
11339 /* case LVM_ISGROUPVIEWENABLED: */
11341 case LVM_ISITEMVISIBLE:
11342 return LISTVIEW_IsItemVisible(infoPtr, (INT)wParam);
11344 case LVM_MAPIDTOINDEX:
11345 return LISTVIEW_MapIdToIndex(infoPtr, (UINT)wParam);
11347 case LVM_MAPINDEXTOID:
11348 return LISTVIEW_MapIndexToId(infoPtr, (INT)wParam);
11350 /* case LVM_MOVEGROUP: */
11352 /* case LVM_MOVEITEMTOGROUP: */
11354 case LVM_REDRAWITEMS:
11355 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
11357 /* case LVM_REMOVEALLGROUPS: */
11359 /* case LVM_REMOVEGROUP: */
11362 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
11364 case LVM_SETBKCOLOR:
11365 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
11367 /* case LVM_SETBKIMAGE: */
11369 case LVM_SETCALLBACKMASK:
11370 infoPtr->uCallbackMask = (UINT)wParam;
11373 case LVM_SETCOLUMNA:
11374 case LVM_SETCOLUMNW:
11375 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11376 uMsg == LVM_SETCOLUMNW);
11378 case LVM_SETCOLUMNORDERARRAY:
11379 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
11381 case LVM_SETCOLUMNWIDTH:
11382 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
11384 case LVM_SETEXTENDEDLISTVIEWSTYLE:
11385 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
11387 /* case LVM_SETGROUPINFO: */
11389 /* case LVM_SETGROUPMETRICS: */
11391 case LVM_SETHOTCURSOR:
11392 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
11394 case LVM_SETHOTITEM:
11395 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
11397 case LVM_SETHOVERTIME:
11398 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)lParam);
11400 case LVM_SETICONSPACING:
11401 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
11403 case LVM_SETIMAGELIST:
11404 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
11406 /* case LVM_SETINFOTIP: */
11408 /* case LVM_SETINSERTMARK: */
11410 /* case LVM_SETINSERTMARKCOLOR: */
11415 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
11416 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, (uMsg == LVM_SETITEMW));
11419 case LVM_SETITEMCOUNT:
11420 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
11422 case LVM_SETITEMPOSITION:
11425 pt.x = (short)LOWORD(lParam);
11426 pt.y = (short)HIWORD(lParam);
11427 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, &pt);
11430 case LVM_SETITEMPOSITION32:
11431 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, (POINT*)lParam);
11433 case LVM_SETITEMSTATE:
11434 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
11436 case LVM_SETITEMTEXTA:
11437 case LVM_SETITEMTEXTW:
11438 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam,
11439 uMsg == LVM_SETITEMTEXTW);
11441 /* case LVM_SETOUTLINECOLOR: */
11443 /* case LVM_SETSELECTEDCOLUMN: */
11445 case LVM_SETSELECTIONMARK:
11446 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
11448 case LVM_SETTEXTBKCOLOR:
11449 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
11451 case LVM_SETTEXTCOLOR:
11452 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
11454 /* case LVM_SETTILEINFO: */
11456 /* case LVM_SETTILEVIEWINFO: */
11458 /* case LVM_SETTILEWIDTH: */
11460 case LVM_SETTOOLTIPS:
11461 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
11463 case LVM_SETUNICODEFORMAT:
11464 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
11467 return LISTVIEW_SetView(infoPtr, wParam);
11469 /* case LVM_SETWORKAREAS: */
11471 /* case LVM_SORTGROUPS: */
11473 case LVM_SORTITEMS:
11474 case LVM_SORTITEMSEX:
11475 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, wParam,
11476 uMsg == LVM_SORTITEMSEX);
11477 case LVM_SUBITEMHITTEST:
11478 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
11481 return LISTVIEW_Update(infoPtr, (INT)wParam);
11483 case CCM_GETVERSION:
11484 return LISTVIEW_GetVersion(infoPtr);
11486 case CCM_SETVERSION:
11487 return LISTVIEW_SetVersion(infoPtr, wParam);
11490 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
11493 return LISTVIEW_Command(infoPtr, wParam, lParam);
11496 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
11499 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
11502 return LISTVIEW_Destroy(infoPtr);
11505 return LISTVIEW_Enable(infoPtr);
11507 case WM_ERASEBKGND:
11508 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
11510 case WM_GETDLGCODE:
11511 return DLGC_WANTCHARS | DLGC_WANTARROWS;
11514 return (LRESULT)infoPtr->hFont;
11517 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0);
11520 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
11523 return LISTVIEW_KillFocus(infoPtr);
11525 case WM_LBUTTONDBLCLK:
11526 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11528 case WM_LBUTTONDOWN:
11529 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11532 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11535 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11537 case WM_MOUSEHOVER:
11538 return LISTVIEW_MouseHover(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11541 return LISTVIEW_NCDestroy(infoPtr);
11544 return LISTVIEW_NCPaint(infoPtr, (HRGN)wParam);
11547 return LISTVIEW_Notify(infoPtr, (LPNMHDR)lParam);
11549 case WM_NOTIFYFORMAT:
11550 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
11552 case WM_PRINTCLIENT:
11553 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
11556 return LISTVIEW_WMPaint(infoPtr, (HDC)wParam);
11558 case WM_RBUTTONDBLCLK:
11559 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11561 case WM_RBUTTONDOWN:
11562 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11565 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11568 return LISTVIEW_SetCursor(infoPtr, wParam, lParam);
11571 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
11574 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
11577 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
11579 case WM_SHOWWINDOW:
11580 return LISTVIEW_ShowWindow(infoPtr, wParam, lParam);
11582 case WM_STYLECHANGED:
11583 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
11585 case WM_STYLECHANGING:
11586 return LISTVIEW_StyleChanging(wParam, (LPSTYLESTRUCT)lParam);
11588 case WM_SYSCOLORCHANGE:
11589 COMCTL32_RefreshSysColors();
11592 /* case WM_TIMER: */
11593 case WM_THEMECHANGED:
11594 return LISTVIEW_ThemeChanged(infoPtr);
11597 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0);
11599 case WM_MOUSEWHEEL:
11600 if (wParam & (MK_SHIFT | MK_CONTROL))
11601 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11602 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
11604 case WM_WINDOWPOSCHANGED:
11605 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
11607 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
11608 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
11610 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
11612 if (notify_measureitem(infoPtr)) LISTVIEW_InvalidateList(infoPtr);
11614 LISTVIEW_Size(infoPtr, ((WINDOWPOS *)lParam)->cx, ((WINDOWPOS *)lParam)->cy);
11616 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11618 /* case WM_WININICHANGE: */
11621 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
11622 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
11624 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11631 * Registers the window class.
11639 void LISTVIEW_Register(void)
11641 WNDCLASSW wndClass;
11643 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
11644 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
11645 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
11646 wndClass.cbClsExtra = 0;
11647 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
11648 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
11649 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
11650 wndClass.lpszClassName = WC_LISTVIEWW;
11651 RegisterClassW(&wndClass);
11656 * Unregisters the window class.
11664 void LISTVIEW_Unregister(void)
11666 UnregisterClassW(WC_LISTVIEWW, NULL);
11671 * Handle any WM_COMMAND messages
11674 * [I] infoPtr : valid pointer to the listview structure
11675 * [I] wParam : the first message parameter
11676 * [I] lParam : the second message parameter
11681 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
11684 TRACE("(%p %x %x %lx)\n", infoPtr, HIWORD(wParam), LOWORD(wParam), lParam);
11686 if (!infoPtr->hwndEdit) return 0;
11688 switch (HIWORD(wParam))
11693 * Adjust the edit window size
11695 WCHAR buffer[1024];
11696 HDC hdc = GetDC(infoPtr->hwndEdit);
11697 HFONT hFont, hOldFont = 0;
11701 if (!infoPtr->hwndEdit || !hdc) return 0;
11702 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
11703 GetWindowRect(infoPtr->hwndEdit, &rect);
11705 /* Select font to get the right dimension of the string */
11706 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
11709 hOldFont = SelectObject(hdc, hFont);
11712 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
11714 TEXTMETRICW textMetric;
11716 /* Add Extra spacing for the next character */
11717 GetTextMetricsW(hdc, &textMetric);
11718 sz.cx += (textMetric.tmMaxCharWidth * 2);
11720 SetWindowPos(infoPtr->hwndEdit, NULL, 0, 0, sz.cx,
11721 rect.bottom - rect.top, SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOZORDER);
11724 SelectObject(hdc, hOldFont);
11726 ReleaseDC(infoPtr->hwndEdit, hdc);
11732 LISTVIEW_CancelEditLabel(infoPtr);
11737 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);