4 * Copyright 1998, 1999 Eric Kohl
5 * Copyright 1999 Luc Tourangeau
6 * Copyright 2000 Jason Mawdsley
7 * Copyright 2001 CodeWeavers Inc.
8 * Copyright 2002 Dimitrie O. Paun
9 * Copyright 2009-2013 Nikolay Sivov
10 * Copyright 2009 Owen Rudge for CodeWeavers
12 * This library is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public
14 * License as published by the Free Software Foundation; either
15 * version 2.1 of the License, or (at your option) any later version.
17 * This library is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Lesser General Public License for more details.
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this library; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
28 * This code was audited for completeness against the documented features
29 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
31 * Unless otherwise noted, we believe this code to be complete, as per
32 * the specification mentioned above.
33 * If you discover missing features, or bugs, please note them below.
37 * Default Message Processing
38 * -- WM_CREATE: create the icon and small icon image lists at this point only if
39 * the LVS_SHAREIMAGELISTS style is not specified.
40 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
41 * or small icon and the LVS_AUTOARRANGE style is specified.
46 * -- Hot item handling, mouse hovering
47 * -- Workareas support
52 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
53 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
54 * -- LVA_SNAPTOGRID not implemented
55 * -- LISTVIEW_ApproximateViewRect partially implemented
56 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
57 * -- LISTVIEW_SetIconSpacing is incomplete
58 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
61 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
62 * linear in the number of items in the list, and this is
63 * unacceptable for large lists.
64 * -- if list is sorted by item text LISTVIEW_InsertItemT could use
65 * binary search to calculate item index (e.g. DPA_Search()).
66 * This requires sorted state to be reliably tracked in item modifiers.
67 * -- we should keep an ordered array of coordinates in iconic mode
68 * this would allow to frame items (iterator_frameditems),
69 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
76 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
82 * -- LVS_NOSCROLL (see Q137520)
86 * -- LVS_EX_BORDERSELECT
90 * -- LVS_EX_MULTIWORKAREAS
92 * -- LVS_EX_SIMPLESELECT
93 * -- LVS_EX_TWOCLICKACTIVATE
94 * -- LVS_EX_UNDERLINECOLD
95 * -- LVS_EX_UNDERLINEHOT
98 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
105 * -- LVM_ENABLEGROUPVIEW
106 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
107 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
108 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
109 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
110 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
111 * -- LVM_GETINSERTMARKRECT
112 * -- LVM_GETNUMBEROFWORKAREAS
113 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
114 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
115 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
116 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
117 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
118 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
119 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
120 * -- LVM_INSERTGROUPSORTED
121 * -- LVM_INSERTMARKHITTEST
122 * -- LVM_ISGROUPVIEWENABLED
124 * -- LVM_MOVEITEMTOGROUP
126 * -- LVM_SETTILEWIDTH
130 * -- ListView_GetHoverTime, ListView_SetHoverTime
131 * -- ListView_GetISearchString
132 * -- ListView_GetNumberOfWorkAreas
133 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
138 * Known differences in message stream from native control (not known if
139 * these differences cause problems):
140 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
141 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
142 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
143 * processing for "USEDOUBLECLICKTIME".
147 #include "wine/port.h"
162 #include "commctrl.h"
163 #include "comctl32.h"
166 #include "wine/debug.h"
167 #include "wine/unicode.h"
169 WINE_DEFAULT_DEBUG_CHANNEL(listview);
171 typedef struct tagCOLUMN_INFO
173 RECT rcHeader; /* tracks the header's rectangle */
174 INT fmt; /* same as LVCOLUMN.fmt */
178 typedef struct tagITEMHDR
182 } ITEMHDR, *LPITEMHDR;
184 typedef struct tagSUBITEM_INFO
190 typedef struct tagITEM_ID ITEM_ID;
192 typedef struct tagITEM_INFO
203 UINT id; /* item id */
204 HDPA item; /* link to item data */
207 typedef struct tagRANGE
213 typedef struct tagRANGES
218 typedef struct tagITERATOR
227 typedef struct tagDELAYED_ITEM_EDIT
233 typedef struct tagLISTVIEW_INFO
237 RECT rcList; /* This rectangle is really the window
238 * client rectangle possibly reduced by the
239 * horizontal scroll bar and/or header - see
240 * LISTVIEW_UpdateSize. This rectangle offset
241 * by the LISTVIEW_GetOrigin value is in
242 * client coordinates */
244 /* notification window */
247 BOOL bDoChangeNotify; /* send change notification messages? */
254 INT nItemCount; /* the number of items in the list */
255 HDPA hdpaItems; /* array ITEM_INFO pointers */
256 HDPA hdpaItemIds; /* array of ITEM_ID pointers */
257 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
258 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
259 RANGES selectionRanges;
260 INT nSelectionMark; /* item to start next multiselection from */
262 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
265 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
266 BOOL colRectsDirty; /* trigger column rectangles requery from header */
269 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
274 PFNLVCOMPARE pfnCompare; /* sorting callback pointer */
278 DWORD dwStyle; /* the cached window GWL_STYLE */
279 DWORD dwLvExStyle; /* extended listview style */
280 DWORD uView; /* current view available through LVM_[G,S]ETVIEW */
286 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
289 HIMAGELIST himlNormal;
290 HIMAGELIST himlSmall;
291 HIMAGELIST himlState;
295 POINT currIconPos; /* this is the position next icon will be placed */
299 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
301 /* marquee selection */
302 BOOL bMarqueeSelect; /* marquee selection/highlight underway */
304 RECT marqueeRect; /* absolute coordinates of marquee selection */
305 RECT marqueeDrawRect; /* relative coordinates for drawing marquee */
306 POINT marqueeOrigin; /* absolute coordinates of marquee click origin */
309 BOOL bFocus; /* control has focus */
311 RECT rcFocus; /* focus bounds */
322 INT ntmHeight; /* Some cached metrics of the font used */
323 INT ntmMaxCharWidth; /* by the listview to draw items */
326 /* mouse operation */
330 POINT ptClickPos; /* point where the user clicked */
331 INT nLButtonDownItem; /* tracks item to reset multiselection on WM_LBUTTONUP */
335 /* keyboard operation */
336 DWORD lastKeyPressTimestamp;
338 INT nSearchParamLength;
339 WCHAR szSearchParam[ MAX_PATH ];
342 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
343 BOOL bIsDrawing; /* Drawing in progress */
344 INT nMeasureItemHeight; /* WM_MEASUREITEM result */
345 BOOL bRedraw; /* WM_SETREDRAW switch */
348 DWORD iVersion; /* CCM_[G,S]ETVERSION */
354 /* How many we debug buffer to allocate */
355 #define DEBUG_BUFFERS 20
356 /* The size of a single debug buffer */
357 #define DEBUG_BUFFER_SIZE 256
359 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
360 #define SB_INTERNAL -1
362 /* maximum size of a label */
363 #define DISP_TEXT_SIZE 260
365 /* padding for items in list and small icon display modes */
366 #define WIDTH_PADDING 12
368 /* padding for items in list, report and small icon display modes */
369 #define HEIGHT_PADDING 1
371 /* offset of items in report display mode */
372 #define REPORT_MARGINX 2
374 /* padding for icon in large icon display mode
375 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
376 * that HITTEST will see.
377 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
378 * ICON_TOP_PADDING - sum of the two above.
379 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
380 * LABEL_HOR_PADDING - between text and sides of box
381 * LABEL_VERT_PADDING - between bottom of text and end of box
383 * ICON_LR_PADDING - additional width above icon size.
384 * ICON_LR_HALF - half of the above value
386 #define ICON_TOP_PADDING_NOTHITABLE 2
387 #define ICON_TOP_PADDING_HITABLE 2
388 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
389 #define ICON_BOTTOM_PADDING 4
390 #define LABEL_HOR_PADDING 5
391 #define LABEL_VERT_PADDING 7
392 #define ICON_LR_PADDING 16
393 #define ICON_LR_HALF (ICON_LR_PADDING/2)
395 /* default label width for items in list and small icon display modes */
396 #define DEFAULT_LABEL_WIDTH 40
397 /* maximum select rectangle width for empty text item in LV_VIEW_DETAILS */
398 #define MAX_EMPTYTEXT_SELECT_WIDTH 80
400 /* default column width for items in list display mode */
401 #define DEFAULT_COLUMN_WIDTH 128
403 /* Size of "line" scroll for V & H scrolls */
404 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
406 /* Padding between image and label */
407 #define IMAGE_PADDING 2
409 /* Padding behind the label */
410 #define TRAILING_LABEL_PADDING 12
411 #define TRAILING_HEADER_PADDING 11
413 /* Border for the icon caption */
414 #define CAPTION_BORDER 2
416 /* Standard DrawText flags */
417 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
418 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
419 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
421 /* Image index from state */
422 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
424 /* The time in milliseconds to reset the search in the list */
425 #define KEY_DELAY 450
427 /* Dump the LISTVIEW_INFO structure to the debug channel */
428 #define LISTVIEW_DUMP(iP) do { \
429 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
430 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
431 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
432 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
433 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
434 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
435 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
436 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
437 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
438 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
441 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
444 * forward declarations
446 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
447 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
448 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
449 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
450 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
451 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
452 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
453 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
454 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
455 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
456 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
457 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
458 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
459 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT);
460 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT);
461 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
462 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
463 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
464 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *, BOOL, BOOL);
465 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *, INT, INT);
467 /******** Text handling functions *************************************/
469 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
470 * text string. The string may be ANSI or Unicode, in which case
471 * the boolean isW tells us the type of the string.
473 * The name of the function tell what type of strings it expects:
474 * W: Unicode, T: ANSI/Unicode - function of isW
477 static inline BOOL is_text(LPCWSTR text)
479 return text != NULL && text != LPSTR_TEXTCALLBACKW;
482 static inline int textlenT(LPCWSTR text, BOOL isW)
484 return !is_text(text) ? 0 :
485 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
488 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
491 if (isSrcW) lstrcpynW(dest, src, max);
492 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
494 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
495 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
498 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
500 LPWSTR wstr = (LPWSTR)text;
502 if (!isW && is_text(text))
504 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
505 wstr = Alloc(len * sizeof(WCHAR));
506 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
508 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
512 static inline void textfreeT(LPWSTR wstr, BOOL isW)
514 if (!isW && is_text(wstr)) Free (wstr);
518 * dest is a pointer to a Unicode string
519 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
521 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
525 if (src == LPSTR_TEXTCALLBACKW)
527 if (is_text(*dest)) Free(*dest);
528 *dest = LPSTR_TEXTCALLBACKW;
532 LPWSTR pszText = textdupTtoW(src, isW);
533 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
534 bResult = Str_SetPtrW(dest, pszText);
535 textfreeT(pszText, isW);
541 * compares a Unicode to a Unicode/ANSI text string
543 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
545 if (!aw) return bt ? -1 : 0;
547 if (aw == LPSTR_TEXTCALLBACKW)
548 return bt == LPSTR_TEXTCALLBACKW ? 1 : -1;
549 if (bt != LPSTR_TEXTCALLBACKW)
551 LPWSTR bw = textdupTtoW(bt, isW);
552 int r = bw ? lstrcmpW(aw, bw) : 1;
560 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
562 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
563 return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n) - CSTR_EQUAL;
566 /******** Debugging functions *****************************************/
568 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
570 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
571 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
574 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
576 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
577 n = min(textlenT(text, isW), n);
578 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
581 static char* debug_getbuf(void)
583 static int index = 0;
584 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
585 return buffers[index++ % DEBUG_BUFFERS];
588 static inline const char* debugrange(const RANGE *lprng)
590 if (!lprng) return "(null)";
591 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
594 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
596 char* buf = debug_getbuf(), *text = buf;
597 int len, size = DEBUG_BUFFER_SIZE;
599 if (pScrollInfo == NULL) return "(null)";
600 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
601 if (len == -1) goto end; buf += len; size -= len;
602 if (pScrollInfo->fMask & SIF_RANGE)
603 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
605 if (len == -1) goto end; buf += len; size -= len;
606 if (pScrollInfo->fMask & SIF_PAGE)
607 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
609 if (len == -1) goto end; buf += len; size -= len;
610 if (pScrollInfo->fMask & SIF_POS)
611 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
613 if (len == -1) goto end; buf += len; size -= len;
614 if (pScrollInfo->fMask & SIF_TRACKPOS)
615 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
617 if (len == -1) goto end; buf += len;
620 buf = text + strlen(text);
622 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
626 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
628 if (!plvnm) return "(null)";
629 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
630 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
631 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
632 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
635 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
637 char* buf = debug_getbuf(), *text = buf;
638 int len, size = DEBUG_BUFFER_SIZE;
640 if (lpLVItem == NULL) return "(null)";
641 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
642 if (len == -1) goto end; buf += len; size -= len;
643 if (lpLVItem->mask & LVIF_STATE)
644 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
646 if (len == -1) goto end; buf += len; size -= len;
647 if (lpLVItem->mask & LVIF_TEXT)
648 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
650 if (len == -1) goto end; buf += len; size -= len;
651 if (lpLVItem->mask & LVIF_IMAGE)
652 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
654 if (len == -1) goto end; buf += len; size -= len;
655 if (lpLVItem->mask & LVIF_PARAM)
656 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
658 if (len == -1) goto end; buf += len; size -= len;
659 if (lpLVItem->mask & LVIF_INDENT)
660 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
662 if (len == -1) goto end; buf += len;
665 buf = text + strlen(text);
667 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
671 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
673 char* buf = debug_getbuf(), *text = buf;
674 int len, size = DEBUG_BUFFER_SIZE;
676 if (lpColumn == NULL) return "(null)";
677 len = snprintf(buf, size, "{");
678 if (len == -1) goto end; buf += len; size -= len;
679 if (lpColumn->mask & LVCF_SUBITEM)
680 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
682 if (len == -1) goto end; buf += len; size -= len;
683 if (lpColumn->mask & LVCF_FMT)
684 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
686 if (len == -1) goto end; buf += len; size -= len;
687 if (lpColumn->mask & LVCF_WIDTH)
688 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
690 if (len == -1) goto end; buf += len; size -= len;
691 if (lpColumn->mask & LVCF_TEXT)
692 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
694 if (len == -1) goto end; buf += len; size -= len;
695 if (lpColumn->mask & LVCF_IMAGE)
696 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
698 if (len == -1) goto end; buf += len; size -= len;
699 if (lpColumn->mask & LVCF_ORDER)
700 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
702 if (len == -1) goto end; buf += len;
705 buf = text + strlen(text);
707 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
711 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
713 if (!lpht) return "(null)";
715 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
716 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
719 /* Return the corresponding text for a given scroll value */
720 static inline LPCSTR debugscrollcode(int nScrollCode)
724 case SB_LINELEFT: return "SB_LINELEFT";
725 case SB_LINERIGHT: return "SB_LINERIGHT";
726 case SB_PAGELEFT: return "SB_PAGELEFT";
727 case SB_PAGERIGHT: return "SB_PAGERIGHT";
728 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
729 case SB_THUMBTRACK: return "SB_THUMBTRACK";
730 case SB_ENDSCROLL: return "SB_ENDSCROLL";
731 case SB_INTERNAL: return "SB_INTERNAL";
732 default: return "unknown";
737 /******** Notification functions ************************************/
739 static int get_ansi_notification(UINT unicodeNotificationCode)
741 switch (unicodeNotificationCode)
743 case LVN_BEGINLABELEDITA:
744 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
745 case LVN_ENDLABELEDITA:
746 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
747 case LVN_GETDISPINFOA:
748 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
749 case LVN_SETDISPINFOA:
750 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
751 case LVN_ODFINDITEMA:
752 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
753 case LVN_GETINFOTIPA:
754 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
755 /* header forwards */
757 case HDN_TRACKW: return HDN_TRACKA;
759 case HDN_ENDTRACKW: return HDN_ENDTRACKA;
760 case HDN_BEGINDRAG: return HDN_BEGINDRAG;
761 case HDN_ENDDRAG: return HDN_ENDDRAG;
762 case HDN_ITEMCHANGINGA:
763 case HDN_ITEMCHANGINGW: return HDN_ITEMCHANGINGA;
764 case HDN_ITEMCHANGEDA:
765 case HDN_ITEMCHANGEDW: return HDN_ITEMCHANGEDA;
767 case HDN_ITEMCLICKW: return HDN_ITEMCLICKA;
768 case HDN_DIVIDERDBLCLICKA:
769 case HDN_DIVIDERDBLCLICKW: return HDN_DIVIDERDBLCLICKA;
772 FIXME("unknown notification %x\n", unicodeNotificationCode);
773 return unicodeNotificationCode;
776 /* forwards header notifications to listview parent */
777 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, NMHEADERW *lpnmhW)
779 LPCWSTR text = NULL, filter = NULL;
781 NMHEADERA *lpnmh = (NMHEADERA*) lpnmhW;
783 /* on unicode format exit earlier */
784 if (infoPtr->notifyFormat == NFR_UNICODE)
785 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, lpnmh->hdr.idFrom,
788 /* header always supplies unicode notifications,
789 all we have to do is to convert strings to ANSI */
792 /* convert item text */
793 if (lpnmh->pitem->mask & HDI_TEXT)
795 text = (LPCWSTR)lpnmh->pitem->pszText;
796 Str_SetPtrWtoA(&lpnmh->pitem->pszText, text);
798 /* convert filter text */
799 if ((lpnmh->pitem->mask & HDI_FILTER) && (lpnmh->pitem->type == HDFT_ISSTRING) &&
800 lpnmh->pitem->pvFilter)
802 filter = (LPCWSTR)((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText;
803 Str_SetPtrWtoA(&((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText, filter);
806 lpnmh->hdr.code = get_ansi_notification(lpnmh->hdr.code);
808 ret = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, lpnmh->hdr.idFrom,
814 Free(lpnmh->pitem->pszText);
815 lpnmh->pitem->pszText = (LPSTR)text;
819 Free(((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText);
820 ((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText = (LPSTR)filter;
826 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
830 TRACE("(code=%d)\n", code);
832 pnmh->hwndFrom = infoPtr->hwndSelf;
833 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
835 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
837 TRACE(" <= %ld\n", result);
842 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
845 HWND hwnd = infoPtr->hwndSelf;
846 notify_hdr(infoPtr, code, &nmh);
847 return IsWindow(hwnd);
850 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
861 item.mask = LVIF_PARAM|LVIF_STATE;
862 item.iItem = htInfo->iItem;
864 item.stateMask = (UINT)-1;
865 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
866 nmia.lParam = item.lParam;
867 nmia.uOldState = item.state;
868 nmia.uNewState = item.state | LVIS_ACTIVATING;
869 nmia.uChanged = LVIF_STATE;
872 nmia.iItem = htInfo->iItem;
873 nmia.iSubItem = htInfo->iSubItem;
874 nmia.ptAction = htInfo->pt;
876 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
877 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
878 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
880 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
883 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
885 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
886 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
889 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
893 HWND hwnd = infoPtr->hwndSelf;
895 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
896 ZeroMemory(&nmia, sizeof(nmia));
897 nmia.iItem = lvht->iItem;
898 nmia.iSubItem = lvht->iSubItem;
899 nmia.ptAction = lvht->pt;
900 item.mask = LVIF_PARAM;
901 item.iItem = lvht->iItem;
903 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmia.lParam = item.lParam;
904 notify_hdr(infoPtr, code, (LPNMHDR)&nmia);
905 return IsWindow(hwnd);
908 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
912 HWND hwnd = infoPtr->hwndSelf;
914 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
916 item.mask = LVIF_PARAM;
919 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
920 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
921 return IsWindow(hwnd);
925 Send notification. depends on dispinfoW having same
926 structure as dispinfoA.
927 infoPtr : listview struct
928 code : *Unicode* notification code
929 pdi : dispinfo structure (can be unicode or ansi)
930 isW : TRUE if dispinfo is Unicode
932 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT code, LPNMLVDISPINFOW pdi, BOOL isW)
934 INT length = 0, ret_length;
935 LPWSTR buffer = NULL, ret_text;
936 BOOL return_ansi = FALSE;
937 BOOL return_unicode = FALSE;
940 if ((pdi->item.mask & LVIF_TEXT) && is_text(pdi->item.pszText))
942 return_unicode = ( isW && infoPtr->notifyFormat == NFR_ANSI);
943 return_ansi = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
946 ret_length = pdi->item.cchTextMax;
947 ret_text = pdi->item.pszText;
949 if (return_unicode || return_ansi)
951 if (code != LVN_GETDISPINFOW)
953 length = return_ansi ?
954 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
955 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
959 length = pdi->item.cchTextMax;
960 *pdi->item.pszText = 0; /* make sure we don't process garbage */
963 buffer = Alloc( (return_ansi ? sizeof(WCHAR) : sizeof(CHAR)) * length);
964 if (!buffer) return FALSE;
967 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
970 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) buffer,
973 pdi->item.pszText = buffer;
974 pdi->item.cchTextMax = length;
977 if (infoPtr->notifyFormat == NFR_ANSI)
978 code = get_ansi_notification(code);
980 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
981 ret = notify_hdr(infoPtr, code, &pdi->hdr);
982 TRACE(" resulting code=%d\n", pdi->hdr.code);
984 if (return_ansi || return_unicode)
986 if (return_ansi && (pdi->hdr.code == LVN_GETDISPINFOA))
988 strcpy((char*)ret_text, (char*)pdi->item.pszText);
990 else if (return_unicode && (pdi->hdr.code == LVN_GETDISPINFOW))
992 strcpyW(ret_text, pdi->item.pszText);
994 else if (return_ansi) /* note : pointer can be changed by app ! */
996 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) ret_text,
997 ret_length, NULL, NULL);
1000 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
1001 ret_text, ret_length);
1003 pdi->item.pszText = ret_text; /* restores our buffer */
1004 pdi->item.cchTextMax = ret_length;
1010 /* if dipsinfo holder changed notification code then convert */
1011 if (!isW && (pdi->hdr.code == LVN_GETDISPINFOW) && (pdi->item.mask & LVIF_TEXT))
1013 length = WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
1015 buffer = Alloc(length * sizeof(CHAR));
1016 if (!buffer) return FALSE;
1018 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) buffer,
1019 ret_length, NULL, NULL);
1021 strcpy((LPSTR)pdi->item.pszText, (LPSTR)buffer);
1028 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
1029 const RECT *rcBounds, const LVITEMW *lplvItem)
1031 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
1032 lpnmlvcd->nmcd.hdc = hdc;
1033 lpnmlvcd->nmcd.rc = *rcBounds;
1034 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
1035 lpnmlvcd->clrText = infoPtr->clrText;
1036 if (!lplvItem) return;
1037 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
1038 lpnmlvcd->iSubItem = lplvItem->iSubItem;
1039 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
1040 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
1041 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
1042 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
1045 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
1047 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
1050 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
1051 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
1052 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
1053 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
1054 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
1055 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
1059 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
1061 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
1062 lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
1063 if (lpnmlvcd->clrText == CLR_DEFAULT)
1064 lpnmlvcd->clrText = comctl32_color.clrWindowText;
1066 /* apparently, for selected items, we have to override the returned values */
1069 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
1071 if (infoPtr->bFocus)
1073 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
1074 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
1076 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
1078 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
1079 lpnmlvcd->clrText = comctl32_color.clrBtnText;
1084 /* Set the text attributes */
1085 if (lpnmlvcd->clrTextBk != CLR_NONE)
1087 SetBkMode(hdc, OPAQUE);
1088 SetBkColor(hdc,lpnmlvcd->clrTextBk);
1091 SetBkMode(hdc, TRANSPARENT);
1092 SetTextColor(hdc, lpnmlvcd->clrText);
1095 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
1097 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
1100 /* returns TRUE when repaint needed, FALSE otherwise */
1101 static BOOL notify_measureitem(LISTVIEW_INFO *infoPtr)
1103 MEASUREITEMSTRUCT mis;
1104 mis.CtlType = ODT_LISTVIEW;
1105 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1109 mis.itemHeight= infoPtr->nItemHeight;
1110 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
1111 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
1113 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
1119 /******** Item iterator functions **********************************/
1121 static RANGES ranges_create(int count);
1122 static void ranges_destroy(RANGES ranges);
1123 static BOOL ranges_add(RANGES ranges, RANGE range);
1124 static BOOL ranges_del(RANGES ranges, RANGE range);
1125 static void ranges_dump(RANGES ranges);
1127 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
1129 RANGE range = { nItem, nItem + 1 };
1131 return ranges_add(ranges, range);
1134 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1136 RANGE range = { nItem, nItem + 1 };
1138 return ranges_del(ranges, range);
1142 * ITERATOR DOCUMENTATION
1144 * The iterator functions allow for easy, and convenient iteration
1145 * over items of interest in the list. Typically, you create a
1146 * iterator, use it, and destroy it, as such:
1149 * iterator_xxxitems(&i, ...);
1150 * while (iterator_{prev,next}(&i)
1152 * //code which uses i.nItem
1154 * iterator_destroy(&i);
1156 * where xxx is either: framed, or visible.
1157 * Note that it is important that the code destroys the iterator
1158 * after it's done with it, as the creation of the iterator may
1159 * allocate memory, which thus needs to be freed.
1161 * You can iterate both forwards, and backwards through the list,
1162 * by using iterator_next or iterator_prev respectively.
1164 * Lower numbered items are draw on top of higher number items in
1165 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1166 * items may overlap). So, to test items, you should use
1168 * which lists the items top to bottom (in Z-order).
1169 * For drawing items, you should use
1171 * which lists the items bottom to top (in Z-order).
1172 * If you keep iterating over the items after the end-of-items
1173 * marker (-1) is returned, the iterator will start from the
1174 * beginning. Typically, you don't need to test for -1,
1175 * because iterator_{next,prev} will return TRUE if more items
1176 * are to be iterated over, or FALSE otherwise.
1178 * Note: the iterator is defined to be bidirectional. That is,
1179 * any number of prev followed by any number of next, or
1180 * five versa, should leave the iterator at the same item:
1181 * prev * n, next * n = next * n, prev * n
1183 * The iterator has a notion of an out-of-order, special item,
1184 * which sits at the start of the list. This is used in
1185 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1186 * which needs to be first, as it may overlap other items.
1188 * The code is a bit messy because we have:
1189 * - a special item to deal with
1190 * - simple range, or composite range
1192 * If you find bugs, or want to add features, please make sure you
1193 * always check/modify *both* iterator_prev, and iterator_next.
1197 * This function iterates through the items in increasing order,
1198 * but prefixed by the special item, then -1. That is:
1199 * special, 1, 2, 3, ..., n, -1.
1200 * Each item is listed only once.
1202 static inline BOOL iterator_next(ITERATOR* i)
1206 i->nItem = i->nSpecial;
1207 if (i->nItem != -1) return TRUE;
1209 if (i->nItem == i->nSpecial)
1211 if (i->ranges) i->index = 0;
1217 if (i->nItem == i->nSpecial) i->nItem++;
1218 if (i->nItem < i->range.upper) return TRUE;
1223 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1224 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1227 else if (i->nItem >= i->range.upper) goto end;
1229 i->nItem = i->range.lower;
1230 if (i->nItem >= 0) goto testitem;
1237 * This function iterates through the items in decreasing order,
1238 * followed by the special item, then -1. That is:
1239 * n, n-1, ..., 3, 2, 1, special, -1.
1240 * Each item is listed only once.
1242 static inline BOOL iterator_prev(ITERATOR* i)
1249 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1252 if (i->nItem == i->nSpecial)
1260 if (i->nItem == i->nSpecial) i->nItem--;
1261 if (i->nItem >= i->range.lower) return TRUE;
1267 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1270 else if (!start && i->nItem < i->range.lower) goto end;
1272 i->nItem = i->range.upper;
1273 if (i->nItem > 0) goto testitem;
1275 return (i->nItem = i->nSpecial) != -1;
1278 static RANGE iterator_range(const ITERATOR *i)
1282 if (!i->ranges) return i->range;
1284 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1286 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1287 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1289 else range.lower = range.upper = 0;
1295 * Releases resources associated with this iterator.
1297 static inline void iterator_destroy(const ITERATOR *i)
1299 ranges_destroy(i->ranges);
1303 * Create an empty iterator.
1305 static inline BOOL iterator_empty(ITERATOR* i)
1307 ZeroMemory(i, sizeof(*i));
1308 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1313 * Create an iterator over a range.
1315 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1323 * Create an iterator over a bunch of ranges.
1324 * Please note that the iterator will take ownership of the ranges,
1325 * and will free them upon destruction.
1327 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1335 * Creates an iterator over the items which intersect frame.
1336 * Uses absolute coordinates rather than compensating for the current offset.
1338 static BOOL iterator_frameditems_absolute(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *frame)
1340 RECT rcItem, rcTemp;
1342 /* in case we fail, we want to return an empty iterator */
1343 if (!iterator_empty(i)) return FALSE;
1345 TRACE("(frame=%s)\n", wine_dbgstr_rect(frame));
1347 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
1351 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->nFocusedItem != -1)
1353 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1354 if (IntersectRect(&rcTemp, &rcItem, frame))
1355 i->nSpecial = infoPtr->nFocusedItem;
1357 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1358 /* to do better here, we need to have PosX, and PosY sorted */
1359 TRACE("building icon ranges:\n");
1360 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1362 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1363 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1364 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1365 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1366 if (IntersectRect(&rcTemp, &rcItem, frame))
1367 ranges_additem(i->ranges, nItem);
1371 else if (infoPtr->uView == LV_VIEW_DETAILS)
1375 if (frame->left >= infoPtr->nItemWidth) return TRUE;
1376 if (frame->top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1378 range.lower = max(frame->top / infoPtr->nItemHeight, 0);
1379 range.upper = min((frame->bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1380 if (range.upper <= range.lower) return TRUE;
1381 if (!iterator_rangeitems(i, range)) return FALSE;
1382 TRACE(" report=%s\n", debugrange(&i->range));
1386 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1387 INT nFirstRow = max(frame->top / infoPtr->nItemHeight, 0);
1388 INT nLastRow = min((frame->bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1395 if (infoPtr->nItemWidth)
1397 nFirstCol = max(frame->left / infoPtr->nItemWidth, 0);
1398 nLastCol = min((frame->right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1402 nFirstCol = max(frame->left, 0);
1403 nLastCol = min(frame->right - 1, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1406 lower = nFirstCol * nPerCol + nFirstRow;
1408 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1409 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1411 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1413 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1414 TRACE("building list ranges:\n");
1415 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1417 item_range.lower = nCol * nPerCol + nFirstRow;
1418 if(item_range.lower >= infoPtr->nItemCount) break;
1419 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1420 TRACE(" list=%s\n", debugrange(&item_range));
1421 ranges_add(i->ranges, item_range);
1429 * Creates an iterator over the items which intersect lprc.
1431 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1436 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1438 LISTVIEW_GetOrigin(infoPtr, &Origin);
1439 OffsetRect(&frame, -Origin.x, -Origin.y);
1441 return iterator_frameditems_absolute(i, infoPtr, &frame);
1445 * Creates an iterator over the items which intersect the visible region of hdc.
1447 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1449 POINT Origin, Position;
1450 RECT rcItem, rcClip;
1453 rgntype = GetClipBox(hdc, &rcClip);
1454 if (rgntype == NULLREGION) return iterator_empty(i);
1455 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1456 if (rgntype == SIMPLEREGION) return TRUE;
1458 /* first deal with the special item */
1459 if (i->nSpecial != -1)
1461 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1462 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1465 /* if we can't deal with the region, we'll just go with the simple range */
1466 LISTVIEW_GetOrigin(infoPtr, &Origin);
1467 TRACE("building visible range:\n");
1468 if (!i->ranges && i->range.lower < i->range.upper)
1470 if (!(i->ranges = ranges_create(50))) return TRUE;
1471 if (!ranges_add(i->ranges, i->range))
1473 ranges_destroy(i->ranges);
1479 /* now delete the invisible items from the list */
1480 while(iterator_next(i))
1482 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1483 rcItem.left = (infoPtr->uView == LV_VIEW_DETAILS) ? Origin.x : Position.x + Origin.x;
1484 rcItem.top = Position.y + Origin.y;
1485 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1486 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1487 if (!RectVisible(hdc, &rcItem))
1488 ranges_delitem(i->ranges, i->nItem);
1490 /* the iterator should restart on the next iterator_next */
1496 /* Remove common elements from two iterators */
1497 /* Passed iterators have to point on the first elements */
1498 static BOOL iterator_remove_common_items(ITERATOR *iter1, ITERATOR *iter2)
1500 if(!iter1->ranges || !iter2->ranges) {
1503 if(iter1->ranges || iter2->ranges ||
1504 (iter1->range.lower<iter2->range.lower && iter1->range.upper>iter2->range.upper) ||
1505 (iter1->range.lower>iter2->range.lower && iter1->range.upper<iter2->range.upper)) {
1506 ERR("result is not a one range iterator\n");
1510 if(iter1->range.lower==-1 || iter2->range.lower==-1)
1513 lower = iter1->range.lower;
1514 upper = iter1->range.upper;
1516 if(lower < iter2->range.lower)
1517 iter1->range.upper = iter2->range.lower;
1518 else if(upper > iter2->range.upper)
1519 iter1->range.lower = iter2->range.upper;
1521 iter1->range.lower = iter1->range.upper = -1;
1523 if(iter2->range.lower < lower)
1524 iter2->range.upper = lower;
1525 else if(iter2->range.upper > upper)
1526 iter2->range.lower = upper;
1528 iter2->range.lower = iter2->range.upper = -1;
1533 iterator_next(iter1);
1534 iterator_next(iter2);
1537 if(iter1->nItem==-1 || iter2->nItem==-1)
1540 if(iter1->nItem == iter2->nItem) {
1541 int delete = iter1->nItem;
1543 iterator_prev(iter1);
1544 iterator_prev(iter2);
1545 ranges_delitem(iter1->ranges, delete);
1546 ranges_delitem(iter2->ranges, delete);
1547 iterator_next(iter1);
1548 iterator_next(iter2);
1549 } else if(iter1->nItem > iter2->nItem)
1550 iterator_next(iter2);
1552 iterator_next(iter1);
1555 iter1->nItem = iter1->range.lower = iter1->range.upper = -1;
1556 iter2->nItem = iter2->range.lower = iter2->range.upper = -1;
1560 /******** Misc helper functions ************************************/
1562 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1563 WPARAM wParam, LPARAM lParam, BOOL isW)
1565 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1566 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1569 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1571 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1572 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON);
1575 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1577 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1578 if(state == 1 || state == 2)
1582 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1583 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1584 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1588 /* this should be called after window style got updated,
1589 it used to reset view state to match current window style */
1590 static inline void map_style_view(LISTVIEW_INFO *infoPtr)
1592 switch (infoPtr->dwStyle & LVS_TYPEMASK)
1595 infoPtr->uView = LV_VIEW_ICON;
1598 infoPtr->uView = LV_VIEW_DETAILS;
1601 infoPtr->uView = LV_VIEW_SMALLICON;
1604 infoPtr->uView = LV_VIEW_LIST;
1608 /* computes next item id value */
1609 static DWORD get_next_itemid(const LISTVIEW_INFO *infoPtr)
1611 INT count = DPA_GetPtrCount(infoPtr->hdpaItemIds);
1615 ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, count - 1);
1616 return lpID->id + 1;
1621 /******** Internal API functions ************************************/
1623 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1625 static COLUMN_INFO mainItem;
1627 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1628 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1630 /* update cached column rectangles */
1631 if (infoPtr->colRectsDirty)
1634 LISTVIEW_INFO *Ptr = (LISTVIEW_INFO*)infoPtr;
1637 for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++) {
1638 info = DPA_GetPtr(infoPtr->hdpaColumns, i);
1639 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, i, (LPARAM)&info->rcHeader);
1641 Ptr->colRectsDirty = FALSE;
1644 return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1647 static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr)
1649 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
1652 if (infoPtr->hwndHeader) return 0;
1654 TRACE("Creating header for list %p\n", infoPtr->hwndSelf);
1656 /* setup creation flags */
1657 dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
1658 dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
1660 hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
1663 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
1664 0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
1665 if (!infoPtr->hwndHeader) return -1;
1667 /* set header unicode format */
1668 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
1670 /* set header font */
1671 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, TRUE);
1673 LISTVIEW_UpdateSize(infoPtr);
1678 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1680 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1683 static inline BOOL LISTVIEW_IsHeaderEnabled(const LISTVIEW_INFO *infoPtr)
1685 return (infoPtr->uView == LV_VIEW_DETAILS ||
1686 infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS) &&
1687 !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER);
1690 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1692 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1695 /* used to handle collapse main item column case */
1696 static inline BOOL LISTVIEW_DrawFocusRect(const LISTVIEW_INFO *infoPtr, HDC hdc)
1698 return (infoPtr->rcFocus.left < infoPtr->rcFocus.right) ?
1699 DrawFocusRect(hdc, &infoPtr->rcFocus) : FALSE;
1702 /* Listview invalidation functions: use _only_ these functions to invalidate */
1704 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1706 return infoPtr->bRedraw;
1709 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1711 if(!is_redrawing(infoPtr)) return;
1712 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1713 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1716 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1720 if(!is_redrawing(infoPtr)) return;
1721 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1722 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1725 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1727 POINT Origin, Position;
1730 if(!is_redrawing(infoPtr)) return;
1731 assert (infoPtr->uView == LV_VIEW_DETAILS);
1732 LISTVIEW_GetOrigin(infoPtr, &Origin);
1733 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1734 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1736 rcBox.bottom = infoPtr->nItemHeight;
1737 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1738 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1741 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1743 LISTVIEW_InvalidateRect(infoPtr, NULL);
1746 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1750 if(!is_redrawing(infoPtr)) return;
1751 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1752 rcCol.top = infoPtr->rcList.top;
1753 rcCol.bottom = infoPtr->rcList.bottom;
1754 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1759 * Retrieves the number of items that can fit vertically in the client area.
1762 * [I] infoPtr : valid pointer to the listview structure
1765 * Number of items per row.
1767 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1769 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1771 return max(nListWidth/(infoPtr->nItemWidth ? infoPtr->nItemWidth : 1), 1);
1776 * Retrieves the number of items that can fit horizontally in the client
1780 * [I] infoPtr : valid pointer to the listview structure
1783 * Number of items per column.
1785 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1787 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1789 return max(nListHeight / infoPtr->nItemHeight, 1);
1793 /*************************************************************************
1794 * LISTVIEW_ProcessLetterKeys
1796 * Processes keyboard messages generated by pressing the letter keys
1798 * What this does is perform a case insensitive search from the
1799 * current position with the following quirks:
1800 * - If two chars or more are pressed in quick succession we search
1801 * for the corresponding string (e.g. 'abc').
1802 * - If there is a delay we wipe away the current search string and
1803 * restart with just that char.
1804 * - If the user keeps pressing the same character, whether slowly or
1805 * fast, so that the search string is entirely composed of this
1806 * character ('aaaaa' for instance), then we search for first item
1807 * that starting with that character.
1808 * - If the user types the above character in quick succession, then
1809 * we must also search for the corresponding string ('aaaaa'), and
1810 * go to that string if there is a match.
1813 * [I] hwnd : handle to the window
1814 * [I] charCode : the character code, the actual character
1815 * [I] keyData : key data
1823 * - The current implementation has a list of characters it will
1824 * accept and it ignores everything else. In particular it will
1825 * ignore accentuated characters which seems to match what
1826 * Windows does. But I'm not sure it makes sense to follow
1828 * - We don't sound a beep when the search fails.
1832 * TREEVIEW_ProcessLetterKeys
1834 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1836 WCHAR buffer[MAX_PATH];
1837 INT endidx, startidx;
1843 /* simple parameter checking */
1844 if (!charCode || !keyData || infoPtr->nItemCount == 0) return 0;
1846 /* only allow the valid WM_CHARs through */
1847 if (!isalnumW(charCode) &&
1848 charCode != '.' && charCode != '`' && charCode != '!' &&
1849 charCode != '@' && charCode != '#' && charCode != '$' &&
1850 charCode != '%' && charCode != '^' && charCode != '&' &&
1851 charCode != '*' && charCode != '(' && charCode != ')' &&
1852 charCode != '-' && charCode != '_' && charCode != '+' &&
1853 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1854 charCode != '}' && charCode != '[' && charCode != '{' &&
1855 charCode != '/' && charCode != '?' && charCode != '>' &&
1856 charCode != '<' && charCode != ',' && charCode != '~')
1859 /* update the search parameters */
1860 prevTime = infoPtr->lastKeyPressTimestamp;
1861 infoPtr->lastKeyPressTimestamp = GetTickCount();
1862 diff = infoPtr->lastKeyPressTimestamp - prevTime;
1864 if (diff >= 0 && diff < KEY_DELAY)
1866 if (infoPtr->nSearchParamLength < MAX_PATH - 1)
1867 infoPtr->szSearchParam[infoPtr->nSearchParamLength++] = charCode;
1869 if (infoPtr->charCode != charCode)
1870 infoPtr->charCode = charCode = 0;
1874 infoPtr->charCode = charCode;
1875 infoPtr->szSearchParam[0] = charCode;
1876 infoPtr->nSearchParamLength = 1;
1879 /* and search from the current position */
1881 endidx = infoPtr->nItemCount;
1883 /* should start from next after focused item, so next item that matches
1884 will be selected, if there isn't any and focused matches it will be selected
1885 on second search stage from beginning of the list */
1886 if (infoPtr->nFocusedItem >= 0 && infoPtr->nItemCount > 1)
1887 startidx = infoPtr->nFocusedItem + 1;
1891 /* let application handle this for virtual listview */
1892 if (infoPtr->dwStyle & LVS_OWNERDATA)
1896 memset(&nmlv.lvfi, 0, sizeof(nmlv.lvfi));
1897 nmlv.lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1898 nmlv.lvfi.psz = infoPtr->szSearchParam;
1899 nmlv.iStart = startidx;
1901 infoPtr->szSearchParam[infoPtr->nSearchParamLength] = 0;
1903 nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1909 /* first search in [startidx, endidx), on failure continue in [0, startidx) */
1912 /* start from first item if not found with >= startidx */
1913 if (i == infoPtr->nItemCount && startidx > 0)
1919 for (i = startidx; i < endidx; i++)
1922 item.mask = LVIF_TEXT;
1925 item.pszText = buffer;
1926 item.cchTextMax = MAX_PATH;
1927 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1929 if (lstrncmpiW(item.pszText, infoPtr->szSearchParam, infoPtr->nSearchParamLength) == 0)
1934 else if (nItem == -1 && lstrncmpiW(item.pszText, infoPtr->szSearchParam, 1) == 0)
1936 /* this would work but we must keep looking for a longer match */
1941 if ( nItem != -1 || /* found something */
1942 endidx != infoPtr->nItemCount || /* second search done */
1943 (startidx == 0 && endidx == infoPtr->nItemCount) /* full range for first search */ )
1949 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1954 /*************************************************************************
1955 * LISTVIEW_UpdateHeaderSize [Internal]
1957 * Function to resize the header control
1960 * [I] hwnd : handle to a window
1961 * [I] nNewScrollPos : scroll pos to set
1966 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1971 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1973 if (!infoPtr->hwndHeader) return;
1975 GetWindowRect(infoPtr->hwndHeader, &winRect);
1976 point[0].x = winRect.left;
1977 point[0].y = winRect.top;
1978 point[1].x = winRect.right;
1979 point[1].y = winRect.bottom;
1981 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1982 point[0].x = -nNewScrollPos;
1983 point[1].x += nNewScrollPos;
1985 SetWindowPos(infoPtr->hwndHeader,0,
1986 point[0].x,point[0].y,point[1].x,point[1].y,
1987 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
1988 SWP_NOZORDER | SWP_NOACTIVATE);
1993 * Update the scrollbars. This functions should be called whenever
1994 * the content, size or view changes.
1997 * [I] infoPtr : valid pointer to the listview structure
2002 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
2004 SCROLLINFO horzInfo, vertInfo;
2007 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
2009 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
2010 horzInfo.cbSize = sizeof(SCROLLINFO);
2011 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
2013 /* for now, we'll set info.nMax to the _count_, and adjust it later */
2014 if (infoPtr->uView == LV_VIEW_LIST)
2016 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
2017 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
2019 /* scroll by at least one column per page */
2020 if(horzInfo.nPage < infoPtr->nItemWidth)
2021 horzInfo.nPage = infoPtr->nItemWidth;
2023 if (infoPtr->nItemWidth)
2024 horzInfo.nPage /= infoPtr->nItemWidth;
2026 else if (infoPtr->uView == LV_VIEW_DETAILS)
2028 horzInfo.nMax = infoPtr->nItemWidth;
2030 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2034 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
2037 if (LISTVIEW_IsHeaderEnabled(infoPtr))
2039 if (DPA_GetPtrCount(infoPtr->hdpaColumns))
2044 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2045 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2047 LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2048 horzInfo.nMax = rcHeader.right;
2049 TRACE("horzInfo.nMax=%d\n", horzInfo.nMax);
2053 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
2054 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
2055 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
2056 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
2057 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
2059 /* Setting the horizontal scroll can change the listview size
2060 * (and potentially everything else) so we need to recompute
2061 * everything again for the vertical scroll
2064 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
2065 vertInfo.cbSize = sizeof(SCROLLINFO);
2066 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
2068 if (infoPtr->uView == LV_VIEW_DETAILS)
2070 vertInfo.nMax = infoPtr->nItemCount;
2072 /* scroll by at least one page */
2073 if(vertInfo.nPage < infoPtr->nItemHeight)
2074 vertInfo.nPage = infoPtr->nItemHeight;
2076 if (infoPtr->nItemHeight > 0)
2077 vertInfo.nPage /= infoPtr->nItemHeight;
2079 else if (infoPtr->uView != LV_VIEW_LIST) /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2083 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
2086 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
2087 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
2088 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
2089 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
2090 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
2092 /* Change of the range may have changed the scroll pos. If so move the content */
2093 if (dx != 0 || dy != 0)
2096 listRect = infoPtr->rcList;
2097 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
2098 SW_ERASE | SW_INVALIDATE);
2101 /* Update the Header Control */
2102 if (infoPtr->hwndHeader)
2104 horzInfo.fMask = SIF_POS;
2105 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
2106 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
2113 * Shows/hides the focus rectangle.
2116 * [I] infoPtr : valid pointer to the listview structure
2117 * [I] fShow : TRUE to show the focus, FALSE to hide it.
2122 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
2126 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
2128 if (infoPtr->nFocusedItem < 0) return;
2130 /* we need some gymnastics in ICON mode to handle large items */
2131 if (infoPtr->uView == LV_VIEW_ICON)
2135 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
2136 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
2138 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
2143 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
2145 /* for some reason, owner draw should work only in report mode */
2146 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
2151 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2152 HFONT hOldFont = SelectObject(hdc, hFont);
2154 item.iItem = infoPtr->nFocusedItem;
2156 item.mask = LVIF_PARAM;
2157 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
2159 ZeroMemory(&dis, sizeof(dis));
2160 dis.CtlType = ODT_LISTVIEW;
2161 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
2162 dis.itemID = item.iItem;
2163 dis.itemAction = ODA_FOCUS;
2164 if (fShow) dis.itemState |= ODS_FOCUS;
2165 dis.hwndItem = infoPtr->hwndSelf;
2167 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
2168 dis.itemData = item.lParam;
2170 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
2172 SelectObject(hdc, hOldFont);
2176 LISTVIEW_DrawFocusRect(infoPtr, hdc);
2179 ReleaseDC(infoPtr->hwndSelf, hdc);
2183 * Invalidates all visible selected items.
2185 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
2189 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
2190 while(iterator_next(&i))
2192 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
2193 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
2195 iterator_destroy(&i);
2200 * DESCRIPTION: [INTERNAL]
2201 * Computes an item's (left,top) corner, relative to rcView.
2202 * That is, the position has NOT been made relative to the Origin.
2203 * This is deliberate, to avoid computing the Origin over, and
2204 * over again, when this function is called in a loop. Instead,
2205 * one can factor the computation of the Origin before the loop,
2206 * and offset the value returned by this function, on every iteration.
2209 * [I] infoPtr : valid pointer to the listview structure
2210 * [I] nItem : item number
2211 * [O] lpptOrig : item top, left corner
2216 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
2218 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
2220 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
2222 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2223 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2225 else if (infoPtr->uView == LV_VIEW_LIST)
2227 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
2228 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
2229 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
2231 else /* LV_VIEW_DETAILS */
2233 lpptPosition->x = REPORT_MARGINX;
2234 /* item is always at zero indexed column */
2235 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2236 lpptPosition->x += LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
2237 lpptPosition->y = nItem * infoPtr->nItemHeight;
2242 * DESCRIPTION: [INTERNAL]
2243 * Compute the rectangles of an item. This is to localize all
2244 * the computations in one place. If you are not interested in some
2245 * of these values, simply pass in a NULL -- the function is smart
2246 * enough to compute only what's necessary. The function computes
2247 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
2248 * one, the BOX rectangle. This rectangle is very cheap to compute,
2249 * and is guaranteed to contain all the other rectangles. Computing
2250 * the ICON rect is also cheap, but all the others are potentially
2251 * expensive. This gives an easy and effective optimization when
2252 * searching (like point inclusion, or rectangle intersection):
2253 * first test against the BOX, and if TRUE, test against the desired
2255 * If the function does not have all the necessary information
2256 * to computed the requested rectangles, will crash with a
2257 * failed assertion. This is done so we catch all programming
2258 * errors, given that the function is called only from our code.
2260 * We have the following 'special' meanings for a few fields:
2261 * * If LVIS_FOCUSED is set, we assume the item has the focus
2262 * This is important in ICON mode, where it might get a larger
2263 * then usual rectangle
2265 * Please note that subitem support works only in REPORT mode.
2268 * [I] infoPtr : valid pointer to the listview structure
2269 * [I] lpLVItem : item to compute the measures for
2270 * [O] lprcBox : ptr to Box rectangle
2271 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
2272 * [0] lprcSelectBox : ptr to select box rectangle
2273 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
2274 * [O] lprcIcon : ptr to Icon rectangle
2275 * Same as LVM_GETITEMRECT with LVIR_ICON
2276 * [O] lprcStateIcon: ptr to State Icon rectangle
2277 * [O] lprcLabel : ptr to Label rectangle
2278 * Same as LVM_GETITEMRECT with LVIR_LABEL
2283 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
2284 LPRECT lprcBox, LPRECT lprcSelectBox,
2285 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
2287 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
2288 RECT Box, SelectBox, Icon, Label;
2289 COLUMN_INFO *lpColumnInfo = NULL;
2290 SIZE labelSize = { 0, 0 };
2292 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
2294 /* Be smart and try to figure out the minimum we have to do */
2295 if (lpLVItem->iSubItem) assert(infoPtr->uView == LV_VIEW_DETAILS);
2296 if (infoPtr->uView == LV_VIEW_ICON && (lprcBox || lprcLabel))
2298 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
2299 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
2301 if (lprcSelectBox) doSelectBox = TRUE;
2302 if (lprcLabel) doLabel = TRUE;
2303 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
2310 /************************************************************/
2311 /* compute the box rectangle (it should be cheap to do) */
2312 /************************************************************/
2313 if (lpLVItem->iSubItem || infoPtr->uView == LV_VIEW_DETAILS)
2314 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
2316 if (lpLVItem->iSubItem)
2318 Box = lpColumnInfo->rcHeader;
2323 Box.right = infoPtr->nItemWidth;
2326 Box.bottom = infoPtr->nItemHeight;
2328 /******************************************************************/
2329 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2330 /******************************************************************/
2333 LONG state_width = 0;
2335 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
2336 state_width = infoPtr->iconStateSize.cx;
2338 if (infoPtr->uView == LV_VIEW_ICON)
2340 Icon.left = Box.left + state_width;
2341 if (infoPtr->himlNormal)
2342 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2343 Icon.top = Box.top + ICON_TOP_PADDING;
2344 Icon.right = Icon.left;
2345 Icon.bottom = Icon.top;
2346 if (infoPtr->himlNormal)
2348 Icon.right += infoPtr->iconSize.cx;
2349 Icon.bottom += infoPtr->iconSize.cy;
2352 else /* LV_VIEW_SMALLICON, LV_VIEW_LIST or LV_VIEW_DETAILS */
2354 Icon.left = Box.left + state_width;
2356 if (infoPtr->uView == LV_VIEW_DETAILS && lpLVItem->iSubItem == 0)
2358 /* we need the indent in report mode */
2359 assert(lpLVItem->mask & LVIF_INDENT);
2360 Icon.left += infoPtr->iconSize.cx * lpLVItem->iIndent + REPORT_MARGINX;
2364 Icon.right = Icon.left;
2365 if (infoPtr->himlSmall &&
2366 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2367 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2368 Icon.right += infoPtr->iconSize.cx;
2369 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2371 if(lprcIcon) *lprcIcon = Icon;
2372 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2374 /* TODO: is this correct? */
2377 lprcStateIcon->left = Icon.left - state_width;
2378 lprcStateIcon->right = Icon.left;
2379 lprcStateIcon->top = Icon.top;
2380 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2381 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2384 else Icon.right = 0;
2386 /************************************************************/
2387 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2388 /************************************************************/
2391 /* calculate how far to the right can the label stretch */
2392 Label.right = Box.right;
2393 if (infoPtr->uView == LV_VIEW_DETAILS)
2395 if (lpLVItem->iSubItem == 0)
2397 /* we need a zero based rect here */
2398 Label = lpColumnInfo->rcHeader;
2399 OffsetRect(&Label, -Label.left, 0);
2403 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && infoPtr->uView == LV_VIEW_DETAILS))
2405 labelSize.cx = infoPtr->nItemWidth;
2406 labelSize.cy = infoPtr->nItemHeight;
2410 /* we need the text in non owner draw mode */
2411 assert(lpLVItem->mask & LVIF_TEXT);
2412 if (is_text(lpLVItem->pszText))
2414 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2415 HDC hdc = GetDC(infoPtr->hwndSelf);
2416 HFONT hOldFont = SelectObject(hdc, hFont);
2420 /* compute rough rectangle where the label will go */
2421 SetRectEmpty(&rcText);
2422 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2423 rcText.bottom = infoPtr->nItemHeight;
2424 if (infoPtr->uView == LV_VIEW_ICON)
2425 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2427 /* now figure out the flags */
2428 if (infoPtr->uView == LV_VIEW_ICON)
2429 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2431 uFormat = LV_SL_DT_FLAGS;
2433 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2435 if (rcText.right != rcText.left)
2436 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2438 labelSize.cy = rcText.bottom - rcText.top;
2440 SelectObject(hdc, hOldFont);
2441 ReleaseDC(infoPtr->hwndSelf, hdc);
2445 if (infoPtr->uView == LV_VIEW_ICON)
2447 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2448 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2449 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2450 Label.right = Label.left + labelSize.cx;
2451 Label.bottom = Label.top + infoPtr->nItemHeight;
2452 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2454 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2455 labelSize.cy /= infoPtr->ntmHeight;
2456 labelSize.cy = max(labelSize.cy, 1);
2457 labelSize.cy *= infoPtr->ntmHeight;
2459 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2461 else if (infoPtr->uView == LV_VIEW_DETAILS)
2463 Label.left = Icon.right;
2464 Label.top = Box.top;
2465 Label.right = lpLVItem->iSubItem ? lpColumnInfo->rcHeader.right :
2466 lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
2467 Label.bottom = Label.top + infoPtr->nItemHeight;
2469 else /* LV_VIEW_SMALLICON or LV_VIEW_LIST */
2471 Label.left = Icon.right;
2472 Label.top = Box.top;
2473 Label.right = min(Label.left + labelSize.cx, Label.right);
2474 Label.bottom = Label.top + infoPtr->nItemHeight;
2477 if (lprcLabel) *lprcLabel = Label;
2478 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2481 /************************************************************/
2482 /* compute SELECT bounding box */
2483 /************************************************************/
2486 if (infoPtr->uView == LV_VIEW_DETAILS)
2488 SelectBox.left = Icon.left;
2489 SelectBox.top = Box.top;
2490 SelectBox.bottom = Box.bottom;
2493 SelectBox.right = min(Label.left + labelSize.cx, Label.right);
2495 SelectBox.right = min(Label.left + MAX_EMPTYTEXT_SELECT_WIDTH, Label.right);
2499 UnionRect(&SelectBox, &Icon, &Label);
2501 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2502 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2505 /* Fix the Box if necessary */
2508 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2509 else *lprcBox = Box;
2511 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2515 * DESCRIPTION: [INTERNAL]
2518 * [I] infoPtr : valid pointer to the listview structure
2519 * [I] nItem : item number
2520 * [O] lprcBox : ptr to Box rectangle
2525 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2527 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2528 POINT Position, Origin;
2531 LISTVIEW_GetOrigin(infoPtr, &Origin);
2532 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2534 /* Be smart and try to figure out the minimum we have to do */
2536 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2537 lvItem.mask |= LVIF_TEXT;
2538 lvItem.iItem = nItem;
2539 lvItem.iSubItem = 0;
2540 lvItem.pszText = szDispText;
2541 lvItem.cchTextMax = DISP_TEXT_SIZE;
2542 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2543 if (infoPtr->uView == LV_VIEW_ICON)
2545 lvItem.mask |= LVIF_STATE;
2546 lvItem.stateMask = LVIS_FOCUSED;
2547 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2549 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2551 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
2552 SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0))
2554 OffsetRect(lprcBox, Origin.x, Position.y + Origin.y);
2557 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2560 /* LISTVIEW_MapIdToIndex helper */
2561 static INT CALLBACK MapIdSearchCompare(LPVOID p1, LPVOID p2, LPARAM lParam)
2563 ITEM_ID *id1 = (ITEM_ID*)p1;
2564 ITEM_ID *id2 = (ITEM_ID*)p2;
2566 if (id1->id == id2->id) return 0;
2568 return (id1->id < id2->id) ? -1 : 1;
2573 * Returns the item index for id specified.
2576 * [I] infoPtr : valid pointer to the listview structure
2577 * [I] iID : item id to get index for
2580 * Item index, or -1 on failure.
2582 static INT LISTVIEW_MapIdToIndex(const LISTVIEW_INFO *infoPtr, UINT iID)
2587 TRACE("iID=%d\n", iID);
2589 if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2590 if (infoPtr->nItemCount == 0) return -1;
2593 index = DPA_Search(infoPtr->hdpaItemIds, &ID, -1, MapIdSearchCompare, 0, DPAS_SORTED);
2597 ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, index);
2598 return DPA_GetPtrIndex(infoPtr->hdpaItems, lpID->item);
2606 * Returns the item id for index given.
2609 * [I] infoPtr : valid pointer to the listview structure
2610 * [I] iItem : item index to get id for
2615 static DWORD LISTVIEW_MapIndexToId(const LISTVIEW_INFO *infoPtr, INT iItem)
2620 TRACE("iItem=%d\n", iItem);
2622 if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2623 if (iItem < 0 || iItem >= infoPtr->nItemCount) return -1;
2625 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, iItem);
2626 lpItem = DPA_GetPtr(hdpaSubItems, 0);
2628 return lpItem->id->id;
2633 * Returns the current icon position, and advances it along the top.
2634 * The returned position is not offset by Origin.
2637 * [I] infoPtr : valid pointer to the listview structure
2638 * [O] lpPos : will get the current icon position
2643 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2645 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2647 *lpPos = infoPtr->currIconPos;
2649 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2650 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2652 infoPtr->currIconPos.x = 0;
2653 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2659 * Returns the current icon position, and advances it down the left edge.
2660 * The returned position is not offset by Origin.
2663 * [I] infoPtr : valid pointer to the listview structure
2664 * [O] lpPos : will get the current icon position
2669 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2671 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2673 *lpPos = infoPtr->currIconPos;
2675 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2676 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2678 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2679 infoPtr->currIconPos.y = 0;
2685 * Moves an icon to the specified position.
2686 * It takes care of invalidating the item, etc.
2689 * [I] infoPtr : valid pointer to the listview structure
2690 * [I] nItem : the item to move
2691 * [I] lpPos : the new icon position
2692 * [I] isNew : flags the item as being new
2698 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2704 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2705 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2707 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2708 LISTVIEW_InvalidateItem(infoPtr, nItem);
2711 /* Allocating a POINTER for every item is too resource intensive,
2712 * so we'll keep the (x,y) in different arrays */
2713 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2714 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2716 LISTVIEW_InvalidateItem(infoPtr, nItem);
2723 * Arranges listview items in icon display mode.
2726 * [I] infoPtr : valid pointer to the listview structure
2727 * [I] nAlignCode : alignment code
2733 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2735 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2739 if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON) return FALSE;
2741 TRACE("nAlignCode=%d\n", nAlignCode);
2743 if (nAlignCode == LVA_DEFAULT)
2745 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2746 else nAlignCode = LVA_ALIGNTOP;
2751 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2752 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2753 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2754 default: return FALSE;
2757 infoPtr->bAutoarrange = TRUE;
2758 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2759 for (i = 0; i < infoPtr->nItemCount; i++)
2761 next_pos(infoPtr, &pos);
2762 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2770 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2771 * For LVS_REPORT always returns empty rectangle.
2774 * [I] infoPtr : valid pointer to the listview structure
2775 * [O] lprcView : bounding rectangle
2781 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2785 SetRectEmpty(lprcView);
2787 switch (infoPtr->uView)
2790 case LV_VIEW_SMALLICON:
2791 for (i = 0; i < infoPtr->nItemCount; i++)
2793 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2794 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2795 lprcView->right = max(lprcView->right, x);
2796 lprcView->bottom = max(lprcView->bottom, y);
2798 if (infoPtr->nItemCount > 0)
2800 lprcView->right += infoPtr->nItemWidth;
2801 lprcView->bottom += infoPtr->nItemHeight;
2806 y = LISTVIEW_GetCountPerColumn(infoPtr);
2807 x = infoPtr->nItemCount / y;
2808 if (infoPtr->nItemCount % y) x++;
2809 lprcView->right = x * infoPtr->nItemWidth;
2810 lprcView->bottom = y * infoPtr->nItemHeight;
2817 * Retrieves the bounding rectangle of all the items.
2820 * [I] infoPtr : valid pointer to the listview structure
2821 * [O] lprcView : bounding rectangle
2827 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2831 TRACE("(lprcView=%p)\n", lprcView);
2833 if (!lprcView) return FALSE;
2835 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2837 if (infoPtr->uView != LV_VIEW_DETAILS)
2839 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2840 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2843 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2850 * Retrieves the subitem pointer associated with the subitem index.
2853 * [I] hdpaSubItems : DPA handle for a specific item
2854 * [I] nSubItem : index of subitem
2857 * SUCCESS : subitem pointer
2860 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2862 SUBITEM_INFO *lpSubItem;
2865 /* we should binary search here if need be */
2866 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2868 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2869 if (lpSubItem->iSubItem == nSubItem)
2879 * Calculates the desired item width.
2882 * [I] infoPtr : valid pointer to the listview structure
2885 * The desired item width.
2887 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2891 TRACE("uView=%d\n", infoPtr->uView);
2893 if (infoPtr->uView == LV_VIEW_ICON)
2894 nItemWidth = infoPtr->iconSpacing.cx;
2895 else if (infoPtr->uView == LV_VIEW_DETAILS)
2897 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2902 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2903 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2905 LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2906 nItemWidth = rcHeader.right;
2909 else /* LV_VIEW_SMALLICON, or LV_VIEW_LIST */
2911 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2915 lvItem.mask = LVIF_TEXT;
2916 lvItem.iSubItem = 0;
2918 for (i = 0; i < infoPtr->nItemCount; i++)
2921 lvItem.pszText = szDispText;
2922 lvItem.cchTextMax = DISP_TEXT_SIZE;
2923 if (LISTVIEW_GetItemW(infoPtr, &lvItem))
2924 nItemWidth = max(LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE),
2928 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2929 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2931 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2939 * Calculates the desired item height.
2942 * [I] infoPtr : valid pointer to the listview structure
2945 * The desired item height.
2947 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2951 TRACE("uView=%d\n", infoPtr->uView);
2953 if (infoPtr->uView == LV_VIEW_ICON)
2954 nItemHeight = infoPtr->iconSpacing.cy;
2957 nItemHeight = infoPtr->ntmHeight;
2958 if (infoPtr->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("(item=%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.
7035 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT item, LPRECT lprc)
7037 RECT rect = { 0, 0, 0, 0 };
7041 if (!lprc) return FALSE;
7043 TRACE("(item=%d, subitem=%d, type=%d)\n", item, lprc->top, lprc->left);
7044 /* Subitem of '0' means item itself, and this works for all control view modes */
7046 return LISTVIEW_GetItemRect(infoPtr, item, lprc);
7048 if (infoPtr->uView != LV_VIEW_DETAILS) return FALSE;
7050 LISTVIEW_GetOrigin(infoPtr, &origin);
7051 /* this works for any item index, no matter if it exists or not */
7052 y = item * infoPtr->nItemHeight + origin.y;
7054 if (infoPtr->hwndHeader && SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, lprc->top, (LPARAM)&rect))
7057 rect.bottom = infoPtr->nItemHeight;
7061 /* Native implementation is broken for this case and garbage is left for left and right fields,
7062 we zero them to get predictable output */
7063 lprc->left = lprc->right = lprc->top = 0;
7064 lprc->bottom = infoPtr->nItemHeight;
7065 OffsetRect(lprc, origin.x, y);
7066 TRACE("return rect %s\n", wine_dbgstr_rect(lprc));
7074 /* it doesn't matter if main item actually has an icon, if imagelist is set icon width is returned */
7075 if (infoPtr->himlSmall)
7076 rect.right = rect.left + infoPtr->iconSize.cx;
7078 rect.right = rect.left;
7080 rect.bottom = rect.top + infoPtr->iconSize.cy;
7088 ERR("Unknown bounds=%d\n", lprc->left);
7092 OffsetRect(&rect, origin.x, y);
7094 TRACE("return rect %s\n", wine_dbgstr_rect(lprc));
7101 * Retrieves the spacing between listview control items.
7104 * [I] infoPtr : valid pointer to the listview structure
7105 * [I] bSmall : flag for small or large icon
7108 * Horizontal + vertical spacing
7110 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
7116 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7120 if (infoPtr->uView == LV_VIEW_ICON)
7121 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
7123 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
7130 * Retrieves the state of a listview control item.
7133 * [I] infoPtr : valid pointer to the listview structure
7134 * [I] nItem : item index
7135 * [I] uMask : state mask
7138 * State specified by the mask.
7140 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
7144 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
7146 lvItem.iItem = nItem;
7147 lvItem.iSubItem = 0;
7148 lvItem.mask = LVIF_STATE;
7149 lvItem.stateMask = uMask;
7150 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
7152 return lvItem.state & uMask;
7157 * Retrieves the text of a listview control item or subitem.
7160 * [I] hwnd : window handle
7161 * [I] nItem : item index
7162 * [IO] lpLVItem : item information
7163 * [I] isW : TRUE if lpLVItem is Unicode
7166 * SUCCESS : string length
7169 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
7171 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
7173 lpLVItem->mask = LVIF_TEXT;
7174 lpLVItem->iItem = nItem;
7175 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
7177 return textlenT(lpLVItem->pszText, isW);
7182 * Searches for an item based on properties + relationships.
7185 * [I] infoPtr : valid pointer to the listview structure
7186 * [I] nItem : item index
7187 * [I] uFlags : relationship flag
7190 * SUCCESS : item index
7193 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
7196 LVFINDINFOW lvFindInfo;
7197 INT nCountPerColumn;
7201 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
7202 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
7204 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
7206 if (uFlags & LVNI_CUT)
7209 if (uFlags & LVNI_DROPHILITED)
7210 uMask |= LVIS_DROPHILITED;
7212 if (uFlags & LVNI_FOCUSED)
7213 uMask |= LVIS_FOCUSED;
7215 if (uFlags & LVNI_SELECTED)
7216 uMask |= LVIS_SELECTED;
7218 /* if we're asked for the focused item, that's only one,
7219 * so it's worth optimizing */
7220 if (uFlags & LVNI_FOCUSED)
7222 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
7223 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
7226 if (uFlags & LVNI_ABOVE)
7228 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
7233 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7239 /* Special case for autoarrange - move 'til the top of a list */
7240 if (is_autoarrange(infoPtr))
7242 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7243 while (nItem - nCountPerRow >= 0)
7245 nItem -= nCountPerRow;
7246 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7251 lvFindInfo.flags = LVFI_NEARESTXY;
7252 lvFindInfo.vkDirection = VK_UP;
7253 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7254 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7256 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7261 else if (uFlags & LVNI_BELOW)
7263 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
7265 while (nItem < infoPtr->nItemCount)
7268 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7274 /* Special case for autoarrange - move 'til the bottom of a list */
7275 if (is_autoarrange(infoPtr))
7277 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7278 while (nItem + nCountPerRow < infoPtr->nItemCount )
7280 nItem += nCountPerRow;
7281 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7286 lvFindInfo.flags = LVFI_NEARESTXY;
7287 lvFindInfo.vkDirection = VK_DOWN;
7288 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7289 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7291 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7296 else if (uFlags & LVNI_TOLEFT)
7298 if (infoPtr->uView == LV_VIEW_LIST)
7300 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
7301 while (nItem - nCountPerColumn >= 0)
7303 nItem -= nCountPerColumn;
7304 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7308 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7310 /* Special case for autoarrange - move 'til the beginning of a row */
7311 if (is_autoarrange(infoPtr))
7313 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7314 while (nItem % nCountPerRow > 0)
7317 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7322 lvFindInfo.flags = LVFI_NEARESTXY;
7323 lvFindInfo.vkDirection = VK_LEFT;
7324 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7325 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7327 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7332 else if (uFlags & LVNI_TORIGHT)
7334 if (infoPtr->uView == LV_VIEW_LIST)
7336 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
7337 while (nItem + nCountPerColumn < infoPtr->nItemCount)
7339 nItem += nCountPerColumn;
7340 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7344 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7346 /* Special case for autoarrange - move 'til the end of a row */
7347 if (is_autoarrange(infoPtr))
7349 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7350 while (nItem % nCountPerRow < nCountPerRow - 1 )
7353 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7358 lvFindInfo.flags = LVFI_NEARESTXY;
7359 lvFindInfo.vkDirection = VK_RIGHT;
7360 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7361 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7363 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7372 /* search by index */
7373 for (i = nItem; i < infoPtr->nItemCount; i++)
7375 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
7383 /* LISTVIEW_GetNumberOfWorkAreas */
7387 * Retrieves the origin coordinates when in icon or small icon display mode.
7390 * [I] infoPtr : valid pointer to the listview structure
7391 * [O] lpptOrigin : coordinate information
7396 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
7398 INT nHorzPos = 0, nVertPos = 0;
7399 SCROLLINFO scrollInfo;
7401 scrollInfo.cbSize = sizeof(SCROLLINFO);
7402 scrollInfo.fMask = SIF_POS;
7404 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
7405 nHorzPos = scrollInfo.nPos;
7406 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
7407 nVertPos = scrollInfo.nPos;
7409 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
7411 lpptOrigin->x = infoPtr->rcList.left;
7412 lpptOrigin->y = infoPtr->rcList.top;
7413 if (infoPtr->uView == LV_VIEW_LIST)
7414 nHorzPos *= infoPtr->nItemWidth;
7415 else if (infoPtr->uView == LV_VIEW_DETAILS)
7416 nVertPos *= infoPtr->nItemHeight;
7418 lpptOrigin->x -= nHorzPos;
7419 lpptOrigin->y -= nVertPos;
7421 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
7426 * Retrieves the width of a string.
7429 * [I] hwnd : window handle
7430 * [I] lpszText : text string to process
7431 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
7434 * SUCCESS : string width (in pixels)
7437 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
7442 if (is_text(lpszText))
7444 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
7445 HDC hdc = GetDC(infoPtr->hwndSelf);
7446 HFONT hOldFont = SelectObject(hdc, hFont);
7449 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
7451 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
7452 SelectObject(hdc, hOldFont);
7453 ReleaseDC(infoPtr->hwndSelf, hdc);
7455 return stringSize.cx;
7460 * Determines which listview item is located at the specified position.
7463 * [I] infoPtr : valid pointer to the listview structure
7464 * [IO] lpht : hit test information
7465 * [I] subitem : fill out iSubItem.
7466 * [I] select : return the index only if the hit selects the item
7469 * (mm 20001022): We must not allow iSubItem to be touched, for
7470 * an app might pass only a structure with space up to iItem!
7471 * (MS Office 97 does that for instance in the file open dialog)
7474 * SUCCESS : item index
7477 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
7479 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
7480 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
7481 POINT Origin, Position, opt;
7486 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
7490 if (subitem) lpht->iSubItem = 0;
7492 LISTVIEW_GetOrigin(infoPtr, &Origin);
7494 /* set whole list relation flags */
7495 if (subitem && infoPtr->uView == LV_VIEW_DETAILS)
7497 /* LVM_SUBITEMHITTEST checks left bound of possible client area */
7498 if (infoPtr->rcList.left > lpht->pt.x && Origin.x < lpht->pt.x)
7499 lpht->flags |= LVHT_TOLEFT;
7501 if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
7502 opt.y = lpht->pt.y + infoPtr->rcList.top;
7506 if (infoPtr->rcList.bottom < opt.y)
7507 lpht->flags |= LVHT_BELOW;
7511 if (infoPtr->rcList.left > lpht->pt.x)
7512 lpht->flags |= LVHT_TOLEFT;
7513 else if (infoPtr->rcList.right < lpht->pt.x)
7514 lpht->flags |= LVHT_TORIGHT;
7516 if (infoPtr->rcList.top > lpht->pt.y)
7517 lpht->flags |= LVHT_ABOVE;
7518 else if (infoPtr->rcList.bottom < lpht->pt.y)
7519 lpht->flags |= LVHT_BELOW;
7522 /* even if item is invalid try to find subitem */
7523 if (infoPtr->uView == LV_VIEW_DETAILS && subitem)
7528 opt.x = lpht->pt.x - Origin.x;
7530 lpht->iSubItem = -1;
7531 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
7533 pRect = &LISTVIEW_GetColumnInfo(infoPtr, j)->rcHeader;
7535 if ((opt.x >= pRect->left) && (opt.x < pRect->right))
7541 TRACE("lpht->iSubItem=%d\n", lpht->iSubItem);
7543 /* if we're outside horizontal columns bounds there's nothing to test further */
7544 if (lpht->iSubItem == -1)
7547 lpht->flags = LVHT_NOWHERE;
7552 TRACE("lpht->flags=0x%x\n", lpht->flags);
7553 if (lpht->flags) return -1;
7555 lpht->flags |= LVHT_NOWHERE;
7557 /* first deal with the large items */
7558 rcSearch.left = lpht->pt.x;
7559 rcSearch.top = lpht->pt.y;
7560 rcSearch.right = rcSearch.left + 1;
7561 rcSearch.bottom = rcSearch.top + 1;
7563 iterator_frameditems(&i, infoPtr, &rcSearch);
7564 iterator_next(&i); /* go to first item in the sequence */
7566 iterator_destroy(&i);
7568 TRACE("lpht->iItem=%d\n", iItem);
7569 if (iItem == -1) return -1;
7571 lvItem.mask = LVIF_STATE | LVIF_TEXT;
7572 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
7573 lvItem.stateMask = LVIS_STATEIMAGEMASK;
7574 if (infoPtr->uView == LV_VIEW_ICON) lvItem.stateMask |= LVIS_FOCUSED;
7575 lvItem.iItem = iItem;
7576 lvItem.iSubItem = subitem ? lpht->iSubItem : 0;
7577 lvItem.pszText = szDispText;
7578 lvItem.cchTextMax = DISP_TEXT_SIZE;
7579 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
7580 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
7582 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
7583 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
7584 opt.x = lpht->pt.x - Position.x - Origin.x;
7586 if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
7587 opt.y = lpht->pt.y - Position.y - Origin.y + infoPtr->rcList.top;
7589 opt.y = lpht->pt.y - Position.y - Origin.y;
7591 if (infoPtr->uView == LV_VIEW_DETAILS)
7594 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
7595 opt.x = lpht->pt.x - Origin.x;
7599 UnionRect(&rcBounds, &rcIcon, &rcLabel);
7600 UnionRect(&rcBounds, &rcBounds, &rcState);
7602 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
7603 if (!PtInRect(&rcBounds, opt)) return -1;
7605 if (PtInRect(&rcIcon, opt))
7606 lpht->flags |= LVHT_ONITEMICON;
7607 else if (PtInRect(&rcLabel, opt))
7608 lpht->flags |= LVHT_ONITEMLABEL;
7609 else if (infoPtr->himlState && PtInRect(&rcState, opt))
7610 lpht->flags |= LVHT_ONITEMSTATEICON;
7611 /* special case for LVS_EX_FULLROWSELECT */
7612 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
7613 !(lpht->flags & LVHT_ONITEM))
7615 lpht->flags = LVHT_ONITEM | LVHT_ABOVE;
7617 if (lpht->flags & LVHT_ONITEM)
7618 lpht->flags &= ~LVHT_NOWHERE;
7619 TRACE("lpht->flags=0x%x\n", lpht->flags);
7621 if (select && !(infoPtr->uView == LV_VIEW_DETAILS &&
7622 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
7623 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
7625 if (infoPtr->uView == LV_VIEW_DETAILS)
7627 /* get main item bounds */
7628 lvItem.iSubItem = 0;
7629 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
7630 UnionRect(&rcBounds, &rcIcon, &rcLabel);
7631 UnionRect(&rcBounds, &rcBounds, &rcState);
7633 if (!PtInRect(&rcBounds, opt)) iItem = -1;
7635 return lpht->iItem = iItem;
7640 * Inserts a new item in the listview control.
7643 * [I] infoPtr : valid pointer to the listview structure
7644 * [I] lpLVItem : item information
7645 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
7648 * SUCCESS : new item index
7651 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
7658 BOOL is_sorted, has_changed;
7660 HWND hwndSelf = infoPtr->hwndSelf;
7662 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
7664 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
7666 /* make sure it's an item, and not a subitem; cannot insert a subitem */
7667 if (!lpLVItem || lpLVItem->iSubItem) return -1;
7669 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
7671 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
7673 /* insert item in listview control data structure */
7674 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
7675 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
7677 /* link with id struct */
7678 if (!(lpID = Alloc(sizeof(ITEM_ID)))) goto fail;
7680 lpID->item = hdpaSubItems;
7681 lpID->id = get_next_itemid(infoPtr);
7682 if ( DPA_InsertPtr(infoPtr->hdpaItemIds, infoPtr->nItemCount, lpID) == -1) goto fail;
7684 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
7685 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
7687 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
7689 /* calculate new item index */
7696 while (i < infoPtr->nItemCount)
7698 hItem = DPA_GetPtr( infoPtr->hdpaItems, i);
7699 item_s = DPA_GetPtr(hItem, 0);
7701 cmpv = textcmpWT(item_s->hdr.pszText, lpLVItem->pszText, isW);
7702 if (infoPtr->dwStyle & LVS_SORTDESCENDING) cmpv *= -1;
7704 if (cmpv >= 0) break;
7710 nItem = min(lpLVItem->iItem, infoPtr->nItemCount);
7712 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
7713 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
7714 if (nItem == -1) goto fail;
7715 infoPtr->nItemCount++;
7717 /* shift indices first so they don't get tangled */
7718 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
7720 /* set the item attributes */
7721 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
7723 /* full size structure expected - _WIN32IE >= 0x560 */
7726 else if (lpLVItem->mask & LVIF_INDENT)
7728 /* indent member expected - _WIN32IE >= 0x300 */
7729 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
7733 /* minimal structure expected */
7734 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
7737 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7739 item.mask |= LVIF_STATE;
7740 item.stateMask |= LVIS_STATEIMAGEMASK;
7741 item.state &= ~LVIS_STATEIMAGEMASK;
7742 item.state |= INDEXTOSTATEIMAGEMASK(1);
7744 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
7746 /* make room for the position, if we are in the right mode */
7747 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7749 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
7751 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
7753 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
7758 /* send LVN_INSERTITEM notification */
7759 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
7761 nmlv.lParam = lpItem->lParam;
7762 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
7763 if (!IsWindow(hwndSelf))
7766 /* align items (set position of each item) */
7767 if (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON)
7771 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
7772 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
7774 LISTVIEW_NextIconPosTop(infoPtr, &pt);
7776 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
7779 /* now is the invalidation fun */
7780 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
7784 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
7785 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
7786 infoPtr->nItemCount--;
7788 DPA_DeletePtr(hdpaSubItems, 0);
7789 DPA_Destroy (hdpaSubItems);
7796 * Checks item visibility.
7799 * [I] infoPtr : valid pointer to the listview structure
7800 * [I] nFirst : item index to check for
7803 * Item visible : TRUE
7804 * Item invisible or failure : FALSE
7806 static BOOL LISTVIEW_IsItemVisible(const LISTVIEW_INFO *infoPtr, INT nItem)
7808 POINT Origin, Position;
7813 TRACE("nItem=%d\n", nItem);
7815 if (nItem < 0 || nItem >= DPA_GetPtrCount(infoPtr->hdpaItems)) return FALSE;
7817 LISTVIEW_GetOrigin(infoPtr, &Origin);
7818 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
7819 rcItem.left = Position.x + Origin.x;
7820 rcItem.top = Position.y + Origin.y;
7821 rcItem.right = rcItem.left + infoPtr->nItemWidth;
7822 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
7824 hdc = GetDC(infoPtr->hwndSelf);
7825 if (!hdc) return FALSE;
7826 ret = RectVisible(hdc, &rcItem);
7827 ReleaseDC(infoPtr->hwndSelf, hdc);
7834 * Redraws a range of items.
7837 * [I] infoPtr : valid pointer to the listview structure
7838 * [I] nFirst : first item
7839 * [I] nLast : last item
7845 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
7849 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
7850 max(nFirst, nLast) >= infoPtr->nItemCount)
7853 for (i = nFirst; i <= nLast; i++)
7854 LISTVIEW_InvalidateItem(infoPtr, i);
7861 * Scroll the content of a listview.
7864 * [I] infoPtr : valid pointer to the listview structure
7865 * [I] dx : horizontal scroll amount in pixels
7866 * [I] dy : vertical scroll amount in pixels
7873 * If the control is in report view (LV_VIEW_DETAILS) the control can
7874 * be scrolled only in line increments. "dy" will be rounded to the
7875 * nearest number of pixels that are a whole line. Ex: if line height
7876 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
7877 * is passed, then the scroll will be 0. (per MSDN 7/2002)
7879 * For: (per experimentation with native control and CSpy ListView)
7880 * LV_VIEW_ICON scrolling in any direction is allowed
7881 * LV_VIEW_SMALLICON scrolling in any direction is allowed
7882 * LV_VIEW_LIST dx=1 = 1 column (horizontal only)
7883 * but will only scroll 1 column per message
7884 * no matter what the value.
7885 * dy must be 0 or FALSE returned.
7886 * LV_VIEW_DETAILS dx=1 = 1 pixel
7890 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7892 switch(infoPtr->uView) {
7893 case LV_VIEW_DETAILS:
7894 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
7895 dy /= infoPtr->nItemHeight;
7898 if (dy != 0) return FALSE;
7904 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx);
7905 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy);
7912 * Sets the background color.
7915 * [I] infoPtr : valid pointer to the listview structure
7916 * [I] color : background color
7922 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
7924 TRACE("(color=%x)\n", color);
7926 if(infoPtr->clrBk != color) {
7927 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
7928 infoPtr->clrBk = color;
7929 if (color == CLR_NONE)
7930 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
7933 infoPtr->hBkBrush = CreateSolidBrush(color);
7934 infoPtr->dwLvExStyle &= ~LVS_EX_TRANSPARENTBKGND;
7941 /* LISTVIEW_SetBkImage */
7943 /*** Helper for {Insert,Set}ColumnT *only* */
7944 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
7945 const LVCOLUMNW *lpColumn, BOOL isW)
7947 if (lpColumn->mask & LVCF_FMT)
7949 /* format member is valid */
7950 lphdi->mask |= HDI_FORMAT;
7952 /* set text alignment (leftmost column must be left-aligned) */
7953 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
7954 lphdi->fmt |= HDF_LEFT;
7955 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
7956 lphdi->fmt |= HDF_RIGHT;
7957 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
7958 lphdi->fmt |= HDF_CENTER;
7960 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
7961 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
7963 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
7965 lphdi->fmt |= HDF_IMAGE;
7966 lphdi->iImage = I_IMAGECALLBACK;
7969 if (lpColumn->fmt & LVCFMT_FIXED_WIDTH)
7970 lphdi->fmt |= HDF_FIXEDWIDTH;
7973 if (lpColumn->mask & LVCF_WIDTH)
7975 lphdi->mask |= HDI_WIDTH;
7976 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
7978 /* make it fill the remainder of the controls width */
7982 for(item_index = 0; item_index < (nColumn - 1); item_index++)
7984 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
7985 lphdi->cxy += rcHeader.right - rcHeader.left;
7988 /* retrieve the layout of the header */
7989 GetClientRect(infoPtr->hwndSelf, &rcHeader);
7990 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
7992 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
7995 lphdi->cxy = lpColumn->cx;
7998 if (lpColumn->mask & LVCF_TEXT)
8000 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
8001 lphdi->fmt |= HDF_STRING;
8002 lphdi->pszText = lpColumn->pszText;
8003 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
8006 if (lpColumn->mask & LVCF_IMAGE)
8008 lphdi->mask |= HDI_IMAGE;
8009 lphdi->iImage = lpColumn->iImage;
8012 if (lpColumn->mask & LVCF_ORDER)
8014 lphdi->mask |= HDI_ORDER;
8015 lphdi->iOrder = lpColumn->iOrder;
8022 * Inserts a new column.
8025 * [I] infoPtr : valid pointer to the listview structure
8026 * [I] nColumn : column index
8027 * [I] lpColumn : column information
8028 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
8031 * SUCCESS : new column index
8034 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
8035 const LVCOLUMNW *lpColumn, BOOL isW)
8037 COLUMN_INFO *lpColumnInfo;
8041 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
8043 if (!lpColumn || nColumn < 0) return -1;
8044 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
8046 ZeroMemory(&hdi, sizeof(HDITEMW));
8047 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
8050 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
8051 * (can be seen in SPY) otherwise column never gets added.
8053 if (!(lpColumn->mask & LVCF_WIDTH)) {
8054 hdi.mask |= HDI_WIDTH;
8059 * when the iSubItem is available Windows copies it to the header lParam. It seems
8060 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
8062 if (lpColumn->mask & LVCF_SUBITEM)
8064 hdi.mask |= HDI_LPARAM;
8065 hdi.lParam = lpColumn->iSubItem;
8068 /* create header if not present */
8069 LISTVIEW_CreateHeader(infoPtr);
8070 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle) &&
8071 (infoPtr->uView == LV_VIEW_DETAILS) && (WS_VISIBLE & infoPtr->dwStyle))
8073 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8076 /* insert item in header control */
8077 nNewColumn = SendMessageW(infoPtr->hwndHeader,
8078 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
8079 nColumn, (LPARAM)&hdi);
8080 if (nNewColumn == -1) return -1;
8081 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
8083 /* create our own column info */
8084 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
8085 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
8087 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
8088 if (lpColumn->mask & LVCF_MINWIDTH) lpColumnInfo->cxMin = lpColumn->cxMin;
8089 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, nNewColumn, (LPARAM)&lpColumnInfo->rcHeader))
8092 /* now we have to actually adjust the data */
8093 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
8095 SUBITEM_INFO *lpSubItem;
8101 item.iSubItem = nNewColumn;
8102 item.mask = LVIF_TEXT | LVIF_IMAGE;
8103 item.iImage = I_IMAGECALLBACK;
8104 item.pszText = LPSTR_TEXTCALLBACKW;
8106 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
8108 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
8109 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
8111 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
8112 if (lpSubItem->iSubItem >= nNewColumn)
8113 lpSubItem->iSubItem++;
8116 /* add new subitem for each item */
8118 set_sub_item(infoPtr, &item, isW, &changed);
8122 /* make space for the new column */
8123 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8124 LISTVIEW_UpdateItemSize(infoPtr);
8129 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
8132 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
8140 * Sets the attributes of a header item.
8143 * [I] infoPtr : valid pointer to the listview structure
8144 * [I] nColumn : column index
8145 * [I] lpColumn : column attributes
8146 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
8152 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
8153 const LVCOLUMNW *lpColumn, BOOL isW)
8155 HDITEMW hdi, hdiget;
8158 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
8160 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
8162 ZeroMemory(&hdi, sizeof(HDITEMW));
8163 if (lpColumn->mask & LVCF_FMT)
8165 hdi.mask |= HDI_FORMAT;
8166 hdiget.mask = HDI_FORMAT;
8167 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdiget))
8168 hdi.fmt = hdiget.fmt & HDF_STRING;
8170 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
8172 /* set header item attributes */
8173 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, nColumn, (LPARAM)&hdi);
8174 if (!bResult) return FALSE;
8176 if (lpColumn->mask & LVCF_FMT)
8178 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
8179 INT oldFmt = lpColumnInfo->fmt;
8181 lpColumnInfo->fmt = lpColumn->fmt;
8182 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
8184 if (infoPtr->uView == LV_VIEW_DETAILS) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
8188 if (lpColumn->mask & LVCF_MINWIDTH)
8189 LISTVIEW_GetColumnInfo(infoPtr, nColumn)->cxMin = lpColumn->cxMin;
8196 * Sets the column order array
8199 * [I] infoPtr : valid pointer to the listview structure
8200 * [I] iCount : number of elements in column order array
8201 * [I] lpiArray : pointer to column order array
8207 static BOOL LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
8209 TRACE("iCount %d lpiArray %p\n", iCount, lpiArray);
8211 if (!lpiArray || !IsWindow(infoPtr->hwndHeader)) return FALSE;
8213 infoPtr->colRectsDirty = TRUE;
8215 return SendMessageW(infoPtr->hwndHeader, HDM_SETORDERARRAY, iCount, (LPARAM)lpiArray);
8220 * Sets the width of a column
8223 * [I] infoPtr : valid pointer to the listview structure
8224 * [I] nColumn : column index
8225 * [I] cx : column width
8231 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
8233 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
8237 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
8239 /* set column width only if in report or list mode */
8240 if (infoPtr->uView != LV_VIEW_DETAILS && infoPtr->uView != LV_VIEW_LIST) return FALSE;
8242 /* take care of invalid cx values */
8243 if(infoPtr->uView == LV_VIEW_DETAILS && cx < -2) cx = LVSCW_AUTOSIZE;
8244 else if (infoPtr->uView == LV_VIEW_LIST && cx < 1) return FALSE;
8246 /* resize all columns if in LV_VIEW_LIST mode */
8247 if(infoPtr->uView == LV_VIEW_LIST)
8249 infoPtr->nItemWidth = cx;
8250 LISTVIEW_InvalidateList(infoPtr);
8254 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
8256 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
8261 lvItem.mask = LVIF_TEXT;
8263 lvItem.iSubItem = nColumn;
8264 lvItem.pszText = szDispText;
8265 lvItem.cchTextMax = DISP_TEXT_SIZE;
8266 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
8268 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
8269 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
8270 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
8272 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
8273 max_cx += infoPtr->iconSize.cx;
8274 max_cx += TRAILING_LABEL_PADDING;
8277 /* autosize based on listview items width */
8278 if(cx == LVSCW_AUTOSIZE)
8280 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
8282 /* if iCol is the last column make it fill the remainder of the controls width */
8283 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
8288 LISTVIEW_GetOrigin(infoPtr, &Origin);
8289 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
8291 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
8295 /* Despite what the MS docs say, if this is not the last
8296 column, then MS resizes the column to the width of the
8297 largest text string in the column, including headers
8298 and items. This is different from LVSCW_AUTOSIZE in that
8299 LVSCW_AUTOSIZE ignores the header string length. */
8302 /* retrieve header text */
8303 hdi.mask = HDI_TEXT;
8304 hdi.cchTextMax = DISP_TEXT_SIZE;
8305 hdi.pszText = szDispText;
8306 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdi))
8308 HDC hdc = GetDC(infoPtr->hwndSelf);
8309 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
8312 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
8313 cx = size.cx + TRAILING_HEADER_PADDING;
8314 /* FIXME: Take into account the header image, if one is present */
8315 SelectObject(hdc, old_font);
8316 ReleaseDC(infoPtr->hwndSelf, hdc);
8318 cx = max (cx, max_cx);
8322 if (cx < 0) return FALSE;
8324 /* call header to update the column change */
8325 hdi.mask = HDI_WIDTH;
8326 hdi.cxy = max(cx, LISTVIEW_GetColumnInfo(infoPtr, nColumn)->cxMin);
8327 TRACE("hdi.cxy=%d\n", hdi.cxy);
8328 return SendMessageW(infoPtr->hwndHeader, HDM_SETITEMW, nColumn, (LPARAM)&hdi);
8332 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
8335 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
8338 HBITMAP hbm_im, hbm_mask, hbm_orig;
8340 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
8341 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
8344 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
8345 ILC_COLOR | ILC_MASK, 2, 2);
8346 hdc_wnd = GetDC(infoPtr->hwndSelf);
8347 hdc = CreateCompatibleDC(hdc_wnd);
8348 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
8349 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
8350 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
8352 rc.left = rc.top = 0;
8353 rc.right = GetSystemMetrics(SM_CXSMICON);
8354 rc.bottom = GetSystemMetrics(SM_CYSMICON);
8356 hbm_orig = SelectObject(hdc, hbm_mask);
8357 FillRect(hdc, &rc, hbr_white);
8358 InflateRect(&rc, -2, -2);
8359 FillRect(hdc, &rc, hbr_black);
8361 SelectObject(hdc, hbm_im);
8362 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
8363 SelectObject(hdc, hbm_orig);
8364 ImageList_Add(himl, hbm_im, hbm_mask);
8366 SelectObject(hdc, hbm_im);
8367 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
8368 SelectObject(hdc, hbm_orig);
8369 ImageList_Add(himl, hbm_im, hbm_mask);
8371 DeleteObject(hbm_mask);
8372 DeleteObject(hbm_im);
8380 * Sets the extended listview style.
8383 * [I] infoPtr : valid pointer to the listview structure
8385 * [I] dwStyle : style
8388 * SUCCESS : previous style
8391 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD mask, DWORD ex_style)
8393 DWORD old_ex_style = infoPtr->dwLvExStyle;
8395 TRACE("mask=0x%08x, ex_style=0x%08x\n", mask, ex_style);
8399 infoPtr->dwLvExStyle = (old_ex_style & ~mask) | (ex_style & mask);
8401 infoPtr->dwLvExStyle = ex_style;
8403 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_CHECKBOXES)
8405 HIMAGELIST himl = 0;
8406 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8409 item.mask = LVIF_STATE;
8410 item.stateMask = LVIS_STATEIMAGEMASK;
8411 item.state = INDEXTOSTATEIMAGEMASK(1);
8412 LISTVIEW_SetItemState(infoPtr, -1, &item);
8414 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
8415 if(!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8416 ImageList_Destroy(infoPtr->himlState);
8418 himl = LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
8419 /* checkbox list replaces previous custom list or... */
8420 if(((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) &&
8421 !(infoPtr->dwStyle & LVS_SHAREIMAGELISTS)) ||
8422 /* ...previous was checkbox list */
8423 (old_ex_style & LVS_EX_CHECKBOXES))
8424 ImageList_Destroy(himl);
8427 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_HEADERDRAGDROP)
8431 /* if not already created */
8432 LISTVIEW_CreateHeader(infoPtr);
8434 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
8435 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
8436 style |= HDS_DRAGDROP;
8438 style &= ~HDS_DRAGDROP;
8439 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style);
8442 /* GRIDLINES adds decoration at top so changes sizes */
8443 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_GRIDLINES)
8445 LISTVIEW_CreateHeader(infoPtr);
8446 LISTVIEW_UpdateSize(infoPtr);
8449 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_FULLROWSELECT)
8451 LISTVIEW_CreateHeader(infoPtr);
8454 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_TRANSPARENTBKGND)
8456 if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
8457 LISTVIEW_SetBkColor(infoPtr, CLR_NONE);
8460 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_HEADERINALLVIEWS)
8462 if (infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS)
8463 LISTVIEW_CreateHeader(infoPtr);
8465 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8466 LISTVIEW_UpdateSize(infoPtr);
8467 LISTVIEW_UpdateScroll(infoPtr);
8470 LISTVIEW_InvalidateList(infoPtr);
8471 return old_ex_style;
8476 * Sets the new hot cursor used during hot tracking and hover selection.
8479 * [I] infoPtr : valid pointer to the listview structure
8480 * [I] hCursor : the new hot cursor handle
8483 * Returns the previous hot cursor
8485 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
8487 HCURSOR oldCursor = infoPtr->hHotCursor;
8489 infoPtr->hHotCursor = hCursor;
8497 * Sets the hot item index.
8500 * [I] infoPtr : valid pointer to the listview structure
8501 * [I] iIndex : index
8504 * SUCCESS : previous hot item index
8505 * FAILURE : -1 (no hot item)
8507 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
8509 INT iOldIndex = infoPtr->nHotItem;
8511 infoPtr->nHotItem = iIndex;
8519 * Sets the amount of time the cursor must hover over an item before it is selected.
8522 * [I] infoPtr : valid pointer to the listview structure
8523 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
8526 * Returns the previous hover time
8528 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
8530 DWORD oldHoverTime = infoPtr->dwHoverTime;
8532 infoPtr->dwHoverTime = dwHoverTime;
8534 return oldHoverTime;
8539 * Sets spacing for icons of LVS_ICON style.
8542 * [I] infoPtr : valid pointer to the listview structure
8543 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
8544 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
8547 * MAKELONG(oldcx, oldcy)
8549 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
8551 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
8553 TRACE("requested=(%d,%d)\n", cx, cy);
8555 /* this is supported only for LVS_ICON style */
8556 if (infoPtr->uView != LV_VIEW_ICON) return oldspacing;
8558 /* set to defaults, if instructed to */
8559 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
8560 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
8562 /* if 0 then compute width
8563 * FIXME: computed cx and cy is not matching native behaviour */
8565 cx = GetSystemMetrics(SM_CXICONSPACING);
8566 if (infoPtr->iconSize.cx + ICON_LR_PADDING > cx)
8567 cx = infoPtr->iconSize.cx + ICON_LR_PADDING;
8570 /* if 0 then compute height */
8572 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
8573 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
8576 infoPtr->iconSpacing.cx = cx;
8577 infoPtr->iconSpacing.cy = cy;
8579 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
8580 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
8581 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
8582 infoPtr->ntmHeight);
8584 /* these depend on the iconSpacing */
8585 LISTVIEW_UpdateItemSize(infoPtr);
8590 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
8594 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
8601 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
8602 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
8611 * [I] infoPtr : valid pointer to the listview structure
8612 * [I] nType : image list type
8613 * [I] himl : image list handle
8616 * SUCCESS : old image list
8619 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
8621 INT oldHeight = infoPtr->nItemHeight;
8622 HIMAGELIST himlOld = 0;
8624 TRACE("(nType=%d, himl=%p\n", nType, himl);
8629 himlOld = infoPtr->himlNormal;
8630 infoPtr->himlNormal = himl;
8631 if (infoPtr->uView == LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
8632 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
8636 himlOld = infoPtr->himlSmall;
8637 infoPtr->himlSmall = himl;
8638 if (infoPtr->uView != LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
8642 himlOld = infoPtr->himlState;
8643 infoPtr->himlState = himl;
8644 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
8645 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
8649 ERR("Unknown icon type=%d\n", nType);
8653 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
8654 if (infoPtr->nItemHeight != oldHeight)
8655 LISTVIEW_UpdateScroll(infoPtr);
8662 * Preallocates memory (does *not* set the actual count of items !)
8665 * [I] infoPtr : valid pointer to the listview structure
8666 * [I] nItems : item count (projected number of items to allocate)
8667 * [I] dwFlags : update flags
8673 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
8675 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
8677 if (infoPtr->dwStyle & LVS_OWNERDATA)
8679 INT nOldCount = infoPtr->nItemCount;
8681 if (nItems < nOldCount)
8683 RANGE range = { nItems, nOldCount };
8684 ranges_del(infoPtr->selectionRanges, range);
8685 if (infoPtr->nFocusedItem >= nItems)
8687 LISTVIEW_SetItemFocus(infoPtr, -1);
8688 SetRectEmpty(&infoPtr->rcFocus);
8692 infoPtr->nItemCount = nItems;
8693 LISTVIEW_UpdateScroll(infoPtr);
8695 /* the flags are valid only in ownerdata report and list modes */
8696 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON) dwFlags = 0;
8698 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
8699 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
8701 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
8702 LISTVIEW_InvalidateList(infoPtr);
8709 LISTVIEW_GetOrigin(infoPtr, &Origin);
8710 nFrom = min(nOldCount, nItems);
8711 nTo = max(nOldCount, nItems);
8713 if (infoPtr->uView == LV_VIEW_DETAILS)
8716 rcErase.top = nFrom * infoPtr->nItemHeight;
8717 rcErase.right = infoPtr->nItemWidth;
8718 rcErase.bottom = nTo * infoPtr->nItemHeight;
8719 OffsetRect(&rcErase, Origin.x, Origin.y);
8720 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8721 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8723 else /* LV_VIEW_LIST */
8725 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
8727 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
8728 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
8729 rcErase.right = rcErase.left + infoPtr->nItemWidth;
8730 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
8731 OffsetRect(&rcErase, Origin.x, Origin.y);
8732 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8733 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8735 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
8737 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
8738 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
8739 OffsetRect(&rcErase, Origin.x, Origin.y);
8740 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8741 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8747 /* According to MSDN for non-LVS_OWNERDATA this is just
8748 * a performance issue. The control allocates its internal
8749 * data structures for the number of items specified. It
8750 * cuts down on the number of memory allocations. Therefore
8751 * we will just issue a WARN here
8753 WARN("for non-ownerdata performance option not implemented.\n");
8761 * Sets the position of an item.
8764 * [I] infoPtr : valid pointer to the listview structure
8765 * [I] nItem : item index
8766 * [I] pt : coordinate
8772 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, const POINT *pt)
8776 TRACE("(nItem=%d, pt=%s\n", nItem, wine_dbgstr_point(pt));
8778 if (!pt || nItem < 0 || nItem >= infoPtr->nItemCount ||
8779 !(infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)) return FALSE;
8782 LISTVIEW_GetOrigin(infoPtr, &Origin);
8784 /* This point value seems to be an undocumented feature.
8785 * The best guess is that it means either at the origin,
8786 * or at true beginning of the list. I will assume the origin. */
8787 if ((Pt.x == -1) && (Pt.y == -1))
8790 if (infoPtr->uView == LV_VIEW_ICON)
8792 Pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
8793 Pt.y -= ICON_TOP_PADDING;
8798 infoPtr->bAutoarrange = FALSE;
8800 return LISTVIEW_MoveIconTo(infoPtr, nItem, &Pt, FALSE);
8805 * Sets the state of one or many items.
8808 * [I] infoPtr : valid pointer to the listview structure
8809 * [I] nItem : item index
8810 * [I] item : item or subitem info
8816 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *item)
8821 if (!item) return FALSE;
8823 lvItem.iItem = nItem;
8824 lvItem.iSubItem = 0;
8825 lvItem.mask = LVIF_STATE;
8826 lvItem.state = item->state;
8827 lvItem.stateMask = item->stateMask;
8828 TRACE("item=%s\n", debuglvitem_t(&lvItem, TRUE));
8835 /* select all isn't allowed in LVS_SINGLESEL */
8836 if ((lvItem.state & lvItem.stateMask & LVIS_SELECTED) && (infoPtr->dwStyle & LVS_SINGLESEL))
8839 /* focus all isn't allowed */
8840 if (lvItem.state & lvItem.stateMask & LVIS_FOCUSED) return FALSE;
8842 notify = infoPtr->bDoChangeNotify;
8843 if (infoPtr->dwStyle & LVS_OWNERDATA)
8845 infoPtr->bDoChangeNotify = FALSE;
8846 if (!(lvItem.state & LVIS_SELECTED) && LISTVIEW_GetSelectedCount(infoPtr))
8847 oldstate |= LVIS_SELECTED;
8848 if (infoPtr->nFocusedItem != -1) oldstate |= LVIS_FOCUSED;
8851 /* apply to all items */
8852 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
8853 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) ret = FALSE;
8855 if (infoPtr->dwStyle & LVS_OWNERDATA)
8859 infoPtr->bDoChangeNotify = notify;
8863 nmlv.uNewState = lvItem.state & lvItem.stateMask;
8864 nmlv.uOldState = oldstate & lvItem.stateMask;
8865 nmlv.uChanged = LVIF_STATE;
8866 nmlv.ptAction.x = nmlv.ptAction.y = 0;
8869 notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
8873 ret = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
8880 * Sets the text of an item or subitem.
8883 * [I] hwnd : window handle
8884 * [I] nItem : item index
8885 * [I] lpLVItem : item or subitem info
8886 * [I] isW : TRUE if input is Unicode
8892 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
8896 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
8897 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
8899 lvItem.iItem = nItem;
8900 lvItem.iSubItem = lpLVItem->iSubItem;
8901 lvItem.mask = LVIF_TEXT;
8902 lvItem.pszText = lpLVItem->pszText;
8903 lvItem.cchTextMax = lpLVItem->cchTextMax;
8905 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
8907 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
8912 * Set item index that marks the start of a multiple selection.
8915 * [I] infoPtr : valid pointer to the listview structure
8916 * [I] nIndex : index
8919 * Index number or -1 if there is no selection mark.
8921 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
8923 INT nOldIndex = infoPtr->nSelectionMark;
8925 TRACE("(nIndex=%d)\n", nIndex);
8927 infoPtr->nSelectionMark = nIndex;
8934 * Sets the text background color.
8937 * [I] infoPtr : valid pointer to the listview structure
8938 * [I] color : text background color
8944 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
8946 TRACE("(color=%x)\n", color);
8948 infoPtr->clrTextBk = color;
8954 * Sets the text foreground color.
8957 * [I] infoPtr : valid pointer to the listview structure
8958 * [I] color : text color
8964 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF color)
8966 TRACE("(color=%x)\n", color);
8968 infoPtr->clrText = color;
8974 * Sets new ToolTip window to ListView control.
8977 * [I] infoPtr : valid pointer to the listview structure
8978 * [I] hwndNewToolTip : handle to new ToolTip
8983 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
8985 HWND hwndOldToolTip = infoPtr->hwndToolTip;
8986 infoPtr->hwndToolTip = hwndNewToolTip;
8987 return hwndOldToolTip;
8992 * sets the Unicode character format flag for the control
8994 * [I] infoPtr :valid pointer to the listview structure
8995 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
8998 * Old Unicode Format
9000 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL unicode)
9002 SHORT rc = infoPtr->notifyFormat;
9003 infoPtr->notifyFormat = (unicode) ? NFR_UNICODE : NFR_ANSI;
9004 return rc == NFR_UNICODE;
9009 * sets the control view mode
9011 * [I] infoPtr :valid pointer to the listview structure
9012 * [I] nView :new view mode value
9018 static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView)
9020 SIZE oldIconSize = infoPtr->iconSize;
9023 if (infoPtr->uView == nView) return 1;
9025 if ((INT)nView < 0 || nView > LV_VIEW_MAX) return -1;
9026 if (nView == LV_VIEW_TILE)
9028 FIXME("View LV_VIEW_TILE unimplemented\n");
9032 infoPtr->uView = nView;
9034 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9035 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9037 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9038 SetRectEmpty(&infoPtr->rcFocus);
9040 himl = (nView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9041 set_icon_size(&infoPtr->iconSize, himl, nView != LV_VIEW_ICON);
9046 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9048 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9049 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9050 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9052 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9054 case LV_VIEW_SMALLICON:
9055 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9057 case LV_VIEW_DETAILS:
9062 LISTVIEW_CreateHeader( infoPtr );
9064 hl.prc = &infoPtr->rcList;
9066 SendMessageW(infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl);
9067 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9068 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9075 LISTVIEW_UpdateItemSize(infoPtr);
9076 LISTVIEW_UpdateSize(infoPtr);
9077 LISTVIEW_UpdateScroll(infoPtr);
9078 LISTVIEW_InvalidateList(infoPtr);
9080 TRACE("nView=%d\n", nView);
9085 /* LISTVIEW_SetWorkAreas */
9089 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMS
9092 * [I] first : pointer to first ITEM_INFO to compare
9093 * [I] second : pointer to second ITEM_INFO to compare
9094 * [I] lParam : HWND of control
9097 * if first comes before second : negative
9098 * if first comes after second : positive
9099 * if first and second are equivalent : zero
9101 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
9103 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
9104 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
9105 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
9107 /* Forward the call to the client defined callback */
9108 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
9113 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMSEX
9116 * [I] first : pointer to first ITEM_INFO to compare
9117 * [I] second : pointer to second ITEM_INFO to compare
9118 * [I] lParam : HWND of control
9121 * if first comes before second : negative
9122 * if first comes after second : positive
9123 * if first and second are equivalent : zero
9125 static INT WINAPI LISTVIEW_CallBackCompareEx(LPVOID first, LPVOID second, LPARAM lParam)
9127 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
9128 INT first_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, first );
9129 INT second_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, second );
9131 /* Forward the call to the client defined callback */
9132 return (infoPtr->pfnCompare)( first_idx, second_idx, infoPtr->lParamSort );
9137 * Sorts the listview items.
9140 * [I] infoPtr : valid pointer to the listview structure
9141 * [I] pfnCompare : application-defined value
9142 * [I] lParamSort : pointer to comparison callback
9143 * [I] IsEx : TRUE when LVM_SORTITEMSEX used
9149 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare,
9150 LPARAM lParamSort, BOOL IsEx)
9154 LPVOID selectionMarkItem = NULL;
9155 LPVOID focusedItem = NULL;
9158 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
9160 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
9162 if (!pfnCompare) return FALSE;
9163 if (!infoPtr->hdpaItems) return FALSE;
9165 /* if there are 0 or 1 items, there is no need to sort */
9166 if (infoPtr->nItemCount < 2) return TRUE;
9168 /* clear selection */
9169 ranges_clear(infoPtr->selectionRanges);
9171 /* save selection mark and focused item */
9172 if (infoPtr->nSelectionMark >= 0)
9173 selectionMarkItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark);
9174 if (infoPtr->nFocusedItem >= 0)
9175 focusedItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
9177 infoPtr->pfnCompare = pfnCompare;
9178 infoPtr->lParamSort = lParamSort;
9180 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompareEx, (LPARAM)infoPtr);
9182 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
9184 /* restore selection ranges */
9185 for (i=0; i < infoPtr->nItemCount; i++)
9187 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
9188 lpItem = DPA_GetPtr(hdpaSubItems, 0);
9190 if (lpItem->state & LVIS_SELECTED)
9191 ranges_additem(infoPtr->selectionRanges, i);
9193 /* restore selection mark and focused item */
9194 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
9195 infoPtr->nFocusedItem = DPA_GetPtrIndex(infoPtr->hdpaItems, focusedItem);
9197 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
9199 /* refresh the display */
9200 LISTVIEW_InvalidateList(infoPtr);
9206 * Update theme handle after a theme change.
9209 * [I] infoPtr : valid pointer to the listview structure
9213 * FAILURE : something else
9215 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
9217 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
9218 CloseThemeData(theme);
9219 OpenThemeData(infoPtr->hwndSelf, themeClass);
9225 * Updates an items or rearranges the listview control.
9228 * [I] infoPtr : valid pointer to the listview structure
9229 * [I] nItem : item index
9235 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
9237 TRACE("(nItem=%d)\n", nItem);
9239 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
9241 /* rearrange with default alignment style */
9242 if (is_autoarrange(infoPtr))
9243 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9245 LISTVIEW_InvalidateItem(infoPtr, nItem);
9252 * Draw the track line at the place defined in the infoPtr structure.
9253 * The line is drawn with a XOR pen so drawing the line for the second time
9254 * in the same place erases the line.
9257 * [I] infoPtr : valid pointer to the listview structure
9263 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
9267 if (infoPtr->xTrackLine == -1)
9270 if (!(hdc = GetDC(infoPtr->hwndSelf)))
9272 PatBlt( hdc, infoPtr->xTrackLine, infoPtr->rcList.top,
9273 1, infoPtr->rcList.bottom - infoPtr->rcList.top, DSTINVERT );
9274 ReleaseDC(infoPtr->hwndSelf, hdc);
9280 * Called when an edit control should be displayed. This function is called after
9281 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
9284 * [I] hwnd : Handle to the listview
9285 * [I] uMsg : WM_TIMER (ignored)
9286 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
9287 * [I] dwTimer : The elapsed time (ignored)
9292 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
9294 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
9295 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9297 KillTimer(hwnd, idEvent);
9298 editItem->fEnabled = FALSE;
9299 /* check if the item is still selected */
9300 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
9301 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
9306 * Creates the listview control - the WM_NCCREATE phase.
9309 * [I] hwnd : window handle
9310 * [I] lpcs : the create parameters
9316 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
9318 LISTVIEW_INFO *infoPtr;
9321 TRACE("(lpcs=%p)\n", lpcs);
9323 /* initialize info pointer */
9324 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
9325 if (!infoPtr) return FALSE;
9327 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
9329 infoPtr->hwndSelf = hwnd;
9330 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
9331 map_style_view(infoPtr);
9332 /* determine the type of structures to use */
9333 infoPtr->hwndNotify = lpcs->hwndParent;
9334 /* infoPtr->notifyFormat will be filled in WM_CREATE */
9336 /* initialize color information */
9337 infoPtr->clrBk = CLR_NONE;
9338 infoPtr->clrText = CLR_DEFAULT;
9339 infoPtr->clrTextBk = CLR_DEFAULT;
9340 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
9342 /* set default values */
9343 infoPtr->nFocusedItem = -1;
9344 infoPtr->nSelectionMark = -1;
9345 infoPtr->nHotItem = -1;
9346 infoPtr->bRedraw = TRUE;
9347 infoPtr->bNoItemMetrics = TRUE;
9348 infoPtr->bDoChangeNotify = TRUE;
9349 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
9350 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
9351 infoPtr->nEditLabelItem = -1;
9352 infoPtr->nLButtonDownItem = -1;
9353 infoPtr->dwHoverTime = HOVER_DEFAULT; /* default system hover time */
9354 infoPtr->nMeasureItemHeight = 0;
9355 infoPtr->xTrackLine = -1; /* no track line */
9356 infoPtr->itemEdit.fEnabled = FALSE;
9357 infoPtr->iVersion = COMCTL32_VERSION;
9358 infoPtr->colRectsDirty = FALSE;
9360 /* get default font (icon title) */
9361 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
9362 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
9363 infoPtr->hFont = infoPtr->hDefaultFont;
9364 LISTVIEW_SaveTextMetrics(infoPtr);
9366 /* allocate memory for the data structure */
9367 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
9368 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
9369 if (!(infoPtr->hdpaItemIds = DPA_Create(10))) goto fail;
9370 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
9371 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
9372 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
9376 DestroyWindow(infoPtr->hwndHeader);
9377 ranges_destroy(infoPtr->selectionRanges);
9378 DPA_Destroy(infoPtr->hdpaItems);
9379 DPA_Destroy(infoPtr->hdpaItemIds);
9380 DPA_Destroy(infoPtr->hdpaPosX);
9381 DPA_Destroy(infoPtr->hdpaPosY);
9382 DPA_Destroy(infoPtr->hdpaColumns);
9389 * Creates the listview control - the WM_CREATE phase. Most of the data is
9390 * already set up in LISTVIEW_NCCreate
9393 * [I] hwnd : window handle
9394 * [I] lpcs : the create parameters
9400 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
9402 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9404 TRACE("(lpcs=%p, style=0x%08x)\n", lpcs, lpcs->style);
9406 infoPtr->dwStyle = lpcs->style;
9407 map_style_view(infoPtr);
9409 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
9410 (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9411 /* on error defaulting to ANSI notifications */
9412 if (infoPtr->notifyFormat == 0) infoPtr->notifyFormat = NFR_ANSI;
9413 TRACE("notify format=%d\n", infoPtr->notifyFormat);
9415 if ((infoPtr->uView == LV_VIEW_DETAILS) && (lpcs->style & WS_VISIBLE))
9417 if (LISTVIEW_CreateHeader(infoPtr) < 0) return -1;
9420 infoPtr->hwndHeader = 0;
9422 /* init item size to avoid division by 0 */
9423 LISTVIEW_UpdateItemSize (infoPtr);
9424 LISTVIEW_UpdateSize (infoPtr);
9426 if (infoPtr->uView == LV_VIEW_DETAILS)
9428 if (!(LVS_NOCOLUMNHEADER & lpcs->style) && (WS_VISIBLE & lpcs->style))
9430 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
9432 LISTVIEW_UpdateScroll(infoPtr);
9433 /* send WM_MEASUREITEM notification */
9434 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED) notify_measureitem(infoPtr);
9437 OpenThemeData(hwnd, themeClass);
9439 /* initialize the icon sizes */
9440 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, infoPtr->uView != LV_VIEW_ICON);
9441 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
9447 * Destroys the listview control.
9450 * [I] infoPtr : valid pointer to the listview structure
9456 static LRESULT LISTVIEW_Destroy(LISTVIEW_INFO *infoPtr)
9458 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
9459 CloseThemeData(theme);
9461 /* delete all items */
9462 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
9469 * Enables the listview control.
9472 * [I] infoPtr : valid pointer to the listview structure
9473 * [I] bEnable : specifies whether to enable or disable the window
9479 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr)
9481 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
9482 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
9488 * Erases the background of the listview control.
9491 * [I] infoPtr : valid pointer to the listview structure
9492 * [I] hdc : device context handle
9498 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
9502 TRACE("(hdc=%p)\n", hdc);
9504 if (!GetClipBox(hdc, &rc)) return FALSE;
9506 if (infoPtr->clrBk == CLR_NONE)
9508 if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
9509 return SendMessageW(infoPtr->hwndNotify, WM_PRINTCLIENT,
9510 (WPARAM)hdc, PRF_ERASEBKGND);
9512 return SendMessageW(infoPtr->hwndNotify, WM_ERASEBKGND, (WPARAM)hdc, 0);
9515 /* for double buffered controls we need to do this during refresh */
9516 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
9518 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
9524 * Helper function for LISTVIEW_[HV]Scroll *only*.
9525 * Performs vertical/horizontal scrolling by a give amount.
9528 * [I] infoPtr : valid pointer to the listview structure
9529 * [I] dx : amount of horizontal scroll
9530 * [I] dy : amount of vertical scroll
9532 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
9534 /* now we can scroll the list */
9535 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
9536 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
9537 /* if we have focus, adjust rect */
9538 OffsetRect(&infoPtr->rcFocus, dx, dy);
9539 UpdateWindow(infoPtr->hwndSelf);
9544 * Performs vertical scrolling.
9547 * [I] infoPtr : valid pointer to the listview structure
9548 * [I] nScrollCode : scroll code
9549 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
9550 * [I] hScrollWnd : scrollbar control window handle
9556 * SB_LINEUP/SB_LINEDOWN:
9557 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
9558 * for LVS_REPORT is 1 line
9559 * for LVS_LIST cannot occur
9562 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
9565 INT nOldScrollPos, nNewScrollPos;
9566 SCROLLINFO scrollInfo;
9569 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
9570 debugscrollcode(nScrollCode), nScrollDiff);
9572 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9574 scrollInfo.cbSize = sizeof(SCROLLINFO);
9575 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
9577 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
9579 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
9581 nOldScrollPos = scrollInfo.nPos;
9582 switch (nScrollCode)
9588 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
9592 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
9596 nScrollDiff = -scrollInfo.nPage;
9600 nScrollDiff = scrollInfo.nPage;
9603 case SB_THUMBPOSITION:
9605 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
9612 /* quit right away if pos isn't changing */
9613 if (nScrollDiff == 0) return 0;
9615 /* calculate new position, and handle overflows */
9616 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
9617 if (nScrollDiff > 0) {
9618 if (nNewScrollPos < nOldScrollPos ||
9619 nNewScrollPos > scrollInfo.nMax)
9620 nNewScrollPos = scrollInfo.nMax;
9622 if (nNewScrollPos > nOldScrollPos ||
9623 nNewScrollPos < scrollInfo.nMin)
9624 nNewScrollPos = scrollInfo.nMin;
9627 /* set the new position, and reread in case it changed */
9628 scrollInfo.fMask = SIF_POS;
9629 scrollInfo.nPos = nNewScrollPos;
9630 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
9632 /* carry on only if it really changed */
9633 if (nNewScrollPos == nOldScrollPos) return 0;
9635 /* now adjust to client coordinates */
9636 nScrollDiff = nOldScrollPos - nNewScrollPos;
9637 if (infoPtr->uView == LV_VIEW_DETAILS) nScrollDiff *= infoPtr->nItemHeight;
9639 /* and scroll the window */
9640 scroll_list(infoPtr, 0, nScrollDiff);
9647 * Performs horizontal scrolling.
9650 * [I] infoPtr : valid pointer to the listview structure
9651 * [I] nScrollCode : scroll code
9652 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
9653 * [I] hScrollWnd : scrollbar control window handle
9659 * SB_LINELEFT/SB_LINERIGHT:
9660 * for LVS_ICON, LVS_SMALLICON 1 pixel
9661 * for LVS_REPORT is 1 pixel
9662 * for LVS_LIST is 1 column --> which is a 1 because the
9663 * scroll is based on columns not pixels
9666 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
9669 INT nOldScrollPos, nNewScrollPos;
9670 SCROLLINFO scrollInfo;
9673 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
9674 debugscrollcode(nScrollCode), nScrollDiff);
9676 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9678 scrollInfo.cbSize = sizeof(SCROLLINFO);
9679 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
9681 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
9683 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
9685 nOldScrollPos = scrollInfo.nPos;
9687 switch (nScrollCode)
9693 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
9697 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
9701 nScrollDiff = -scrollInfo.nPage;
9705 nScrollDiff = scrollInfo.nPage;
9708 case SB_THUMBPOSITION:
9710 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
9717 /* quit right away if pos isn't changing */
9718 if (nScrollDiff == 0) return 0;
9720 /* calculate new position, and handle overflows */
9721 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
9722 if (nScrollDiff > 0) {
9723 if (nNewScrollPos < nOldScrollPos ||
9724 nNewScrollPos > scrollInfo.nMax)
9725 nNewScrollPos = scrollInfo.nMax;
9727 if (nNewScrollPos > nOldScrollPos ||
9728 nNewScrollPos < scrollInfo.nMin)
9729 nNewScrollPos = scrollInfo.nMin;
9732 /* set the new position, and reread in case it changed */
9733 scrollInfo.fMask = SIF_POS;
9734 scrollInfo.nPos = nNewScrollPos;
9735 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
9737 /* carry on only if it really changed */
9738 if (nNewScrollPos == nOldScrollPos) return 0;
9740 if (infoPtr->hwndHeader) LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
9742 /* now adjust to client coordinates */
9743 nScrollDiff = nOldScrollPos - nNewScrollPos;
9744 if (infoPtr->uView == LV_VIEW_LIST) nScrollDiff *= infoPtr->nItemWidth;
9746 /* and scroll the window */
9747 scroll_list(infoPtr, nScrollDiff, 0);
9752 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
9754 INT gcWheelDelta = 0;
9755 INT pulScrollLines = 3;
9757 TRACE("(wheelDelta=%d)\n", wheelDelta);
9759 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
9760 gcWheelDelta -= wheelDelta;
9762 switch(infoPtr->uView)
9765 case LV_VIEW_SMALLICON:
9767 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
9768 * should be fixed in the future.
9770 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
9771 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE);
9774 case LV_VIEW_DETAILS:
9775 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
9777 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
9778 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
9779 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll);
9784 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0);
9795 * [I] infoPtr : valid pointer to the listview structure
9796 * [I] nVirtualKey : virtual key
9797 * [I] lKeyData : key data
9802 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
9804 HWND hwndSelf = infoPtr->hwndSelf;
9806 NMLVKEYDOWN nmKeyDown;
9808 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
9810 /* send LVN_KEYDOWN notification */
9811 nmKeyDown.wVKey = nVirtualKey;
9812 nmKeyDown.flags = 0;
9813 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
9814 if (!IsWindow(hwndSelf))
9817 switch (nVirtualKey)
9820 nItem = infoPtr->nFocusedItem;
9821 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
9822 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
9826 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
9828 if (!notify(infoPtr, NM_RETURN)) return 0;
9829 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
9834 if (infoPtr->nItemCount > 0)
9839 if (infoPtr->nItemCount > 0)
9840 nItem = infoPtr->nItemCount - 1;
9844 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TOLEFT);
9848 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_ABOVE);
9852 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TORIGHT);
9856 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_BELOW);
9860 if (infoPtr->uView == LV_VIEW_DETAILS)
9862 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
9863 if (infoPtr->nFocusedItem == topidx)
9864 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
9869 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
9870 * LISTVIEW_GetCountPerRow(infoPtr);
9871 if(nItem < 0) nItem = 0;
9875 if (infoPtr->uView == LV_VIEW_DETAILS)
9877 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
9878 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
9879 if (infoPtr->nFocusedItem == topidx + cnt - 1)
9880 nItem = infoPtr->nFocusedItem + cnt - 1;
9882 nItem = topidx + cnt - 1;
9885 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
9886 * LISTVIEW_GetCountPerRow(infoPtr);
9887 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
9891 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
9892 LISTVIEW_KeySelection(infoPtr, nItem, nVirtualKey == VK_SPACE);
9902 * [I] infoPtr : valid pointer to the listview structure
9907 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
9911 /* if we did not have the focus, there's nothing to do */
9912 if (!infoPtr->bFocus) return 0;
9914 /* send NM_KILLFOCUS notification */
9915 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
9917 /* if we have a focus rectangle, get rid of it */
9918 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
9920 /* if have a marquee selection, stop it */
9921 if (infoPtr->bMarqueeSelect)
9923 /* Remove the marquee rectangle and release our mouse capture */
9924 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeRect);
9927 SetRect(&infoPtr->marqueeRect, 0, 0, 0, 0);
9929 infoPtr->bMarqueeSelect = FALSE;
9930 infoPtr->bScrolling = FALSE;
9931 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
9934 /* set window focus flag */
9935 infoPtr->bFocus = FALSE;
9937 /* invalidate the selected items before resetting focus flag */
9938 LISTVIEW_InvalidateSelectedItems(infoPtr);
9945 * Processes double click messages (left mouse button).
9948 * [I] infoPtr : valid pointer to the listview structure
9949 * [I] wKey : key flag
9950 * [I] x,y : mouse coordinate
9955 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9957 LVHITTESTINFO htInfo;
9959 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
9961 /* Cancel the item edition if any */
9962 if (infoPtr->itemEdit.fEnabled)
9964 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
9965 infoPtr->itemEdit.fEnabled = FALSE;
9968 /* send NM_RELEASEDCAPTURE notification */
9969 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9974 /* send NM_DBLCLK notification */
9975 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
9976 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
9978 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
9979 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
9986 * Processes mouse down messages (left mouse button).
9989 * infoPtr [I ] valid pointer to the listview structure
9990 * wKey [I ] key flag
9991 * x,y [I ] mouse coordinate
9996 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9998 LVHITTESTINFO lvHitTestInfo;
9999 static BOOL bGroupSelect = TRUE;
10000 POINT pt = { x, y };
10003 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10005 /* send NM_RELEASEDCAPTURE notification */
10006 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10008 /* set left button down flag and record the click position */
10009 infoPtr->bLButtonDown = TRUE;
10010 infoPtr->ptClickPos = pt;
10011 infoPtr->bDragging = FALSE;
10012 infoPtr->bMarqueeSelect = FALSE;
10013 infoPtr->bScrolling = FALSE;
10015 lvHitTestInfo.pt.x = x;
10016 lvHitTestInfo.pt.y = y;
10018 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
10019 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
10020 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
10022 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
10024 toggle_checkbox_state(infoPtr, nItem);
10028 if (infoPtr->dwStyle & LVS_SINGLESEL)
10030 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10031 infoPtr->nEditLabelItem = nItem;
10033 LISTVIEW_SetSelection(infoPtr, nItem);
10037 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
10041 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
10042 LISTVIEW_SetItemFocus(infoPtr, nItem);
10043 infoPtr->nSelectionMark = nItem;
10049 item.state = LVIS_SELECTED | LVIS_FOCUSED;
10050 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
10052 LISTVIEW_SetItemState(infoPtr,nItem,&item);
10053 infoPtr->nSelectionMark = nItem;
10056 else if (wKey & MK_CONTROL)
10060 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
10062 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
10063 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
10064 LISTVIEW_SetItemState(infoPtr, nItem, &item);
10065 infoPtr->nSelectionMark = nItem;
10067 else if (wKey & MK_SHIFT)
10069 LISTVIEW_SetGroupSelection(infoPtr, nItem);
10073 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10075 infoPtr->nEditLabelItem = nItem;
10076 infoPtr->nLButtonDownItem = nItem;
10078 LISTVIEW_SetItemFocus(infoPtr, nItem);
10081 /* set selection (clears other pre-existing selections) */
10082 LISTVIEW_SetSelection(infoPtr, nItem);
10086 if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
10087 if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
10091 if (!infoPtr->bFocus)
10092 SetFocus(infoPtr->hwndSelf);
10094 /* remove all selections */
10095 if (!(wKey & MK_CONTROL) && !(wKey & MK_SHIFT))
10096 LISTVIEW_DeselectAll(infoPtr);
10105 * Processes mouse up messages (left mouse button).
10108 * infoPtr [I ] valid pointer to the listview structure
10109 * wKey [I ] key flag
10110 * x,y [I ] mouse coordinate
10115 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10117 LVHITTESTINFO lvHitTestInfo;
10119 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10121 if (!infoPtr->bLButtonDown) return 0;
10123 lvHitTestInfo.pt.x = x;
10124 lvHitTestInfo.pt.y = y;
10126 /* send NM_CLICK notification */
10127 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10128 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
10130 /* set left button flag */
10131 infoPtr->bLButtonDown = FALSE;
10133 /* set a single selection, reset others */
10134 if(lvHitTestInfo.iItem == infoPtr->nLButtonDownItem && lvHitTestInfo.iItem != -1)
10135 LISTVIEW_SetSelection(infoPtr, infoPtr->nLButtonDownItem);
10136 infoPtr->nLButtonDownItem = -1;
10138 if (infoPtr->bDragging || infoPtr->bMarqueeSelect)
10140 /* Remove the marquee rectangle and release our mouse capture */
10141 if (infoPtr->bMarqueeSelect)
10143 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
10147 SetRect(&infoPtr->marqueeRect, 0, 0, 0, 0);
10148 SetRect(&infoPtr->marqueeDrawRect, 0, 0, 0, 0);
10150 infoPtr->bDragging = FALSE;
10151 infoPtr->bMarqueeSelect = FALSE;
10152 infoPtr->bScrolling = FALSE;
10154 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
10158 /* if we clicked on a selected item, edit the label */
10159 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
10161 /* we want to make sure the user doesn't want to do a double click. So we will
10162 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
10164 infoPtr->itemEdit.fEnabled = TRUE;
10165 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
10166 SetTimer(infoPtr->hwndSelf,
10167 (UINT_PTR)&infoPtr->itemEdit,
10168 GetDoubleClickTime(),
10169 LISTVIEW_DelayedEditItem);
10172 if (!infoPtr->bFocus)
10173 SetFocus(infoPtr->hwndSelf);
10180 * Destroys the listview control (called after WM_DESTROY).
10183 * [I] infoPtr : valid pointer to the listview structure
10188 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
10194 /* destroy data structure */
10195 DPA_Destroy(infoPtr->hdpaItems);
10196 DPA_Destroy(infoPtr->hdpaItemIds);
10197 DPA_Destroy(infoPtr->hdpaPosX);
10198 DPA_Destroy(infoPtr->hdpaPosY);
10200 for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++)
10201 Free(DPA_GetPtr(infoPtr->hdpaColumns, i));
10202 DPA_Destroy(infoPtr->hdpaColumns);
10203 ranges_destroy(infoPtr->selectionRanges);
10205 /* destroy image lists */
10206 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
10208 ImageList_Destroy(infoPtr->himlNormal);
10209 ImageList_Destroy(infoPtr->himlSmall);
10210 ImageList_Destroy(infoPtr->himlState);
10213 /* destroy font, bkgnd brush */
10214 infoPtr->hFont = 0;
10215 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
10216 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
10218 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
10220 /* free listview info pointer*/
10228 * Handles notifications.
10231 * [I] infoPtr : valid pointer to the listview structure
10232 * [I] lpnmhdr : notification information
10237 static LRESULT LISTVIEW_Notify(LISTVIEW_INFO *infoPtr, NMHDR *lpnmhdr)
10241 TRACE("(lpnmhdr=%p)\n", lpnmhdr);
10243 if (!lpnmhdr || lpnmhdr->hwndFrom != infoPtr->hwndHeader) return 0;
10245 /* remember: HDN_LAST < HDN_FIRST */
10246 if (lpnmhdr->code > HDN_FIRST || lpnmhdr->code < HDN_LAST) return 0;
10247 lpnmh = (NMHEADERW *)lpnmhdr;
10249 if (lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
10251 switch (lpnmhdr->code)
10256 COLUMN_INFO *lpColumnInfo;
10260 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
10263 /* remove the old line (if any) */
10264 LISTVIEW_DrawTrackLine(infoPtr);
10266 /* compute & draw the new line */
10267 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
10268 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
10269 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
10270 infoPtr->xTrackLine = x + ptOrigin.x;
10271 LISTVIEW_DrawTrackLine(infoPtr);
10272 return notify_forward_header(infoPtr, lpnmh);
10275 case HDN_ENDTRACKA:
10276 case HDN_ENDTRACKW:
10277 /* remove the track line (if any) */
10278 LISTVIEW_DrawTrackLine(infoPtr);
10279 infoPtr->xTrackLine = -1;
10280 return notify_forward_header(infoPtr, lpnmh);
10282 case HDN_BEGINDRAG:
10283 if ((infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP) == 0) return 1;
10284 return notify_forward_header(infoPtr, lpnmh);
10287 infoPtr->colRectsDirty = TRUE;
10288 LISTVIEW_InvalidateList(infoPtr);
10289 return notify_forward_header(infoPtr, lpnmh);
10291 case HDN_ITEMCHANGEDW:
10292 case HDN_ITEMCHANGEDA:
10294 COLUMN_INFO *lpColumnInfo;
10298 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
10300 hdi.mask = HDI_WIDTH;
10301 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi)) return 0;
10305 cxy = lpnmh->pitem->cxy;
10307 /* determine how much we change since the last know position */
10308 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
10309 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
10312 lpColumnInfo->rcHeader.right += dx;
10314 hdi.mask = HDI_ORDER;
10315 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi);
10317 /* not the rightmost one */
10318 if (hdi.iOrder + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
10320 INT nIndex = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
10321 hdi.iOrder + 1, 0);
10322 LISTVIEW_ScrollColumns(infoPtr, nIndex, dx);
10326 /* only needs to update the scrolls */
10327 infoPtr->nItemWidth += dx;
10328 LISTVIEW_UpdateScroll(infoPtr);
10330 LISTVIEW_UpdateItemSize(infoPtr);
10331 if (infoPtr->uView == LV_VIEW_DETAILS && is_redrawing(infoPtr))
10334 RECT rcCol = lpColumnInfo->rcHeader;
10336 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
10337 OffsetRect(&rcCol, ptOrigin.x, 0);
10339 rcCol.top = infoPtr->rcList.top;
10340 rcCol.bottom = infoPtr->rcList.bottom;
10342 /* resizing left-aligned columns leaves most of the left side untouched */
10343 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
10345 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
10348 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
10351 /* when shrinking the last column clear the now unused field */
10352 if (hdi.iOrder == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
10358 /* deal with right from rightmost column area */
10359 right.left = rcCol.right;
10360 right.top = rcCol.top;
10361 right.bottom = rcCol.bottom;
10362 right.right = infoPtr->rcList.right;
10364 LISTVIEW_InvalidateRect(infoPtr, &right);
10367 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
10373 case HDN_ITEMCLICKW:
10374 case HDN_ITEMCLICKA:
10376 /* Handle sorting by Header Column */
10379 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
10381 nmlv.iSubItem = lpnmh->iItem;
10382 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
10383 return notify_forward_header(infoPtr, lpnmh);
10386 case HDN_DIVIDERDBLCLICKW:
10387 case HDN_DIVIDERDBLCLICKA:
10388 /* FIXME: for LVS_EX_HEADERINALLVIEWS and not LV_VIEW_DETAILS
10389 we should use LVSCW_AUTOSIZE_USEHEADER, helper rework or
10390 split needed for that */
10391 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
10392 return notify_forward_header(infoPtr, lpnmh);
10399 * Paint non-client area of control.
10402 * [I] infoPtr : valid pointer to the listview structureof the sender
10403 * [I] region : update region
10406 * TRUE - frame was painted
10407 * FALSE - call default window proc
10409 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
10411 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
10415 int cxEdge = GetSystemMetrics (SM_CXEDGE),
10416 cyEdge = GetSystemMetrics (SM_CYEDGE);
10419 return DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)region, 0);
10421 GetWindowRect(infoPtr->hwndSelf, &r);
10423 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
10424 r.right - cxEdge, r.bottom - cyEdge);
10425 if (region != (HRGN)1)
10426 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
10427 OffsetRect(&r, -r.left, -r.top);
10429 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
10430 OffsetRect(&r, -r.left, -r.top);
10432 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
10433 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
10434 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
10435 ReleaseDC(infoPtr->hwndSelf, dc);
10437 /* Call default proc to get the scrollbars etc. painted */
10438 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
10445 * Determines the type of structure to use.
10448 * [I] infoPtr : valid pointer to the listview structureof the sender
10449 * [I] hwndFrom : listview window handle
10450 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
10455 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
10457 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
10459 if (nCommand == NF_REQUERY)
10460 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
10462 return infoPtr->notifyFormat;
10467 * Paints/Repaints the listview control. Internal use.
10470 * [I] infoPtr : valid pointer to the listview structure
10471 * [I] hdc : device context handle
10476 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
10478 TRACE("(hdc=%p)\n", hdc);
10480 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
10482 infoPtr->bNoItemMetrics = FALSE;
10483 LISTVIEW_UpdateItemSize(infoPtr);
10484 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
10485 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10486 LISTVIEW_UpdateScroll(infoPtr);
10489 if (infoPtr->hwndHeader) UpdateWindow(infoPtr->hwndHeader);
10492 LISTVIEW_Refresh(infoPtr, hdc, NULL);
10497 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
10498 if (!hdc) return 1;
10499 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
10500 EndPaint(infoPtr->hwndSelf, &ps);
10508 * Paints/Repaints the listview control, WM_PAINT handler.
10511 * [I] infoPtr : valid pointer to the listview structure
10512 * [I] hdc : device context handle
10517 static inline LRESULT LISTVIEW_WMPaint(LISTVIEW_INFO *infoPtr, HDC hdc)
10519 TRACE("(hdc=%p)\n", hdc);
10521 if (!is_redrawing(infoPtr))
10522 return DefWindowProcW (infoPtr->hwndSelf, WM_PAINT, (WPARAM)hdc, 0);
10524 return LISTVIEW_Paint(infoPtr, hdc);
10529 * Paints/Repaints the listview control.
10532 * [I] infoPtr : valid pointer to the listview structure
10533 * [I] hdc : device context handle
10534 * [I] options : drawing options
10539 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
10541 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
10543 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
10546 if (options & PRF_ERASEBKGND)
10547 LISTVIEW_EraseBkgnd(infoPtr, hdc);
10549 if (options & PRF_CLIENT)
10550 LISTVIEW_Paint(infoPtr, hdc);
10558 * Processes double click messages (right mouse button).
10561 * [I] infoPtr : valid pointer to the listview structure
10562 * [I] wKey : key flag
10563 * [I] x,y : mouse coordinate
10568 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10570 LVHITTESTINFO lvHitTestInfo;
10572 TRACE("(key=%hu,X=%u,Y=%u)\n", wKey, x, y);
10574 /* send NM_RELEASEDCAPTURE notification */
10575 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10577 /* send NM_RDBLCLK notification */
10578 lvHitTestInfo.pt.x = x;
10579 lvHitTestInfo.pt.y = y;
10580 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10581 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
10588 * Processes mouse down messages (right mouse button).
10591 * [I] infoPtr : valid pointer to the listview structure
10592 * [I] wKey : key flag
10593 * [I] x,y : mouse coordinate
10598 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10600 LVHITTESTINFO lvHitTestInfo;
10603 TRACE("(key=%hu,X=%u,Y=%u)\n", wKey, x, y);
10605 /* send NM_RELEASEDCAPTURE notification */
10606 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10608 /* make sure the listview control window has the focus */
10609 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
10611 /* set right button down flag */
10612 infoPtr->bRButtonDown = TRUE;
10614 /* determine the index of the selected item */
10615 lvHitTestInfo.pt.x = x;
10616 lvHitTestInfo.pt.y = y;
10617 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
10619 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
10621 LISTVIEW_SetItemFocus(infoPtr, nItem);
10622 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
10623 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10624 LISTVIEW_SetSelection(infoPtr, nItem);
10628 LISTVIEW_DeselectAll(infoPtr);
10636 * Processes mouse up messages (right mouse button).
10639 * [I] infoPtr : valid pointer to the listview structure
10640 * [I] wKey : key flag
10641 * [I] x,y : mouse coordinate
10646 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10648 LVHITTESTINFO lvHitTestInfo;
10651 TRACE("(key=%hu,X=%u,Y=%u)\n", wKey, x, y);
10653 if (!infoPtr->bRButtonDown) return 0;
10655 /* set button flag */
10656 infoPtr->bRButtonDown = FALSE;
10658 /* Send NM_RCLICK notification */
10659 lvHitTestInfo.pt.x = x;
10660 lvHitTestInfo.pt.y = y;
10661 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10662 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
10664 /* Change to screen coordinate for WM_CONTEXTMENU */
10665 pt = lvHitTestInfo.pt;
10666 ClientToScreen(infoPtr->hwndSelf, &pt);
10668 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
10669 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
10670 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
10681 * [I] infoPtr : valid pointer to the listview structure
10682 * [I] hwnd : window handle of window containing the cursor
10683 * [I] nHittest : hit-test code
10684 * [I] wMouseMsg : ideintifier of the mouse message
10687 * TRUE if cursor is set
10690 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10692 LVHITTESTINFO lvHitTestInfo;
10694 if (!LISTVIEW_IsHotTracking(infoPtr)) goto forward;
10696 if (!infoPtr->hHotCursor) goto forward;
10698 GetCursorPos(&lvHitTestInfo.pt);
10699 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) goto forward;
10701 SetCursor(infoPtr->hHotCursor);
10707 return DefWindowProcW(infoPtr->hwndSelf, WM_SETCURSOR, wParam, lParam);
10715 * [I] infoPtr : valid pointer to the listview structure
10716 * [I] hwndLoseFocus : handle of previously focused window
10721 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
10723 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
10725 /* if we have the focus already, there's nothing to do */
10726 if (infoPtr->bFocus) return 0;
10728 /* send NM_SETFOCUS notification */
10729 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
10731 /* set window focus flag */
10732 infoPtr->bFocus = TRUE;
10734 /* put the focus rect back on */
10735 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
10737 /* redraw all visible selected items */
10738 LISTVIEW_InvalidateSelectedItems(infoPtr);
10748 * [I] infoPtr : valid pointer to the listview structure
10749 * [I] fRedraw : font handle
10750 * [I] fRedraw : redraw flag
10755 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
10757 HFONT oldFont = infoPtr->hFont;
10759 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
10761 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
10762 if (infoPtr->hFont == oldFont) return 0;
10764 LISTVIEW_SaveTextMetrics(infoPtr);
10766 if (infoPtr->uView == LV_VIEW_DETAILS)
10768 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
10769 LISTVIEW_UpdateSize(infoPtr);
10770 LISTVIEW_UpdateScroll(infoPtr);
10773 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
10780 * Message handling for WM_SETREDRAW.
10781 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
10784 * [I] infoPtr : valid pointer to the listview structure
10785 * [I] bRedraw: state of redraw flag
10788 * DefWinProc return value
10790 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
10792 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
10794 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
10795 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
10797 infoPtr->bRedraw = bRedraw;
10799 if(!bRedraw) return 0;
10801 if (is_autoarrange(infoPtr))
10802 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10803 LISTVIEW_UpdateScroll(infoPtr);
10805 /* despite what the WM_SETREDRAW docs says, apps expect us
10806 * to invalidate the listview here... stupid! */
10807 LISTVIEW_InvalidateList(infoPtr);
10814 * Resizes the listview control. This function processes WM_SIZE
10815 * messages. At this time, the width and height are not used.
10818 * [I] infoPtr : valid pointer to the listview structure
10819 * [I] Width : new width
10820 * [I] Height : new height
10825 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
10827 RECT rcOld = infoPtr->rcList;
10829 TRACE("(width=%d, height=%d)\n", Width, Height);
10831 LISTVIEW_UpdateSize(infoPtr);
10832 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
10834 /* do not bother with display related stuff if we're not redrawing */
10835 if (!is_redrawing(infoPtr)) return 0;
10837 if (is_autoarrange(infoPtr))
10838 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10840 LISTVIEW_UpdateScroll(infoPtr);
10842 /* refresh all only for lists whose height changed significantly */
10843 if ((infoPtr->uView == LV_VIEW_LIST) &&
10844 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
10845 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
10846 LISTVIEW_InvalidateList(infoPtr);
10853 * Sets the size information.
10856 * [I] infoPtr : valid pointer to the listview structure
10861 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
10863 TRACE("uView=%d, rcList(old)=%s\n", infoPtr->uView, wine_dbgstr_rect(&infoPtr->rcList));
10865 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
10867 if (infoPtr->uView == LV_VIEW_LIST)
10869 /* Apparently the "LIST" style is supposed to have the same
10870 * number of items in a column even if there is no scroll bar.
10871 * Since if a scroll bar already exists then the bottom is already
10872 * reduced, only reduce if the scroll bar does not currently exist.
10873 * The "2" is there to mimic the native control. I think it may be
10874 * related to either padding or edges. (GLA 7/2002)
10876 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
10877 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
10878 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
10881 /* if control created invisible header isn't created */
10882 if (infoPtr->hwndHeader)
10887 hl.prc = &infoPtr->rcList;
10889 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
10890 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
10892 if (LISTVIEW_IsHeaderEnabled(infoPtr))
10893 wp.flags |= SWP_SHOWWINDOW;
10896 wp.flags |= SWP_HIDEWINDOW;
10900 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
10901 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
10903 infoPtr->rcList.top = max(wp.cy, 0);
10905 /* extra padding for grid */
10906 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
10907 infoPtr->rcList.top += 2;
10909 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
10914 * Processes WM_STYLECHANGED messages.
10917 * [I] infoPtr : valid pointer to the listview structure
10918 * [I] wStyleType : window style type (normal or extended)
10919 * [I] lpss : window style information
10924 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
10925 const STYLESTRUCT *lpss)
10927 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
10928 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
10931 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
10932 wStyleType, lpss->styleOld, lpss->styleNew);
10934 if (wStyleType != GWL_STYLE) return 0;
10936 infoPtr->dwStyle = lpss->styleNew;
10937 map_style_view(infoPtr);
10939 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
10940 ((lpss->styleNew & WS_HSCROLL) == 0))
10941 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
10943 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
10944 ((lpss->styleNew & WS_VSCROLL) == 0))
10945 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
10947 if (uNewView != uOldView)
10949 SIZE oldIconSize = infoPtr->iconSize;
10952 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
10953 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
10955 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
10956 SetRectEmpty(&infoPtr->rcFocus);
10958 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
10959 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
10961 if (uNewView == LVS_ICON)
10963 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
10965 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
10966 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
10967 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
10970 else if (uNewView == LVS_REPORT)
10975 LISTVIEW_CreateHeader( infoPtr );
10977 hl.prc = &infoPtr->rcList;
10979 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
10980 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
10981 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
10982 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
10985 LISTVIEW_UpdateItemSize(infoPtr);
10988 if (uNewView == LVS_REPORT || infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS)
10990 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
10992 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
10994 /* Turn off the header control */
10995 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
10996 TRACE("Hide header control, was 0x%08x\n", style);
10997 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
10999 /* Turn on the header control */
11000 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
11002 TRACE("Show header control, was 0x%08x\n", style);
11003 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
11009 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
11010 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
11011 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
11013 /* update the size of the client area */
11014 LISTVIEW_UpdateSize(infoPtr);
11016 /* add scrollbars if needed */
11017 LISTVIEW_UpdateScroll(infoPtr);
11019 /* invalidate client area + erase background */
11020 LISTVIEW_InvalidateList(infoPtr);
11027 * Processes WM_STYLECHANGING messages.
11030 * [I] wStyleType : window style type (normal or extended)
11031 * [I0] lpss : window style information
11036 static INT LISTVIEW_StyleChanging(WPARAM wStyleType,
11039 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
11040 wStyleType, lpss->styleOld, lpss->styleNew);
11042 /* don't forward LVS_OWNERDATA only if not already set to */
11043 if ((lpss->styleNew ^ lpss->styleOld) & LVS_OWNERDATA)
11045 if (lpss->styleOld & LVS_OWNERDATA)
11046 lpss->styleNew |= LVS_OWNERDATA;
11048 lpss->styleNew &= ~LVS_OWNERDATA;
11056 * Processes WM_SHOWWINDOW messages.
11059 * [I] infoPtr : valid pointer to the listview structure
11060 * [I] bShown : window is being shown (FALSE when hidden)
11061 * [I] iStatus : window show status
11066 static LRESULT LISTVIEW_ShowWindow(LISTVIEW_INFO *infoPtr, WPARAM bShown, LPARAM iStatus)
11068 /* header delayed creation */
11069 if ((infoPtr->uView == LV_VIEW_DETAILS) && bShown)
11071 LISTVIEW_CreateHeader(infoPtr);
11073 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle))
11074 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
11077 return DefWindowProcW(infoPtr->hwndSelf, WM_SHOWWINDOW, bShown, iStatus);
11082 * Processes CCM_GETVERSION messages.
11085 * [I] infoPtr : valid pointer to the listview structure
11090 static inline LRESULT LISTVIEW_GetVersion(const LISTVIEW_INFO *infoPtr)
11092 return infoPtr->iVersion;
11097 * Processes CCM_SETVERSION messages.
11100 * [I] infoPtr : valid pointer to the listview structure
11101 * [I] iVersion : version to be set
11104 * -1 when requested version is greater than DLL version;
11105 * previous version otherwise
11107 static LRESULT LISTVIEW_SetVersion(LISTVIEW_INFO *infoPtr, DWORD iVersion)
11109 INT iOldVersion = infoPtr->iVersion;
11111 if (iVersion > COMCTL32_VERSION)
11114 infoPtr->iVersion = iVersion;
11116 TRACE("new version %d\n", iVersion);
11118 return iOldVersion;
11123 * Window procedure of the listview control.
11126 static LRESULT WINAPI
11127 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
11129 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
11131 TRACE("(hwnd=%p uMsg=%x wParam=%lx lParam=%lx)\n", hwnd, uMsg, wParam, lParam);
11133 if (!infoPtr && (uMsg != WM_NCCREATE))
11134 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11138 case LVM_APPROXIMATEVIEWRECT:
11139 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
11140 LOWORD(lParam), HIWORD(lParam));
11142 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
11144 case LVM_CANCELEDITLABEL:
11145 return LISTVIEW_CancelEditLabel(infoPtr);
11147 case LVM_CREATEDRAGIMAGE:
11148 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
11150 case LVM_DELETEALLITEMS:
11151 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
11153 case LVM_DELETECOLUMN:
11154 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
11156 case LVM_DELETEITEM:
11157 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
11159 case LVM_EDITLABELA:
11160 case LVM_EDITLABELW:
11161 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam,
11162 uMsg == LVM_EDITLABELW);
11163 /* case LVM_ENABLEGROUPVIEW: */
11165 case LVM_ENSUREVISIBLE:
11166 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
11168 case LVM_FINDITEMW:
11169 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
11171 case LVM_FINDITEMA:
11172 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
11174 case LVM_GETBKCOLOR:
11175 return infoPtr->clrBk;
11177 /* case LVM_GETBKIMAGE: */
11179 case LVM_GETCALLBACKMASK:
11180 return infoPtr->uCallbackMask;
11182 case LVM_GETCOLUMNA:
11183 case LVM_GETCOLUMNW:
11184 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11185 uMsg == LVM_GETCOLUMNW);
11187 case LVM_GETCOLUMNORDERARRAY:
11188 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
11190 case LVM_GETCOLUMNWIDTH:
11191 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
11193 case LVM_GETCOUNTPERPAGE:
11194 return LISTVIEW_GetCountPerPage(infoPtr);
11196 case LVM_GETEDITCONTROL:
11197 return (LRESULT)infoPtr->hwndEdit;
11199 case LVM_GETEXTENDEDLISTVIEWSTYLE:
11200 return infoPtr->dwLvExStyle;
11202 /* case LVM_GETGROUPINFO: */
11204 /* case LVM_GETGROUPMETRICS: */
11206 case LVM_GETHEADER:
11207 return (LRESULT)infoPtr->hwndHeader;
11209 case LVM_GETHOTCURSOR:
11210 return (LRESULT)infoPtr->hHotCursor;
11212 case LVM_GETHOTITEM:
11213 return infoPtr->nHotItem;
11215 case LVM_GETHOVERTIME:
11216 return infoPtr->dwHoverTime;
11218 case LVM_GETIMAGELIST:
11219 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
11221 /* case LVM_GETINSERTMARK: */
11223 /* case LVM_GETINSERTMARKCOLOR: */
11225 /* case LVM_GETINSERTMARKRECT: */
11227 case LVM_GETISEARCHSTRINGA:
11228 case LVM_GETISEARCHSTRINGW:
11229 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
11234 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, uMsg == LVM_GETITEMW);
11236 case LVM_GETITEMCOUNT:
11237 return infoPtr->nItemCount;
11239 case LVM_GETITEMPOSITION:
11240 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
11242 case LVM_GETITEMRECT:
11243 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
11245 case LVM_GETITEMSPACING:
11246 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
11248 case LVM_GETITEMSTATE:
11249 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
11251 case LVM_GETITEMTEXTA:
11252 case LVM_GETITEMTEXTW:
11253 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam,
11254 uMsg == LVM_GETITEMTEXTW);
11256 case LVM_GETNEXTITEM:
11257 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
11259 case LVM_GETNUMBEROFWORKAREAS:
11260 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
11263 case LVM_GETORIGIN:
11264 if (!lParam) return FALSE;
11265 if (infoPtr->uView == LV_VIEW_DETAILS ||
11266 infoPtr->uView == LV_VIEW_LIST) return FALSE;
11267 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
11270 /* case LVM_GETOUTLINECOLOR: */
11272 /* case LVM_GETSELECTEDCOLUMN: */
11274 case LVM_GETSELECTEDCOUNT:
11275 return LISTVIEW_GetSelectedCount(infoPtr);
11277 case LVM_GETSELECTIONMARK:
11278 return infoPtr->nSelectionMark;
11280 case LVM_GETSTRINGWIDTHA:
11281 case LVM_GETSTRINGWIDTHW:
11282 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam,
11283 uMsg == LVM_GETSTRINGWIDTHW);
11285 case LVM_GETSUBITEMRECT:
11286 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
11288 case LVM_GETTEXTBKCOLOR:
11289 return infoPtr->clrTextBk;
11291 case LVM_GETTEXTCOLOR:
11292 return infoPtr->clrText;
11294 /* case LVM_GETTILEINFO: */
11296 /* case LVM_GETTILEVIEWINFO: */
11298 case LVM_GETTOOLTIPS:
11299 if( !infoPtr->hwndToolTip )
11300 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
11301 return (LRESULT)infoPtr->hwndToolTip;
11303 case LVM_GETTOPINDEX:
11304 return LISTVIEW_GetTopIndex(infoPtr);
11306 case LVM_GETUNICODEFORMAT:
11307 return (infoPtr->notifyFormat == NFR_UNICODE);
11310 return infoPtr->uView;
11312 case LVM_GETVIEWRECT:
11313 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
11315 case LVM_GETWORKAREAS:
11316 FIXME("LVM_GETWORKAREAS: unimplemented\n");
11319 /* case LVM_HASGROUP: */
11322 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, TRUE);
11324 case LVM_INSERTCOLUMNA:
11325 case LVM_INSERTCOLUMNW:
11326 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11327 uMsg == LVM_INSERTCOLUMNW);
11329 /* case LVM_INSERTGROUP: */
11331 /* case LVM_INSERTGROUPSORTED: */
11333 case LVM_INSERTITEMA:
11334 case LVM_INSERTITEMW:
11335 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, uMsg == LVM_INSERTITEMW);
11337 /* case LVM_INSERTMARKHITTEST: */
11339 /* case LVM_ISGROUPVIEWENABLED: */
11341 case LVM_ISITEMVISIBLE:
11342 return LISTVIEW_IsItemVisible(infoPtr, (INT)wParam);
11344 case LVM_MAPIDTOINDEX:
11345 return LISTVIEW_MapIdToIndex(infoPtr, (UINT)wParam);
11347 case LVM_MAPINDEXTOID:
11348 return LISTVIEW_MapIndexToId(infoPtr, (INT)wParam);
11350 /* case LVM_MOVEGROUP: */
11352 /* case LVM_MOVEITEMTOGROUP: */
11354 case LVM_REDRAWITEMS:
11355 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
11357 /* case LVM_REMOVEALLGROUPS: */
11359 /* case LVM_REMOVEGROUP: */
11362 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
11364 case LVM_SETBKCOLOR:
11365 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
11367 /* case LVM_SETBKIMAGE: */
11369 case LVM_SETCALLBACKMASK:
11370 infoPtr->uCallbackMask = (UINT)wParam;
11373 case LVM_SETCOLUMNA:
11374 case LVM_SETCOLUMNW:
11375 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11376 uMsg == LVM_SETCOLUMNW);
11378 case LVM_SETCOLUMNORDERARRAY:
11379 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
11381 case LVM_SETCOLUMNWIDTH:
11382 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
11384 case LVM_SETEXTENDEDLISTVIEWSTYLE:
11385 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
11387 /* case LVM_SETGROUPINFO: */
11389 /* case LVM_SETGROUPMETRICS: */
11391 case LVM_SETHOTCURSOR:
11392 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
11394 case LVM_SETHOTITEM:
11395 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
11397 case LVM_SETHOVERTIME:
11398 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)lParam);
11400 case LVM_SETICONSPACING:
11401 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
11403 case LVM_SETIMAGELIST:
11404 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
11406 /* case LVM_SETINFOTIP: */
11408 /* case LVM_SETINSERTMARK: */
11410 /* case LVM_SETINSERTMARKCOLOR: */
11415 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
11416 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, (uMsg == LVM_SETITEMW));
11419 case LVM_SETITEMCOUNT:
11420 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
11422 case LVM_SETITEMPOSITION:
11425 pt.x = (short)LOWORD(lParam);
11426 pt.y = (short)HIWORD(lParam);
11427 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, &pt);
11430 case LVM_SETITEMPOSITION32:
11431 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, (POINT*)lParam);
11433 case LVM_SETITEMSTATE:
11434 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
11436 case LVM_SETITEMTEXTA:
11437 case LVM_SETITEMTEXTW:
11438 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam,
11439 uMsg == LVM_SETITEMTEXTW);
11441 /* case LVM_SETOUTLINECOLOR: */
11443 /* case LVM_SETSELECTEDCOLUMN: */
11445 case LVM_SETSELECTIONMARK:
11446 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
11448 case LVM_SETTEXTBKCOLOR:
11449 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
11451 case LVM_SETTEXTCOLOR:
11452 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
11454 /* case LVM_SETTILEINFO: */
11456 /* case LVM_SETTILEVIEWINFO: */
11458 /* case LVM_SETTILEWIDTH: */
11460 case LVM_SETTOOLTIPS:
11461 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
11463 case LVM_SETUNICODEFORMAT:
11464 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
11467 return LISTVIEW_SetView(infoPtr, wParam);
11469 /* case LVM_SETWORKAREAS: */
11471 /* case LVM_SORTGROUPS: */
11473 case LVM_SORTITEMS:
11474 case LVM_SORTITEMSEX:
11475 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, wParam,
11476 uMsg == LVM_SORTITEMSEX);
11477 case LVM_SUBITEMHITTEST:
11478 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
11481 return LISTVIEW_Update(infoPtr, (INT)wParam);
11483 case CCM_GETVERSION:
11484 return LISTVIEW_GetVersion(infoPtr);
11486 case CCM_SETVERSION:
11487 return LISTVIEW_SetVersion(infoPtr, wParam);
11490 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
11493 return LISTVIEW_Command(infoPtr, wParam, lParam);
11496 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
11499 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
11502 return LISTVIEW_Destroy(infoPtr);
11505 return LISTVIEW_Enable(infoPtr);
11507 case WM_ERASEBKGND:
11508 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
11510 case WM_GETDLGCODE:
11511 return DLGC_WANTCHARS | DLGC_WANTARROWS;
11514 return (LRESULT)infoPtr->hFont;
11517 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0);
11520 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
11523 return LISTVIEW_KillFocus(infoPtr);
11525 case WM_LBUTTONDBLCLK:
11526 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11528 case WM_LBUTTONDOWN:
11529 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11532 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11535 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11537 case WM_MOUSEHOVER:
11538 return LISTVIEW_MouseHover(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11541 return LISTVIEW_NCDestroy(infoPtr);
11544 return LISTVIEW_NCPaint(infoPtr, (HRGN)wParam);
11547 return LISTVIEW_Notify(infoPtr, (LPNMHDR)lParam);
11549 case WM_NOTIFYFORMAT:
11550 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
11552 case WM_PRINTCLIENT:
11553 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
11556 return LISTVIEW_WMPaint(infoPtr, (HDC)wParam);
11558 case WM_RBUTTONDBLCLK:
11559 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11561 case WM_RBUTTONDOWN:
11562 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11565 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11568 return LISTVIEW_SetCursor(infoPtr, wParam, lParam);
11571 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
11574 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
11577 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
11579 case WM_SHOWWINDOW:
11580 return LISTVIEW_ShowWindow(infoPtr, wParam, lParam);
11582 case WM_STYLECHANGED:
11583 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
11585 case WM_STYLECHANGING:
11586 return LISTVIEW_StyleChanging(wParam, (LPSTYLESTRUCT)lParam);
11588 case WM_SYSCOLORCHANGE:
11589 COMCTL32_RefreshSysColors();
11592 /* case WM_TIMER: */
11593 case WM_THEMECHANGED:
11594 return LISTVIEW_ThemeChanged(infoPtr);
11597 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0);
11599 case WM_MOUSEWHEEL:
11600 if (wParam & (MK_SHIFT | MK_CONTROL))
11601 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11602 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
11604 case WM_WINDOWPOSCHANGED:
11605 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
11607 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
11608 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
11610 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
11612 if (notify_measureitem(infoPtr)) LISTVIEW_InvalidateList(infoPtr);
11614 LISTVIEW_Size(infoPtr, ((WINDOWPOS *)lParam)->cx, ((WINDOWPOS *)lParam)->cy);
11616 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11618 /* case WM_WININICHANGE: */
11621 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
11622 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
11624 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11631 * Registers the window class.
11639 void LISTVIEW_Register(void)
11641 WNDCLASSW wndClass;
11643 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
11644 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
11645 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
11646 wndClass.cbClsExtra = 0;
11647 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
11648 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
11649 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
11650 wndClass.lpszClassName = WC_LISTVIEWW;
11651 RegisterClassW(&wndClass);
11656 * Unregisters the window class.
11664 void LISTVIEW_Unregister(void)
11666 UnregisterClassW(WC_LISTVIEWW, NULL);
11671 * Handle any WM_COMMAND messages
11674 * [I] infoPtr : valid pointer to the listview structure
11675 * [I] wParam : the first message parameter
11676 * [I] lParam : the second message parameter
11681 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
11684 TRACE("(%p %x %x %lx)\n", infoPtr, HIWORD(wParam), LOWORD(wParam), lParam);
11686 if (!infoPtr->hwndEdit) return 0;
11688 switch (HIWORD(wParam))
11693 * Adjust the edit window size
11695 WCHAR buffer[1024];
11696 HDC hdc = GetDC(infoPtr->hwndEdit);
11697 HFONT hFont, hOldFont = 0;
11701 if (!infoPtr->hwndEdit || !hdc) return 0;
11702 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
11703 GetWindowRect(infoPtr->hwndEdit, &rect);
11705 /* Select font to get the right dimension of the string */
11706 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
11709 hOldFont = SelectObject(hdc, hFont);
11712 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
11714 TEXTMETRICW textMetric;
11716 /* Add Extra spacing for the next character */
11717 GetTextMetricsW(hdc, &textMetric);
11718 sz.cx += (textMetric.tmMaxCharWidth * 2);
11720 SetWindowPos(infoPtr->hwndEdit, NULL, 0, 0, sz.cx,
11721 rect.bottom - rect.top, SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOZORDER);
11724 SelectObject(hdc, hOldFont);
11726 ReleaseDC(infoPtr->hwndEdit, hdc);
11732 LISTVIEW_CancelEditLabel(infoPtr);
11737 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);