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-2012 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->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
2960 if (infoPtr->himlState)
2961 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2962 if (infoPtr->himlSmall)
2963 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2964 if (infoPtr->himlState || infoPtr->himlSmall)
2965 nItemHeight += HEIGHT_PADDING;
2966 if (infoPtr->nMeasureItemHeight > 0)
2967 nItemHeight = infoPtr->nMeasureItemHeight;
2970 return max(nItemHeight, 1);
2975 * Updates the width, and height of an item.
2978 * [I] infoPtr : valid pointer to the listview structure
2983 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2985 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2986 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2992 * Retrieves and saves important text metrics info for the current
2996 * [I] infoPtr : valid pointer to the listview structure
2999 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
3001 HDC hdc = GetDC(infoPtr->hwndSelf);
3002 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
3003 HFONT hOldFont = SelectObject(hdc, hFont);
3007 if (GetTextMetricsW(hdc, &tm))
3009 infoPtr->ntmHeight = tm.tmHeight;
3010 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
3013 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
3014 infoPtr->nEllipsisWidth = sz.cx;
3016 SelectObject(hdc, hOldFont);
3017 ReleaseDC(infoPtr->hwndSelf, hdc);
3019 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
3024 * A compare function for ranges
3027 * [I] range1 : pointer to range 1;
3028 * [I] range2 : pointer to range 2;
3032 * > 0 : if range 1 > range 2
3033 * < 0 : if range 2 > range 1
3034 * = 0 : if range intersects range 2
3036 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
3040 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
3042 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
3047 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
3052 #define ranges_check(ranges, desc) if (TRACE_ON(listview)) ranges_assert(ranges, desc, __FILE__, __LINE__)
3054 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *file, int line)
3059 TRACE("*** Checking %s:%d:%s ***\n", file, line, desc);
3061 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
3062 ranges_dump(ranges);
3063 if (DPA_GetPtrCount(ranges->hdpa) > 0)
3065 prev = DPA_GetPtr(ranges->hdpa, 0);
3066 assert (prev->lower >= 0 && prev->lower < prev->upper);
3067 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
3069 curr = DPA_GetPtr(ranges->hdpa, i);
3070 assert (prev->upper <= curr->lower);
3071 assert (curr->lower < curr->upper);
3075 TRACE("--- Done checking---\n");
3078 static RANGES ranges_create(int count)
3080 RANGES ranges = Alloc(sizeof(struct tagRANGES));
3081 if (!ranges) return NULL;
3082 ranges->hdpa = DPA_Create(count);
3083 if (ranges->hdpa) return ranges;
3088 static void ranges_clear(RANGES ranges)
3092 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3093 Free(DPA_GetPtr(ranges->hdpa, i));
3094 DPA_DeleteAllPtrs(ranges->hdpa);
3098 static void ranges_destroy(RANGES ranges)
3100 if (!ranges) return;
3101 ranges_clear(ranges);
3102 DPA_Destroy(ranges->hdpa);
3106 static RANGES ranges_clone(RANGES ranges)
3111 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
3113 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3115 RANGE *newrng = Alloc(sizeof(RANGE));
3116 if (!newrng) goto fail;
3117 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
3118 DPA_SetPtr(clone->hdpa, i, newrng);
3123 TRACE ("clone failed\n");
3124 ranges_destroy(clone);
3128 static RANGES ranges_diff(RANGES ranges, RANGES sub)
3132 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
3133 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
3138 static void ranges_dump(RANGES ranges)
3142 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3143 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
3146 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
3148 RANGE srchrng = { nItem, nItem + 1 };
3150 TRACE("(nItem=%d)\n", nItem);
3151 ranges_check(ranges, "before contain");
3152 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
3155 static INT ranges_itemcount(RANGES ranges)
3159 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3161 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
3162 count += sel->upper - sel->lower;
3168 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
3170 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
3173 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3174 if (index == -1) return TRUE;
3176 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
3178 chkrng = DPA_GetPtr(ranges->hdpa, index);
3179 if (chkrng->lower >= nItem)
3180 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
3181 if (chkrng->upper > nItem)
3182 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
3187 static BOOL ranges_add(RANGES ranges, RANGE range)
3192 TRACE("(%s)\n", debugrange(&range));
3193 ranges_check(ranges, "before add");
3195 /* try find overlapping regions first */
3196 srchrgn.lower = range.lower - 1;
3197 srchrgn.upper = range.upper + 1;
3198 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
3204 TRACE("Adding new range\n");
3206 /* create the brand new range to insert */
3207 newrgn = Alloc(sizeof(RANGE));
3208 if(!newrgn) goto fail;
3211 /* figure out where to insert it */
3212 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3213 TRACE("index=%d\n", index);
3214 if (index == -1) index = 0;
3216 /* and get it over with */
3217 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3225 RANGE *chkrgn, *mrgrgn;
3226 INT fromindex, mergeindex;
3228 chkrgn = DPA_GetPtr(ranges->hdpa, index);
3229 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
3231 chkrgn->lower = min(range.lower, chkrgn->lower);
3232 chkrgn->upper = max(range.upper, chkrgn->upper);
3234 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
3236 /* merge now common ranges */
3238 srchrgn.lower = chkrgn->lower - 1;
3239 srchrgn.upper = chkrgn->upper + 1;
3243 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
3244 if (mergeindex == -1) break;
3245 if (mergeindex == index)
3247 fromindex = index + 1;
3251 TRACE("Merge with index %i\n", mergeindex);
3253 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
3254 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
3255 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
3257 DPA_DeletePtr(ranges->hdpa, mergeindex);
3258 if (mergeindex < index) index --;
3262 ranges_check(ranges, "after add");
3266 ranges_check(ranges, "failed add");
3270 static BOOL ranges_del(RANGES ranges, RANGE range)
3275 TRACE("(%s)\n", debugrange(&range));
3276 ranges_check(ranges, "before del");
3278 /* we don't use DPAS_SORTED here, since we need *
3279 * to find the first overlapping range */
3280 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
3283 chkrgn = DPA_GetPtr(ranges->hdpa, index);
3285 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
3287 /* case 1: Same range */
3288 if ( (chkrgn->upper == range.upper) &&
3289 (chkrgn->lower == range.lower) )
3291 DPA_DeletePtr(ranges->hdpa, index);
3295 /* case 2: engulf */
3296 else if ( (chkrgn->upper <= range.upper) &&
3297 (chkrgn->lower >= range.lower) )
3299 DPA_DeletePtr(ranges->hdpa, index);
3302 /* case 3: overlap upper */
3303 else if ( (chkrgn->upper <= range.upper) &&
3304 (chkrgn->lower < range.lower) )
3306 chkrgn->upper = range.lower;
3308 /* case 4: overlap lower */
3309 else if ( (chkrgn->upper > range.upper) &&
3310 (chkrgn->lower >= range.lower) )
3312 chkrgn->lower = range.upper;
3315 /* case 5: fully internal */
3320 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
3321 newrgn->lower = chkrgn->lower;
3322 newrgn->upper = range.lower;
3323 chkrgn->lower = range.upper;
3324 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3332 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
3335 ranges_check(ranges, "after del");
3339 ranges_check(ranges, "failed del");
3345 * Removes all selection ranges
3348 * [I] infoPtr : valid pointer to the listview structure
3349 * [I] toSkip : item range to skip removing the selection
3355 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
3364 lvItem.stateMask = LVIS_SELECTED;
3366 /* need to clone the DPA because callbacks can change it */
3367 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
3368 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
3369 while(iterator_next(&i))
3370 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
3371 /* note that the iterator destructor will free the cloned range */
3372 iterator_destroy(&i);
3377 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
3381 if (!(toSkip = ranges_create(1))) return FALSE;
3382 if (nItem != -1) ranges_additem(toSkip, nItem);
3383 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
3384 ranges_destroy(toSkip);
3388 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
3390 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
3395 * Retrieves the number of items that are marked as selected.
3398 * [I] infoPtr : valid pointer to the listview structure
3401 * Number of items selected.
3403 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
3405 INT nSelectedCount = 0;
3407 if (infoPtr->uCallbackMask & LVIS_SELECTED)
3410 for (i = 0; i < infoPtr->nItemCount; i++)
3412 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3417 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3419 TRACE("nSelectedCount=%d\n", nSelectedCount);
3420 return nSelectedCount;
3425 * Manages the item focus.
3428 * [I] infoPtr : valid pointer to the listview structure
3429 * [I] nItem : item index
3432 * TRUE : focused item changed
3433 * FALSE : focused item has NOT changed
3435 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3437 INT oldFocus = infoPtr->nFocusedItem;
3440 if (nItem == infoPtr->nFocusedItem) return FALSE;
3442 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3443 lvItem.stateMask = LVIS_FOCUSED;
3444 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3446 return oldFocus != infoPtr->nFocusedItem;
3449 /* Helper function for LISTVIEW_ShiftIndices *only* */
3450 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3452 if (nShiftItem < nItem) return nShiftItem;
3454 if (nShiftItem > nItem) return nShiftItem + direction;
3456 if (direction > 0) return nShiftItem + direction;
3458 return min(nShiftItem, infoPtr->nItemCount - 1);
3463 * Updates the various indices after an item has been inserted or deleted.
3466 * [I] infoPtr : valid pointer to the listview structure
3467 * [I] nItem : item index
3468 * [I] direction : Direction of shift, +1 or -1.
3473 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3478 /* temporarily disable change notification while shifting items */
3479 bOldChange = infoPtr->bDoChangeNotify;
3480 infoPtr->bDoChangeNotify = FALSE;
3482 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3484 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3486 assert(abs(direction) == 1);
3488 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3490 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3491 if (nNewFocus != infoPtr->nFocusedItem)
3492 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3494 /* But we are not supposed to modify nHotItem! */
3496 infoPtr->bDoChangeNotify = bOldChange;
3502 * Adds a block of selections.
3505 * [I] infoPtr : valid pointer to the listview structure
3506 * [I] nItem : item index
3509 * Whether the window is still valid.
3511 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3513 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3514 INT nLast = max(infoPtr->nSelectionMark, nItem);
3515 HWND hwndSelf = infoPtr->hwndSelf;
3516 NMLVODSTATECHANGE nmlv;
3521 /* Temporarily disable change notification
3522 * If the control is LVS_OWNERDATA, we need to send
3523 * only one LVN_ODSTATECHANGED notification.
3524 * See MSDN documentation for LVN_ITEMCHANGED.
3526 bOldChange = infoPtr->bDoChangeNotify;
3527 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3529 if (nFirst == -1) nFirst = nItem;
3531 item.state = LVIS_SELECTED;
3532 item.stateMask = LVIS_SELECTED;
3534 for (i = nFirst; i <= nLast; i++)
3535 LISTVIEW_SetItemState(infoPtr,i,&item);
3537 ZeroMemory(&nmlv, sizeof(nmlv));
3538 nmlv.iFrom = nFirst;
3541 nmlv.uOldState = item.state;
3543 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3544 if (!IsWindow(hwndSelf))
3546 infoPtr->bDoChangeNotify = bOldChange;
3553 * Sets a single group selection.
3556 * [I] infoPtr : valid pointer to the listview structure
3557 * [I] nItem : item index
3562 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3569 if (!(selection = ranges_create(100))) return;
3571 item.state = LVIS_SELECTED;
3572 item.stateMask = LVIS_SELECTED;
3574 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
3576 if (infoPtr->nSelectionMark == -1)
3578 infoPtr->nSelectionMark = nItem;
3579 ranges_additem(selection, nItem);
3585 sel.lower = min(infoPtr->nSelectionMark, nItem);
3586 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3587 ranges_add(selection, sel);
3592 RECT rcItem, rcSel, rcSelMark;
3595 rcItem.left = LVIR_BOUNDS;
3596 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3597 rcSelMark.left = LVIR_BOUNDS;
3598 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3599 UnionRect(&rcSel, &rcItem, &rcSelMark);
3600 iterator_frameditems(&i, infoPtr, &rcSel);
3601 while(iterator_next(&i))
3603 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3604 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3606 iterator_destroy(&i);
3609 /* disable per item notifications on LVS_OWNERDATA style
3610 FIXME: single LVN_ODSTATECHANGED should be used */
3611 bOldChange = infoPtr->bDoChangeNotify;
3612 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3614 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3617 iterator_rangesitems(&i, selection);
3618 while(iterator_next(&i))
3619 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3620 /* this will also destroy the selection */
3621 iterator_destroy(&i);
3623 infoPtr->bDoChangeNotify = bOldChange;
3625 LISTVIEW_SetItemFocus(infoPtr, nItem);
3630 * Sets a single selection.
3633 * [I] infoPtr : valid pointer to the listview structure
3634 * [I] nItem : item index
3639 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3643 TRACE("nItem=%d\n", nItem);
3645 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3647 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3648 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3649 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3651 infoPtr->nSelectionMark = nItem;
3656 * Set selection(s) with keyboard.
3659 * [I] infoPtr : valid pointer to the listview structure
3660 * [I] nItem : item index
3661 * [I] space : VK_SPACE code sent
3664 * SUCCESS : TRUE (needs to be repainted)
3665 * FAILURE : FALSE (nothing has changed)
3667 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem, BOOL space)
3669 /* FIXME: pass in the state */
3670 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3671 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3672 BOOL bResult = FALSE;
3674 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3675 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3679 if (infoPtr->dwStyle & LVS_SINGLESEL || (wShift == 0 && wCtrl == 0))
3680 LISTVIEW_SetSelection(infoPtr, nItem);
3684 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3688 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3689 lvItem.stateMask = LVIS_SELECTED;
3692 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3693 if (lvItem.state & LVIS_SELECTED)
3694 infoPtr->nSelectionMark = nItem;
3696 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3699 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3702 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3706 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3708 LVHITTESTINFO lvHitTestInfo;
3710 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3711 lvHitTestInfo.pt.x = pt.x;
3712 lvHitTestInfo.pt.y = pt.y;
3714 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3716 lpLVItem->mask = LVIF_PARAM;
3717 lpLVItem->iItem = lvHitTestInfo.iItem;
3718 lpLVItem->iSubItem = 0;
3720 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3723 static inline BOOL LISTVIEW_IsHotTracking(const LISTVIEW_INFO *infoPtr)
3725 return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3726 (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3727 (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3732 * Called when the mouse is being actively tracked and has hovered for a specified
3736 * [I] infoPtr : valid pointer to the listview structure
3737 * [I] fwKeys : key indicator
3738 * [I] x,y : mouse position
3741 * 0 if the message was processed, non-zero if there was an error
3744 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3745 * over the item for a certain period of time.
3748 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, INT x, INT y)
3752 if (notify_hdr(infoPtr, NM_HOVER, &hdr)) return 0;
3754 if (LISTVIEW_IsHotTracking(infoPtr))
3762 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3763 LISTVIEW_SetSelection(infoPtr, item.iItem);
3765 SetFocus(infoPtr->hwndSelf);
3771 #define SCROLL_LEFT 0x1
3772 #define SCROLL_RIGHT 0x2
3773 #define SCROLL_UP 0x4
3774 #define SCROLL_DOWN 0x8
3778 * Utility routine to draw and highlight items within a marquee selection rectangle.
3781 * [I] infoPtr : valid pointer to the listview structure
3782 * [I] coords_orig : original co-ordinates of the cursor
3783 * [I] coords_offs : offsetted coordinates of the cursor
3784 * [I] offset : offset amount
3785 * [I] scroll : Bitmask of which directions we should scroll, if at all
3790 static void LISTVIEW_MarqueeHighlight(LISTVIEW_INFO *infoPtr, const POINT *coords_orig,
3791 const POINT *coords_offs, const POINT *offset,
3794 BOOL controlDown = FALSE;
3796 ITERATOR old_elems, new_elems;
3799 if (coords_offs->x > infoPtr->marqueeOrigin.x)
3801 rect.left = infoPtr->marqueeOrigin.x;
3802 rect.right = coords_offs->x;
3806 rect.left = coords_offs->x;
3807 rect.right = infoPtr->marqueeOrigin.x;
3810 if (coords_offs->y > infoPtr->marqueeOrigin.y)
3812 rect.top = infoPtr->marqueeOrigin.y;
3813 rect.bottom = coords_offs->y;
3817 rect.top = coords_offs->y;
3818 rect.bottom = infoPtr->marqueeOrigin.y;
3821 /* Cancel out the old marquee rectangle and draw the new one */
3822 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
3824 /* Scroll by the appropriate distance if applicable - speed up scrolling as
3825 the cursor is further away */
3827 if ((scroll & SCROLL_LEFT) && (coords_orig->x <= 0))
3828 LISTVIEW_Scroll(infoPtr, coords_orig->x, 0);
3830 if ((scroll & SCROLL_RIGHT) && (coords_orig->x >= infoPtr->rcList.right))
3831 LISTVIEW_Scroll(infoPtr, (coords_orig->x - infoPtr->rcList.right), 0);
3833 if ((scroll & SCROLL_UP) && (coords_orig->y <= 0))
3834 LISTVIEW_Scroll(infoPtr, 0, coords_orig->y);
3836 if ((scroll & SCROLL_DOWN) && (coords_orig->y >= infoPtr->rcList.bottom))
3837 LISTVIEW_Scroll(infoPtr, 0, (coords_orig->y - infoPtr->rcList.bottom));
3839 iterator_frameditems_absolute(&old_elems, infoPtr, &infoPtr->marqueeRect);
3841 CopyRect(&infoPtr->marqueeRect, &rect);
3843 CopyRect(&infoPtr->marqueeDrawRect, &rect);
3844 OffsetRect(&infoPtr->marqueeDrawRect, offset->x, offset->y);
3846 iterator_frameditems_absolute(&new_elems, infoPtr, &infoPtr->marqueeRect);
3847 iterator_remove_common_items(&old_elems, &new_elems);
3849 /* Iterate over no longer selected items */
3850 while (iterator_next(&old_elems))
3852 if (old_elems.nItem > -1)
3854 if (LISTVIEW_GetItemState(infoPtr, old_elems.nItem, LVIS_SELECTED) == LVIS_SELECTED)
3857 item.state = LVIS_SELECTED;
3859 item.stateMask = LVIS_SELECTED;
3861 LISTVIEW_SetItemState(infoPtr, old_elems.nItem, &item);
3864 iterator_destroy(&old_elems);
3867 /* Iterate over newly selected items */
3868 if (GetKeyState(VK_CONTROL) & 0x8000)
3871 while (iterator_next(&new_elems))
3873 if (new_elems.nItem > -1)
3875 /* If CTRL is pressed, invert. If not, always select the item. */
3876 if ((controlDown) && (LISTVIEW_GetItemState(infoPtr, new_elems.nItem, LVIS_SELECTED)))
3879 item.state = LVIS_SELECTED;
3881 item.stateMask = LVIS_SELECTED;
3883 LISTVIEW_SetItemState(infoPtr, new_elems.nItem, &item);
3886 iterator_destroy(&new_elems);
3888 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
3893 * Called when we are in a marquee selection that involves scrolling the listview (ie,
3894 * the cursor is outside the bounds of the client area). This is a TIMERPROC.
3897 * [I] hwnd : Handle to the listview
3898 * [I] uMsg : WM_TIMER (ignored)
3899 * [I] idEvent : The timer ID interpreted as a pointer to a LISTVIEW_INFO struct
3900 * [I] dwTimer : The elapsed time (ignored)
3905 static VOID CALLBACK LISTVIEW_ScrollTimer(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
3907 LISTVIEW_INFO *infoPtr;
3908 SCROLLINFO scrollInfo;
3914 infoPtr = (LISTVIEW_INFO *) idEvent;
3919 /* Get the current cursor position and convert to client coordinates */
3920 GetCursorPos(&coords_orig);
3921 ScreenToClient(hWnd, &coords_orig);
3923 /* Ensure coordinates are within client bounds */
3924 coords_offs.x = max(min(coords_orig.x, infoPtr->rcList.right), 0);
3925 coords_offs.y = max(min(coords_orig.y, infoPtr->rcList.bottom), 0);
3928 LISTVIEW_GetOrigin(infoPtr, &offset);
3930 /* Offset coordinates by the appropriate amount */
3931 coords_offs.x -= offset.x;
3932 coords_offs.y -= offset.y;
3934 scrollInfo.cbSize = sizeof(SCROLLINFO);
3935 scrollInfo.fMask = SIF_ALL;
3937 /* Work out in which directions we can scroll */
3938 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3940 if (scrollInfo.nPos != scrollInfo.nMin)
3941 scroll |= SCROLL_UP;
3943 if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
3944 scroll |= SCROLL_DOWN;
3947 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3949 if (scrollInfo.nPos != scrollInfo.nMin)
3950 scroll |= SCROLL_LEFT;
3952 if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
3953 scroll |= SCROLL_RIGHT;
3956 if (((coords_orig.x <= 0) && (scroll & SCROLL_LEFT)) ||
3957 ((coords_orig.y <= 0) && (scroll & SCROLL_UP)) ||
3958 ((coords_orig.x >= infoPtr->rcList.right) && (scroll & SCROLL_RIGHT)) ||
3959 ((coords_orig.y >= infoPtr->rcList.bottom) && (scroll & SCROLL_DOWN)))
3961 LISTVIEW_MarqueeHighlight(infoPtr, &coords_orig, &coords_offs, &offset, scroll);
3967 * Called whenever WM_MOUSEMOVE is received.
3970 * [I] infoPtr : valid pointer to the listview structure
3971 * [I] fwKeys : key indicator
3972 * [I] x,y : mouse position
3975 * 0 if the message is processed, non-zero if there was an error
3977 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3979 if (!(fwKeys & MK_LBUTTON))
3980 infoPtr->bLButtonDown = FALSE;
3982 if (infoPtr->bLButtonDown)
3986 LVHITTESTINFO lvHitTestInfo;
3987 WORD wDragWidth = GetSystemMetrics(SM_CXDRAG);
3988 WORD wDragHeight= GetSystemMetrics(SM_CYDRAG);
3990 if (infoPtr->bMarqueeSelect)
4000 LISTVIEW_GetOrigin(infoPtr, &offset);
4002 /* Ensure coordinates are within client bounds */
4003 coords_offs.x = max(min(x, infoPtr->rcList.right), 0);
4004 coords_offs.y = max(min(y, infoPtr->rcList.bottom), 0);
4006 /* Offset coordinates by the appropriate amount */
4007 coords_offs.x -= offset.x;
4008 coords_offs.y -= offset.y;
4010 /* Enable the timer if we're going outside our bounds, in case the user doesn't
4011 move the mouse again */
4013 if ((x <= 0) || (y <= 0) || (x >= infoPtr->rcList.right) ||
4014 (y >= infoPtr->rcList.bottom))
4016 if (!infoPtr->bScrolling)
4018 infoPtr->bScrolling = TRUE;
4019 SetTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr, 1, LISTVIEW_ScrollTimer);
4024 infoPtr->bScrolling = FALSE;
4025 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
4028 LISTVIEW_MarqueeHighlight(infoPtr, &coords_orig, &coords_offs, &offset, 0);
4032 rect.left = infoPtr->ptClickPos.x - wDragWidth;
4033 rect.right = infoPtr->ptClickPos.x + wDragWidth;
4034 rect.top = infoPtr->ptClickPos.y - wDragHeight;
4035 rect.bottom = infoPtr->ptClickPos.y + wDragHeight;
4040 lvHitTestInfo.pt = tmp;
4041 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
4043 /* reset item marker */
4044 if (infoPtr->nLButtonDownItem != lvHitTestInfo.iItem)
4045 infoPtr->nLButtonDownItem = -1;
4047 if (!PtInRect(&rect, tmp))
4049 /* this path covers the following:
4050 1. WM_LBUTTONDOWN over selected item (sets focus on it)
4051 2. change focus with keys
4052 3. move mouse over item from step 1 selects it and moves focus on it */
4053 if (infoPtr->nLButtonDownItem != -1 &&
4054 !LISTVIEW_GetItemState(infoPtr, infoPtr->nLButtonDownItem, LVIS_SELECTED))
4058 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
4059 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
4061 LISTVIEW_SetItemState(infoPtr, infoPtr->nLButtonDownItem, &lvItem);
4062 infoPtr->nLButtonDownItem = -1;
4065 if (!infoPtr->bDragging)
4067 lvHitTestInfo.pt = infoPtr->ptClickPos;
4068 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
4070 /* If the click is outside the range of an item, begin a
4071 highlight. If not, begin an item drag. */
4072 if (lvHitTestInfo.iItem == -1)
4076 /* If we're allowing multiple selections, send notification.
4077 If return value is non-zero, cancel. */
4078 if (!(infoPtr->dwStyle & LVS_SINGLESEL) && (notify_hdr(infoPtr, LVN_MARQUEEBEGIN, &hdr) == 0))
4080 /* Store the absolute coordinates of the click */
4082 LISTVIEW_GetOrigin(infoPtr, &offset);
4084 infoPtr->marqueeOrigin.x = infoPtr->ptClickPos.x - offset.x;
4085 infoPtr->marqueeOrigin.y = infoPtr->ptClickPos.y - offset.y;
4087 /* Begin selection and capture mouse */
4088 infoPtr->bMarqueeSelect = TRUE;
4089 SetCapture(infoPtr->hwndSelf);
4096 ZeroMemory(&nmlv, sizeof(nmlv));
4097 nmlv.iItem = lvHitTestInfo.iItem;
4098 nmlv.ptAction = infoPtr->ptClickPos;
4100 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
4101 infoPtr->bDragging = TRUE;
4109 /* see if we are supposed to be tracking mouse hovering */
4110 if (LISTVIEW_IsHotTracking(infoPtr)) {
4111 TRACKMOUSEEVENT trackinfo;
4113 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
4114 trackinfo.dwFlags = TME_QUERY;
4116 /* see if we are already tracking this hwnd */
4117 _TrackMouseEvent(&trackinfo);
4119 if(!(trackinfo.dwFlags & TME_HOVER) || trackinfo.hwndTrack != infoPtr->hwndSelf) {
4120 trackinfo.dwFlags = TME_HOVER;
4121 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
4122 trackinfo.hwndTrack = infoPtr->hwndSelf;
4124 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
4125 _TrackMouseEvent(&trackinfo);
4134 * Tests whether the item is assignable to a list with style lStyle
4136 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
4138 if ( (lpLVItem->mask & LVIF_TEXT) &&
4139 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
4140 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
4148 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
4151 * [I] infoPtr : valid pointer to the listview structure
4152 * [I] lpLVItem : valid pointer to new item attributes
4153 * [I] isNew : the item being set is being inserted
4154 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4155 * [O] bChanged : will be set to TRUE if the item really changed
4161 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
4167 /* stateMask is ignored for LVM_INSERTITEM */
4168 UINT stateMask = isNew ? ~0 : lpLVItem->stateMask;
4172 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
4174 if (lpLVItem->mask == 0) return TRUE;
4176 if (infoPtr->dwStyle & LVS_OWNERDATA)
4178 /* a virtual listview only stores selection and focus */
4179 if (lpLVItem->mask & ~LVIF_STATE)
4185 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4186 lpItem = DPA_GetPtr(hdpaSubItems, 0);
4190 /* we need to get the lParam and state of the item */
4191 item.iItem = lpLVItem->iItem;
4192 item.iSubItem = lpLVItem->iSubItem;
4193 item.mask = LVIF_STATE | LVIF_PARAM;
4194 item.stateMask = (infoPtr->dwStyle & LVS_OWNERDATA) ? LVIS_FOCUSED | LVIS_SELECTED : ~0;
4198 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
4200 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
4201 /* determine what fields will change */
4202 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & stateMask & ~infoPtr->uCallbackMask))
4203 uChanged |= LVIF_STATE;
4205 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
4206 uChanged |= LVIF_IMAGE;
4208 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
4209 uChanged |= LVIF_PARAM;
4211 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
4212 uChanged |= LVIF_INDENT;
4214 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
4215 uChanged |= LVIF_TEXT;
4217 TRACE("uChanged=0x%x\n", uChanged);
4218 if (!uChanged) return TRUE;
4221 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4222 nmlv.iItem = lpLVItem->iItem;
4223 nmlv.uNewState = (item.state & ~stateMask) | (lpLVItem->state & stateMask);
4224 nmlv.uOldState = item.state;
4225 nmlv.uChanged = uChanged;
4226 nmlv.lParam = item.lParam;
4228 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
4229 /* and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications */
4231 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
4233 HWND hwndSelf = infoPtr->hwndSelf;
4235 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
4237 if (!IsWindow(hwndSelf))
4241 /* copy information */
4242 if (lpLVItem->mask & LVIF_TEXT)
4243 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
4245 if (lpLVItem->mask & LVIF_IMAGE)
4246 lpItem->hdr.iImage = lpLVItem->iImage;
4248 if (lpLVItem->mask & LVIF_PARAM)
4249 lpItem->lParam = lpLVItem->lParam;
4251 if (lpLVItem->mask & LVIF_INDENT)
4252 lpItem->iIndent = lpLVItem->iIndent;
4254 if (uChanged & LVIF_STATE)
4256 if (lpItem && (stateMask & ~infoPtr->uCallbackMask))
4258 lpItem->state &= ~stateMask;
4259 lpItem->state |= (lpLVItem->state & stateMask);
4261 if (lpLVItem->state & stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
4263 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
4264 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
4266 else if (stateMask & LVIS_SELECTED)
4268 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
4270 /* if we are asked to change focus, and we manage it, do it */
4271 if (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
4273 if (lpLVItem->state & LVIS_FOCUSED)
4275 /* update selection mark */
4276 if (infoPtr->nFocusedItem == -1 && infoPtr->nSelectionMark == -1)
4277 infoPtr->nSelectionMark = lpLVItem->iItem;
4279 if (infoPtr->nFocusedItem != -1)
4281 /* remove current focus */
4282 item.mask = LVIF_STATE;
4284 item.stateMask = LVIS_FOCUSED;
4286 /* recurse with redrawing an item */
4287 LISTVIEW_SetItemState(infoPtr, infoPtr->nFocusedItem, &item);
4290 infoPtr->nFocusedItem = lpLVItem->iItem;
4291 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, infoPtr->uView == LV_VIEW_LIST);
4293 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
4295 infoPtr->nFocusedItem = -1;
4300 /* if we're inserting the item, we're done */
4301 if (isNew) return TRUE;
4303 /* send LVN_ITEMCHANGED notification */
4304 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
4305 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
4312 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
4315 * [I] infoPtr : valid pointer to the listview structure
4316 * [I] lpLVItem : valid pointer to new subitem attributes
4317 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4318 * [O] bChanged : will be set to TRUE if the item really changed
4324 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
4327 SUBITEM_INFO *lpSubItem;
4329 /* we do not support subitems for virtual listviews */
4330 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
4332 /* set subitem only if column is present */
4333 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4335 /* First do some sanity checks */
4336 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
4337 particularly useful. We currently do not actually do anything with
4338 the flag on subitems.
4340 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE | LVIF_DI_SETITEM)) return FALSE;
4341 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
4343 /* get the subitem structure, and create it if not there */
4344 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4345 assert (hdpaSubItems);
4347 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
4350 SUBITEM_INFO *tmpSubItem;
4353 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
4354 if (!lpSubItem) return FALSE;
4355 /* we could binary search here, if need be...*/
4356 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4358 tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
4359 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
4361 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
4366 lpSubItem->iSubItem = lpLVItem->iSubItem;
4367 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
4371 if ((lpLVItem->mask & LVIF_IMAGE) && (lpSubItem->hdr.iImage != lpLVItem->iImage))
4373 lpSubItem->hdr.iImage = lpLVItem->iImage;
4377 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpSubItem->hdr.pszText, lpLVItem->pszText, isW))
4379 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
4388 * Sets item attributes.
4391 * [I] infoPtr : valid pointer to the listview structure
4392 * [I] lpLVItem : new item attributes
4393 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4399 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
4401 HWND hwndSelf = infoPtr->hwndSelf;
4402 LPWSTR pszText = NULL;
4403 BOOL bResult, bChanged = FALSE;
4406 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
4408 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
4411 /* Store old item area */
4412 LISTVIEW_GetItemBox(infoPtr, lpLVItem->iItem, &oldItemArea);
4414 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
4415 if ((lpLVItem->mask & LVIF_TEXT) && is_text(lpLVItem->pszText))
4417 pszText = lpLVItem->pszText;
4418 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
4421 /* actually set the fields */
4422 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
4424 if (lpLVItem->iSubItem)
4425 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
4427 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
4428 if (!IsWindow(hwndSelf))
4431 /* redraw item, if necessary */
4432 if (bChanged && !infoPtr->bIsDrawing)
4434 /* this little optimization eliminates some nasty flicker */
4435 if ( infoPtr->uView == LV_VIEW_DETAILS && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
4436 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
4437 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
4438 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
4441 LISTVIEW_InvalidateRect(infoPtr, &oldItemArea);
4442 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
4448 textfreeT(lpLVItem->pszText, isW);
4449 lpLVItem->pszText = pszText;
4457 * Retrieves the index of the item at coordinate (0, 0) of the client area.
4460 * [I] infoPtr : valid pointer to the listview structure
4465 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
4468 SCROLLINFO scrollInfo;
4470 scrollInfo.cbSize = sizeof(SCROLLINFO);
4471 scrollInfo.fMask = SIF_POS;
4473 if (infoPtr->uView == LV_VIEW_LIST)
4475 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
4476 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
4478 else if (infoPtr->uView == LV_VIEW_DETAILS)
4480 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
4481 nItem = scrollInfo.nPos;
4485 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
4486 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
4489 TRACE("nItem=%d\n", nItem);
4497 * Erases the background of the given rectangle
4500 * [I] infoPtr : valid pointer to the listview structure
4501 * [I] hdc : device context handle
4502 * [I] lprcBox : clipping rectangle
4508 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
4510 if (!infoPtr->hBkBrush) return FALSE;
4512 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
4514 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
4522 * [I] infoPtr : valid pointer to the listview structure
4523 * [I] hdc : device context handle
4524 * [I] nItem : item index
4525 * [I] nSubItem : subitem index
4526 * [I] pos : item position in client coordinates
4527 * [I] cdmode : custom draw mode
4533 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
4536 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4537 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
4538 DWORD cdsubitemmode = CDRF_DODEFAULT;
4540 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
4541 NMLVCUSTOMDRAW nmlvcd;
4546 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
4548 /* get information needed for drawing the item */
4549 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
4550 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
4551 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
4552 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK | LVIS_CUT;
4553 lvItem.iItem = nItem;
4554 lvItem.iSubItem = nSubItem;
4557 lvItem.cchTextMax = DISP_TEXT_SIZE;
4558 lvItem.pszText = szDispText;
4559 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
4560 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4561 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
4562 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
4563 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
4565 /* now check if we need to update the focus rectangle */
4566 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
4568 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
4569 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
4570 OffsetRect(&rcBox, pos.x, pos.y);
4571 OffsetRect(&rcSelect, pos.x, pos.y);
4572 OffsetRect(&rcIcon, pos.x, pos.y);
4573 OffsetRect(&rcStateIcon, pos.x, pos.y);
4574 OffsetRect(&rcLabel, pos.x, pos.y);
4575 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
4576 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
4577 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
4579 /* fill in the custom draw structure */
4580 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
4582 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
4583 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
4584 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
4585 if (cdmode & CDRF_NOTIFYITEMDRAW)
4586 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4587 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
4588 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
4589 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
4590 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
4592 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
4593 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
4595 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
4596 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4597 else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
4598 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
4600 /* in full row select, subitems, will just use main item's colors */
4601 if (nSubItem && infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4602 nmlvcd.clrTextBk = CLR_NONE;
4604 /* FIXME: temporary hack */
4605 rcSelect.left = rcLabel.left;
4607 /* draw the selection background, if we're drawing the main item */
4610 /* in icon mode, the label rect is really what we want to draw the
4612 if (infoPtr->uView == LV_VIEW_ICON)
4615 if (infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4617 /* we have to update left focus bound too if item isn't in leftmost column
4618 and reduce right box bound */
4619 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
4623 if ((leftmost = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0)))
4625 INT Originx = pos.x - LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
4626 INT index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
4627 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4629 rcBox.right = LISTVIEW_GetColumnInfo(infoPtr, index)->rcHeader.right + Originx;
4630 rcSelect.left = LISTVIEW_GetColumnInfo(infoPtr, leftmost)->rcHeader.left + Originx;
4634 rcSelect.right = rcBox.right;
4637 if (nmlvcd.clrTextBk != CLR_NONE)
4638 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, NULL, 0, NULL);
4639 /* store new focus rectangle */
4640 if (infoPtr->nFocusedItem == nItem) infoPtr->rcFocus = rcSelect;
4644 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
4646 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
4649 TRACE("uStateImage=%d\n", uStateImage);
4650 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
4651 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
4656 himl = (infoPtr->uView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
4657 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
4661 TRACE("iImage=%d\n", lvItem.iImage);
4663 if (lvItem.state & (LVIS_SELECTED | LVIS_CUT) && infoPtr->bFocus)
4664 style = ILD_SELECTED;
4668 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
4669 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk,
4670 lvItem.state & LVIS_CUT ? RGB(255, 255, 255) : CLR_DEFAULT,
4674 /* Don't bother painting item being edited */
4675 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
4677 /* figure out the text drawing flags */
4678 uFormat = (infoPtr->uView == LV_VIEW_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
4679 if (infoPtr->uView == LV_VIEW_ICON)
4680 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
4683 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
4685 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
4686 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
4687 default: uFormat |= DT_LEFT;
4690 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
4692 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
4693 else rcLabel.left += LABEL_HOR_PADDING;
4695 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
4697 /* for GRIDLINES reduce the bottom so the text formats correctly */
4698 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4701 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
4704 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
4705 notify_postpaint(infoPtr, &nmlvcd);
4706 if (cdsubitemmode & CDRF_NEWFONT)
4707 SelectObject(hdc, hOldFont);
4713 * Draws listview items when in owner draw mode.
4716 * [I] infoPtr : valid pointer to the listview structure
4717 * [I] hdc : device context handle
4722 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4724 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
4725 DWORD cditemmode = CDRF_DODEFAULT;
4726 NMLVCUSTOMDRAW nmlvcd;
4727 POINT Origin, Position;
4733 ZeroMemory(&dis, sizeof(dis));
4735 /* Get scroll info once before loop */
4736 LISTVIEW_GetOrigin(infoPtr, &Origin);
4738 /* iterate through the invalidated rows */
4739 while(iterator_next(i))
4741 item.iItem = i->nItem;
4743 item.mask = LVIF_PARAM | LVIF_STATE;
4744 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4745 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
4747 dis.CtlType = ODT_LISTVIEW;
4749 dis.itemID = item.iItem;
4750 dis.itemAction = ODA_DRAWENTIRE;
4752 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
4753 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
4754 dis.hwndItem = infoPtr->hwndSelf;
4756 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
4757 dis.rcItem.left = Position.x + Origin.x;
4758 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
4759 dis.rcItem.top = Position.y + Origin.y;
4760 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
4761 dis.itemData = item.lParam;
4763 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
4766 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
4767 * structure for the rest. of the paint cycle
4769 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
4770 if (cdmode & CDRF_NOTIFYITEMDRAW)
4771 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4773 if (!(cditemmode & CDRF_SKIPDEFAULT))
4775 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
4776 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
4779 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
4780 notify_postpaint(infoPtr, &nmlvcd);
4786 * Draws listview items when in report display mode.
4789 * [I] infoPtr : valid pointer to the listview structure
4790 * [I] hdc : device context handle
4791 * [I] cdmode : custom draw mode
4796 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4799 RECT rcClip, rcItem;
4800 POINT Origin, Position;
4807 /* figure out what to draw */
4808 rgntype = GetClipBox(hdc, &rcClip);
4809 if (rgntype == NULLREGION) return;
4811 /* Get scroll info once before loop */
4812 LISTVIEW_GetOrigin(infoPtr, &Origin);
4814 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
4816 /* narrow down the columns we need to paint */
4817 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
4819 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
4821 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4822 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
4823 ranges_additem(colRanges, index);
4825 iterator_rangesitems(&j, colRanges);
4827 /* in full row select, we _have_ to draw the main item */
4828 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4831 /* iterate through the invalidated rows */
4832 while(iterator_next(i))
4834 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4835 Position.y += Origin.y;
4837 /* iterate through the invalidated columns */
4838 while(iterator_next(&j))
4840 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4841 Position.x = (j.nItem == 0) ? rcItem.left + Origin.x : Origin.x;
4843 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4846 rcItem.bottom = infoPtr->nItemHeight;
4847 OffsetRect(&rcItem, Origin.x, Position.y);
4848 if (!RectVisible(hdc, &rcItem)) continue;
4851 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4854 iterator_destroy(&j);
4859 * Draws the gridlines if necessary when in report display mode.
4862 * [I] infoPtr : valid pointer to the listview structure
4863 * [I] hdc : device context handle
4868 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
4874 RECT rcClip, rcItem = {0};
4882 /* figure out what to draw */
4883 rgntype = GetClipBox(hdc, &rcClip);
4884 if (rgntype == NULLREGION) return;
4886 /* Get scroll info once before loop */
4887 LISTVIEW_GetOrigin(infoPtr, &Origin);
4889 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
4891 /* narrow down the columns we need to paint */
4892 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
4894 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
4896 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4897 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
4898 ranges_additem(colRanges, index);
4901 /* is right most vertical line visible? */
4902 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
4904 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4905 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4906 rmost = (rcItem.right + Origin.x < rcClip.right);
4909 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
4911 hOldPen = SelectObject ( hdc, hPen );
4913 /* draw the vertical lines for the columns */
4914 iterator_rangesitems(&j, colRanges);
4915 while(iterator_next(&j))
4917 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4918 if (rcItem.left == 0) continue; /* skip leftmost column */
4919 rcItem.left += Origin.x;
4920 rcItem.right += Origin.x;
4921 rcItem.top = infoPtr->rcList.top;
4922 rcItem.bottom = infoPtr->rcList.bottom;
4923 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
4924 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4925 LineTo (hdc, rcItem.left, rcItem.bottom);
4927 iterator_destroy(&j);
4928 /* draw rightmost grid line if visible */
4931 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
4932 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4933 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4935 rcItem.right += Origin.x;
4937 MoveToEx (hdc, rcItem.right, infoPtr->rcList.top, NULL);
4938 LineTo (hdc, rcItem.right, infoPtr->rcList.bottom);
4941 /* draw the horizontal lines for the rows */
4942 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
4943 rcItem.left = infoPtr->rcList.left;
4944 rcItem.right = infoPtr->rcList.right;
4945 for(y = Origin.y > 1 ? Origin.y - 1 : itemheight - 1 + Origin.y % itemheight; y<=infoPtr->rcList.bottom; y+=itemheight)
4947 rcItem.bottom = rcItem.top = y;
4948 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
4949 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4950 LineTo (hdc, rcItem.right, rcItem.top);
4953 SelectObject( hdc, hOldPen );
4954 DeleteObject( hPen );
4957 ranges_destroy(colRanges);
4962 * Draws listview items when in list display mode.
4965 * [I] infoPtr : valid pointer to the listview structure
4966 * [I] hdc : device context handle
4967 * [I] cdmode : custom draw mode
4972 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4974 POINT Origin, Position;
4976 /* Get scroll info once before loop */
4977 LISTVIEW_GetOrigin(infoPtr, &Origin);
4979 while(iterator_prev(i))
4981 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4982 Position.x += Origin.x;
4983 Position.y += Origin.y;
4985 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4992 * Draws listview items.
4995 * [I] infoPtr : valid pointer to the listview structure
4996 * [I] hdc : device context handle
4997 * [I] prcErase : rect to be erased before refresh (may be NULL)
5002 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
5004 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
5005 NMLVCUSTOMDRAW nmlvcd;
5012 HBITMAP hbmp = NULL;
5015 LISTVIEW_DUMP(infoPtr);
5017 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
5018 TRACE("double buffering\n");
5020 hdc = CreateCompatibleDC(hdcOrig);
5022 ERR("Failed to create DC for backbuffer\n");
5025 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
5026 infoPtr->rcList.bottom);
5028 ERR("Failed to create bitmap for backbuffer\n");
5033 SelectObject(hdc, hbmp);
5034 SelectObject(hdc, infoPtr->hFont);
5036 if(GetClipBox(hdcOrig, &rcClient))
5037 IntersectClipRect(hdc, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
5039 /* Save dc values we're gonna trash while drawing
5040 * FIXME: Should be done in LISTVIEW_DrawItem() */
5041 hOldFont = SelectObject(hdc, infoPtr->hFont);
5042 oldBkMode = GetBkMode(hdc);
5043 oldBkColor = GetBkColor(hdc);
5044 oldTextColor = GetTextColor(hdc);
5047 infoPtr->bIsDrawing = TRUE;
5050 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
5051 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
5052 /* If no erasing was done (usually because RedrawWindow was called
5053 * with RDW_INVALIDATE only) we need to copy the old contents into
5054 * the backbuffer before continuing. */
5055 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
5056 infoPtr->rcList.right - infoPtr->rcList.left,
5057 infoPtr->rcList.bottom - infoPtr->rcList.top,
5058 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
5061 /* FIXME: Shouldn't need to do this */
5062 oldClrTextBk = infoPtr->clrTextBk;
5063 oldClrText = infoPtr->clrText;
5065 infoPtr->cditemmode = CDRF_DODEFAULT;
5067 GetClientRect(infoPtr->hwndSelf, &rcClient);
5068 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
5069 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
5070 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
5071 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
5073 /* Use these colors to draw the items */
5074 infoPtr->clrTextBk = nmlvcd.clrTextBk;
5075 infoPtr->clrText = nmlvcd.clrText;
5077 /* nothing to draw */
5078 if(infoPtr->nItemCount == 0) goto enddraw;
5080 /* figure out what we need to draw */
5081 iterator_visibleitems(&i, infoPtr, hdc);
5082 range = iterator_range(&i);
5084 /* send cache hint notification */
5085 if (infoPtr->dwStyle & LVS_OWNERDATA)
5089 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
5090 nmlv.iFrom = range.lower;
5091 nmlv.iTo = range.upper - 1;
5092 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
5095 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
5096 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
5099 if (infoPtr->uView == LV_VIEW_DETAILS)
5100 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
5101 else /* LV_VIEW_LIST, LV_VIEW_ICON or LV_VIEW_SMALLICON */
5102 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
5104 /* if we have a focus rect and it's visible, draw it */
5105 if (infoPtr->bFocus && range.lower <= infoPtr->nFocusedItem &&
5106 (range.upper - 1) >= infoPtr->nFocusedItem)
5107 LISTVIEW_DrawFocusRect(infoPtr, hdc);
5109 iterator_destroy(&i);
5112 /* For LVS_EX_GRIDLINES go and draw lines */
5113 /* This includes the case where there were *no* items */
5114 if ((infoPtr->uView == LV_VIEW_DETAILS) && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
5115 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
5117 /* Draw marquee rectangle if appropriate */
5118 if (infoPtr->bMarqueeSelect)
5119 DrawFocusRect(hdc, &infoPtr->marqueeDrawRect);
5121 if (cdmode & CDRF_NOTIFYPOSTPAINT)
5122 notify_postpaint(infoPtr, &nmlvcd);
5124 infoPtr->clrTextBk = oldClrTextBk;
5125 infoPtr->clrText = oldClrText;
5128 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
5129 infoPtr->rcList.right - infoPtr->rcList.left,
5130 infoPtr->rcList.bottom - infoPtr->rcList.top,
5131 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
5136 SelectObject(hdc, hOldFont);
5137 SetBkMode(hdc, oldBkMode);
5138 SetBkColor(hdc, oldBkColor);
5139 SetTextColor(hdc, oldTextColor);
5142 infoPtr->bIsDrawing = FALSE;
5148 * Calculates the approximate width and height of a given number of items.
5151 * [I] infoPtr : valid pointer to the listview structure
5152 * [I] nItemCount : number of items
5153 * [I] wWidth : width
5154 * [I] wHeight : height
5157 * Returns a DWORD. The width in the low word and the height in high word.
5159 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
5160 WORD wWidth, WORD wHeight)
5162 DWORD dwViewRect = 0;
5164 if (nItemCount == -1)
5165 nItemCount = infoPtr->nItemCount;
5167 if (infoPtr->uView == LV_VIEW_LIST)
5169 INT nItemCountPerColumn = 1;
5170 INT nColumnCount = 0;
5172 if (wHeight == 0xFFFF)
5174 /* use current height */
5175 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
5178 if (wHeight < infoPtr->nItemHeight)
5179 wHeight = infoPtr->nItemHeight;
5183 if (infoPtr->nItemHeight > 0)
5185 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
5186 if (nItemCountPerColumn == 0)
5187 nItemCountPerColumn = 1;
5189 if (nItemCount % nItemCountPerColumn != 0)
5190 nColumnCount = nItemCount / nItemCountPerColumn;
5192 nColumnCount = nItemCount / nItemCountPerColumn + 1;
5196 /* Microsoft padding magic */
5197 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
5198 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
5200 dwViewRect = MAKELONG(wWidth, wHeight);
5202 else if (infoPtr->uView == LV_VIEW_DETAILS)
5206 if (infoPtr->nItemCount > 0)
5208 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
5209 wWidth = rcBox.right - rcBox.left;
5210 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
5214 /* use current height and width */
5215 if (wHeight == 0xffff)
5216 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
5217 if (wWidth == 0xffff)
5218 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
5221 dwViewRect = MAKELONG(wWidth, wHeight);
5223 else if (infoPtr->uView == LV_VIEW_ICON)
5229 nItemWidth = infoPtr->iconSpacing.cx;
5230 nItemHeight = infoPtr->iconSpacing.cy;
5232 if (wWidth == 0xffff)
5233 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
5235 if (wWidth < nItemWidth)
5236 wWidth = nItemWidth;
5238 cols = wWidth / nItemWidth;
5239 if (cols > nItemCount)
5246 rows = nItemCount / cols;
5247 if (nItemCount % cols)
5253 wHeight = (nItemHeight * rows)+2;
5254 wWidth = (nItemWidth * cols)+2;
5256 dwViewRect = MAKELONG(wWidth, wHeight);
5258 else if (infoPtr->uView == LV_VIEW_SMALLICON)
5259 FIXME("uView == LV_VIEW_SMALLICON: not implemented\n");
5266 * Cancel edit label with saving item text.
5269 * [I] infoPtr : valid pointer to the listview structure
5272 * Always returns TRUE.
5274 static LRESULT LISTVIEW_CancelEditLabel(LISTVIEW_INFO *infoPtr)
5276 if (infoPtr->hwndEdit)
5278 /* handle value will be lost after LISTVIEW_EndEditLabelT */
5279 HWND edit = infoPtr->hwndEdit;
5281 LISTVIEW_EndEditLabelT(infoPtr, TRUE, IsWindowUnicode(infoPtr->hwndEdit));
5282 SendMessageW(edit, WM_CLOSE, 0, 0);
5290 * Create a drag image list for the specified item.
5293 * [I] infoPtr : valid pointer to the listview structure
5294 * [I] iItem : index of item
5295 * [O] lppt : Upper-left corner of the image
5298 * Returns a handle to the image list if successful, NULL otherwise.
5300 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
5306 HBITMAP hbmp, hOldbmp;
5307 HIMAGELIST dragList = 0;
5308 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
5310 if (iItem < 0 || iItem >= infoPtr->nItemCount || !lppt)
5313 rcItem.left = LVIR_BOUNDS;
5314 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
5317 lppt->x = rcItem.left;
5318 lppt->y = rcItem.top;
5320 size.cx = rcItem.right - rcItem.left;
5321 size.cy = rcItem.bottom - rcItem.top;
5323 hdcOrig = GetDC(infoPtr->hwndSelf);
5324 hdc = CreateCompatibleDC(hdcOrig);
5325 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
5326 hOldbmp = SelectObject(hdc, hbmp);
5328 rcItem.left = rcItem.top = 0;
5329 rcItem.right = size.cx;
5330 rcItem.bottom = size.cy;
5331 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
5334 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
5336 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
5337 SelectObject(hdc, hOldbmp);
5338 ImageList_Add(dragList, hbmp, 0);
5341 SelectObject(hdc, hOldbmp);
5345 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
5347 TRACE("ret=%p\n", dragList);
5355 * Removes all listview items and subitems.
5358 * [I] infoPtr : valid pointer to the listview structure
5364 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
5367 HDPA hdpaSubItems = NULL;
5376 /* we do it directly, to avoid notifications */
5377 ranges_clear(infoPtr->selectionRanges);
5378 infoPtr->nSelectionMark = -1;
5379 infoPtr->nFocusedItem = -1;
5380 SetRectEmpty(&infoPtr->rcFocus);
5381 /* But we are supposed to leave nHotItem as is! */
5384 /* send LVN_DELETEALLITEMS notification */
5385 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
5387 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
5389 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
5391 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5393 /* send LVN_DELETEITEM notification, if not suppressed
5394 and if it is not a virtual listview */
5395 if (!bSuppress) notify_deleteitem(infoPtr, i);
5396 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
5397 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5398 /* free id struct */
5399 j = DPA_GetPtrIndex(infoPtr->hdpaItemIds, lpItem->id);
5400 lpID = DPA_GetPtr(infoPtr->hdpaItemIds, j);
5401 DPA_DeletePtr(infoPtr->hdpaItemIds, j);
5403 /* both item and subitem start with ITEMHDR header */
5404 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
5406 hdrItem = DPA_GetPtr(hdpaSubItems, j);
5407 if (is_text(hdrItem->pszText)) Free(hdrItem->pszText);
5410 DPA_Destroy(hdpaSubItems);
5411 DPA_DeletePtr(infoPtr->hdpaItems, i);
5413 DPA_DeletePtr(infoPtr->hdpaPosX, i);
5414 DPA_DeletePtr(infoPtr->hdpaPosY, i);
5415 infoPtr->nItemCount --;
5420 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
5421 LISTVIEW_UpdateScroll(infoPtr);
5423 LISTVIEW_InvalidateList(infoPtr);
5430 * Scrolls, and updates the columns, when a column is changing width.
5433 * [I] infoPtr : valid pointer to the listview structure
5434 * [I] nColumn : column to scroll
5435 * [I] dx : amount of scroll, in pixels
5440 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
5442 COLUMN_INFO *lpColumnInfo;
5448 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
5449 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
5450 rcCol = lpColumnInfo->rcHeader;
5451 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
5452 rcCol.left = rcCol.right;
5454 /* adjust the other columns */
5455 hdi.mask = HDI_ORDER;
5456 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdi))
5458 INT nOrder = hdi.iOrder;
5459 for (nCol = 0; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
5461 hdi.mask = HDI_ORDER;
5462 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nCol, (LPARAM)&hdi);
5463 if (hdi.iOrder >= nOrder) {
5464 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
5465 lpColumnInfo->rcHeader.left += dx;
5466 lpColumnInfo->rcHeader.right += dx;
5471 /* do not update screen if not in report mode */
5472 if (!is_redrawing(infoPtr) || infoPtr->uView != LV_VIEW_DETAILS) return;
5474 /* Need to reset the item width when inserting a new column */
5475 infoPtr->nItemWidth += dx;
5477 LISTVIEW_UpdateScroll(infoPtr);
5478 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
5480 /* scroll to cover the deleted column, and invalidate for redraw */
5481 rcOld = infoPtr->rcList;
5482 rcOld.left = ptOrigin.x + rcCol.left + dx;
5483 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
5488 * Removes a column from the listview control.
5491 * [I] infoPtr : valid pointer to the listview structure
5492 * [I] nColumn : column index
5498 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
5502 TRACE("nColumn=%d\n", nColumn);
5504 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
5505 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5507 /* While the MSDN specifically says that column zero should not be deleted,
5508 what actually happens is that the column itself is deleted but no items or subitems
5512 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
5514 if (!SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nColumn, 0))
5517 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
5518 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
5520 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
5522 SUBITEM_INFO *lpSubItem, *lpDelItem;
5524 INT nItem, nSubItem, i;
5526 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
5528 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
5531 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
5533 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
5534 if (lpSubItem->iSubItem == nColumn)
5537 lpDelItem = lpSubItem;
5539 else if (lpSubItem->iSubItem > nColumn)
5541 lpSubItem->iSubItem--;
5545 /* if we found our subitem, zap it */
5549 if (is_text(lpDelItem->hdr.pszText))
5550 Free(lpDelItem->hdr.pszText);
5555 /* free dpa memory */
5556 DPA_DeletePtr(hdpaSubItems, nSubItem);
5561 /* update the other column info */
5562 LISTVIEW_UpdateItemSize(infoPtr);
5563 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
5564 LISTVIEW_InvalidateList(infoPtr);
5566 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
5573 * Invalidates the listview after an item's insertion or deletion.
5576 * [I] infoPtr : valid pointer to the listview structure
5577 * [I] nItem : item index
5578 * [I] dir : -1 if deleting, 1 if inserting
5583 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
5585 INT nPerCol, nItemCol, nItemRow;
5589 /* if we don't refresh, what's the point of scrolling? */
5590 if (!is_redrawing(infoPtr)) return;
5592 assert (abs(dir) == 1);
5594 /* arrange icons if autoarrange is on */
5595 if (is_autoarrange(infoPtr))
5597 BOOL arrange = TRUE;
5598 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
5599 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
5600 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
5603 /* scrollbars need updating */
5604 LISTVIEW_UpdateScroll(infoPtr);
5606 /* figure out the item's position */
5607 if (infoPtr->uView == LV_VIEW_DETAILS)
5608 nPerCol = infoPtr->nItemCount + 1;
5609 else if (infoPtr->uView == LV_VIEW_LIST)
5610 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
5611 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
5614 nItemCol = nItem / nPerCol;
5615 nItemRow = nItem % nPerCol;
5616 LISTVIEW_GetOrigin(infoPtr, &Origin);
5618 /* move the items below up a slot */
5619 rcScroll.left = nItemCol * infoPtr->nItemWidth;
5620 rcScroll.top = nItemRow * infoPtr->nItemHeight;
5621 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
5622 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
5623 OffsetRect(&rcScroll, Origin.x, Origin.y);
5624 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
5625 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
5627 TRACE("Scrolling rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
5628 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
5629 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
5632 /* report has only that column, so we're done */
5633 if (infoPtr->uView == LV_VIEW_DETAILS) return;
5635 /* now for LISTs, we have to deal with the columns to the right */
5636 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
5638 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
5639 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
5640 OffsetRect(&rcScroll, Origin.x, Origin.y);
5641 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
5642 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
5643 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
5648 * Removes an item from the listview control.
5651 * [I] infoPtr : valid pointer to the listview structure
5652 * [I] nItem : item index
5658 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
5661 const BOOL is_icon = (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON);
5663 TRACE("(nItem=%d)\n", nItem);
5665 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5667 /* remove selection, and focus */
5669 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
5670 LISTVIEW_SetItemState(infoPtr, nItem, &item);
5672 /* send LVN_DELETEITEM notification. */
5673 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
5675 /* we need to do this here, because we'll be deleting stuff */
5677 LISTVIEW_InvalidateItem(infoPtr, nItem);
5679 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5687 hdpaSubItems = DPA_DeletePtr(infoPtr->hdpaItems, nItem);
5688 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5690 /* free id struct */
5691 i = DPA_GetPtrIndex(infoPtr->hdpaItemIds, lpItem->id);
5692 lpID = DPA_GetPtr(infoPtr->hdpaItemIds, i);
5693 DPA_DeletePtr(infoPtr->hdpaItemIds, i);
5695 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
5697 hdrItem = DPA_GetPtr(hdpaSubItems, i);
5698 if (is_text(hdrItem->pszText)) Free(hdrItem->pszText);
5701 DPA_Destroy(hdpaSubItems);
5706 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
5707 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
5710 infoPtr->nItemCount--;
5711 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
5713 /* now is the invalidation fun */
5715 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
5722 * Callback implementation for editlabel control
5725 * [I] infoPtr : valid pointer to the listview structure
5726 * [I] storeText : store edit box text as item text
5727 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
5733 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, BOOL storeText, BOOL isW)
5735 HWND hwndSelf = infoPtr->hwndSelf;
5736 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
5737 NMLVDISPINFOW dispInfo;
5738 INT editedItem = infoPtr->nEditLabelItem;
5740 WCHAR *pszText = NULL;
5745 DWORD len = isW ? GetWindowTextLengthW(infoPtr->hwndEdit) : GetWindowTextLengthA(infoPtr->hwndEdit);
5749 if ((pszText = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))))
5751 if (isW) GetWindowTextW(infoPtr->hwndEdit, pszText, len+1);
5752 else GetWindowTextA(infoPtr->hwndEdit, (CHAR*)pszText, len+1);
5757 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
5759 ZeroMemory(&dispInfo, sizeof(dispInfo));
5760 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
5761 dispInfo.item.iItem = editedItem;
5762 dispInfo.item.iSubItem = 0;
5763 dispInfo.item.stateMask = ~0;
5764 dispInfo.item.pszText = szDispText;
5765 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
5766 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW))
5773 same = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
5776 LPWSTR tmp = textdupTtoW(pszText, FALSE);
5777 same = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
5778 textfreeT(tmp, FALSE);
5781 /* add the text from the edit in */
5782 dispInfo.item.mask |= LVIF_TEXT;
5783 dispInfo.item.pszText = same ? NULL : pszText;
5784 dispInfo.item.cchTextMax = textlenT(dispInfo.item.pszText, isW);
5786 /* Do we need to update the Item Text */
5787 res = notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW);
5789 infoPtr->nEditLabelItem = -1;
5790 infoPtr->hwndEdit = 0;
5792 if (!res) goto cleanup;
5794 if (!IsWindow(hwndSelf))
5799 if (!pszText) return TRUE;
5806 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5808 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, editedItem);
5809 ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
5810 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
5812 LISTVIEW_InvalidateItem(infoPtr, editedItem);
5818 ZeroMemory(&dispInfo, sizeof(dispInfo));
5819 dispInfo.item.mask = LVIF_TEXT;
5820 dispInfo.item.iItem = editedItem;
5821 dispInfo.item.iSubItem = 0;
5822 dispInfo.item.pszText = pszText;
5823 dispInfo.item.cchTextMax = textlenT(pszText, isW);
5824 res = LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
5834 * Subclassed edit control windproc function
5837 * [I] hwnd : the edit window handle
5838 * [I] uMsg : the message that is to be processed
5839 * [I] wParam : first message parameter
5840 * [I] lParam : second message parameter
5841 * [I] isW : TRUE if input is Unicode
5846 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
5848 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
5851 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
5852 hwnd, uMsg, wParam, lParam, isW);
5857 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
5861 WNDPROC editProc = infoPtr->EditWndProc;
5862 infoPtr->EditWndProc = 0;
5863 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
5864 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
5868 if (VK_ESCAPE == (INT)wParam)
5873 else if (VK_RETURN == (INT)wParam)
5877 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
5881 if (infoPtr->hwndEdit)
5882 LISTVIEW_EndEditLabelT(infoPtr, save, isW);
5884 SendMessageW(hwnd, WM_CLOSE, 0, 0);
5890 * Subclassed edit control Unicode windproc function
5893 * [I] hwnd : the edit window handle
5894 * [I] uMsg : the message that is to be processed
5895 * [I] wParam : first message parameter
5896 * [I] lParam : second message parameter
5900 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
5902 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
5907 * Subclassed edit control ANSI windproc function
5910 * [I] hwnd : the edit window handle
5911 * [I] uMsg : the message that is to be processed
5912 * [I] wParam : first message parameter
5913 * [I] lParam : second message parameter
5917 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
5919 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
5924 * Creates a subclassed edit control
5927 * [I] infoPtr : valid pointer to the listview structure
5928 * [I] text : initial text for the edit
5929 * [I] style : the window style
5930 * [I] isW : TRUE if input is Unicode
5934 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, BOOL isW)
5936 static const DWORD style = WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER|WS_VISIBLE;
5937 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
5940 TRACE("(%p, text=%s, isW=%d)\n", infoPtr, debugtext_t(text, isW), isW);
5942 /* window will be resized and positioned after LVN_BEGINLABELEDIT */
5944 hedit = CreateWindowW(WC_EDITW, text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
5946 hedit = CreateWindowA(WC_EDITA, (LPCSTR)text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
5948 if (!hedit) return 0;
5950 infoPtr->EditWndProc = (WNDPROC)
5951 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
5952 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
5954 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
5955 SendMessageW(hedit, EM_SETLIMITTEXT, DISP_TEXT_SIZE-1, 0);
5962 * Begin in place editing of specified list view item
5965 * [I] infoPtr : valid pointer to the listview structure
5966 * [I] nItem : item index
5967 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
5973 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
5975 WCHAR disptextW[DISP_TEXT_SIZE] = { 0 };
5976 HWND hwndSelf = infoPtr->hwndSelf;
5977 NMLVDISPINFOW dispInfo;
5978 HFONT hOldFont = NULL;
5984 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
5986 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
5988 /* remove existing edit box */
5989 if (infoPtr->hwndEdit)
5991 SetFocus(infoPtr->hwndSelf);
5992 infoPtr->hwndEdit = 0;
5995 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5997 infoPtr->nEditLabelItem = nItem;
5999 LISTVIEW_SetSelection(infoPtr, nItem);
6000 LISTVIEW_SetItemFocus(infoPtr, nItem);
6001 LISTVIEW_InvalidateItem(infoPtr, nItem);
6003 rect.left = LVIR_LABEL;
6004 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
6006 ZeroMemory(&dispInfo, sizeof(dispInfo));
6007 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
6008 dispInfo.item.iItem = nItem;
6009 dispInfo.item.iSubItem = 0;
6010 dispInfo.item.stateMask = ~0;
6011 dispInfo.item.pszText = disptextW;
6012 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
6013 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
6015 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, isW);
6016 if (!infoPtr->hwndEdit) return 0;
6018 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
6020 if (!IsWindow(hwndSelf))
6022 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
6023 infoPtr->hwndEdit = 0;
6027 TRACE("disp text=%s\n", debugtext_t(dispInfo.item.pszText, isW));
6029 /* position and display edit box */
6030 hdc = GetDC(infoPtr->hwndSelf);
6032 /* select the font to get appropriate metric dimensions */
6034 hOldFont = SelectObject(hdc, infoPtr->hFont);
6036 /* use real edit box content, it could be altered during LVN_BEGINLABELEDIT notification */
6037 GetWindowTextW(infoPtr->hwndEdit, disptextW, DISP_TEXT_SIZE);
6038 TRACE("edit box text=%s\n", debugstr_w(disptextW));
6040 /* get string length in pixels */
6041 GetTextExtentPoint32W(hdc, disptextW, lstrlenW(disptextW), &sz);
6043 /* add extra spacing for the next character */
6044 GetTextMetricsW(hdc, &tm);
6045 sz.cx += tm.tmMaxCharWidth * 2;
6048 SelectObject(hdc, hOldFont);
6050 ReleaseDC(infoPtr->hwndSelf, hdc);
6052 sz.cy = rect.bottom - rect.top + 2;
6055 TRACE("moving edit=(%d,%d)-(%d,%d)\n", rect.left, rect.top, sz.cx, sz.cy);
6056 MoveWindow(infoPtr->hwndEdit, rect.left, rect.top, sz.cx, sz.cy, FALSE);
6057 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
6058 SetFocus(infoPtr->hwndEdit);
6059 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
6060 return infoPtr->hwndEdit;
6066 * Ensures the specified item is visible, scrolling into view if necessary.
6069 * [I] infoPtr : valid pointer to the listview structure
6070 * [I] nItem : item index
6071 * [I] bPartial : partially or entirely visible
6077 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
6079 INT nScrollPosHeight = 0;
6080 INT nScrollPosWidth = 0;
6081 INT nHorzAdjust = 0;
6082 INT nVertAdjust = 0;
6085 RECT rcItem, rcTemp;
6087 rcItem.left = LVIR_BOUNDS;
6088 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
6090 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
6092 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
6094 /* scroll left/right, but in LV_VIEW_DETAILS mode */
6095 if (infoPtr->uView == LV_VIEW_LIST)
6096 nScrollPosWidth = infoPtr->nItemWidth;
6097 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
6098 nScrollPosWidth = 1;
6100 if (rcItem.left < infoPtr->rcList.left)
6103 if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.left - infoPtr->rcList.left;
6108 if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.right - infoPtr->rcList.right;
6112 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
6114 /* scroll up/down, but not in LVS_LIST mode */
6115 if (infoPtr->uView == LV_VIEW_DETAILS)
6116 nScrollPosHeight = infoPtr->nItemHeight;
6117 else if ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON))
6118 nScrollPosHeight = 1;
6120 if (rcItem.top < infoPtr->rcList.top)
6123 if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
6128 if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
6132 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
6134 if (nScrollPosWidth)
6136 INT diff = nHorzDiff / nScrollPosWidth;
6137 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
6138 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff);
6141 if (nScrollPosHeight)
6143 INT diff = nVertDiff / nScrollPosHeight;
6144 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
6145 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff);
6153 * Searches for an item with specific characteristics.
6156 * [I] hwnd : window handle
6157 * [I] nStart : base item index
6158 * [I] lpFindInfo : item information to look for
6161 * SUCCESS : index of item
6164 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
6165 const LVFINDINFOW *lpFindInfo)
6167 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6168 BOOL bWrap = FALSE, bNearest = FALSE;
6169 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
6170 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
6171 POINT Position, Destination;
6174 /* Search in virtual listviews should be done by application, not by
6175 listview control, so we just send LVN_ODFINDITEMW and return the result */
6176 if (infoPtr->dwStyle & LVS_OWNERDATA)
6180 nmlv.iStart = nStart;
6181 nmlv.lvfi = *lpFindInfo;
6182 return notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
6185 if (!lpFindInfo || nItem < 0) return -1;
6188 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL) ||
6189 lpFindInfo->flags & LVFI_SUBSTRING)
6191 lvItem.mask |= LVIF_TEXT;
6192 lvItem.pszText = szDispText;
6193 lvItem.cchTextMax = DISP_TEXT_SIZE;
6196 if (lpFindInfo->flags & LVFI_WRAP)
6199 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
6200 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON))
6205 LISTVIEW_GetOrigin(infoPtr, &Origin);
6206 Destination.x = lpFindInfo->pt.x - Origin.x;
6207 Destination.y = lpFindInfo->pt.y - Origin.y;
6208 switch(lpFindInfo->vkDirection)
6210 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
6211 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
6212 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
6213 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
6214 case VK_HOME: Destination.x = Destination.y = 0; break;
6215 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
6216 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
6218 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
6219 Destination.x = rcArea.right;
6220 Destination.y = rcArea.bottom;
6222 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
6226 else Destination.x = Destination.y = 0;
6228 /* if LVFI_PARAM is specified, all other flags are ignored */
6229 if (lpFindInfo->flags & LVFI_PARAM)
6231 lvItem.mask |= LVIF_PARAM;
6233 lvItem.mask &= ~LVIF_TEXT;
6237 for (; nItem < nLast; nItem++)
6239 lvItem.iItem = nItem;
6240 lvItem.iSubItem = 0;
6241 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6243 if (lvItem.mask & LVIF_PARAM)
6245 if (lpFindInfo->lParam == lvItem.lParam)
6251 if (lvItem.mask & LVIF_TEXT)
6253 if (lpFindInfo->flags & (LVFI_PARTIAL | LVFI_SUBSTRING))
6255 WCHAR *p = strstrW(lvItem.pszText, lpFindInfo->psz);
6256 if (!p || p != lvItem.pszText) continue;
6260 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
6264 if (!bNearest) return nItem;
6266 /* This is very inefficient. To do a good job here,
6267 * we need a sorted array of (x,y) item positions */
6268 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
6270 /* compute the distance^2 to the destination */
6271 xdist = Destination.x - Position.x;
6272 ydist = Destination.y - Position.y;
6273 dist = xdist * xdist + ydist * ydist;
6275 /* remember the distance, and item if it's closer */
6279 nNearestItem = nItem;
6286 nLast = min(nStart + 1, infoPtr->nItemCount);
6291 return nNearestItem;
6296 * Searches for an item with specific characteristics.
6299 * [I] hwnd : window handle
6300 * [I] nStart : base item index
6301 * [I] lpFindInfo : item information to look for
6304 * SUCCESS : index of item
6307 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
6308 const LVFINDINFOA *lpFindInfo)
6310 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL) ||
6311 lpFindInfo->flags & LVFI_SUBSTRING;
6316 memcpy(&fiw, lpFindInfo, sizeof(fiw));
6317 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
6318 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
6319 textfreeT(strW, FALSE);
6325 * Retrieves column attributes.
6328 * [I] infoPtr : valid pointer to the listview structure
6329 * [I] nColumn : column index
6330 * [IO] lpColumn : column information
6331 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
6332 * otherwise it is in fact a LPLVCOLUMNA
6338 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
6340 COLUMN_INFO *lpColumnInfo;
6343 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6344 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6346 /* initialize memory */
6347 ZeroMemory(&hdi, sizeof(hdi));
6349 if (lpColumn->mask & LVCF_TEXT)
6351 hdi.mask |= HDI_TEXT;
6352 hdi.pszText = lpColumn->pszText;
6353 hdi.cchTextMax = lpColumn->cchTextMax;
6356 if (lpColumn->mask & LVCF_IMAGE)
6357 hdi.mask |= HDI_IMAGE;
6359 if (lpColumn->mask & LVCF_ORDER)
6360 hdi.mask |= HDI_ORDER;
6362 if (lpColumn->mask & LVCF_SUBITEM)
6363 hdi.mask |= HDI_LPARAM;
6365 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
6367 if (lpColumn->mask & LVCF_FMT)
6368 lpColumn->fmt = lpColumnInfo->fmt;
6370 if (lpColumn->mask & LVCF_WIDTH)
6371 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
6373 if (lpColumn->mask & LVCF_IMAGE)
6374 lpColumn->iImage = hdi.iImage;
6376 if (lpColumn->mask & LVCF_ORDER)
6377 lpColumn->iOrder = hdi.iOrder;
6379 if (lpColumn->mask & LVCF_SUBITEM)
6380 lpColumn->iSubItem = hdi.lParam;
6382 if (lpColumn->mask & LVCF_MINWIDTH)
6383 lpColumn->cxMin = lpColumnInfo->cxMin;
6389 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
6391 TRACE("iCount=%d, lpiArray=%p\n", iCount, lpiArray);
6396 return SendMessageW(infoPtr->hwndHeader, HDM_GETORDERARRAY, iCount, (LPARAM)lpiArray);
6401 * Retrieves the column width.
6404 * [I] infoPtr : valid pointer to the listview structure
6405 * [I] int : column index
6408 * SUCCESS : column width
6411 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
6413 INT nColumnWidth = 0;
6416 TRACE("nColumn=%d\n", nColumn);
6418 /* we have a 'column' in LIST and REPORT mode only */
6419 switch(infoPtr->uView)
6422 nColumnWidth = infoPtr->nItemWidth;
6424 case LV_VIEW_DETAILS:
6425 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDN_ITEMCHANGED.
6426 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
6427 * HDN_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
6429 * TODO: should we do the same in LVM_GETCOLUMN?
6431 hdItem.mask = HDI_WIDTH;
6432 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
6434 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
6437 nColumnWidth = hdItem.cxy;
6441 TRACE("nColumnWidth=%d\n", nColumnWidth);
6442 return nColumnWidth;
6447 * In list or report display mode, retrieves the number of items that can fit
6448 * vertically in the visible area. In icon or small icon display mode,
6449 * retrieves the total number of visible items.
6452 * [I] infoPtr : valid pointer to the listview structure
6455 * Number of fully visible items.
6457 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
6459 switch (infoPtr->uView)
6462 case LV_VIEW_SMALLICON:
6463 return infoPtr->nItemCount;
6464 case LV_VIEW_DETAILS:
6465 return LISTVIEW_GetCountPerColumn(infoPtr);
6467 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
6475 * Retrieves an image list handle.
6478 * [I] infoPtr : valid pointer to the listview structure
6479 * [I] nImageList : image list identifier
6482 * SUCCESS : image list handle
6485 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
6489 case LVSIL_NORMAL: return infoPtr->himlNormal;
6490 case LVSIL_SMALL: return infoPtr->himlSmall;
6491 case LVSIL_STATE: return infoPtr->himlState;
6492 case LVSIL_GROUPHEADER:
6493 FIXME("LVSIL_GROUPHEADER not supported\n");
6496 WARN("got unknown imagelist index - %d\n", nImageList);
6501 /* LISTVIEW_GetISearchString */
6505 * Retrieves item attributes.
6508 * [I] hwnd : window handle
6509 * [IO] lpLVItem : item info
6510 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
6511 * if FALSE, then lpLVItem is a LPLVITEMA.
6514 * This is the internal 'GetItem' interface -- it tries to
6515 * be smart and avoid text copies, if possible, by modifying
6516 * lpLVItem->pszText to point to the text string. Please note
6517 * that this is not always possible (e.g. OWNERDATA), so on
6518 * entry you *must* supply valid values for pszText, and cchTextMax.
6519 * The only difference to the documented interface is that upon
6520 * return, you should use *only* the lpLVItem->pszText, rather than
6521 * the buffer pointer you provided on input. Most code already does
6522 * that, so it's not a problem.
6523 * For the two cases when the text must be copied (that is,
6524 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
6530 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
6532 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
6533 NMLVDISPINFOW dispInfo;
6539 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6541 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
6544 if (lpLVItem->mask == 0) return TRUE;
6545 TRACE("mask=%x\n", lpLVItem->mask);
6547 /* make a local copy */
6548 isubitem = lpLVItem->iSubItem;
6550 /* a quick optimization if all we're asked is the focus state
6551 * these queries are worth optimising since they are common,
6552 * and can be answered in constant time, without the heavy accesses */
6553 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
6554 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
6556 lpLVItem->state = 0;
6557 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6558 lpLVItem->state |= LVIS_FOCUSED;
6562 ZeroMemory(&dispInfo, sizeof(dispInfo));
6564 /* if the app stores all the data, handle it separately */
6565 if (infoPtr->dwStyle & LVS_OWNERDATA)
6567 dispInfo.item.state = 0;
6569 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
6570 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) ||
6571 ((lpLVItem->mask & LVIF_STATE) && (infoPtr->uCallbackMask & lpLVItem->stateMask)))
6573 UINT mask = lpLVItem->mask;
6575 /* NOTE: copy only fields which we _know_ are initialized, some apps
6576 * depend on the uninitialized fields being 0 */
6577 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
6578 dispInfo.item.iItem = lpLVItem->iItem;
6579 dispInfo.item.iSubItem = isubitem;
6580 if (lpLVItem->mask & LVIF_TEXT)
6582 if (lpLVItem->mask & LVIF_NORECOMPUTE)
6584 dispInfo.item.mask &= ~(LVIF_TEXT | LVIF_NORECOMPUTE);
6587 dispInfo.item.pszText = lpLVItem->pszText;
6588 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
6591 if (lpLVItem->mask & LVIF_STATE)
6592 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
6593 /* could be zeroed on LVIF_NORECOMPUTE case */
6594 if (dispInfo.item.mask)
6596 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
6597 dispInfo.item.stateMask = lpLVItem->stateMask;
6598 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6600 /* full size structure expected - _WIN32IE >= 0x560 */
6601 *lpLVItem = dispInfo.item;
6603 else if (lpLVItem->mask & LVIF_INDENT)
6605 /* indent member expected - _WIN32IE >= 0x300 */
6606 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
6610 /* minimal structure expected */
6611 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
6613 lpLVItem->mask = mask;
6614 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
6618 /* make sure lParam is zeroed out */
6619 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
6621 /* callback marked pointer required here */
6622 if ((lpLVItem->mask & LVIF_TEXT) && (lpLVItem->mask & LVIF_NORECOMPUTE))
6623 lpLVItem->pszText = LPSTR_TEXTCALLBACKW;
6625 /* we store only a little state, so if we're not asked, we're done */
6626 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
6628 /* if focus is handled by us, report it */
6629 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
6631 lpLVItem->state &= ~LVIS_FOCUSED;
6632 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6633 lpLVItem->state |= LVIS_FOCUSED;
6636 /* and do the same for selection, if we handle it */
6637 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
6639 lpLVItem->state &= ~LVIS_SELECTED;
6640 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
6641 lpLVItem->state |= LVIS_SELECTED;
6647 /* find the item and subitem structures before we proceed */
6648 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
6649 lpItem = DPA_GetPtr(hdpaSubItems, 0);
6654 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
6655 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
6658 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
6663 pItemHdr = &lpItem->hdr;
6665 /* Do we need to query the state from the app? */
6666 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
6668 dispInfo.item.mask |= LVIF_STATE;
6669 dispInfo.item.stateMask = infoPtr->uCallbackMask;
6672 /* Do we need to enquire about the image? */
6673 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
6674 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
6676 dispInfo.item.mask |= LVIF_IMAGE;
6677 dispInfo.item.iImage = I_IMAGECALLBACK;
6680 /* Only items support indentation */
6681 if ((lpLVItem->mask & LVIF_INDENT) && lpItem->iIndent == I_INDENTCALLBACK &&
6684 dispInfo.item.mask |= LVIF_INDENT;
6685 dispInfo.item.iIndent = I_INDENTCALLBACK;
6688 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
6689 if ((lpLVItem->mask & LVIF_TEXT) && !(lpLVItem->mask & LVIF_NORECOMPUTE) &&
6690 !is_text(pItemHdr->pszText))
6692 dispInfo.item.mask |= LVIF_TEXT;
6693 dispInfo.item.pszText = lpLVItem->pszText;
6694 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
6695 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
6696 *dispInfo.item.pszText = '\0';
6699 /* If we don't have all the requested info, query the application */
6700 if (dispInfo.item.mask)
6702 dispInfo.item.iItem = lpLVItem->iItem;
6703 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
6704 dispInfo.item.lParam = lpItem->lParam;
6705 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
6706 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
6709 /* we should not store values for subitems */
6710 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
6712 /* Now, handle the iImage field */
6713 if (dispInfo.item.mask & LVIF_IMAGE)
6715 lpLVItem->iImage = dispInfo.item.iImage;
6716 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
6717 pItemHdr->iImage = dispInfo.item.iImage;
6719 else if (lpLVItem->mask & LVIF_IMAGE)
6721 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
6722 lpLVItem->iImage = pItemHdr->iImage;
6724 lpLVItem->iImage = 0;
6727 /* The pszText field */
6728 if (dispInfo.item.mask & LVIF_TEXT)
6730 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
6731 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
6733 lpLVItem->pszText = dispInfo.item.pszText;
6735 else if (lpLVItem->mask & LVIF_TEXT)
6737 /* if LVN_GETDISPINFO's disabled with LVIF_NORECOMPUTE return callback placeholder */
6738 if (isW || !is_text(pItemHdr->pszText)) lpLVItem->pszText = pItemHdr->pszText;
6739 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
6742 /* Next is the lParam field */
6743 if (dispInfo.item.mask & LVIF_PARAM)
6745 lpLVItem->lParam = dispInfo.item.lParam;
6746 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
6747 lpItem->lParam = dispInfo.item.lParam;
6749 else if (lpLVItem->mask & LVIF_PARAM)
6750 lpLVItem->lParam = lpItem->lParam;
6752 /* if this is a subitem, we're done */
6753 if (isubitem) return TRUE;
6755 /* ... the state field (this one is different due to uCallbackmask) */
6756 if (lpLVItem->mask & LVIF_STATE)
6758 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
6759 if (dispInfo.item.mask & LVIF_STATE)
6761 lpLVItem->state &= ~dispInfo.item.stateMask;
6762 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
6764 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
6766 lpLVItem->state &= ~LVIS_FOCUSED;
6767 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6768 lpLVItem->state |= LVIS_FOCUSED;
6770 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
6772 lpLVItem->state &= ~LVIS_SELECTED;
6773 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
6774 lpLVItem->state |= LVIS_SELECTED;
6778 /* and last, but not least, the indent field */
6779 if (dispInfo.item.mask & LVIF_INDENT)
6781 lpLVItem->iIndent = dispInfo.item.iIndent;
6782 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && lpItem->iIndent == I_INDENTCALLBACK)
6783 lpItem->iIndent = dispInfo.item.iIndent;
6785 else if (lpLVItem->mask & LVIF_INDENT)
6787 lpLVItem->iIndent = lpItem->iIndent;
6795 * Retrieves item attributes.
6798 * [I] hwnd : window handle
6799 * [IO] lpLVItem : item info
6800 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
6801 * if FALSE, then lpLVItem is a LPLVITEMA.
6804 * This is the external 'GetItem' interface -- it properly copies
6805 * the text in the provided buffer.
6811 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
6816 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
6819 pszText = lpLVItem->pszText;
6820 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
6821 if (bResult && (lpLVItem->mask & LVIF_TEXT) && lpLVItem->pszText != pszText)
6823 if (lpLVItem->pszText != LPSTR_TEXTCALLBACKW)
6824 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
6826 pszText = LPSTR_TEXTCALLBACKW;
6828 lpLVItem->pszText = pszText;
6836 * Retrieves the position (upper-left) of the listview control item.
6837 * Note that for LVS_ICON style, the upper-left is that of the icon
6838 * and not the bounding box.
6841 * [I] infoPtr : valid pointer to the listview structure
6842 * [I] nItem : item index
6843 * [O] lpptPosition : coordinate information
6849 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
6853 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
6855 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
6857 LISTVIEW_GetOrigin(infoPtr, &Origin);
6858 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
6860 if (infoPtr->uView == LV_VIEW_ICON)
6862 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
6863 lpptPosition->y += ICON_TOP_PADDING;
6865 lpptPosition->x += Origin.x;
6866 lpptPosition->y += Origin.y;
6868 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
6875 * Retrieves the bounding rectangle for a listview control item.
6878 * [I] infoPtr : valid pointer to the listview structure
6879 * [I] nItem : item index
6880 * [IO] lprc : bounding rectangle coordinates
6881 * lprc->left specifies the portion of the item for which the bounding
6882 * rectangle will be retrieved.
6884 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
6885 * including the icon and label.
6888 * * Experiment shows that native control returns:
6889 * * width = min (48, length of text line)
6890 * * .left = position.x - (width - iconsize.cx)/2
6891 * * .right = .left + width
6892 * * height = #lines of text * ntmHeight + icon height + 8
6893 * * .top = position.y - 2
6894 * * .bottom = .top + height
6895 * * separation between items .y = itemSpacing.cy - height
6896 * * .x = itemSpacing.cx - width
6897 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
6900 * * Experiment shows that native control returns:
6901 * * width = iconSize.cx + 16
6902 * * .left = position.x - (width - iconsize.cx)/2
6903 * * .right = .left + width
6904 * * height = iconSize.cy + 4
6905 * * .top = position.y - 2
6906 * * .bottom = .top + height
6907 * * separation between items .y = itemSpacing.cy - height
6908 * * .x = itemSpacing.cx - width
6909 * LVIR_LABEL Returns the bounding rectangle of the item text.
6912 * * Experiment shows that native control returns:
6913 * * width = text length
6914 * * .left = position.x - width/2
6915 * * .right = .left + width
6916 * * height = ntmH * linecount + 2
6917 * * .top = position.y + iconSize.cy + 6
6918 * * .bottom = .top + height
6919 * * separation between items .y = itemSpacing.cy - height
6920 * * .x = itemSpacing.cx - width
6921 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
6922 * rectangles, but excludes columns in report view.
6929 * Note that the bounding rectangle of the label in the LVS_ICON view depends
6930 * upon whether the window has the focus currently and on whether the item
6931 * is the one with the focus. Ensure that the control's record of which
6932 * item has the focus agrees with the items' records.
6934 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
6936 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6937 BOOL doLabel = TRUE, oversizedBox = FALSE;
6938 POINT Position, Origin;
6942 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
6944 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
6946 LISTVIEW_GetOrigin(infoPtr, &Origin);
6947 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
6949 /* Be smart and try to figure out the minimum we have to do */
6950 if (lprc->left == LVIR_ICON) doLabel = FALSE;
6951 if (infoPtr->uView == LV_VIEW_DETAILS && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
6952 if (infoPtr->uView == LV_VIEW_ICON && lprc->left != LVIR_ICON &&
6953 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
6954 oversizedBox = TRUE;
6956 /* get what we need from the item before hand, so we make
6957 * only one request. This can speed up things, if data
6958 * is stored on the app side */
6960 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
6961 if (doLabel) lvItem.mask |= LVIF_TEXT;
6962 lvItem.iItem = nItem;
6963 lvItem.iSubItem = 0;
6964 lvItem.pszText = szDispText;
6965 lvItem.cchTextMax = DISP_TEXT_SIZE;
6966 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
6967 /* we got the state already up, simulate it here, to avoid a reget */
6968 if (infoPtr->uView == LV_VIEW_ICON && (lprc->left != LVIR_ICON))
6970 lvItem.mask |= LVIF_STATE;
6971 lvItem.stateMask = LVIS_FOCUSED;
6972 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
6975 if (infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
6976 lprc->left = LVIR_BOUNDS;
6982 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
6986 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
6990 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
6993 case LVIR_SELECTBOUNDS:
6994 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
6998 WARN("Unknown value: %d\n", lprc->left);
7002 if (infoPtr->uView == LV_VIEW_DETAILS)
7004 if (mode != LVIR_BOUNDS)
7005 OffsetRect(lprc, Origin.x + LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left,
7006 Position.y + Origin.y);
7008 OffsetRect(lprc, Origin.x, Position.y + Origin.y);
7011 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
7013 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
7020 * Retrieves the spacing between listview control items.
7023 * [I] infoPtr : valid pointer to the listview structure
7024 * [IO] lprc : rectangle to receive the output
7025 * on input, lprc->top = nSubItem
7026 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
7028 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
7029 * not only those of the first column.
7030 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
7036 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
7038 POINT Position, Origin;
7042 if (!lprc) return FALSE;
7044 nColumn = lprc->top;
7046 TRACE("(nItem=%d, nSubItem=%d, type=%d)\n", nItem, lprc->top, lprc->left);
7047 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
7049 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
7051 if (infoPtr->uView != LV_VIEW_DETAILS) return FALSE;
7053 /* special case for header items */
7056 if (lprc->left != LVIR_BOUNDS)
7058 FIXME("Only LVIR_BOUNDS is implemented for header, got %d\n", lprc->left);
7062 if (infoPtr->hwndHeader)
7063 return SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, lprc->top, (LPARAM)lprc);
7066 memset(lprc, 0, sizeof(RECT));
7071 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
7072 LISTVIEW_GetOrigin(infoPtr, &Origin);
7074 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7077 lvItem.iItem = nItem;
7078 lvItem.iSubItem = nColumn;
7083 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
7088 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
7092 ERR("Unknown bounds=%d\n", lprc->left);
7096 OffsetRect(lprc, Origin.x, Position.y);
7097 TRACE("return rect %s\n", wine_dbgstr_rect(lprc));
7104 * Retrieves the spacing between listview control items.
7107 * [I] infoPtr : valid pointer to the listview structure
7108 * [I] bSmall : flag for small or large icon
7111 * Horizontal + vertical spacing
7113 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
7119 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7123 if (infoPtr->uView == LV_VIEW_ICON)
7124 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
7126 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
7133 * Retrieves the state of a listview control item.
7136 * [I] infoPtr : valid pointer to the listview structure
7137 * [I] nItem : item index
7138 * [I] uMask : state mask
7141 * State specified by the mask.
7143 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
7147 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
7149 lvItem.iItem = nItem;
7150 lvItem.iSubItem = 0;
7151 lvItem.mask = LVIF_STATE;
7152 lvItem.stateMask = uMask;
7153 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
7155 return lvItem.state & uMask;
7160 * Retrieves the text of a listview control item or subitem.
7163 * [I] hwnd : window handle
7164 * [I] nItem : item index
7165 * [IO] lpLVItem : item information
7166 * [I] isW : TRUE if lpLVItem is Unicode
7169 * SUCCESS : string length
7172 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
7174 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
7176 lpLVItem->mask = LVIF_TEXT;
7177 lpLVItem->iItem = nItem;
7178 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
7180 return textlenT(lpLVItem->pszText, isW);
7185 * Searches for an item based on properties + relationships.
7188 * [I] infoPtr : valid pointer to the listview structure
7189 * [I] nItem : item index
7190 * [I] uFlags : relationship flag
7193 * SUCCESS : item index
7196 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
7199 LVFINDINFOW lvFindInfo;
7200 INT nCountPerColumn;
7204 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
7205 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
7207 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
7209 if (uFlags & LVNI_CUT)
7212 if (uFlags & LVNI_DROPHILITED)
7213 uMask |= LVIS_DROPHILITED;
7215 if (uFlags & LVNI_FOCUSED)
7216 uMask |= LVIS_FOCUSED;
7218 if (uFlags & LVNI_SELECTED)
7219 uMask |= LVIS_SELECTED;
7221 /* if we're asked for the focused item, that's only one,
7222 * so it's worth optimizing */
7223 if (uFlags & LVNI_FOCUSED)
7225 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
7226 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
7229 if (uFlags & LVNI_ABOVE)
7231 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
7236 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7242 /* Special case for autoarrange - move 'til the top of a list */
7243 if (is_autoarrange(infoPtr))
7245 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7246 while (nItem - nCountPerRow >= 0)
7248 nItem -= nCountPerRow;
7249 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7254 lvFindInfo.flags = LVFI_NEARESTXY;
7255 lvFindInfo.vkDirection = VK_UP;
7256 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7257 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7259 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7264 else if (uFlags & LVNI_BELOW)
7266 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
7268 while (nItem < infoPtr->nItemCount)
7271 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7277 /* Special case for autoarrange - move 'til the bottom of a list */
7278 if (is_autoarrange(infoPtr))
7280 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7281 while (nItem + nCountPerRow < infoPtr->nItemCount )
7283 nItem += nCountPerRow;
7284 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7289 lvFindInfo.flags = LVFI_NEARESTXY;
7290 lvFindInfo.vkDirection = VK_DOWN;
7291 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7292 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7294 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7299 else if (uFlags & LVNI_TOLEFT)
7301 if (infoPtr->uView == LV_VIEW_LIST)
7303 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
7304 while (nItem - nCountPerColumn >= 0)
7306 nItem -= nCountPerColumn;
7307 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7311 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7313 /* Special case for autoarrange - move 'til the beginning of a row */
7314 if (is_autoarrange(infoPtr))
7316 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7317 while (nItem % nCountPerRow > 0)
7320 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7325 lvFindInfo.flags = LVFI_NEARESTXY;
7326 lvFindInfo.vkDirection = VK_LEFT;
7327 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7328 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7330 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7335 else if (uFlags & LVNI_TORIGHT)
7337 if (infoPtr->uView == LV_VIEW_LIST)
7339 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
7340 while (nItem + nCountPerColumn < infoPtr->nItemCount)
7342 nItem += nCountPerColumn;
7343 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7347 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7349 /* Special case for autoarrange - move 'til the end of a row */
7350 if (is_autoarrange(infoPtr))
7352 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7353 while (nItem % nCountPerRow < nCountPerRow - 1 )
7356 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7361 lvFindInfo.flags = LVFI_NEARESTXY;
7362 lvFindInfo.vkDirection = VK_RIGHT;
7363 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7364 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7366 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7375 /* search by index */
7376 for (i = nItem; i < infoPtr->nItemCount; i++)
7378 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
7386 /* LISTVIEW_GetNumberOfWorkAreas */
7390 * Retrieves the origin coordinates when in icon or small icon display mode.
7393 * [I] infoPtr : valid pointer to the listview structure
7394 * [O] lpptOrigin : coordinate information
7399 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
7401 INT nHorzPos = 0, nVertPos = 0;
7402 SCROLLINFO scrollInfo;
7404 scrollInfo.cbSize = sizeof(SCROLLINFO);
7405 scrollInfo.fMask = SIF_POS;
7407 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
7408 nHorzPos = scrollInfo.nPos;
7409 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
7410 nVertPos = scrollInfo.nPos;
7412 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
7414 lpptOrigin->x = infoPtr->rcList.left;
7415 lpptOrigin->y = infoPtr->rcList.top;
7416 if (infoPtr->uView == LV_VIEW_LIST)
7417 nHorzPos *= infoPtr->nItemWidth;
7418 else if (infoPtr->uView == LV_VIEW_DETAILS)
7419 nVertPos *= infoPtr->nItemHeight;
7421 lpptOrigin->x -= nHorzPos;
7422 lpptOrigin->y -= nVertPos;
7424 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
7429 * Retrieves the width of a string.
7432 * [I] hwnd : window handle
7433 * [I] lpszText : text string to process
7434 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
7437 * SUCCESS : string width (in pixels)
7440 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
7445 if (is_text(lpszText))
7447 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
7448 HDC hdc = GetDC(infoPtr->hwndSelf);
7449 HFONT hOldFont = SelectObject(hdc, hFont);
7452 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
7454 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
7455 SelectObject(hdc, hOldFont);
7456 ReleaseDC(infoPtr->hwndSelf, hdc);
7458 return stringSize.cx;
7463 * Determines which listview item is located at the specified position.
7466 * [I] infoPtr : valid pointer to the listview structure
7467 * [IO] lpht : hit test information
7468 * [I] subitem : fill out iSubItem.
7469 * [I] select : return the index only if the hit selects the item
7472 * (mm 20001022): We must not allow iSubItem to be touched, for
7473 * an app might pass only a structure with space up to iItem!
7474 * (MS Office 97 does that for instance in the file open dialog)
7477 * SUCCESS : item index
7480 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
7482 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
7483 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
7484 POINT Origin, Position, opt;
7489 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
7493 if (subitem) lpht->iSubItem = 0;
7495 LISTVIEW_GetOrigin(infoPtr, &Origin);
7497 /* set whole list relation flags */
7498 if (subitem && infoPtr->uView == LV_VIEW_DETAILS)
7500 /* LVM_SUBITEMHITTEST checks left bound of possible client area */
7501 if (infoPtr->rcList.left > lpht->pt.x && Origin.x < lpht->pt.x)
7502 lpht->flags |= LVHT_TOLEFT;
7504 if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
7505 opt.y = lpht->pt.y + infoPtr->rcList.top;
7509 if (infoPtr->rcList.bottom < opt.y)
7510 lpht->flags |= LVHT_BELOW;
7514 if (infoPtr->rcList.left > lpht->pt.x)
7515 lpht->flags |= LVHT_TOLEFT;
7516 else if (infoPtr->rcList.right < lpht->pt.x)
7517 lpht->flags |= LVHT_TORIGHT;
7519 if (infoPtr->rcList.top > lpht->pt.y)
7520 lpht->flags |= LVHT_ABOVE;
7521 else if (infoPtr->rcList.bottom < lpht->pt.y)
7522 lpht->flags |= LVHT_BELOW;
7525 /* even if item is invalid try to find subitem */
7526 if (infoPtr->uView == LV_VIEW_DETAILS && subitem)
7531 opt.x = lpht->pt.x - Origin.x;
7533 lpht->iSubItem = -1;
7534 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
7536 pRect = &LISTVIEW_GetColumnInfo(infoPtr, j)->rcHeader;
7538 if ((opt.x >= pRect->left) && (opt.x < pRect->right))
7544 TRACE("lpht->iSubItem=%d\n", lpht->iSubItem);
7546 /* if we're outside horizontal columns bounds there's nothing to test further */
7547 if (lpht->iSubItem == -1)
7550 lpht->flags = LVHT_NOWHERE;
7555 TRACE("lpht->flags=0x%x\n", lpht->flags);
7556 if (lpht->flags) return -1;
7558 lpht->flags |= LVHT_NOWHERE;
7560 /* first deal with the large items */
7561 rcSearch.left = lpht->pt.x;
7562 rcSearch.top = lpht->pt.y;
7563 rcSearch.right = rcSearch.left + 1;
7564 rcSearch.bottom = rcSearch.top + 1;
7566 iterator_frameditems(&i, infoPtr, &rcSearch);
7567 iterator_next(&i); /* go to first item in the sequence */
7569 iterator_destroy(&i);
7571 TRACE("lpht->iItem=%d\n", iItem);
7572 if (iItem == -1) return -1;
7574 lvItem.mask = LVIF_STATE | LVIF_TEXT;
7575 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
7576 lvItem.stateMask = LVIS_STATEIMAGEMASK;
7577 if (infoPtr->uView == LV_VIEW_ICON) lvItem.stateMask |= LVIS_FOCUSED;
7578 lvItem.iItem = iItem;
7579 lvItem.iSubItem = subitem ? lpht->iSubItem : 0;
7580 lvItem.pszText = szDispText;
7581 lvItem.cchTextMax = DISP_TEXT_SIZE;
7582 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
7583 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
7585 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
7586 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
7587 opt.x = lpht->pt.x - Position.x - Origin.x;
7589 if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
7590 opt.y = lpht->pt.y - Position.y - Origin.y + infoPtr->rcList.top;
7592 opt.y = lpht->pt.y - Position.y - Origin.y;
7594 if (infoPtr->uView == LV_VIEW_DETAILS)
7597 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
7598 opt.x = lpht->pt.x - Origin.x;
7602 UnionRect(&rcBounds, &rcIcon, &rcLabel);
7603 UnionRect(&rcBounds, &rcBounds, &rcState);
7605 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
7606 if (!PtInRect(&rcBounds, opt)) return -1;
7608 if (PtInRect(&rcIcon, opt))
7609 lpht->flags |= LVHT_ONITEMICON;
7610 else if (PtInRect(&rcLabel, opt))
7611 lpht->flags |= LVHT_ONITEMLABEL;
7612 else if (infoPtr->himlState && PtInRect(&rcState, opt))
7613 lpht->flags |= LVHT_ONITEMSTATEICON;
7614 /* special case for LVS_EX_FULLROWSELECT */
7615 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
7616 !(lpht->flags & LVHT_ONITEM))
7618 lpht->flags = LVHT_ONITEM | LVHT_ABOVE;
7620 if (lpht->flags & LVHT_ONITEM)
7621 lpht->flags &= ~LVHT_NOWHERE;
7622 TRACE("lpht->flags=0x%x\n", lpht->flags);
7624 if (select && !(infoPtr->uView == LV_VIEW_DETAILS &&
7625 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
7626 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
7628 if (infoPtr->uView == LV_VIEW_DETAILS)
7630 /* get main item bounds */
7631 lvItem.iSubItem = 0;
7632 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
7633 UnionRect(&rcBounds, &rcIcon, &rcLabel);
7634 UnionRect(&rcBounds, &rcBounds, &rcState);
7636 if (!PtInRect(&rcBounds, opt)) iItem = -1;
7638 return lpht->iItem = iItem;
7643 * Inserts a new item in the listview control.
7646 * [I] infoPtr : valid pointer to the listview structure
7647 * [I] lpLVItem : item information
7648 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
7651 * SUCCESS : new item index
7654 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
7661 BOOL is_sorted, has_changed;
7663 HWND hwndSelf = infoPtr->hwndSelf;
7665 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
7667 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
7669 /* make sure it's an item, and not a subitem; cannot insert a subitem */
7670 if (!lpLVItem || lpLVItem->iSubItem) return -1;
7672 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
7674 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
7676 /* insert item in listview control data structure */
7677 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
7678 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
7680 /* link with id struct */
7681 if (!(lpID = Alloc(sizeof(ITEM_ID)))) goto fail;
7683 lpID->item = hdpaSubItems;
7684 lpID->id = get_next_itemid(infoPtr);
7685 if ( DPA_InsertPtr(infoPtr->hdpaItemIds, infoPtr->nItemCount, lpID) == -1) goto fail;
7687 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
7688 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
7690 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
7692 /* calculate new item index */
7699 while (i < infoPtr->nItemCount)
7701 hItem = DPA_GetPtr( infoPtr->hdpaItems, i);
7702 item_s = DPA_GetPtr(hItem, 0);
7704 cmpv = textcmpWT(item_s->hdr.pszText, lpLVItem->pszText, isW);
7705 if (infoPtr->dwStyle & LVS_SORTDESCENDING) cmpv *= -1;
7707 if (cmpv >= 0) break;
7713 nItem = min(lpLVItem->iItem, infoPtr->nItemCount);
7715 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
7716 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
7717 if (nItem == -1) goto fail;
7718 infoPtr->nItemCount++;
7720 /* shift indices first so they don't get tangled */
7721 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
7723 /* set the item attributes */
7724 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
7726 /* full size structure expected - _WIN32IE >= 0x560 */
7729 else if (lpLVItem->mask & LVIF_INDENT)
7731 /* indent member expected - _WIN32IE >= 0x300 */
7732 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
7736 /* minimal structure expected */
7737 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
7740 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7742 item.mask |= LVIF_STATE;
7743 item.stateMask |= LVIS_STATEIMAGEMASK;
7744 item.state &= ~LVIS_STATEIMAGEMASK;
7745 item.state |= INDEXTOSTATEIMAGEMASK(1);
7747 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
7749 /* make room for the position, if we are in the right mode */
7750 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7752 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
7754 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
7756 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
7761 /* send LVN_INSERTITEM notification */
7762 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
7764 nmlv.lParam = lpItem->lParam;
7765 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
7766 if (!IsWindow(hwndSelf))
7769 /* align items (set position of each item) */
7770 if (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON)
7774 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
7775 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
7777 LISTVIEW_NextIconPosTop(infoPtr, &pt);
7779 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
7782 /* now is the invalidation fun */
7783 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
7787 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
7788 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
7789 infoPtr->nItemCount--;
7791 DPA_DeletePtr(hdpaSubItems, 0);
7792 DPA_Destroy (hdpaSubItems);
7799 * Checks item visibility.
7802 * [I] infoPtr : valid pointer to the listview structure
7803 * [I] nFirst : item index to check for
7806 * Item visible : TRUE
7807 * Item invisible or failure : FALSE
7809 static BOOL LISTVIEW_IsItemVisible(const LISTVIEW_INFO *infoPtr, INT nItem)
7811 POINT Origin, Position;
7816 TRACE("nItem=%d\n", nItem);
7818 if (nItem < 0 || nItem >= DPA_GetPtrCount(infoPtr->hdpaItems)) return FALSE;
7820 LISTVIEW_GetOrigin(infoPtr, &Origin);
7821 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
7822 rcItem.left = Position.x + Origin.x;
7823 rcItem.top = Position.y + Origin.y;
7824 rcItem.right = rcItem.left + infoPtr->nItemWidth;
7825 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
7827 hdc = GetDC(infoPtr->hwndSelf);
7828 if (!hdc) return FALSE;
7829 ret = RectVisible(hdc, &rcItem);
7830 ReleaseDC(infoPtr->hwndSelf, hdc);
7837 * Redraws a range of items.
7840 * [I] infoPtr : valid pointer to the listview structure
7841 * [I] nFirst : first item
7842 * [I] nLast : last item
7848 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
7852 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
7853 max(nFirst, nLast) >= infoPtr->nItemCount)
7856 for (i = nFirst; i <= nLast; i++)
7857 LISTVIEW_InvalidateItem(infoPtr, i);
7864 * Scroll the content of a listview.
7867 * [I] infoPtr : valid pointer to the listview structure
7868 * [I] dx : horizontal scroll amount in pixels
7869 * [I] dy : vertical scroll amount in pixels
7876 * If the control is in report view (LV_VIEW_DETAILS) the control can
7877 * be scrolled only in line increments. "dy" will be rounded to the
7878 * nearest number of pixels that are a whole line. Ex: if line height
7879 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
7880 * is passed, then the scroll will be 0. (per MSDN 7/2002)
7882 * For: (per experimentation with native control and CSpy ListView)
7883 * LV_VIEW_ICON scrolling in any direction is allowed
7884 * LV_VIEW_SMALLICON scrolling in any direction is allowed
7885 * LV_VIEW_LIST dx=1 = 1 column (horizontal only)
7886 * but will only scroll 1 column per message
7887 * no matter what the value.
7888 * dy must be 0 or FALSE returned.
7889 * LV_VIEW_DETAILS dx=1 = 1 pixel
7893 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7895 switch(infoPtr->uView) {
7896 case LV_VIEW_DETAILS:
7897 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
7898 dy /= infoPtr->nItemHeight;
7901 if (dy != 0) return FALSE;
7907 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx);
7908 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy);
7915 * Sets the background color.
7918 * [I] infoPtr : valid pointer to the listview structure
7919 * [I] color : background color
7925 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
7927 TRACE("(color=%x)\n", color);
7929 if(infoPtr->clrBk != color) {
7930 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
7931 infoPtr->clrBk = color;
7932 if (color == CLR_NONE)
7933 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
7936 infoPtr->hBkBrush = CreateSolidBrush(color);
7937 infoPtr->dwLvExStyle &= ~LVS_EX_TRANSPARENTBKGND;
7944 /* LISTVIEW_SetBkImage */
7946 /*** Helper for {Insert,Set}ColumnT *only* */
7947 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
7948 const LVCOLUMNW *lpColumn, BOOL isW)
7950 if (lpColumn->mask & LVCF_FMT)
7952 /* format member is valid */
7953 lphdi->mask |= HDI_FORMAT;
7955 /* set text alignment (leftmost column must be left-aligned) */
7956 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
7957 lphdi->fmt |= HDF_LEFT;
7958 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
7959 lphdi->fmt |= HDF_RIGHT;
7960 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
7961 lphdi->fmt |= HDF_CENTER;
7963 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
7964 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
7966 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
7968 lphdi->fmt |= HDF_IMAGE;
7969 lphdi->iImage = I_IMAGECALLBACK;
7972 if (lpColumn->fmt & LVCFMT_FIXED_WIDTH)
7973 lphdi->fmt |= HDF_FIXEDWIDTH;
7976 if (lpColumn->mask & LVCF_WIDTH)
7978 lphdi->mask |= HDI_WIDTH;
7979 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
7981 /* make it fill the remainder of the controls width */
7985 for(item_index = 0; item_index < (nColumn - 1); item_index++)
7987 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
7988 lphdi->cxy += rcHeader.right - rcHeader.left;
7991 /* retrieve the layout of the header */
7992 GetClientRect(infoPtr->hwndSelf, &rcHeader);
7993 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
7995 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
7998 lphdi->cxy = lpColumn->cx;
8001 if (lpColumn->mask & LVCF_TEXT)
8003 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
8004 lphdi->fmt |= HDF_STRING;
8005 lphdi->pszText = lpColumn->pszText;
8006 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
8009 if (lpColumn->mask & LVCF_IMAGE)
8011 lphdi->mask |= HDI_IMAGE;
8012 lphdi->iImage = lpColumn->iImage;
8015 if (lpColumn->mask & LVCF_ORDER)
8017 lphdi->mask |= HDI_ORDER;
8018 lphdi->iOrder = lpColumn->iOrder;
8025 * Inserts a new column.
8028 * [I] infoPtr : valid pointer to the listview structure
8029 * [I] nColumn : column index
8030 * [I] lpColumn : column information
8031 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
8034 * SUCCESS : new column index
8037 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
8038 const LVCOLUMNW *lpColumn, BOOL isW)
8040 COLUMN_INFO *lpColumnInfo;
8044 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
8046 if (!lpColumn || nColumn < 0) return -1;
8047 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
8049 ZeroMemory(&hdi, sizeof(HDITEMW));
8050 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
8053 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
8054 * (can be seen in SPY) otherwise column never gets added.
8056 if (!(lpColumn->mask & LVCF_WIDTH)) {
8057 hdi.mask |= HDI_WIDTH;
8062 * when the iSubItem is available Windows copies it to the header lParam. It seems
8063 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
8065 if (lpColumn->mask & LVCF_SUBITEM)
8067 hdi.mask |= HDI_LPARAM;
8068 hdi.lParam = lpColumn->iSubItem;
8071 /* create header if not present */
8072 LISTVIEW_CreateHeader(infoPtr);
8073 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle) &&
8074 (infoPtr->uView == LV_VIEW_DETAILS) && (WS_VISIBLE & infoPtr->dwStyle))
8076 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8079 /* insert item in header control */
8080 nNewColumn = SendMessageW(infoPtr->hwndHeader,
8081 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
8082 nColumn, (LPARAM)&hdi);
8083 if (nNewColumn == -1) return -1;
8084 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
8086 /* create our own column info */
8087 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
8088 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
8090 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
8091 if (lpColumn->mask & LVCF_MINWIDTH) lpColumnInfo->cxMin = lpColumn->cxMin;
8092 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, nNewColumn, (LPARAM)&lpColumnInfo->rcHeader))
8095 /* now we have to actually adjust the data */
8096 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
8098 SUBITEM_INFO *lpSubItem;
8104 item.iSubItem = nNewColumn;
8105 item.mask = LVIF_TEXT | LVIF_IMAGE;
8106 item.iImage = I_IMAGECALLBACK;
8107 item.pszText = LPSTR_TEXTCALLBACKW;
8109 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
8111 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
8112 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
8114 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
8115 if (lpSubItem->iSubItem >= nNewColumn)
8116 lpSubItem->iSubItem++;
8119 /* add new subitem for each item */
8121 set_sub_item(infoPtr, &item, isW, &changed);
8125 /* make space for the new column */
8126 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8127 LISTVIEW_UpdateItemSize(infoPtr);
8132 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
8135 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
8143 * Sets the attributes of a header item.
8146 * [I] infoPtr : valid pointer to the listview structure
8147 * [I] nColumn : column index
8148 * [I] lpColumn : column attributes
8149 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
8155 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
8156 const LVCOLUMNW *lpColumn, BOOL isW)
8158 HDITEMW hdi, hdiget;
8161 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
8163 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
8165 ZeroMemory(&hdi, sizeof(HDITEMW));
8166 if (lpColumn->mask & LVCF_FMT)
8168 hdi.mask |= HDI_FORMAT;
8169 hdiget.mask = HDI_FORMAT;
8170 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdiget))
8171 hdi.fmt = hdiget.fmt & HDF_STRING;
8173 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
8175 /* set header item attributes */
8176 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, nColumn, (LPARAM)&hdi);
8177 if (!bResult) return FALSE;
8179 if (lpColumn->mask & LVCF_FMT)
8181 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
8182 INT oldFmt = lpColumnInfo->fmt;
8184 lpColumnInfo->fmt = lpColumn->fmt;
8185 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
8187 if (infoPtr->uView == LV_VIEW_DETAILS) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
8191 if (lpColumn->mask & LVCF_MINWIDTH)
8192 LISTVIEW_GetColumnInfo(infoPtr, nColumn)->cxMin = lpColumn->cxMin;
8199 * Sets the column order array
8202 * [I] infoPtr : valid pointer to the listview structure
8203 * [I] iCount : number of elements in column order array
8204 * [I] lpiArray : pointer to column order array
8210 static BOOL LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
8212 TRACE("iCount %d lpiArray %p\n", iCount, lpiArray);
8214 if (!lpiArray || !IsWindow(infoPtr->hwndHeader)) return FALSE;
8216 infoPtr->colRectsDirty = TRUE;
8218 return SendMessageW(infoPtr->hwndHeader, HDM_SETORDERARRAY, iCount, (LPARAM)lpiArray);
8223 * Sets the width of a column
8226 * [I] infoPtr : valid pointer to the listview structure
8227 * [I] nColumn : column index
8228 * [I] cx : column width
8234 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
8236 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
8240 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
8242 /* set column width only if in report or list mode */
8243 if (infoPtr->uView != LV_VIEW_DETAILS && infoPtr->uView != LV_VIEW_LIST) return FALSE;
8245 /* take care of invalid cx values */
8246 if(infoPtr->uView == LV_VIEW_DETAILS && cx < -2) cx = LVSCW_AUTOSIZE;
8247 else if (infoPtr->uView == LV_VIEW_LIST && cx < 1) return FALSE;
8249 /* resize all columns if in LV_VIEW_LIST mode */
8250 if(infoPtr->uView == LV_VIEW_LIST)
8252 infoPtr->nItemWidth = cx;
8253 LISTVIEW_InvalidateList(infoPtr);
8257 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
8259 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
8264 lvItem.mask = LVIF_TEXT;
8266 lvItem.iSubItem = nColumn;
8267 lvItem.pszText = szDispText;
8268 lvItem.cchTextMax = DISP_TEXT_SIZE;
8269 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
8271 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
8272 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
8273 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
8275 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
8276 max_cx += infoPtr->iconSize.cx;
8277 max_cx += TRAILING_LABEL_PADDING;
8280 /* autosize based on listview items width */
8281 if(cx == LVSCW_AUTOSIZE)
8283 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
8285 /* if iCol is the last column make it fill the remainder of the controls width */
8286 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
8291 LISTVIEW_GetOrigin(infoPtr, &Origin);
8292 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
8294 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
8298 /* Despite what the MS docs say, if this is not the last
8299 column, then MS resizes the column to the width of the
8300 largest text string in the column, including headers
8301 and items. This is different from LVSCW_AUTOSIZE in that
8302 LVSCW_AUTOSIZE ignores the header string length. */
8305 /* retrieve header text */
8306 hdi.mask = HDI_TEXT;
8307 hdi.cchTextMax = DISP_TEXT_SIZE;
8308 hdi.pszText = szDispText;
8309 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdi))
8311 HDC hdc = GetDC(infoPtr->hwndSelf);
8312 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
8315 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
8316 cx = size.cx + TRAILING_HEADER_PADDING;
8317 /* FIXME: Take into account the header image, if one is present */
8318 SelectObject(hdc, old_font);
8319 ReleaseDC(infoPtr->hwndSelf, hdc);
8321 cx = max (cx, max_cx);
8325 if (cx < 0) return FALSE;
8327 /* call header to update the column change */
8328 hdi.mask = HDI_WIDTH;
8329 hdi.cxy = max(cx, LISTVIEW_GetColumnInfo(infoPtr, nColumn)->cxMin);
8330 TRACE("hdi.cxy=%d\n", hdi.cxy);
8331 return SendMessageW(infoPtr->hwndHeader, HDM_SETITEMW, nColumn, (LPARAM)&hdi);
8335 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
8338 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
8341 HBITMAP hbm_im, hbm_mask, hbm_orig;
8343 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
8344 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
8347 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
8348 ILC_COLOR | ILC_MASK, 2, 2);
8349 hdc_wnd = GetDC(infoPtr->hwndSelf);
8350 hdc = CreateCompatibleDC(hdc_wnd);
8351 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
8352 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
8353 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
8355 rc.left = rc.top = 0;
8356 rc.right = GetSystemMetrics(SM_CXSMICON);
8357 rc.bottom = GetSystemMetrics(SM_CYSMICON);
8359 hbm_orig = SelectObject(hdc, hbm_mask);
8360 FillRect(hdc, &rc, hbr_white);
8361 InflateRect(&rc, -2, -2);
8362 FillRect(hdc, &rc, hbr_black);
8364 SelectObject(hdc, hbm_im);
8365 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
8366 SelectObject(hdc, hbm_orig);
8367 ImageList_Add(himl, hbm_im, hbm_mask);
8369 SelectObject(hdc, hbm_im);
8370 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
8371 SelectObject(hdc, hbm_orig);
8372 ImageList_Add(himl, hbm_im, hbm_mask);
8374 DeleteObject(hbm_mask);
8375 DeleteObject(hbm_im);
8383 * Sets the extended listview style.
8386 * [I] infoPtr : valid pointer to the listview structure
8388 * [I] dwStyle : style
8391 * SUCCESS : previous style
8394 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD mask, DWORD ex_style)
8396 DWORD old_ex_style = infoPtr->dwLvExStyle;
8398 TRACE("mask=0x%08x, ex_style=0x%08x\n", mask, ex_style);
8402 infoPtr->dwLvExStyle = (old_ex_style & ~mask) | (ex_style & mask);
8404 infoPtr->dwLvExStyle = ex_style;
8406 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_CHECKBOXES)
8408 HIMAGELIST himl = 0;
8409 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8412 item.mask = LVIF_STATE;
8413 item.stateMask = LVIS_STATEIMAGEMASK;
8414 item.state = INDEXTOSTATEIMAGEMASK(1);
8415 LISTVIEW_SetItemState(infoPtr, -1, &item);
8417 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
8418 if(!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8419 ImageList_Destroy(infoPtr->himlState);
8421 himl = LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
8422 /* checkbox list replaces previous custom list or... */
8423 if(((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) &&
8424 !(infoPtr->dwStyle & LVS_SHAREIMAGELISTS)) ||
8425 /* ...previous was checkbox list */
8426 (old_ex_style & LVS_EX_CHECKBOXES))
8427 ImageList_Destroy(himl);
8430 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_HEADERDRAGDROP)
8434 /* if not already created */
8435 LISTVIEW_CreateHeader(infoPtr);
8437 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
8438 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
8439 style |= HDS_DRAGDROP;
8441 style &= ~HDS_DRAGDROP;
8442 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style);
8445 /* GRIDLINES adds decoration at top so changes sizes */
8446 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_GRIDLINES)
8448 LISTVIEW_CreateHeader(infoPtr);
8449 LISTVIEW_UpdateSize(infoPtr);
8452 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_FULLROWSELECT)
8454 LISTVIEW_CreateHeader(infoPtr);
8457 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_TRANSPARENTBKGND)
8459 if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
8460 LISTVIEW_SetBkColor(infoPtr, CLR_NONE);
8463 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_HEADERINALLVIEWS)
8465 if (infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS)
8466 LISTVIEW_CreateHeader(infoPtr);
8468 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8469 LISTVIEW_UpdateSize(infoPtr);
8470 LISTVIEW_UpdateScroll(infoPtr);
8473 LISTVIEW_InvalidateList(infoPtr);
8474 return old_ex_style;
8479 * Sets the new hot cursor used during hot tracking and hover selection.
8482 * [I] infoPtr : valid pointer to the listview structure
8483 * [I] hCursor : the new hot cursor handle
8486 * Returns the previous hot cursor
8488 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
8490 HCURSOR oldCursor = infoPtr->hHotCursor;
8492 infoPtr->hHotCursor = hCursor;
8500 * Sets the hot item index.
8503 * [I] infoPtr : valid pointer to the listview structure
8504 * [I] iIndex : index
8507 * SUCCESS : previous hot item index
8508 * FAILURE : -1 (no hot item)
8510 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
8512 INT iOldIndex = infoPtr->nHotItem;
8514 infoPtr->nHotItem = iIndex;
8522 * Sets the amount of time the cursor must hover over an item before it is selected.
8525 * [I] infoPtr : valid pointer to the listview structure
8526 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
8529 * Returns the previous hover time
8531 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
8533 DWORD oldHoverTime = infoPtr->dwHoverTime;
8535 infoPtr->dwHoverTime = dwHoverTime;
8537 return oldHoverTime;
8542 * Sets spacing for icons of LVS_ICON style.
8545 * [I] infoPtr : valid pointer to the listview structure
8546 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
8547 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
8550 * MAKELONG(oldcx, oldcy)
8552 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
8554 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
8556 TRACE("requested=(%d,%d)\n", cx, cy);
8558 /* this is supported only for LVS_ICON style */
8559 if (infoPtr->uView != LV_VIEW_ICON) return oldspacing;
8561 /* set to defaults, if instructed to */
8562 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
8563 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
8565 /* if 0 then compute width
8566 * FIXME: computed cx and cy is not matching native behaviour */
8568 cx = GetSystemMetrics(SM_CXICONSPACING);
8569 if (infoPtr->iconSize.cx + ICON_LR_PADDING > cx)
8570 cx = infoPtr->iconSize.cx + ICON_LR_PADDING;
8573 /* if 0 then compute height */
8575 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
8576 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
8579 infoPtr->iconSpacing.cx = cx;
8580 infoPtr->iconSpacing.cy = cy;
8582 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
8583 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
8584 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
8585 infoPtr->ntmHeight);
8587 /* these depend on the iconSpacing */
8588 LISTVIEW_UpdateItemSize(infoPtr);
8593 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
8597 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
8604 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
8605 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
8614 * [I] infoPtr : valid pointer to the listview structure
8615 * [I] nType : image list type
8616 * [I] himl : image list handle
8619 * SUCCESS : old image list
8622 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
8624 INT oldHeight = infoPtr->nItemHeight;
8625 HIMAGELIST himlOld = 0;
8627 TRACE("(nType=%d, himl=%p\n", nType, himl);
8632 himlOld = infoPtr->himlNormal;
8633 infoPtr->himlNormal = himl;
8634 if (infoPtr->uView == LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
8635 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
8639 himlOld = infoPtr->himlSmall;
8640 infoPtr->himlSmall = himl;
8641 if (infoPtr->uView != LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
8645 himlOld = infoPtr->himlState;
8646 infoPtr->himlState = himl;
8647 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
8648 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
8652 ERR("Unknown icon type=%d\n", nType);
8656 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
8657 if (infoPtr->nItemHeight != oldHeight)
8658 LISTVIEW_UpdateScroll(infoPtr);
8665 * Preallocates memory (does *not* set the actual count of items !)
8668 * [I] infoPtr : valid pointer to the listview structure
8669 * [I] nItems : item count (projected number of items to allocate)
8670 * [I] dwFlags : update flags
8676 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
8678 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
8680 if (infoPtr->dwStyle & LVS_OWNERDATA)
8682 INT nOldCount = infoPtr->nItemCount;
8684 if (nItems < nOldCount)
8686 RANGE range = { nItems, nOldCount };
8687 ranges_del(infoPtr->selectionRanges, range);
8688 if (infoPtr->nFocusedItem >= nItems)
8690 LISTVIEW_SetItemFocus(infoPtr, -1);
8691 SetRectEmpty(&infoPtr->rcFocus);
8695 infoPtr->nItemCount = nItems;
8696 LISTVIEW_UpdateScroll(infoPtr);
8698 /* the flags are valid only in ownerdata report and list modes */
8699 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON) dwFlags = 0;
8701 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
8702 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
8704 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
8705 LISTVIEW_InvalidateList(infoPtr);
8712 LISTVIEW_GetOrigin(infoPtr, &Origin);
8713 nFrom = min(nOldCount, nItems);
8714 nTo = max(nOldCount, nItems);
8716 if (infoPtr->uView == LV_VIEW_DETAILS)
8719 rcErase.top = nFrom * infoPtr->nItemHeight;
8720 rcErase.right = infoPtr->nItemWidth;
8721 rcErase.bottom = nTo * infoPtr->nItemHeight;
8722 OffsetRect(&rcErase, Origin.x, Origin.y);
8723 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8724 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8726 else /* LV_VIEW_LIST */
8728 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
8730 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
8731 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
8732 rcErase.right = rcErase.left + 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);
8738 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
8740 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
8741 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
8742 OffsetRect(&rcErase, Origin.x, Origin.y);
8743 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8744 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8750 /* According to MSDN for non-LVS_OWNERDATA this is just
8751 * a performance issue. The control allocates its internal
8752 * data structures for the number of items specified. It
8753 * cuts down on the number of memory allocations. Therefore
8754 * we will just issue a WARN here
8756 WARN("for non-ownerdata performance option not implemented.\n");
8764 * Sets the position of an item.
8767 * [I] infoPtr : valid pointer to the listview structure
8768 * [I] nItem : item index
8769 * [I] pt : coordinate
8775 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, const POINT *pt)
8779 TRACE("(nItem=%d, pt=%s\n", nItem, wine_dbgstr_point(pt));
8781 if (!pt || nItem < 0 || nItem >= infoPtr->nItemCount ||
8782 !(infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)) return FALSE;
8785 LISTVIEW_GetOrigin(infoPtr, &Origin);
8787 /* This point value seems to be an undocumented feature.
8788 * The best guess is that it means either at the origin,
8789 * or at true beginning of the list. I will assume the origin. */
8790 if ((Pt.x == -1) && (Pt.y == -1))
8793 if (infoPtr->uView == LV_VIEW_ICON)
8795 Pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
8796 Pt.y -= ICON_TOP_PADDING;
8801 infoPtr->bAutoarrange = FALSE;
8803 return LISTVIEW_MoveIconTo(infoPtr, nItem, &Pt, FALSE);
8808 * Sets the state of one or many items.
8811 * [I] infoPtr : valid pointer to the listview structure
8812 * [I] nItem : item index
8813 * [I] item : item or subitem info
8819 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *item)
8824 if (!item) return FALSE;
8826 lvItem.iItem = nItem;
8827 lvItem.iSubItem = 0;
8828 lvItem.mask = LVIF_STATE;
8829 lvItem.state = item->state;
8830 lvItem.stateMask = item->stateMask;
8831 TRACE("item=%s\n", debuglvitem_t(&lvItem, TRUE));
8838 /* select all isn't allowed in LVS_SINGLESEL */
8839 if ((lvItem.state & lvItem.stateMask & LVIS_SELECTED) && (infoPtr->dwStyle & LVS_SINGLESEL))
8842 /* focus all isn't allowed */
8843 if (lvItem.state & lvItem.stateMask & LVIS_FOCUSED) return FALSE;
8845 notify = infoPtr->bDoChangeNotify;
8846 if (infoPtr->dwStyle & LVS_OWNERDATA)
8848 infoPtr->bDoChangeNotify = FALSE;
8849 if (!(lvItem.state & LVIS_SELECTED) && LISTVIEW_GetSelectedCount(infoPtr))
8850 oldstate |= LVIS_SELECTED;
8851 if (infoPtr->nFocusedItem != -1) oldstate |= LVIS_FOCUSED;
8854 /* apply to all items */
8855 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
8856 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) ret = FALSE;
8858 if (infoPtr->dwStyle & LVS_OWNERDATA)
8862 infoPtr->bDoChangeNotify = notify;
8866 nmlv.uNewState = lvItem.state & lvItem.stateMask;
8867 nmlv.uOldState = oldstate & lvItem.stateMask;
8868 nmlv.uChanged = LVIF_STATE;
8869 nmlv.ptAction.x = nmlv.ptAction.y = 0;
8872 notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
8876 ret = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
8883 * Sets the text of an item or subitem.
8886 * [I] hwnd : window handle
8887 * [I] nItem : item index
8888 * [I] lpLVItem : item or subitem info
8889 * [I] isW : TRUE if input is Unicode
8895 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
8899 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
8900 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
8902 lvItem.iItem = nItem;
8903 lvItem.iSubItem = lpLVItem->iSubItem;
8904 lvItem.mask = LVIF_TEXT;
8905 lvItem.pszText = lpLVItem->pszText;
8906 lvItem.cchTextMax = lpLVItem->cchTextMax;
8908 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
8910 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
8915 * Set item index that marks the start of a multiple selection.
8918 * [I] infoPtr : valid pointer to the listview structure
8919 * [I] nIndex : index
8922 * Index number or -1 if there is no selection mark.
8924 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
8926 INT nOldIndex = infoPtr->nSelectionMark;
8928 TRACE("(nIndex=%d)\n", nIndex);
8930 infoPtr->nSelectionMark = nIndex;
8937 * Sets the text background color.
8940 * [I] infoPtr : valid pointer to the listview structure
8941 * [I] color : text background color
8947 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
8949 TRACE("(color=%x)\n", color);
8951 infoPtr->clrTextBk = color;
8957 * Sets the text foreground color.
8960 * [I] infoPtr : valid pointer to the listview structure
8961 * [I] color : text color
8967 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF color)
8969 TRACE("(color=%x)\n", color);
8971 infoPtr->clrText = color;
8977 * Sets new ToolTip window to ListView control.
8980 * [I] infoPtr : valid pointer to the listview structure
8981 * [I] hwndNewToolTip : handle to new ToolTip
8986 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
8988 HWND hwndOldToolTip = infoPtr->hwndToolTip;
8989 infoPtr->hwndToolTip = hwndNewToolTip;
8990 return hwndOldToolTip;
8995 * sets the Unicode character format flag for the control
8997 * [I] infoPtr :valid pointer to the listview structure
8998 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
9001 * Old Unicode Format
9003 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL unicode)
9005 SHORT rc = infoPtr->notifyFormat;
9006 infoPtr->notifyFormat = (unicode) ? NFR_UNICODE : NFR_ANSI;
9007 return rc == NFR_UNICODE;
9012 * sets the control view mode
9014 * [I] infoPtr :valid pointer to the listview structure
9015 * [I] nView :new view mode value
9021 static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView)
9023 SIZE oldIconSize = infoPtr->iconSize;
9026 if (infoPtr->uView == nView) return 1;
9028 if ((INT)nView < 0 || nView > LV_VIEW_MAX) return -1;
9029 if (nView == LV_VIEW_TILE)
9031 FIXME("View LV_VIEW_TILE unimplemented\n");
9035 infoPtr->uView = nView;
9037 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9038 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9040 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9041 SetRectEmpty(&infoPtr->rcFocus);
9043 himl = (nView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9044 set_icon_size(&infoPtr->iconSize, himl, nView != LV_VIEW_ICON);
9049 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9051 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9052 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9053 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9055 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9057 case LV_VIEW_SMALLICON:
9058 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9060 case LV_VIEW_DETAILS:
9065 LISTVIEW_CreateHeader( infoPtr );
9067 hl.prc = &infoPtr->rcList;
9069 SendMessageW(infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl);
9070 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9071 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9078 LISTVIEW_UpdateItemSize(infoPtr);
9079 LISTVIEW_UpdateSize(infoPtr);
9080 LISTVIEW_UpdateScroll(infoPtr);
9081 LISTVIEW_InvalidateList(infoPtr);
9083 TRACE("nView=%d\n", nView);
9088 /* LISTVIEW_SetWorkAreas */
9092 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMS
9095 * [I] first : pointer to first ITEM_INFO to compare
9096 * [I] second : pointer to second ITEM_INFO to compare
9097 * [I] lParam : HWND of control
9100 * if first comes before second : negative
9101 * if first comes after second : positive
9102 * if first and second are equivalent : zero
9104 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
9106 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
9107 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
9108 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
9110 /* Forward the call to the client defined callback */
9111 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
9116 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMSEX
9119 * [I] first : pointer to first ITEM_INFO to compare
9120 * [I] second : pointer to second ITEM_INFO to compare
9121 * [I] lParam : HWND of control
9124 * if first comes before second : negative
9125 * if first comes after second : positive
9126 * if first and second are equivalent : zero
9128 static INT WINAPI LISTVIEW_CallBackCompareEx(LPVOID first, LPVOID second, LPARAM lParam)
9130 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
9131 INT first_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, first );
9132 INT second_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, second );
9134 /* Forward the call to the client defined callback */
9135 return (infoPtr->pfnCompare)( first_idx, second_idx, infoPtr->lParamSort );
9140 * Sorts the listview items.
9143 * [I] infoPtr : valid pointer to the listview structure
9144 * [I] pfnCompare : application-defined value
9145 * [I] lParamSort : pointer to comparison callback
9146 * [I] IsEx : TRUE when LVM_SORTITEMSEX used
9152 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare,
9153 LPARAM lParamSort, BOOL IsEx)
9157 LPVOID selectionMarkItem = NULL;
9158 LPVOID focusedItem = NULL;
9161 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
9163 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
9165 if (!pfnCompare) return FALSE;
9166 if (!infoPtr->hdpaItems) return FALSE;
9168 /* if there are 0 or 1 items, there is no need to sort */
9169 if (infoPtr->nItemCount < 2) return TRUE;
9171 /* clear selection */
9172 ranges_clear(infoPtr->selectionRanges);
9174 /* save selection mark and focused item */
9175 if (infoPtr->nSelectionMark >= 0)
9176 selectionMarkItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark);
9177 if (infoPtr->nFocusedItem >= 0)
9178 focusedItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
9180 infoPtr->pfnCompare = pfnCompare;
9181 infoPtr->lParamSort = lParamSort;
9183 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompareEx, (LPARAM)infoPtr);
9185 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
9187 /* restore selection ranges */
9188 for (i=0; i < infoPtr->nItemCount; i++)
9190 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
9191 lpItem = DPA_GetPtr(hdpaSubItems, 0);
9193 if (lpItem->state & LVIS_SELECTED)
9194 ranges_additem(infoPtr->selectionRanges, i);
9196 /* restore selection mark and focused item */
9197 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
9198 infoPtr->nFocusedItem = DPA_GetPtrIndex(infoPtr->hdpaItems, focusedItem);
9200 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
9202 /* refresh the display */
9203 LISTVIEW_InvalidateList(infoPtr);
9209 * Update theme handle after a theme change.
9212 * [I] infoPtr : valid pointer to the listview structure
9216 * FAILURE : something else
9218 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
9220 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
9221 CloseThemeData(theme);
9222 OpenThemeData(infoPtr->hwndSelf, themeClass);
9228 * Updates an items or rearranges the listview control.
9231 * [I] infoPtr : valid pointer to the listview structure
9232 * [I] nItem : item index
9238 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
9240 TRACE("(nItem=%d)\n", nItem);
9242 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
9244 /* rearrange with default alignment style */
9245 if (is_autoarrange(infoPtr))
9246 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9248 LISTVIEW_InvalidateItem(infoPtr, nItem);
9255 * Draw the track line at the place defined in the infoPtr structure.
9256 * The line is drawn with a XOR pen so drawing the line for the second time
9257 * in the same place erases the line.
9260 * [I] infoPtr : valid pointer to the listview structure
9266 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
9270 if (infoPtr->xTrackLine == -1)
9273 if (!(hdc = GetDC(infoPtr->hwndSelf)))
9275 PatBlt( hdc, infoPtr->xTrackLine, infoPtr->rcList.top,
9276 1, infoPtr->rcList.bottom - infoPtr->rcList.top, DSTINVERT );
9277 ReleaseDC(infoPtr->hwndSelf, hdc);
9283 * Called when an edit control should be displayed. This function is called after
9284 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
9287 * [I] hwnd : Handle to the listview
9288 * [I] uMsg : WM_TIMER (ignored)
9289 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
9290 * [I] dwTimer : The elapsed time (ignored)
9295 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
9297 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
9298 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9300 KillTimer(hwnd, idEvent);
9301 editItem->fEnabled = FALSE;
9302 /* check if the item is still selected */
9303 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
9304 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
9309 * Creates the listview control - the WM_NCCREATE phase.
9312 * [I] hwnd : window handle
9313 * [I] lpcs : the create parameters
9319 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
9321 LISTVIEW_INFO *infoPtr;
9324 TRACE("(lpcs=%p)\n", lpcs);
9326 /* initialize info pointer */
9327 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
9328 if (!infoPtr) return FALSE;
9330 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
9332 infoPtr->hwndSelf = hwnd;
9333 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
9334 map_style_view(infoPtr);
9335 /* determine the type of structures to use */
9336 infoPtr->hwndNotify = lpcs->hwndParent;
9337 /* infoPtr->notifyFormat will be filled in WM_CREATE */
9339 /* initialize color information */
9340 infoPtr->clrBk = CLR_NONE;
9341 infoPtr->clrText = CLR_DEFAULT;
9342 infoPtr->clrTextBk = CLR_DEFAULT;
9343 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
9345 /* set default values */
9346 infoPtr->nFocusedItem = -1;
9347 infoPtr->nSelectionMark = -1;
9348 infoPtr->nHotItem = -1;
9349 infoPtr->bRedraw = TRUE;
9350 infoPtr->bNoItemMetrics = TRUE;
9351 infoPtr->bDoChangeNotify = TRUE;
9352 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
9353 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
9354 infoPtr->nEditLabelItem = -1;
9355 infoPtr->nLButtonDownItem = -1;
9356 infoPtr->dwHoverTime = HOVER_DEFAULT; /* default system hover time */
9357 infoPtr->nMeasureItemHeight = 0;
9358 infoPtr->xTrackLine = -1; /* no track line */
9359 infoPtr->itemEdit.fEnabled = FALSE;
9360 infoPtr->iVersion = COMCTL32_VERSION;
9361 infoPtr->colRectsDirty = FALSE;
9363 /* get default font (icon title) */
9364 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
9365 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
9366 infoPtr->hFont = infoPtr->hDefaultFont;
9367 LISTVIEW_SaveTextMetrics(infoPtr);
9369 /* allocate memory for the data structure */
9370 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
9371 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
9372 if (!(infoPtr->hdpaItemIds = DPA_Create(10))) goto fail;
9373 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
9374 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
9375 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
9379 DestroyWindow(infoPtr->hwndHeader);
9380 ranges_destroy(infoPtr->selectionRanges);
9381 DPA_Destroy(infoPtr->hdpaItems);
9382 DPA_Destroy(infoPtr->hdpaItemIds);
9383 DPA_Destroy(infoPtr->hdpaPosX);
9384 DPA_Destroy(infoPtr->hdpaPosY);
9385 DPA_Destroy(infoPtr->hdpaColumns);
9392 * Creates the listview control - the WM_CREATE phase. Most of the data is
9393 * already set up in LISTVIEW_NCCreate
9396 * [I] hwnd : window handle
9397 * [I] lpcs : the create parameters
9403 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
9405 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9407 TRACE("(lpcs=%p, style=0x%08x)\n", lpcs, lpcs->style);
9409 infoPtr->dwStyle = lpcs->style;
9410 map_style_view(infoPtr);
9412 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
9413 (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9414 /* on error defaulting to ANSI notifications */
9415 if (infoPtr->notifyFormat == 0) infoPtr->notifyFormat = NFR_ANSI;
9416 TRACE("notify format=%d\n", infoPtr->notifyFormat);
9418 if ((infoPtr->uView == LV_VIEW_DETAILS) && (lpcs->style & WS_VISIBLE))
9420 if (LISTVIEW_CreateHeader(infoPtr) < 0) return -1;
9423 infoPtr->hwndHeader = 0;
9425 /* init item size to avoid division by 0 */
9426 LISTVIEW_UpdateItemSize (infoPtr);
9427 LISTVIEW_UpdateSize (infoPtr);
9429 if (infoPtr->uView == LV_VIEW_DETAILS)
9431 if (!(LVS_NOCOLUMNHEADER & lpcs->style) && (WS_VISIBLE & lpcs->style))
9433 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
9435 LISTVIEW_UpdateScroll(infoPtr);
9436 /* send WM_MEASUREITEM notification */
9437 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED) notify_measureitem(infoPtr);
9440 OpenThemeData(hwnd, themeClass);
9442 /* initialize the icon sizes */
9443 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, infoPtr->uView != LV_VIEW_ICON);
9444 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
9450 * Destroys the listview control.
9453 * [I] infoPtr : valid pointer to the listview structure
9459 static LRESULT LISTVIEW_Destroy(LISTVIEW_INFO *infoPtr)
9461 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
9462 CloseThemeData(theme);
9464 /* delete all items */
9465 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
9472 * Enables the listview control.
9475 * [I] infoPtr : valid pointer to the listview structure
9476 * [I] bEnable : specifies whether to enable or disable the window
9482 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr)
9484 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
9485 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
9491 * Erases the background of the listview control.
9494 * [I] infoPtr : valid pointer to the listview structure
9495 * [I] hdc : device context handle
9501 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
9505 TRACE("(hdc=%p)\n", hdc);
9507 if (!GetClipBox(hdc, &rc)) return FALSE;
9509 if (infoPtr->clrBk == CLR_NONE)
9511 if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
9512 return SendMessageW(infoPtr->hwndNotify, WM_PRINTCLIENT,
9513 (WPARAM)hdc, PRF_ERASEBKGND);
9515 return SendMessageW(infoPtr->hwndNotify, WM_ERASEBKGND, (WPARAM)hdc, 0);
9518 /* for double buffered controls we need to do this during refresh */
9519 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
9521 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
9527 * Helper function for LISTVIEW_[HV]Scroll *only*.
9528 * Performs vertical/horizontal scrolling by a give amount.
9531 * [I] infoPtr : valid pointer to the listview structure
9532 * [I] dx : amount of horizontal scroll
9533 * [I] dy : amount of vertical scroll
9535 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
9537 /* now we can scroll the list */
9538 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
9539 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
9540 /* if we have focus, adjust rect */
9541 OffsetRect(&infoPtr->rcFocus, dx, dy);
9542 UpdateWindow(infoPtr->hwndSelf);
9547 * Performs vertical scrolling.
9550 * [I] infoPtr : valid pointer to the listview structure
9551 * [I] nScrollCode : scroll code
9552 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
9553 * [I] hScrollWnd : scrollbar control window handle
9559 * SB_LINEUP/SB_LINEDOWN:
9560 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
9561 * for LVS_REPORT is 1 line
9562 * for LVS_LIST cannot occur
9565 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
9568 INT nOldScrollPos, nNewScrollPos;
9569 SCROLLINFO scrollInfo;
9572 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
9573 debugscrollcode(nScrollCode), nScrollDiff);
9575 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9577 scrollInfo.cbSize = sizeof(SCROLLINFO);
9578 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
9580 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
9582 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
9584 nOldScrollPos = scrollInfo.nPos;
9585 switch (nScrollCode)
9591 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
9595 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
9599 nScrollDiff = -scrollInfo.nPage;
9603 nScrollDiff = scrollInfo.nPage;
9606 case SB_THUMBPOSITION:
9608 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
9615 /* quit right away if pos isn't changing */
9616 if (nScrollDiff == 0) return 0;
9618 /* calculate new position, and handle overflows */
9619 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
9620 if (nScrollDiff > 0) {
9621 if (nNewScrollPos < nOldScrollPos ||
9622 nNewScrollPos > scrollInfo.nMax)
9623 nNewScrollPos = scrollInfo.nMax;
9625 if (nNewScrollPos > nOldScrollPos ||
9626 nNewScrollPos < scrollInfo.nMin)
9627 nNewScrollPos = scrollInfo.nMin;
9630 /* set the new position, and reread in case it changed */
9631 scrollInfo.fMask = SIF_POS;
9632 scrollInfo.nPos = nNewScrollPos;
9633 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
9635 /* carry on only if it really changed */
9636 if (nNewScrollPos == nOldScrollPos) return 0;
9638 /* now adjust to client coordinates */
9639 nScrollDiff = nOldScrollPos - nNewScrollPos;
9640 if (infoPtr->uView == LV_VIEW_DETAILS) nScrollDiff *= infoPtr->nItemHeight;
9642 /* and scroll the window */
9643 scroll_list(infoPtr, 0, nScrollDiff);
9650 * Performs horizontal scrolling.
9653 * [I] infoPtr : valid pointer to the listview structure
9654 * [I] nScrollCode : scroll code
9655 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
9656 * [I] hScrollWnd : scrollbar control window handle
9662 * SB_LINELEFT/SB_LINERIGHT:
9663 * for LVS_ICON, LVS_SMALLICON 1 pixel
9664 * for LVS_REPORT is 1 pixel
9665 * for LVS_LIST is 1 column --> which is a 1 because the
9666 * scroll is based on columns not pixels
9669 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
9672 INT nOldScrollPos, nNewScrollPos;
9673 SCROLLINFO scrollInfo;
9676 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
9677 debugscrollcode(nScrollCode), nScrollDiff);
9679 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9681 scrollInfo.cbSize = sizeof(SCROLLINFO);
9682 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
9684 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
9686 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
9688 nOldScrollPos = scrollInfo.nPos;
9690 switch (nScrollCode)
9696 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
9700 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
9704 nScrollDiff = -scrollInfo.nPage;
9708 nScrollDiff = scrollInfo.nPage;
9711 case SB_THUMBPOSITION:
9713 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
9720 /* quit right away if pos isn't changing */
9721 if (nScrollDiff == 0) return 0;
9723 /* calculate new position, and handle overflows */
9724 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
9725 if (nScrollDiff > 0) {
9726 if (nNewScrollPos < nOldScrollPos ||
9727 nNewScrollPos > scrollInfo.nMax)
9728 nNewScrollPos = scrollInfo.nMax;
9730 if (nNewScrollPos > nOldScrollPos ||
9731 nNewScrollPos < scrollInfo.nMin)
9732 nNewScrollPos = scrollInfo.nMin;
9735 /* set the new position, and reread in case it changed */
9736 scrollInfo.fMask = SIF_POS;
9737 scrollInfo.nPos = nNewScrollPos;
9738 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
9740 /* carry on only if it really changed */
9741 if (nNewScrollPos == nOldScrollPos) return 0;
9743 if (infoPtr->hwndHeader) LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
9745 /* now adjust to client coordinates */
9746 nScrollDiff = nOldScrollPos - nNewScrollPos;
9747 if (infoPtr->uView == LV_VIEW_LIST) nScrollDiff *= infoPtr->nItemWidth;
9749 /* and scroll the window */
9750 scroll_list(infoPtr, nScrollDiff, 0);
9755 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
9757 INT gcWheelDelta = 0;
9758 INT pulScrollLines = 3;
9760 TRACE("(wheelDelta=%d)\n", wheelDelta);
9762 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
9763 gcWheelDelta -= wheelDelta;
9765 switch(infoPtr->uView)
9768 case LV_VIEW_SMALLICON:
9770 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
9771 * should be fixed in the future.
9773 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
9774 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE);
9777 case LV_VIEW_DETAILS:
9778 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
9780 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
9781 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
9782 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll);
9787 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0);
9798 * [I] infoPtr : valid pointer to the listview structure
9799 * [I] nVirtualKey : virtual key
9800 * [I] lKeyData : key data
9805 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
9807 HWND hwndSelf = infoPtr->hwndSelf;
9809 NMLVKEYDOWN nmKeyDown;
9811 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
9813 /* send LVN_KEYDOWN notification */
9814 nmKeyDown.wVKey = nVirtualKey;
9815 nmKeyDown.flags = 0;
9816 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
9817 if (!IsWindow(hwndSelf))
9820 switch (nVirtualKey)
9823 nItem = infoPtr->nFocusedItem;
9824 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
9825 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
9829 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
9831 if (!notify(infoPtr, NM_RETURN)) return 0;
9832 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
9837 if (infoPtr->nItemCount > 0)
9842 if (infoPtr->nItemCount > 0)
9843 nItem = infoPtr->nItemCount - 1;
9847 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TOLEFT);
9851 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_ABOVE);
9855 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TORIGHT);
9859 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_BELOW);
9863 if (infoPtr->uView == LV_VIEW_DETAILS)
9865 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
9866 if (infoPtr->nFocusedItem == topidx)
9867 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
9872 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
9873 * LISTVIEW_GetCountPerRow(infoPtr);
9874 if(nItem < 0) nItem = 0;
9878 if (infoPtr->uView == LV_VIEW_DETAILS)
9880 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
9881 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
9882 if (infoPtr->nFocusedItem == topidx + cnt - 1)
9883 nItem = infoPtr->nFocusedItem + cnt - 1;
9885 nItem = topidx + cnt - 1;
9888 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
9889 * LISTVIEW_GetCountPerRow(infoPtr);
9890 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
9894 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
9895 LISTVIEW_KeySelection(infoPtr, nItem, nVirtualKey == VK_SPACE);
9905 * [I] infoPtr : valid pointer to the listview structure
9910 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
9914 /* if we did not have the focus, there's nothing to do */
9915 if (!infoPtr->bFocus) return 0;
9917 /* send NM_KILLFOCUS notification */
9918 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
9920 /* if we have a focus rectangle, get rid of it */
9921 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
9923 /* if have a marquee selection, stop it */
9924 if (infoPtr->bMarqueeSelect)
9926 /* Remove the marquee rectangle and release our mouse capture */
9927 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeRect);
9930 SetRect(&infoPtr->marqueeRect, 0, 0, 0, 0);
9932 infoPtr->bMarqueeSelect = FALSE;
9933 infoPtr->bScrolling = FALSE;
9934 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
9937 /* set window focus flag */
9938 infoPtr->bFocus = FALSE;
9940 /* invalidate the selected items before resetting focus flag */
9941 LISTVIEW_InvalidateSelectedItems(infoPtr);
9948 * Processes double click messages (left mouse button).
9951 * [I] infoPtr : valid pointer to the listview structure
9952 * [I] wKey : key flag
9953 * [I] x,y : mouse coordinate
9958 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9960 LVHITTESTINFO htInfo;
9962 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
9964 /* Cancel the item edition if any */
9965 if (infoPtr->itemEdit.fEnabled)
9967 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
9968 infoPtr->itemEdit.fEnabled = FALSE;
9971 /* send NM_RELEASEDCAPTURE notification */
9972 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9977 /* send NM_DBLCLK notification */
9978 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
9979 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
9981 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
9982 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
9989 * Processes mouse down messages (left mouse button).
9992 * infoPtr [I ] valid pointer to the listview structure
9993 * wKey [I ] key flag
9994 * x,y [I ] mouse coordinate
9999 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10001 LVHITTESTINFO lvHitTestInfo;
10002 static BOOL bGroupSelect = TRUE;
10003 POINT pt = { x, y };
10006 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10008 /* send NM_RELEASEDCAPTURE notification */
10009 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10011 /* set left button down flag and record the click position */
10012 infoPtr->bLButtonDown = TRUE;
10013 infoPtr->ptClickPos = pt;
10014 infoPtr->bDragging = FALSE;
10015 infoPtr->bMarqueeSelect = FALSE;
10016 infoPtr->bScrolling = FALSE;
10018 lvHitTestInfo.pt.x = x;
10019 lvHitTestInfo.pt.y = y;
10021 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
10022 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
10023 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
10025 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
10027 toggle_checkbox_state(infoPtr, nItem);
10031 if (infoPtr->dwStyle & LVS_SINGLESEL)
10033 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10034 infoPtr->nEditLabelItem = nItem;
10036 LISTVIEW_SetSelection(infoPtr, nItem);
10040 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
10044 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
10045 LISTVIEW_SetItemFocus(infoPtr, nItem);
10046 infoPtr->nSelectionMark = nItem;
10052 item.state = LVIS_SELECTED | LVIS_FOCUSED;
10053 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
10055 LISTVIEW_SetItemState(infoPtr,nItem,&item);
10056 infoPtr->nSelectionMark = nItem;
10059 else if (wKey & MK_CONTROL)
10063 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
10065 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
10066 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
10067 LISTVIEW_SetItemState(infoPtr, nItem, &item);
10068 infoPtr->nSelectionMark = nItem;
10070 else if (wKey & MK_SHIFT)
10072 LISTVIEW_SetGroupSelection(infoPtr, nItem);
10076 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10078 infoPtr->nEditLabelItem = nItem;
10079 infoPtr->nLButtonDownItem = nItem;
10081 LISTVIEW_SetItemFocus(infoPtr, nItem);
10084 /* set selection (clears other pre-existing selections) */
10085 LISTVIEW_SetSelection(infoPtr, nItem);
10089 if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
10090 if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
10094 if (!infoPtr->bFocus)
10095 SetFocus(infoPtr->hwndSelf);
10097 /* remove all selections */
10098 if (!(wKey & MK_CONTROL) && !(wKey & MK_SHIFT))
10099 LISTVIEW_DeselectAll(infoPtr);
10108 * Processes mouse up messages (left mouse button).
10111 * infoPtr [I ] valid pointer to the listview structure
10112 * wKey [I ] key flag
10113 * x,y [I ] mouse coordinate
10118 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10120 LVHITTESTINFO lvHitTestInfo;
10122 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10124 if (!infoPtr->bLButtonDown) return 0;
10126 lvHitTestInfo.pt.x = x;
10127 lvHitTestInfo.pt.y = y;
10129 /* send NM_CLICK notification */
10130 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10131 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
10133 /* set left button flag */
10134 infoPtr->bLButtonDown = FALSE;
10136 /* set a single selection, reset others */
10137 if(lvHitTestInfo.iItem == infoPtr->nLButtonDownItem && lvHitTestInfo.iItem != -1)
10138 LISTVIEW_SetSelection(infoPtr, infoPtr->nLButtonDownItem);
10139 infoPtr->nLButtonDownItem = -1;
10141 if (infoPtr->bDragging || infoPtr->bMarqueeSelect)
10143 /* Remove the marquee rectangle and release our mouse capture */
10144 if (infoPtr->bMarqueeSelect)
10146 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
10150 SetRect(&infoPtr->marqueeRect, 0, 0, 0, 0);
10151 SetRect(&infoPtr->marqueeDrawRect, 0, 0, 0, 0);
10153 infoPtr->bDragging = FALSE;
10154 infoPtr->bMarqueeSelect = FALSE;
10155 infoPtr->bScrolling = FALSE;
10157 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
10161 /* if we clicked on a selected item, edit the label */
10162 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
10164 /* we want to make sure the user doesn't want to do a double click. So we will
10165 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
10167 infoPtr->itemEdit.fEnabled = TRUE;
10168 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
10169 SetTimer(infoPtr->hwndSelf,
10170 (UINT_PTR)&infoPtr->itemEdit,
10171 GetDoubleClickTime(),
10172 LISTVIEW_DelayedEditItem);
10175 if (!infoPtr->bFocus)
10176 SetFocus(infoPtr->hwndSelf);
10183 * Destroys the listview control (called after WM_DESTROY).
10186 * [I] infoPtr : valid pointer to the listview structure
10191 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
10197 /* destroy data structure */
10198 DPA_Destroy(infoPtr->hdpaItems);
10199 DPA_Destroy(infoPtr->hdpaItemIds);
10200 DPA_Destroy(infoPtr->hdpaPosX);
10201 DPA_Destroy(infoPtr->hdpaPosY);
10203 for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++)
10204 Free(DPA_GetPtr(infoPtr->hdpaColumns, i));
10205 DPA_Destroy(infoPtr->hdpaColumns);
10206 ranges_destroy(infoPtr->selectionRanges);
10208 /* destroy image lists */
10209 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
10211 ImageList_Destroy(infoPtr->himlNormal);
10212 ImageList_Destroy(infoPtr->himlSmall);
10213 ImageList_Destroy(infoPtr->himlState);
10216 /* destroy font, bkgnd brush */
10217 infoPtr->hFont = 0;
10218 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
10219 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
10221 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
10223 /* free listview info pointer*/
10231 * Handles notifications.
10234 * [I] infoPtr : valid pointer to the listview structure
10235 * [I] lpnmhdr : notification information
10240 static LRESULT LISTVIEW_Notify(LISTVIEW_INFO *infoPtr, NMHDR *lpnmhdr)
10244 TRACE("(lpnmhdr=%p)\n", lpnmhdr);
10246 if (!lpnmhdr || lpnmhdr->hwndFrom != infoPtr->hwndHeader) return 0;
10248 /* remember: HDN_LAST < HDN_FIRST */
10249 if (lpnmhdr->code > HDN_FIRST || lpnmhdr->code < HDN_LAST) return 0;
10250 lpnmh = (NMHEADERW *)lpnmhdr;
10252 if (lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
10254 switch (lpnmhdr->code)
10259 COLUMN_INFO *lpColumnInfo;
10263 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
10266 /* remove the old line (if any) */
10267 LISTVIEW_DrawTrackLine(infoPtr);
10269 /* compute & draw the new line */
10270 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
10271 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
10272 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
10273 infoPtr->xTrackLine = x + ptOrigin.x;
10274 LISTVIEW_DrawTrackLine(infoPtr);
10275 return notify_forward_header(infoPtr, lpnmh);
10278 case HDN_ENDTRACKA:
10279 case HDN_ENDTRACKW:
10280 /* remove the track line (if any) */
10281 LISTVIEW_DrawTrackLine(infoPtr);
10282 infoPtr->xTrackLine = -1;
10283 return notify_forward_header(infoPtr, lpnmh);
10285 case HDN_BEGINDRAG:
10286 if ((infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP) == 0) return 1;
10287 return notify_forward_header(infoPtr, lpnmh);
10290 infoPtr->colRectsDirty = TRUE;
10291 LISTVIEW_InvalidateList(infoPtr);
10292 return notify_forward_header(infoPtr, lpnmh);
10294 case HDN_ITEMCHANGEDW:
10295 case HDN_ITEMCHANGEDA:
10297 COLUMN_INFO *lpColumnInfo;
10301 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
10303 hdi.mask = HDI_WIDTH;
10304 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi)) return 0;
10308 cxy = lpnmh->pitem->cxy;
10310 /* determine how much we change since the last know position */
10311 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
10312 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
10315 lpColumnInfo->rcHeader.right += dx;
10317 hdi.mask = HDI_ORDER;
10318 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi);
10320 /* not the rightmost one */
10321 if (hdi.iOrder + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
10323 INT nIndex = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
10324 hdi.iOrder + 1, 0);
10325 LISTVIEW_ScrollColumns(infoPtr, nIndex, dx);
10329 /* only needs to update the scrolls */
10330 infoPtr->nItemWidth += dx;
10331 LISTVIEW_UpdateScroll(infoPtr);
10333 LISTVIEW_UpdateItemSize(infoPtr);
10334 if (infoPtr->uView == LV_VIEW_DETAILS && is_redrawing(infoPtr))
10337 RECT rcCol = lpColumnInfo->rcHeader;
10339 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
10340 OffsetRect(&rcCol, ptOrigin.x, 0);
10342 rcCol.top = infoPtr->rcList.top;
10343 rcCol.bottom = infoPtr->rcList.bottom;
10345 /* resizing left-aligned columns leaves most of the left side untouched */
10346 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
10348 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
10351 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
10354 /* when shrinking the last column clear the now unused field */
10355 if (hdi.iOrder == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
10361 /* deal with right from rightmost column area */
10362 right.left = rcCol.right;
10363 right.top = rcCol.top;
10364 right.bottom = rcCol.bottom;
10365 right.right = infoPtr->rcList.right;
10367 LISTVIEW_InvalidateRect(infoPtr, &right);
10370 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
10376 case HDN_ITEMCLICKW:
10377 case HDN_ITEMCLICKA:
10379 /* Handle sorting by Header Column */
10382 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
10384 nmlv.iSubItem = lpnmh->iItem;
10385 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
10386 return notify_forward_header(infoPtr, lpnmh);
10389 case HDN_DIVIDERDBLCLICKW:
10390 case HDN_DIVIDERDBLCLICKA:
10391 /* FIXME: for LVS_EX_HEADERINALLVIEWS and not LV_VIEW_DETAILS
10392 we should use LVSCW_AUTOSIZE_USEHEADER, helper rework or
10393 split needed for that */
10394 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
10395 return notify_forward_header(infoPtr, lpnmh);
10402 * Paint non-client area of control.
10405 * [I] infoPtr : valid pointer to the listview structureof the sender
10406 * [I] region : update region
10409 * TRUE - frame was painted
10410 * FALSE - call default window proc
10412 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
10414 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
10418 int cxEdge = GetSystemMetrics (SM_CXEDGE),
10419 cyEdge = GetSystemMetrics (SM_CYEDGE);
10422 return DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)region, 0);
10424 GetWindowRect(infoPtr->hwndSelf, &r);
10426 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
10427 r.right - cxEdge, r.bottom - cyEdge);
10428 if (region != (HRGN)1)
10429 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
10430 OffsetRect(&r, -r.left, -r.top);
10432 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
10433 OffsetRect(&r, -r.left, -r.top);
10435 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
10436 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
10437 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
10438 ReleaseDC(infoPtr->hwndSelf, dc);
10440 /* Call default proc to get the scrollbars etc. painted */
10441 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
10448 * Determines the type of structure to use.
10451 * [I] infoPtr : valid pointer to the listview structureof the sender
10452 * [I] hwndFrom : listview window handle
10453 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
10458 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
10460 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
10462 if (nCommand == NF_REQUERY)
10463 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
10465 return infoPtr->notifyFormat;
10470 * Paints/Repaints the listview control. Internal use.
10473 * [I] infoPtr : valid pointer to the listview structure
10474 * [I] hdc : device context handle
10479 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
10481 TRACE("(hdc=%p)\n", hdc);
10483 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
10485 infoPtr->bNoItemMetrics = FALSE;
10486 LISTVIEW_UpdateItemSize(infoPtr);
10487 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
10488 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10489 LISTVIEW_UpdateScroll(infoPtr);
10492 if (infoPtr->hwndHeader) UpdateWindow(infoPtr->hwndHeader);
10495 LISTVIEW_Refresh(infoPtr, hdc, NULL);
10500 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
10501 if (!hdc) return 1;
10502 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
10503 EndPaint(infoPtr->hwndSelf, &ps);
10511 * Paints/Repaints the listview control, WM_PAINT handler.
10514 * [I] infoPtr : valid pointer to the listview structure
10515 * [I] hdc : device context handle
10520 static inline LRESULT LISTVIEW_WMPaint(LISTVIEW_INFO *infoPtr, HDC hdc)
10522 TRACE("(hdc=%p)\n", hdc);
10524 if (!is_redrawing(infoPtr))
10525 return DefWindowProcW (infoPtr->hwndSelf, WM_PAINT, (WPARAM)hdc, 0);
10527 return LISTVIEW_Paint(infoPtr, hdc);
10532 * Paints/Repaints the listview control.
10535 * [I] infoPtr : valid pointer to the listview structure
10536 * [I] hdc : device context handle
10537 * [I] options : drawing options
10542 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
10544 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
10546 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
10549 if (options & PRF_ERASEBKGND)
10550 LISTVIEW_EraseBkgnd(infoPtr, hdc);
10552 if (options & PRF_CLIENT)
10553 LISTVIEW_Paint(infoPtr, hdc);
10561 * Processes double click messages (right mouse button).
10564 * [I] infoPtr : valid pointer to the listview structure
10565 * [I] wKey : key flag
10566 * [I] x,y : mouse coordinate
10571 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10573 LVHITTESTINFO lvHitTestInfo;
10575 TRACE("(key=%hu,X=%u,Y=%u)\n", wKey, x, y);
10577 /* send NM_RELEASEDCAPTURE notification */
10578 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10580 /* send NM_RDBLCLK notification */
10581 lvHitTestInfo.pt.x = x;
10582 lvHitTestInfo.pt.y = y;
10583 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10584 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
10591 * Processes mouse down messages (right mouse button).
10594 * [I] infoPtr : valid pointer to the listview structure
10595 * [I] wKey : key flag
10596 * [I] x,y : mouse coordinate
10601 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10603 LVHITTESTINFO lvHitTestInfo;
10606 TRACE("(key=%hu,X=%u,Y=%u)\n", wKey, x, y);
10608 /* send NM_RELEASEDCAPTURE notification */
10609 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10611 /* make sure the listview control window has the focus */
10612 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
10614 /* set right button down flag */
10615 infoPtr->bRButtonDown = TRUE;
10617 /* determine the index of the selected item */
10618 lvHitTestInfo.pt.x = x;
10619 lvHitTestInfo.pt.y = y;
10620 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
10622 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
10624 LISTVIEW_SetItemFocus(infoPtr, nItem);
10625 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
10626 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10627 LISTVIEW_SetSelection(infoPtr, nItem);
10631 LISTVIEW_DeselectAll(infoPtr);
10639 * Processes mouse up messages (right mouse button).
10642 * [I] infoPtr : valid pointer to the listview structure
10643 * [I] wKey : key flag
10644 * [I] x,y : mouse coordinate
10649 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10651 LVHITTESTINFO lvHitTestInfo;
10654 TRACE("(key=%hu,X=%u,Y=%u)\n", wKey, x, y);
10656 if (!infoPtr->bRButtonDown) return 0;
10658 /* set button flag */
10659 infoPtr->bRButtonDown = FALSE;
10661 /* Send NM_RCLICK notification */
10662 lvHitTestInfo.pt.x = x;
10663 lvHitTestInfo.pt.y = y;
10664 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10665 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
10667 /* Change to screen coordinate for WM_CONTEXTMENU */
10668 pt = lvHitTestInfo.pt;
10669 ClientToScreen(infoPtr->hwndSelf, &pt);
10671 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
10672 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
10673 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
10684 * [I] infoPtr : valid pointer to the listview structure
10685 * [I] hwnd : window handle of window containing the cursor
10686 * [I] nHittest : hit-test code
10687 * [I] wMouseMsg : ideintifier of the mouse message
10690 * TRUE if cursor is set
10693 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10695 LVHITTESTINFO lvHitTestInfo;
10697 if (!LISTVIEW_IsHotTracking(infoPtr)) goto forward;
10699 if (!infoPtr->hHotCursor) goto forward;
10701 GetCursorPos(&lvHitTestInfo.pt);
10702 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) goto forward;
10704 SetCursor(infoPtr->hHotCursor);
10710 return DefWindowProcW(infoPtr->hwndSelf, WM_SETCURSOR, wParam, lParam);
10718 * [I] infoPtr : valid pointer to the listview structure
10719 * [I] hwndLoseFocus : handle of previously focused window
10724 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
10726 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
10728 /* if we have the focus already, there's nothing to do */
10729 if (infoPtr->bFocus) return 0;
10731 /* send NM_SETFOCUS notification */
10732 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
10734 /* set window focus flag */
10735 infoPtr->bFocus = TRUE;
10737 /* put the focus rect back on */
10738 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
10740 /* redraw all visible selected items */
10741 LISTVIEW_InvalidateSelectedItems(infoPtr);
10751 * [I] infoPtr : valid pointer to the listview structure
10752 * [I] fRedraw : font handle
10753 * [I] fRedraw : redraw flag
10758 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
10760 HFONT oldFont = infoPtr->hFont;
10762 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
10764 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
10765 if (infoPtr->hFont == oldFont) return 0;
10767 LISTVIEW_SaveTextMetrics(infoPtr);
10769 if (infoPtr->uView == LV_VIEW_DETAILS)
10771 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
10772 LISTVIEW_UpdateSize(infoPtr);
10773 LISTVIEW_UpdateScroll(infoPtr);
10776 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
10783 * Message handling for WM_SETREDRAW.
10784 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
10787 * [I] infoPtr : valid pointer to the listview structure
10788 * [I] bRedraw: state of redraw flag
10791 * DefWinProc return value
10793 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
10795 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
10797 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
10798 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
10800 infoPtr->bRedraw = bRedraw;
10802 if(!bRedraw) return 0;
10804 if (is_autoarrange(infoPtr))
10805 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10806 LISTVIEW_UpdateScroll(infoPtr);
10808 /* despite what the WM_SETREDRAW docs says, apps expect us
10809 * to invalidate the listview here... stupid! */
10810 LISTVIEW_InvalidateList(infoPtr);
10817 * Resizes the listview control. This function processes WM_SIZE
10818 * messages. At this time, the width and height are not used.
10821 * [I] infoPtr : valid pointer to the listview structure
10822 * [I] Width : new width
10823 * [I] Height : new height
10828 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
10830 RECT rcOld = infoPtr->rcList;
10832 TRACE("(width=%d, height=%d)\n", Width, Height);
10834 LISTVIEW_UpdateSize(infoPtr);
10835 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
10837 /* do not bother with display related stuff if we're not redrawing */
10838 if (!is_redrawing(infoPtr)) return 0;
10840 if (is_autoarrange(infoPtr))
10841 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10843 LISTVIEW_UpdateScroll(infoPtr);
10845 /* refresh all only for lists whose height changed significantly */
10846 if ((infoPtr->uView == LV_VIEW_LIST) &&
10847 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
10848 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
10849 LISTVIEW_InvalidateList(infoPtr);
10856 * Sets the size information.
10859 * [I] infoPtr : valid pointer to the listview structure
10864 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
10866 TRACE("uView=%d, rcList(old)=%s\n", infoPtr->uView, wine_dbgstr_rect(&infoPtr->rcList));
10868 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
10870 if (infoPtr->uView == LV_VIEW_LIST)
10872 /* Apparently the "LIST" style is supposed to have the same
10873 * number of items in a column even if there is no scroll bar.
10874 * Since if a scroll bar already exists then the bottom is already
10875 * reduced, only reduce if the scroll bar does not currently exist.
10876 * The "2" is there to mimic the native control. I think it may be
10877 * related to either padding or edges. (GLA 7/2002)
10879 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
10880 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
10881 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
10884 /* if control created invisible header isn't created */
10885 if (infoPtr->hwndHeader)
10890 hl.prc = &infoPtr->rcList;
10892 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
10893 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
10895 if (LISTVIEW_IsHeaderEnabled(infoPtr))
10896 wp.flags |= SWP_SHOWWINDOW;
10899 wp.flags |= SWP_HIDEWINDOW;
10903 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
10904 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
10906 infoPtr->rcList.top = max(wp.cy, 0);
10908 /* extra padding for grid */
10909 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
10910 infoPtr->rcList.top += 2;
10912 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
10917 * Processes WM_STYLECHANGED messages.
10920 * [I] infoPtr : valid pointer to the listview structure
10921 * [I] wStyleType : window style type (normal or extended)
10922 * [I] lpss : window style information
10927 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
10928 const STYLESTRUCT *lpss)
10930 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
10931 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
10934 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
10935 wStyleType, lpss->styleOld, lpss->styleNew);
10937 if (wStyleType != GWL_STYLE) return 0;
10939 infoPtr->dwStyle = lpss->styleNew;
10940 map_style_view(infoPtr);
10942 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
10943 ((lpss->styleNew & WS_HSCROLL) == 0))
10944 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
10946 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
10947 ((lpss->styleNew & WS_VSCROLL) == 0))
10948 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
10950 if (uNewView != uOldView)
10952 SIZE oldIconSize = infoPtr->iconSize;
10955 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
10956 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
10958 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
10959 SetRectEmpty(&infoPtr->rcFocus);
10961 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
10962 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
10964 if (uNewView == LVS_ICON)
10966 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
10968 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
10969 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
10970 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
10973 else if (uNewView == LVS_REPORT)
10978 LISTVIEW_CreateHeader( infoPtr );
10980 hl.prc = &infoPtr->rcList;
10982 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
10983 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
10984 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
10985 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
10988 LISTVIEW_UpdateItemSize(infoPtr);
10991 if (uNewView == LVS_REPORT || infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS)
10993 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
10995 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
10997 /* Turn off the header control */
10998 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
10999 TRACE("Hide header control, was 0x%08x\n", style);
11000 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
11002 /* Turn on the header control */
11003 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
11005 TRACE("Show header control, was 0x%08x\n", style);
11006 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
11012 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
11013 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
11014 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
11016 /* update the size of the client area */
11017 LISTVIEW_UpdateSize(infoPtr);
11019 /* add scrollbars if needed */
11020 LISTVIEW_UpdateScroll(infoPtr);
11022 /* invalidate client area + erase background */
11023 LISTVIEW_InvalidateList(infoPtr);
11030 * Processes WM_STYLECHANGING messages.
11033 * [I] wStyleType : window style type (normal or extended)
11034 * [I0] lpss : window style information
11039 static INT LISTVIEW_StyleChanging(WPARAM wStyleType,
11042 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
11043 wStyleType, lpss->styleOld, lpss->styleNew);
11045 /* don't forward LVS_OWNERDATA only if not already set to */
11046 if ((lpss->styleNew ^ lpss->styleOld) & LVS_OWNERDATA)
11048 if (lpss->styleOld & LVS_OWNERDATA)
11049 lpss->styleNew |= LVS_OWNERDATA;
11051 lpss->styleNew &= ~LVS_OWNERDATA;
11059 * Processes WM_SHOWWINDOW messages.
11062 * [I] infoPtr : valid pointer to the listview structure
11063 * [I] bShown : window is being shown (FALSE when hidden)
11064 * [I] iStatus : window show status
11069 static LRESULT LISTVIEW_ShowWindow(LISTVIEW_INFO *infoPtr, WPARAM bShown, LPARAM iStatus)
11071 /* header delayed creation */
11072 if ((infoPtr->uView == LV_VIEW_DETAILS) && bShown)
11074 LISTVIEW_CreateHeader(infoPtr);
11076 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle))
11077 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
11080 return DefWindowProcW(infoPtr->hwndSelf, WM_SHOWWINDOW, bShown, iStatus);
11085 * Processes CCM_GETVERSION messages.
11088 * [I] infoPtr : valid pointer to the listview structure
11093 static inline LRESULT LISTVIEW_GetVersion(const LISTVIEW_INFO *infoPtr)
11095 return infoPtr->iVersion;
11100 * Processes CCM_SETVERSION messages.
11103 * [I] infoPtr : valid pointer to the listview structure
11104 * [I] iVersion : version to be set
11107 * -1 when requested version is greater than DLL version;
11108 * previous version otherwise
11110 static LRESULT LISTVIEW_SetVersion(LISTVIEW_INFO *infoPtr, DWORD iVersion)
11112 INT iOldVersion = infoPtr->iVersion;
11114 if (iVersion > COMCTL32_VERSION)
11117 infoPtr->iVersion = iVersion;
11119 TRACE("new version %d\n", iVersion);
11121 return iOldVersion;
11126 * Window procedure of the listview control.
11129 static LRESULT WINAPI
11130 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
11132 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
11134 TRACE("(hwnd=%p uMsg=%x wParam=%lx lParam=%lx)\n", hwnd, uMsg, wParam, lParam);
11136 if (!infoPtr && (uMsg != WM_NCCREATE))
11137 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11141 case LVM_APPROXIMATEVIEWRECT:
11142 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
11143 LOWORD(lParam), HIWORD(lParam));
11145 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
11147 case LVM_CANCELEDITLABEL:
11148 return LISTVIEW_CancelEditLabel(infoPtr);
11150 case LVM_CREATEDRAGIMAGE:
11151 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
11153 case LVM_DELETEALLITEMS:
11154 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
11156 case LVM_DELETECOLUMN:
11157 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
11159 case LVM_DELETEITEM:
11160 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
11162 case LVM_EDITLABELA:
11163 case LVM_EDITLABELW:
11164 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam,
11165 uMsg == LVM_EDITLABELW);
11166 /* case LVM_ENABLEGROUPVIEW: */
11168 case LVM_ENSUREVISIBLE:
11169 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
11171 case LVM_FINDITEMW:
11172 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
11174 case LVM_FINDITEMA:
11175 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
11177 case LVM_GETBKCOLOR:
11178 return infoPtr->clrBk;
11180 /* case LVM_GETBKIMAGE: */
11182 case LVM_GETCALLBACKMASK:
11183 return infoPtr->uCallbackMask;
11185 case LVM_GETCOLUMNA:
11186 case LVM_GETCOLUMNW:
11187 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11188 uMsg == LVM_GETCOLUMNW);
11190 case LVM_GETCOLUMNORDERARRAY:
11191 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
11193 case LVM_GETCOLUMNWIDTH:
11194 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
11196 case LVM_GETCOUNTPERPAGE:
11197 return LISTVIEW_GetCountPerPage(infoPtr);
11199 case LVM_GETEDITCONTROL:
11200 return (LRESULT)infoPtr->hwndEdit;
11202 case LVM_GETEXTENDEDLISTVIEWSTYLE:
11203 return infoPtr->dwLvExStyle;
11205 /* case LVM_GETGROUPINFO: */
11207 /* case LVM_GETGROUPMETRICS: */
11209 case LVM_GETHEADER:
11210 return (LRESULT)infoPtr->hwndHeader;
11212 case LVM_GETHOTCURSOR:
11213 return (LRESULT)infoPtr->hHotCursor;
11215 case LVM_GETHOTITEM:
11216 return infoPtr->nHotItem;
11218 case LVM_GETHOVERTIME:
11219 return infoPtr->dwHoverTime;
11221 case LVM_GETIMAGELIST:
11222 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
11224 /* case LVM_GETINSERTMARK: */
11226 /* case LVM_GETINSERTMARKCOLOR: */
11228 /* case LVM_GETINSERTMARKRECT: */
11230 case LVM_GETISEARCHSTRINGA:
11231 case LVM_GETISEARCHSTRINGW:
11232 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
11237 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, uMsg == LVM_GETITEMW);
11239 case LVM_GETITEMCOUNT:
11240 return infoPtr->nItemCount;
11242 case LVM_GETITEMPOSITION:
11243 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
11245 case LVM_GETITEMRECT:
11246 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
11248 case LVM_GETITEMSPACING:
11249 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
11251 case LVM_GETITEMSTATE:
11252 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
11254 case LVM_GETITEMTEXTA:
11255 case LVM_GETITEMTEXTW:
11256 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam,
11257 uMsg == LVM_GETITEMTEXTW);
11259 case LVM_GETNEXTITEM:
11260 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
11262 case LVM_GETNUMBEROFWORKAREAS:
11263 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
11266 case LVM_GETORIGIN:
11267 if (!lParam) return FALSE;
11268 if (infoPtr->uView == LV_VIEW_DETAILS ||
11269 infoPtr->uView == LV_VIEW_LIST) return FALSE;
11270 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
11273 /* case LVM_GETOUTLINECOLOR: */
11275 /* case LVM_GETSELECTEDCOLUMN: */
11277 case LVM_GETSELECTEDCOUNT:
11278 return LISTVIEW_GetSelectedCount(infoPtr);
11280 case LVM_GETSELECTIONMARK:
11281 return infoPtr->nSelectionMark;
11283 case LVM_GETSTRINGWIDTHA:
11284 case LVM_GETSTRINGWIDTHW:
11285 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam,
11286 uMsg == LVM_GETSTRINGWIDTHW);
11288 case LVM_GETSUBITEMRECT:
11289 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
11291 case LVM_GETTEXTBKCOLOR:
11292 return infoPtr->clrTextBk;
11294 case LVM_GETTEXTCOLOR:
11295 return infoPtr->clrText;
11297 /* case LVM_GETTILEINFO: */
11299 /* case LVM_GETTILEVIEWINFO: */
11301 case LVM_GETTOOLTIPS:
11302 if( !infoPtr->hwndToolTip )
11303 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
11304 return (LRESULT)infoPtr->hwndToolTip;
11306 case LVM_GETTOPINDEX:
11307 return LISTVIEW_GetTopIndex(infoPtr);
11309 case LVM_GETUNICODEFORMAT:
11310 return (infoPtr->notifyFormat == NFR_UNICODE);
11313 return infoPtr->uView;
11315 case LVM_GETVIEWRECT:
11316 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
11318 case LVM_GETWORKAREAS:
11319 FIXME("LVM_GETWORKAREAS: unimplemented\n");
11322 /* case LVM_HASGROUP: */
11325 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, TRUE);
11327 case LVM_INSERTCOLUMNA:
11328 case LVM_INSERTCOLUMNW:
11329 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11330 uMsg == LVM_INSERTCOLUMNW);
11332 /* case LVM_INSERTGROUP: */
11334 /* case LVM_INSERTGROUPSORTED: */
11336 case LVM_INSERTITEMA:
11337 case LVM_INSERTITEMW:
11338 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, uMsg == LVM_INSERTITEMW);
11340 /* case LVM_INSERTMARKHITTEST: */
11342 /* case LVM_ISGROUPVIEWENABLED: */
11344 case LVM_ISITEMVISIBLE:
11345 return LISTVIEW_IsItemVisible(infoPtr, (INT)wParam);
11347 case LVM_MAPIDTOINDEX:
11348 return LISTVIEW_MapIdToIndex(infoPtr, (UINT)wParam);
11350 case LVM_MAPINDEXTOID:
11351 return LISTVIEW_MapIndexToId(infoPtr, (INT)wParam);
11353 /* case LVM_MOVEGROUP: */
11355 /* case LVM_MOVEITEMTOGROUP: */
11357 case LVM_REDRAWITEMS:
11358 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
11360 /* case LVM_REMOVEALLGROUPS: */
11362 /* case LVM_REMOVEGROUP: */
11365 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
11367 case LVM_SETBKCOLOR:
11368 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
11370 /* case LVM_SETBKIMAGE: */
11372 case LVM_SETCALLBACKMASK:
11373 infoPtr->uCallbackMask = (UINT)wParam;
11376 case LVM_SETCOLUMNA:
11377 case LVM_SETCOLUMNW:
11378 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11379 uMsg == LVM_SETCOLUMNW);
11381 case LVM_SETCOLUMNORDERARRAY:
11382 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
11384 case LVM_SETCOLUMNWIDTH:
11385 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
11387 case LVM_SETEXTENDEDLISTVIEWSTYLE:
11388 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
11390 /* case LVM_SETGROUPINFO: */
11392 /* case LVM_SETGROUPMETRICS: */
11394 case LVM_SETHOTCURSOR:
11395 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
11397 case LVM_SETHOTITEM:
11398 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
11400 case LVM_SETHOVERTIME:
11401 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)lParam);
11403 case LVM_SETICONSPACING:
11404 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
11406 case LVM_SETIMAGELIST:
11407 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
11409 /* case LVM_SETINFOTIP: */
11411 /* case LVM_SETINSERTMARK: */
11413 /* case LVM_SETINSERTMARKCOLOR: */
11418 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
11419 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, (uMsg == LVM_SETITEMW));
11422 case LVM_SETITEMCOUNT:
11423 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
11425 case LVM_SETITEMPOSITION:
11428 pt.x = (short)LOWORD(lParam);
11429 pt.y = (short)HIWORD(lParam);
11430 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, &pt);
11433 case LVM_SETITEMPOSITION32:
11434 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, (POINT*)lParam);
11436 case LVM_SETITEMSTATE:
11437 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
11439 case LVM_SETITEMTEXTA:
11440 case LVM_SETITEMTEXTW:
11441 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam,
11442 uMsg == LVM_SETITEMTEXTW);
11444 /* case LVM_SETOUTLINECOLOR: */
11446 /* case LVM_SETSELECTEDCOLUMN: */
11448 case LVM_SETSELECTIONMARK:
11449 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
11451 case LVM_SETTEXTBKCOLOR:
11452 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
11454 case LVM_SETTEXTCOLOR:
11455 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
11457 /* case LVM_SETTILEINFO: */
11459 /* case LVM_SETTILEVIEWINFO: */
11461 /* case LVM_SETTILEWIDTH: */
11463 case LVM_SETTOOLTIPS:
11464 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
11466 case LVM_SETUNICODEFORMAT:
11467 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
11470 return LISTVIEW_SetView(infoPtr, wParam);
11472 /* case LVM_SETWORKAREAS: */
11474 /* case LVM_SORTGROUPS: */
11476 case LVM_SORTITEMS:
11477 case LVM_SORTITEMSEX:
11478 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, wParam,
11479 uMsg == LVM_SORTITEMSEX);
11480 case LVM_SUBITEMHITTEST:
11481 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
11484 return LISTVIEW_Update(infoPtr, (INT)wParam);
11486 case CCM_GETVERSION:
11487 return LISTVIEW_GetVersion(infoPtr);
11489 case CCM_SETVERSION:
11490 return LISTVIEW_SetVersion(infoPtr, wParam);
11493 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
11496 return LISTVIEW_Command(infoPtr, wParam, lParam);
11499 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
11502 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
11505 return LISTVIEW_Destroy(infoPtr);
11508 return LISTVIEW_Enable(infoPtr);
11510 case WM_ERASEBKGND:
11511 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
11513 case WM_GETDLGCODE:
11514 return DLGC_WANTCHARS | DLGC_WANTARROWS;
11517 return (LRESULT)infoPtr->hFont;
11520 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0);
11523 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
11526 return LISTVIEW_KillFocus(infoPtr);
11528 case WM_LBUTTONDBLCLK:
11529 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11531 case WM_LBUTTONDOWN:
11532 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11535 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11538 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11540 case WM_MOUSEHOVER:
11541 return LISTVIEW_MouseHover(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11544 return LISTVIEW_NCDestroy(infoPtr);
11547 return LISTVIEW_NCPaint(infoPtr, (HRGN)wParam);
11550 return LISTVIEW_Notify(infoPtr, (LPNMHDR)lParam);
11552 case WM_NOTIFYFORMAT:
11553 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
11555 case WM_PRINTCLIENT:
11556 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
11559 return LISTVIEW_WMPaint(infoPtr, (HDC)wParam);
11561 case WM_RBUTTONDBLCLK:
11562 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11564 case WM_RBUTTONDOWN:
11565 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11568 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11571 return LISTVIEW_SetCursor(infoPtr, wParam, lParam);
11574 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
11577 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
11580 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
11582 case WM_SHOWWINDOW:
11583 return LISTVIEW_ShowWindow(infoPtr, wParam, lParam);
11585 case WM_STYLECHANGED:
11586 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
11588 case WM_STYLECHANGING:
11589 return LISTVIEW_StyleChanging(wParam, (LPSTYLESTRUCT)lParam);
11591 case WM_SYSCOLORCHANGE:
11592 COMCTL32_RefreshSysColors();
11595 /* case WM_TIMER: */
11596 case WM_THEMECHANGED:
11597 return LISTVIEW_ThemeChanged(infoPtr);
11600 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0);
11602 case WM_MOUSEWHEEL:
11603 if (wParam & (MK_SHIFT | MK_CONTROL))
11604 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11605 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
11607 case WM_WINDOWPOSCHANGED:
11608 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
11610 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
11611 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
11613 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
11615 if (notify_measureitem(infoPtr)) LISTVIEW_InvalidateList(infoPtr);
11617 LISTVIEW_Size(infoPtr, ((WINDOWPOS *)lParam)->cx, ((WINDOWPOS *)lParam)->cy);
11619 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11621 /* case WM_WININICHANGE: */
11624 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
11625 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
11627 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11634 * Registers the window class.
11642 void LISTVIEW_Register(void)
11644 WNDCLASSW wndClass;
11646 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
11647 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
11648 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
11649 wndClass.cbClsExtra = 0;
11650 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
11651 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
11652 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
11653 wndClass.lpszClassName = WC_LISTVIEWW;
11654 RegisterClassW(&wndClass);
11659 * Unregisters the window class.
11667 void LISTVIEW_Unregister(void)
11669 UnregisterClassW(WC_LISTVIEWW, NULL);
11674 * Handle any WM_COMMAND messages
11677 * [I] infoPtr : valid pointer to the listview structure
11678 * [I] wParam : the first message parameter
11679 * [I] lParam : the second message parameter
11684 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
11687 TRACE("(%p %x %x %lx)\n", infoPtr, HIWORD(wParam), LOWORD(wParam), lParam);
11689 if (!infoPtr->hwndEdit) return 0;
11691 switch (HIWORD(wParam))
11696 * Adjust the edit window size
11698 WCHAR buffer[1024];
11699 HDC hdc = GetDC(infoPtr->hwndEdit);
11700 HFONT hFont, hOldFont = 0;
11704 if (!infoPtr->hwndEdit || !hdc) return 0;
11705 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
11706 GetWindowRect(infoPtr->hwndEdit, &rect);
11708 /* Select font to get the right dimension of the string */
11709 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
11712 hOldFont = SelectObject(hdc, hFont);
11715 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
11717 TEXTMETRICW textMetric;
11719 /* Add Extra spacing for the next character */
11720 GetTextMetricsW(hdc, &textMetric);
11721 sz.cx += (textMetric.tmMaxCharWidth * 2);
11723 SetWindowPos(infoPtr->hwndEdit, NULL, 0, 0, sz.cx,
11724 rect.bottom - rect.top, SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOZORDER);
11727 SelectObject(hdc, hOldFont);
11729 ReleaseDC(infoPtr->hwndEdit, hdc);
11735 LISTVIEW_CancelEditLabel(infoPtr);
11740 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);